29 #include "absl/container/flat_hash_map.h"
30 #include "absl/container/flat_hash_set.h"
31 #include "absl/random/random.h"
32 #include "absl/strings/str_join.h"
58 bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
66 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
68 int new_num_constraints = 0;
69 const int old_num_non_empty_constraints =
71 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
72 const auto type = context_->
working_model->constraints(c).constraint_case();
73 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
74 if (type == ConstraintProto::ConstraintCase::kInterval) {
75 interval_mapping[c] = new_num_constraints;
77 context_->
working_model->mutable_constraints(new_num_constraints++)
80 context_->
working_model->mutable_constraints()->DeleteSubrange(
81 new_num_constraints, old_num_non_empty_constraints - new_num_constraints);
82 for (ConstraintProto& ct_ref :
85 [&interval_mapping](
int* ref) {
86 *ref = interval_mapping[*ref];
93 bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
98 const int old_size =
ct->enforcement_literal().size();
99 for (
const int literal :
ct->enforcement_literal()) {
108 return RemoveConstraint(
ct);
115 return RemoveConstraint(
ct);
122 const int64 obj_coeff =
126 context_->
UpdateRuleStats(
"enforcement literal with unique direction");
128 return RemoveConstraint(
ct);
132 ct->set_enforcement_literal(new_size++,
literal);
134 ct->mutable_enforcement_literal()->Truncate(new_size);
135 return new_size != old_size;
138 bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
143 bool changed =
false;
144 int num_true_literals = 0;
146 for (
const int literal :
ct->bool_xor().literals()) {
167 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
171 }
else if (new_size == 2) {
174 if (num_true_literals % 2 == 1) {
176 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
178 if (num_true_literals > 1) {
179 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
182 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
186 bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
193 for (
const int literal :
ct->enforcement_literal()) {
196 ct->clear_enforcement_literal();
200 bool changed =
false;
203 for (
const int literal :
ct->bool_or().literals()) {
210 return RemoveConstraint(
ct);
218 return RemoveConstraint(
ct);
222 return RemoveConstraint(
ct);
241 return RemoveConstraint(
ct);
254 ct->mutable_bool_or()->mutable_literals()->Clear();
256 ct->mutable_bool_or()->add_literals(lit);
262 ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
263 ConstraintProto*
ct) {
266 ct->mutable_bool_or()->clear_literals();
267 for (
const int lit :
ct->enforcement_literal()) {
270 ct->clear_enforcement_literal();
278 bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
283 for (
const int literal :
ct->bool_and().literals()) {
286 return RemoveConstraint(
ct);
289 bool changed =
false;
291 for (
const int literal :
ct->bool_and().literals()) {
294 return MarkConstraintAsFalse(
ct);
314 ct->mutable_bool_and()->mutable_literals()->Clear();
316 ct->mutable_bool_and()->add_literals(lit);
325 if (
ct->enforcement_literal().size() == 1 &&
326 ct->bool_and().literals().size() == 1) {
327 const int enforcement =
ct->enforcement_literal(0);
337 ct->bool_and().literals(0));
345 bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto*
ct) {
346 const bool is_at_most_one =
347 ct->constraint_case() == ConstraintProto::kAtMostOne;
348 const std::string
name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
349 auto* literals = is_at_most_one
350 ?
ct->mutable_at_most_one()->mutable_literals()
351 :
ct->mutable_exactly_one()->mutable_literals();
355 for (
const int literal : *literals) {
361 int num_positive = 0;
362 int num_negative = 0;
363 for (
const int other : *literals) {
384 return RemoveConstraint(
ct);
390 bool changed =
false;
392 for (
const int literal : *literals) {
395 for (
const int other : *literals) {
400 return RemoveConstraint(
ct);
428 bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
431 const bool changed = PresolveAtMostOrExactlyOne(
ct);
432 if (
ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
435 const auto& literals =
ct->at_most_one().literals();
436 if (literals.empty()) {
438 return RemoveConstraint(
ct);
442 if (literals.size() == 1) {
444 return RemoveConstraint(
ct);
450 bool CpModelPresolver::PresolveExactlyOne(ConstraintProto*
ct) {
453 const bool changed = PresolveAtMostOrExactlyOne(
ct);
454 if (
ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
457 const auto& literals =
ct->exactly_one().literals();
458 if (literals.empty()) {
463 if (literals.size() == 1) {
466 return RemoveConstraint(
ct);
470 if (literals.size() == 2) {
474 return RemoveConstraint(
ct);
480 bool CpModelPresolver::PresolveIntMax(ConstraintProto*
ct) {
482 if (
ct->int_max().vars().empty()) {
484 return MarkConstraintAsFalse(
ct);
486 const int target_ref =
ct->int_max().target();
491 bool contains_target_ref =
false;
492 std::set<int> used_ref;
494 for (
const int ref :
ct->int_max().vars()) {
495 if (ref == target_ref) contains_target_ref =
true;
501 used_ref.insert(ref);
502 ct->mutable_int_max()->set_vars(new_size++, ref);
506 if (new_size < ct->int_max().vars_size()) {
509 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
510 if (contains_target_ref) {
512 for (
const int ref :
ct->int_max().vars()) {
513 if (ref == target_ref)
continue;
514 ConstraintProto* new_ct = context_->
working_model->add_constraints();
515 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
516 auto* arg = new_ct->mutable_linear();
517 arg->add_vars(target_ref);
524 return RemoveConstraint(
ct);
528 Domain infered_domain;
529 for (
const int ref :
ct->int_max().vars()) {
530 infered_domain = infered_domain.UnionWith(
535 bool domain_reduced =
false;
547 const Domain& target_domain = context_->
DomainOf(target_ref);
548 if (infered_domain.IntersectionWith(Domain(
kint64min, target_domain.Max()))
549 .IsIncludedIn(target_domain)) {
550 if (infered_domain.Max() <= target_domain.Max()) {
553 }
else if (
ct->enforcement_literal().empty()) {
555 for (
const int ref :
ct->int_max().vars()) {
558 ref, Domain(
kint64min, target_domain.Max()))) {
566 for (
const int ref :
ct->int_max().vars()) {
567 ConstraintProto* new_ct = context_->
working_model->add_constraints();
568 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
569 ct->mutable_linear()->add_vars(ref);
570 ct->mutable_linear()->add_coeffs(1);
572 ct->mutable_linear()->add_domain(target_domain.Max());
579 return RemoveConstraint(
ct);
585 const int size =
ct->int_max().vars_size();
586 const int64 target_max = context_->
MaxOf(target_ref);
587 for (
const int ref :
ct->int_max().vars()) {
594 if (context_->
MaxOf(ref) >= infered_min) {
595 ct->mutable_int_max()->set_vars(new_size++, ref);
598 if (domain_reduced) {
602 bool modified =
false;
603 if (new_size < size) {
605 ct->mutable_int_max()->mutable_vars()->Truncate(new_size);
611 return MarkConstraintAsFalse(
ct);
617 ConstraintProto* new_ct = context_->
working_model->add_constraints();
619 auto* arg = new_ct->mutable_linear();
620 arg->add_vars(target_ref);
622 arg->add_vars(
ct->int_max().vars(0));
627 return RemoveConstraint(
ct);
632 bool CpModelPresolver::PresolveLinMin(ConstraintProto*
ct) {
635 const auto copy =
ct->lin_min();
637 ct->mutable_lin_max()->mutable_target());
638 for (
const LinearExpressionProto& expr : copy.exprs()) {
639 LinearExpressionProto*
const new_expr =
ct->mutable_lin_max()->add_exprs();
642 return PresolveLinMax(
ct);
645 bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
647 if (
ct->lin_max().exprs().empty()) {
649 return MarkConstraintAsFalse(
ct);
656 bool changed = CanonicalizeLinearExpression(
657 *
ct,
ct->mutable_lin_max()->mutable_target());
658 for (LinearExpressionProto& exp : *(
ct->mutable_lin_max()->mutable_exprs())) {
659 changed |= CanonicalizeLinearExpression(*
ct, &exp);
665 int64 infered_min = context_->
MinOf(
ct->lin_max().target());
666 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
677 for (
int i = 0; i <
ct->lin_max().exprs_size(); ++i) {
678 const LinearExpressionProto& expr =
ct->lin_max().exprs(i);
679 if (context_->
MaxOf(expr) >= infered_min) {
680 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
685 if (new_size < ct->lin_max().exprs_size()) {
687 ct->mutable_lin_max()->mutable_exprs()->DeleteSubrange(
688 new_size,
ct->lin_max().exprs_size() - new_size);
695 bool CpModelPresolver::PresolveIntAbs(ConstraintProto*
ct) {
698 const int target_ref =
ct->int_max().target();
703 const Domain new_target_domain = var_domain.
UnionWith(var_domain.Negation())
713 const Domain target_domain = context_->
DomainOf(target_ref);
714 const Domain new_var_domain =
715 target_domain.
UnionWith(target_domain.Negation());
725 ConstraintProto* new_ct = context_->
working_model->add_constraints();
726 new_ct->set_name(
ct->name());
727 auto* arg = new_ct->mutable_linear();
728 arg->add_vars(target_ref);
735 return RemoveConstraint(
ct);
740 ConstraintProto* new_ct = context_->
working_model->add_constraints();
741 new_ct->set_name(
ct->name());
742 auto* arg = new_ct->mutable_linear();
743 arg->add_vars(target_ref);
750 return RemoveConstraint(
ct);
756 context_->
IsFixed(target_ref)) {
757 if (!context_->
IsFixed(target_ref)) {
762 return RemoveConstraint(
ct);
772 bool CpModelPresolver::PresolveIntMin(ConstraintProto*
ct) {
775 const auto copy =
ct->int_min();
776 ct->mutable_int_max()->set_target(
NegatedRef(copy.target()));
777 for (
const int ref : copy.vars()) {
780 return PresolveIntMax(
ct);
783 bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
787 if (
ct->int_prod().vars().empty()) {
792 return RemoveConstraint(
ct);
794 bool changed =
false;
799 for (
int i = 0; i <
ct->int_prod().vars().size(); ++i) {
800 const int ref =
ct->int_prod().vars(i);
802 if (r.representative != ref && r.offset == 0) {
804 ct->mutable_int_prod()->set_vars(i, r.representative);
819 const int old_target =
ct->int_prod().target();
820 const int new_target = context_->
working_model->variables_size();
822 IntegerVariableProto* var_proto = context_->
working_model->add_variables();
829 ct->mutable_int_prod()->set_target(new_target);
830 if (context_->
IsFixed(new_target)) {
842 ConstraintProto* new_ct = context_->
working_model->add_constraints();
843 LinearConstraintProto* lin = new_ct->mutable_linear();
844 lin->add_vars(old_target);
846 lin->add_vars(new_target);
847 lin->add_coeffs(-constant);
857 for (
const int ref :
ct->int_prod().vars()) {
858 implied = implied.ContinuousMultiplicationBy(context_->
DomainOf(ref));
860 bool modified =
false;
869 if (
ct->int_prod().vars_size() == 2) {
870 int a =
ct->int_prod().vars(0);
871 int b =
ct->int_prod().vars(1);
872 const int product =
ct->int_prod().target();
882 return RemoveConstraint(
ct);
883 }
else if (
b != product) {
884 ConstraintProto*
const lin = context_->
working_model->add_constraints();
885 lin->mutable_linear()->add_vars(
b);
886 lin->mutable_linear()->add_coeffs(value_a);
887 lin->mutable_linear()->add_vars(product);
888 lin->mutable_linear()->add_coeffs(-1);
889 lin->mutable_linear()->add_domain(0);
890 lin->mutable_linear()->add_domain(0);
893 return RemoveConstraint(
ct);
894 }
else if (value_a != 1) {
899 return RemoveConstraint(
ct);
902 return RemoveConstraint(
ct);
904 }
else if (
a ==
b &&
a == product) {
909 return RemoveConstraint(
ct);
914 const int target_ref =
ct->int_prod().target();
916 for (
const int var :
ct->int_prod().vars()) {
918 if (context_->
MinOf(
var) < 0)
return changed;
919 if (context_->
MaxOf(
var) > 1)
return changed;
928 ConstraintProto* new_ct = context_->
working_model->add_constraints();
929 new_ct->add_enforcement_literal(target_ref);
930 auto* arg = new_ct->mutable_bool_and();
931 for (
const int var :
ct->int_prod().vars()) {
932 arg->add_literals(
var);
936 ConstraintProto* new_ct = context_->
working_model->add_constraints();
937 auto* arg = new_ct->mutable_bool_or();
938 arg->add_literals(target_ref);
939 for (
const int var :
ct->int_prod().vars()) {
944 return RemoveConstraint(
ct);
947 bool CpModelPresolver::PresolveIntDiv(ConstraintProto*
ct) {
951 const int target =
ct->int_div().target();
952 const int ref_x =
ct->int_div().vars(0);
953 const int ref_div =
ct->int_div().vars(1);
960 const int64 divisor = context_->
MinOf(ref_div);
962 LinearConstraintProto*
const lin =
963 context_->
working_model->add_constraints()->mutable_linear();
964 lin->add_vars(ref_x);
966 lin->add_vars(target);
972 return RemoveConstraint(
ct);
974 bool domain_modified =
false;
978 if (domain_modified) {
980 "int_div: updated domain of target in target = X / cte");
991 if (context_->
MinOf(target) >= 0 && context_->
MinOf(ref_x) >= 0 &&
993 LinearConstraintProto*
const lin =
994 context_->
working_model->add_constraints()->mutable_linear();
995 lin->add_vars(ref_x);
997 lin->add_vars(target);
998 lin->add_coeffs(-divisor);
1000 lin->add_domain(divisor - 1);
1003 "int_div: linearize positive division with a constant divisor");
1004 return RemoveConstraint(
ct);
1012 bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1013 bool changed =
false;
1018 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
1019 for (
int& ref : *
ct->mutable_enforcement_literal()) {
1031 bool work_to_do =
false;
1034 if (r.representative !=
var) {
1039 if (!work_to_do)
return false;
1043 [&changed,
this](
int* ref) {
1054 [&changed,
this](
int* ref) {
1065 void CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
1070 const int num_vars =
ct->linear().vars().size();
1071 for (
int i = 0; i < num_vars; ++i) {
1072 const int64 magnitude = std::abs(
ct->linear().coeffs(i));
1074 if (gcd == 1)
break;
1078 for (
int i = 0; i < num_vars; ++i) {
1079 ct->mutable_linear()->set_coeffs(i,
ct->linear().coeffs(i) / gcd);
1083 if (
ct->linear().domain_size() == 0) {
1084 return (
void)MarkConstraintAsFalse(
ct);
1089 void CpModelPresolver::PresolveLinearEqualityModuloTwo(ConstraintProto*
ct) {
1090 if (!
ct->enforcement_literal().empty())
return;
1091 if (
ct->linear().domain().size() != 2)
return;
1092 if (
ct->linear().domain(0) !=
ct->linear().domain(1))
return;
1097 std::vector<int> literals;
1098 for (
int i = 0; i <
ct->linear().vars().size(); ++i) {
1099 const int64 coeff =
ct->linear().coeffs(i);
1100 const int ref =
ct->linear().vars(i);
1101 if (coeff % 2 == 0)
continue;
1104 if (literals.size() > 2)
return;
1106 if (literals.size() == 1) {
1107 const int64 rhs = std::abs(
ct->linear().domain(0));
1108 context_->
UpdateRuleStats(
"linear: only one odd Boolean in equality");
1110 }
else if (literals.size() == 2) {
1111 const int64 rhs = std::abs(
ct->linear().domain(0));
1112 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
1122 template <
typename ProtoWithVarsAndCoeffs>
1123 bool CpModelPresolver::CanonicalizeLinearExpressionInternal(
1124 const ConstraintProto&
ct, ProtoWithVarsAndCoeffs*
proto,
int64* offset) {
1130 int64 sum_of_fixed_terms = 0;
1131 bool remapped =
false;
1132 const int old_size =
proto->vars().size();
1134 for (
int i = 0; i < old_size; ++i) {
1135 const int ref =
proto->vars(i);
1139 if (coeff == 0)
continue;
1142 sum_of_fixed_terms += coeff * context_->
MinOf(
var);
1148 bool removed =
false;
1149 for (
const int enf :
ct.enforcement_literal()) {
1153 sum_of_fixed_terms += coeff;
1162 context_->
UpdateRuleStats(
"linear: enforcement literal in expression");
1167 if (r.representative !=
var) {
1169 sum_of_fixed_terms += coeff * r.
offset;
1171 tmp_terms_.push_back({r.representative, coeff * r.coeff});
1173 proto->clear_vars();
1174 proto->clear_coeffs();
1175 std::sort(tmp_terms_.begin(), tmp_terms_.end());
1176 int current_var = 0;
1177 int64 current_coeff = 0;
1178 for (
const auto entry : tmp_terms_) {
1180 if (entry.first == current_var) {
1181 current_coeff += entry.second;
1183 if (current_coeff != 0) {
1184 proto->add_vars(current_var);
1185 proto->add_coeffs(current_coeff);
1187 current_var = entry.first;
1188 current_coeff = entry.second;
1191 if (current_coeff != 0) {
1192 proto->add_vars(current_var);
1193 proto->add_coeffs(current_coeff);
1198 if (
proto->vars().size() < old_size) {
1201 *offset = sum_of_fixed_terms;
1202 return remapped ||
proto->vars().size() < old_size;
1205 bool CpModelPresolver::CanonicalizeLinearExpression(
1206 const ConstraintProto&
ct, LinearExpressionProto* exp) {
1208 const bool result = CanonicalizeLinearExpressionInternal(
ct, exp, &offset);
1209 exp->set_offset(exp->offset() + offset);
1213 bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
1214 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1220 CanonicalizeLinearExpressionInternal(*
ct,
ct->mutable_linear(), &offset);
1224 ct->mutable_linear());
1226 DivideLinearByGcd(
ct);
1230 bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
1231 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1236 std::set<int> index_to_erase;
1237 const int num_vars =
ct->linear().vars().size();
1243 for (
int i = 0; i < num_vars; ++i) {
1244 const int var =
ct->linear().vars(i);
1245 const int64 coeff =
ct->linear().coeffs(i);
1249 const auto term_domain =
1251 if (!exact)
continue;
1255 if (new_rhs.NumIntervals() > 100)
continue;
1262 index_to_erase.insert(i);
1269 if (index_to_erase.empty()) {
1271 if (context_->
params().presolve_substitution_level() <= 0)
return false;
1272 if (!
ct->enforcement_literal().empty())
return false;
1276 if (rhs.Min() != rhs.Max())
return false;
1278 for (
int i = 0; i < num_vars; ++i) {
1279 const int var =
ct->linear().vars(i);
1280 const int64 coeff =
ct->linear().coeffs(i);
1298 const int64 objective_coeff =
1301 if (objective_coeff % coeff != 0)
continue;
1305 const auto term_domain =
1307 if (!exact)
continue;
1309 if (new_rhs.NumIntervals() > 100)
continue;
1317 objective_coeff))) {
1334 LOG(
WARNING) <<
"This was not supposed to happen and the presolve "
1335 "could be improved.";
1338 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
1342 return RemoveConstraint(
ct);
1351 "linear: singleton column in equality and in objective.");
1353 index_to_erase.insert(i);
1357 if (index_to_erase.empty())
return false;
1366 for (
int i = 0; i < num_vars; ++i) {
1367 if (index_to_erase.count(i)) {
1371 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(i));
1372 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(i));
1375 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
1376 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
1378 DivideLinearByGcd(
ct);
1382 bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
1383 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1389 if (
ct->linear().vars().empty()) {
1392 if (rhs.Contains(0)) {
1393 return RemoveConstraint(
ct);
1395 return MarkConstraintAsFalse(
ct);
1402 if (
ct->linear().vars_size() == 1 &&
ct->enforcement_literal_size() > 0 &&
1403 ct->linear().coeffs(0) == 1 &&
1406 context_->
UpdateRuleStats(
"linear: remove abs from abs(x) in domain");
1407 const Domain implied_abs_target_domain =
1410 .IntersectionWith(context_->
DomainOf(
ct->linear().vars(0)));
1412 if (implied_abs_target_domain.IsEmpty()) {
1413 return MarkConstraintAsFalse(
ct);
1416 const Domain new_abs_var_domain =
1417 implied_abs_target_domain
1418 .UnionWith(implied_abs_target_domain.Negation())
1419 .IntersectionWith(context_->
DomainOf(abs_arg));
1421 if (new_abs_var_domain.IsEmpty()) {
1422 return MarkConstraintAsFalse(
ct);
1425 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1426 new_ct->set_name(
ct->name());
1427 for (
const int literal :
ct->enforcement_literal()) {
1428 new_ct->add_enforcement_literal(
literal);
1430 auto* arg = new_ct->mutable_linear();
1431 arg->add_vars(abs_arg);
1435 return RemoveConstraint(
ct);
1439 if (
ct->enforcement_literal_size() != 1 ||
ct->linear().vars_size() != 1 ||
1440 (
ct->linear().coeffs(0) != 1 &&
ct->linear().coeffs(0) == -1)) {
1444 const int literal =
ct->enforcement_literal(0);
1445 const LinearConstraintProto& linear =
ct->linear();
1446 const int ref = linear.vars(0);
1451 if (linear.domain_size() == 2 && linear.domain(0) == linear.domain(1)) {
1453 : -linear.domain(0) * coeff;
1462 if (complement.Size() != 1)
return false;
1464 : -complement.Min() * coeff;
1479 if (
ct->linear().vars().size() == 1) {
1481 ?
ct->linear().coeffs(0)
1482 : -
ct->linear().coeffs(0);
1487 rhs.InverseMultiplicationBy(coeff))) {
1490 return RemoveConstraint(
ct);
1497 const LinearConstraintProto& arg =
ct->linear();
1498 if (arg.vars_size() == 2) {
1500 const int64 rhs_min = rhs.Min();
1501 const int64 rhs_max = rhs.Max();
1502 if (rhs_min == rhs_max) {
1503 const int v1 = arg.vars(0);
1504 const int v2 = arg.vars(1);
1505 const int64 coeff1 = arg.coeffs(0);
1506 const int64 coeff2 = arg.coeffs(1);
1510 }
else if (coeff2 == 1) {
1512 }
else if (coeff1 == -1) {
1514 }
else if (coeff2 == -1) {
1517 if (added)
return RemoveConstraint(
ct);
1527 bool IsLeConstraint(
const Domain& domain,
const Domain& all_values) {
1528 return all_values.IntersectionWith(Domain(
kint64min, domain.Max()))
1529 .IsIncludedIn(domain);
1533 bool IsGeConstraint(
const Domain& domain,
const Domain& all_values) {
1534 return all_values.IntersectionWith(Domain(domain.Min(),
kint64max))
1535 .IsIncludedIn(domain);
1541 bool RhsCanBeFixedToMin(
int64 coeff,
const Domain& var_domain,
1542 const Domain& terms,
const Domain& rhs) {
1543 if (var_domain.NumIntervals() != 1)
return false;
1544 if (std::abs(coeff) != 1)
return false;
1552 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
1555 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
1561 bool RhsCanBeFixedToMax(
int64 coeff,
const Domain& var_domain,
1562 const Domain& terms,
const Domain& rhs) {
1563 if (var_domain.NumIntervals() != 1)
return false;
1564 if (std::abs(coeff) != 1)
return false;
1566 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
1569 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
1576 void TakeIntersectionWith(
const absl::flat_hash_set<int>& current,
1577 absl::flat_hash_set<int>* to_clear) {
1578 std::vector<int> new_set;
1579 for (
const int c : *to_clear) {
1580 if (current.contains(c)) new_set.push_back(c);
1583 for (
const int c : new_set) to_clear->insert(c);
1588 bool CpModelPresolver::PropagateDomainsInLinear(
int c, ConstraintProto*
ct) {
1589 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1597 const int num_vars =
ct->linear().vars_size();
1598 term_domains.resize(num_vars + 1);
1599 left_domains.resize(num_vars + 1);
1600 left_domains[0] = Domain(0);
1601 for (
int i = 0; i < num_vars; ++i) {
1602 const int var =
ct->linear().vars(i);
1603 const int64 coeff =
ct->linear().coeffs(i);
1606 left_domains[i + 1] =
1609 const Domain& implied_rhs = left_domains[num_vars];
1613 if (implied_rhs.IsIncludedIn(old_rhs)) {
1615 return RemoveConstraint(
ct);
1619 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
1620 if (rhs.IsEmpty()) {
1622 return MarkConstraintAsFalse(
ct);
1624 if (rhs != old_rhs) {
1632 bool is_le_constraint = IsLeConstraint(rhs, implied_rhs);
1633 bool is_ge_constraint = IsGeConstraint(rhs, implied_rhs);
1636 if (
ct->enforcement_literal().size() > 1)
return false;
1638 bool new_bounds =
false;
1639 bool recanonicalize =
false;
1640 Domain negated_rhs = rhs.Negation();
1641 Domain right_domain(0);
1643 Domain implied_term_domain;
1644 term_domains[num_vars] = Domain(0);
1645 for (
int i = num_vars - 1; i >= 0; --i) {
1646 const int var =
ct->linear().vars(i);
1647 const int64 var_coeff =
ct->linear().coeffs(i);
1649 right_domain.AdditionWith(term_domains[i + 1]).RelaxIfTooComplex();
1650 implied_term_domain = left_domains[i].AdditionWith(right_domain);
1651 new_domain = implied_term_domain.AdditionWith(negated_rhs)
1652 .InverseMultiplicationBy(-var_coeff);
1654 if (
ct->enforcement_literal().empty()) {
1659 }
else if (
ct->enforcement_literal().size() == 1) {
1670 recanonicalize =
true;
1674 if (is_le_constraint || is_ge_constraint) {
1675 CHECK_NE(is_le_constraint, is_ge_constraint);
1676 if ((var_coeff > 0) == is_ge_constraint) {
1691 const bool is_in_objective =
1695 const int64 obj_coeff =
1704 if (obj_coeff <= 0 &&
1714 recanonicalize =
true;
1718 if (obj_coeff >= 0 &&
1728 recanonicalize =
true;
1736 if (!
ct->enforcement_literal().empty())
continue;
1748 if (rhs.Min() != rhs.Max() &&
1751 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
1753 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
1754 implied_term_domain, rhs)) {
1755 rhs = Domain(rhs.Min());
1758 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
1759 implied_term_domain, rhs)) {
1760 rhs = Domain(rhs.Max());
1766 negated_rhs = rhs.Negation();
1770 right_domain = Domain(0);
1774 is_le_constraint =
false;
1775 is_ge_constraint =
false;
1776 for (
const int var :
ct->linear().vars()) {
1793 if (
ct->linear().vars().size() <= 2)
continue;
1798 if (rhs.Min() != rhs.Max())
continue;
1804 if (context_->
DomainOf(
var) != new_domain)
continue;
1805 if (std::abs(var_coeff) != 1)
continue;
1806 if (context_->
params().presolve_substitution_level() <= 0)
continue;
1812 bool is_in_objective =
false;
1814 is_in_objective =
true;
1820 if (is_in_objective) col_size--;
1821 const int row_size =
ct->linear().vars_size();
1825 const int num_entries_added = (row_size - 1) * (col_size - 1);
1826 const int num_entries_removed = col_size + row_size - 1;
1828 if (num_entries_added > num_entries_removed) {
1834 std::vector<int> others;
1843 if (context_->
working_model->constraints(c).constraint_case() !=
1844 ConstraintProto::ConstraintCase::kLinear) {
1848 for (
const int ref :
1849 context_->
working_model->constraints(c).enforcement_literal()) {
1855 others.push_back(c);
1857 if (abort)
continue;
1860 for (
const int c : others) {
1873 if (is_in_objective) {
1878 absl::StrCat(
"linear: variable substitution ", others.size()));
1889 const int ct_index = context_->
mapping_model->constraints().size();
1891 LinearConstraintProto* mapping_linear_ct =
1894 std::swap(mapping_linear_ct->mutable_vars()->at(0),
1895 mapping_linear_ct->mutable_vars()->at(i));
1896 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
1897 mapping_linear_ct->mutable_coeffs()->at(i));
1898 return RemoveConstraint(
ct);
1903 if (recanonicalize)
return CanonicalizeLinear(
ct);
1914 void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
1915 int ct_index, ConstraintProto*
ct) {
1916 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
1921 const LinearConstraintProto& arg =
ct->linear();
1922 const int num_vars = arg.vars_size();
1926 if (num_vars <= 1)
return;
1930 int64 max_coeff_magnitude = 0;
1931 for (
int i = 0; i < num_vars; ++i) {
1932 const int ref = arg.vars(i);
1933 const int64 coeff = arg.coeffs(i);
1934 const int64 term_a = coeff * context_->
MinOf(ref);
1935 const int64 term_b = coeff * context_->
MaxOf(ref);
1936 max_coeff_magnitude =
std::max(max_coeff_magnitude, std::abs(coeff));
1937 min_sum +=
std::min(term_a, term_b);
1938 max_sum +=
std::max(term_a, term_b);
1947 const auto& domain =
ct->linear().domain();
1948 const int64 ub_threshold = domain[domain.size() - 2] - min_sum;
1949 const int64 lb_threshold = max_sum - domain[1];
1951 if (max_coeff_magnitude <
std::max(ub_threshold, lb_threshold))
return;
1970 const bool lower_bounded = min_sum < rhs_domain.Min();
1971 const bool upper_bounded = max_sum > rhs_domain.Max();
1972 if (!lower_bounded && !upper_bounded)
return;
1973 if (lower_bounded && upper_bounded) {
1975 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
1977 if (!
ct->name().empty()) {
1978 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
1981 new_ct1->mutable_linear());
1983 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
1985 if (!
ct->name().empty()) {
1986 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
1989 new_ct2->mutable_linear());
1992 return (
void)RemoveConstraint(
ct);
1998 const int64 threshold = lower_bounded ? ub_threshold : lb_threshold;
2001 const bool only_booleans =
2002 !context_->
params().presolve_extract_integer_enforcement();
2007 int64 rhs_offset = 0;
2008 bool some_integer_encoding_were_extracted =
false;
2009 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
2010 for (
int i = 0; i < arg.vars_size(); ++i) {
2011 int ref = arg.vars(i);
2012 int64 coeff = arg.coeffs(i);
2019 if (context_->
IsFixed(ref) || coeff < threshold ||
2020 (only_booleans && !is_boolean)) {
2022 mutable_arg->set_vars(new_size, mutable_arg->vars(i));
2023 mutable_arg->set_coeffs(new_size, mutable_arg->coeffs(i));
2031 some_integer_encoding_were_extracted =
true;
2033 "linear: extracted integer enforcement literal");
2035 if (lower_bounded) {
2036 ct->add_enforcement_literal(is_boolean
2039 ref, context_->
MinOf(ref)));
2040 rhs_offset -= coeff * context_->
MinOf(ref);
2042 ct->add_enforcement_literal(is_boolean
2045 ref, context_->
MaxOf(ref)));
2046 rhs_offset -= coeff * context_->
MaxOf(ref);
2049 mutable_arg->mutable_vars()->Truncate(new_size);
2050 mutable_arg->mutable_coeffs()->Truncate(new_size);
2052 if (some_integer_encoding_were_extracted) {
2058 void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
2063 const LinearConstraintProto& arg =
ct->linear();
2064 const int num_vars = arg.vars_size();
2067 for (
int i = 0; i < num_vars; ++i) {
2068 const int ref = arg.vars(i);
2069 const int64 coeff = arg.coeffs(i);
2070 const int64 term_a = coeff * context_->
MinOf(ref);
2071 const int64 term_b = coeff * context_->
MaxOf(ref);
2072 min_sum +=
std::min(term_a, term_b);
2073 max_sum +=
std::max(term_a, term_b);
2075 for (
const int type : {0, 1}) {
2076 std::vector<int> at_most_one;
2077 for (
int i = 0; i < num_vars; ++i) {
2078 const int ref = arg.vars(i);
2079 const int64 coeff = arg.coeffs(i);
2080 if (context_->
MinOf(ref) != 0)
continue;
2081 if (context_->
MaxOf(ref) != 1)
continue;
2086 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
2087 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
2090 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
2091 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
2095 if (at_most_one.size() > 1) {
2101 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2102 new_ct->set_name(
ct->name());
2103 for (
const int ref : at_most_one) {
2104 new_ct->mutable_at_most_one()->add_literals(ref);
2113 bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
2114 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
2119 const LinearConstraintProto& arg =
ct->linear();
2120 const int num_vars = arg.vars_size();
2122 int64 max_coeff = 0;
2125 for (
int i = 0; i < num_vars; ++i) {
2127 const int var = arg.vars(i);
2128 const int64 coeff = arg.coeffs(i);
2131 if (context_->
MinOf(
var) != 0)
return false;
2132 if (context_->
MaxOf(
var) != 1)
return false;
2136 min_coeff =
std::min(min_coeff, coeff);
2137 max_coeff =
std::max(max_coeff, coeff);
2141 min_coeff =
std::min(min_coeff, -coeff);
2142 max_coeff =
std::max(max_coeff, -coeff);
2154 if ((!rhs_domain.Contains(min_sum) &&
2155 min_sum + min_coeff > rhs_domain.Max()) ||
2156 (!rhs_domain.Contains(max_sum) &&
2157 max_sum - min_coeff < rhs_domain.Min())) {
2158 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
2159 return MarkConstraintAsFalse(
ct);
2161 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
2163 return RemoveConstraint(
ct);
2170 DCHECK(!rhs_domain.IsEmpty());
2171 if (min_sum + min_coeff > rhs_domain.Max()) {
2174 const auto copy = arg;
2175 ct->mutable_bool_and()->clear_literals();
2176 for (
int i = 0; i < num_vars; ++i) {
2177 ct->mutable_bool_and()->add_literals(
2178 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2180 return PresolveBoolAnd(
ct);
2181 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
2184 const auto copy = arg;
2185 ct->mutable_bool_and()->clear_literals();
2186 for (
int i = 0; i < num_vars; ++i) {
2187 ct->mutable_bool_and()->add_literals(
2188 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2190 return PresolveBoolAnd(
ct);
2191 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
2192 rhs_domain.front().end >= max_sum) {
2195 const auto copy = arg;
2196 ct->mutable_bool_or()->clear_literals();
2197 for (
int i = 0; i < num_vars; ++i) {
2198 ct->mutable_bool_or()->add_literals(
2199 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2201 return PresolveBoolOr(
ct);
2202 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
2203 rhs_domain.back().start <= min_sum) {
2206 const auto copy = arg;
2207 ct->mutable_bool_or()->clear_literals();
2208 for (
int i = 0; i < num_vars; ++i) {
2209 ct->mutable_bool_or()->add_literals(
2210 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2212 return PresolveBoolOr(
ct);
2214 min_sum + max_coeff <= rhs_domain.Max() &&
2215 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2216 rhs_domain.back().start <= min_sum) {
2219 const auto copy = arg;
2220 ct->mutable_at_most_one()->clear_literals();
2221 for (
int i = 0; i < num_vars; ++i) {
2222 ct->mutable_at_most_one()->add_literals(
2223 copy.coeffs(i) > 0 ? copy.vars(i) :
NegatedRef(copy.vars(i)));
2227 max_sum - max_coeff >= rhs_domain.Min() &&
2228 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2229 rhs_domain.front().end >= max_sum) {
2232 const auto copy = arg;
2233 ct->mutable_at_most_one()->clear_literals();
2234 for (
int i = 0; i < num_vars; ++i) {
2235 ct->mutable_at_most_one()->add_literals(
2236 copy.coeffs(i) > 0 ?
NegatedRef(copy.vars(i)) : copy.vars(i));
2240 min_sum < rhs_domain.Min() &&
2241 min_sum + min_coeff >= rhs_domain.Min() &&
2242 min_sum + 2 * min_coeff > rhs_domain.Max() &&
2243 min_sum + max_coeff <= rhs_domain.Max()) {
2245 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
2246 exactly_one->set_name(
ct->name());
2247 for (
int i = 0; i < num_vars; ++i) {
2248 exactly_one->mutable_exactly_one()->add_literals(
2249 arg.coeffs(i) > 0 ? arg.vars(i) :
NegatedRef(arg.vars(i)));
2252 return RemoveConstraint(
ct);
2254 max_sum > rhs_domain.Max() &&
2255 max_sum - min_coeff <= rhs_domain.Max() &&
2256 max_sum - 2 * min_coeff < rhs_domain.Min() &&
2257 max_sum - max_coeff >= rhs_domain.Min()) {
2259 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
2260 exactly_one->set_name(
ct->name());
2261 for (
int i = 0; i < num_vars; ++i) {
2262 exactly_one->mutable_exactly_one()->add_literals(
2263 arg.coeffs(i) > 0 ?
NegatedRef(arg.vars(i)) : arg.vars(i));
2266 return RemoveConstraint(
ct);
2273 if (num_vars > 3)
return false;
2278 const int max_mask = (1 << arg.vars_size());
2279 for (
int mask = 0; mask < max_mask; ++mask) {
2281 for (
int i = 0; i < num_vars; ++i) {
2282 if ((mask >> i) & 1)
value += arg.coeffs(i);
2284 if (rhs_domain.Contains(
value))
continue;
2287 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2288 auto* new_arg = new_ct->mutable_bool_or();
2290 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2292 for (
int i = 0; i < num_vars; ++i) {
2293 new_arg->add_literals(((mask >> i) & 1) ?
NegatedRef(arg.vars(i))
2299 return RemoveConstraint(
ct);
2304 void AddLinearExpression(
int64 coeff,
const LinearExpressionProto& exp,
2305 LinearConstraintProto* linear_ct) {
2306 const int size = exp.vars().size();
2307 for (
int i = 0; i < size; ++i) {
2308 linear_ct->add_vars(exp.vars(i));
2309 linear_ct->add_coeffs(coeff * exp.coeffs(i));
2311 if (exp.offset() != 0) {
2313 .AdditionWith(Domain(-coeff * exp.offset())),
2320 bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
2323 if (
ct->enforcement_literal().empty() && !
ct->interval().has_start_view()) {
2324 bool changed =
false;
2325 const int start =
ct->interval().start();
2326 const int end =
ct->interval().end();
2327 const int size =
ct->interval().size();
2328 const Domain start_domain = context_->
DomainOf(start);
2329 const Domain end_domain = context_->
DomainOf(end);
2330 const Domain size_domain = context_->
DomainOf(size);
2337 end, start_domain.AdditionWith(size_domain), &changed)) {
2341 start, end_domain.AdditionWith(size_domain.Negation()), &changed)) {
2345 size, end_domain.AdditionWith(start_domain.Negation()), &changed)) {
2354 if (!
ct->interval().has_start_view()) {
2356 const int start =
ct->interval().start();
2357 const int end =
ct->interval().end();
2358 const int size =
ct->interval().size();
2359 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2360 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2361 new_ct->mutable_linear()->add_domain(0);
2362 new_ct->mutable_linear()->add_domain(0);
2363 new_ct->mutable_linear()->add_vars(start);
2364 new_ct->mutable_linear()->add_coeffs(1);
2365 new_ct->mutable_linear()->add_vars(size);
2366 new_ct->mutable_linear()->add_coeffs(1);
2367 new_ct->mutable_linear()->add_vars(end);
2368 new_ct->mutable_linear()->add_coeffs(-1);
2372 return RemoveConstraint(
ct);
2380 if (context_->
params().convert_intervals()) {
2381 bool changed =
false;
2382 IntervalConstraintProto*
interval =
ct->mutable_interval();
2383 if (!
ct->interval().has_start_view()) {
2388 interval->mutable_start_view()->add_coeffs(1);
2389 interval->mutable_start_view()->set_offset(0);
2391 interval->mutable_size_view()->add_coeffs(1);
2392 interval->mutable_size_view()->set_offset(0);
2394 interval->mutable_end_view()->add_coeffs(1);
2395 interval->mutable_end_view()->set_offset(0);
2402 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2403 *(new_ct->mutable_enforcement_literal()) =
ct->enforcement_literal();
2404 new_ct->mutable_linear()->add_domain(0);
2405 new_ct->mutable_linear()->add_domain(0);
2406 AddLinearExpression(1,
interval->start_view(), new_ct->mutable_linear());
2407 AddLinearExpression(1,
interval->size_view(), new_ct->mutable_linear());
2408 AddLinearExpression(-1,
interval->end_view(), new_ct->mutable_linear());
2418 CanonicalizeLinearExpression(*
ct,
interval->mutable_start_view());
2419 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size_view());
2420 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end_view());
2429 if ( (
false) &&
ct->enforcement_literal().empty() &&
2430 context_->
IsFixed(
ct->interval().size())) {
2432 1, context_->
MinOf(
ct->interval().size()));
2439 bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
2442 const int index_ref =
ct->element().index();
2443 const int target_ref =
ct->element().target();
2448 bool all_constants =
true;
2449 absl::flat_hash_set<int64> constant_set;
2450 bool all_included_in_target_domain =
true;
2453 bool reduced_index_domain =
false;
2455 Domain(0,
ct->element().vars_size() - 1),
2456 &reduced_index_domain)) {
2464 std::vector<int64> possible_indices;
2465 const Domain& index_domain = context_->
DomainOf(index_ref);
2466 for (
const ClosedInterval&
interval : index_domain) {
2469 const int ref =
ct->element().vars(index_value);
2470 const int64 target_value =
2471 target_ref == index_ref ? index_value : -index_value;
2473 possible_indices.push_back(target_value);
2477 if (possible_indices.size() < index_domain.Size()) {
2483 "element: reduced index domain when target equals index");
2489 Domain infered_domain;
2490 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
2491 const Domain& target_domain = context_->
DomainOf(target_ref);
2492 std::vector<int64> possible_indices;
2493 for (
const ClosedInterval
interval : initial_index_domain) {
2497 const int ref =
ct->element().vars(
value);
2498 const Domain& domain = context_->
DomainOf(ref);
2499 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
2500 possible_indices.push_back(
value);
2501 if (domain.IsFixed()) {
2502 constant_set.insert(domain.Min());
2504 all_constants =
false;
2506 if (!domain.IsIncludedIn(target_domain)) {
2507 all_included_in_target_domain =
false;
2509 infered_domain = infered_domain.
UnionWith(domain);
2512 if (possible_indices.size() < initial_index_domain.Size()) {
2519 bool domain_modified =
false;
2521 &domain_modified)) {
2524 if (domain_modified) {
2530 if (context_->
IsFixed(index_ref)) {
2531 const int var =
ct->element().vars(context_->
MinOf(index_ref));
2532 if (
var != target_ref) {
2533 LinearConstraintProto*
const lin =
2534 context_->
working_model->add_constraints()->mutable_linear();
2536 lin->add_coeffs(-1);
2537 lin->add_vars(target_ref);
2544 return RemoveConstraint(
ct);
2550 if (all_constants && constant_set.size() == 1) {
2553 return RemoveConstraint(
ct);
2558 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
2560 const int64 v0 = context_->
MinOf(
ct->element().vars(0));
2561 const int64 v1 = context_->
MinOf(
ct->element().vars(1));
2563 LinearConstraintProto*
const lin =
2564 context_->
working_model->add_constraints()->mutable_linear();
2565 lin->add_vars(target_ref);
2567 lin->add_vars(index_ref);
2568 lin->add_coeffs(v0 - v1);
2569 lin->add_domain(v0);
2570 lin->add_domain(v0);
2572 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
2573 return RemoveConstraint(
ct);
2577 const AffineRelation::Relation r_index =
2579 if (r_index.representative != index_ref) {
2581 if (context_->
DomainOf(r_index.representative).
Size() >
2589 const int array_size =
ct->element().vars_size();
2591 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
2592 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
2593 r_index.offset + r_max * r_index.coeff >= 0 &&
2594 r_index.offset + r_max * r_index.coeff < array_size) {
2596 ElementConstraintProto*
const element =
2597 context_->
working_model->add_constraints()->mutable_element();
2598 for (
int64 v = 0; v <= r_max; ++v) {
2599 const int64 scaled_index = v * r_index.coeff + r_index.offset;
2601 CHECK_LT(scaled_index, array_size);
2602 element->add_vars(
ct->element().vars(scaled_index));
2604 element->set_index(r_ref);
2605 element->set_target(target_ref);
2607 if (r_index.coeff == 1) {
2613 return RemoveConstraint(
ct);
2619 if (all_constants && unique_index) {
2623 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
2626 return RemoveConstraint(
ct);
2629 const bool unique_target =
2631 context_->
IsFixed(target_ref);
2632 if (all_included_in_target_domain && unique_target) {
2636 return RemoveConstraint(
ct);
2639 if (unique_target && !context_->
IsFixed(target_ref)) {
2649 bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
2652 if (
ct->table().vars().empty()) {
2654 return RemoveConstraint(
ct);
2660 const int num_vars =
ct->table().vars_size();
2661 const int num_tuples =
ct->table().values_size() / num_vars;
2662 std::vector<int64> tuple(num_vars);
2663 std::vector<std::vector<int64>> new_tuples;
2664 new_tuples.reserve(num_tuples);
2665 std::vector<absl::flat_hash_set<int64>> new_domains(num_vars);
2666 std::vector<AffineRelation::Relation> affine_relations;
2668 absl::flat_hash_set<int> visited;
2669 for (
const int ref :
ct->table().vars()) {
2677 bool modified_variables =
false;
2678 for (
int v = 0; v < num_vars; ++v) {
2679 const int ref =
ct->table().vars(v);
2681 affine_relations.push_back(r);
2682 if (r.representative != ref) {
2683 modified_variables =
true;
2687 for (
int i = 0; i < num_tuples; ++i) {
2688 bool delete_row =
false;
2690 for (
int j = 0; j < num_vars; ++j) {
2691 const int ref =
ct->table().vars(j);
2692 int64 v =
ct->table().values(i * num_vars + j);
2693 const AffineRelation::Relation& r = affine_relations[j];
2694 if (r.representative != ref) {
2695 const int64 inverse_value = (v - r.offset) / r.coeff;
2696 if (inverse_value * r.coeff + r.offset != v) {
2709 if (delete_row)
continue;
2710 new_tuples.push_back(tuple);
2711 for (
int j = 0; j < num_vars; ++j) {
2712 const int64 v = tuple[j];
2713 new_domains[j].insert(v);
2719 if (new_tuples.size() < num_tuples || modified_variables) {
2720 ct->mutable_table()->clear_values();
2721 for (
const std::vector<int64>& t : new_tuples) {
2722 for (
const int64 v : t) {
2723 ct->mutable_table()->add_values(v);
2726 if (new_tuples.size() < num_tuples) {
2731 if (modified_variables) {
2732 for (
int j = 0; j < num_vars; ++j) {
2733 const AffineRelation::Relation& r = affine_relations[j];
2734 if (r.representative !=
ct->table().vars(j)) {
2735 ct->mutable_table()->set_vars(j, r.representative);
2739 "table: replace variable by canonical affine one");
2743 if (
ct->table().negated())
return modified_variables;
2746 bool changed =
false;
2747 for (
int j = 0; j < num_vars; ++j) {
2748 const int ref =
ct->table().vars(j);
2752 new_domains[j].end())),
2760 if (num_vars == 1) {
2763 return RemoveConstraint(
ct);
2768 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].size();
2769 if (prod == new_tuples.size()) {
2771 return RemoveConstraint(
ct);
2777 if (new_tuples.size() > 0.7 * prod) {
2779 std::vector<std::vector<int64>> var_to_values(num_vars);
2780 for (
int j = 0; j < num_vars; ++j) {
2781 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].end());
2783 std::vector<std::vector<int64>> all_tuples(prod);
2784 for (
int i = 0; i < prod; ++i) {
2785 all_tuples[i].resize(num_vars);
2787 for (
int j = 0; j < num_vars; ++j) {
2788 all_tuples[i][j] = var_to_values[j][
index % var_to_values[j].size()];
2789 index /= var_to_values[j].size();
2795 std::vector<std::vector<int64>> diff(prod - new_tuples.size());
2796 std::set_difference(all_tuples.begin(), all_tuples.end(),
2797 new_tuples.begin(), new_tuples.end(), diff.begin());
2800 ct->mutable_table()->set_negated(!
ct->table().negated());
2801 ct->mutable_table()->clear_values();
2802 for (
const std::vector<int64>& t : diff) {
2803 for (
const int64 v : t)
ct->mutable_table()->add_values(v);
2807 return modified_variables;
2810 bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
2814 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
2816 bool constraint_has_changed =
false;
2818 const int size = all_diff.vars_size();
2821 return RemoveConstraint(
ct);
2825 return RemoveConstraint(
ct);
2828 bool something_was_propagated =
false;
2829 std::vector<int> new_variables;
2830 for (
int i = 0; i < size; ++i) {
2831 if (!context_->
IsFixed(all_diff.vars(i))) {
2832 new_variables.push_back(all_diff.vars(i));
2837 bool propagated =
false;
2838 for (
int j = 0; j < size; ++j) {
2839 if (i == j)
continue;
2842 Domain(
value).Complement())) {
2850 something_was_propagated =
true;
2854 std::sort(new_variables.begin(), new_variables.end());
2855 for (
int i = 1; i < new_variables.size(); ++i) {
2856 if (new_variables[i] == new_variables[i - 1]) {
2858 "Duplicate variable in all_diff");
2862 if (new_variables.size() < all_diff.vars_size()) {
2863 all_diff.mutable_vars()->Clear();
2864 for (
const int var : new_variables) {
2865 all_diff.add_vars(
var);
2868 something_was_propagated =
true;
2869 constraint_has_changed =
true;
2870 if (new_variables.size() <= 1)
continue;
2875 Domain domain = context_->
DomainOf(all_diff.vars(0));
2876 for (
int i = 1; i < all_diff.vars_size(); ++i) {
2879 if (all_diff.vars_size() == domain.Size()) {
2880 absl::flat_hash_map<int64, std::vector<int>> value_to_refs;
2881 for (
const int ref : all_diff.vars()) {
2884 value_to_refs[v].push_back(ref);
2888 bool propagated =
false;
2889 for (
const auto& it : value_to_refs) {
2890 if (it.second.size() == 1 &&
2892 const int ref = it.second.
front();
2901 "all_diff: propagated mandatory values in permutation");
2902 something_was_propagated =
true;
2905 if (!something_was_propagated)
break;
2908 return constraint_has_changed;
2915 std::vector<int> GetLiteralsFromSetPPCConstraint(
const ConstraintProto&
ct) {
2916 std::vector<int> sorted_literals;
2917 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
2918 for (
const int literal :
ct.at_most_one().literals()) {
2919 sorted_literals.push_back(
literal);
2921 }
else if (
ct.constraint_case() == ConstraintProto::kBoolOr) {
2922 for (
const int literal :
ct.bool_or().literals()) {
2923 sorted_literals.push_back(
literal);
2925 }
else if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
2926 for (
const int literal :
ct.exactly_one().literals()) {
2927 sorted_literals.push_back(
literal);
2930 std::sort(sorted_literals.begin(), sorted_literals.end());
2931 return sorted_literals;
2936 void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
2937 absl::flat_hash_map<int, int>* ref_to_bool_and) {
2938 if (ref_to_bool_and->contains(lhs)) {
2939 const int ct_index = (*ref_to_bool_and)[lhs];
2940 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
2941 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
2942 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
2943 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
2946 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
2947 ConstraintProto*
ct =
proto->add_constraints();
2948 ct->add_enforcement_literal(lhs);
2949 ct->mutable_bool_and()->add_literals(rhs);
2953 template <
typename ClauseContainer>
2954 void ExtractClauses(
bool use_bool_and,
const ClauseContainer& container,
2955 CpModelProto*
proto) {
2962 absl::flat_hash_map<int, int> ref_to_bool_and;
2963 for (
int i = 0; i < container.NumClauses(); ++i) {
2964 const std::vector<Literal>& clause = container.Clause(i);
2965 if (clause.empty())
continue;
2968 if (use_bool_and && clause.size() == 2) {
2969 const int a = clause[0].IsPositive()
2970 ? clause[0].Variable().value()
2972 const int b = clause[1].IsPositive()
2973 ? clause[1].Variable().value()
2980 ConstraintProto*
ct =
proto->add_constraints();
2981 for (
const Literal l : clause) {
2982 if (l.IsPositive()) {
2983 ct->mutable_bool_or()->add_literals(l.Variable().value());
2985 ct->mutable_bool_or()->add_literals(
NegatedRef(l.Variable().value()));
2993 int64 CpModelPresolver::StartMin(
2994 const IntervalConstraintProto&
interval)
const {
2999 int64 CpModelPresolver::EndMax(
const IntervalConstraintProto&
interval)
const {
3004 int64 CpModelPresolver::SizeMin(
const IntervalConstraintProto&
interval)
const {
3009 int64 CpModelPresolver::SizeMax(
const IntervalConstraintProto&
interval)
const {
3014 bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
3016 const NoOverlapConstraintProto&
proto =
ct->no_overlap();
3017 const int initial_num_intervals =
proto.intervals_size();
3021 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3022 const int interval_index =
proto.intervals(i);
3024 .constraint_case() ==
3025 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3028 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3030 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3034 ct->mutable_no_overlap()->mutable_intervals()->begin(),
3035 ct->mutable_no_overlap()->mutable_intervals()->end(),
3036 [
this](
int i1,
int i2) {
3037 return StartMin(context_->working_model->constraints(i1).interval()) <
3038 StartMin(context_->working_model->constraints(i2).interval());
3047 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3048 const int interval_index =
proto.intervals(i);
3049 const IntervalConstraintProto&
interval =
3050 context_->
working_model->constraints(interval_index).interval();
3051 const int64 end_max_of_previous_intervals = end_max_so_far;
3053 if (StartMin(
interval) >= end_max_of_previous_intervals &&
3054 (i + 1 ==
proto.intervals_size() ||
3056 ->constraints(
proto.intervals(i + 1))
3061 ct->mutable_no_overlap()->set_intervals(new_size++, interval_index);
3063 ct->mutable_no_overlap()->mutable_intervals()->Truncate(new_size);
3065 if (
proto.intervals_size() == 1) {
3067 return RemoveConstraint(
ct);
3069 if (
proto.intervals().empty()) {
3071 return RemoveConstraint(
ct);
3074 return new_size < initial_num_intervals;
3077 bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
3080 const CumulativeConstraintProto&
proto =
ct->cumulative();
3084 bool changed =
false;
3085 int num_zero_demand_removed = 0;
3086 for (
int i = 0; i <
proto.intervals_size(); ++i) {
3088 .constraint_case() ==
3089 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
3093 const int demand_ref =
proto.demands(i);
3094 const int64 demand_max = context_->
MaxOf(demand_ref);
3095 if (demand_max == 0) {
3096 num_zero_demand_removed++;
3100 ct->mutable_cumulative()->set_intervals(new_size,
proto.intervals(i));
3101 ct->mutable_cumulative()->set_demands(new_size,
proto.demands(i));
3104 if (new_size <
proto.intervals_size()) {
3106 ct->mutable_cumulative()->mutable_intervals()->Truncate(new_size);
3107 ct->mutable_cumulative()->mutable_demands()->Truncate(new_size);
3110 if (num_zero_demand_removed > 0) {
3111 context_->
UpdateRuleStats(
"cumulative: removed intervals with no demands");
3114 if (new_size == 0) {
3116 return RemoveConstraint(
ct);
3120 if (!context_->
IsFixed(
proto.capacity()))
return changed;
3123 const int num_intervals =
proto.intervals_size();
3124 bool with_start_view =
false;
3125 std::vector<int> start_refs(num_intervals, -1);
3127 int num_duration_one = 0;
3128 int num_greater_half_capacity = 0;
3130 bool has_optional_interval =
false;
3131 for (
int i = 0; i < num_intervals; ++i) {
3133 const ConstraintProto&
ct =
3135 if (!
ct.enforcement_literal().empty()) has_optional_interval =
true;
3136 const IntervalConstraintProto&
interval =
ct.interval();
3137 if (
interval.has_start_view()) with_start_view =
true;
3139 const int demand_ref =
proto.demands(i);
3148 const int64 demand_min = context_->
MinOf(demand_ref);
3149 const int64 demand_max = context_->
MaxOf(demand_ref);
3151 num_greater_half_capacity++;
3155 if (
ct.enforcement_literal().empty()) {
3158 CHECK_EQ(
ct.enforcement_literal().size(), 1);
3164 }
else if (demand_max >
capacity) {
3165 if (
ct.enforcement_literal().empty()) {
3166 context_->
UpdateRuleStats(
"cumulative: demand_max exceeds capacity.");
3175 "cumulative: demand_max of optional interval exceeds capacity.");
3181 if (num_greater_half_capacity == num_intervals) {
3182 if (num_duration_one == num_intervals && !has_optional_interval &&
3185 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3186 auto* arg = new_ct->mutable_all_diff();
3187 for (
const int var : start_refs) arg->add_vars(
var);
3189 return RemoveConstraint(
ct);
3192 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3193 auto* arg = new_ct->mutable_no_overlap();
3198 return RemoveConstraint(
ct);
3205 bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
3208 RoutesConstraintProto&
proto = *
ct->mutable_routes();
3211 const int num_arcs =
proto.literals_size();
3212 for (
int i = 0; i < num_arcs; ++i) {
3213 const int ref =
proto.literals(i);
3220 proto.set_literals(new_size, ref);
3225 if (new_size < num_arcs) {
3226 proto.mutable_literals()->Truncate(new_size);
3227 proto.mutable_tails()->Truncate(new_size);
3228 proto.mutable_heads()->Truncate(new_size);
3234 bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
3237 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
3241 ct->mutable_circuit()->mutable_heads());
3245 std::vector<std::vector<int>> incoming_arcs;
3246 std::vector<std::vector<int>> outgoing_arcs;
3248 const int num_arcs =
proto.literals_size();
3249 for (
int i = 0; i < num_arcs; ++i) {
3250 const int ref =
proto.literals(i);
3258 incoming_arcs[
head].push_back(ref);
3259 outgoing_arcs[
tail].push_back(ref);
3267 bool loop_again =
true;
3268 int num_fixed_at_true = 0;
3269 while (loop_again) {
3271 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
3272 for (
const std::vector<int>& refs : *node_to_refs) {
3273 if (refs.size() == 1) {
3275 ++num_fixed_at_true;
3284 for (
const int ref : refs) {
3294 if (num_true == 1) {
3295 for (
const int ref : refs) {
3296 if (ref != true_ref) {
3297 if (!context_->
IsFixed(ref)) {
3308 if (num_fixed_at_true > 0) {
3315 int circuit_start = -1;
3316 std::vector<int>
next(num_nodes, -1);
3317 std::vector<int> new_in_degree(num_nodes, 0);
3318 std::vector<int> new_out_degree(num_nodes, 0);
3319 for (
int i = 0; i < num_arcs; ++i) {
3320 const int ref =
proto.literals(i);
3328 circuit_start =
proto.tails(i);
3332 ++new_out_degree[
proto.tails(i)];
3333 ++new_in_degree[
proto.heads(i)];
3336 proto.set_literals(new_size,
proto.literals(i));
3346 for (
int i = 0; i < num_nodes; ++i) {
3348 if (incoming_arcs[i].empty() && outgoing_arcs[i].empty())
continue;
3350 if (new_in_degree[i] == 0 || new_out_degree[i] == 0) {
3356 if (circuit_start != -1) {
3357 std::vector<bool> visited(num_nodes,
false);
3358 int current = circuit_start;
3359 while (current != -1 && !visited[current]) {
3360 visited[current] =
true;
3361 current =
next[current];
3363 if (current == circuit_start) {
3366 for (
int i = 0; i < num_arcs; ++i) {
3367 if (visited[
proto.tails(i)])
continue;
3375 return RemoveConstraint(
ct);
3379 if (num_true == new_size) {
3381 return RemoveConstraint(
ct);
3387 for (
int i = 0; i < num_nodes; ++i) {
3388 for (
const std::vector<int>* arc_literals :
3389 {&incoming_arcs[i], &outgoing_arcs[i]}) {
3390 std::vector<int> literals;
3391 for (
const int ref : *arc_literals) {
3397 literals.push_back(ref);
3399 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
3408 if (new_size < num_arcs) {
3409 proto.mutable_tails()->Truncate(new_size);
3410 proto.mutable_heads()->Truncate(new_size);
3411 proto.mutable_literals()->Truncate(new_size);
3418 bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
3421 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
3422 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
3426 bool all_affine =
true;
3427 std::vector<AffineRelation::Relation> affine_relations;
3428 for (
int v = 0; v <
proto.vars_size(); ++v) {
3429 const int var =
ct->automaton().vars(v);
3431 affine_relations.push_back(r);
3432 if (r.representative ==
var) {
3436 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
3437 r.offset != affine_relations[v - 1].offset)) {
3444 for (
int v = 0; v <
proto.vars_size(); ++v) {
3447 const AffineRelation::Relation rep = affine_relations.front();
3449 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3450 const int64 label =
proto.transition_label(t);
3451 int64 inverse_label = (label - rep.offset) / rep.coeff;
3452 if (inverse_label * rep.coeff + rep.offset == label) {
3453 if (new_size != t) {
3454 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3455 proto.set_transition_head(new_size,
proto.transition_head(t));
3457 proto.set_transition_label(new_size, inverse_label);
3461 if (new_size <
proto.transition_tail_size()) {
3462 proto.mutable_transition_tail()->Truncate(new_size);
3463 proto.mutable_transition_label()->Truncate(new_size);
3464 proto.mutable_transition_head()->Truncate(new_size);
3472 for (
int v = 1; v <
proto.vars_size(); ++v) {
3477 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3478 const int64 label =
proto.transition_label(t);
3479 if (hull.Contains(label)) {
3480 if (new_size != t) {
3481 proto.set_transition_tail(new_size,
proto.transition_tail(t));
3482 proto.set_transition_label(new_size, label);
3483 proto.set_transition_head(new_size,
proto.transition_head(t));
3488 if (new_size <
proto.transition_tail_size()) {
3489 proto.mutable_transition_tail()->Truncate(new_size);
3490 proto.mutable_transition_label()->Truncate(new_size);
3491 proto.mutable_transition_head()->Truncate(new_size);
3496 const int n =
proto.vars_size();
3497 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
3500 std::vector<std::set<int64>> reachable_states(n + 1);
3501 reachable_states[0].insert(
proto.starting_state());
3502 reachable_states[n] = {
proto.final_states().begin(),
3503 proto.final_states().end()};
3510 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3512 const int64 label =
proto.transition_label(t);
3516 reachable_states[
time + 1].insert(
head);
3520 std::vector<std::set<int64>> reached_values(n);
3524 std::set<int64> new_set;
3525 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
3527 const int64 label =
proto.transition_label(t);
3533 new_set.insert(
tail);
3534 reached_values[
time].insert(label);
3536 reachable_states[
time].swap(new_set);
3539 bool removed_values =
false;
3544 {reached_values[time].begin(), reached_values[time].end()}),
3549 if (removed_values) {
3555 bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
3559 bool changed =
false;
3561 ReservoirConstraintProto& mutable_reservoir = *
ct->mutable_reservoir();
3562 if (mutable_reservoir.actives().empty()) {
3564 for (
int i = 0; i < mutable_reservoir.times_size(); ++i) {
3565 mutable_reservoir.add_actives(true_literal);
3570 const auto& demand_is_null = [&](
int i) {
3571 return mutable_reservoir.demands(i) == 0 ||
3577 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3578 if (demand_is_null(i)) num_zeros++;
3581 if (num_zeros > 0) {
3584 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3585 if (demand_is_null(i))
continue;
3586 mutable_reservoir.set_demands(new_size, mutable_reservoir.demands(i));
3587 mutable_reservoir.set_times(new_size, mutable_reservoir.times(i));
3588 mutable_reservoir.set_actives(new_size, mutable_reservoir.actives(i));
3592 mutable_reservoir.mutable_demands()->Truncate(new_size);
3593 mutable_reservoir.mutable_times()->Truncate(new_size);
3594 mutable_reservoir.mutable_actives()->Truncate(new_size);
3597 "reservoir: remove zero demands or inactive events.");
3600 const int num_events = mutable_reservoir.demands_size();
3601 int64 gcd = mutable_reservoir.demands().empty()
3603 : std::abs(mutable_reservoir.demands(0));
3604 int num_positives = 0;
3605 int num_negatives = 0;
3606 int64 sum_of_demands = 0;
3607 int64 max_sum_of_positive_demands = 0;
3608 int64 min_sum_of_negative_demands = 0;
3609 for (
int i = 0; i < num_events; ++i) {
3610 const int64 demand = mutable_reservoir.demands(i);
3611 sum_of_demands +=
demand;
3615 max_sum_of_positive_demands +=
demand;
3619 min_sum_of_negative_demands +=
demand;
3623 if (min_sum_of_negative_demands >= mutable_reservoir.min_level() &&
3624 max_sum_of_positive_demands <= mutable_reservoir.max_level()) {
3626 return RemoveConstraint(
ct);
3629 if (min_sum_of_negative_demands > mutable_reservoir.max_level() ||
3630 max_sum_of_positive_demands < mutable_reservoir.min_level()) {
3635 if (min_sum_of_negative_demands > mutable_reservoir.min_level()) {
3636 mutable_reservoir.set_min_level(min_sum_of_negative_demands);
3638 "reservoir: increase min_level to reachable value");
3641 if (max_sum_of_positive_demands < mutable_reservoir.max_level()) {
3642 mutable_reservoir.set_max_level(max_sum_of_positive_demands);
3643 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
3646 if (mutable_reservoir.min_level() <= 0 &&
3647 mutable_reservoir.max_level() >= 0 &&
3648 (num_positives == 0 || num_negatives == 0)) {
3652 context_->
working_model->add_constraints()->mutable_linear();
3653 int64 fixed_contrib = 0;
3654 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3655 const int64 demand = mutable_reservoir.demands(i);
3658 const int active = mutable_reservoir.actives(i);
3660 sum->add_vars(active);
3664 sum->add_coeffs(-
demand);
3668 sum->add_domain(mutable_reservoir.min_level() - fixed_contrib);
3669 sum->add_domain(mutable_reservoir.max_level() - fixed_contrib);
3671 return RemoveConstraint(
ct);
3675 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3676 mutable_reservoir.set_demands(i, mutable_reservoir.demands(i) / gcd);
3682 const Domain reduced_domain =
3683 Domain({mutable_reservoir.min_level(), mutable_reservoir.max_level()})
3684 .InverseMultiplicationBy(gcd);
3685 mutable_reservoir.set_min_level(reduced_domain.Min());
3686 mutable_reservoir.set_max_level(reduced_domain.Max());
3687 context_->
UpdateRuleStats(
"reservoir: simplify demands and levels by gcd.");
3690 if (num_positives == 1 && num_negatives > 0) {
3692 "TODO reservoir: one producer, multiple consumers.");
3695 absl::flat_hash_set<std::pair<int, int>> time_active_set;
3696 for (
int i = 0; i < mutable_reservoir.demands_size(); ++i) {
3697 const std::pair<int, int> key = std::make_pair(
3698 mutable_reservoir.times(i), mutable_reservoir.actives(i));
3699 if (time_active_set.contains(key)) {
3700 context_->
UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
3703 time_active_set.insert(key);
3713 void CpModelPresolver::ExtractBoolAnd() {
3714 absl::flat_hash_map<int, int> ref_to_bool_and;
3715 const int num_constraints = context_->
working_model->constraints_size();
3716 std::vector<int> to_remove;
3717 for (
int c = 0; c < num_constraints; ++c) {
3721 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr &&
3722 ct.bool_or().literals().size() == 2) {
3726 to_remove.push_back(c);
3730 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne &&
3731 ct.at_most_one().literals().size() == 2) {
3732 AddImplication(
ct.at_most_one().literals(0),
3735 to_remove.push_back(c);
3741 for (
const int c : to_remove) {
3742 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3748 void CpModelPresolver::Probe() {
3752 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
3772 auto* local_param =
model.GetOrCreate<SatParameters>();
3773 *local_param = context_->
params();
3774 local_param->set_use_implied_bounds(
false);
3776 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(
3778 model.Register<ModelRandomGenerator>(context_->
random());
3779 auto* encoder =
model.GetOrCreate<IntegerEncoder>();
3780 encoder->DisableImplicationBetweenLiteral();
3781 auto* mapping =
model.GetOrCreate<CpModelMapping>();
3785 auto* sat_solver =
model.GetOrCreate<SatSolver>();
3786 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
3787 if (mapping->ConstraintIsAlreadyLoaded(&
ct))
continue;
3789 if (sat_solver->IsModelUnsat()) {
3793 encoder->AddAllImplicationsBetweenAssociatedLiterals();
3794 if (!sat_solver->Propagate()) {
3802 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
3803 auto* prober =
model.GetOrCreate<Prober>();
3804 prober->ProbeBooleanVariables(1.0);
3806 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
3807 if (sat_solver->IsModelUnsat() || !implication_graph->DetectEquivalences()) {
3812 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
3813 for (
int i = 0; i < sat_solver->LiteralTrail().
Index(); ++i) {
3814 const Literal l = sat_solver->LiteralTrail()[i];
3815 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
3822 const int num_variables = context_->
working_model->variables().size();
3823 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
3824 for (
int var = 0;
var < num_variables; ++
var) {
3827 if (!mapping->IsBoolean(
var)) {
3830 integer_trail->InitialVariableDomain(mapping->Integer(
var)))) {
3837 const Literal l = mapping->Literal(
var);
3838 const Literal r = implication_graph->RepresentativeOf(l);
3841 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
3851 void CpModelPresolver::PresolvePureSatPart() {
3856 const int num_variables = context_->
working_model->variables_size();
3857 SatPostsolver sat_postsolver(num_variables);
3858 SatPresolver sat_presolver(&sat_postsolver);
3859 sat_presolver.SetNumVariables(num_variables);
3860 sat_presolver.SetTimeLimit(context_->
time_limit());
3862 SatParameters params = context_->
params();
3869 if (params.cp_model_postsolve_with_full_solver()) {
3870 params.set_presolve_blocked_clause(
false);
3876 params.set_presolve_use_bva(
false);
3877 sat_presolver.SetParameters(params);
3880 absl::flat_hash_set<int> used_variables;
3881 auto convert = [&used_variables](
int ref) {
3883 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
3884 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
3892 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
3894 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
3895 ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3914 std::vector<Literal> clause;
3915 int num_removed_constraints = 0;
3916 for (
int i = 0; i < context_->
working_model->constraints_size(); ++i) {
3919 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolOr) {
3920 ++num_removed_constraints;
3922 for (
const int ref :
ct.bool_or().literals()) {
3923 clause.push_back(convert(ref));
3925 for (
const int ref :
ct.enforcement_literal()) {
3926 clause.push_back(convert(ref).Negated());
3928 sat_presolver.AddClause(clause);
3935 if (
ct.constraint_case() == ConstraintProto::ConstraintCase::kBoolAnd) {
3936 ++num_removed_constraints;
3937 std::vector<Literal> clause;
3938 for (
const int ref :
ct.enforcement_literal()) {
3939 clause.push_back(convert(ref).Negated());
3942 for (
const int ref :
ct.bool_and().literals()) {
3943 clause.back() = convert(ref);
3944 sat_presolver.AddClause(clause);
3954 if (num_removed_constraints == 0)
return;
3964 std::vector<bool> can_be_removed(num_variables,
false);
3965 for (
int i = 0; i < num_variables; ++i) {
3967 can_be_removed[i] =
true;
3973 if (used_variables.contains(i) && context_->
IsFixed(i)) {
3975 sat_presolver.AddClause({convert(i)});
3977 sat_presolver.AddClause({convert(
NegatedRef(i))});
3985 const int num_passes = params.presolve_use_bva() ? 4 : 1;
3986 for (
int i = 0; i < num_passes; ++i) {
3987 const int old_num_clause = sat_postsolver.NumClauses();
3988 if (!sat_presolver.Presolve(can_be_removed, context_->
log_info())) {
3989 VLOG(1) <<
"UNSAT during SAT presolve.";
3992 if (old_num_clause == sat_postsolver.NumClauses())
break;
3996 const int new_num_variables = sat_presolver.NumVariables();
3997 if (new_num_variables > context_->
working_model->variables_size()) {
3998 VLOG(1) <<
"New variables added by the SAT presolver.";
4000 i < new_num_variables; ++i) {
4001 IntegerVariableProto* var_proto =
4003 var_proto->add_domain(0);
4004 var_proto->add_domain(1);
4010 ExtractClauses(
true, sat_presolver, context_->
working_model);
4018 ExtractClauses(
false, sat_postsolver,
4026 void CpModelPresolver::ExpandObjective() {
4045 int unique_expanded_constraint = -1;
4046 const bool objective_was_a_single_variable =
4051 const int num_variables = context_->
working_model->variables_size();
4052 const int num_constraints = context_->
working_model->constraints_size();
4053 absl::flat_hash_set<int> relevant_constraints;
4054 std::vector<int> var_to_num_relevant_constraints(num_variables, 0);
4055 for (
int ct_index = 0; ct_index < num_constraints; ++ct_index) {
4056 const ConstraintProto&
ct = context_->
working_model->constraints(ct_index);
4058 if (!
ct.enforcement_literal().empty() ||
4059 ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear ||
4060 ct.linear().domain().size() != 2 ||
4061 ct.linear().domain(0) !=
ct.linear().domain(1)) {
4065 relevant_constraints.insert(ct_index);
4066 const int num_terms =
ct.linear().vars_size();
4067 for (
int i = 0; i < num_terms; ++i) {
4068 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]++;
4072 std::set<int> var_to_process;
4074 const int var = entry.first;
4076 if (var_to_num_relevant_constraints[
var] != 0) {
4077 var_to_process.insert(
var);
4082 int num_expansions = 0;
4083 absl::flat_hash_set<int> processed_vars;
4084 std::vector<int> new_vars_in_objective;
4085 while (!relevant_constraints.empty()) {
4087 int objective_var = -1;
4088 while (!var_to_process.empty()) {
4089 const int var = *var_to_process.begin();
4090 CHECK(!processed_vars.contains(
var));
4091 if (var_to_num_relevant_constraints[
var] == 0) {
4092 processed_vars.insert(
var);
4093 var_to_process.erase(
var);
4098 var_to_process.erase(
var);
4101 objective_var =
var;
4105 if (objective_var == -1)
break;
4107 processed_vars.insert(objective_var);
4108 var_to_process.erase(objective_var);
4110 int expanded_linear_index = -1;
4111 int64 objective_coeff_in_expanded_constraint;
4112 int64 size_of_expanded_constraint = 0;
4113 const auto& non_deterministic_list =
4115 std::vector<int> constraints_with_objective(non_deterministic_list.begin(),
4116 non_deterministic_list.end());
4117 std::sort(constraints_with_objective.begin(),
4118 constraints_with_objective.end());
4119 for (
const int ct_index : constraints_with_objective) {
4120 if (relevant_constraints.count(ct_index) == 0)
continue;
4121 const ConstraintProto&
ct =
4126 relevant_constraints.erase(ct_index);
4127 const int num_terms =
ct.linear().vars_size();
4128 for (
int i = 0; i < num_terms; ++i) {
4129 var_to_num_relevant_constraints[
PositiveRef(
ct.linear().vars(i))]--;
4141 bool is_present =
false;
4142 int64 objective_coeff;
4143 for (
int i = 0; i < num_terms; ++i) {
4144 const int ref =
ct.linear().vars(i);
4145 const int64 coeff =
ct.linear().coeffs(i);
4147 CHECK(!is_present) <<
"Duplicate variables not supported.";
4149 objective_coeff = (ref == objective_var) ? coeff : -coeff;
4162 if (std::abs(objective_coeff) == 1 &&
4163 num_terms > size_of_expanded_constraint) {
4164 expanded_linear_index = ct_index;
4165 size_of_expanded_constraint = num_terms;
4166 objective_coeff_in_expanded_constraint = objective_coeff;
4170 if (expanded_linear_index != -1) {
4171 context_->
UpdateRuleStats(
"objective: expanded objective constraint.");
4175 CHECK_EQ(std::abs(objective_coeff_in_expanded_constraint), 1);
4176 const ConstraintProto&
ct =
4177 context_->
working_model->constraints(expanded_linear_index);
4179 objective_var, objective_coeff_in_expanded_constraint,
ct,
4180 &new_vars_in_objective);
4183 for (
const int var : new_vars_in_objective) {
4184 if (!processed_vars.contains(
var)) var_to_process.insert(
var);
4197 for (
int i = 0; i < size_of_expanded_constraint; ++i) {
4198 const int ref =
ct.linear().vars(i);
4203 -
ct.linear().coeffs(i)))
4204 .RelaxIfTooComplex();
4206 implied_domain = implied_domain.InverseMultiplicationBy(
4207 objective_coeff_in_expanded_constraint);
4211 if (implied_domain.IsIncludedIn(context_->
DomainOf(objective_var))) {
4212 context_->
UpdateRuleStats(
"objective: removed objective constraint.");
4214 context_->
working_model->mutable_constraints(expanded_linear_index)
4218 unique_expanded_constraint = expanded_linear_index;
4228 if (num_expansions == 1 && objective_was_a_single_variable &&
4229 unique_expanded_constraint != -1) {
4231 "objective: removed unique objective constraint.");
4232 ConstraintProto* mutable_ct = context_->
working_model->mutable_constraints(
4233 unique_expanded_constraint);
4234 *(context_->
mapping_model->add_constraints()) = *mutable_ct;
4235 mutable_ct->Clear();
4247 void CpModelPresolver::MergeNoOverlapConstraints() {
4250 const int num_constraints = context_->
working_model->constraints_size();
4251 int old_num_no_overlaps = 0;
4252 int old_num_intervals = 0;
4255 std::vector<int> disjunctive_index;
4256 std::vector<std::vector<Literal>> cliques;
4257 for (
int c = 0; c < num_constraints; ++c) {
4259 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kNoOverlap) {
4262 std::vector<Literal> clique;
4263 for (
const int i :
ct.no_overlap().intervals()) {
4264 clique.push_back(Literal(BooleanVariable(i),
true));
4266 cliques.push_back(clique);
4267 disjunctive_index.push_back(c);
4269 old_num_no_overlaps++;
4270 old_num_intervals += clique.size();
4272 if (old_num_no_overlaps == 0)
return;
4276 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
4277 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4278 graph->Resize(num_constraints);
4279 for (
const std::vector<Literal>& clique : cliques) {
4282 CHECK(graph->AddAtMostOne(clique));
4284 CHECK(graph->DetectEquivalences());
4285 graph->TransformIntoMaxCliques(
4286 &cliques, context_->
params().merge_no_overlap_work_limit());
4289 int new_num_no_overlaps = 0;
4290 int new_num_intervals = 0;
4291 for (
int i = 0; i < cliques.size(); ++i) {
4292 const int ct_index = disjunctive_index[i];
4293 ConstraintProto*
ct =
4296 if (cliques[i].empty())
continue;
4297 for (
const Literal l : cliques[i]) {
4298 CHECK(l.IsPositive());
4299 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
4301 new_num_no_overlaps++;
4302 new_num_intervals += cliques[i].size();
4304 if (old_num_intervals != new_num_intervals ||
4305 old_num_no_overlaps != new_num_no_overlaps) {
4306 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
4307 old_num_intervals,
" intervals) into ",
4308 new_num_no_overlaps,
" no-overlaps (",
4309 new_num_intervals,
" intervals).");
4318 void CpModelPresolver::TransformIntoMaxCliques() {
4321 auto convert = [](
int ref) {
4322 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
4323 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
4325 const int num_constraints = context_->
working_model->constraints_size();
4328 std::vector<std::vector<Literal>> cliques;
4330 for (
int c = 0; c < num_constraints; ++c) {
4331 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4332 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne) {
4333 std::vector<Literal> clique;
4334 for (
const int ref :
ct->at_most_one().literals()) {
4335 clique.push_back(convert(ref));
4337 cliques.push_back(clique);
4338 if (RemoveConstraint(
ct)) {
4341 }
else if (
ct->constraint_case() ==
4342 ConstraintProto::ConstraintCase::kBoolAnd) {
4343 if (
ct->enforcement_literal().size() != 1)
continue;
4344 const Literal enforcement = convert(
ct->enforcement_literal(0));
4345 for (
const int ref :
ct->bool_and().literals()) {
4346 if (ref ==
ct->enforcement_literal(0))
continue;
4347 cliques.push_back({enforcement, convert(ref).Negated()});
4349 if (RemoveConstraint(
ct)) {
4355 const int num_old_cliques = cliques.size();
4359 const int num_variables = context_->
working_model->variables().size();
4360 local_model.GetOrCreate<Trail>()->Resize(num_variables);
4361 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
4362 graph->Resize(num_variables);
4363 for (
const std::vector<Literal>& clique : cliques) {
4364 if (!graph->AddAtMostOne(clique)) {
4368 if (!graph->DetectEquivalences()) {
4371 graph->TransformIntoMaxCliques(
4372 &cliques, context_->
params().merge_at_most_one_work_limit());
4377 for (
int var = 0;
var < num_variables; ++
var) {
4378 const Literal l = Literal(BooleanVariable(
var),
true);
4379 if (graph->RepresentativeOf(l) != l) {
4380 const Literal r = graph->RepresentativeOf(l);
4382 var, r.IsPositive() ? r.Variable().value()
4387 int num_new_cliques = 0;
4388 for (
const std::vector<Literal>& clique : cliques) {
4389 if (clique.empty())
continue;
4392 for (
const Literal
literal : clique) {
4394 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
4396 ct->mutable_at_most_one()->add_literals(
4402 PresolveAtMostOne(
ct);
4405 if (num_new_cliques != num_old_cliques) {
4406 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
4410 LOG(
INFO) <<
"Merged " << num_old_cliques <<
" into " << num_new_cliques
4417 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4420 if (ExploitEquivalenceRelations(c,
ct)) {
4425 if (PresolveEnforcementLiteral(
ct)) {
4430 switch (
ct->constraint_case()) {
4431 case ConstraintProto::ConstraintCase::kBoolOr:
4432 return PresolveBoolOr(
ct);
4433 case ConstraintProto::ConstraintCase::kBoolAnd:
4434 return PresolveBoolAnd(
ct);
4435 case ConstraintProto::ConstraintCase::kAtMostOne:
4436 return PresolveAtMostOne(
ct);
4437 case ConstraintProto::ConstraintCase::kExactlyOne:
4438 return PresolveExactlyOne(
ct);
4439 case ConstraintProto::ConstraintCase::kBoolXor:
4440 return PresolveBoolXor(
ct);
4441 case ConstraintProto::ConstraintCase::kIntMax:
4442 if (
ct->int_max().vars_size() == 2 &&
4444 return PresolveIntAbs(
ct);
4446 return PresolveIntMax(
ct);
4448 case ConstraintProto::ConstraintCase::kIntMin:
4449 return PresolveIntMin(
ct);
4450 case ConstraintProto::ConstraintCase::kLinMax:
4451 return PresolveLinMax(
ct);
4452 case ConstraintProto::ConstraintCase::kLinMin:
4453 return PresolveLinMin(
ct);
4454 case ConstraintProto::ConstraintCase::kIntProd:
4455 return PresolveIntProd(
ct);
4456 case ConstraintProto::ConstraintCase::kIntDiv:
4457 return PresolveIntDiv(
ct);
4458 case ConstraintProto::ConstraintCase::kLinear: {
4459 if (CanonicalizeLinear(
ct)) {
4462 if (PresolveSmallLinear(
ct)) {
4465 if (PropagateDomainsInLinear(c,
ct)) {
4468 if (PresolveSmallLinear(
ct)) {
4472 if (RemoveSingletonInLinear(
ct)) {
4477 if (PresolveSmallLinear(
ct)) {
4481 if (PresolveLinearOnBooleans(
ct)) {
4484 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kLinear) {
4485 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
4486 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
4487 if (
ct->constraint_case() ==
4488 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4492 if (
ct->enforcement_literal_size() > old_num_enforcement_literals &&
4493 PresolveSmallLinear(
ct)) {
4496 PresolveLinearEqualityModuloTwo(
ct);
4500 case ConstraintProto::ConstraintCase::kInterval:
4501 return PresolveInterval(c,
ct);
4502 case ConstraintProto::ConstraintCase::kElement:
4503 return PresolveElement(
ct);
4504 case ConstraintProto::ConstraintCase::kTable:
4505 return PresolveTable(
ct);
4506 case ConstraintProto::ConstraintCase::kAllDiff:
4507 return PresolveAllDiff(
ct);
4508 case ConstraintProto::ConstraintCase::kNoOverlap:
4509 return PresolveNoOverlap(
ct);
4510 case ConstraintProto::ConstraintCase::kCumulative:
4511 return PresolveCumulative(
ct);
4512 case ConstraintProto::ConstraintCase::kCircuit:
4513 return PresolveCircuit(
ct);
4514 case ConstraintProto::ConstraintCase::kRoutes:
4515 return PresolveRoutes(
ct);
4516 case ConstraintProto::ConstraintCase::kAutomaton:
4517 return PresolveAutomaton(
ct);
4518 case ConstraintProto::ConstraintCase::kReservoir:
4519 return PresolveReservoir(
ct);
4528 bool CpModelPresolver::ProcessSetPPCSubset(
4529 int c1,
int c2,
const std::vector<int>& c2_minus_c1,
4530 const std::vector<int>& original_constraint_index,
4531 std::vector<bool>* removed) {
4534 CHECK(!(*removed)[c1]);
4535 CHECK(!(*removed)[c2]);
4537 ConstraintProto* ct1 = context_->
working_model->mutable_constraints(
4538 original_constraint_index[c1]);
4539 ConstraintProto* ct2 = context_->
working_model->mutable_constraints(
4540 original_constraint_index[c2]);
4542 if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4543 ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4544 (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4545 ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4550 for (
const int literal : c2_minus_c1) {
4556 if (ct2->constraint_case() != ConstraintProto::kExactlyOne) {
4557 ConstraintProto copy = *ct2;
4558 (*ct2->mutable_exactly_one()->mutable_literals()) =
4559 copy.at_most_one().literals();
4563 (*removed)[c1] =
true;
4569 if ((ct1->constraint_case() == ConstraintProto::kBoolOr ||
4570 ct1->constraint_case() == ConstraintProto::kExactlyOne) &&
4571 ct2->constraint_case() == ConstraintProto::kBoolOr) {
4574 (*removed)[c2] =
true;
4580 if (ct1->constraint_case() == ConstraintProto::kAtMostOne &&
4581 (ct2->constraint_case() == ConstraintProto::kAtMostOne ||
4582 ct2->constraint_case() == ConstraintProto::kExactlyOne)) {
4584 (*removed)[c1] =
true;
4600 bool CpModelPresolver::ProcessSetPPC() {
4601 const int num_constraints = context_->
working_model->constraints_size();
4605 std::vector<uint64> signatures;
4609 std::vector<std::vector<int>> constraint_literals;
4613 std::vector<std::vector<int>> literals_to_constraints;
4616 std::vector<bool> removed;
4620 std::vector<int> original_constraint_index;
4624 int num_setppc_constraints = 0;
4625 for (
int c = 0; c < num_constraints; ++c) {
4626 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
4627 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4628 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4629 ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4638 if (
ct->constraint_case() == ConstraintProto::ConstraintCase::kBoolOr ||
4639 ct->constraint_case() == ConstraintProto::ConstraintCase::kAtMostOne ||
4640 ct->constraint_case() == ConstraintProto::ConstraintCase::kExactlyOne) {
4641 constraint_literals.push_back(GetLiteralsFromSetPPCConstraint(*
ct));
4644 for (
const int literal : constraint_literals.back()) {
4646 signature |= (
int64{1} << (positive_literal % 64));
4648 if (positive_literal >= literals_to_constraints.size()) {
4649 literals_to_constraints.resize(positive_literal + 1);
4651 literals_to_constraints[positive_literal].push_back(
4652 num_setppc_constraints);
4654 signatures.push_back(signature);
4655 removed.push_back(
false);
4656 original_constraint_index.push_back(c);
4657 num_setppc_constraints++;
4660 VLOG(1) <<
"#setppc constraints: " << num_setppc_constraints;
4663 absl::flat_hash_set<std::pair<int, int>> compared_constraints;
4664 for (
const std::vector<int>& literal_to_constraints :
4665 literals_to_constraints) {
4666 for (
int index1 = 0; index1 < literal_to_constraints.size(); ++index1) {
4669 const int c1 = literal_to_constraints[index1];
4670 if (removed[c1])
continue;
4671 const std::vector<int>& c1_literals = constraint_literals[c1];
4673 for (
int index2 = index1 + 1; index2 < literal_to_constraints.size();
4675 const int c2 = literal_to_constraints[index2];
4676 if (removed[c2])
continue;
4677 if (removed[c1])
break;
4680 if (c1 == c2)
continue;
4684 std::pair<int, int>(c1, c2))) {
4687 compared_constraints.insert({c1, c2});
4691 if (compared_constraints.size() >= 50000)
return true;
4693 const bool smaller = (signatures[c1] & ~signatures[c2]) == 0;
4694 const bool larger = (signatures[c2] & ~signatures[c1]) == 0;
4695 if (!(smaller || larger))
continue;
4698 const std::vector<int>& c2_literals = constraint_literals[c2];
4702 std::vector<int> c1_minus_c2;
4704 std::vector<int> c2_minus_c1;
4707 if (c1_minus_c2.empty()) {
4708 if (!ProcessSetPPCSubset(c1, c2, c2_minus_c1,
4709 original_constraint_index, &removed)) {
4712 }
else if (c2_minus_c1.empty()) {
4713 if (!ProcessSetPPCSubset(c2, c1, c1_minus_c2,
4714 original_constraint_index, &removed)) {
4725 void CpModelPresolver::TryToSimplifyDomain(
int var) {
4733 if (r.representative !=
var)
return;
4748 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
4753 if (domain.NumIntervals() != domain.Size())
return;
4755 const int64 var_min = domain.Min();
4756 int64 gcd = domain[1].start - var_min;
4758 const ClosedInterval& i = domain[
index];
4760 const int64 shifted_value = i.start - var_min;
4764 if (gcd == 1)
break;
4766 if (gcd == 1)
return;
4770 std::vector<int64> scaled_values;
4772 const ClosedInterval& i = domain[
index];
4774 const int64 shifted_value = i.start - var_min;
4775 scaled_values.push_back(shifted_value / gcd);
4787 void CpModelPresolver::EncodeAllAffineRelations() {
4788 int64 num_added = 0;
4793 if (r.representative ==
var)
continue;
4800 if (!PresolveAffineRelationIfAny(
var))
break;
4807 auto* arg =
ct->mutable_linear();
4810 arg->add_vars(r.representative);
4811 arg->add_coeffs(-r.coeff);
4812 arg->add_domain(r.offset);
4813 arg->add_domain(r.offset);
4821 if (context_->
log_info() && num_added > 0) {
4822 LOG(
INFO) << num_added <<
" affine relations still in the model.";
4827 bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
4831 if (r.representative ==
var)
return true;
4850 auto* arg =
ct->mutable_linear();
4853 arg->add_vars(r.representative);
4854 arg->add_coeffs(-r.coeff);
4855 arg->add_domain(r.offset);
4856 arg->add_domain(r.offset);
4862 void CpModelPresolver::PresolveToFixPoint() {
4866 const int64 max_num_operations =
4867 context_->
params().cp_model_max_num_presolve_operations() > 0
4868 ? context_->
params().cp_model_max_num_presolve_operations()
4874 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
4879 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
4881 std::deque<int> queue;
4882 for (
int c = 0; c < in_queue.size(); ++c) {
4883 if (context_->
working_model->constraints(c).constraint_case() !=
4884 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
4894 if (context_->
params().permute_presolve_constraint_order()) {
4895 std::shuffle(queue.begin(), queue.end(), *context_->
random());
4897 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
4898 const int score_a = context_->ConstraintToVars(a).size();
4899 const int score_b = context_->ConstraintToVars(b).size();
4900 return score_a < score_b || (score_a == score_b && a < b);
4910 const int c = queue.front();
4911 in_queue[c] =
false;
4914 const int old_num_constraint =
4918 LOG(
INFO) <<
"Unsat after presolving constraint #" << c
4919 <<
" (warning, dump might be inconsistent): "
4920 << context_->
working_model->constraints(c).ShortDebugString();
4924 const int new_num_constraints =
4926 if (new_num_constraints > old_num_constraint) {
4928 in_queue.resize(new_num_constraints,
true);
4929 for (
int c = old_num_constraint; c < new_num_constraints; ++c) {
4946 const int current_num_variables = context_->
working_model->variables_size();
4947 for (
int v = 0; v < current_num_variables; ++v) {
4949 if (!PresolveAffineRelationIfAny(v))
return;
4954 TryToSimplifyDomain(v);
4963 in_queue.resize(context_->
working_model->constraints_size(),
false);
4968 if (c >= 0 && !in_queue[c]) {
4978 const int num_vars = context_->
working_model->variables_size();
4979 for (
int v = 0; v < num_vars; ++v) {
4981 if (constraints.size() != 1)
continue;
4982 const int c = *constraints.begin();
4983 if (c < 0)
continue;
4989 std::pair<int, int>(v, c))) {
4992 var_constraint_pair_already_called.insert({v, c});
5002 std::sort(queue.begin(), queue.end());
5016 VarDomination var_dom;
5017 DualBoundStrengthening dual_bound_strengthening;
5019 if (!dual_bound_strengthening.Strengthen(context_))
return;
5036 const int num_constraints = context_->
working_model->constraints_size();
5037 for (
int c = 0; c < num_constraints; ++c) {
5038 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5039 switch (
ct->constraint_case()) {
5040 case ConstraintProto::ConstraintCase::kNoOverlap:
5042 if (PresolveNoOverlap(
ct)) {
5046 case ConstraintProto::ConstraintCase::kNoOverlap2D:
5050 case ConstraintProto::ConstraintCase::kCumulative:
5052 if (PresolveCumulative(
ct)) {
5056 case ConstraintProto::ConstraintCase::kBoolOr: {
5059 for (
const auto& pair :
5061 bool modified =
false;
5082 <<
" affine relations were detected.";
5084 <<
" variable equivalence relations were detected.";
5085 std::map<std::string, int> sorted_rules(
context->stats_by_rule_name.begin(),
5086 context->stats_by_rule_name.end());
5087 for (
const auto& entry : sorted_rules) {
5088 if (entry.second == 1) {
5089 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied 1 time.";
5091 LOG(
INFO) <<
"- rule '" << entry.first <<
"' was applied " << entry.second
5102 std::vector<int>* postsolve_mapping) {
5108 std::vector<int>* postsolve_mapping)
5109 : postsolve_mapping_(postsolve_mapping), context_(
context) {
5112 context_->
params().keep_all_feasible_solutions_in_presolve() ||
5113 context_->
params().enumerate_all_solutions() ||
5114 context_->
params().fill_tightened_domains_in_response() ||
5115 !context_->
params().cp_model_presolve();
5118 for (
const auto& decision_strategy :
5120 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
5155 if (!context_->
params().cp_model_presolve()) {
5167 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
5168 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5169 PresolveEnforcementLiteral(
ct);
5170 switch (
ct->constraint_case()) {
5171 case ConstraintProto::ConstraintCase::kBoolOr:
5174 case ConstraintProto::ConstraintCase::kBoolAnd:
5175 PresolveBoolAnd(
ct);
5177 case ConstraintProto::ConstraintCase::kAtMostOne:
5178 PresolveAtMostOne(
ct);
5180 case ConstraintProto::ConstraintCase::kExactlyOne:
5181 PresolveExactlyOne(
ct);
5183 case ConstraintProto::ConstraintCase::kLinear:
5184 CanonicalizeLinear(
ct);
5196 for (
int iter = 0; iter < context_->
params().max_presolve_iterations();
5201 int old_num_non_empty_constraints = 0;
5202 for (
int c = 0; c < context_->
working_model->constraints_size(); ++c) {
5205 if (type == ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET)
continue;
5206 old_num_non_empty_constraints++;
5213 PresolveToFixPoint();
5220 if (context_->
params().cp_model_probing_level() > 0) {
5223 PresolveToFixPoint();
5230 if (context_->
params().cp_model_use_sat_presolve()) {
5232 PresolvePureSatPart();
5245 const int old_size = context_->
working_model->constraints_size();
5246 for (
int c = 0; c < old_size; ++c) {
5247 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
5248 if (
ct->constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
5251 ExtractAtMostOneFromLinear(
ct);
5256 if (iter == 0) TransformIntoMaxCliques();
5273 PresolveToFixPoint();
5279 old_num_non_empty_constraints)) {
5286 MergeNoOverlapConstraints();
5296 EncodeAllAffineRelations();
5303 const std::vector<int> duplicates =
5305 if (!duplicates.empty()) {
5306 for (
const int c : duplicates) {
5309 if (type == ConstraintProto::ConstraintCase::kInterval) {
5326 context_->
working_model->add_constraints()->mutable_bool_or();
5338 absl::flat_hash_set<int> used_variables;
5339 for (DecisionStrategyProto& strategy :
5341 DecisionStrategyProto copy = strategy;
5342 strategy.clear_variables();
5343 for (
const int ref : copy.variables()) {
5352 used_variables.insert(
var);
5360 strategy.add_variables(rep);
5361 if (strategy.variable_selection_strategy() !=
5362 DecisionStrategyProto::CHOOSE_FIRST) {
5363 DecisionStrategyProto::AffineTransformation* t =
5364 strategy.add_transformations();
5367 t->set_positive_coeff(std::abs(r.
coeff));
5375 strategy.add_variables(ref);
5381 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
5392 postsolve_mapping_->clear();
5393 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
5394 for (
int i = 0; i < context_->
working_model->variables_size(); ++i) {
5399 mapping[i] = postsolve_mapping_->size();
5400 postsolve_mapping_->push_back(i);
5403 if (context_->
params().permute_variable_randomly()) {
5404 std::shuffle(postsolve_mapping_->begin(), postsolve_mapping_->end(),
5406 for (
int i = 0; i < postsolve_mapping_->size(); ++i) {
5407 mapping[(*postsolve_mapping_)[i]] = i;
5432 if (!error.empty()) {
5434 LOG(
INFO) <<
"Error while validating postsolved model: " << error;
5441 if (!error.empty()) {
5443 LOG(
INFO) <<
"Error while validating mapping_model model: " << error;
5457 auto mapping_function = [&mapping](
int* ref) {
5462 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
5468 if (
proto->has_objective()) {
5469 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
5470 mapping_function(&mutable_ref);
5475 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
5476 mapping_function(&mutable_ref);
5481 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
5482 DecisionStrategyProto copy = strategy;
5483 strategy.clear_variables();
5484 for (
const int ref : copy.variables()) {
5490 strategy.clear_transformations();
5491 for (
const auto& transform : copy.transformations()) {
5492 const int ref = transform.var();
5495 auto* new_transform = strategy.add_transformations();
5496 *new_transform = transform;
5503 if (
proto->has_solution_hint()) {
5504 auto* mutable_hint =
proto->mutable_solution_hint();
5506 for (
int i = 0; i < mutable_hint->vars_size(); ++i) {
5507 const int old_ref = mutable_hint->vars(i);
5508 const int64 old_value = mutable_hint->values(i);
5516 const int image = mapping[
var];
5518 mutable_hint->set_vars(new_size, image);
5519 mutable_hint->set_values(new_size,
value);
5524 mutable_hint->mutable_vars()->Truncate(new_size);
5525 mutable_hint->mutable_values()->Truncate(new_size);
5527 proto->clear_solution_hint();
5532 std::vector<IntegerVariableProto> new_variables;
5533 for (
int i = 0; i < mapping.size(); ++i) {
5534 const int image = mapping[i];
5535 if (image < 0)
continue;
5536 if (image >= new_variables.size()) {
5537 new_variables.resize(image + 1, IntegerVariableProto());
5539 new_variables[image].Swap(
proto->mutable_variables(i));
5541 proto->clear_variables();
5542 for (IntegerVariableProto& proto_ref : new_variables) {
5543 proto->add_variables()->Swap(&proto_ref);
5547 for (
const IntegerVariableProto& v :
proto->variables()) {
5553 std::vector<int> result;
5556 ConstraintProto copy;
5557 absl::flat_hash_map<int64, int> equiv_constraints;
5560 const int num_constraints =
model_proto.constraints().size();
5561 for (
int c = 0; c < num_constraints; ++c) {
5562 if (
model_proto.constraints(c).constraint_case() ==
5563 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
5572 s = copy.SerializeAsString();
5574 const int64 hash = std::hash<std::string>()(s);
5575 const auto insert = equiv_constraints.insert({
hash, c});
5576 if (!insert.second) {
5578 const int other_c_with_same_hash = insert.first->second;
5579 copy =
model_proto.constraints(other_c_with_same_hash);
5581 if (s == copy.SerializeAsString()) {
5582 result.push_back(c);
#define DCHECK_NE(val1, val2)
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define CHECK_GT(val1, val2)
#define DCHECK_GE(val1, val2)
#define CHECK_NE(val1, val2)
#define DCHECK_GT(val1, val2)
#define DCHECK_LT(val1, val2)
#define DCHECK(condition)
#define CHECK_LE(val1, val2)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
Domain MultiplicationBy(int64 coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
static Domain FromValues(std::vector< int64 > values)
Creates a domain from the union of an unsorted list of integer values.
int64 Size() const
Returns the number of elements in the domain.
Domain InverseMultiplicationBy(const int64 coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
ClosedInterval front() const
Domain UnionWith(const Domain &domain) const
Returns the union of D and domain.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
Domain DivisionBy(int64 coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e / coeff}.
static int64 GCD64(int64 x, int64 y)
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
void Set(IntegerType index)
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
void AdvanceDeterministicTime(double deterministic_duration)
Advances the deterministic time.
void RemoveEmptyConstraints()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
bool PresolveOneConstraint(int c)
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void MarkProcessingAsDoneForNow()
int NumDeductions() const
void AddDeduction(int literal_ref, int var, Domain domain)
bool StoreAbsRelation(int target_ref, int ref)
SparseBitset< int64 > modified_domains
bool ConstraintVariableUsageIsConsistent()
int64 num_presolve_operations
bool ModelIsUnsat() const
bool VariableIsOnlyUsedInEncoding(int ref) const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool ConstraintVariableGraphIsUpToDate() const
int GetOrCreateVarValueEncoding(int ref, int64 value)
bool DomainContains(int ref, int64 value) const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64 value)
const Domain & ObjectiveDomain() const
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
void WriteObjectiveToProto() const
int GetLiteralRepresentative(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64 value)
std::vector< int > tmp_literals
int64 MaxOf(int ref) const
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
bool ObjectiveDomainIsConstraining() const
CpModelProto * mapping_model
const std::vector< int > & ConstraintToVars(int c) const
void UpdateNewConstraintsVariableUsage()
bool VariableIsUniqueAndRemovable(int ref) const
void RemoveVariableFromAffineRelation(int var)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
bool PropagateAffineRelation(int ref)
Domain DomainOf(int ref) const
void InitializeNewDomains()
int GetVariableRepresentative(int ref) const
int NewIntVar(const Domain &domain)
void MarkVariableAsRemoved(int ref)
DomainDeductions deductions
std::vector< Domain > tmp_left_domains
bool DomainIsEmpty(int ref) const
void CanonicalizeDomainOfSizeTwo(int var)
bool LiteralIsTrue(int lit) const
void StoreBooleanEqualityRelation(int ref_a, int ref_b)
int IntervalUsage(int c) const
CpModelProto * working_model
bool SubstituteVariableInObjective(int var_in_equality, int64 coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
int GetOrCreateConstantVar(int64 cst)
bool LiteralIsFalse(int lit) const
const absl::flat_hash_map< int, int64 > & ObjectiveMap() const
void UpdateRuleStats(const std::string &name, int num_times=1)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective()
const SatParameters & params() const
void RemoveAllVariablesFromAffineRelationConstraint()
AffineRelation::Relation GetAffineRelation(int ref) const
bool VariableIsNotUsedAnymore(int ref) const
void UpdateConstraintVariableUsage(int c)
bool keep_all_feasible_solutions
bool IsFixed(int ref) const
std::vector< Domain > tmp_term_domains
const absl::flat_hash_set< int > & VarToConstraints(int var) const
ModelRandomGenerator * random()
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int64 MinOf(int ref) const
absl::flat_hash_set< int > tmp_literal_set
void ReadObjectiveFromProto()
bool CanBeUsedAsLiteral(int ref) const
void RegisterVariablesUsedInAssumptions()
void ExploitFixedDomain(int var)
bool GetAbsRelation(int target_ref, int *ref)
bool StoreAffineRelation(int ref_x, int ref_y, int64 coeff, int64 offset)
CpModelProto const * model_proto
SharedTimeLimit * time_limit
GurobiMPCallbackContext * context
static const int64 kint64max
static const int32 kint32min
static const int64 kint64min
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
void STLSetDifference(const In1 &a, const In2 &b, Out *out, Compare compare)
bool ContainsKey(const Collection &collection, const Key &key)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
bool RefIsPositive(int ref)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)
const LiteralIndex kNoLiteralIndex(-1)
bool HasEnforcementLiteral(const ConstraintProto &ct)
void ExpandCpModel(PresolveContext *context)
constexpr int kAffineRelationConstraint
bool PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
void SubstituteVariable(int var, int64 var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
void DetectDominanceRelations(const PresolveContext &context, VarDomination *var_domination, DualBoundStrengthening *dual_bound_strengthening)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
int ReindexArcs(IntContainer *tails, IntContainer *heads)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
std::vector< int > FindDuplicateConstraints(const CpModelProto &model_proto)
void LogInfoFromContext(const PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
constexpr int kObjectiveConstraint
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
std::string ValidateCpModel(const CpModelProto &model)
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...