32#include "absl/algorithm/container.h"
33#include "absl/base/attributes.h"
34#include "absl/container/btree_map.h"
35#include "absl/container/btree_set.h"
36#include "absl/container/flat_hash_map.h"
37#include "absl/container/flat_hash_set.h"
38#include "absl/container/inlined_vector.h"
39#include "absl/flags/flag.h"
40#include "absl/hash/hash.h"
41#include "absl/log/check.h"
42#include "absl/log/log.h"
43#include "absl/log/vlog_is_on.h"
44#include "absl/numeric/int128.h"
45#include "absl/random/distributions.h"
46#include "absl/status/statusor.h"
47#include "absl/strings/str_cat.h"
48#include "absl/types/span.h"
49#include "google/protobuf/arena.h"
50#include "google/protobuf/repeated_field.h"
51#include "google/protobuf/repeated_ptr_field.h"
109 result.vars[0] = IntegerVariable(2 * a);
110 result.vars[1] = IntegerVariable(2 *
b);
111 result.coeffs[0] = IntegerValue(coeff_a);
112 result.coeffs[1] = IntegerValue(coeff_b);
119 const int num_vars = linear.vars().size();
120 for (
int i = 0;
i < num_vars; ++
i) {
122 if (linear.coeffs(
i) == 0)
return false;
140 interval_representative_.clear();
141 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
143 int new_num_constraints = 0;
144 const int old_num_non_empty_constraints =
145 context_->working_model->constraints_size();
146 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
147 const auto type = context_->working_model->constraints(c).constraint_case();
150 context_->working_model->mutable_constraints(new_num_constraints)
151 ->Swap(context_->working_model->mutable_constraints(c));
156 const auto [it, inserted] = interval_representative_.insert(
157 {new_num_constraints, new_num_constraints});
158 interval_mapping[c] = it->second;
159 if (it->second != new_num_constraints) {
160 context_->UpdateRuleStats(
161 "intervals: change duplicate index across constraints");
165 new_num_constraints++;
168 context_->working_model->mutable_constraints(), new_num_constraints);
170 *context_->working_model->mutable_constraints()) {
172 [&interval_mapping](
int* ref) {
173 *ref = interval_mapping[*ref];
180bool CpModelPresolver::PresolveEnforcementLiteral(
ConstraintProto* ct) {
184 auto remove_if_not_interval = [
this, ct]() {
186 return MarkOptionalIntervalAsFalse(ct);
188 return RemoveConstraint(ct);
194 context_->tmp_literal_set.clear();
196 if (context_->LiteralIsTrue(literal)) {
198 context_->UpdateRuleStats(
"enforcement: true literal");
202 if (context_->LiteralIsFalse(literal)) {
203 context_->UpdateRuleStats(
"enforcement: false literal");
204 return remove_if_not_interval();
207 if (context_->VariableIsUniqueAndRemovable(literal)) {
209 context_->UpdateRuleStats(
"enforcement: literal not used");
210 CHECK(context_->SetLiteralToFalse(literal));
211 return remove_if_not_interval();
217 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
218 const int64_t obj_coeff =
222 context_->UpdateRuleStats(
"enforcement: literal with unique direction");
223 CHECK(context_->SetLiteralToFalse(literal));
224 return remove_if_not_interval();
233 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
235 context_->UpdateRuleStats(
"enforcement: removed duplicate literal");
238 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
239 context_->UpdateRuleStats(
"enforcement: can never be true");
240 return remove_if_not_interval();
247 return new_size != old_size;
251 if (context_->ModelIsUnsat())
return false;
254 bool changed =
false;
255 int num_true_literals = 0;
256 int true_literal = std::numeric_limits<int32_t>::min();
257 for (
const int literal : ct->bool_xor().literals()) {
264 if (context_->VariableIsUniqueAndRemovable(literal)) {
265 context_->UpdateRuleStats(
"TODO bool_xor: remove constraint");
268 if (context_->LiteralIsFalse(literal)) {
269 context_->UpdateRuleStats(
"bool_xor: remove false literal");
272 }
else if (context_->LiteralIsTrue(literal)) {
273 true_literal = literal;
278 ct->mutable_bool_xor()->set_literals(new_size++, literal);
282 if (num_true_literals % 2 == 0) {
283 return MarkConstraintAsFalse(ct,
"bool_xor: always false");
285 context_->UpdateRuleStats(
"bool_xor: always true");
286 return RemoveConstraint(ct);
290 if (num_true_literals % 2 == 0) {
291 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
292 return context_->NotifyThatModelIsUnsat(
293 "bool_xor: cannot fix last literal");
296 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
297 return context_->NotifyThatModelIsUnsat(
298 "bool_xor: cannot fix last literal");
301 context_->UpdateRuleStats(
"bool_xor: one active literal");
302 return RemoveConstraint(ct);
303 }
else if (new_size == 2) {
304 const int a = ct->bool_xor().literals(0);
305 const int b = ct->bool_xor().literals(1);
307 if (num_true_literals % 2 == 0) {
308 return MarkConstraintAsFalse(ct,
"bool_xor: always false");
310 context_->UpdateRuleStats(
"bool_xor: always true");
311 return RemoveConstraint(ct);
315 if (num_true_literals % 2 == 1) {
316 return MarkConstraintAsFalse(ct,
"bool_xor: always false");
318 context_->UpdateRuleStats(
"bool_xor: always true");
319 return RemoveConstraint(ct);
323 if (num_true_literals % 2 == 0) {
324 if (!context_->StoreBooleanEqualityRelation(a,
NegatedRef(
b))) {
328 if (!context_->StoreBooleanEqualityRelation(a,
b)) {
332 context_->UpdateNewConstraintsVariableUsage();
333 context_->UpdateRuleStats(
"bool_xor: two active literals");
334 return RemoveConstraint(ct);
338 if (num_true_literals % 2 == 1) {
339 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
340 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
342 if (num_true_literals > 1) {
343 context_->UpdateRuleStats(
"bool_xor: remove even number of true literals");
346 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
351 if (context_->ModelIsUnsat())
return false;
356 context_->UpdateRuleStats(
"bool_or: removed enforcement literal");
357 for (
const int literal : ct->enforcement_literal()) {
358 ct->mutable_bool_or()->add_literals(
NegatedRef(literal));
360 ct->clear_enforcement_literal();
368 bool changed =
false;
369 context_->tmp_literals.clear();
370 context_->tmp_literal_set.clear();
371 for (
const int literal : ct->bool_or().literals()) {
372 if (context_->LiteralIsFalse(literal)) {
376 if (context_->LiteralIsTrue(literal)) {
377 context_->UpdateRuleStats(
"bool_or: always true");
378 return RemoveConstraint(ct);
383 if (context_->VariableIsUniqueAndRemovable(literal)) {
384 context_->UpdateRuleStats(
"bool_or: singleton");
385 if (!context_->SetLiteralToTrue(literal))
return true;
386 return RemoveConstraint(ct);
388 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
389 context_->UpdateRuleStats(
"bool_or: always true");
390 return RemoveConstraint(ct);
393 if (context_->tmp_literal_set.contains(literal)) {
396 context_->tmp_literal_set.insert(literal);
397 context_->tmp_literals.push_back(literal);
400 context_->tmp_literal_set.clear();
402 if (context_->tmp_literals.empty()) {
403 context_->UpdateRuleStats(
"bool_or: empty");
404 return context_->NotifyThatModelIsUnsat();
406 if (context_->tmp_literals.size() == 1) {
407 context_->UpdateRuleStats(
"bool_or: only one literal");
408 if (!context_->SetLiteralToTrue(context_->tmp_literals[0]))
return true;
409 return RemoveConstraint(ct);
411 if (context_->tmp_literals.size() == 2) {
414 context_->UpdateRuleStats(
"bool_or: implications");
415 ct->add_enforcement_literal(
NegatedRef(context_->tmp_literals[0]));
416 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
421 context_->UpdateRuleStats(
"bool_or: fixed literals");
422 ct->mutable_bool_or()->mutable_literals()->Clear();
423 for (
const int lit : context_->tmp_literals) {
424 ct->mutable_bool_or()->add_literals(lit);
432ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
434 if (!context_->MarkConstraintAsFalse(ct, reason))
return false;
439ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkOptionalIntervalAsFalse(
442 CHECK_EQ(ct->enforcement_literal_size(), 1);
443 const int enforcement_literal = ct->enforcement_literal(0);
444 if (!context_->SetLiteralToFalse(enforcement_literal)) {
451 ct->mutable_interval()->clear_start();
452 ct->mutable_interval()->clear_end();
453 ct->mutable_interval()->clear_size();
458 if (context_->ModelIsUnsat())
return false;
461 context_->UpdateRuleStats(
"bool_and: non-reified");
462 for (
const int literal : ct->bool_and().literals()) {
463 if (!context_->SetLiteralToTrue(literal))
return true;
465 return RemoveConstraint(ct);
468 bool changed =
false;
469 context_->tmp_literals.clear();
470 context_->tmp_literal_set.clear();
471 const absl::flat_hash_set<int> enforcement_literals_set(
472 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
473 for (
const int literal : ct->bool_and().literals()) {
474 if (context_->LiteralIsFalse(literal)) {
475 return MarkConstraintAsFalse(ct,
"bool_and: always false");
477 if (context_->LiteralIsTrue(literal)) {
481 if (enforcement_literals_set.contains(literal)) {
482 context_->UpdateRuleStats(
"bool_and: x => x");
486 if (enforcement_literals_set.contains(
NegatedRef(literal))) {
487 return MarkConstraintAsFalse(ct,
"bool_and: x => not x");
489 if (context_->VariableIsUniqueAndRemovable(literal)) {
492 context_->UpdateRuleStats(
493 "bool_and: setting unused literal in rhs to true");
494 if (!context_->SetLiteralToTrue(literal))
return true;
498 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
499 return MarkConstraintAsFalse(ct,
"bool_and: cannot be enforced");
502 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
504 context_->tmp_literals.push_back(literal);
507 context_->UpdateRuleStats(
"bool_and: removed duplicate literal");
514 if (context_->tmp_literals.empty())
return RemoveConstraint(ct);
517 ct->mutable_bool_and()->mutable_literals()->Clear();
518 for (
const int lit : context_->tmp_literals) {
519 ct->mutable_bool_and()->add_literals(lit);
521 context_->UpdateRuleStats(
"bool_and: fixed literals");
528 if (ct->enforcement_literal().size() == 1 &&
529 ct->bool_and().literals().size() == 1) {
530 const int enforcement = ct->enforcement_literal(0);
531 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
533 int64_t obj_coeff = context_->ObjectiveMap().at(var);
538 context_->UpdateRuleStats(
"bool_and: dual equality");
543 const int implied_literal = ct->bool_and().literals(0);
544 solution_crush_.SetLiteralToValueIf(enforcement,
true, implied_literal);
545 if (!context_->StoreBooleanEqualityRelation(enforcement,
556bool CpModelPresolver::PresolveAtMostOrExactlyOne(
ConstraintProto* ct) {
558 const std::string name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
559 auto* literals = is_at_most_one
560 ? ct->mutable_at_most_one()->mutable_literals()
561 : ct->mutable_exactly_one()->mutable_literals();
565 std::sort(literals->begin(), literals->end());
568 context_->tmp_literal_set.clear();
569 for (
const int literal : *literals) {
570 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
572 if (!context_->SetLiteralToFalse(literal))
return false;
573 context_->UpdateRuleStats(absl::StrCat(name,
"duplicate literals"));
575 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
576 int num_positive = 0;
577 int num_negative = 0;
578 for (
const int other : *literals) {
580 if (!context_->SetLiteralToFalse(other))
return false;
581 context_->UpdateRuleStats(absl::StrCat(name,
"x and not(x)"));
583 if (other == literal) {
593 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
596 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
599 return RemoveConstraint(ct);
605 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
608 bool changed =
false;
609 context_->tmp_literals.clear();
610 for (
const int literal : *literals) {
611 if (context_->LiteralIsTrue(literal)) {
612 context_->UpdateRuleStats(absl::StrCat(name,
"satisfied"));
613 for (
const int other : *literals) {
614 if (other != literal) {
615 if (!context_->SetLiteralToFalse(other))
return false;
618 return RemoveConstraint(ct);
621 if (context_->LiteralIsFalse(literal)) {
627 if (context_->VariableIsUniqueAndRemovable(literal)) {
630 singleton_literal_with_cost.push_back({literal, 0});
633 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
634 const auto it = context_->ObjectiveMap().find(
PositiveRef(literal));
635 DCHECK(it != context_->ObjectiveMap().end());
637 singleton_literal_with_cost.push_back({literal, it->second});
641 singleton_literal_with_cost.push_back({literal, -it->second});
646 context_->tmp_literals.push_back(literal);
649 bool transform_to_at_most_one =
false;
650 if (!singleton_literal_with_cost.empty()) {
654 if (singleton_literal_with_cost.size() > 1) {
656 singleton_literal_with_cost,
657 [](
const std::pair<int, int64_t>& a,
658 const std::pair<int, int64_t>&
b) {
return a.second <
b.second; });
659 for (
int i = 1;
i < singleton_literal_with_cost.size(); ++
i) {
660 context_->UpdateRuleStats(
"at_most_one: dominated singleton");
661 if (!context_->SetLiteralToFalse(
662 singleton_literal_with_cost[
i].first)) {
666 singleton_literal_with_cost.resize(1);
669 const int literal = singleton_literal_with_cost[0].first;
670 const int64_t literal_cost = singleton_literal_with_cost[0].second;
671 if (is_at_most_one && literal_cost >= 0) {
673 context_->UpdateRuleStats(
"at_most_one: singleton");
674 if (!context_->SetLiteralToFalse(literal))
return false;
675 }
else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
682 DCHECK(!context_->ObjectiveMap().contains(
PositiveRef(literal)));
684 if (!is_at_most_one) transform_to_at_most_one =
true;
685 is_at_most_one =
true;
687 context_->UpdateRuleStats(
"exactly_one: singleton");
688 context_->MarkVariableAsRemoved(
PositiveRef(literal));
691 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
692 ->mutable_exactly_one();
693 for (
const int lit : context_->tmp_literals) {
694 mapping_exo->add_literals(lit);
696 mapping_exo->add_literals(literal);
699 context_->tmp_literals.push_back(literal);
703 if (!is_at_most_one && !transform_to_at_most_one &&
704 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
705 context_->UpdateRuleStats(
"exactly_one: simplified objective");
708 if (transform_to_at_most_one) {
711 literals = ct->mutable_at_most_one()->mutable_literals();
715 for (
const int lit : context_->tmp_literals) {
718 context_->UpdateRuleStats(absl::StrCat(name,
"removed literals"));
724 if (context_->ModelIsUnsat())
return false;
727 const bool changed = PresolveAtMostOrExactlyOne(ct);
731 const auto& literals = ct->at_most_one().literals();
732 if (literals.empty()) {
733 context_->UpdateRuleStats(
"at_most_one: empty or all false");
734 return RemoveConstraint(ct);
738 if (literals.size() == 1) {
739 context_->UpdateRuleStats(
"at_most_one: size one");
740 return RemoveConstraint(ct);
747 if (context_->ModelIsUnsat())
return false;
749 const bool changed = PresolveAtMostOrExactlyOne(ct);
753 const auto& literals = ct->exactly_one().literals();
754 if (literals.empty()) {
755 return context_->NotifyThatModelIsUnsat(
"exactly_one: empty or all false");
759 if (literals.size() == 1) {
760 context_->UpdateRuleStats(
"exactly_one: size one");
761 if (!context_->SetLiteralToTrue(literals[0]))
return false;
762 return RemoveConstraint(ct);
766 if (literals.size() == 2) {
767 context_->UpdateRuleStats(
"exactly_one: size two");
768 if (!context_->StoreBooleanEqualityRelation(literals[0],
772 return RemoveConstraint(ct);
778bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto& ct,
780 if (context_->ModelIsUnsat())
return false;
783 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
784 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
785 changed |= CanonicalizeLinearExpression(ct, &exp);
793 LinearArgumentProto* lin_max = ct->mutable_lin_max();
797 for (
const LinearExpressionProto& expr : lin_max->exprs()) {
801 if (gcd <= 1)
return true;
805 const LinearExpressionProto& target = lin_max->target();
806 const int64_t old_gcd = gcd;
808 if (gcd != old_gcd) {
809 if (target.vars().empty()) {
810 return context_->NotifyThatModelIsUnsat(
"infeasible lin_max");
815 if (target.vars().size() == 1) {
817 context_->UpdateRuleStats(
"lin_max: canonicalize target using gcd");
818 if (!context_->CanonicalizeAffineVariable(
819 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
822 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
823 context_->UpdateConstraintVariableUsage(c);
826 context_->UpdateRuleStats(
827 "TODO lin_max: lhs not trivially divisible by rhs gcd");
830 if (gcd <= 1)
return true;
832 context_->UpdateRuleStats(
"lin_max: divising by gcd");
834 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
843 int var, int64_t value) {
844 int64_t result = expr.offset();
845 for (
int i = 0;
i < expr.vars().size(); ++
i) {
846 CHECK_EQ(expr.vars(
i), var);
847 result += expr.coeffs(
i) * value;
852template <
class ExpressionList>
853int GetFirstVar(ExpressionList exprs) {
855 for (
const int var : expr.vars()) {
865bool CpModelPresolver::PropagateAndReduceAffineMax(
ConstraintProto* ct) {
867 const int unique_var = GetFirstVar(ct->lin_max().exprs());
869 const auto& lin_max = ct->lin_max();
870 const int num_exprs = lin_max.exprs_size();
871 const auto& target = lin_max.target();
872 std::vector<int> num_wins(num_exprs, 0);
873 std::vector<int64_t> reachable_target_values;
874 std::vector<int64_t> valid_variable_values;
875 std::vector<int64_t> tmp_values(num_exprs);
877 const bool target_has_same_unique_var =
878 target.vars_size() == 1 && target.vars(0) == unique_var;
880 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
882 for (
const int64_t value : context_->DomainOf(unique_var).Values()) {
883 int64_t current_max = std::numeric_limits<int64_t>::min();
886 for (
int i = 0;
i < num_exprs; ++
i) {
888 EvaluateSingleVariableExpression(lin_max.exprs(
i), unique_var, value);
889 current_max = std::max(current_max, v);
894 if (!context_->DomainContains(target, current_max))
continue;
898 if (target_has_same_unique_var &&
899 EvaluateSingleVariableExpression(target, unique_var, value) !=
904 valid_variable_values.push_back(value);
905 reachable_target_values.push_back(current_max);
906 for (
int i = 0;
i < num_exprs; ++
i) {
907 DCHECK_LE(tmp_values[
i], current_max);
908 if (tmp_values[
i] == current_max) {
914 if (reachable_target_values.empty() || valid_variable_values.empty()) {
915 return MarkConstraintAsFalse(ct,
916 "lin_max: infeasible affine_max constraint");
920 bool reduced =
false;
921 if (!context_->IntersectDomainWith(
926 context_->UpdateRuleStats(
"lin_max: affine_max target domain reduced");
931 bool reduced =
false;
932 if (!context_->IntersectDomainWith(
937 context_->UpdateRuleStats(
938 "lin_max: unique affine_max var domain reduced");
943 for (
int i = 0;
i < num_exprs; ++
i) {
944 if (num_wins[
i] == valid_variable_values.size()) {
945 const LinearExpressionProto winner_expr = lin_max.exprs(
i);
946 ct->mutable_lin_max()->clear_exprs();
947 *ct->mutable_lin_max()->add_exprs() = winner_expr;
952 bool changed =
false;
953 if (ct->lin_max().exprs_size() > 1) {
955 for (
int i = 0;
i < num_exprs; ++
i) {
956 if (num_wins[
i] == 0)
continue;
957 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(
i);
960 if (new_size < ct->lin_max().exprs_size()) {
961 context_->UpdateRuleStats(
"lin_max: removed affine_max exprs");
968 if (context_->IsFixed(target)) {
969 context_->UpdateRuleStats(
"lin_max: fixed affine_max target");
970 return RemoveConstraint(ct);
973 if (target_has_same_unique_var) {
974 context_->UpdateRuleStats(
"lin_max: target_affine(x) = max(affine_i(x))");
975 return RemoveConstraint(ct);
983 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
984 context_->MarkVariableAsRemoved(target.vars(0));
985 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
986 context_->UpdateRuleStats(
"lin_max: unused affine_max target");
987 return RemoveConstraint(ct);
994 const LinearExpressionProto& target = ct->lin_max().target();
999 int64_t infered_min = context_->MinOf(target);
1000 int64_t infered_max = std::numeric_limits<int64_t>::min();
1001 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1002 infered_min = std::max(infered_min, context_->MinOf(expr));
1003 infered_max = std::max(infered_max, context_->MaxOf(expr));
1006 if (target.vars().empty()) {
1007 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
1008 return MarkConstraintAsFalse(ct,
"lin_max: infeasible");
1011 if (target.vars().size() <= 1) {
1013 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1014 rhs_domain = rhs_domain.UnionWith(
1015 context_->DomainSuperSetOf(expr).IntersectionWith(
1016 {infered_min, infered_max}));
1018 bool reduced =
false;
1019 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1023 context_->UpdateRuleStats(
"lin_max: target domain reduced");
1029 const int64_t target_min = context_->MinOf(target);
1030 bool changed =
false;
1037 bool has_greater_or_equal_to_target_min =
false;
1038 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1039 int index_to_keep = -1;
1040 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1041 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1042 if (context_->MinOf(expr) >= target_min) {
1043 const int64_t expr_max = context_->MaxOf(expr);
1044 if (expr_max > max_at_index_to_keep) {
1045 max_at_index_to_keep = expr_max;
1048 has_greater_or_equal_to_target_min =
true;
1053 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1054 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1055 const int64_t expr_max = context_->MaxOf(expr);
1058 if (expr_max < target_min)
continue;
1059 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1060 i != index_to_keep) {
1063 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1066 if (new_size < ct->lin_max().exprs_size()) {
1067 context_->UpdateRuleStats(
"lin_max: removed exprs");
1078 if (context_->ModelIsUnsat())
return false;
1081 const LinearExpressionProto& target = ct->lin_max().target();
1084 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1086 for (
const LinearExpressionProto& e : ct->lin_max().exprs()) {
1088 LinearConstraintProto* prec =
1089 context_->working_model->add_constraints()->mutable_linear();
1090 prec->add_domain(0);
1091 prec->add_domain(std::numeric_limits<int64_t>::max());
1095 context_->UpdateRuleStats(
"lin_max: x = max(x, ...)");
1096 return RemoveConstraint(ct);
1100 const bool is_one_var_affine_max =
1102 ct->lin_max().target().vars_size() <= 1;
1103 bool unique_var_is_small_enough =
false;
1106 if (is_one_var_affine_max) {
1107 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1108 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1112 if (is_one_var_affine_max && unique_var_is_small_enough) {
1113 changed = PropagateAndReduceAffineMax(ct);
1114 }
else if (is_int_abs) {
1115 changed = PropagateAndReduceIntAbs(ct);
1117 changed = PropagateAndReduceLinMax(ct);
1120 if (context_->ModelIsUnsat())
return false;
1126 if (ct->lin_max().exprs().empty()) {
1127 return MarkConstraintAsFalse(ct,
"lin_max: no exprs");
1131 if (ct->lin_max().exprs().size() < 10) {
1132 const int num_exprs = ct->lin_max().exprs().size();
1134 bool simplified =
false;
1135 std::vector<bool> can_be_removed(num_exprs,
false);
1136 for (
int i = 0;
i < num_exprs; ++
i) {
1137 if (ct->lin_max().exprs(
i).vars().size() != 1)
continue;
1138 for (
int j = 0; j < num_exprs; ++j) {
1139 if (
i == j)
continue;
1140 if (can_be_removed[j])
continue;
1145 if (ct->lin_max().exprs(j).vars().size() != 1)
continue;
1148 const LinearExpression2 expr2 = GetLinearExpression2FromProto(
1149 ct->lin_max().exprs(
i).vars(0), ct->lin_max().exprs(
i).coeffs(0),
1150 ct->lin_max().exprs(j).vars(0), -ct->lin_max().exprs(j).coeffs(0));
1152 const IntegerValue ub(ct->lin_max().exprs(j).offset() -
1153 ct->lin_max().exprs(
i).offset());
1154 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
1157 can_be_removed[
i] =
true;
1164 context_->UpdateRuleStats(
1165 "lin_max: removed expression smaller than others");
1167 for (
int i = 0;
i < num_exprs; ++
i) {
1168 if (can_be_removed[
i])
continue;
1169 *ct->mutable_lin_max()->mutable_exprs(new_size++) =
1170 ct->lin_max().exprs(
i);
1174 context_->UpdateConstraintVariableUsage(c);
1180 if (ct->lin_max().exprs().size() == 1) {
1181 context_->UpdateRuleStats(
"lin_max: converted to equality");
1182 ConstraintProto* new_ct = context_->working_model->add_constraints();
1184 auto* arg = new_ct->mutable_linear();
1185 const LinearExpressionProto& a = ct->lin_max().target();
1186 const LinearExpressionProto&
b = ct->lin_max().exprs(0);
1187 for (
int i = 0;
i < a.vars().size(); ++
i) {
1188 arg->add_vars(a.vars(
i));
1189 arg->add_coeffs(a.coeffs(
i));
1191 for (
int i = 0;
i <
b.vars().size(); ++
i) {
1192 arg->add_vars(
b.vars(
i));
1193 arg->add_coeffs(-
b.coeffs(
i));
1195 arg->add_domain(
b.offset() - a.offset());
1196 arg->add_domain(
b.offset() - a.offset());
1197 context_->UpdateNewConstraintsVariableUsage();
1198 return RemoveConstraint(ct);
1201 if (!DivideLinMaxByGcd(c, ct))
return false;
1206 const int64_t target_min = context_->MinOf(target);
1207 const int64_t target_max = context_->MaxOf(target);
1210 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1211 const int64_t value_min = context_->MinOf(expr);
1212 bool modified =
false;
1213 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1218 context_->UpdateRuleStats(
"lin_max: reduced expression domain");
1220 const int64_t value_max = context_->MaxOf(expr);
1221 if (value_max > target_max) {
1222 context_->UpdateRuleStats(
"TODO lin_max: linear expression above max");
1226 if (abort)
return changed;
1230 bool linear_target_domain_contains_max_domain =
false;
1232 int64_t infered_min = std::numeric_limits<int64_t>::min();
1233 int64_t infered_max = std::numeric_limits<int64_t>::min();
1234 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1235 infered_min = std::max(infered_min, context_->MinOf(expr));
1236 infered_max = std::max(infered_max, context_->MaxOf(expr));
1239 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1240 rhs_domain = rhs_domain.UnionWith(
1241 context_->DomainSuperSetOf(expr).IntersectionWith(
1242 {infered_min, infered_max}));
1248 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1249 const Domain target_domain =
1250 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1251 : context_->DomainOf(target.vars(0)).Negation();
1252 linear_target_domain_contains_max_domain =
1253 rhs_domain.IsIncludedIn(target_domain);
1268 if (linear_target_domain_contains_max_domain) {
1269 const int target_var = target.vars(0);
1271 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1272 for (
const int var : expr.vars()) {
1273 if (var == target_var &&
1284 context_->UpdateRuleStats(
1285 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1286 linear_target_domain_contains_max_domain =
false;
1291 if (linear_target_domain_contains_max_domain &&
1292 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1293 context_->UpdateRuleStats(
"lin_max: unused affine target");
1294 context_->MarkVariableAsRemoved(target.vars(0));
1295 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1296 return RemoveConstraint(ct);
1301 if (linear_target_domain_contains_max_domain &&
1302 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1303 (target.coeffs(0) > 0) ==
1304 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1305 context_->UpdateRuleStats(
"lin_max: rewrite with precedences");
1306 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1307 LinearConstraintProto* prec =
1308 context_->working_model->add_constraints()->mutable_linear();
1309 prec->add_domain(0);
1310 prec->add_domain(std::numeric_limits<int64_t>::max());
1314 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1315 return RemoveConstraint(ct);
1319 if (target_min == target_max) {
1320 bool all_booleans =
true;
1321 std::vector<int> literals;
1322 const int64_t fixed_target = target_min;
1323 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1324 const int64_t value_min = context_->MinOf(expr);
1325 const int64_t value_max = context_->MaxOf(expr);
1326 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
1327 if (value_max < fixed_target)
continue;
1329 if (value_min == value_max && value_max == fixed_target) {
1330 context_->UpdateRuleStats(
"lin_max: always satisfied");
1331 return RemoveConstraint(ct);
1333 if (context_->ExpressionIsAffineBoolean(expr)) {
1334 CHECK_EQ(value_max, fixed_target);
1335 literals.push_back(context_->LiteralForExpressionMax(expr));
1337 all_booleans =
false;
1341 if (literals.empty()) {
1342 return MarkConstraintAsFalse(ct,
"lin_max: all boolean and no support");
1346 context_->UpdateRuleStats(
"lin_max: fixed target and all booleans");
1347 for (
const int lit : literals) {
1348 ct->mutable_bool_or()->add_literals(lit);
1355 changed |= PresolveLinMaxWhenAllBoolean(ct);
1360bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(
ConstraintProto* ct) {
1361 if (context_->ModelIsUnsat())
return false;
1364 const LinearExpressionProto& target = ct->lin_max().target();
1365 if (!context_->ExpressionIsAffineBoolean(target))
return false;
1367 const int64_t target_min = context_->MinOf(target);
1368 const int64_t target_max = context_->MaxOf(target);
1369 const int target_ref = context_->LiteralForExpressionMax(target);
1371 bool min_is_reachable =
false;
1372 std::vector<int> min_literals;
1373 std::vector<int> literals_above_min;
1374 std::vector<int> max_literals;
1376 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1377 if (!context_->ExpressionIsAffineBoolean(expr))
return false;
1378 const int64_t value_min = context_->MinOf(expr);
1379 const int64_t value_max = context_->MaxOf(expr);
1380 const int ref = context_->LiteralForExpressionMax(expr);
1384 if (value_min > target_min) {
1385 context_->UpdateRuleStats(
"lin_max: fix target");
1386 (void)context_->SetLiteralToTrue(target_ref);
1389 if (value_max > target_max) {
1390 context_->UpdateRuleStats(
"lin_max: fix bool expr");
1391 (void)context_->SetLiteralToFalse(ref);
1396 if (value_min == value_max) {
1397 if (value_min == target_min) min_is_reachable =
true;
1401 CHECK_LE(value_min, target_min);
1402 if (value_min == target_min) {
1406 CHECK_LE(value_max, target_max);
1407 if (value_max == target_max) {
1408 max_literals.push_back(ref);
1409 literals_above_min.push_back(ref);
1410 }
else if (value_max > target_min) {
1411 literals_above_min.push_back(ref);
1412 }
else if (value_max == target_min) {
1413 min_literals.push_back(ref);
1417 context_->UpdateRuleStats(
"lin_max: all booleans");
1420 ConstraintProto* clause = context_->working_model->add_constraints();
1421 clause->add_enforcement_literal(target_ref);
1422 clause->mutable_bool_or();
1423 for (
const int lit : max_literals) {
1424 clause->mutable_bool_or()->add_literals(lit);
1428 for (
const int lit : literals_above_min) {
1429 context_->AddImplication(lit, target_ref);
1432 if (!min_is_reachable) {
1434 ConstraintProto* clause = context_->working_model->add_constraints();
1435 clause->add_enforcement_literal(
NegatedRef(target_ref));
1436 clause->mutable_bool_or();
1437 for (
const int lit : min_literals) {
1438 clause->mutable_bool_or()->add_literals(lit);
1442 context_->UpdateNewConstraintsVariableUsage();
1443 return RemoveConstraint(ct);
1449 CHECK_EQ(ct->enforcement_literal_size(), 0);
1450 if (context_->ModelIsUnsat())
return false;
1451 const LinearExpressionProto& target_expr = ct->lin_max().target();
1452 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1453 DCHECK_EQ(expr.vars_size(), 1);
1457 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1458 const Domain new_target_domain =
1459 expr_domain.UnionWith(expr_domain.Negation())
1460 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1461 bool target_domain_modified =
false;
1462 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1463 &target_domain_modified)) {
1466 if (expr_domain.IsFixed()) {
1467 context_->UpdateRuleStats(
"lin_max: fixed expression in int_abs");
1468 return RemoveConstraint(ct);
1470 if (target_domain_modified) {
1471 context_->UpdateRuleStats(
"lin_max: propagate domain from x to abs(x)");
1477 const Domain target_domain =
1478 context_->DomainSuperSetOf(target_expr)
1479 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1480 const Domain new_expr_domain =
1481 target_domain.UnionWith(target_domain.Negation());
1482 bool expr_domain_modified =
false;
1483 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1484 &expr_domain_modified)) {
1489 if (context_->IsFixed(target_expr)) {
1490 context_->UpdateRuleStats(
"lin_max: fixed abs target");
1491 return RemoveConstraint(ct);
1493 if (expr_domain_modified) {
1494 context_->UpdateRuleStats(
"lin_max: propagate domain from abs(x) to x");
1499 if (context_->MinOf(expr) >= 0) {
1500 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1501 ConstraintProto* new_ct = context_->working_model->add_constraints();
1502 new_ct->set_name(ct->name());
1503 auto* arg = new_ct->mutable_linear();
1508 bool changed =
false;
1509 if (!CanonicalizeLinear(new_ct, &changed)) {
1512 context_->UpdateNewConstraintsVariableUsage();
1513 return RemoveConstraint(ct);
1516 if (context_->MaxOf(expr) <= 0) {
1517 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1518 ConstraintProto* new_ct = context_->working_model->add_constraints();
1519 new_ct->set_name(ct->name());
1520 auto* arg = new_ct->mutable_linear();
1525 bool changed =
false;
1526 if (!CanonicalizeLinear(new_ct, &changed)) {
1529 context_->UpdateNewConstraintsVariableUsage();
1530 return RemoveConstraint(ct);
1538 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1539 context_->MarkVariableAsRemoved(target_expr.vars(0));
1540 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1541 context_->UpdateRuleStats(
"lin_max: unused abs target");
1542 return RemoveConstraint(ct);
1550 if (expr.
exprs().size() == 2) {
1556 if (expr0.
vars().size() == 1 && expr1.
vars().size() == 1 &&
1572bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1576 bool domain_modified =
false;
1583 &domain_modified)) {
1596 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1597 const LinearExpressionProto& target = ct->int_prod().target();
1598 if (!absl::c_any_of(ct->int_prod().exprs(),
1599 [&target](
const LinearExpressionProto& expr) {
1600 return absl::c_linear_search(expr.vars(),
1603 const Domain target_domain =
1605 .AdditionWith(context_->
DomainOf(target.vars(0)));
1610 return RemoveConstraint(ct);
1617 int64_t constant_factor = 1;
1619 bool changed =
false;
1620 LinearArgumentProto old_proto = ct->int_prod();
1621 LinearArgumentProto* proto = ct->mutable_int_prod();
1622 for (
int i = 0;
i < ct->int_prod().exprs().size(); ++
i) {
1623 LinearExpressionProto expr = ct->int_prod().exprs(
i);
1624 if (context_->IsFixed(expr)) {
1625 const int64_t expr_value = context_->FixedValue(expr);
1626 constant_factor =
CapProd(constant_factor, expr_value);
1627 context_->UpdateRuleStats(
"int_prod: removed constant expressions");
1632 constant_factor =
CapProd(constant_factor, expr_divisor);
1633 *proto->mutable_exprs(new_size++) = expr;
1636 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1637 proto->mutable_exprs()->end());
1639 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1640 if (!context_->DomainContains(ct->int_prod().target(), constant_factor)) {
1641 return MarkConstraintAsFalse(ct,
"int_prod: always false");
1644 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1645 Domain(constant_factor))) {
1648 context_->UpdateRuleStats(
"int_prod: constant product");
1651 ConstraintProto* new_ct = context_->working_model->add_constraints();
1652 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1653 LinearConstraintProto*
const lin = new_ct->mutable_linear();
1654 lin->add_domain(constant_factor);
1655 lin->add_domain(constant_factor);
1657 context_->UpdateNewConstraintsVariableUsage();
1658 context_->UpdateRuleStats(
"enforced int_prod: constant product");
1660 return RemoveConstraint(ct);
1664 if (context_->IsFixed(ct->int_prod().target()) &&
1665 context_->FixedValue(ct->int_prod().target()) == 0 &&
1666 constant_factor != 1) {
1667 context_->UpdateRuleStats(
"int_prod: simplify by constant factor");
1668 constant_factor = 1;
1674 context_->UpdateRuleStats(
"int_prod: overflow if non zero");
1675 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1678 constant_factor = 1;
1682 if (ct->int_prod().exprs().size() == 1) {
1683 LinearExpressionProto*
const target =
1684 ct->mutable_int_prod()->mutable_target();
1685 ConstraintProto*
const new_ct = context_->working_model->add_constraints();
1686 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1687 LinearConstraintProto*
const lin = new_ct->mutable_linear();
1689 if (context_->IsFixed(*target)) {
1690 int64_t target_value = context_->FixedValue(*target);
1691 if (target_value % constant_factor != 0) {
1692 context_->working_model->mutable_constraints()->RemoveLast();
1693 return MarkConstraintAsFalse(
1694 ct,
"int_prod: product incompatible with fixed target");
1697 lin->add_domain(target_value / constant_factor);
1698 lin->add_domain(target_value / constant_factor);
1700 context_->UpdateNewConstraintsVariableUsage();
1701 context_->UpdateRuleStats(
"int_prod: expression is constant");
1702 return RemoveConstraint(ct);
1709 std::gcd(
static_cast<uint64_t
>(std::abs(constant_factor)),
1710 static_cast<uint64_t
>(std::abs(target_divisor)));
1712 constant_factor /= gcd;
1720 ct->int_prod().target(), 1, lin) ||
1722 ct->int_prod().exprs(0), -constant_factor, lin);
1727 lin->coeffs(), lin->domain(0))) {
1728 context_->working_model->mutable_constraints()->RemoveLast();
1732 context_->UpdateNewConstraintsVariableUsage();
1733 context_->UpdateRuleStats(
"int_prod: linearize product by constant");
1734 return RemoveConstraint(ct);
1738 if (constant_factor != 1) {
1746 const LinearExpressionProto old_target = ct->int_prod().target();
1747 if (!context_->IsFixed(old_target)) {
1752 CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1753 -context_->MinOf(old_target))) >=
1754 std::numeric_limits<int64_t>::max() / 2) {
1758 *ct->mutable_int_prod() = old_proto;
1759 context_->UpdateRuleStats(
1760 "int_prod: enforcement or overflow prevented creating a affine "
1764 const int ref = old_target.vars(0);
1765 const int64_t coeff = old_target.coeffs(0);
1766 const int64_t offset = old_target.offset();
1767 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1771 if (context_->IsFixed(ref)) {
1777 if (context_->IsFixed(old_target)) {
1778 const int64_t target_value = context_->FixedValue(old_target);
1779 if (target_value % constant_factor != 0) {
1780 return MarkConstraintAsFalse(
1781 ct,
"int_prod: constant factor does not divide constant target");
1784 proto->clear_target();
1785 proto->mutable_target()->set_offset(target_value / constant_factor);
1786 context_->UpdateRuleStats(
1787 "int_prod: divide product and fixed target by constant factor");
1790 const AffineRelation::Relation r =
1791 context_->GetAffineRelation(old_target.vars(0));
1792 const absl::int128 temp_coeff =
1793 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1794 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1795 const absl::int128 temp_offset =
1796 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1797 absl::int128(old_target.offset());
1798 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1799 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1800 const absl::int128 new_offset =
1801 temp_offset / absl::int128(constant_factor);
1809 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1810 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1811 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1812 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1813 return MarkConstraintAsFalse(
1814 ct,
"int_prod: overflow during simplification");
1818 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1819 proto->mutable_target()->set_vars(0, r.representative);
1820 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1821 context_->UpdateRuleStats(
"int_prod: divide product by constant factor");
1828 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1830 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1832 !context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1833 &domain_modified)) {
1836 if (domain_modified) {
1837 context_->UpdateRuleStats(absl::StrCat(
1838 is_square ?
"int_square" :
"int_prod",
": reduced target domain"));
1843 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1844 DCHECK_GE(target_max, 0);
1846 bool expr_reduced =
false;
1848 !context_->IntersectDomainWith(ct->int_prod().exprs(0),
1849 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1853 context_->UpdateRuleStats(
"int_square: reduced expr domain");
1857 if (ct->int_prod().exprs_size() == 2) {
1858 LinearExpressionProto a = ct->int_prod().exprs(0);
1859 LinearExpressionProto
b = ct->int_prod().exprs(1);
1860 const LinearExpressionProto product = ct->int_prod().target();
1865 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1868 context_->UpdateRuleStats(
"int_square: fix variable to zero or one");
1869 return RemoveConstraint(ct);
1871 context_->UpdateRuleStats(
1872 "TODO enforced int_square: fix variable to zero or one");
1878 if (ct->int_prod().exprs().size() == 2) {
1879 const auto is_boolean_affine =
1880 [context = context_](
const LinearExpressionProto& expr) {
1881 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1882 context->MaxOf(expr.vars(0)) == 1;
1884 const LinearExpressionProto* boolean_linear =
nullptr;
1885 const LinearExpressionProto* other_linear =
nullptr;
1886 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1887 boolean_linear = &ct->int_prod().exprs(0);
1888 other_linear = &ct->int_prod().exprs(1);
1889 }
else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1890 boolean_linear = &ct->int_prod().exprs(1);
1891 other_linear = &ct->int_prod().exprs(0);
1893 if (boolean_linear) {
1900 ConstraintProto* constraint_for_false =
1901 context_->working_model->add_constraints();
1902 ConstraintProto* constraint_for_true =
1903 context_->working_model->add_constraints();
1904 *constraint_for_true->mutable_enforcement_literal() =
1905 ct->enforcement_literal();
1906 *constraint_for_false->mutable_enforcement_literal() =
1907 ct->enforcement_literal();
1908 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1909 constraint_for_false->add_enforcement_literal(
1911 LinearConstraintProto* linear_for_false =
1912 constraint_for_false->mutable_linear();
1913 LinearConstraintProto* linear_for_true =
1914 constraint_for_true->mutable_linear();
1916 linear_for_false->add_domain(0);
1917 linear_for_false->add_domain(0);
1919 *other_linear, boolean_linear->offset(), linear_for_false);
1923 linear_for_true->add_domain(0);
1924 linear_for_true->add_domain(0);
1926 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1930 context_->CanonicalizeLinearConstraint(constraint_for_false);
1931 context_->CanonicalizeLinearConstraint(constraint_for_true);
1933 linear_for_false->vars(),
1934 linear_for_false->coeffs()) ||
1936 linear_for_true->vars(),
1937 linear_for_true->coeffs())) {
1938 context_->working_model->mutable_constraints()->RemoveLast();
1939 context_->working_model->mutable_constraints()->RemoveLast();
1941 context_->UpdateRuleStats(
"int_prod: boolean affine term");
1942 context_->UpdateNewConstraintsVariableUsage();
1943 return RemoveConstraint(ct);
1949 const LinearExpressionProto target_expr = ct->int_prod().target();
1951 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1954 std::vector<int> literals;
1955 for (
const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1957 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1960 literals.push_back(lit);
1964 context_->UpdateRuleStats(
"int_prod: all boolean");
1966 ConstraintProto* new_ct = context_->working_model->add_constraints();
1967 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1968 new_ct->add_enforcement_literal(target);
1969 auto* arg = new_ct->mutable_bool_and();
1970 for (
const int lit : literals) {
1971 arg->add_literals(lit);
1975 ConstraintProto* new_ct = context_->working_model->add_constraints();
1976 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1977 auto* arg = new_ct->mutable_bool_or();
1978 arg->add_literals(target);
1979 for (
const int lit : literals) {
1983 context_->UpdateNewConstraintsVariableUsage();
1984 return RemoveConstraint(ct);
1988 if (context_->ModelIsUnsat())
return false;
1992 const LinearExpressionProto target = ct->int_div().target();
1993 const LinearExpressionProto expr = ct->int_div().exprs(0);
1994 const LinearExpressionProto div = ct->int_div().exprs(1);
1997 if (!context_->IntersectDomainWith(target, Domain(1))) {
2000 context_->UpdateRuleStats(
"int_div: y = x / x");
2001 return RemoveConstraint(ct);
2003 if (!context_->IntersectDomainWith(target, Domain(-1))) {
2006 context_->UpdateRuleStats(
"int_div: y = - x / x");
2007 return RemoveConstraint(ct);
2013 if (ct->enforcement_literal().empty() &&
2014 context_->ConstraintToVars(c).size() == 1) {
2015 const int var = context_->ConstraintToVars(c)[0];
2016 if (context_->DomainOf(var).Size() >= 100) {
2017 context_->UpdateRuleStats(
2018 "TODO int_div: single variable with large domain");
2020 std::vector<int64_t> possible_values;
2021 for (
const int64_t v : context_->DomainOf(var).Values()) {
2022 const int64_t target_v =
2023 EvaluateSingleVariableExpression(target, var, v);
2024 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
2025 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
2026 if (div_v == 0)
continue;
2027 if (target_v == expr_v / div_v) {
2028 possible_values.push_back(v);
2031 (void)context_->IntersectDomainWith(var,
2033 context_->UpdateRuleStats(
"int_div: single variable");
2034 return RemoveConstraint(ct);
2039 if (!context_->IsFixed(div))
return false;
2041 const int64_t divisor = context_->FixedValue(div);
2044 if (divisor == 1 || divisor == -1) {
2045 LinearConstraintProto*
const lin =
2046 context_->working_model->add_constraints()->mutable_linear();
2051 context_->UpdateNewConstraintsVariableUsage();
2052 context_->UpdateRuleStats(
"int_div: rewrite to equality");
2053 return RemoveConstraint(ct);
2058 bool domain_modified =
false;
2059 const Domain target_implied_domain =
2060 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
2062 if (!context_->IntersectDomainWith(target, target_implied_domain,
2063 &domain_modified)) {
2066 if (domain_modified) {
2068 if (target_implied_domain.IsFixed()) {
2069 context_->UpdateRuleStats(
2070 "int_div: target has been fixed by propagating X / cte");
2072 context_->UpdateRuleStats(
2073 "int_div: updated domain of target in target = X / cte");
2079 if (context_->IsFixed(target) &&
2081 1 + std::abs(context_->FixedValue(target)))) !=
2082 std::numeric_limits<int64_t>::max()) {
2083 int64_t t = context_->FixedValue(target);
2084 int64_t d = divisor;
2090 const Domain expr_implied_domain =
2092 ? Domain(t * d, (t + 1) * d - 1)
2093 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
2094 bool domain_modified =
false;
2095 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
2096 &domain_modified)) {
2099 if (domain_modified) {
2100 context_->UpdateRuleStats(
"int_div: target and divisor are fixed");
2102 context_->UpdateRuleStats(
"int_div: always true");
2104 return RemoveConstraint(ct);
2110 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2112 CapProd(divisor, context_->MaxOf(target)) !=
2113 std::numeric_limits<int64_t>::max()) {
2114 LinearConstraintProto*
const lin =
2115 context_->working_model->add_constraints()->mutable_linear();
2117 lin->add_domain(divisor - 1);
2120 context_->UpdateNewConstraintsVariableUsage();
2121 context_->UpdateRuleStats(
2122 "int_div: linearize positive division with a constant divisor");
2124 return RemoveConstraint(ct);
2133 if (context_->ModelIsUnsat())
return false;
2138 const LinearExpressionProto target = ct->int_mod().target();
2139 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2140 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2142 if (context_->MinOf(target) > 0) {
2143 bool domain_changed =
false;
2144 if (!context_->IntersectDomainWith(
2145 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2149 if (domain_changed) {
2150 context_->UpdateRuleStats(
2151 "int_mod: non negative target implies positive expression");
2155 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2156 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2157 return context_->NotifyThatModelIsUnsat(
2158 "int_mod: incompatible target and mod");
2161 if (context_->MaxOf(target) < 0) {
2162 bool domain_changed =
false;
2163 if (!context_->IntersectDomainWith(
2164 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2168 if (domain_changed) {
2169 context_->UpdateRuleStats(
2170 "int_mod: non positive target implies negative expression");
2174 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2175 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2176 expr.vars().size() == 1) {
2178 const int64_t fixed_mod = context_->FixedValue(mod);
2179 const int64_t fixed_target = context_->FixedValue(target);
2181 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2183 fixed_target - expr.offset())) {
2187 context_->UpdateRuleStats(
"int_mod: fixed mod and target");
2188 return RemoveConstraint(ct);
2191 bool domain_changed =
false;
2192 if (!context_->IntersectDomainWith(
2194 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2195 context_->DomainSuperSetOf(mod)),
2200 if (domain_changed) {
2201 context_->UpdateRuleStats(
"int_mod: reduce target domain");
2209 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2210 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2211 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2212 target.vars(0) != expr.vars(0)) {
2213 const int64_t fixed_mod = context_->FixedValue(mod);
2214 std::vector<int64_t> values;
2215 const Domain dom = context_->DomainOf(target.vars(0));
2216 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2217 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2218 const int64_t target_term = rhs - target.offset();
2219 if (target_term % target.coeffs(0) != 0)
continue;
2220 if (dom.
Contains(target_term / target.coeffs(0))) {
2221 values.push_back(v);
2225 context_->UpdateRuleStats(
"int_mod: remove singleton target");
2226 if (!context_->IntersectDomainWith(expr.vars(0),
2230 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2232 context_->UpdateConstraintVariableUsage(c);
2233 context_->MarkVariableAsRemoved(target.vars(0));
2242bool CpModelPresolver::ExploitEquivalenceRelations(
int c,
ConstraintProto* ct) {
2243 bool changed =
false;
2249 for (
int& ref : *ct->mutable_enforcement_literal()) {
2250 const int rep = this->context_->GetLiteralRepresentative(ref);
2261 bool work_to_do =
false;
2262 for (
const int var : context_->ConstraintToVars(c)) {
2263 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2264 if (r.representative != var) {
2269 if (!work_to_do)
return false;
2273 [&changed,
this](
int* ref) {
2274 const int rep = this->context_->GetLiteralRepresentative(*ref);
2285 if (context_->ModelIsUnsat())
return false;
2289 const int num_vars = ct->linear().vars().size();
2290 for (
int i = 0;
i < num_vars; ++
i) {
2291 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2292 gcd = std::gcd(gcd, magnitude);
2293 if (gcd == 1)
break;
2296 context_->UpdateRuleStats(
"linear: divide by GCD");
2297 for (
int i = 0;
i < num_vars; ++
i) {
2298 ct->mutable_linear()->set_coeffs(
i, ct->linear().coeffs(
i) / gcd);
2302 if (ct->linear().domain_size() == 0) {
2303 return MarkConstraintAsFalse(ct,
"linear: not satisfied after GCD");
2309bool CpModelPresolver::CanonicalizeLinearExpression(
2311 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2314bool CpModelPresolver::CanonicalizeLinear(
ConstraintProto* ct,
bool* changed) {
2316 if (context_->ModelIsUnsat())
return false;
2318 if (ct->linear().domain().empty()) {
2320 return MarkConstraintAsFalse(ct,
"linear: no domain");
2323 bool is_impossible =
false;
2324 *changed = context_->CanonicalizeLinearConstraint(ct, &is_impossible);
2325 if (is_impossible) {
2327 return MarkConstraintAsFalse(ct,
"linear: never in domain");
2329 *changed |= DivideLinearByGcd(ct);
2335 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2336 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2337 ref_coeff = -ref_coeff;
2340 ct->mutable_linear());
2343 if (ct->linear().vars().empty()) {
2345 return PresolveEmptyLinearConstraint(ct);
2353 context_->ModelIsUnsat()) {
2357 absl::btree_set<int> index_to_erase;
2358 const int num_vars = ct->linear().vars().size();
2364 for (
int i = 0;
i < num_vars; ++
i) {
2365 const int var = ct->linear().vars(
i);
2366 const int64_t coeff = ct->linear().coeffs(
i);
2368 if (context_->VariableIsUniqueAndRemovable(var)) {
2373 if (std::abs(coeff) != 1)
continue;
2376 const auto term_domain =
2377 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2378 if (!exact)
continue;
2381 const Domain new_rhs = rhs.AdditionWith(term_domain);
2382 if (new_rhs.NumIntervals() > 100)
continue;
2388 context_->UpdateRuleStats(
"linear: singleton column");
2389 index_to_erase.insert(
i);
2402 if (index_to_erase.empty()) {
2403 int num_singletons = 0;
2404 for (
const int var : ct->linear().vars()) {
2406 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2407 !context_->VariableIsUniqueAndRemovable(var)) {
2412 if (num_singletons == num_vars) {
2414 std::vector<Domain> domains;
2415 std::vector<int64_t> coeffs;
2416 std::vector<int64_t> costs;
2417 for (
int i = 0;
i < num_vars; ++
i) {
2418 const int var = ct->linear().vars(
i);
2420 domains.push_back(context_->DomainOf(var));
2421 coeffs.push_back(ct->linear().coeffs(
i));
2422 costs.push_back(context_->ObjectiveCoeff(var));
2424 BasicKnapsackSolver solver;
2425 const auto& result = solver.Solve(domains, coeffs, costs,
2427 if (!result.solved) {
2428 context_->UpdateRuleStats(
2429 "TODO independent linear: minimize single linear constraint");
2430 }
else if (result.infeasible) {
2431 return MarkConstraintAsFalse(
2432 ct,
"independent linear: no DP solution to simple constraint");
2434 if (ct->enforcement_literal().empty()) {
2436 context_->UpdateRuleStats(
"independent linear: solved by DP");
2437 for (
int i = 0;
i < num_vars; ++
i) {
2438 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2439 Domain(result.solution[
i]))) {
2443 return RemoveConstraint(ct);
2448 if (ct->enforcement_literal().size() == 1) {
2449 indicator = ct->enforcement_literal(0);
2452 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2453 auto* new_ct = context_->working_model->add_constraints();
2454 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2455 new_ct->mutable_bool_or()->add_literals(indicator);
2456 context_->UpdateNewConstraintsVariableUsage();
2458 for (
int i = 0;
i < num_vars; ++
i) {
2459 const int64_t best_value =
2460 costs[
i] > 0 ? domains[
i].Min() : domains[
i].Max();
2461 const int64_t other_value = result.solution[
i];
2462 if (best_value == other_value) {
2463 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2464 Domain(best_value))) {
2469 solution_crush_.SetVarToConditionalValue(
2470 ct->linear().vars(
i), {indicator}, other_value, best_value);
2472 if (!context_->StoreAffineRelation(ct->linear().vars(
i), indicator,
2473 other_value - best_value,
2478 if (!context_->StoreAffineRelation(
2480 best_value - other_value, other_value)) {
2485 context_->UpdateRuleStats(
2486 "independent linear: with enforcement, but solved by DP");
2487 return RemoveConstraint(ct);
2493 if (index_to_erase.empty()) {
2495 if (context_->params().presolve_substitution_level() <= 0)
return false;
2496 if (!ct->enforcement_literal().empty())
return false;
2500 if (rhs.
Min() != rhs.
Max())
return false;
2502 for (
int i = 0;
i < num_vars; ++
i) {
2503 const int var = ct->linear().vars(
i);
2504 const int64_t coeff = ct->linear().coeffs(
i);
2515 if (!context_->VariableWithCostIsUnique(var))
continue;
2516 DCHECK(context_->ObjectiveMap().contains(var));
2523 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2525 if (objective_coeff % coeff != 0)
continue;
2531 if (std::abs(objective_coeff) != 1)
continue;
2535 const auto term_domain =
2536 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2537 if (!exact)
continue;
2538 const Domain new_rhs = rhs.AdditionWith(term_domain);
2539 if (new_rhs.NumIntervals() > 100)
continue;
2547 if (context_->ObjectiveMap().size() == 1) {
2550 if (!context_->RecomputeSingletonObjectiveDomain()) {
2555 if (context_->IsFixed(var))
continue;
2557 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2558 if (context_->ModelIsUnsat())
return true;
2562 context_->UpdateRuleStats(
"linear: singleton column define objective");
2563 context_->MarkVariableAsRemoved(var);
2564 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2565 return RemoveConstraint(ct);
2575 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2576 if (context_->ModelIsUnsat())
return true;
2580 context_->UpdateRuleStats(
2581 "linear: singleton column in equality and in objective");
2583 index_to_erase.insert(
i);
2587 if (index_to_erase.empty())
return false;
2598 if (!ct->enforcement_literal().empty()) {
2599 for (
const int i : index_to_erase) {
2600 const int var = ct->linear().vars(
i);
2602 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2603 new_lin->add_vars(var);
2604 new_lin->add_coeffs(1);
2613 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2614 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2615 const std::string name(new_ct->name());
2617 new_ct->set_name(absl::StrCat(ct->name(),
" copy ", name));
2619 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2623 for (
int i = 0;
i < num_vars; ++
i) {
2624 if (index_to_erase.count(
i)) {
2625 context_->MarkVariableAsRemoved(ct->linear().vars(
i));
2628 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(
i));
2629 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(
i));
2632 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2633 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2635 DivideLinearByGcd(ct);
2641bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2644 const int num_variables = ct->linear().vars().size();
2645 for (
int i = 0;
i < num_variables; ++
i) {
2646 if (
i == target_index)
continue;
2647 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2648 gcd = std::gcd(gcd, magnitude);
2649 if (gcd == 1)
return false;
2655 const int ref = ct->linear().vars(target_index);
2656 const int64_t coeff = ct->linear().coeffs(target_index);
2657 const int64_t rhs = ct->linear().domain(0);
2661 if (coeff % gcd == 0)
return false;
2663 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2669 bool changed =
false;
2670 (void)CanonicalizeLinear(ct, &changed);
2678 ct.linear().domain().size() == 2 &&
2679 ct.linear().domain(0) == ct.linear().domain(1) &&
2680 ct.enforcement_literal().empty();
2696bool CpModelPresolver::PresolveLinearEqualityWithModulo(
ConstraintProto* ct) {
2697 if (context_->ModelIsUnsat())
return false;
2698 if (!IsLinearEqualityConstraint(*ct))
return false;
2700 const int num_variables = ct->linear().vars().size();
2701 if (num_variables < 2)
return false;
2703 std::vector<int> mod2_indices;
2704 std::vector<int> mod3_indices;
2705 std::vector<int> mod5_indices;
2707 int64_t min_magnitude;
2708 int num_smallest = 0;
2710 for (
int i = 0;
i < num_variables; ++
i) {
2711 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2712 if (num_smallest == 0 || magnitude < min_magnitude) {
2713 min_magnitude = magnitude;
2716 }
else if (magnitude == min_magnitude) {
2720 if (magnitude % 2 != 0) mod2_indices.push_back(
i);
2721 if (magnitude % 3 != 0) mod3_indices.push_back(
i);
2722 if (magnitude % 5 != 0) mod5_indices.push_back(
i);
2725 if (mod2_indices.size() == 2) {
2727 std::vector<int> literals;
2728 for (
const int i : mod2_indices) {
2729 const int ref = ct->linear().vars(
i);
2730 if (!context_->CanBeUsedAsLiteral(ref)) {
2734 literals.push_back(ref);
2737 const int64_t rhs = std::abs(ct->linear().domain(0));
2738 context_->UpdateRuleStats(
"linear: only two odd Booleans in equality");
2740 if (!context_->StoreBooleanEqualityRelation(literals[0],
2745 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2754 if (mod2_indices.size() == 1) {
2755 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2757 if (mod3_indices.size() == 1) {
2758 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2760 if (mod5_indices.size() == 1) {
2761 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2763 if (num_smallest == 1) {
2764 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2771 CHECK_EQ(ct->linear().vars().size(), 1);
2773 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(0)));
2775 const int var = ct->linear().vars(0);
2776 const Domain var_domain = context_->DomainOf(var);
2780 if (rhs.IsEmpty()) {
2781 return MarkConstraintAsFalse(ct,
"linear1: infeasible");
2783 if (rhs == var_domain) {
2784 context_->UpdateRuleStats(
"linear1: always true");
2785 return RemoveConstraint(ct);
2790 if (ct->linear().coeffs(0) != 1) {
2791 context_->UpdateRuleStats(
"linear1: canonicalized");
2792 ct->mutable_linear()->set_coeffs(0, 1);
2798 context_->UpdateRuleStats(
"linear1: without enforcement");
2799 if (!context_->IntersectDomainWith(var, rhs))
return false;
2800 return RemoveConstraint(ct);
2804 if (context_->CanBeUsedAsLiteral(var)) {
2805 DCHECK(rhs.IsFixed());
2806 if (rhs.FixedValue() == 1) {
2807 ct->mutable_bool_and()->add_literals(var);
2809 CHECK_EQ(rhs.FixedValue(), 0);
2810 ct->mutable_bool_and()->add_literals(
NegatedRef(var));
2819 bool changed =
false;
2820 if (ct->enforcement_literal().size() == 1) {
2823 int lit = ct->enforcement_literal(0);
2828 const int representative = context_->GetLiteralRepresentative(lit);
2829 if (lit != representative) {
2830 lit = representative;
2831 ct->set_enforcement_literal(0, lit);
2832 context_->UpdateRuleStats(
"linear1: remapped enforcement literal");
2836 if (rhs.IsFixed()) {
2837 const int64_t value = rhs.FixedValue();
2839 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2840 if (lit == encoding_lit)
return changed;
2841 context_->AddImplication(lit, encoding_lit);
2842 context_->UpdateNewConstraintsVariableUsage();
2844 context_->UpdateRuleStats(
"linear1: transformed to implication");
2847 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2850 context_->modified_domains.Set(var);
2852 context_->UpdateNewConstraintsVariableUsage();
2857 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2858 if (complement.IsFixed()) {
2859 const int64_t value = complement.FixedValue();
2861 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2862 if (
NegatedRef(lit) == encoding_lit)
return changed;
2863 context_->AddImplication(lit,
NegatedRef(encoding_lit));
2864 context_->UpdateNewConstraintsVariableUsage();
2866 context_->UpdateRuleStats(
"linear1: transformed to implication");
2869 if (context_->StoreLiteralImpliesVarNeValue(lit, var, value)) {
2872 context_->modified_domains.Set(var);
2874 context_->UpdateNewConstraintsVariableUsage();
2884 DCHECK_EQ(ct->linear().vars().size(), 2);
2886 const LinearConstraintProto& arg = ct->linear();
2887 const int var1 = arg.vars(0);
2888 const int var2 = arg.vars(1);
2889 const int64_t coeff1 = arg.coeffs(0);
2890 const int64_t coeff2 = arg.coeffs(1);
2894 const LinearExpression2 expr2 =
2895 GetLinearExpression2FromProto(var1, coeff1, var2, coeff2);
2896 const IntegerValue lb(arg.domain(0));
2897 const IntegerValue ub(arg.domain(arg.domain().size() - 1));
2899 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
2909 if (!ct->enforcement_literal().empty() &&
2910 ct->linear().domain().size() == 2) {
2911 context_->UpdateRuleStats(
"linear2: already known enforced relation");
2912 return RemoveConstraint(ct);
2915 return MarkConstraintAsFalse(ct,
"linear2: infeasible relation");
2916 }
else if (ct->enforcement_literal().empty()) {
2917 known_linear2_.Add(expr2, lb, ub);
2918 if (context_->ModelIsUnsat())
return false;
2923 bool mult1_is_exact =
true;
2924 bool mult2_is_exact =
true;
2925 const Domain scaled_domain1 =
2926 context_->DomainOf(var1).MultiplicationBy(coeff1, &mult1_is_exact);
2927 const Domain scaled_domain2 =
2928 context_->DomainOf(var2).MultiplicationBy(coeff2, &mult2_is_exact);
2929 if (mult1_is_exact && mult2_is_exact) {
2930 const Domain infeasible_reachable_values =
2931 scaled_domain1.AdditionWith(scaled_domain2)
2932 .IntersectionWith(rhs.Complement());
2934 if (!infeasible_reachable_values.IsEmpty() &&
2935 infeasible_reachable_values.IsFixed()) {
2936 return PresolveLinear2NeCst(ct, infeasible_reachable_values.FixedValue());
2940 if (rhs.IsFixed()) {
2941 if (ct->enforcement_literal().empty()) {
2942 return PresolveUnenforcedLinear2EqCst(ct, rhs.FixedValue());
2944 return PresolveEnforcedLinear2EqCst(ct, rhs.FixedValue());
2948 return PresolveLinear2WithBooleans(ct);
2960bool CpModelPresolver::PresolveLinear2WithBooleans(
ConstraintProto* ct) {
2961 DCHECK_EQ(ct->linear().vars().size(), 2);
2963 const LinearConstraintProto& arg = ct->linear();
2964 const int var1 = arg.vars(0);
2965 const int var2 = arg.vars(1);
2966 const int64_t coeff1 = arg.coeffs(0);
2967 const int64_t coeff2 = arg.coeffs(1);
2970 int64_t value_on_true, coeff;
2971 if (context_->CanBeUsedAsLiteral(var1)) {
2973 value_on_true = coeff1;
2976 }
else if (context_->CanBeUsedAsLiteral(var2)) {
2978 value_on_true = coeff2;
2987 const Domain rhs_if_true =
2988 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2989 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2990 const bool implied_false =
2991 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2992 const bool implied_true =
2993 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2994 if (implied_true && implied_false) {
2995 return MarkConstraintAsFalse(ct,
"linear2: infeasible");
2996 }
else if (implied_true) {
2997 context_->UpdateRuleStats(
"linear2: boolean with one feasible value");
3000 ConstraintProto* new_ct = context_->working_model->add_constraints();
3001 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3002 new_ct->mutable_bool_and()->add_literals(lit);
3003 context_->UpdateNewConstraintsVariableUsage();
3006 ct->mutable_linear()->Clear();
3007 ct->mutable_linear()->add_vars(var);
3008 ct->mutable_linear()->add_coeffs(1);
3010 return PresolveSmallLinear(ct) ||
true;
3011 }
else if (implied_false) {
3012 context_->UpdateRuleStats(
"linear2: boolean with one feasible value");
3015 ConstraintProto* new_ct = context_->working_model->add_constraints();
3016 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3017 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
3018 context_->UpdateNewConstraintsVariableUsage();
3021 ct->mutable_linear()->Clear();
3022 ct->mutable_linear()->add_vars(var);
3023 ct->mutable_linear()->add_coeffs(1);
3025 return PresolveSmallLinear(ct) ||
true;
3026 }
else if (ct->enforcement_literal().empty() &&
3027 !context_->CanBeUsedAsLiteral(var)) {
3032 context_->UpdateRuleStats(
"linear2: contains a boolean");
3035 const Domain var_domain = context_->DomainOf(var);
3036 if (!var_domain.IsIncludedIn(rhs_if_true)) {
3037 ConstraintProto* new_ct = context_->working_model->add_constraints();
3038 new_ct->add_enforcement_literal(lit);
3039 new_ct->mutable_linear()->add_vars(var);
3040 new_ct->mutable_linear()->add_coeffs(1);
3042 new_ct->mutable_linear());
3046 if (!var_domain.IsIncludedIn(rhs_if_false)) {
3047 ConstraintProto* new_ct = context_->working_model->add_constraints();
3048 new_ct->add_enforcement_literal(
NegatedRef(lit));
3049 new_ct->mutable_linear()->add_vars(var);
3050 new_ct->mutable_linear()->add_coeffs(1);
3052 new_ct->mutable_linear());
3055 context_->UpdateNewConstraintsVariableUsage();
3056 return RemoveConstraint(ct);
3060 context_->UpdateRuleStats(
"TODO linear2: contains a boolean");
3064bool CpModelPresolver::PresolveLinear2NeCst(
ConstraintProto* ct, int64_t rhs) {
3065 const LinearConstraintProto& arg = ct->linear();
3066 const int var1 = arg.vars(0);
3067 const int var2 = arg.vars(1);
3069 const int64_t coeff1 = arg.coeffs(0);
3070 const int64_t coeff2 = arg.coeffs(1);
3080 context_->UpdateRuleStats(
"linear2: remove always feasible ax + by != cte");
3081 return RemoveConstraint(ct);
3084 const Domain domain_of_z =
3085 context_->DomainOf(var1)
3086 .AdditionWith(Domain(-x0))
3087 .InverseMultiplicationBy(
b)
3088 .IntersectionWith(context_->DomainOf(var2)
3089 .AdditionWith(Domain(-y0))
3090 .InverseMultiplicationBy(-a));
3091 const int64_t max_domain_size =
3092 context_->params().max_domain_size_for_linear2_expansion();
3093 const int64_t small_domain_size = max_domain_size / 2;
3094 if (domain_of_z.Size() <= max_domain_size &&
3095 (context_->IsMostlyFullyEncoded(var1) ||
3096 context_->DomainSize(var1) <= small_domain_size) &&
3097 (context_->IsMostlyFullyEncoded(var2) ||
3098 context_->DomainSize(var2) <= small_domain_size)) {
3101 int num_clauses = 0;
3102 for (
const int64_t z : domain_of_z.Values()) {
3103 const int64_t value1 = x0 +
b * z;
3104 const int64_t value2 = y0 - a * z;
3105 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3106 if (!context_->VarCanTakeValue(var1, value1) ||
3107 !context_->VarCanTakeValue(var2, value2)) {
3112 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value1);
3113 const int lit2 = context_->GetOrCreateVarValueEncoding(var2, value2);
3114 auto* bool_or = context_->AddEnforcedConstraint(ct)->mutable_bool_or();
3120 VLOG(3) <<
"ConvertLinear2NeCst: |enforcements| = "
3121 << ct->enforcement_literal_size()
3122 <<
", domain1 = " << context_->DomainOf(var1)
3123 <<
", domain2 = " << context_->DomainOf(var2)
3124 <<
", coeff1 = " << coeff1 <<
", coeff2 = " << coeff2
3125 <<
", domain_of_z = " << domain_of_z
3126 <<
", num_clauses = " << num_clauses;
3128 context_->UpdateNewConstraintsVariableUsage();
3129 context_->UpdateRuleStats(
"linear2: convert ax + by != cte to clauses");
3130 return RemoveConstraint(ct);
3132 VLOG(3) <<
"TODO ConvertLinear2NeCst: |enforcements| = "
3133 << ct->enforcement_literal_size()
3134 <<
", domain1 = " << context_->DomainOf(var1)
3135 <<
", domain2 = " << context_->DomainOf(var2)
3136 <<
", coeff1 = " << coeff1 <<
", coeff2 = " << coeff2
3137 <<
", rhs = " << rhs <<
", domain_of_z = " << domain_of_z
3138 <<
", |encoding1| = " << context_->GetValueEncodingSize(var1)
3139 <<
", |encoding2| = " << context_->GetValueEncodingSize(var2);
3140 context_->UpdateRuleStats(
3141 "TODO linear2: convert ax + by != cte to clauses for large domains");
3146bool CpModelPresolver::PresolveUnenforcedLinear2EqCst(
ConstraintProto* ct,
3148 DCHECK_EQ(ct->linear().vars().size(), 2);
3150 const LinearConstraintProto& arg = ct->linear();
3151 const int var1 = arg.vars(0);
3152 const int var2 = arg.vars(1);
3153 const int64_t coeff1 = arg.coeffs(0);
3154 const int64_t coeff2 = arg.coeffs(1);
3157 CHECK(ct->enforcement_literal().empty());
3164 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
3165 }
else if (coeff2 == 1) {
3166 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
3167 }
else if (coeff1 == -1) {
3168 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
3169 }
else if (coeff2 == -1) {
3170 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
3179 context_->UpdateRuleStats(
"TODO linear2: ax + by = cte");
3181 if (added)
return RemoveConstraint(ct);
3185bool CpModelPresolver::PresolveEnforcedLinear2EqCst(
ConstraintProto* ct,
3187 CHECK(!ct->enforcement_literal().empty());
3188 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(0)));
3189 DCHECK(context_->VariableIsAffineRepresentative(ct->linear().vars(1)));
3190 const LinearConstraintProto& arg = ct->linear();
3192 const int var1 = arg.vars(0);
3193 const int64_t coeff1 = arg.coeffs(0);
3194 const Domain d1 = context_->DomainOf(var1);
3196 const int var2 = arg.vars(1);
3197 const int64_t coeff2 = arg.coeffs(1);
3198 const Domain d2 = context_->DomainOf(var2);
3207 return MarkConstraintAsFalse(
3208 ct,
"linear2: implied ax + by = cte has no solutions");
3210 const Domain reduced_domain =
3211 context_->DomainOf(var1)
3212 .AdditionWith(Domain(-x0))
3213 .InverseMultiplicationBy(
b)
3214 .IntersectionWith(context_->DomainOf(var2)
3215 .AdditionWith(Domain(-y0))
3216 .InverseMultiplicationBy(-a));
3218 if (reduced_domain.IsEmpty()) {
3219 return MarkConstraintAsFalse(
3220 ct,
"linear2: implied ax + by = cte has no solutions");
3223 if (reduced_domain.Size() == 1) {
3224 const int64_t z = reduced_domain.FixedValue();
3225 const int64_t value1 = x0 +
b * z;
3226 const int64_t value2 = y0 - a * z;
3228 DCHECK(context_->DomainOf(var1).Contains(value1));
3229 DCHECK(context_->DomainOf(var2).Contains(value2));
3230 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3232 LinearConstraintProto* linear1 =
3233 context_->AddEnforcedConstraint(ct)->mutable_linear();
3234 linear1->add_vars(var1);
3235 linear1->add_coeffs(1);
3236 linear1->add_domain(value1);
3237 linear1->add_domain(value1);
3239 LinearConstraintProto* linear2 =
3240 context_->AddEnforcedConstraint(ct)->mutable_linear();
3241 linear2->add_vars(var2);
3242 linear2->add_coeffs(1);
3243 linear2->add_domain(value2);
3244 linear2->add_domain(value2);
3246 context_->UpdateRuleStats(
3247 "linear2: implied ax + by = cte has only one solution");
3248 context_->UpdateNewConstraintsVariableUsage();
3249 return RemoveConstraint(ct);
3252 const int64_t max_domain_size =
3253 context_->params().max_domain_size_for_linear2_expansion();
3254 const int64_t small_domain_size = max_domain_size / 2;
3256 if (reduced_domain.Size() <= max_domain_size &&
3257 (context_->IsMostlyFullyEncoded(var1) ||
3258 context_->DomainSize(var1) <= small_domain_size) &&
3259 (context_->IsMostlyFullyEncoded(var2) ||
3260 context_->DomainSize(var2) <= small_domain_size)) {
3263 const auto imply_one_direction = [ct,
this, &num_imply1, &num_imply2](
3264 const Domain& domain1,
3265 const Domain& domain2,
int var1,
3266 int var2, int64_t coeff1,
3267 int64_t coeff2, int64_t cte) {
3268 if (context_->ModelIsUnsat())
return false;
3270 for (
const int64_t value : domain1.Values()) {
3271 const int64_t residual = cte - coeff1 * value;
3272 const int64_t implied_value = residual / coeff2;
3273 if (residual % coeff2 != 0 || !domain2.Contains(implied_value)) {
3274 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value);
3275 context_->AddEnforcedConstraint(ct)->mutable_bool_and()->add_literals(
3279 const int lit1 = context_->GetOrCreateVarValueEncoding(var1, value);
3281 context_->GetOrCreateVarValueEncoding(var2, implied_value);
3282 ConstraintProto* imply_value = context_->AddEnforcedConstraint(ct);
3283 imply_value->mutable_bool_or()->add_literals(
NegatedRef(lit1));
3284 imply_value->mutable_bool_or()->add_literals(lit2);
3292 if (!imply_one_direction(d1, d2, var1, var2, coeff1, coeff2, rhs)) {
3295 if (d1.Size() > 2 || d2.Size() > 2 || num_imply1 > 0) {
3296 if (!imply_one_direction(d2, d1, var2, var1, coeff2, coeff1, rhs)) {
3301 VLOG(3) <<
"ConvertLinear2EqCst: |enforcements| = "
3302 << ct->enforcement_literal_size() <<
", domain1 = " << d1
3303 <<
", domain2 = " << d2 <<
", coeff1 = " << coeff1
3304 <<
", coeff2 = " << coeff2 <<
", num_imply1 = " << num_imply1
3305 <<
", num_imply2 = " << num_imply2;
3307 context_->UpdateNewConstraintsVariableUsage();
3308 context_->UpdateRuleStats(
3309 "linear2: convert implied ax + by == cte to clauses");
3310 return RemoveConstraint(ct);
3311 }
else if (std::abs(coeff1) != 1 || std::abs(coeff2) != 1 ||
3312 coeff1 + coeff2 != 0) {
3313 VLOG(3) <<
"TODO ConvertLinear2EqCst: |enforcements| = "
3314 << ct->enforcement_literal_size()
3315 <<
", domain1 = " << context_->DomainOf(var1)
3316 <<
", domain2 = " << context_->DomainOf(var2)
3317 <<
", coeff1 = " << coeff1 <<
", coeff2 = " << coeff2
3318 <<
", rhs = " << rhs
3319 <<
", |encoding1| = " << context_->GetValueEncodingSize(var1)
3320 <<
", |encoding2| = " << context_->GetValueEncodingSize(var2);
3321 context_->UpdateRuleStats(
3322 "TODO linear2: convert implied ax + by == cte to clauses for large "
3328bool CpModelPresolver::PresolveEmptyLinearConstraint(
ConstraintProto* ct) {
3331 context_->UpdateRuleStats(
"linear: empty");
3332 return RemoveConstraint(ct);
3334 return MarkConstraintAsFalse(ct,
"linear: empty");
3340 if (context_->ModelIsUnsat())
return false;
3342 bool changed =
false;
3343 if (ct->linear().vars().size() <= 2) {
3344 if (!CanonicalizeLinear(ct, &changed))
return true;
3348 if (ct->linear().vars().empty()) {
3349 return PresolveEmptyLinearConstraint(ct);
3350 }
else if (ct->linear().vars().size() == 1) {
3351 return PresolveLinearOfSizeOne(ct) || changed;
3352 }
else if (ct->linear().vars().size() == 2) {
3353 return PresolveLinearOfSizeTwo(ct) || changed;
3361 if (ct->linear().vars().size() <= 1)
return false;
3362 if (context_->ModelIsUnsat())
return false;
3365 if (context_->params().enumerate_all_solutions())
return false;
3367 const LinearConstraintProto& linear_constraint = ct->linear();
3368 if (linear_constraint.domain_size() != 2)
return false;
3369 if (linear_constraint.domain(0) != linear_constraint.domain(1))
return false;
3371 std::vector<int64_t> lbs(linear_constraint.vars_size());
3372 std::vector<int64_t> ubs(linear_constraint.vars_size());
3373 for (
int i = 0;
i < linear_constraint.vars_size(); ++
i) {
3374 lbs[
i] = context_->MinOf(linear_constraint.vars(
i));
3375 ubs[
i] = context_->MaxOf(linear_constraint.vars(
i));
3378 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3380 if (!diophantine_solution.has_solutions) {
3381 return MarkConstraintAsFalse(ct,
"diophantine: equality has no solutions");
3383 if (diophantine_solution.no_reformulation_needed)
return false;
3386 for (
const std::vector<absl::int128>&
b : diophantine_solution.kernel_basis) {
3388 context_->UpdateRuleStats(
3389 "diophantine: couldn't apply due to int64_t overflow");
3394 context_->UpdateRuleStats(
3395 "diophantine: couldn't apply due to int64_t overflow");
3399 const int num_replaced_variables =
3400 static_cast<int>(diophantine_solution.special_solution.size());
3401 const int num_new_variables =
3402 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3403 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3404 for (
int i = 0;
i < num_new_variables; ++
i) {
3407 context_->UpdateRuleStats(
3408 "diophantine: couldn't apply due to int64_t overflow");
3418 std::vector<int> new_variables(num_new_variables);
3419 for (
int i = 0;
i < num_new_variables; ++
i) {
3420 new_variables[
i] = context_->working_model->variables_size();
3421 IntegerVariableProto* var = context_->working_model->add_variables();
3423 static_cast<int64_t
>(diophantine_solution.kernel_vars_lbs[
i]));
3425 static_cast<int64_t
>(diophantine_solution.kernel_vars_ubs[
i]));
3426 if (!ct->name().empty()) {
3427 var->set_name(absl::StrCat(
"u_diophantine_", ct->name(),
"_",
i));
3437 std::vector<std::vector<int64_t>> lin_vars_lbs(num_replaced_variables);
3438 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3439 ConstraintProto* identity = context_->working_model->add_constraints();
3440 LinearConstraintProto* lin = identity->mutable_linear();
3441 if (!ct->name().empty()) {
3442 identity->set_name(absl::StrCat(
"c_diophantine_", ct->name(),
"_",
i));
3444 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3446 linear_constraint.vars(diophantine_solution.index_permutation[
i]);
3448 lin_vars_lbs[
i].push_back(context_->MinOf(var));
3451 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3453 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3454 for (
int j = std::max(1,
i); j < num_replaced_variables; ++j) {
3455 lin->add_vars(new_variables[j - 1]);
3456 lin_vars_lbs[
i].push_back(
3457 static_cast<int64_t
>(diophantine_solution.kernel_vars_lbs[j - 1]));
3459 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3461 for (
int j = num_replaced_variables; j < linear_constraint.vars_size();
3464 linear_constraint.vars(diophantine_solution.index_permutation[j]);
3466 lin_vars_lbs[
i].push_back(context_->MinOf(var));
3468 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3476 context_->UpdateRuleStats(
3477 "diophantine: couldn't apply due to overflowing activity of new "
3480 context_->working_model->mutable_constraints()->DeleteSubrange(
3481 context_->working_model->constraints_size() -
i - 1,
i + 1);
3482 context_->working_model->mutable_variables()->DeleteSubrange(
3483 context_->working_model->variables_size() - num_new_variables,
3488 context_->InitializeNewDomains();
3492 const int num_constraints = context_->working_model->constraints_size();
3493 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3494 const LinearConstraintProto& linear =
3495 context_->working_model->constraints(num_constraints - 1 -
i).linear();
3496 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3497 solution_crush_.SetVarToLinearConstraintSolution(
3498 ct->enforcement_literal(), std::nullopt, linear.vars(), lin_vars_lbs[
i],
3499 linear.coeffs(), linear.domain(0));
3502 if (VLOG_IS_ON(2)) {
3503 std::string log_eq = absl::StrCat(linear_constraint.domain(0),
" = ");
3504 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3505 for (
int i = 0;
i < terms_to_show; ++
i) {
3506 if (
i > 0) absl::StrAppend(&log_eq,
" + ");
3509 linear_constraint.coeffs(diophantine_solution.index_permutation[
i]),
3511 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
3513 if (terms_to_show < linear_constraint.vars_size()) {
3514 absl::StrAppend(&log_eq,
"+ ... (", linear_constraint.vars_size(),
3517 VLOG(2) <<
"[Diophantine] " << log_eq;
3520 context_->UpdateRuleStats(
"diophantine: reformulated equality");
3521 context_->UpdateNewConstraintsVariableUsage();
3522 return RemoveConstraint(ct);
3537void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3540 if (context_->ModelIsUnsat())
return;
3543 const LinearConstraintProto& lin = ct->linear();
3544 if (lin.domain().size() != 2)
return;
3550 int64_t max_variation = 0;
3552 rd_entries_.clear();
3553 rd_magnitudes_.clear();
3557 int64_t max_magnitude = 0;
3558 const int num_terms = lin.vars().size();
3559 for (
int i = 0;
i < num_terms; ++
i) {
3560 const int64_t coeff = lin.coeffs(
i);
3561 const int64_t magnitude = std::abs(lin.coeffs(
i));
3562 if (magnitude == 0)
continue;
3563 max_magnitude = std::max(max_magnitude, magnitude);
3568 lb = context_->MinOf(lin.vars(
i));
3569 ub = context_->MaxOf(lin.vars(
i));
3571 lb = -context_->MaxOf(lin.vars(
i));
3572 ub = -context_->MinOf(lin.vars(
i));
3574 lb_sum += lb * magnitude;
3575 ub_sum += ub * magnitude;
3578 if (lb == ub)
return;
3580 rd_lbs_.push_back(lb);
3581 rd_ubs_.push_back(ub);
3582 rd_magnitudes_.push_back(magnitude);
3583 rd_entries_.push_back({magnitude, magnitude * (ub - lb),
i});
3584 max_variation += rd_entries_.back().max_variation;
3589 if (lb_sum > rhs.
Max() || rhs.
Min() > ub_sum) {
3590 (void)MarkConstraintAsFalse(ct,
"linear: trivially false");
3591 context_->UpdateConstraintVariableUsage(c);
3594 const IntegerValue rhs_ub(
CapSub(rhs.
Max(), lb_sum));
3595 const IntegerValue rhs_lb(
CapSub(ub_sum, rhs.
Min()));
3596 const bool use_ub = max_variation > rhs_ub;
3597 const bool use_lb = max_variation > rhs_lb;
3598 if (!use_ub && !use_lb) {
3599 context_->UpdateRuleStats(
"linear: trivially true");
3600 (void)RemoveConstraint(ct);
3601 context_->UpdateConstraintVariableUsage(c);
3606 if (max_magnitude <= 1)
return;
3611 lb_feasible_.Reset(rhs_lb.value());
3612 lb_infeasible_.Reset(rhs.
Min() - lb_sum - 1);
3615 ub_feasible_.Reset(rhs_ub.value());
3616 ub_infeasible_.Reset(ub_sum - rhs.
Max() - 1);
3622 int64_t max_error = max_variation;
3623 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3624 [](
const RdEntry& a,
const RdEntry&
b) {
3625 return a.magnitude > b.magnitude;
3628 rd_divisors_.clear();
3629 for (
int i = 0;
i < rd_entries_.size(); ++
i) {
3630 const RdEntry& e = rd_entries_[
i];
3631 gcd = std::gcd(gcd, e.magnitude);
3632 max_error -= e.max_variation;
3638 range += e.max_variation / e.magnitude;
3639 if (
i + 1 < rd_entries_.size() &&
3640 e.magnitude == rd_entries_[
i + 1].magnitude) {
3643 const int64_t saved_range = range;
3646 if (e.magnitude > 1) {
3651 rd_divisors_.push_back(e.magnitude);
3655 bool simplify_lb =
false;
3657 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3658 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3661 if (
CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3662 lb_feasible_.Bound()) {
3667 if (
CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3668 lb_infeasible_.Bound()) {
3674 bool simplify_ub =
false;
3676 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3677 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3678 if (
CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3679 ub_feasible_.Bound()) {
3682 if (
CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3683 ub_infeasible_.Bound()) {
3690 if (max_error == 0)
break;
3691 if (simplify_lb && simplify_ub) {
3693 context_->UpdateRuleStats(
"linear: remove irrelevant part");
3694 int64_t shift_lb = 0;
3695 int64_t shift_ub = 0;
3698 for (
int j = 0; j <=
i; ++j) {
3699 const int index = rd_entries_[j].index;
3700 const int64_t m = rd_magnitudes_[index];
3701 shift_lb += rd_lbs_[index] * m;
3702 shift_ub += rd_ubs_[index] * m;
3703 rd_vars_.push_back(lin.vars(index));
3704 rd_coeffs_.push_back(lin.coeffs(index));
3706 LinearConstraintProto* mut_lin = ct->mutable_linear();
3707 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3708 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3714 const int64_t new_rhs_lb =
3715 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3716 const int64_t new_rhs_ub =
3717 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3718 if (new_rhs_lb > new_rhs_ub) {
3719 (void)MarkConstraintAsFalse(ct,
"linear: false after simplification");
3720 context_->UpdateConstraintVariableUsage(c);
3724 DivideLinearByGcd(ct);
3725 context_->UpdateConstraintVariableUsage(c);
3733 if (DivideLinearByGcd(ct)) {
3734 context_->UpdateConstraintVariableUsage(c);
3741 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3742 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3743 context_->UpdateRuleStats(
"linear: reduce rhs with DP");
3744 const int64_t new_rhs_lb =
3745 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3746 const int64_t new_rhs_ub =
3747 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3748 if (new_rhs_lb > new_rhs_ub) {
3749 (void)MarkConstraintAsFalse(ct,
"linear: reduce rhs with DP");
3750 context_->UpdateConstraintVariableUsage(c);
3757 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3758 for (
const int64_t divisor : rd_divisors_) {
3762 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.
Max(), &new_ub)) {
3767 int64_t minus_new_lb;
3768 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3769 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3770 rd_lbs_[
i] = -rd_lbs_[
i];
3771 rd_ubs_[
i] = -rd_ubs_[
i];
3774 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.
Min(),
3776 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3777 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3778 rd_lbs_[
i] = -rd_lbs_[
i];
3779 rd_ubs_[
i] = -rd_ubs_[
i];
3785 context_->UpdateRuleStats(
"linear: simplify using approximate gcd");
3787 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3788 for (
int i = 0;
i < lin.coeffs().size(); ++
i) {
3789 const int64_t new_coeff =
3791 if (new_coeff == 0)
continue;
3792 mutable_linear->set_vars(new_size, lin.vars(
i));
3793 mutable_linear->set_coeffs(new_size, new_coeff);
3796 mutable_linear->mutable_vars()->Truncate(new_size);
3797 mutable_linear->mutable_coeffs()->Truncate(new_size);
3798 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3799 if (new_rhs.IsEmpty()) {
3800 (void)MarkConstraintAsFalse(ct,
"linear: false after approximate gcd");
3804 context_->UpdateConstraintVariableUsage(c);
3814bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
3815 const Domain& terms,
const Domain& rhs) {
3816 if (var_domain.NumIntervals() != 1)
return false;
3817 if (std::abs(coeff) != 1)
return false;
3825 if (coeff == 1 && terms.
Max() + var_domain.
Min() <= rhs.
Min()) {
3828 if (coeff == -1 && terms.
Max() - var_domain.
Max() <= rhs.
Min()) {
3834bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
3835 const Domain& terms,
const Domain& rhs) {
3836 if (var_domain.NumIntervals() != 1)
return false;
3837 if (std::abs(coeff) != 1)
return false;
3839 if (coeff == 1 && terms.
Min() + var_domain.
Max() >= rhs.
Max()) {
3842 if (coeff == -1 && terms.
Min() - var_domain.
Min() >= rhs.
Max()) {
3848int FixLiteralFromSet(
const absl::flat_hash_set<int>& literals_at_true,
3852 const int num_terms = linear->vars().size();
3854 for (
int i = 0;
i < num_terms; ++
i) {
3855 const int var = linear->vars(
i);
3856 const int64_t coeff = linear->coeffs(
i);
3857 if (literals_at_true.contains(var)) {
3861 }
else if (!literals_at_true.contains(
NegatedRef(var))) {
3862 linear->set_vars(new_size, var);
3863 linear->set_coeffs(new_size, coeff);
3870 linear->mutable_vars()->Truncate(new_size);
3871 linear->mutable_coeffs()->Truncate(new_size);
3881void CpModelPresolver::ProcessAtMostOneAndLinear() {
3882 if (time_limit_->LimitReached())
return;
3883 if (context_->ModelIsUnsat())
return;
3884 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
3885 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3887 ActivityBoundHelper amo_in_linear;
3888 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3890 int num_changes = 0;
3891 const int num_constraints = context_->working_model->constraints_size();
3892 for (
int c = 0;
c < num_constraints; ++
c) {
3893 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3897 for (
int i = 0;
i < 5; ++
i) {
3898 const int old_size = ct->linear().vars().size();
3899 const int old_enf_size = ct->enforcement_literal().size();
3900 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3901 if (context_->ModelIsUnsat())
return;
3903 if (ct->linear().vars().size() == old_size &&
3904 ct->enforcement_literal().size() == old_enf_size) {
3911 timer.AddCounter(
"num_changes", num_changes);
3919void CpModelPresolver::ProcessOneLinearWithAmo(
int ct_index,
3923 if (ct->linear().vars().size() <= 1)
return;
3930 Domain non_boolean_domain(0);
3931 const int initial_size = ct->linear().vars().size();
3932 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3933 int64_t max_magnitude = 0;
3934 for (
int i = 0;
i < initial_size; ++
i) {
3936 int ref = ct->linear().vars(
i);
3937 int64_t coeff = ct->linear().coeffs(
i);
3942 if (context_->CanBeUsedAsLiteral(ref)) {
3943 tmp_terms_.push_back({ref, coeff});
3944 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3945 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3947 non_boolean_domain =
3950 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3951 .RelaxIfTooComplex();
3952 temp_ct_.mutable_linear()->add_vars(ref);
3953 temp_ct_.mutable_linear()->add_coeffs(coeff);
3958 if (tmp_terms_.empty())
return;
3969 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3970 min_magnitude < max_magnitude) {
3971 int64_t min_activity = 0;
3972 for (
const auto [ref, coeff] : tmp_terms_) {
3974 min_activity += coeff;
3977 const int64_t transformed_rhs = rhs.
Max() - min_activity;
3978 if (min_activity >= rhs.
Min() && max_magnitude <= transformed_rhs) {
3979 std::vector<int> literals;
3980 for (
const auto [ref, coeff] : tmp_terms_) {
3981 if (coeff + min_magnitude > transformed_rhs)
continue;
3982 literals.push_back(coeff > 0 ? ref :
NegatedRef(ref));
3984 if (helper->IsAmo(literals)) {
3986 context_->UpdateRuleStats(
"linear + amo: detect hidden AMO");
3988 for (
int i = 0;
i < initial_size; ++
i) {
3990 if (ct->linear().coeffs(
i) > 0) {
3991 ct->mutable_linear()->set_coeffs(
i, 1);
3993 ct->mutable_linear()->set_coeffs(
i, -1);
4004 const int64_t min_bool_activity =
4005 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
4006 const int64_t max_bool_activity =
4007 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
4011 const Domain activity = non_boolean_domain.AdditionWith(
4012 Domain(min_bool_activity, max_bool_activity));
4013 if (activity.IntersectionWith(rhs).IsEmpty()) {
4015 (void)MarkConstraintAsFalse(ct,
4016 "linear + amo: infeasible linear constraint");
4017 context_->UpdateConstraintVariableUsage(ct_index);
4019 }
else if (activity.IsIncludedIn(rhs)) {
4020 context_->UpdateRuleStats(
"linear + amo: trivial linear constraint");
4022 context_->UpdateConstraintVariableUsage(ct_index);
4027 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
4030 Domain(min_bool_activity, max_bool_activity).Negation()),
4031 temp_ct_.mutable_linear());
4032 if (!PropagateDomainsInLinear(-1, &temp_ct_)) {
4035 if (context_->ModelIsUnsat())
return;
4050 std::vector<int> new_enforcement;
4051 std::vector<int> must_be_true;
4052 for (
int i = 0;
i < tmp_terms_.size(); ++
i) {
4053 const int ref = tmp_terms_[
i].first;
4055 const Domain bool0(conditional_mins_[
i][0], conditional_maxs_[
i][0]);
4056 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
4057 if (activity0.IntersectionWith(rhs).IsEmpty()) {
4059 must_be_true.push_back(ref);
4060 }
else if (activity0.IsIncludedIn(rhs)) {
4062 new_enforcement.push_back(ref);
4065 const Domain bool1(conditional_mins_[
i][1], conditional_maxs_[
i][1]);
4066 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
4067 if (activity1.IntersectionWith(rhs).IsEmpty()) {
4070 }
else if (activity1.IsIncludedIn(rhs)) {
4081 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
4084 context_->UpdateRuleStats(
"linear + amo: fixed literal",
4085 must_be_true.size());
4086 for (
const int lit : must_be_true) {
4087 if (!context_->SetLiteralToTrue(lit))
return;
4089 bool changed =
false;
4090 if (!CanonicalizeLinear(ct, &changed))
return;
4091 context_->UpdateConstraintVariableUsage(ct_index);
4095 if (!new_enforcement.empty()) {
4096 context_->UpdateRuleStats(
"linear + amo: extracted enforcement literal",
4097 new_enforcement.size());
4098 for (
const int ref : new_enforcement) {
4099 ct->add_enforcement_literal(ref);
4103 if (!ct->enforcement_literal().empty()) {
4104 const int old_enf_size = ct->enforcement_literal().size();
4105 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
4106 context_->UpdateRuleStats(
"linear + amo: infeasible enforcement");
4108 context_->UpdateConstraintVariableUsage(ct_index);
4111 if (ct->enforcement_literal().size() < old_enf_size) {
4112 context_->UpdateRuleStats(
"linear + amo: simplified enforcement list");
4113 context_->UpdateConstraintVariableUsage(ct_index);
4116 for (
const int lit : must_be_true) {
4120 (void)MarkConstraintAsFalse(
4121 ct,
"linear + amo: advanced infeasible linear constraint");
4122 context_->UpdateConstraintVariableUsage(ct_index);
4128 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
4132 context_->UpdateRuleStats(
"linear + amo: added implications");
4133 ConstraintProto* new_ct = context_->working_model->add_constraints();
4134 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4135 for (
const int lit : must_be_true) {
4136 new_ct->mutable_bool_and()->add_literals(lit);
4137 temp_set_.insert(lit);
4139 context_->UpdateNewConstraintsVariableUsage();
4142 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
4143 if (num_fixed > new_enforcement.size()) {
4144 context_->UpdateRuleStats(
4145 "linear + amo: fixed literal implied by enforcement");
4147 if (num_fixed > 0) {
4148 context_->UpdateConstraintVariableUsage(ct_index);
4152 if (ct->linear().vars().empty()) {
4153 context_->UpdateRuleStats(
"linear + amo: empty after processing");
4154 PresolveSmallLinear(ct);
4155 context_->UpdateConstraintVariableUsage(ct_index);
4160 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
4161 context_->UpdateConstraintVariableUsage(ct_index);
4170 if (!ct->enforcement_literal().empty()) {
4173 Domain non_boolean_domain(0);
4174 const int num_ct_terms = ct->linear().vars().size();
4175 for (
int i = 0;
i < num_ct_terms; ++
i) {
4176 const int ref = ct->linear().vars(
i);
4177 const int64_t coeff = ct->linear().coeffs(
i);
4179 if (context_->CanBeUsedAsLiteral(ref)) {
4180 tmp_terms_.push_back({ref, coeff});
4182 non_boolean_domain =
4185 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
4186 .RelaxIfTooComplex();
4189 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
4191 if (num_removed > 0) {
4192 context_->UpdateRuleStats(
"linear + amo: removed enforcement literal",
4194 context_->UpdateConstraintVariableUsage(ct_index);
4199bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
4202 if (context_->ModelIsUnsat())
return false;
4205 int64_t min_activity;
4206 int64_t max_activity;
4209 const int num_vars = ct->linear().vars_size();
4210 auto& term_domains = context_->tmp_term_domains;
4211 auto& left_domains = context_->tmp_left_domains;
4212 const bool slow_mode = num_vars < 10;
4216 term_domains.resize(num_vars + 1);
4217 left_domains.resize(num_vars + 1);
4218 left_domains[0] = Domain(0);
4219 term_domains[num_vars] = Domain(0);
4220 for (
int i = 0;
i < num_vars; ++
i) {
4221 const int var = ct->linear().vars(
i);
4222 const int64_t coeff = ct->linear().coeffs(
i);
4224 term_domains[
i] = context_->DomainOf(var).MultiplicationBy(coeff);
4225 left_domains[
i + 1] =
4226 left_domains[
i].AdditionWith(term_domains[
i]).RelaxIfTooComplex();
4229 std::tie(min_activity, max_activity) =
4230 context_->ComputeMinMaxActivity(ct->linear());
4232 const Domain& implied_rhs =
4233 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
4237 if (implied_rhs.IsIncludedIn(old_rhs)) {
4238 if (ct_index != -1) context_->UpdateRuleStats(
"linear: always true");
4239 return RemoveConstraint(ct);
4243 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
4244 if (rhs.IsEmpty()) {
4245 return MarkConstraintAsFalse(ct,
"linear: infeasible");
4247 if (rhs != old_rhs) {
4248 if (ct_index != -1) context_->UpdateRuleStats(
"linear: simplified rhs");
4253 if (ct->enforcement_literal().size() > 1)
return false;
4255 bool new_bounds =
false;
4256 bool recanonicalize =
false;
4257 Domain negated_rhs = rhs.Negation();
4258 Domain right_domain(0);
4260 Domain activity_minus_term;
4261 for (
int i = num_vars - 1;
i >= 0; --
i) {
4262 const int var = ct->linear().vars(
i);
4263 const int64_t var_coeff = ct->linear().coeffs(
i);
4267 right_domain.AdditionWith(term_domains[
i + 1]).RelaxIfTooComplex();
4268 activity_minus_term = left_domains[
i].AdditionWith(right_domain);
4270 int64_t min_term = var_coeff * context_->MinOf(var);
4271 int64_t max_term = var_coeff * context_->MaxOf(var);
4272 if (var_coeff < 0) std::swap(min_term, max_term);
4273 activity_minus_term =
4274 Domain(min_activity - min_term, max_activity - max_term);
4276 new_domain = activity_minus_term.AdditionWith(negated_rhs)
4277 .InverseMultiplicationBy(-var_coeff);
4279 if (ct->enforcement_literal().empty()) {
4281 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
4284 }
else if (ct->enforcement_literal().size() == 1) {
4287 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
4288 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
4293 if (context_->IsFixed(var)) {
4295 recanonicalize =
true;
4300 if (ct_index == -1)
continue;
4301 if (!ct->enforcement_literal().empty())
continue;
4313 if (rhs.
Min() != rhs.
Max() &&
4314 context_->VariableWithCostIsUniqueAndRemovable(var)) {
4315 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
4316 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
4318 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
4319 activity_minus_term, rhs)) {
4320 rhs = Domain(rhs.
Min());
4323 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
4324 activity_minus_term, rhs)) {
4325 rhs = Domain(rhs.
Max());
4329 context_->UpdateRuleStats(
"linear: tightened into equality");
4331 solution_crush_.SetVarToLinearConstraintSolution(
4332 {},
i, ct->linear().vars(),
4333 {}, ct->linear().coeffs(), rhs.FixedValue());
4335 negated_rhs = rhs.Negation();
4339 right_domain = Domain(0);
4353 if (ct->linear().vars().size() <= 2)
continue;
4358 if (rhs.
Min() != rhs.
Max())
continue;
4365 if (context_->params().keep_all_feasible_solutions_in_presolve())
continue;
4371 if (std::abs(var_coeff) != 1)
continue;
4372 if (context_->params().presolve_substitution_level() <= 0)
continue;
4375 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
4377 int col_size = context_->VarToConstraints(var).size();
4378 if (is_in_objective) col_size--;
4379 const int row_size = ct->linear().vars_size();
4383 const int num_entries_added = (row_size - 1) * (col_size - 1);
4384 const int num_entries_removed = col_size + row_size - 1;
4385 if (num_entries_added > num_entries_removed)
continue;
4390 std::vector<int> others;
4392 for (
const int c : context_->VarToConstraints(var)) {
4398 if (c == ct_index)
continue;
4399 if (context_->working_model->constraints(c).constraint_case() !=
4404 for (
const int ref :
4405 context_->working_model->constraints(c).enforcement_literal()) {
4412 others.push_back(c);
4414 if (abort)
continue;
4420 if (context_->DomainOf(var) != new_domain) {
4427 if (others.size() != 1)
continue;
4428 const ConstraintProto& other_ct =
4429 context_->working_model->constraints(others.front());
4430 if (!other_ct.enforcement_literal().empty())
continue;
4434 const LinearConstraintProto& other_lin = other_ct.linear();
4435 if (other_lin.vars().size() > 100)
continue;
4437 int64_t other_coeff = 0;
4438 for (
int i = 0;
i < other_lin.vars().size(); ++
i) {
4439 const int v = other_lin.vars(
i);
4440 const int64_t coeff = other_lin.coeffs(
i);
4444 other_coeff += coeff;
4448 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4449 .RelaxIfTooComplex();
4452 if (other_coeff == 0)
continue;
4453 implied = implied.InverseMultiplicationBy(other_coeff);
4457 if (!context_->IntersectDomainWith(var, implied))
return false;
4458 if (context_->IsFixed(var))
continue;
4459 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4463 context_->UpdateRuleStats(
"linear: doubleton free");
4469 if (is_in_objective &&
4470 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4471 if (context_->ModelIsUnsat())
return false;
4476 ConstraintProto copy_if_we_abort;
4477 absl::c_sort(others);
4478 for (
const int c : others) {
4482 copy_if_we_abort = context_->working_model->constraints(c);
4490 var, var_coeff, *ct,
4491 context_->working_model->mutable_constraints(c))) {
4498 bool changed =
false;
4499 if (!CanonicalizeLinear(context_->working_model->mutable_constraints(c),
4504 context_->UpdateConstraintVariableUsage(c);
4511 *context_->working_model,
4512 context_->working_model->constraints(c).linear().vars(),
4513 context_->working_model->constraints(c).linear().coeffs())) {
4515 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4521 context_->UpdateConstraintVariableUsage(c);
4523 if (abort)
continue;
4525 context_->UpdateRuleStats(
4526 absl::StrCat(
"linear: variable substitution ", others.size()));
4535 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4536 context_->MarkVariableAsRemoved(var);
4537 ConstraintProto* mapping_ct =
4538 context_->NewMappingConstraint(__FILE__, __LINE__);
4540 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4541 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4542 mapping_linear_ct->mutable_vars()->at(
i));
4543 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4544 mapping_linear_ct->mutable_coeffs()->at(
i));
4545 return RemoveConstraint(ct);
4549 if (ct_index == -1) {
4551 context_->UpdateRuleStats(
4552 "linear: reduced variable domains in derived constraint");
4558 context_->UpdateRuleStats(
"linear: reduced variable domains");
4560 if (recanonicalize) {
4561 bool changed =
false;
4562 (void)CanonicalizeLinear(ct, &changed);
4570void CpModelPresolver::LowerThanCoeffStrengthening(
bool from_lower_bound,
4571 int64_t min_magnitude,
4574 const LinearConstraintProto& arg = ct->linear();
4575 const int64_t second_threshold = rhs - min_magnitude;
4576 const int num_vars = arg.vars_size();
4589 if (min_magnitude <= second_threshold) {
4591 int64_t max_magnitude_left = 0;
4592 int64_t max_activity_left = 0;
4593 int64_t activity_when_coeff_are_one = 0;
4595 for (
int i = 0;
i < num_vars; ++
i) {
4596 const int64_t magnitude = std::abs(arg.coeffs(
i));
4597 if (magnitude <= second_threshold) {
4598 gcd = std::gcd(gcd, magnitude);
4599 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4600 const int64_t bound_diff =
4601 context_->MaxOf(arg.vars(
i)) - context_->MinOf(arg.vars(
i));
4602 activity_when_coeff_are_one += bound_diff;
4603 max_activity_left += magnitude * bound_diff;
4606 CHECK_GT(min_magnitude, 0);
4607 CHECK_LE(min_magnitude, max_magnitude_left);
4610 int64_t new_rhs = 0;
4611 bool set_all_to_one =
false;
4612 if (max_activity_left <= rhs) {
4614 context_->UpdateRuleStats(
"linear with partial amo: trivial");
4615 new_rhs = activity_when_coeff_are_one;
4616 set_all_to_one =
true;
4617 }
else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4619 context_->UpdateRuleStats(
"linear with partial amo: constant coeff");
4620 new_rhs = rhs / min_magnitude;
4621 set_all_to_one =
true;
4622 }
else if (gcd > 1) {
4624 context_->UpdateRuleStats(
"linear with partial amo: gcd");
4625 new_rhs = rhs / gcd;
4629 int64_t rhs_offset = 0;
4630 for (
int i = 0;
i < num_vars; ++
i) {
4631 const int ref = arg.vars(
i);
4632 const int64_t coeff = from_lower_bound ? arg.coeffs(
i) : -arg.coeffs(
i);
4635 const int64_t magnitude = std::abs(coeff);
4636 if (magnitude > rhs) {
4637 new_coeff = new_rhs + 1;
4638 }
else if (magnitude > second_threshold) {
4639 new_coeff = new_rhs;
4641 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4647 ct->mutable_linear()->set_coeffs(
i, new_coeff);
4648 rhs_offset += new_coeff * context_->MinOf(ref);
4650 ct->mutable_linear()->set_coeffs(
i, -new_coeff);
4651 rhs_offset -= new_coeff * context_->MaxOf(ref);
4655 ct->mutable_linear());
4660 int64_t rhs_offset = 0;
4661 for (
int i = 0;
i < num_vars; ++
i) {
4662 int ref = arg.vars(
i);
4663 int64_t coeff = arg.coeffs(
i);
4670 if (ct->enforcement_literal().empty()) {
4678 context_->UpdateRuleStats(
"linear: fix variable to its bound");
4679 if (!context_->IntersectDomainWith(
4680 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4681 : context_->MaxOf(ref)))) {
4689 if (coeff > second_threshold && coeff < rhs) {
4690 context_->UpdateRuleStats(
4691 "linear: coefficient strengthening by increasing it");
4692 if (from_lower_bound) {
4694 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4697 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4699 ct->mutable_linear()->set_coeffs(
i, arg.coeffs(
i) > 0 ? rhs : -rhs);
4702 if (rhs_offset != 0) {
4704 ct->mutable_linear());
4715void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4718 if (context_->ModelIsUnsat())
return;
4720 const LinearConstraintProto& arg = ct->linear();
4721 const int num_vars = arg.vars_size();
4725 if (num_vars <= 1)
return;
4727 int64_t min_sum = 0;
4728 int64_t max_sum = 0;
4729 int64_t max_coeff_magnitude = 0;
4730 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4731 for (
int i = 0;
i < num_vars; ++
i) {
4732 const int ref = arg.vars(
i);
4733 const int64_t coeff = arg.coeffs(
i);
4735 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4736 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4737 min_sum += coeff * context_->MinOf(ref);
4738 max_sum += coeff * context_->MaxOf(ref);
4740 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4741 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4742 min_sum += coeff * context_->MaxOf(ref);
4743 max_sum += coeff * context_->MinOf(ref);
4746 if (max_coeff_magnitude == 1)
return;
4754 const auto& domain = ct->linear().domain();
4755 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4756 const int64_t lb_threshold = max_sum - domain[1];
4757 if (max_coeff_magnitude + min_coeff_magnitude <
4758 std::max(ub_threshold, lb_threshold)) {
4763 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4764 min_coeff_magnitude < max_coeff_magnitude) {
4765 const int64_t rhs_min = domain[0];
4766 const int64_t rhs_max = domain[1];
4767 if (min_sum >= rhs_min &&
4768 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4769 LowerThanCoeffStrengthening(
true,
4770 min_coeff_magnitude, rhs_max - min_sum, ct);
4773 if (max_sum <= rhs_max &&
4774 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4775 LowerThanCoeffStrengthening(
false,
4776 min_coeff_magnitude, max_sum - rhs_min, ct);
4796 const bool lower_bounded = min_sum < rhs_domain.
Min();
4797 const bool upper_bounded = max_sum > rhs_domain.
Max();
4798 if (!lower_bounded && !upper_bounded)
return;
4799 if (lower_bounded && upper_bounded) {
4804 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold))
return;
4806 context_->UpdateRuleStats(
"linear: split boxed constraint");
4807 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4809 if (!ct->name().empty()) {
4810 new_ct1->set_name(absl::StrCat(ct->name(),
" (part 1)"));
4813 new_ct1->mutable_linear());
4815 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4817 if (!ct->name().empty()) {
4818 new_ct2->set_name(absl::StrCat(ct->name(),
" (part 2)"));
4821 new_ct2->mutable_linear());
4823 context_->UpdateNewConstraintsVariableUsage();
4825 context_->UpdateConstraintVariableUsage(ct_index);
4832 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4840 int64_t second_threshold =
4842 threshold - min_coeff_magnitude);
4850 if (rhs_domain.NumIntervals() > 1) {
4851 second_threshold = threshold;
4859 const bool only_extract_booleans =
4860 !context_->params().presolve_extract_integer_enforcement() ||
4861 context_->ModelIsExpanded();
4866 int64_t rhs_offset = 0;
4867 bool some_integer_encoding_were_extracted =
false;
4868 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4869 for (
int i = 0;
i < arg.vars_size(); ++
i) {
4870 int ref = arg.vars(
i);
4871 int64_t coeff = arg.coeffs(
i);
4879 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4880 if (context_->IsFixed(ref) || coeff < threshold ||
4881 (only_extract_booleans && !is_boolean)) {
4882 mutable_arg->set_vars(new_size, mutable_arg->vars(
i));
4884 int64_t new_magnitude = std::abs(arg.coeffs(
i));
4885 if (coeff > threshold) {
4888 new_magnitude = threshold;
4889 context_->UpdateRuleStats(
"linear: coefficient strenghtening");
4890 }
else if (coeff > second_threshold && coeff < threshold) {
4893 new_magnitude = second_threshold;
4894 context_->UpdateRuleStats(
"linear: advanced coefficient strenghtening");
4896 if (coeff != new_magnitude) {
4897 if (lower_bounded) {
4899 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4902 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4906 mutable_arg->set_coeffs(
4907 new_size, arg.coeffs(
i) > 0 ? new_magnitude : -new_magnitude);
4913 context_->UpdateRuleStats(
"linear: extracted enforcement literal");
4915 some_integer_encoding_were_extracted =
true;
4916 context_->UpdateRuleStats(
4917 "linear: extracted integer enforcement literal");
4919 if (lower_bounded) {
4920 ct->add_enforcement_literal(is_boolean
4922 : context_->GetOrCreateVarValueEncoding(
4923 ref, context_->MinOf(ref)));
4924 rhs_offset -= coeff * context_->MinOf(ref);
4926 ct->add_enforcement_literal(is_boolean
4928 : context_->GetOrCreateVarValueEncoding(
4929 ref, context_->MaxOf(ref)));
4930 rhs_offset -= coeff * context_->MaxOf(ref);
4933 mutable_arg->mutable_vars()->Truncate(new_size);
4934 mutable_arg->mutable_coeffs()->Truncate(new_size);
4936 if (some_integer_encoding_were_extracted || new_size == 1) {
4937 context_->UpdateConstraintVariableUsage(ct_index);
4938 context_->UpdateNewConstraintsVariableUsage();
4942void CpModelPresolver::ExtractAtMostOneFromLinear(
ConstraintProto* ct) {
4943 if (context_->ModelIsUnsat())
return;
4947 const LinearConstraintProto& arg = ct->linear();
4948 const int num_vars = arg.vars_size();
4949 int64_t min_sum = 0;
4950 int64_t max_sum = 0;
4951 for (
int i = 0;
i < num_vars; ++
i) {
4952 const int ref = arg.vars(
i);
4953 const int64_t coeff = arg.coeffs(
i);
4954 const int64_t term_a = coeff * context_->MinOf(ref);
4955 const int64_t term_b = coeff * context_->MaxOf(ref);
4956 min_sum += std::min(term_a, term_b);
4957 max_sum += std::max(term_a, term_b);
4959 for (
const int type : {0, 1}) {
4960 std::vector<int> at_most_one;
4961 for (
int i = 0;
i < num_vars; ++
i) {
4962 const int ref = arg.vars(
i);
4963 const int64_t coeff = arg.coeffs(
i);
4964 if (context_->MinOf(ref) != 0)
continue;
4965 if (context_->MaxOf(ref) != 1)
continue;
4970 if (min_sum + 2 * std::abs(coeff) > rhs.
Max()) {
4971 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
4974 if (max_sum - 2 * std::abs(coeff) < rhs.
Min()) {
4975 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
4979 if (at_most_one.size() > 1) {
4981 context_->UpdateRuleStats(
"linear: extracted at most one (max)");
4983 context_->UpdateRuleStats(
"linear: extracted at most one (min)");
4985 ConstraintProto* new_ct = context_->working_model->add_constraints();
4986 new_ct->set_name(ct->name());
4987 for (
const int ref : at_most_one) {
4988 new_ct->mutable_at_most_one()->add_literals(ref);
4990 context_->UpdateNewConstraintsVariableUsage();
4998 if (ct->linear().vars().empty())
return false;
4999 if (context_->ModelIsUnsat())
return false;
5002 int64_t sum_of_coeffs = 0;
5003 int num_positive = 0;
5004 int num_negative = 0;
5006 const LinearConstraintProto& arg = ct->linear();
5007 const int num_vars = arg.vars_size();
5008 int64_t min_coeff = std::numeric_limits<int64_t>::max();
5009 int64_t max_coeff = 0;
5010 int64_t min_sum = 0;
5011 int64_t max_sum = 0;
5012 for (
int i = 0;
i < num_vars; ++
i) {
5014 const int var = arg.vars(
i);
5015 const int64_t coeff = arg.coeffs(
i);
5018 if (context_->MinOf(var) != 0)
return false;
5019 if (context_->MaxOf(var) != 1)
return false;
5021 sum_of_coeffs += coeff;
5025 min_coeff = std::min(min_coeff, coeff);
5026 max_coeff = std::max(max_coeff, coeff);
5031 min_coeff = std::min(min_coeff, -coeff);
5032 max_coeff = std::max(max_coeff, -coeff);
5035 CHECK_LE(min_coeff, max_coeff);
5044 if ((!rhs_domain.
Contains(min_sum) &&
5045 min_sum + min_coeff > rhs_domain.
Max()) ||
5047 max_sum - min_coeff < rhs_domain.
Min())) {
5048 return MarkConstraintAsFalse(ct,
5049 "linear: all booleans and trivially false");
5051 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
5052 context_->UpdateRuleStats(
"linear: all booleans and trivially true");
5053 return RemoveConstraint(ct);
5063 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
5064 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
5065 rhs_domain.FixedValue() == 0) {
5067 context_->UpdateRuleStats(
"linear: all equivalent!");
5068 for (
int i = 1;
i < num_vars; ++
i) {
5069 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
5070 ct->linear().vars(
i))) {
5074 return RemoveConstraint(ct);
5081 DCHECK(!rhs_domain.IsEmpty());
5082 if (min_sum + min_coeff > rhs_domain.
Max()) {
5084 context_->UpdateRuleStats(
"linear: negative reified and");
5085 const auto copy = arg;
5086 ct->mutable_bool_and()->clear_literals();
5087 for (
int i = 0;
i < num_vars; ++
i) {
5088 ct->mutable_bool_and()->add_literals(
5089 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
5091 PresolveBoolAnd(ct);
5093 }
else if (max_sum - min_coeff < rhs_domain.
Min()) {
5095 context_->UpdateRuleStats(
"linear: positive reified and");
5096 const auto copy = arg;
5097 ct->mutable_bool_and()->clear_literals();
5098 for (
int i = 0;
i < num_vars; ++
i) {
5099 ct->mutable_bool_and()->add_literals(
5100 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
5102 PresolveBoolAnd(ct);
5104 }
else if (min_sum + min_coeff >= rhs_domain.
Min() &&
5105 rhs_domain.front().end >= max_sum) {
5107 context_->UpdateRuleStats(
"linear: positive clause");
5108 const auto copy = arg;
5109 ct->mutable_bool_or()->clear_literals();
5110 for (
int i = 0;
i < num_vars; ++
i) {
5111 ct->mutable_bool_or()->add_literals(
5112 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
5116 }
else if (max_sum - min_coeff <= rhs_domain.
Max() &&
5117 rhs_domain.back().start <= min_sum) {
5119 context_->UpdateRuleStats(
"linear: negative clause");
5120 const auto copy = arg;
5121 ct->mutable_bool_or()->clear_literals();
5122 for (
int i = 0;
i < num_vars; ++
i) {
5123 ct->mutable_bool_or()->add_literals(
5124 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
5129 min_sum + max_coeff <= rhs_domain.
Max() &&
5130 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
5131 rhs_domain.back().start <= min_sum) {
5134 context_->UpdateRuleStats(
"linear: positive at most one");
5135 const auto copy = arg;
5136 ct->mutable_at_most_one()->clear_literals();
5137 for (
int i = 0;
i < num_vars; ++
i) {
5138 ct->mutable_at_most_one()->add_literals(
5139 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
5143 max_sum - max_coeff >= rhs_domain.
Min() &&
5144 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
5145 rhs_domain.front().end >= max_sum) {
5148 context_->UpdateRuleStats(
"linear: negative at most one");
5149 const auto copy = arg;
5150 ct->mutable_at_most_one()->clear_literals();
5151 for (
int i = 0;
i < num_vars; ++
i) {
5152 ct->mutable_at_most_one()->add_literals(
5153 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
5157 min_sum < rhs_domain.
Min() &&
5158 min_sum + min_coeff >= rhs_domain.
Min() &&
5159 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
5160 min_sum + max_coeff <= rhs_domain.
Max()) {
5162 context_->UpdateRuleStats(
"linear: positive equal one");
5163 ConstraintProto* exactly_one = context_->working_model->add_constraints();
5164 exactly_one->set_name(ct->name());
5165 for (
int i = 0;
i < num_vars; ++
i) {
5166 exactly_one->mutable_exactly_one()->add_literals(
5169 context_->UpdateNewConstraintsVariableUsage();
5170 return RemoveConstraint(ct);
5172 max_sum > rhs_domain.
Max() &&
5173 max_sum - min_coeff <= rhs_domain.
Max() &&
5174 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
5175 max_sum - max_coeff >= rhs_domain.
Min()) {
5177 context_->UpdateRuleStats(
"linear: negative equal one");
5178 ConstraintProto* exactly_one = context_->working_model->add_constraints();
5179 exactly_one->set_name(ct->name());
5180 for (
int i = 0;
i < num_vars; ++
i) {
5181 exactly_one->mutable_exactly_one()->add_literals(
5184 context_->UpdateNewConstraintsVariableUsage();
5185 return RemoveConstraint(ct);
5192 if (num_vars > 3)
return false;
5193 context_->UpdateRuleStats(
"linear: small Boolean expression");
5197 const int max_mask = (1 << arg.vars_size());
5198 for (
int mask = 0; mask < max_mask; ++mask) {
5200 for (
int i = 0;
i < num_vars; ++
i) {
5201 if ((mask >>
i) & 1) value += arg.coeffs(
i);
5203 if (rhs_domain.
Contains(value))
continue;
5206 ConstraintProto* new_ct = context_->working_model->add_constraints();
5207 auto* new_arg = new_ct->mutable_bool_or();
5209 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
5211 for (
int i = 0;
i < num_vars; ++
i) {
5212 new_arg->add_literals(((mask >>
i) & 1) ?
NegatedRef(arg.vars(
i))
5217 context_->UpdateNewConstraintsVariableUsage();
5218 return RemoveConstraint(ct);
5222 if (context_->ModelIsUnsat())
return false;
5223 IntervalConstraintProto* interval = ct->mutable_interval();
5226 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
5227 context_->UpdateRuleStats(
"interval: negative size implies unperformed");
5228 return MarkOptionalIntervalAsFalse(ct);
5231 if (ct->enforcement_literal().empty()) {
5232 bool domain_changed =
false;
5234 if (!context_->IntersectDomainWith(
5235 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
5239 if (domain_changed) {
5240 context_->UpdateRuleStats(
5241 "interval: performed intervals must have a positive size");
5247 if (context_->ConstraintVariableGraphIsUpToDate() &&
5248 context_->IntervalUsage(c) == 0) {
5249 context_->UpdateRuleStats(
"intervals: removed unused interval");
5250 return RemoveConstraint(ct);
5253 bool changed =
false;
5254 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
5255 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
5256 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
5264 const int size = ct->inverse().f_direct().size();
5265 bool changed =
false;
5268 for (
const int ref : ct->inverse().f_direct()) {
5269 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
5270 VLOG(1) <<
"Empty domain for a variable in PresolveInverse()";
5274 for (
const int ref : ct->inverse().f_inverse()) {
5275 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
5276 VLOG(1) <<
"Empty domain for a variable in PresolveInverse()";
5286 absl::flat_hash_set<int> direct_vars;
5287 for (
const int ref : ct->inverse().f_direct()) {
5288 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
5290 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
5294 absl::flat_hash_set<int> inverse_vars;
5295 for (
const int ref : ct->inverse().f_inverse()) {
5296 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
5298 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
5306 const auto filter_inverse_domain =
5307 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
5309 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
5310 for (
int i = 0;
i < size; ++
i) {
5311 const Domain domain = context_->DomainOf(inverse[
i]);
5312 for (
const int64_t j : domain.Values()) {
5313 inverse_values[
i].insert(j);
5320 std::vector<int64_t> possible_values;
5321 for (
int i = 0;
i < size; ++
i) {
5322 possible_values.clear();
5323 const Domain domain = context_->DomainOf(direct[
i]);
5324 bool removed_value =
false;
5325 for (
const int64_t j : domain.Values()) {
5326 if (inverse_values[j].contains(
i)) {
5327 possible_values.push_back(j);
5329 removed_value =
true;
5332 if (removed_value) {
5334 if (!context_->IntersectDomainWith(
5336 VLOG(1) <<
"Empty domain for a variable in PresolveInverse()";
5344 if (!filter_inverse_domain(ct->inverse().f_direct(),
5345 ct->inverse().f_inverse())) {
5349 if (!filter_inverse_domain(ct->inverse().f_inverse(),
5350 ct->inverse().f_direct())) {
5355 context_->UpdateRuleStats(
"inverse: reduce domains");
5362 if (context_->ModelIsUnsat())
return false;
5364 if (ct->element().exprs().empty()) {
5365 return MarkConstraintAsFalse(ct,
"element: empty array");
5368 bool changed =
false;
5369 changed |= CanonicalizeLinearExpression(
5370 *ct, ct->mutable_element()->mutable_linear_index());
5371 changed |= CanonicalizeLinearExpression(
5372 *ct, ct->mutable_element()->mutable_linear_target());
5373 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
5374 changed |= CanonicalizeLinearExpression(
5375 *ct, ct->mutable_element()->mutable_exprs(
i));
5378 const LinearExpressionProto& index = ct->element().linear_index();
5379 const LinearExpressionProto& target = ct->element().linear_target();
5386 bool index_modified =
false;
5387 if (!context_->IntersectDomainWith(
5388 index, Domain(0, ct->element().exprs_size() - 1),
5392 if (index_modified) {
5393 context_->UpdateRuleStats(
5394 "element: reduced index domain from array size");
5399 if (context_->IsFixed(index)) {
5400 const int64_t index_value = context_->FixedValue(index);
5401 ConstraintProto* new_ct = context_->working_model->add_constraints();
5402 new_ct->mutable_linear()->add_domain(0);
5403 new_ct->mutable_linear()->add_domain(0);
5406 new_ct->mutable_linear());
5407 bool is_impossible =
false;
5408 context_->CanonicalizeLinearConstraint(new_ct, &is_impossible);
5409 if (is_impossible) {
5410 return context_->NotifyThatModelIsUnsat(
5411 "element: impossible fixed index");
5413 context_->UpdateNewConstraintsVariableUsage();
5414 context_->UpdateRuleStats(
"element: fixed index");
5415 return RemoveConstraint(ct);
5419 const int index_var = index.vars(0);
5423 const Domain& index_var_domain = context_->DomainOf(index_var);
5424 std::vector<int64_t> reached_indices(ct->element().exprs_size(),
false);
5425 for (
const int64_t index_var_value : index_var_domain.Values()) {
5426 const int64_t index_value =
5428 reached_indices[index_value] =
true;
5429 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5430 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5431 const int64_t expr_value =
5433 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5434 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5435 ct->mutable_element()
5436 ->mutable_exprs(index_value)
5437 ->set_offset(expr_value);
5439 context_->UpdateRuleStats(
5440 "element: fix expression depending on the index");
5445 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
5446 if (!reached_indices[
i]) {
5447 ct->mutable_element()->mutable_exprs(
i)->Clear();
5455 if (changed) context_->UpdateConstraintVariableUsage(c);
5459 const Domain& index_var_domain = context_->DomainOf(index_var);
5460 const Domain& target_domain = context_->DomainSuperSetOf(target);
5461 std::vector<int64_t> possible_index_var_values;
5462 for (
const int64_t index_var_value : index_var_domain.Values()) {
5463 const int64_t index_value =
5465 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5467 bool is_possible_index;
5468 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5471 is_possible_index = context_->DomainContains(
5475 context_->IntersectionOfAffineExprsIsNotEmpty(target, expr);
5478 if (is_possible_index) {
5479 possible_index_var_values.push_back(index_var_value);
5481 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5485 if (possible_index_var_values.size() < index_var_domain.Size()) {
5486 if (!context_->IntersectDomainWith(
5490 context_->UpdateRuleStats(
"element: reduced index domain ");
5492 if (context_->IsFixed(index)) {
5493 ConstraintProto*
const eq = context_->working_model->add_constraints();
5494 eq->mutable_linear()->add_domain(0);
5495 eq->mutable_linear()->add_domain(0);
5498 ct->element().exprs(context_->FixedValue(index)), -1,
5499 eq->mutable_linear());
5500 context_->CanonicalizeLinearConstraint(eq);
5501 context_->UpdateNewConstraintsVariableUsage();
5502 context_->UpdateRuleStats(
"element: fixed index");
5503 return RemoveConstraint(ct);
5508 bool all_included_in_target_domain =
true;
5511 Domain infered_domain;
5512 const Domain& index_var_domain = context_->DomainOf(index_var);
5513 const Domain& target_domain = context_->DomainSuperSetOf(target);
5514 for (
const int64_t index_var_value : index_var_domain.Values()) {
5515 const int64_t index_value =
5517 CHECK_GE(index_value, 0);
5518 CHECK_LT(index_value, ct->element().exprs_size());
5519 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5520 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5521 if (!expr_domain.IsIncludedIn(target_domain)) {
5522 all_included_in_target_domain =
false;
5524 infered_domain = infered_domain.UnionWith(expr_domain);
5527 bool domain_modified =
false;
5528 if (!context_->IntersectDomainWith(target, infered_domain,
5529 &domain_modified)) {
5532 if (domain_modified) {
5533 context_->UpdateRuleStats(
"element: reduce target domain");
5537 bool all_constants =
true;
5539 const Domain& index_var_domain = context_->DomainOf(index_var);
5540 std::vector<int64_t> expr_constants;
5542 for (
const int64_t index_var_value : index_var_domain.Values()) {
5543 const int64_t index_value =
5545 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5546 if (context_->IsFixed(expr)) {
5547 expr_constants.push_back(context_->FixedValue(expr));
5549 all_constants =
false;
5556 if (all_constants) {
5557 if (context_->IsFixed(target)) {
5561 context_->UpdateRuleStats(
"element: one value array");
5562 return RemoveConstraint(ct);
5564 int64_t first_index_var_value;
5565 int64_t first_target_var_value;
5566 int64_t d_index = 0;
5567 int64_t d_target = 0;
5569 bool is_affine =
true;
5570 const Domain& index_var_domain = context_->DomainOf(index_var);
5571 for (
const int64_t index_var_value : index_var_domain.Values()) {
5573 const int64_t index_value =
5575 const int64_t expr_value =
5576 context_->FixedValue(ct->element().exprs(index_value));
5578 if (num_terms == 1) {
5579 first_index_var_value = index_var_value;
5580 first_target_var_value = target_var_value;
5581 }
else if (num_terms == 2) {
5582 d_index = index_var_value - first_index_var_value;
5583 d_target = target_var_value - first_target_var_value;
5584 const int64_t gcd = std::gcd(d_index, d_target);
5588 const int64_t offset =
CapSub(
5589 CapProd(d_index,
CapSub(target_var_value, first_target_var_value)),
5590 CapProd(d_target,
CapSub(index_var_value, first_index_var_value)));
5598 const int64_t offset =
CapSub(
CapProd(first_target_var_value, d_index),
5599 CapProd(first_index_var_value, d_target));
5601 ConstraintProto*
const lin = context_->working_model->add_constraints();
5602 lin->mutable_linear()->add_vars(target.vars(0));
5603 lin->mutable_linear()->add_coeffs(d_index);
5604 lin->mutable_linear()->add_vars(index_var);
5605 lin->mutable_linear()->add_coeffs(-d_target);
5606 lin->mutable_linear()->add_domain(offset);
5607 lin->mutable_linear()->add_domain(offset);
5608 context_->CanonicalizeLinearConstraint(lin);
5609 context_->UpdateNewConstraintsVariableUsage();
5610 context_->UpdateRuleStats(
"element: rewrite as affine constraint");
5611 return RemoveConstraint(ct);
5623 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5625 auto count = [&local_var_occurrence_counter](
5626 const LinearExpressionProto& expr)
mutable {
5627 for (
const int var : expr.vars()) {
5628 local_var_occurrence_counter[var]++;
5633 for (
const int64_t index_var_value :
5634 context_->DomainOf(index_var).Values()) {
5640 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5641 local_var_occurrence_counter.at(index_var) == 1 && all_constants) {
5647 context_->UpdateRuleStats(
5648 "element: removed as the index is not used elsewhere");
5649 context_->MarkVariableAsRemoved(index_var);
5650 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5651 return RemoveConstraint(ct);
5656 if (!context_->IsFixed(target) &&
5657 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5658 local_var_occurrence_counter.at(target.vars(0)) == 1 &&
5659 all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5660 context_->UpdateRuleStats(
5661 "element: removed as the target is not used elsewhere");
5662 context_->MarkVariableAsRemoved(target.vars(0));
5663 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5664 return RemoveConstraint(ct);
5671 if (context_->ModelIsUnsat())
return false;
5673 bool changed =
false;
5674 for (
int i = 0;
i < ct->table().exprs_size(); ++
i) {
5675 changed |= CanonicalizeLinearExpression(
5676 *ct, ct->mutable_table()->mutable_exprs(
i));
5679 const int initial_num_exprs = ct->table().exprs_size();
5681 changed |= (ct->table().exprs_size() != initial_num_exprs);
5683 if (ct->table().exprs().empty()) {
5684 context_->UpdateRuleStats(
"table: no expressions");
5685 return RemoveConstraint(ct);
5688 if (ct->table().values().empty()) {
5689 if (ct->table().negated()) {
5690 context_->UpdateRuleStats(
"table: negative table without tuples");
5691 return RemoveConstraint(ct);
5693 return MarkConstraintAsFalse(ct,
"table: positive table without tuples");
5697 int num_fixed_exprs = 0;
5698 for (
const LinearExpressionProto& expr : ct->table().exprs()) {
5699 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5701 if (num_fixed_exprs == ct->table().exprs_size()) {
5702 context_->UpdateRuleStats(
"table: all expressions are fixed");
5703 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5704 if (ct->table().negated() == ct->table().values().empty()) {
5705 context_->UpdateRuleStats(
"table: always true");
5706 return RemoveConstraint(ct);
5708 return MarkConstraintAsFalse(ct,
"table: always false");
5710 return RemoveConstraint(ct);
5713 if (num_fixed_exprs > 0) {
5718 if (ct->table().negated())
return changed;
5724 const int num_exprs = ct->table().exprs_size();
5725 const int num_tuples = ct->table().values_size() / num_exprs;
5726 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5727 for (
int e = 0; e < num_exprs; ++e) {
5728 const LinearExpressionProto& expr = ct->table().exprs(e);
5729 if (context_->IsFixed(expr)) {
5730 new_domains[e].push_back(context_->FixedValue(expr));
5734 for (
int t = 0; t < num_tuples; ++t) {
5735 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5738 DCHECK_EQ(1, expr.vars_size());
5739 DCHECK_EQ(1, expr.coeffs(0));
5740 DCHECK_EQ(0, expr.offset());
5741 const int var = expr.vars(0);
5742 bool domain_modified =
false;
5744 &domain_modified)) {
5747 if (domain_modified) {
5748 context_->UpdateRuleStats(
"table: reduce variable domain");
5752 if (num_exprs == 1) {
5755 context_->UpdateRuleStats(
"table: only one column!");
5756 return RemoveConstraint(ct);
5761 for (
int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5762 if (prod ==
static_cast<double>(num_tuples)) {
5763 context_->UpdateRuleStats(
"table: all tuples!");
5764 return RemoveConstraint(ct);
5770 if (
static_cast<double>(num_tuples) > 0.7 * prod) {
5771 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5772 for (
int t = 0; t < num_tuples; ++t) {
5773 current_tuples[t].resize(num_exprs);
5774 for (
int e = 0; e < num_exprs; ++e) {
5775 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5780 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5781 for (
int e = 0; e < num_exprs; ++e) {
5782 var_to_values[e].assign(new_domains[e].
begin(), new_domains[e].
end());
5784 std::vector<std::vector<int64_t>> all_tuples(prod);
5785 for (
int i = 0;
i < prod; ++
i) {
5786 all_tuples[
i].resize(num_exprs);
5788 for (
int j = 0; j < num_exprs; ++j) {
5789 all_tuples[
i][j] = var_to_values[j][index % var_to_values[j].size()];
5790 index /= var_to_values[j].size();
5796 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5797 std::set_difference(all_tuples.begin(), all_tuples.end(),
5798 current_tuples.begin(), current_tuples.end(),
5802 ct->mutable_table()->set_negated(!ct->table().negated());
5803 ct->mutable_table()->clear_values();
5804 for (
const std::vector<int64_t>& t : diff) {
5805 for (
const int64_t v : t) ct->mutable_table()->add_values(v);
5807 context_->UpdateRuleStats(
"table: negated");
5816class UniqueNonNegativeValue {
5818 void Add(
int value) {
5819 DCHECK_GE(value, 0);
5827 bool HasUniqueValue()
const {
return value_ >= 0; }
5829 int64_t value()
const {
5830 DCHECK(HasUniqueValue());
5838std::string Plural(
int n, std::string_view s) {
5839 return n <= 1 ? absl::StrCat(n,
" ", s)
5846 if (context_->ModelIsUnsat())
return false;
5850 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5852 bool variables_have_changed =
false;
5853 for (LinearExpressionProto& exp :
5854 *(ct->mutable_all_diff()->mutable_exprs())) {
5855 variables_have_changed |= CanonicalizeLinearExpression(*ct, &exp);
5858 const int size = all_diff.exprs_size();
5860 context_->UpdateRuleStats(
"all_diff: empty constraint");
5861 return RemoveConstraint(ct);
5864 context_->UpdateRuleStats(
"all_diff: one expression");
5865 return RemoveConstraint(ct);
5868 absl::flat_hash_set<int64_t> fixed_values;
5870 for (
int i = 0;
i < size; ++
i) {
5871 if (!context_->IsFixed(all_diff.exprs(
i))) {
5872 if (
i != new_size) {
5873 *all_diff.mutable_exprs(new_size) = all_diff.exprs(
i);
5877 const int64_t value = context_->FixedValue(all_diff.exprs(
i));
5878 if (!fixed_values.insert(value).second) {
5879 return context_->NotifyThatModelIsUnsat(
5880 "all_diff: duplicate fixed values");
5885 if (new_size < size) {
5886 all_diff.mutable_exprs()->DeleteSubrange(new_size, size - new_size);
5887 context_->UpdateRuleStats(
"all_diff: remove fixed expressions");
5890 if (!fixed_values.empty()) {
5891 const Domain to_keep =
5894 bool propagated =
false;
5895 for (
int i = 0;
i < all_diff.exprs_size(); ++
i) {
5896 if (!context_->IntersectDomainWith(all_diff.exprs(
i), to_keep,
5902 context_->UpdateRuleStats(
"all_diff: propagate fixed expressions");
5909 absl::btree_map<int, std::vector<std::pair<int64_t, int64_t>>> terms;
5910 std::vector<int64_t> forbidden_values;
5911 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
5912 if (expr.vars_size() != 1)
continue;
5913 terms[expr.vars(0)].push_back(
5914 std::make_pair(expr.coeffs(0), expr.offset()));
5916 for (
auto& [var, terms] : terms) {
5917 if (terms.size() == 1)
continue;
5918 std::sort(terms.begin(), terms.end());
5921 for (
int i = 1;
i < terms.size(); ++
i) {
5922 if (terms[
i] == terms[
i - 1]) {
5923 return context_->NotifyThatModelIsUnsat(
5924 "all_diff: duplicate expressions");
5932 forbidden_values.clear();
5933 for (
int i = 0;
i + 1 < terms.size(); ++
i) {
5934 for (
int j =
i + 1; j < terms.size(); ++j) {
5935 const int64_t coeff = terms[
i].first - terms[j].first;
5936 if (coeff == 0)
continue;
5937 const int64_t offset = terms[j].second - terms[
i].second;
5938 const int64_t value = offset / coeff;
5939 if (value * coeff == offset) {
5940 forbidden_values.push_back(value);
5944 if (!forbidden_values.empty()) {
5946 bool propagated =
false;
5947 if (!context_->IntersectDomainWith(var, to_keep, &propagated)) {
5951 context_->UpdateRuleStats(
5952 "all_diff: propagate expressions with the same variable");
5958 if (all_diff.exprs_size() >= 2 && all_diff.exprs_size() <= 512) {
5959 Domain union_of_domains = context_->DomainSuperSetOf(all_diff.exprs(0));
5960 for (
int i = 1;
i < all_diff.exprs_size(); ++
i) {
5961 union_of_domains = union_of_domains.UnionWith(
5962 context_->DomainSuperSetOf(all_diff.exprs(
i)));
5965 if (union_of_domains.Size() < all_diff.exprs_size()) {
5966 return context_->NotifyThatModelIsUnsat(
5967 "all_diff: more expressions than values");
5970 if (all_diff.exprs_size() == union_of_domains.Size()) {
5971 absl::btree_map<int64_t, UniqueNonNegativeValue> value_to_index;
5972 for (
int i = 0;
i < all_diff.exprs_size(); ++
i) {
5973 const LinearExpressionProto& expr = all_diff.exprs(
i);
5974 DCHECK_EQ(expr.vars_size(), 1);
5975 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5980 bool propagated =
false;
5981 for (
const auto& [value, unique_index] : value_to_index) {
5982 if (!unique_index.HasUniqueValue())
continue;
5984 const LinearExpressionProto& expr =
5985 all_diff.exprs(unique_index.value());
5986 if (!context_->IntersectDomainWith(expr, Domain(value), &propagated)) {
5992 context_->UpdateRuleStats(
5993 "all_diff: propagated mandatory values in permutation");
5998 return variables_have_changed;
6005void AddImplication(
int lhs,
int rhs,
CpModelProto* proto,
6006 absl::flat_hash_map<int, int>* ref_to_bool_and) {
6007 if (ref_to_bool_and->contains(lhs)) {
6008 const int ct_index = (*ref_to_bool_and)[lhs];
6009 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
6010 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
6011 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
6012 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
6015 (*ref_to_bool_and)[lhs] = proto->constraints_size();
6018 ct->mutable_bool_and()->add_literals(rhs);
6022template <
typename ClauseContainer>
6023void ExtractClauses(
bool merge_into_bool_and,
6024 absl::Span<const int> index_mapping,
6026 std::string_view debug_name =
"") {
6033 absl::flat_hash_map<int, int> ref_to_bool_and;
6034 for (
int i = 0;
i < container.NumClauses(); ++
i) {
6035 const std::vector<Literal>& clause = container.Clause(
i);
6036 if (clause.empty())
continue;
6041 if (merge_into_bool_and && clause.size() == 2) {
6042 const int var_a = index_mapping[clause[0].Variable().value()];
6043 const int var_b = index_mapping[clause[1].Variable().value()];
6044 const int ref_a = clause[0].IsPositive() ? var_a :
NegatedRef(var_a);
6045 const int ref_b = clause[1].IsPositive() ? var_b :
NegatedRef(var_b);
6046 AddImplication(
NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
6052 if (!debug_name.empty()) {
6053 ct->set_name(debug_name);
6056 for (
const Literal l : clause) {
6057 const int var = index_mapping[l.Variable().value()];
6058 if (l.IsPositive()) {
6059 ct->mutable_bool_or()->add_literals(var);
6061 ct->mutable_bool_or()->add_literals(
NegatedRef(var));
6070 if (context_->ModelIsUnsat())
return false;
6071 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
6072 bool changed =
false;
6077 const int initial_num_intervals = proto->intervals_size();
6078 for (
int i = 0;
i < initial_num_intervals; ++
i) {
6079 const int interval_index = proto->intervals(
i);
6080 if (context_->ConstraintIsInactive(interval_index))
continue;
6081 proto->set_intervals(new_size++, interval_index);
6084 if (new_size < initial_num_intervals) {
6085 proto->mutable_intervals()->Truncate(new_size);
6086 context_->UpdateRuleStats(
"no_overlap: removed absent intervals");
6090 if (proto->intervals_size() == 1) {
6091 context_->UpdateRuleStats(
"no_overlap: only one interval");
6092 return RemoveConstraint(ct);
6094 if (proto->intervals().empty()) {
6095 context_->UpdateRuleStats(
"no_overlap: no intervals");
6096 return RemoveConstraint(ct);
6106 absl::flat_hash_set<int> visited_intervals;
6107 absl::flat_hash_set<int> duplicate_intervals;
6108 for (
const int interval_index : proto->intervals()) {
6109 if (!visited_intervals.insert(interval_index).second) {
6110 duplicate_intervals.insert(interval_index);
6114 const int initial_num_intervals = proto->intervals_size();
6116 visited_intervals.clear();
6118 for (
int i = 0;
i < initial_num_intervals; ++
i) {
6119 const int interval_index = proto->intervals(
i);
6121 if (duplicate_intervals.contains(interval_index)) {
6123 if (!visited_intervals.insert(interval_index).second)
continue;
6125 ConstraintProto* interval_ct =
6126 context_->working_model->mutable_constraints(interval_index);
6129 if (context_->SizeMin(interval_index) > 0) {
6131 context_->UpdateRuleStats(
6132 "no_overlap: unperform duplicate non zero-sized intervals");
6133 if (!MarkOptionalIntervalAsFalse(interval_ct)) {
6139 return context_->NotifyThatModelIsUnsat(
6140 "no_overlap: duplicate interval with positive size");
6145 if (context_->SizeMax(interval_index) > 0) {
6147 if (!context_->ConstraintIsOptional(interval_index)) {
6148 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
6152 context_->UpdateRuleStats(
6153 "no_overlap: zero the size of performed duplicate intervals");
6157 const int performed_literal = interval_ct->enforcement_literal(0);
6158 ConstraintProto* size_eq_zero =
6159 context_->working_model->add_constraints();
6160 size_eq_zero->add_enforcement_literal(performed_literal);
6161 size_eq_zero->mutable_linear()->add_domain(0);
6162 size_eq_zero->mutable_linear()->add_domain(0);
6164 interval_ct->interval().size(), 1,
6165 size_eq_zero->mutable_linear());
6166 context_->UpdateRuleStats(
6167 "no_overlap: make duplicate intervals as unperformed or zero "
6169 context_->UpdateNewConstraintsVariableUsage();
6174 proto->set_intervals(new_size++, interval_index);
6177 if (new_size < initial_num_intervals) {
6178 proto->mutable_intervals()->Truncate(new_size);
6184 if (proto->intervals_size() > 1) {
6185 std::vector<IndexedInterval> indexed_intervals;
6186 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
6187 const int index = proto->intervals(
i);
6188 indexed_intervals.push_back({index,
6189 IntegerValue(context_->StartMin(index)),
6190 IntegerValue(context_->EndMax(index))});
6192 std::vector<std::vector<int>> components;
6195 if (components.size() > 1) {
6196 for (
const std::vector<int>& intervals : components) {
6197 if (intervals.size() <= 1)
continue;
6199 NoOverlapConstraintProto* new_no_overlap =
6200 context_->working_model->add_constraints()->mutable_no_overlap();
6203 for (
const int i : intervals) {
6204 new_no_overlap->add_intervals(
i);
6207 context_->UpdateNewConstraintsVariableUsage();
6208 context_->UpdateRuleStats(
"no_overlap: split into disjoint components");
6209 return RemoveConstraint(ct);
6213 std::vector<int> constant_intervals;
6214 int64_t size_min_of_non_constant_intervals =
6215 std::numeric_limits<int64_t>::max();
6216 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6217 const int interval_index = proto->intervals(
i);
6218 if (context_->IntervalIsConstant(interval_index)) {
6219 constant_intervals.push_back(interval_index);
6221 size_min_of_non_constant_intervals =
6222 std::min(size_min_of_non_constant_intervals,
6223 context_->SizeMin(interval_index));
6227 bool move_constraint_last =
false;
6228 if (!constant_intervals.empty()) {
6230 std::sort(constant_intervals.begin(), constant_intervals.end(),
6231 [
this](
int i1,
int i2) {
6232 const int64_t s1 = context_->StartMin(i1);
6233 const int64_t e1 = context_->EndMax(i1);
6234 const int64_t s2 = context_->StartMin(i2);
6235 const int64_t e2 = context_->EndMax(i2);
6236 return std::tie(s1, e1) < std::tie(s2, e2);
6242 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
6243 if (context_->EndMax(constant_intervals[
i]) >
6244 context_->StartMin(constant_intervals[
i + 1])) {
6245 context_->UpdateRuleStats(
"no_overlap: constant intervals overlap");
6246 return context_->NotifyThatModelIsUnsat();
6250 if (constant_intervals.size() == proto->intervals_size()) {
6251 context_->UpdateRuleStats(
"no_overlap: no variable intervals");
6252 return RemoveConstraint(ct);
6255 absl::flat_hash_set<int> intervals_to_remove;
6259 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
6260 const int start =
i;
6261 while (
i + 1 < constant_intervals.size() &&
6262 context_->StartMin(constant_intervals[
i + 1]) -
6263 context_->EndMax(constant_intervals[
i]) <
6264 size_min_of_non_constant_intervals) {
6267 if (
i == start)
continue;
6268 for (
int j = start; j <=
i; ++j) {
6269 intervals_to_remove.insert(constant_intervals[j]);
6271 const int64_t new_start = context_->StartMin(constant_intervals[start]);
6272 const int64_t new_end = context_->EndMax(constant_intervals[
i]);
6273 proto->add_intervals(context_->working_model->constraints_size());
6274 IntervalConstraintProto* new_interval =
6275 context_->working_model->add_constraints()->mutable_interval();
6276 new_interval->mutable_start()->set_offset(new_start);
6277 new_interval->mutable_size()->set_offset(new_end - new_start);
6278 new_interval->mutable_end()->set_offset(new_end);
6279 move_constraint_last =
true;
6283 if (!intervals_to_remove.empty()) {
6285 const int old_size = proto->intervals_size();
6286 for (
int i = 0;
i < old_size; ++
i) {
6287 const int interval_index = proto->intervals(
i);
6288 if (intervals_to_remove.contains(interval_index)) {
6291 proto->set_intervals(new_size++, interval_index);
6293 CHECK_LT(new_size, old_size);
6294 proto->mutable_intervals()->Truncate(new_size);
6295 context_->UpdateRuleStats(
6296 "no_overlap: merge constant contiguous intervals");
6297 intervals_to_remove.clear();
6298 constant_intervals.clear();
6300 context_->UpdateNewConstraintsVariableUsage();
6306 int num_size_zero_or_one = 0;
6307 bool has_optional_size_one =
false;
6308 for (
const int index : proto->intervals()) {
6309 const ConstraintProto& interval_ct =
6310 context_->working_model->constraints(index);
6311 const LinearExpressionProto& size = interval_ct.interval().size();
6312 if (size.vars().empty() && size.offset() >= 0 && size.offset() <= 1) {
6313 ++num_size_zero_or_one;
6315 if (size.vars().empty() && size.offset() == 1 &&
6316 !interval_ct.enforcement_literal().empty()) {
6317 has_optional_size_one =
true;
6320 const int initial_num_intervals = proto->intervals().size();
6321 if (num_size_zero_or_one == initial_num_intervals) {
6322 if (has_optional_size_one) {
6326 for (
const int index : proto->intervals()) {
6327 const IntervalConstraintProto& interval =
6328 context_->working_model->constraints(index).interval();
6329 if (interval.size().offset() == 0)
continue;
6330 proto->set_intervals(new_size++, index);
6332 if (new_size < initial_num_intervals) {
6333 proto->mutable_intervals()->Truncate(new_size);
6335 context_->UpdateRuleStats(
"no_overlap: removed size 0 from all diff");
6340 AllDifferentConstraintProto* all_diff =
6341 context_->AddEnforcedConstraint(ct)->mutable_all_diff();
6342 for (
const int index : proto->intervals()) {
6343 const IntervalConstraintProto& interval =
6344 context_->working_model->constraints(index).interval();
6345 if (interval.size().offset() == 0)
continue;
6346 *all_diff->add_exprs() = interval.start();
6348 if (all_diff->exprs_size() < initial_num_intervals) {
6349 context_->UpdateRuleStats(
"no_overlap: removed size 0 from all diff");
6351 context_->UpdateNewConstraintsVariableUsage();
6352 context_->UpdateRuleStats(
"no_overlap: converted to all diff");
6353 return RemoveConstraint(ct);
6358 if (proto->intervals_size() == 1) {
6359 context_->UpdateRuleStats(
"no_overlap: only one interval");
6360 return RemoveConstraint(ct);
6362 if (proto->intervals().empty()) {
6363 context_->UpdateRuleStats(
"no_overlap: no intervals");
6364 return RemoveConstraint(ct);
6370 if (move_constraint_last) {
6372 *context_->working_model->add_constraints() = *ct;
6373 context_->UpdateNewConstraintsVariableUsage();
6374 return RemoveConstraint(ct);
6380bool CpModelPresolver::PresolveNoOverlap2DFramed(
6381 absl::Span<const Rectangle> fixed_boxes,
6382 absl::Span<const RectangleInRange> non_fixed_boxes,
ConstraintProto* ct) {
6383 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6385 DCHECK(!non_fixed_boxes.empty());
6386 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
6387 for (
const RectangleInRange& box : non_fixed_boxes) {
6388 bounding_box.GrowToInclude(box.bounding_area);
6390 std::vector<Rectangle> espace_for_single_box =
6391 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
6394 std::vector<Rectangle> empty;
6397 if (espace_for_single_box.size() != 1) {
6401 Rectangle fixed_boxes_bb = fixed_boxes.front();
6402 for (
const Rectangle& box : fixed_boxes) {
6403 fixed_boxes_bb.GrowToInclude(box);
6405 const Rectangle framed_region = espace_for_single_box.front();
6406 for (
const RectangleInRange& box : non_fixed_boxes) {
6407 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
6411 if (non_fixed_boxes.size() > 1 &&
6412 (2 * box.x_size <= framed_region.SizeX() ||
6413 2 * box.y_size <= framed_region.SizeY())) {
6418 const int x_interval_index = proto.x_intervals(box.box_index);
6419 const int y_interval_index = proto.y_intervals(box.box_index);
6420 if (!context_->working_model->constraints(x_interval_index)
6421 .enforcement_literal()
6423 !context_->working_model->constraints(y_interval_index)
6424 .enforcement_literal()
6426 if (context_->working_model->constraints(x_interval_index)
6427 .enforcement_literal(0) !=
6428 context_->working_model->constraints(y_interval_index)
6429 .enforcement_literal(0)) {
6439 std::vector<int> enforcement_literals_for_amo;
6440 bool has_mandatory =
false;
6441 for (
const RectangleInRange& box : non_fixed_boxes) {
6442 const int box_index = box.box_index;
6443 const int x_interval_index = proto.x_intervals(box_index);
6444 const int y_interval_index = proto.y_intervals(box_index);
6445 const ConstraintProto& x_interval_ct =
6446 context_->working_model->constraints(x_interval_index);
6447 const ConstraintProto& y_interval_ct =
6448 context_->working_model->constraints(y_interval_index);
6449 if (x_interval_ct.enforcement_literal().empty() &&
6450 y_interval_ct.enforcement_literal().empty()) {
6452 if (has_mandatory) {
6453 return context_->NotifyThatModelIsUnsat(
6454 "Two mandatory boxes in the same space");
6456 has_mandatory =
true;
6457 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
6458 Domain(framed_region.x_min.value(),
6459 framed_region.x_max.value()))) {
6462 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
6463 Domain(framed_region.x_min.value(),
6464 framed_region.x_max.value()))) {
6467 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
6468 Domain(framed_region.y_min.value(),
6469 framed_region.y_max.value()))) {
6472 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
6473 Domain(framed_region.y_min.value(),
6474 framed_region.y_max.value()))) {
6478 auto add_linear_constraint = [&](
const ConstraintProto& interval_ct,
6479 int enforcement_literal,
6480 IntegerValue min, IntegerValue max) {
6483 context_->AddImplyInDomain(enforcement_literal,
6484 interval_ct.interval().start(),
6485 Domain(min.value(), max.value()));
6486 context_->AddImplyInDomain(enforcement_literal,
6487 interval_ct.interval().end(),
6488 Domain(min.value(), max.value()));
6490 const int enforcement_literal =
6491 x_interval_ct.enforcement_literal().empty()
6492 ? y_interval_ct.enforcement_literal(0)
6493 : x_interval_ct.enforcement_literal(0);
6494 enforcement_literals_for_amo.push_back(enforcement_literal);
6495 add_linear_constraint(x_interval_ct, enforcement_literal,
6496 framed_region.x_min, framed_region.x_max);
6497 add_linear_constraint(y_interval_ct, enforcement_literal,
6498 framed_region.y_min, framed_region.y_max);
6501 if (has_mandatory) {
6502 for (
const int lit : enforcement_literals_for_amo) {
6503 if (!context_->SetLiteralToFalse(lit)) {
6507 }
else if (enforcement_literals_for_amo.size() > 1) {
6508 context_->working_model->add_constraints()
6509 ->mutable_at_most_one()
6510 ->mutable_literals()
6511 ->Add(enforcement_literals_for_amo.begin(),
6512 enforcement_literals_for_amo.end());
6514 context_->UpdateRuleStats(
"no_overlap_2d: at most one rectangle in region");
6515 context_->UpdateNewConstraintsVariableUsage();
6516 return RemoveConstraint(ct);
6519bool CpModelPresolver::ExpandEncoded2DBinPacking(
6520 absl::Span<const Rectangle> fixed_boxes,
6521 absl::Span<const RectangleInRange> non_fixed_boxes,
ConstraintProto* ct) {
6522 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6524 non_fixed_boxes, fixed_boxes,
6526 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6527 if (disjoint_packing_presolve_result.bins.empty())
return false;
6529 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6530 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6531 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6534 for (
int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6535 const int b = non_fixed_boxes[idx].box_index;
6536 const ConstraintProto& x_interval_ct =
6537 context_->working_model->constraints(proto.x_intervals(
b));
6538 const ConstraintProto& y_interval_ct =
6539 context_->working_model->constraints(proto.y_intervals(
b));
6540 if (x_interval_ct.enforcement_literal().empty() &&
6541 y_interval_ct.enforcement_literal().empty()) {
6545 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6546 ? y_interval_ct.enforcement_literal(0)
6547 : x_interval_ct.enforcement_literal(0);
6548 int potentially_other_enforcement_literal =
6549 y_interval_ct.enforcement_literal().empty()
6550 ? x_interval_ct.enforcement_literal(0)
6551 : y_interval_ct.enforcement_literal(0);
6553 if (enforcement_literal == potentially_other_enforcement_literal) {
6555 box_to_presence_literal[idx].push_back(
NegatedRef(enforcement_literal));
6557 const int interval_is_absent_literal =
6558 context_->NewBoolVarWithConjunction(
6559 {enforcement_literal, potentially_other_enforcement_literal});
6561 BoolArgumentProto* bool_or =
6562 context_->working_model->add_constraints()->mutable_bool_or();
6563 bool_or->add_literals(
NegatedRef(interval_is_absent_literal));
6564 for (
const int lit :
6565 {enforcement_literal, potentially_other_enforcement_literal}) {
6566 context_->AddImplication(
NegatedRef(interval_is_absent_literal), lit);
6569 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6573 for (
int bin_index = 0;
6574 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6575 const Disjoint2dPackingResult::Bin& bin =
6576 disjoint_packing_presolve_result.bins[bin_index];
6577 NoOverlap2DConstraintProto new_no_overlap_2d;
6578 for (
const Rectangle& ret : bin.fixed_boxes) {
6579 new_no_overlap_2d.add_x_intervals(
6580 context_->working_model->constraints_size());
6581 new_no_overlap_2d.add_y_intervals(
6582 context_->working_model->constraints_size() + 1);
6583 IntervalConstraintProto* new_interval =
6584 context_->working_model->add_constraints()->mutable_interval();
6585 new_interval->mutable_start()->set_offset(ret.x_min.value());
6586 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6587 new_interval->mutable_end()->set_offset(ret.x_max.value());
6590 context_->working_model->add_constraints()->mutable_interval();
6591 new_interval->mutable_start()->set_offset(ret.y_min.value());
6592 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6593 new_interval->mutable_end()->set_offset(ret.y_max.value());
6595 for (
const int idx : bin.non_fixed_box_indexes) {
6596 int presence_in_box_lit = context_->NewBoolVar(
"binpacking");
6597 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6598 const int b = non_fixed_boxes[idx].box_index;
6599 box_in_area_lits.push_back({.box_index =
b,
6600 .area_index = bin_index,
6601 .literal = presence_in_box_lit});
6602 const ConstraintProto& x_interval_ct =
6603 context_->working_model->constraints(proto.x_intervals(
b));
6604 const ConstraintProto& y_interval_ct =
6605 context_->working_model->constraints(proto.y_intervals(
b));
6606 ConstraintProto* new_interval_x =
6607 context_->working_model->add_constraints();
6608 *new_interval_x = x_interval_ct;
6609 new_interval_x->clear_enforcement_literal();
6610 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6611 ConstraintProto* new_interval_y =
6612 context_->working_model->add_constraints();
6613 *new_interval_y = y_interval_ct;
6614 new_interval_y->clear_enforcement_literal();
6615 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6616 new_no_overlap_2d.add_x_intervals(
6617 context_->working_model->constraints_size() - 2);
6618 new_no_overlap_2d.add_y_intervals(
6619 context_->working_model->constraints_size() - 1);
6621 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6622 &new_no_overlap_2d);
6626 for (
int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6627 const std::vector<int>& presence_literals =
6628 box_to_presence_literal[box_index];
6629 if (presence_literals.empty()) {
6630 return context_->NotifyThatModelIsUnsat(
6631 "A mandatory box cannot be placed in any position");
6634 context_->working_model->add_constraints()->mutable_exactly_one();
6635 for (
const int presence_literal : presence_literals) {
6636 exactly_one->add_literals(presence_literal);
6639 CompactVectorVector<int, Rectangle> areas;
6640 for (
int bin_index = 0;
6641 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6642 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6644 solution_crush_.AssignVariableToPackingArea(
6645 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6647 context_->UpdateNewConstraintsVariableUsage();
6648 context_->UpdateRuleStats(
6649 "no_overlap_2d: fixed boxes partition available space, converted "
6650 "to optional regions");
6651 return RemoveConstraint(ct);
6654bool CpModelPresolver::PresolveNoOverlap2D(
int ,
ConstraintProto* ct) {
6655 if (context_->ModelIsUnsat()) {
6659 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6660 bool truncated =
false;
6664 const int initial_num_boxes = proto.x_intervals_size();
6666 for (
int i = 0;
i < proto.x_intervals_size(); ++
i) {
6667 const int x_interval_index = proto.x_intervals(
i);
6668 const int y_interval_index = proto.y_intervals(
i);
6670 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6671 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6676 for (
const int interval_index : {x_interval_index, y_interval_index}) {
6677 if (context_->StartMin(interval_index) >
6678 context_->EndMax(interval_index)) {
6679 const ConstraintProto& interval_ct =
6680 context_->working_model->constraints(interval_index);
6681 if (interval_ct.enforcement_literal_size() == 1) {
6682 const int literal = interval_ct.enforcement_literal(0);
6683 if (!context_->SetLiteralToFalse(literal)) {
6687 return context_->NotifyThatModelIsUnsat(
6688 "no_overlap_2d: impossible interval");
6692 if (context_->SizeMin(interval_index) < 0) {
6693 const ConstraintProto& interval_ct =
6694 context_->working_model->constraints(interval_index);
6695 if (interval_ct.enforcement_literal().empty()) {
6696 bool domain_changed =
false;
6698 if (!context_->IntersectDomainWith(
6699 interval_ct.interval().size(),
6700 Domain(0, std::numeric_limits<int64_t>::max()),
6708 if (context_->ConstraintIsInactive(x_interval_index) ||
6709 context_->ConstraintIsInactive(y_interval_index)) {
6716 if (new_size < initial_num_boxes) {
6718 context_->UpdateRuleStats(
"no_overlap_2d: removed inactive boxes");
6719 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6720 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6723 if (new_size == 0) {
6724 context_->UpdateRuleStats(
"no_overlap_2d: no boxes");
6725 return RemoveConstraint(ct);
6728 if (new_size == 1) {
6729 context_->UpdateRuleStats(
"no_overlap_2d: only one box");
6730 return RemoveConstraint(ct);
6736 bool x_constant =
true;
6737 bool y_constant =
true;
6738 bool has_zero_sized_interval =
false;
6739 bool has_potential_zero_sized_interval =
false;
6741 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6742 std::vector<RectangleInRange> non_fixed_boxes;
6743 absl::flat_hash_set<int> fixed_item_indexes;
6744 for (
int i = 0;
i < proto.x_intervals_size(); ++
i) {
6745 const int x_interval_index = proto.x_intervals(
i);
6746 const int y_interval_index = proto.y_intervals(
i);
6748 bounding_boxes.push_back(
6749 {IntegerValue(context_->StartMin(x_interval_index)),
6750 IntegerValue(context_->EndMax(x_interval_index)),
6751 IntegerValue(context_->StartMin(y_interval_index)),
6752 IntegerValue(context_->EndMax(y_interval_index))});
6753 if (context_->IntervalIsConstant(x_interval_index) &&
6754 context_->IntervalIsConstant(y_interval_index) &&
6755 context_->SizeMax(x_interval_index) > 0 &&
6756 context_->SizeMax(y_interval_index) > 0) {
6757 fixed_boxes.push_back(bounding_boxes.back());
6758 fixed_item_indexes.insert(
i);
6760 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6761 non_fixed_boxes.push_back(
6763 .bounding_area = bounding_boxes.back(),
6764 .x_size = std::max(int64_t{0}, context_->SizeMin(x_interval_index)),
6766 std::max(int64_t{0}, context_->SizeMin(y_interval_index))});
6769 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6772 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6775 if (context_->SizeMax(x_interval_index) <= 0 ||
6776 context_->SizeMax(y_interval_index) <= 0) {
6777 has_zero_sized_interval =
true;
6779 if (context_->SizeMin(x_interval_index) <= 0 ||
6780 context_->SizeMin(y_interval_index) <= 0) {
6781 has_potential_zero_sized_interval =
true;
6785 const CompactVectorVector<int> components =
6787 if (components.size() > 1) {
6788 for (
int i = 0;
i < components.size(); ++
i) {
6789 absl::Span<const int> boxes = components[
i];
6790 if (boxes.size() <= 1)
continue;
6792 NoOverlap2DConstraintProto* new_no_overlap_2d =
6793 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6794 for (
const int b : boxes) {
6795 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6796 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6799 context_->UpdateNewConstraintsVariableUsage();
6800 context_->UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
6801 return RemoveConstraint(ct);
6805 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6806 context_->UpdateRuleStats(
6807 "no_overlap_2d: a dimension is constant, splitting into many "
6809 std::vector<IndexedInterval> indexed_intervals;
6810 for (
int i = 0;
i < proto.x_intervals_size(); ++
i) {
6811 int x = proto.x_intervals(
i);
6812 int y = proto.y_intervals(
i);
6813 if (x_constant) std::swap(x, y);
6814 indexed_intervals.push_back({
x, IntegerValue(context_->StartMin(y)),
6815 IntegerValue(context_->EndMax(y))});
6817 CompactVectorVector<int> no_overlaps;
6818 absl::c_stable_sort(indexed_intervals,
6819 IndexedInterval::ComparatorByStart());
6821 for (
int i = 0;
i < no_overlaps.size(); ++
i) {
6822 ConstraintProto* new_ct = context_->working_model->add_constraints();
6825 for (
const int i : no_overlaps[
i]) {
6826 new_ct->mutable_no_overlap()->add_intervals(
i);
6829 context_->UpdateNewConstraintsVariableUsage();
6830 return RemoveConstraint(ct);
6836 return context_->NotifyThatModelIsUnsat(
6837 "Two fixed boxes in no_overlap_2d overlap");
6840 if (non_fixed_bounding_boxes.empty()) {
6841 context_->UpdateRuleStats(
"no_overlap_2d: all boxes are fixed");
6842 return RemoveConstraint(ct);
6848 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6849 const bool presolved =
6852 NoOverlap2DConstraintProto new_no_overlap_2d;
6855 const int old_size = proto.x_intervals_size();
6856 for (
int i = 0;
i < old_size; ++
i) {
6857 if (fixed_item_indexes.contains(
i)) {
6860 new_no_overlap_2d.add_x_intervals(proto.x_intervals(
i));
6861 new_no_overlap_2d.add_y_intervals(proto.y_intervals(
i));
6863 for (
const Rectangle& fixed_box : fixed_boxes) {
6864 const int item_x_interval =
6865 context_->working_model->constraints().size();
6866 IntervalConstraintProto* new_interval =
6867 context_->working_model->add_constraints()->mutable_interval();
6868 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6869 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6870 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6872 const int item_y_interval =
6873 context_->working_model->constraints().size();
6875 context_->working_model->add_constraints()->mutable_interval();
6876 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6877 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6878 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6880 new_no_overlap_2d.add_x_intervals(item_x_interval);
6881 new_no_overlap_2d.add_y_intervals(item_y_interval);
6883 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6884 &new_no_overlap_2d);
6885 context_->UpdateNewConstraintsVariableUsage();
6886 context_->UpdateRuleStats(
"no_overlap_2d: presolved fixed rectangles");
6887 return RemoveConstraint(ct);
6891 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6892 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6893 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6902 const CompactVectorVector<int> non_fixed_components =
6904 if (non_fixed_components.size() > 1) {
6905 for (
int i = 0;
i < non_fixed_components.size(); ++
i) {
6908 absl::Span<const int> indexes = non_fixed_components[
i];
6910 NoOverlap2DConstraintProto* new_no_overlap_2d =
6911 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6912 for (
const int idx : indexes) {
6913 const int b = non_fixed_boxes[idx].box_index;
6914 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6915 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6917 for (
const int b : fixed_item_indexes) {
6918 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6919 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6922 context_->UpdateNewConstraintsVariableUsage();
6923 context_->UpdateRuleStats(
6924 "no_overlap_2d: split into disjoint components duplicating fixed "
6926 return RemoveConstraint(ct);
6929 if (!has_potential_zero_sized_interval) {
6930 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6934 RunPropagatorsForConstraint(*ct);
6946void CpModelPresolver::DetectDuplicateIntervals(
6947 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6948 interval_representative_.clear();
6949 bool changed =
false;
6950 const int size = intervals->size();
6951 for (
int i = 0;
i < size; ++
i) {
6952 const int index = (*intervals)[
i];
6953 const auto [it, inserted] = interval_representative_.insert({index, index});
6954 if (it->second != index) {
6956 intervals->Set(
i, it->second);
6957 context_->UpdateRuleStats(
6958 "intervals: change duplicate index inside constraint");
6961 if (changed) context_->UpdateConstraintVariableUsage(c);
6965 if (context_->ModelIsUnsat())
return false;
6969 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6971 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6972 for (LinearExpressionProto& exp :
6973 *(ct->mutable_cumulative()->mutable_demands())) {
6974 changed |= CanonicalizeLinearExpression(*ct, &exp);
6977 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6981 bool domain_changed =
false;
6982 if (!context_->IntersectDomainWith(
6983 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6986 if (domain_changed) {
6987 context_->UpdateRuleStats(
"cumulative: trimmed negative capacity");
6993 std::vector<int> intervals = {proto->intervals().begin(),
6994 proto->intervals().end()};
6996 if (intervals.size() < proto->intervals_size()) {
6997 absl::btree_map<int, std::vector<LinearExpressionProto>>
6999 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
7000 interval_to_sizes[proto->intervals(
i)].push_back(proto->demands(
i));
7003 absl::btree_map<int, int64_t> terms;
7004 proto->clear_intervals();
7005 proto->clear_demands();
7006 for (
const auto& [interval, demands] : interval_to_sizes) {
7009 for (
const LinearExpressionProto& demand : demands) {
7010 for (
int i = 0;
i < demand.vars_size(); ++
i) {
7011 terms[demand.vars(
i)] += demand.coeffs(
i);
7013 offset += demand.offset();
7015 if (terms.size() <= 1) {
7016 proto->add_intervals(interval);
7017 LinearExpressionProto* demand = proto->add_demands();
7018 for (
const auto& [var, coeff] : terms) {
7019 demand->add_vars(var);
7020 demand->add_coeffs(coeff);
7022 demand->set_offset(offset);
7023 context_->UpdateRuleStats(
7024 "cumulative: merged demands of identical interval");
7026 LinearConstraintProto* sum_of_terms =
7027 context_->working_model->add_constraints()->mutable_linear();
7028 std::vector<int> vars;
7029 vars.reserve(terms.size());
7030 std::vector<int64_t> coeffs;
7031 coeffs.reserve(terms.size());
7032 Domain new_domain(0);
7033 for (
const auto& [var, coeff] : terms) {
7034 vars.push_back(var);
7035 coeffs.push_back(coeff);
7036 new_domain = new_domain.AdditionWith(
7037 context_->DomainOf(var).ContinuousMultiplicationBy(coeff));
7038 sum_of_terms->add_vars(var);
7039 sum_of_terms->add_coeffs(coeff);
7041 const int variable_demand = context_->NewIntVar(new_domain);
7042 context_->solution_crush().SetVarToLinearExpression(variable_demand,
7044 sum_of_terms->add_vars(variable_demand);
7045 sum_of_terms->add_coeffs(-1);
7047 context_->UpdateNewConstraintsVariableUsage();
7049 proto->add_intervals(interval);
7050 LinearExpressionProto* demand = proto->add_demands();
7051 demand->add_vars(variable_demand);
7052 demand->add_coeffs(1);
7053 demand->set_offset(offset);
7056 context_->UpdateRuleStats(
7057 "cumulative: merged variable demands of identical interval");
7067 int num_zero_demand_removed = 0;
7068 int num_zero_size_removed = 0;
7069 int num_incompatible_intervals = 0;
7070 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
7071 if (context_->ConstraintIsInactive(proto->intervals(
i)))
continue;
7073 const LinearExpressionProto& demand_expr = proto->demands(
i);
7074 const int64_t demand_max = context_->MaxOf(demand_expr);
7075 if (demand_max == 0) {
7076 num_zero_demand_removed++;
7080 const int interval_index = proto->intervals(
i);
7081 if (context_->SizeMax(interval_index) <= 0) {
7083 num_zero_size_removed++;
7088 const int64_t start_min = context_->StartMin(interval_index);
7089 const int64_t end_max = context_->EndMax(interval_index);
7090 if (start_min > end_max) {
7091 if (context_->ConstraintIsOptional(interval_index)) {
7092 ConstraintProto* interval_ct =
7093 context_->working_model->mutable_constraints(interval_index);
7094 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
7095 const int literal = interval_ct->enforcement_literal(0);
7096 if (!context_->SetLiteralToFalse(literal)) {
7099 num_incompatible_intervals++;
7102 return context_->NotifyThatModelIsUnsat(
7103 "cumulative: inconsistent intervals cannot be performed");
7107 if (context_->MinOf(demand_expr) > capacity_max) {
7108 if (context_->ConstraintIsOptional(interval_index)) {
7109 if (context_->SizeMin(interval_index) > 0) {
7110 ConstraintProto* interval_ct =
7111 context_->working_model->mutable_constraints(interval_index);
7112 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
7113 const int literal = interval_ct->enforcement_literal(0);
7114 if (!context_->SetLiteralToFalse(literal)) {
7117 num_incompatible_intervals++;
7122 const ConstraintProto& interval_ct =
7123 context_->working_model->constraints(interval_index);
7124 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
7128 context_->UpdateRuleStats(
7129 "cumulative: zero size of performed demand that exceeds "
7131 ++num_zero_demand_removed;
7136 proto->set_intervals(new_size, interval_index);
7137 *proto->mutable_demands(new_size) = proto->demands(
i);
7141 if (new_size < proto->intervals_size()) {
7143 proto->mutable_intervals()->Truncate(new_size);
7144 proto->mutable_demands()->erase(
7145 proto->mutable_demands()->begin() + new_size,
7146 proto->mutable_demands()->end());
7149 if (num_zero_demand_removed > 0) {
7150 context_->UpdateRuleStats(
7151 "cumulative: removed intervals with no demands");
7153 if (num_zero_size_removed > 0) {
7154 context_->UpdateRuleStats(
7155 "cumulative: removed intervals with a size of zero");
7157 if (num_incompatible_intervals > 0) {
7158 context_->UpdateRuleStats(
7159 "cumulative: removed intervals that can't be performed");
7165 for (
int i = 0;
i < proto->demands_size(); ++
i) {
7166 const int interval = proto->intervals(
i);
7167 const LinearExpressionProto& demand_expr = proto->demands(
i);
7168 if (context_->ConstraintIsOptional(interval))
continue;
7169 if (context_->SizeMin(interval) <= 0)
continue;
7170 bool domain_changed =
false;
7171 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
7175 if (domain_changed) {
7176 context_->UpdateRuleStats(
7177 "cumulative: fit demand in [0..capacity_max]");
7189 if (proto->intervals_size() > 1) {
7190 std::vector<IndexedInterval> indexed_intervals;
7191 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
7192 const int index = proto->intervals(
i);
7193 indexed_intervals.push_back({
i, IntegerValue(context_->StartMin(index)),
7194 IntegerValue(context_->EndMax(index))});
7196 std::vector<std::vector<int>> components;
7199 if (components.size() > 1) {
7200 for (
const std::vector<int>& component : components) {
7201 CumulativeConstraintProto* new_cumulative =
7202 context_->working_model->add_constraints()->mutable_cumulative();
7203 for (
const int i : component) {
7204 new_cumulative->add_intervals(proto->intervals(
i));
7205 *new_cumulative->add_demands() = proto->demands(
i);
7207 *new_cumulative->mutable_capacity() = proto->capacity();
7209 context_->UpdateNewConstraintsVariableUsage();
7210 context_->UpdateRuleStats(
"cumulative: split into disjoint components");
7211 return RemoveConstraint(ct);
7219 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
7220 const int64_t capacity_min = context_->MinOf(proto->capacity());
7221 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
7222 const int interval_index = proto->intervals(
i);
7223 const int64_t demand_max = context_->MaxOf(proto->demands(
i));
7224 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
7225 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
7234 int num_possible_overloads = 0;
7235 int64_t current_load = 0;
7236 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
7237 for (
const auto& it : time_to_demand_deltas) {
7238 num_possible_overloads_before[it.first] = num_possible_overloads;
7239 current_load += it.second;
7240 if (current_load > capacity_min) {
7241 ++num_possible_overloads;
7244 CHECK_EQ(current_load, 0);
7247 if (num_possible_overloads == 0) {
7248 context_->UpdateRuleStats(
7249 "cumulative: max profile is always under the min capacity");
7250 return RemoveConstraint(ct);
7260 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
7261 const int index = proto->intervals(
i);
7262 const int64_t start_min = context_->StartMin(index);
7263 const int64_t end_max = context_->EndMax(index);
7272 if (start_min >= end_max)
continue;
7278 const int num_diff = num_possible_overloads_before.at(end_max) -
7279 num_possible_overloads_before.at(start_min);
7280 if (num_diff == 0)
continue;
7282 proto->set_intervals(new_size, proto->intervals(
i));
7283 *proto->mutable_demands(new_size) = proto->demands(
i);
7287 if (new_size < proto->intervals_size()) {
7289 proto->mutable_intervals()->Truncate(new_size);
7290 proto->mutable_demands()->erase(
7291 proto->mutable_demands()->begin() + new_size,
7292 proto->mutable_demands()->end());
7293 context_->UpdateRuleStats(
7294 "cumulative: remove never conflicting intervals");
7298 if (proto->intervals().empty()) {
7299 context_->UpdateRuleStats(
"cumulative: no intervals");
7300 return RemoveConstraint(ct);
7304 int64_t max_of_performed_demand_mins = 0;
7305 int64_t sum_of_max_demands = 0;
7306 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
7307 const int interval_index = proto->intervals(
i);
7308 const ConstraintProto& interval_ct =
7309 context_->working_model->constraints(interval_index);
7311 const LinearExpressionProto& demand_expr = proto->demands(
i);
7312 sum_of_max_demands += context_->MaxOf(demand_expr);
7314 if (interval_ct.enforcement_literal().empty() &&
7315 context_->SizeMin(interval_index) > 0) {
7316 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
7317 context_->MinOf(demand_expr));
7321 const LinearExpressionProto& capacity_expr = proto->capacity();
7322 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
7323 context_->UpdateRuleStats(
"cumulative: propagate min capacity");
7324 if (!context_->IntersectDomainWith(
7325 capacity_expr, Domain(max_of_performed_demand_mins,
7326 std::numeric_limits<int64_t>::max()))) {
7331 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
7332 context_->UpdateRuleStats(
"cumulative: cannot fit performed demands");
7333 return context_->NotifyThatModelIsUnsat();
7336 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
7337 context_->UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
7338 return RemoveConstraint(ct);
7342 if (context_->IsFixed(proto->capacity())) {
7344 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
7345 const LinearExpressionProto& demand_expr = ct->cumulative().demands(
i);
7346 if (!context_->IsFixed(demand_expr)) {
7351 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
7352 if (gcd == 1)
break;
7356 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
7357 const int64_t demand = context_->MinOf(ct->cumulative().demands(
i));
7358 *proto->mutable_demands(
i) = ConstantExpressionProto(demand / gcd);
7361 const int64_t old_capacity = context_->MinOf(proto->capacity());
7362 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
7363 context_->UpdateRuleStats(
7364 "cumulative: divide demands and capacity by gcd");
7368 const int num_intervals = proto->intervals_size();
7369 const LinearExpressionProto& capacity_expr = proto->capacity();
7371 std::vector<LinearExpressionProto> start_exprs(num_intervals);
7373 int num_duration_one = 0;
7374 int num_greater_half_capacity = 0;
7376 bool has_optional_interval =
false;
7377 for (
int i = 0;
i < num_intervals; ++
i) {
7378 const int index = proto->intervals(
i);
7380 if (context_->ConstraintIsOptional(index)) has_optional_interval =
true;
7381 const ConstraintProto& ct =
7382 context_->working_model->constraints(proto->intervals(
i));
7383 const IntervalConstraintProto& interval = ct.interval();
7384 start_exprs[
i] = interval.start();
7386 const LinearExpressionProto& demand_expr = proto->demands(
i);
7387 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
7390 if (context_->SizeMin(index) <= 0) {
7396 const int64_t demand_min = context_->MinOf(demand_expr);
7397 const int64_t demand_max = context_->MaxOf(demand_expr);
7398 if (demand_min > capacity_max / 2) {
7399 num_greater_half_capacity++;
7401 if (demand_min > capacity_max) {
7402 context_->UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
7403 if (!context_->ConstraintIsOptional(index)) {
7404 return context_->NotifyThatModelIsUnsat();
7406 CHECK_EQ(ct.enforcement_literal().size(), 1);
7407 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
7412 }
else if (demand_max > capacity_max) {
7413 if (ct.enforcement_literal().empty()) {
7414 context_->UpdateRuleStats(
7415 "cumulative: demand_max exceeds capacity max");
7416 if (!context_->IntersectDomainWith(
7418 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
7424 context_->UpdateRuleStats(
7425 "cumulative: demand_max of optional interval exceeds capacity");
7430 if (num_greater_half_capacity == num_intervals) {
7431 if (num_duration_one == num_intervals && !has_optional_interval) {
7432 context_->UpdateRuleStats(
"cumulative: convert to all_different");
7433 ConstraintProto* new_ct = context_->working_model->add_constraints();
7434 auto* arg = new_ct->mutable_all_diff();
7435 for (
const LinearExpressionProto& expr : start_exprs) {
7436 *arg->add_exprs() = expr;
7438 if (!context_->IsFixed(capacity_expr)) {
7439 const int64_t capacity_min = context_->MinOf(capacity_expr);
7440 for (
const LinearExpressionProto& expr : proto->demands()) {
7441 if (capacity_min >= context_->MaxOf(expr))
continue;
7442 LinearConstraintProto* fit =
7443 context_->working_model->add_constraints()->mutable_linear();
7445 fit->add_domain(std::numeric_limits<int64_t>::max());
7450 context_->UpdateNewConstraintsVariableUsage();
7451 return RemoveConstraint(ct);
7453 context_->UpdateRuleStats(
"cumulative: convert to no_overlap");
7456 for (
int i = 0;
i < proto->demands_size(); ++
i) {
7457 const LinearExpressionProto& demand_expr = proto->demands(
i);
7458 const int64_t demand_max = context_->MaxOf(demand_expr);
7459 if (demand_max > context_->MinOf(capacity_expr)) {
7460 ConstraintProto* capacity_gt =
7461 context_->working_model->add_constraints();
7462 *capacity_gt->mutable_enforcement_literal() =
7463 context_->working_model->constraints(proto->intervals(
i))
7464 .enforcement_literal();
7465 capacity_gt->mutable_linear()->add_domain(0);
7466 capacity_gt->mutable_linear()->add_domain(
7467 std::numeric_limits<int64_t>::max());
7469 capacity_gt->mutable_linear());
7471 capacity_gt->mutable_linear());
7475 ConstraintProto* new_ct = context_->working_model->add_constraints();
7476 auto* arg = new_ct->mutable_no_overlap();
7477 for (
const int interval : proto->intervals()) {
7478 arg->add_intervals(interval);
7480 context_->UpdateNewConstraintsVariableUsage();
7481 return RemoveConstraint(ct);
7485 RunPropagatorsForConstraint(*ct);
7490 if (context_->ModelIsUnsat())
return false;
7493 RoutesConstraintProto& proto = *ct->mutable_routes();
7495 const int old_size = proto.literals_size();
7497 std::vector<bool> has_incoming_or_outgoing_arcs;
7498 const int num_arcs = proto.literals_size();
7499 for (
int i = 0;
i < num_arcs; ++
i) {
7500 const int ref = proto.literals(
i);
7501 const int tail = proto.tails(
i);
7502 const int head = proto.heads(
i);
7504 if (tail >= has_incoming_or_outgoing_arcs.size()) {
7505 has_incoming_or_outgoing_arcs.resize(tail + 1,
false);
7507 if (head >= has_incoming_or_outgoing_arcs.size()) {
7508 has_incoming_or_outgoing_arcs.resize(head + 1,
false);
7511 if (context_->LiteralIsFalse(ref)) {
7512 context_->UpdateRuleStats(
"routes: removed false arcs");
7515 proto.set_literals(new_size, ref);
7516 proto.set_tails(new_size, tail);
7517 proto.set_heads(new_size, head);
7519 has_incoming_or_outgoing_arcs[tail] =
true;
7520 has_incoming_or_outgoing_arcs[head] =
true;
7523 if (old_size > 0 && new_size == 0) {
7527 return context_->NotifyThatModelIsUnsat(
7528 "routes: graph with nodes and no arcs");
7533 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
7534 if (!has_incoming_or_outgoing_arcs[n]) {
7535 return context_->NotifyThatModelIsUnsat(absl::StrCat(
7536 "routes: node ", n,
" misses incoming or outgoing arcs"));
7540 if (new_size < num_arcs) {
7541 proto.mutable_literals()->Truncate(new_size);
7542 proto.mutable_tails()->Truncate(new_size);
7543 proto.mutable_heads()->Truncate(new_size);
7547 RunPropagatorsForConstraint(*ct);
7552 if (context_->ModelIsUnsat())
return false;
7555 CircuitConstraintProto& proto = *ct->mutable_circuit();
7558 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
7559 ct->mutable_circuit()->mutable_heads());
7563 std::vector<std::vector<int>> incoming_arcs;
7564 std::vector<std::vector<int>> outgoing_arcs;
7566 const int num_arcs = proto.literals_size();
7567 for (
int i = 0;
i < num_arcs; ++
i) {
7568 const int ref = proto.literals(
i);
7569 const int tail = proto.tails(
i);
7570 const int head = proto.heads(
i);
7571 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
7572 if (std::max(tail, head) >= incoming_arcs.size()) {
7573 incoming_arcs.resize(std::max(tail, head) + 1);
7574 outgoing_arcs.resize(std::max(tail, head) + 1);
7576 incoming_arcs[head].push_back(ref);
7577 outgoing_arcs[tail].push_back(ref);
7581 for (
int i = 0;
i < num_nodes; ++
i) {
7582 if (incoming_arcs[
i].empty() || outgoing_arcs[
i].empty()) {
7583 return MarkConstraintAsFalse(ct,
"circuit: node with no arcs");
7592 bool loop_again =
true;
7593 int num_fixed_at_true = 0;
7594 while (loop_again) {
7596 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7597 for (
const std::vector<int>& refs : *node_to_refs) {
7598 if (refs.size() == 1) {
7599 if (!context_->LiteralIsTrue(refs.front())) {
7600 ++num_fixed_at_true;
7601 if (!context_->SetLiteralToTrue(refs.front()))
return true;
7609 for (
const int ref : refs) {
7610 if (context_->LiteralIsTrue(ref)) {
7617 return context_->NotifyThatModelIsUnsat();
7619 if (num_true == 1) {
7620 for (
const int ref : refs) {
7621 if (ref != true_ref) {
7622 if (!context_->IsFixed(ref)) {
7623 context_->UpdateRuleStats(
"circuit: set literal to false");
7626 if (!context_->SetLiteralToFalse(ref))
return true;
7633 if (num_fixed_at_true > 0) {
7634 context_->UpdateRuleStats(
"circuit: fixed singleton arcs");
7640 int circuit_start = -1;
7641 std::vector<int> next(num_nodes, -1);
7642 std::vector<int> new_in_degree(num_nodes, 0);
7643 std::vector<int> new_out_degree(num_nodes, 0);
7644 for (
int i = 0;
i < num_arcs; ++
i) {
7645 const int ref = proto.literals(
i);
7646 if (context_->LiteralIsFalse(ref))
continue;
7647 if (context_->LiteralIsTrue(ref)) {
7648 if (next[proto.tails(
i)] != -1) {
7649 return context_->NotifyThatModelIsUnsat();
7651 next[proto.tails(
i)] = proto.heads(
i);
7652 if (proto.tails(
i) != proto.heads(
i)) {
7653 circuit_start = proto.tails(
i);
7657 ++new_out_degree[proto.tails(
i)];
7658 ++new_in_degree[proto.heads(
i)];
7659 proto.set_tails(new_size, proto.tails(
i));
7660 proto.set_heads(new_size, proto.heads(
i));
7661 proto.set_literals(new_size, ref);
7671 for (
int i = 0;
i < num_nodes; ++
i) {
7672 if (new_in_degree[
i] == 0 || new_out_degree[
i] == 0) {
7673 return context_->NotifyThatModelIsUnsat();
7678 if (circuit_start != -1) {
7679 std::vector<bool> visited(num_nodes,
false);
7680 int current = circuit_start;
7681 while (current != -1 && !visited[current]) {
7682 visited[current] =
true;
7683 current = next[current];
7685 if (current == circuit_start) {
7688 std::vector<bool> has_self_arc(num_nodes,
false);
7689 for (
int i = 0;
i < num_arcs; ++
i) {
7690 if (visited[proto.tails(
i)])
continue;
7691 if (proto.tails(
i) == proto.heads(
i)) {
7692 has_self_arc[proto.tails(
i)] =
true;
7693 if (!context_->SetLiteralToTrue(proto.literals(
i)))
return true;
7695 if (!context_->SetLiteralToFalse(proto.literals(
i)))
return true;
7698 for (
int n = 0; n < num_nodes; ++n) {
7699 if (!visited[n] && !has_self_arc[n]) {
7701 return MarkConstraintAsFalse(
7702 ct,
"circuit: non-covering fixed subcircuit");
7705 context_->UpdateRuleStats(
"circuit: fully specified");
7706 return RemoveConstraint(ct);
7710 if (num_true == new_size) {
7711 context_->UpdateRuleStats(
"circuit: empty circuit");
7712 return RemoveConstraint(ct);
7718 for (
int i = 0;
i < num_nodes; ++
i) {
7719 for (
const std::vector<int>* arc_literals :
7720 {&incoming_arcs[
i], &outgoing_arcs[
i]}) {
7721 std::vector<int> literals;
7722 for (
const int ref : *arc_literals) {
7723 if (context_->LiteralIsFalse(ref))
continue;
7724 if (context_->LiteralIsTrue(ref)) {
7728 literals.push_back(ref);
7730 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
7731 context_->UpdateRuleStats(
"circuit: degree 2");
7732 if (!context_->StoreBooleanEqualityRelation(literals[0],
7741 if (new_size < num_arcs) {
7742 proto.mutable_tails()->Truncate(new_size);
7743 proto.mutable_heads()->Truncate(new_size);
7744 proto.mutable_literals()->Truncate(new_size);
7745 context_->UpdateRuleStats(
"circuit: removed false arcs");
7748 RunPropagatorsForConstraint(*ct);
7753 if (context_->ModelIsUnsat())
return false;
7757 AutomatonConstraintProto* proto = ct->mutable_automaton();
7758 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7762 bool changed =
false;
7763 for (
int i = 0;
i < proto->exprs_size(); ++
i) {
7764 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(
i));
7767 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7768 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7772 for (
int time = 0; time < reachable_labels.size(); ++time) {
7773 const LinearExpressionProto& expr = proto->exprs(time);
7774 if (context_->IsFixed(expr)) {
7775 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7776 return MarkConstraintAsFalse(ct,
"automaton: unsat");
7779 std::vector<int64_t> unscaled_reachable_labels;
7780 for (
const int64_t label : reachable_labels[time]) {
7783 bool removed_values =
false;
7784 if (!context_->IntersectDomainWith(
7789 if (removed_values) {
7790 context_->UpdateRuleStats(
"automaton: reduce variable domain");
7799 if (context_->ModelIsUnsat())
return false;
7803 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7804 bool changed =
false;
7805 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7806 changed |= CanonicalizeLinearExpression(*ct, &exp);
7808 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7809 changed |= CanonicalizeLinearExpression(*ct, &exp);
7812 if (proto.active_literals().empty()) {
7813 const int true_literal = context_->GetTrueLiteral();
7814 for (
int i = 0;
i < proto.time_exprs_size(); ++
i) {
7815 proto.add_active_literals(true_literal);
7820 const auto& demand_is_null = [&](
int i) {
7821 return (context_->IsFixed(proto.level_changes(
i)) &&
7822 context_->FixedValue(proto.level_changes(
i)) == 0) ||
7823 context_->LiteralIsFalse(proto.active_literals(
i));
7828 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7829 if (demand_is_null(
i)) num_zeros++;
7832 if (num_zeros > 0) {
7835 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7836 if (demand_is_null(
i))
continue;
7837 *proto.mutable_level_changes(new_size) = proto.level_changes(
i);
7838 *proto.mutable_time_exprs(new_size) = proto.time_exprs(
i);
7839 proto.set_active_literals(new_size, proto.active_literals(
i));
7843 proto.mutable_level_changes()->erase(
7844 proto.mutable_level_changes()->begin() + new_size,
7845 proto.mutable_level_changes()->end());
7846 proto.mutable_time_exprs()->erase(
7847 proto.mutable_time_exprs()->begin() + new_size,
7848 proto.mutable_time_exprs()->end());
7849 proto.mutable_active_literals()->Truncate(new_size);
7851 context_->UpdateRuleStats(
7852 "reservoir: remove zero level_changes or inactive events");
7856 for (
const LinearExpressionProto& level_change : proto.level_changes()) {
7857 if (!context_->IsFixed(level_change))
return changed;
7860 const int num_events = proto.level_changes_size();
7861 int64_t gcd = proto.level_changes().empty()
7863 : std::abs(context_->FixedValue(proto.level_changes(0)));
7864 int num_positives = 0;
7865 int num_negatives = 0;
7866 int64_t max_sum_of_positive_level_changes = 0;
7867 int64_t min_sum_of_negative_level_changes = 0;
7868 for (
int i = 0;
i < num_events; ++
i) {
7869 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7870 gcd = std::gcd(gcd, std::abs(demand));
7873 max_sum_of_positive_level_changes += demand;
7875 DCHECK_LT(demand, 0);
7877 min_sum_of_negative_level_changes += demand;
7881 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7882 max_sum_of_positive_level_changes <= proto.max_level()) {
7883 context_->UpdateRuleStats(
"reservoir: always feasible");
7884 return RemoveConstraint(ct);
7887 if (min_sum_of_negative_level_changes > proto.max_level() ||
7888 max_sum_of_positive_level_changes < proto.min_level()) {
7889 context_->UpdateRuleStats(
"reservoir: trivially infeasible");
7890 return context_->NotifyThatModelIsUnsat();
7893 if (min_sum_of_negative_level_changes > proto.min_level()) {
7894 proto.set_min_level(min_sum_of_negative_level_changes);
7895 context_->UpdateRuleStats(
7896 "reservoir: increase min_level to reachable value");
7899 if (max_sum_of_positive_level_changes < proto.max_level()) {
7900 proto.set_max_level(max_sum_of_positive_level_changes);
7901 context_->UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
7904 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7905 (num_positives == 0 || num_negatives == 0)) {
7908 auto*
const sum_ct = context_->working_model->add_constraints();
7909 auto*
const sum = sum_ct->mutable_linear();
7910 int64_t fixed_contrib = 0;
7911 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7912 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7913 DCHECK_NE(demand, 0);
7915 const int active = proto.active_literals(
i);
7917 sum->add_vars(active);
7918 sum->add_coeffs(demand);
7921 sum->add_coeffs(-demand);
7922 fixed_contrib += demand;
7925 sum->add_domain(proto.min_level() - fixed_contrib);
7926 sum->add_domain(proto.max_level() - fixed_contrib);
7927 context_->UpdateRuleStats(
"reservoir: converted to linear");
7928 bool changed =
false;
7929 if (!CanonicalizeLinear(sum_ct, &changed)) {
7932 return RemoveConstraint(ct);
7936 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7937 proto.mutable_level_changes(
i)->set_offset(
7938 context_->FixedValue(proto.level_changes(
i)) / gcd);
7939 proto.mutable_level_changes(
i)->clear_vars();
7940 proto.mutable_level_changes(
i)->clear_coeffs();
7946 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7947 .InverseMultiplicationBy(gcd);
7948 proto.set_min_level(reduced_domain.
Min());
7949 proto.set_max_level(reduced_domain.
Max());
7950 context_->UpdateRuleStats(
7951 "reservoir: simplify level_changes and levels by gcd");
7954 if (num_positives == 1 && num_negatives > 0) {
7955 context_->UpdateRuleStats(
7956 "TODO reservoir: one producer, multiple consumers");
7959 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7960 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7961 const LinearExpressionProto& time = proto.time_exprs(
i);
7962 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7964 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7965 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7967 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7968 proto.active_literals(
i));
7969 if (time_active_set.contains(key)) {
7970 context_->UpdateRuleStats(
"TODO reservoir: merge synchronized events");
7973 time_active_set.insert(key);
7977 RunPropagatorsForConstraint(*ct);
7984void CpModelPresolver::ConvertToBoolAnd() {
7985 absl::flat_hash_map<int, int> ref_to_bool_and;
7986 const int num_constraints = context_->working_model->constraints_size();
7987 std::vector<int> to_remove;
7988 for (
int c = 0;
c < num_constraints; ++
c) {
7989 const ConstraintProto& ct = context_->working_model->constraints(c);
7993 ct.bool_or().literals().size() == 2) {
7994 AddImplication(
NegatedRef(ct.bool_or().literals(0)),
7995 ct.bool_or().literals(1), context_->working_model,
7997 to_remove.push_back(c);
8002 ct.at_most_one().literals().size() == 2) {
8003 AddImplication(ct.at_most_one().literals(0),
8005 context_->working_model, &ref_to_bool_and);
8006 to_remove.push_back(c);
8011 context_->UpdateNewConstraintsVariableUsage();
8012 for (
const int c : to_remove) {
8013 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8014 CHECK(RemoveConstraint(ct));
8015 context_->UpdateConstraintVariableUsage(c);
8019void CpModelPresolver::RunPropagatorsForConstraint(
const ConstraintProto& ct) {
8020 if (context_->ModelIsUnsat())
return;
8027 SatParameters local_params;
8028 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(
true);
8029 local_params.set_exploit_all_precedences(
true);
8030 local_params.set_use_hard_precedences_in_cumulative(
true);
8031 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
8032 local_params.set_use_overload_checker_in_cumulative(
true);
8033 local_params.set_use_strong_propagation_in_disjunctive(
true);
8034 local_params.set_use_timetable_edge_finding_in_cumulative(
true);
8035 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
8036 local_params.set_use_timetabling_in_no_overlap_2d(
true);
8037 local_params.set_use_energetic_reasoning_in_no_overlap_2d(
true);
8038 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(
true);
8039 local_params.set_use_conservative_scale_overload_checker(
true);
8040 local_params.set_use_dual_scheduling_heuristics(
true);
8042 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8043 std::vector<int> variable_mapping;
8048 &model,
"single constraint")) {
8052 time_limit_->AdvanceDeterministicTime(
8053 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8054 auto* mapping = model.GetOrCreate<CpModelMapping>();
8055 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
8056 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
8057 auto* trail = model.GetOrCreate<Trail>();
8059 int num_changed_bounds = 0;
8060 int num_fixed_bools = 0;
8061 for (
int var = 0; var < variable_mapping.size(); ++var) {
8062 const int proto_var = variable_mapping[var];
8063 if (mapping->IsBoolean(var)) {
8064 const Literal l = mapping->Literal(var);
8065 if (trail->Assignment().LiteralIsFalse(l)) {
8066 if (!context_->SetLiteralToFalse(proto_var))
return;
8069 }
else if (trail->Assignment().LiteralIsTrue(l)) {
8070 if (!context_->SetLiteralToTrue(proto_var))
return;
8075 const Literal r = implication_graph->RepresentativeOf(l);
8078 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
8079 if (r_var < 0)
continue;
8080 if (!context_->StoreBooleanEqualityRelation(
8081 proto_var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
8087 bool changed =
false;
8088 if (!context_->IntersectDomainWith(
8090 integer_trail->InitialVariableDomain(mapping->Integer(var)),
8094 if (changed) ++num_changed_bounds;
8097 if (num_changed_bounds > 0) {
8098 context_->UpdateRuleStats(
"propagators: changed bounds",
8099 num_changed_bounds);
8101 if (num_fixed_bools > 0) {
8102 context_->UpdateRuleStats(
"propagators: fixed booleans", num_fixed_bools);
8108void CpModelPresolver::Probe() {
8109 auto probing_timer =
8110 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
8119 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
8120 auto* sat_solver = model.GetOrCreate<SatSolver>();
8121 auto* mapping = model.GetOrCreate<CpModelMapping>();
8122 auto* prober = model.GetOrCreate<Prober>();
8135 const auto& assignment = sat_solver->Assignment();
8136 prober->SetPropagationCallback([&](Literal decision) {
8137 if (probing_timer->WorkLimitIsReached())
return;
8138 const int decision_var =
8139 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
8140 if (decision_var < 0)
return;
8141 probing_timer->TrackSimpleLoop(
8142 context_->VarToConstraints(decision_var).size());
8143 std::vector<int> to_update;
8144 for (
const int c : context_->VarToConstraints(decision_var)) {
8145 if (c < 0)
continue;
8146 const ConstraintProto& ct = context_->working_model->constraints(c);
8147 if (ct.enforcement_literal().size() > 2) {
8161 bool decision_is_positive =
false;
8162 bool has_false_literal =
false;
8163 bool simplification_possible =
false;
8164 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
8165 for (
const int ref : ct.enforcement_literal()) {
8166 const Literal lit = mapping->Literal(ref);
8169 decision_is_positive = assignment.LiteralIsTrue(lit);
8170 if (!decision_is_positive)
break;
8173 if (assignment.LiteralIsFalse(lit)) {
8175 has_false_literal =
true;
8176 }
else if (assignment.LiteralIsTrue(lit)) {
8178 simplification_possible =
true;
8181 if (!decision_is_positive)
continue;
8183 if (has_false_literal) {
8185 auto* mutable_ct = context_->working_model->mutable_constraints(c);
8186 mutable_ct->Clear();
8187 mutable_ct->add_enforcement_literal(decision_ref);
8188 mutable_ct->mutable_bool_and()->add_literals(
NegatedRef(false_ref));
8189 context_->UpdateRuleStats(
8190 "probing: reduced enforced constraint to implication");
8191 to_update.push_back(c);
8195 if (simplification_possible) {
8197 auto* mutable_enforcements =
8198 context_->working_model->mutable_constraints(c)
8199 ->mutable_enforcement_literal();
8200 for (
const int ref : ct.enforcement_literal()) {
8202 assignment.LiteralIsTrue(mapping->Literal(ref))) {
8205 mutable_enforcements->Set(new_size++, ref);
8207 mutable_enforcements->Truncate(new_size);
8208 context_->UpdateRuleStats(
"probing: simplified enforcement list");
8209 to_update.push_back(c);
8215 if (ct.bool_or().literals().size() <= 2)
continue;
8219 bool decision_is_negative =
false;
8220 bool has_true_literal =
false;
8221 bool simplification_possible =
false;
8222 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
8223 for (
const int ref : ct.bool_or().literals()) {
8224 const Literal lit = mapping->Literal(ref);
8227 decision_is_negative = assignment.LiteralIsFalse(lit);
8228 if (!decision_is_negative)
break;
8231 if (assignment.LiteralIsTrue(lit)) {
8233 has_true_literal =
true;
8234 }
else if (assignment.LiteralIsFalse(lit)) {
8236 simplification_possible =
true;
8239 if (!decision_is_negative)
continue;
8241 if (has_true_literal) {
8244 auto* mutable_bool_or =
8245 context_->working_model->mutable_constraints(c)->mutable_bool_or();
8246 mutable_bool_or->mutable_literals()->Clear();
8247 mutable_bool_or->add_literals(decision_ref);
8248 mutable_bool_or->add_literals(true_ref);
8249 context_->UpdateRuleStats(
"probing: bool_or reduced to implication");
8250 to_update.push_back(c);
8254 if (simplification_possible) {
8256 auto* mutable_bool_or =
8257 context_->working_model->mutable_constraints(c)->mutable_bool_or();
8258 for (
const int ref : ct.bool_or().literals()) {
8260 assignment.LiteralIsFalse(mapping->Literal(ref))) {
8263 mutable_bool_or->set_literals(new_size++, ref);
8265 mutable_bool_or->mutable_literals()->Truncate(new_size);
8266 context_->UpdateRuleStats(
"probing: simplified clauses");
8267 to_update.push_back(c);
8271 absl::c_sort(to_update);
8272 for (
const int c : to_update) {
8273 context_->UpdateConstraintVariableUsage(c);
8277 prober->ProbeBooleanVariables(
8278 context_->params().probing_deterministic_time_limit());
8280 for (
const auto& [expr, ub] : model.GetOrCreate<RootLevelLinear2Bounds>()
8281 ->GetSortedNonTrivialUpperBounds()) {
8288 const int proto_var0 = mapping->GetProtoVariableFromIntegerVariable(var0);
8289 const int proto_var1 = mapping->GetProtoVariableFromIntegerVariable(var1);
8290 if (proto_var0 < 0 || proto_var1 < 0)
continue;
8292 ? expr.coeffs[0].value()
8293 : -expr.coeffs[0].value();
8295 ? expr.coeffs[1].value()
8296 : -expr.coeffs[1].value();
8298 GetLinearExpression2FromProto(proto_var0, coeff0, proto_var1, coeff1),
8302 probing_timer->AddCounter(
"probed", prober->num_decisions());
8303 probing_timer->AddToWork(
8304 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8305 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
8306 return (
void)context_->NotifyThatModelIsUnsat(
"during probing");
8309 time_limit_->ResetHistory();
8313 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
8314 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
8315 const Literal l = sat_solver->LiteralTrail()[
i];
8316 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
8318 const int ref = l.IsPositive() ? var :
NegatedRef(var);
8319 if (context_->IsFixed(ref))
continue;
8321 if (!context_->SetLiteralToTrue(ref))
return;
8324 probing_timer->AddCounter(
"fixed_bools", num_fixed);
8327 int num_changed_bounds = 0;
8328 const int num_variables = context_->working_model->variables().size();
8329 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
8330 for (
int var = 0; var < num_variables; ++var) {
8333 if (!mapping->IsBoolean(var)) {
8334 bool changed =
false;
8335 if (!context_->IntersectDomainWith(
8336 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
8340 if (changed) ++num_changed_bounds;
8345 const Literal l = mapping->Literal(var);
8346 const Literal r = implication_graph->RepresentativeOf(l);
8350 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
8352 if (!context_->StoreBooleanEqualityRelation(
8353 var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
8358 probing_timer->AddCounter(
"new_bounds", num_changed_bounds);
8359 probing_timer->AddCounter(
"equiv", num_equiv);
8360 probing_timer->AddCounter(
"new_binary_clauses",
8361 prober->num_new_binary_clauses());
8366 DetectDuplicateConstraintsWithDifferentEnforcements(
8367 mapping, implication_graph, model.GetOrCreate<Trail>());
8370 probing_timer.reset();
8373 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
8374 PresolveTimer timer(
"MaxClique", logger_, time_limit_);
8375 std::vector<std::vector<Literal>> cliques;
8376 std::vector<int> clique_ct_index;
8380 int64_t num_literals_before = 0;
8381 const int num_constraints = context_->working_model->constraints_size();
8382 for (
int c = 0;
c < num_constraints; ++
c) {
8383 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8385 std::vector<Literal> clique;
8386 for (
const int ref : ct->at_most_one().literals()) {
8387 clique.push_back(mapping->Literal(ref));
8389 num_literals_before += clique.size();
8390 cliques.push_back(clique);
8392 context_->UpdateConstraintVariableUsage(c);
8394 if (ct->enforcement_literal().size() != 1)
continue;
8395 const Literal enforcement =
8396 mapping->Literal(ct->enforcement_literal(0));
8397 for (
const int ref : ct->bool_and().literals()) {
8398 if (ref == ct->enforcement_literal(0))
continue;
8399 num_literals_before += 2;
8400 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
8403 context_->UpdateConstraintVariableUsage(c);
8406 const int64_t num_old_cliques = cliques.size();
8411 double limit = context_->params().merge_at_most_one_work_limit();
8412 if (num_literals_before > 1e6) {
8413 limit *= num_literals_before / 1e6;
8417 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
8419 timer.AddToWork(dtime);
8424 int num_new_cliques = 0;
8425 int64_t num_literals_after = 0;
8426 for (
const std::vector<Literal>& clique : cliques) {
8427 if (clique.empty())
continue;
8429 num_literals_after += clique.size();
8430 ConstraintProto* ct = context_->working_model->add_constraints();
8431 for (
const Literal literal : clique) {
8433 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
8434 if (var < 0)
continue;
8435 if (literal.IsPositive()) {
8436 ct->mutable_at_most_one()->add_literals(var);
8438 ct->mutable_at_most_one()->add_literals(
NegatedRef(var));
8443 PresolveAtMostOne(ct);
8445 context_->UpdateNewConstraintsVariableUsage();
8446 if (num_new_cliques != num_old_cliques) {
8447 context_->UpdateRuleStats(
"at_most_one: transformed into max clique");
8450 if (num_old_cliques != num_new_cliques ||
8451 num_literals_before != num_literals_after) {
8453 absl::StrCat(
"Merged ", Plural(num_old_cliques,
"constraint"),
8454 " with ", Plural(num_literals_before,
"literal"),
8455 " into ", Plural(num_new_cliques,
"constraint"),
8456 " with ", Plural(num_literals_after,
"literal")));
8464 absl::Span<const int> var_mapping,
8466 const int num_vars = assignment.NumberOfVariables();
8467 for (
int i = 0;
i < num_vars; ++
i) {
8468 const Literal lit(BooleanVariable(
i),
true);
8469 const int ref = var_mapping[
i];
8470 if (assignment.LiteralIsTrue(lit)) {
8471 if (!context->SetLiteralToTrue(ref))
return false;
8472 }
else if (assignment.LiteralIsFalse(lit)) {
8473 if (!context->SetLiteralToFalse(ref))
return false;
8483bool CpModelPresolver::PresolvePureSatPart() {
8486 if (context_->ModelIsUnsat())
return true;
8487 if (context_->params().keep_all_feasible_solutions_in_presolve())
return true;
8490 int num_variables = 0;
8491 int num_ignored_variables = 0;
8492 const int total_num_vars = context_->working_model->variables().size();
8493 std::vector<int> new_index(total_num_vars, -1);
8494 std::vector<int> new_to_old_index;
8495 for (
int i = 0;
i < total_num_vars; ++
i) {
8496 if (!context_->CanBeUsedAsLiteral(
i)) {
8497 ++num_ignored_variables;
8502 if (context_->VarToConstraints(
i).empty())
continue;
8504 new_to_old_index.push_back(
i);
8505 new_index[
i] = num_variables++;
8506 DCHECK_EQ(num_variables, new_to_old_index.size());
8510 auto convert = [&new_index](
int ref) {
8512 DCHECK_NE(index, -1);
8528 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8529 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
8530 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8531 sat_solver->SetNumVariables(num_variables);
8536 for (
const int var : new_to_old_index) {
8537 if (context_->IsFixed(var)) {
8538 if (context_->LiteralIsTrue(var)) {
8539 if (!sat_solver->AddUnitClause({convert(var)}))
return false;
8541 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
8548 std::vector<Literal> clause;
8549 int num_removed_constraints = 0;
8550 int num_ignored_constraints = 0;
8551 const bool load_amo = context_->params().load_at_most_ones_in_sat_presolve();
8552 for (
int i = 0;
i < context_->working_model->constraints_size(); ++
i) {
8553 const ConstraintProto& ct = context_->working_model->constraints(
i);
8556 ++num_removed_constraints;
8558 for (
const int ref : ct.bool_or().literals()) {
8559 clause.push_back(convert(ref));
8561 for (
const int ref : ct.enforcement_literal()) {
8562 clause.push_back(convert(ref).Negated());
8564 sat_solver->AddProblemClause(clause);
8566 context_->working_model->mutable_constraints(
i)->Clear();
8567 context_->UpdateConstraintVariableUsage(
i);
8573 ct.enforcement_literal().empty() &&
8574 !ct.at_most_one().literals().empty()) {
8576 for (
const int ref : ct.at_most_one().literals()) {
8577 clause.push_back(convert(ref));
8579 if (!graph->AddAtMostOne(clause))
return false;
8581 ++num_removed_constraints;
8582 context_->working_model->mutable_constraints(
i)->Clear();
8583 context_->UpdateConstraintVariableUsage(
i);
8588 ct.enforcement_literal().empty()) {
8590 for (
const int ref : ct.exactly_one().literals()) {
8591 clause.push_back(convert(ref));
8595 if (!graph->AddAtMostOne(clause))
return false;
8596 sat_solver->AddProblemClause(clause);
8598 ++num_removed_constraints;
8599 context_->working_model->mutable_constraints(
i)->Clear();
8600 context_->UpdateConstraintVariableUsage(
i);
8607 const int left_size = ct.enforcement_literal().size();
8608 const int right_size = ct.bool_and().literals().size();
8609 if (left_size > 1 && right_size > 1 &&
8610 (left_size + 1) * right_size > 10'000) {
8611 ++num_ignored_constraints;
8615 ++num_removed_constraints;
8616 std::vector<Literal> clause;
8617 for (
const int ref : ct.enforcement_literal()) {
8618 clause.push_back(convert(ref).Negated());
8621 for (
const int ref : ct.bool_and().literals()) {
8622 clause.back() = convert(ref);
8623 sat_solver->AddProblemClause(clause);
8626 context_->working_model->mutable_constraints(
i)->Clear();
8627 context_->UpdateConstraintVariableUsage(
i);
8635 ++num_ignored_constraints;
8637 if (sat_solver->ModelIsUnsat())
return false;
8640 if (num_removed_constraints == 0)
return true;
8650 int num_in_extra_constraints = 0;
8651 std::vector<bool> can_be_removed(num_variables,
false);
8652 for (
int i = 0;
i < num_variables; ++
i) {
8653 const int var = new_to_old_index[
i];
8654 if (context_->VarToConstraints(var).empty()) {
8655 can_be_removed[
i] =
true;
8659 ++num_in_extra_constraints;
8672 SatParameters sat_params = context_->params();
8673 if (sat_params.debug_postsolve_with_full_solver() ||
8674 sat_params.fill_tightened_domains_in_response()) {
8675 sat_params.set_presolve_blocked_clause(
false);
8679 if (!sat_params.debug_postsolve_with_full_solver()) {
8680 sat_params.set_filter_sat_postsolve_clauses(
true);
8683 SatPostsolver sat_postsolver(num_variables);
8693 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8694 if (!context_->params().debug_postsolve_with_full_solver() &&
8695 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8696 num_in_extra_constraints == 0) {
8702 &local_model, logger_)) {
8705 if (sat_solver->LiteralTrail().Index() == num_variables) {
8707 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8712 SatPresolveOptions options;
8713 options.log_info =
true;
8714 options.extract_binary_clauses_in_probing =
false;
8715 options.use_transitive_reduction =
false;
8716 options.deterministic_time_limit =
8717 context_->params().presolve_probing_deterministic_time_limit();
8718 options.use_equivalence_sat_sweeping =
8719 context_->params().inprocessing_use_sat_sweeping();
8721 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8722 inprocessing->ProvideLogger(logger_);
8723 if (!inprocessing->PresolveLoop(options))
return false;
8724 for (
const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8725 sat_postsolver.Add(c[0], c);
8733 if (sat_solver->ModelIsUnsat())
return false;
8738 sat_params.set_presolve_use_bva(
false);
8746 if (context_->params().keep_symmetry_in_presolve()) {
8747 sat_params.set_presolve_use_bva(
false);
8751 if (!sat_solver->ResetToLevelZero())
return false;
8752 time_limit_->AdvanceDeterministicTime(
8753 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8757 graph->CleanupAllRemovedAndFixedVariables();
8758 graph->ResetAtMostOneIterator();
8760 absl::Span<const Literal> amo = graph->NextAtMostOne();
8761 if (amo.empty())
break;
8764 ConstraintProto* ct = context_->working_model->add_constraints();
8765 ct->mutable_at_most_one()->mutable_literals()->Reserve(amo.size());
8766 for (Literal l : amo) {
8770 if (l.Index() < equiv_map.size()) {
8771 l = Literal(equiv_map[l]);
8774 const int var = new_to_old_index[l.Variable().value()];
8775 ct->mutable_at_most_one()->add_literals(l.IsPositive() ? var
8779 can_be_removed[l.Variable().value()] =
false;
8782 context_->UpdateNewConstraintsVariableUsage();
8785 SatPresolver sat_presolver(&sat_postsolver, logger_);
8786 sat_presolver.SetNumVariables(num_variables);
8787 if (!equiv_map.empty()) {
8788 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8790 sat_presolver.SetTimeLimit(time_limit_);
8791 sat_presolver.SetParameters(sat_params);
8795 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
8796 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[
i]);
8798 if (!sat_solver->ExtractClauses(&sat_presolver))
return false;
8802 for (
int i = 0;
i < 1; ++
i) {
8803 const int old_num_clause = sat_postsolver.NumClauses();
8804 if (!sat_presolver.Presolve(can_be_removed))
return false;
8805 if (old_num_clause == sat_postsolver.NumClauses())
break;
8809 const int new_num_variables = sat_presolver.NumVariables();
8810 if (new_num_variables > num_variables) {
8811 VLOG(1) <<
"New variables added by the SAT presolver.";
8812 for (
int i = num_variables;
i < new_num_variables; ++
i) {
8813 new_to_old_index.push_back(context_->working_model->variables().size());
8814 IntegerVariableProto* var_proto =
8815 context_->working_model->add_variables();
8816 var_proto->add_domain(0);
8817 var_proto->add_domain(1);
8819 context_->InitializeNewDomains();
8823 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8829 ExtractClauses(
true, new_to_old_index, sat_presolver,
8830 context_->working_model);
8833 context_->UpdateNewConstraintsVariableUsage();
8838 for (
int i = 0;
i < num_variables; ++
i) {
8839 const int var = new_to_old_index[
i];
8840 if (context_->VarToConstraints(var).empty()) {
8841 context_->MarkVariableAsRemoved(var);
8846 const std::string name =
8847 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ?
"sat_postsolver" :
"";
8848 ExtractClauses(
false, new_to_old_index,
8849 sat_postsolver, context_->mapping_model, name);
8853void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8854 if (context_->ModelIsUnsat())
return;
8858 if (!context_->CanonicalizeObjective()) {
8862 std::vector<int> exos;
8863 const int num_constraints = context_->working_model->constraints_size();
8864 for (
int c = 0;
c < num_constraints; ++
c) {
8865 const ConstraintProto& ct = context_->working_model->constraints(c);
8866 if (!ct.enforcement_literal().empty())
continue;
8884 for (
int i = 0;
i < 3; ++
i) {
8885 for (
const int c : exos) {
8886 const ConstraintProto& ct = context_->working_model->constraints(c);
8887 const int num_terms = ct.exactly_one().literals().size();
8888 if (num_terms <= 1)
continue;
8889 int64_t min_obj = std::numeric_limits<int64_t>::max();
8890 int64_t second_min = std::numeric_limits<int64_t>::max();
8891 for (
int i = 0;
i < num_terms; ++
i) {
8892 const int literal = ct.exactly_one().literals(
i);
8893 const int64_t var_obj = context_->ObjectiveCoeff(
PositiveRef(literal));
8894 const int64_t obj =
RefIsPositive(literal) ? var_obj : -var_obj;
8895 if (obj < min_obj) {
8896 second_min = min_obj;
8898 }
else if (obj < second_min) {
8902 if (second_min == 0)
continue;
8904 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8906 if (context_->ModelIsUnsat())
return;
8911 if (num_shifts > 0) {
8912 context_->UpdateRuleStats(
"objective: shifted cost with exactly ones",
8917bool CpModelPresolver::PropagateObjective() {
8918 if (!context_->working_model->has_objective())
return true;
8919 if (context_->ModelIsUnsat())
return false;
8920 context_->WriteObjectiveToProto();
8922 int64_t min_activity = 0;
8923 int64_t max_variation = 0;
8924 const CpObjectiveProto& objective = context_->working_model->objective();
8925 const int num_terms = objective.vars().size();
8926 for (
int i = 0;
i < num_terms; ++
i) {
8927 const int var = objective.vars(
i);
8928 const int64_t coeff = objective.coeffs(
i);
8932 const int64_t domain_min = context_->MinOf(var);
8933 const int64_t domain_max = context_->MaxOf(var);
8935 min_activity += coeff * domain_min;
8937 min_activity += coeff * domain_max;
8939 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8940 max_variation = std::max(max_variation, variation);
8944 const int64_t slack =
8947 return context_->NotifyThatModelIsUnsat(
8948 "infeasible while propagating objective");
8952 if (max_variation <= slack)
return true;
8954 int num_propagations = 0;
8955 for (
int i = 0;
i < num_terms; ++
i) {
8956 const int var = objective.vars(
i);
8957 const int64_t coeff = objective.coeffs(
i);
8958 const int64_t domain_min = context_->MinOf(var);
8959 const int64_t domain_max = context_->MaxOf(var);
8961 const int64_t new_diff = slack / std::abs(coeff);
8962 if (new_diff >= domain_max - domain_min)
continue;
8966 if (!context_->IntersectDomainWith(
8967 var, Domain(domain_min, domain_min + new_diff))) {
8971 if (!context_->IntersectDomainWith(
8972 var, Domain(domain_max - new_diff, domain_max))) {
8977 CHECK_GT(num_propagations, 0);
8979 context_->UpdateRuleStats(
"objective: restricted var domains by propagation",
8998void CpModelPresolver::ExpandObjective() {
8999 if (time_limit_->LimitReached())
return;
9000 if (context_->ModelIsUnsat())
return;
9001 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9005 if (!context_->CanonicalizeObjective()) {
9009 const int num_variables = context_->working_model->variables_size();
9010 const int num_constraints = context_->working_model->constraints_size();
9013 const auto get_index = [](
int var,
bool to_lb) {
9014 return 2 * var + (to_lb ? 0 : 1);
9016 const auto get_lit_index = [](
int lit) {
9019 const int num_nodes = 2 * num_variables;
9020 std::vector<std::vector<int>> index_graph(num_nodes);
9024 std::vector<int> index_to_best_c(num_nodes, -1);
9025 std::vector<int> index_to_best_size(num_nodes, 0);
9029 int num_entries = 0;
9030 int num_propagations = 0;
9031 int num_tight_variables = 0;
9032 int num_tight_constraints = 0;
9033 const int kNumEntriesThreshold = 1e8;
9034 for (
int c = 0;
c < num_constraints; ++
c) {
9035 if (num_entries > kNumEntriesThreshold)
break;
9037 const ConstraintProto& ct = context_->working_model->constraints(c);
9038 if (!ct.enforcement_literal().empty())
continue;
9047 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
9048 context_->UpdateConstraintVariableUsage(c);
9052 const int num_terms = ct.exactly_one().literals().size();
9053 ++num_tight_constraints;
9054 num_tight_variables += num_terms;
9055 for (
int i = 0;
i < num_terms; ++
i) {
9056 if (num_entries > kNumEntriesThreshold)
break;
9057 const int neg_index = get_lit_index(ct.exactly_one().literals(
i)) ^ 1;
9059 const int old_c = index_to_best_c[neg_index];
9060 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
9061 index_to_best_c[neg_index] =
c;
9062 index_to_best_size[neg_index] = num_terms;
9065 for (
int j = 0; j < num_terms; ++j) {
9066 if (j ==
i)
continue;
9067 const int other_index = get_lit_index(ct.exactly_one().literals(j));
9069 index_graph[neg_index].push_back(other_index);
9076 if (!IsLinearEqualityConstraint(ct))
continue;
9080 const auto [min_activity, max_activity] =
9081 context_->ComputeMinMaxActivity(ct.linear());
9083 bool is_tight =
false;
9084 const int64_t rhs = ct.linear().domain(0);
9085 const int num_terms = ct.linear().vars_size();
9086 for (
int i = 0;
i < num_terms; ++
i) {
9087 const int var = ct.linear().vars(
i);
9088 const int64_t coeff = ct.linear().coeffs(
i);
9089 if (std::abs(coeff) != 1)
continue;
9090 if (num_entries > kNumEntriesThreshold)
break;
9092 const int index = get_index(var, coeff > 0);
9094 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
9095 const int64_t implied_shifted_ub = rhs - min_activity;
9096 if (implied_shifted_ub <= var_range) {
9097 if (implied_shifted_ub < var_range) ++num_propagations;
9099 ++num_tight_variables;
9101 const int neg_index = index ^ 1;
9102 const int old_c = index_to_best_c[neg_index];
9103 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
9104 index_to_best_c[neg_index] =
c;
9105 index_to_best_size[neg_index] = num_terms;
9108 for (
int j = 0; j < num_terms; ++j) {
9109 if (j ==
i)
continue;
9110 const int other_index =
9111 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
9113 index_graph[neg_index].push_back(other_index);
9116 const int64_t implied_shifted_lb = max_activity - rhs;
9117 if (implied_shifted_lb <= var_range) {
9118 if (implied_shifted_lb < var_range) ++num_propagations;
9120 ++num_tight_variables;
9122 const int old_c = index_to_best_c[index];
9123 if (old_c == -1 || num_terms > index_to_best_size[index]) {
9124 index_to_best_c[index] =
c;
9125 index_to_best_size[index] = num_terms;
9128 for (
int j = 0; j < num_terms; ++j) {
9129 if (j ==
i)
continue;
9130 const int other_index =
9131 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
9133 index_graph[index].push_back(other_index);
9137 if (is_tight) ++num_tight_constraints;
9143 if (num_propagations > 0) {
9144 context_->UpdateRuleStats(
"TODO objective: propagation possible!");
9163 if (!topo_order.ok()) {
9166 std::vector<int64_t> var_min(num_variables);
9167 std::vector<int64_t> var_max(num_variables);
9168 for (
int var = 0; var < num_variables; ++var) {
9169 var_min[var] = context_->MinOf(var);
9170 var_max[var] = context_->MaxOf(var);
9173 std::vector<std::vector<int>> components;
9175 index_graph, &components);
9176 for (
const std::vector<int>& compo : components) {
9177 if (compo.size() == 1)
continue;
9179 const int rep_var = compo[0] / 2;
9180 const bool rep_to_lp = (compo[0] % 2) == 0;
9181 for (
int i = 1;
i < compo.size(); ++
i) {
9182 const int var = compo[
i] / 2;
9183 const bool to_lb = (compo[
i] % 2) == 0;
9187 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
9188 const int64_t var_coeff = to_lb ? 1 : -1;
9189 const int64_t offset =
9190 (to_lb ? -var_min[var] : var_max[var]) -
9191 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
9192 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
9193 rep_coeff * offset)) {
9197 context_->UpdateRuleStats(
"objective: detected equivalence",
9206 int num_expands = 0;
9208 for (
const int index : *topo_order) {
9209 if (index_graph[index].empty())
continue;
9211 const int var = index / 2;
9212 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9213 if (obj_coeff == 0)
continue;
9215 const bool to_lb = (index % 2) == 0;
9216 if (obj_coeff > 0 == to_lb) {
9217 const ConstraintProto& ct =
9218 context_->working_model->constraints(index_to_best_c[index]);
9221 for (
const int lit : ct.exactly_one().literals()) {
9231 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
9233 if (context_->ModelIsUnsat())
return;
9237 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
9242 int64_t objective_coeff_in_expanded_constraint = 0;
9243 const int num_terms = ct.linear().vars().size();
9244 for (
int i = 0;
i < num_terms; ++
i) {
9245 if (ct.linear().vars(
i) == var) {
9246 objective_coeff_in_expanded_constraint = ct.linear().coeffs(
i);
9250 if (objective_coeff_in_expanded_constraint == 0) {
9255 if (!context_->SubstituteVariableInObjective(
9256 var, objective_coeff_in_expanded_constraint, ct)) {
9257 if (context_->ModelIsUnsat())
return;
9266 if (num_expands > 0) {
9267 context_->UpdateRuleStats(
"objective: expanded via tight equality",
9271 timer.AddCounter(
"propagations", num_propagations);
9272 timer.AddCounter(
"entries", num_entries);
9273 timer.AddCounter(
"tight_variables", num_tight_variables);
9274 timer.AddCounter(
"tight_constraints", num_tight_constraints);
9275 timer.AddCounter(
"expands", num_expands);
9276 timer.AddCounter(
"issues", num_issues);
9279bool CpModelPresolver::MergeCliqueConstraintsHelper(
9280 std::vector<std::vector<Literal>>& cliques, std::string_view entry_name,
9281 PresolveTimer& timer) {
9282 if (cliques.empty())
return false;
9283 const int num_constraints = context_->working_model->constraints_size();
9284 int old_num_clique_constraints = cliques.size();
9285 int old_num_entries = 0;
9286 for (
const std::vector<Literal>& clique : cliques) {
9287 old_num_entries += clique.size();
9292 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
9293 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
9294 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9295 graph->Resize(num_constraints);
9296 for (
const std::vector<Literal>& clique : cliques) {
9299 CHECK(graph->AddAtMostOne(clique));
9301 CHECK(graph->DetectEquivalences());
9302 graph->TransformIntoMaxCliques(
9306 time_limit_->ResetHistory();
9309 int new_num_clique_constraints = 0;
9310 int new_num_entries = 0;
9311 for (
const std::vector<Literal>& clique : cliques) {
9312 if (clique.empty())
continue;
9313 new_num_clique_constraints++;
9314 new_num_entries += clique.size();
9317 if (old_num_clique_constraints != new_num_clique_constraints ||
9318 old_num_entries != new_num_entries) {
9319 timer.AddMessage(absl::StrCat(
9320 "Merged ", Plural(old_num_clique_constraints,
"constraint"),
" with ",
9321 Plural(old_num_entries, entry_name),
" into ",
9322 Plural(new_num_clique_constraints,
"constraint"),
" with ",
9323 Plural(new_num_entries, entry_name)));
9330bool CpModelPresolver::MergeNoOverlapConstraints() {
9331 PresolveTimer timer(
"MergeNoOverlap", logger_, time_limit_);
9332 if (context_->ModelIsUnsat())
return false;
9333 if (time_limit_->LimitReached())
return true;
9335 const int num_constraints = context_->working_model->constraints_size();
9339 std::vector<int> disjunctive_index;
9340 std::vector<std::vector<Literal>> cliques;
9341 for (
int c = 0;
c < num_constraints; ++
c) {
9342 const ConstraintProto& ct = context_->working_model->constraints(c);
9345 std::vector<Literal> clique;
9346 for (
const int i : ct.no_overlap().intervals()) {
9347 clique.push_back(Literal(BooleanVariable(
i),
true));
9349 cliques.push_back(clique);
9350 disjunctive_index.push_back(c);
9353 if (!MergeCliqueConstraintsHelper(cliques,
"interval", timer)) {
9358 for (
int i = 0;
i < cliques.size(); ++
i) {
9359 const int ct_index = disjunctive_index[
i];
9360 if (RemoveConstraint(
9361 context_->working_model->mutable_constraints(ct_index))) {
9362 context_->UpdateConstraintVariableUsage(ct_index);
9365 for (
int i = 0;
i < cliques.size(); ++
i) {
9366 if (cliques[
i].empty())
continue;
9367 ConstraintProto* ct = context_->working_model->add_constraints();
9368 for (
const Literal l : cliques[
i]) {
9369 CHECK(l.IsPositive());
9370 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
9373 context_->UpdateRuleStats(
"no_overlap: merged constraints");
9374 context_->UpdateNewConstraintsVariableUsage();
9378bool CpModelPresolver::MergeNoOverlap2DConstraints() {
9379 PresolveTimer timer(
"MergeNoOverlap2D", logger_, time_limit_);
9380 if (context_->ModelIsUnsat())
return false;
9381 if (time_limit_->LimitReached())
return true;
9383 const int num_constraints = context_->working_model->constraints_size();
9387 std::vector<int> no_overlap2d_index;
9388 std::vector<std::vector<Literal>> cliques;
9389 absl::flat_hash_map<std::pair<int, int>,
int> rectangle_to_index;
9390 std::vector<std::pair<int, int>> index_to_rectangle;
9391 for (
int c = 0;
c < num_constraints; ++
c) {
9392 const ConstraintProto& ct = context_->working_model->constraints(c);
9395 std::vector<Literal> clique;
9396 for (
int i = 0;
i < ct.no_overlap_2d().x_intervals_size(); ++
i) {
9397 const std::pair<int, int> rect = {ct.no_overlap_2d().x_intervals(
i),
9398 ct.no_overlap_2d().y_intervals(
i)};
9399 const auto [it, inserted] =
9400 rectangle_to_index.insert({rect, rectangle_to_index.size()});
9401 if (inserted) index_to_rectangle.push_back(rect);
9402 clique.push_back(Literal(BooleanVariable(it->second),
true));
9404 cliques.push_back(clique);
9405 no_overlap2d_index.push_back(c);
9408 if (!MergeCliqueConstraintsHelper(cliques,
"rectangle", timer)) {
9413 for (
int i = 0;
i < cliques.size(); ++
i) {
9414 const int ct_index = no_overlap2d_index[
i];
9415 if (RemoveConstraint(
9416 context_->working_model->mutable_constraints(ct_index))) {
9417 context_->UpdateConstraintVariableUsage(ct_index);
9420 for (
int i = 0;
i < cliques.size(); ++
i) {
9421 if (cliques[
i].empty())
continue;
9422 ConstraintProto* ct = context_->working_model->add_constraints();
9423 for (
const Literal l : cliques[
i]) {
9424 CHECK(l.IsPositive());
9425 const std::pair<int, int> rect = index_to_rectangle[l.Variable().value()];
9426 ct->mutable_no_overlap_2d()->add_x_intervals(rect.first);
9427 ct->mutable_no_overlap_2d()->add_y_intervals(rect.second);
9430 context_->UpdateRuleStats(
"no_overlap_2d: merged constraints");
9431 context_->UpdateNewConstraintsVariableUsage();
9435void CpModelPresolver::DetectEncodedComplexDomains(
PresolveContext* context) {
9436 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9437 if (context->ModelIsUnsat())
return;
9438 if (time_limit_->LimitReached())
return;
9440 std::vector<VariableEncodingLocalModel> local_models =
9442 for (VariableEncodingLocalModel& local_model : local_models) {
9443 if (time_limit_->LimitReached())
return;
9454void CpModelPresolver::TransformIntoMaxCliques() {
9455 if (context_->ModelIsUnsat())
return;
9456 if (context_->params().merge_at_most_one_work_limit() <= 0.0)
return;
9458 auto convert = [](
int ref) {
9459 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
9460 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
9462 const int num_constraints = context_->working_model->constraints_size();
9466 std::vector<std::vector<Literal>> cliques;
9468 for (
int c = 0;
c < num_constraints; ++
c) {
9469 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9471 std::vector<Literal> clique;
9472 for (
const int ref : ct->at_most_one().literals()) {
9473 clique.push_back(convert(ref));
9475 cliques.push_back(clique);
9476 if (RemoveConstraint(ct)) {
9477 context_->UpdateConstraintVariableUsage(c);
9480 if (ct->enforcement_literal().size() != 1)
continue;
9481 const Literal enforcement = convert(ct->enforcement_literal(0));
9482 for (
const int ref : ct->bool_and().literals()) {
9483 if (ref == ct->enforcement_literal(0))
continue;
9484 cliques.push_back({enforcement, convert(ref).Negated()});
9486 if (RemoveConstraint(ct)) {
9487 context_->UpdateConstraintVariableUsage(c);
9492 int64_t num_literals_before = 0;
9493 const int num_old_cliques = cliques.size();
9497 const int num_variables = context_->working_model->
variables().size();
9498 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9499 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9500 graph->Resize(num_variables);
9501 for (
const std::vector<Literal>& clique : cliques) {
9502 num_literals_before += clique.size();
9503 if (!graph->AddAtMostOne(clique)) {
9504 return (
void)context_->NotifyThatModelIsUnsat();
9507 if (!graph->DetectEquivalences()) {
9508 return (
void)context_->NotifyThatModelIsUnsat();
9510 graph->MergeAtMostOnes(
9511 absl::MakeSpan(cliques),
9517 for (
int var = 0; var < num_variables; ++var) {
9518 const Literal l = Literal(BooleanVariable(var),
true);
9519 if (graph->RepresentativeOf(l) != l) {
9520 const Literal r = graph->RepresentativeOf(l);
9521 if (!context_->StoreBooleanEqualityRelation(
9522 var, r.IsPositive() ? r.Variable().value()
9529 int num_new_cliques = 0;
9530 int64_t num_literals_after = 0;
9531 for (
const std::vector<Literal>& clique : cliques) {
9532 if (clique.empty())
continue;
9534 num_literals_after += clique.size();
9535 ConstraintProto* ct = context_->working_model->add_constraints();
9536 for (
const Literal literal : clique) {
9537 if (literal.IsPositive()) {
9538 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
9540 ct->mutable_at_most_one()->add_literals(
9546 PresolveAtMostOne(ct);
9548 context_->UpdateNewConstraintsVariableUsage();
9549 if (num_new_cliques != num_old_cliques) {
9550 context_->UpdateRuleStats(
"at_most_one: transformed into max clique");
9553 if (num_old_cliques != num_new_cliques ||
9554 num_literals_before != num_literals_after) {
9555 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
" with ",
9556 num_literals_before,
" literals) into ", num_new_cliques,
"(",
9557 num_literals_after,
" literals) at_most_ones.");
9561void CpModelPresolver::TransformClausesToExactlyOne() {
9562 if (context_->ModelIsUnsat())
return;
9563 if (!context_->params().find_clauses_that_are_exactly_one())
return;
9564 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9566 auto convert = [](
int ref) {
9567 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
9568 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
9570 const int num_constraints = context_->working_model->constraints_size();
9574 const int num_variables = context_->working_model->
variables().size();
9575 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9576 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9577 graph->Resize(num_variables);
9582 std::vector<Literal> tmp_clique;
9583 std::vector<int> clause_indices;
9584 std::vector<std::vector<Literal>> clauses;
9585 for (
int c = 0;
c < num_constraints; ++
c) {
9586 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9589 for (
const int ref : ct->at_most_one().literals()) {
9590 tmp_clique.push_back(convert(ref));
9593 if (!graph->AddAtMostOne(tmp_clique)) {
9594 return (
void)context_->NotifyThatModelIsUnsat();
9597 if (ct->enforcement_literal().size() != 1)
continue;
9598 const Literal enforcement = convert(ct->enforcement_literal(0));
9599 for (
const int ref : ct->bool_and().literals()) {
9600 if (ref == ct->enforcement_literal(0))
continue;
9602 if (!graph->AddAtMostOne({enforcement, convert(ref).Negated()})) {
9603 return (
void)context_->NotifyThatModelIsUnsat();
9607 if (!ct->enforcement_literal().empty())
continue;
9608 clause_indices.push_back(c);
9609 std::vector<Literal> clause;
9610 clause.reserve(ct->bool_or().literals().size());
9611 for (
const int ref : ct->bool_or().literals()) {
9612 clause.push_back(convert(ref));
9614 clauses.push_back(std::move(clause));
9618 if (!graph->DetectEquivalences()) {
9619 return (
void)context_->NotifyThatModelIsUnsat();
9625 for (
int var = 0; var < num_variables; ++var) {
9626 const Literal l = Literal(BooleanVariable(var),
true);
9627 if (graph->RepresentativeOf(l) != l) {
9628 const Literal r = graph->RepresentativeOf(l);
9629 if (!context_->StoreBooleanEqualityRelation(
9630 var, r.IsPositive() ? r.Variable().value()
9637 auto signature = [](absl::Span<const Literal> literals) {
9638 uint64_t result = 0;
9639 for (
const Literal l : literals) {
9640 result |= (l.Index().value()) & 63;
9644 auto implied_signature = [](absl::Span<const Literal> literals) {
9645 uint64_t result = literals[0].Index().value() & 63;
9646 for (
const Literal l : literals.subspan(1)) {
9647 result |= (l.NegatedIndex().value()) & 63;
9656 int num_transformed = 0;
9657 int num_checked = 0;
9658 util_intops::StrongVector<LiteralIndex, int> count(2 * num_variables, 0);
9659 util_intops::StrongVector<LiteralIndex, int> signatures(2 * num_variables, 0);
9660 for (
int i = 0;
i < clauses.size(); ++
i) {
9663 const int clause_size = clauses[
i].size();
9666 timer.TrackSimpleLoop(clause_size);
9667 const uint64_t clause_signature = signature(clauses[
i]);
9668 for (
const Literal l : clauses[
i]) {
9669 if (count[l] == 0)
continue;
9670 if (count[l] < clause_size || (clause_signature & ~signatures[l])) {
9675 if (!is_exo)
continue;
9677 timer.TrackSimpleLoop(clause_size);
9678 for (
const Literal l : clauses[
i]) {
9679 graph->ResetWorkDone();
9680 absl::Span<const Literal> implied = graph->GetAllImpliedLiterals(l);
9681 CHECK_GT(implied.size(), 0);
9682 count[l] = implied.size();
9683 signatures[l] = implied_signature(implied);
9684 timer.AddToWork(graph->WorkDone() * 1e-9);
9685 if (implied.size() < clause_size || (clause_signature & ~signatures[l])) {
9689 timer.TrackSimpleLoop(clause_size);
9690 for (
const Literal o : clauses[
i]) {
9691 if (o == l)
continue;
9692 if (!graph->LiteralIsImplied(o.Negated())) {
9701 context_->UpdateRuleStats(
"clauses: transformed into exactly one");
9702 google::protobuf::RepeatedField<int32_t> tmp =
9703 context_->working_model->constraints(clause_indices[
i])
9706 *(context_->working_model->mutable_constraints(clause_indices[
i])
9707 ->mutable_exactly_one()
9708 ->mutable_literals()) = tmp;
9710 if (timer.WorkLimitIsReached())
break;
9713 timer.AddCounter(
"num_amos", num_amos);
9714 timer.AddCounter(
"num_clauses", clauses.size());
9715 timer.AddCounter(
"num_transformed", num_transformed);
9716 timer.AddCounter(
"num_checked", num_checked);
9720 if (context_->ModelIsUnsat())
return false;
9721 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9724 if (ExploitEquivalenceRelations(c, ct)) {
9725 context_->UpdateConstraintVariableUsage(c);
9729 if (PresolveEnforcementLiteral(ct)) {
9730 context_->UpdateConstraintVariableUsage(c);
9736 return PresolveBoolOr(ct);
9738 return PresolveBoolAnd(ct);
9740 return PresolveAtMostOne(ct);
9742 return PresolveExactlyOne(ct);
9744 return PresolveBoolXor(ct);
9747 context_->UpdateConstraintVariableUsage(c);
9749 return PresolveLinMax(c, ct);
9752 context_->UpdateConstraintVariableUsage(c);
9754 return PresolveIntProd(ct);
9757 context_->UpdateConstraintVariableUsage(c);
9759 return PresolveIntDiv(c, ct);
9762 context_->UpdateConstraintVariableUsage(c);
9764 return PresolveIntMod(c, ct);
9766 bool changed =
false;
9767 if (!CanonicalizeLinear(ct, &changed)) {
9771 context_->UpdateConstraintVariableUsage(c);
9773 if (PropagateDomainsInLinear(c, ct)) {
9774 context_->UpdateConstraintVariableUsage(c);
9776 if (PresolveSmallLinear(ct)) {
9777 context_->UpdateConstraintVariableUsage(c);
9779 if (PresolveLinearEqualityWithModulo(ct)) {
9780 context_->UpdateConstraintVariableUsage(c);
9783 if (RemoveSingletonInLinear(ct)) {
9784 context_->UpdateConstraintVariableUsage(c);
9788 if (PresolveSmallLinear(ct)) {
9789 context_->UpdateConstraintVariableUsage(c);
9792 if (PresolveSmallLinear(ct)) {
9793 context_->UpdateConstraintVariableUsage(c);
9795 if (PresolveLinearOnBooleans(ct)) {
9796 context_->UpdateConstraintVariableUsage(c);
9801 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
9802 if (context_->ModelIsUnsat())
return false;
9804 if (DivideLinearByGcd(ct)) {
9805 context_->UpdateConstraintVariableUsage(c);
9807 if (PresolveSmallLinear(ct)) {
9808 context_->UpdateConstraintVariableUsage(c);
9812 if (PresolveDiophantine(ct)) {
9813 context_->UpdateConstraintVariableUsage(c);
9816 TryToReduceCoefficientsOfLinearConstraint(c, ct);
9820 return PresolveInterval(c, ct);
9822 return PresolveInverse(ct);
9824 return PresolveElement(c, ct);
9826 return PresolveTable(ct);
9828 return PresolveAllDiff(ct);
9830 DetectDuplicateIntervals(c,
9832 return PresolveNoOverlap(ct);
9834 const bool changed = PresolveNoOverlap2D(c, ct);
9840 DetectDuplicateIntervals(
9842 DetectDuplicateIntervals(
9848 DetectDuplicateIntervals(c,
9850 return PresolveCumulative(ct);
9852 return PresolveCircuit(ct);
9854 return PresolveRoutes(ct);
9856 return PresolveAutomaton(ct);
9858 return PresolveReservoir(ct);
9865bool CpModelPresolver::ProcessSetPPCSubset(
int subset_c,
int superset_c,
9866 absl::flat_hash_set<int>* tmp_set,
9867 bool* remove_subset,
9868 bool* remove_superset,
9869 bool* stop_processing_superset) {
9892 for (
const int literal :
9896 if (tmp_set->contains(literal))
continue;
9903 ConstraintProto copy = *superset_ct;
9908 *remove_subset =
true;
9915 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
9916 *remove_superset =
true;
9923 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
9924 *remove_subset =
true;
9933 int64_t min_sum = std::numeric_limits<int64_t>::max();
9934 int64_t max_sum = std::numeric_limits<int64_t>::min();
9939 int num_matches = 0;
9941 Domain reachable(0);
9942 std::vector<std::pair<int64_t, int>> coeff_counts;
9943 for (
int i = 0;
i < superset_ct->
linear().vars().size(); ++
i) {
9946 if (tmp_set->contains(var)) {
9948 min_sum = std::min(min_sum, coeff);
9949 max_sum = std::max(max_sum, coeff);
9950 coeff_counts.push_back({superset_ct->
linear().
coeffs(
i), 1});
9955 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9956 .RelaxIfTooComplex();
9957 temp_ct_.mutable_linear()->add_vars(var);
9958 temp_ct_.mutable_linear()->add_coeffs(coeff);
9968 if (num_matches != tmp_set->size())
return true;
9970 context_->UpdateRuleStats(
"setppc: exactly_one included in linear");
9972 context_->UpdateRuleStats(
"setppc: at_most_one included in linear");
9975 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9977 if (reachable.IsIncludedIn(superset_rhs)) {
9979 context_->UpdateRuleStats(
"setppc: removed trivial linear constraint");
9980 *remove_superset =
true;
9983 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9985 *stop_processing_superset =
true;
9986 return MarkConstraintAsFalse(
9987 superset_ct,
"setppc: removed infeasible linear constraint");
9993 CHECK_GT(num_matches, 0);
9996 temp_ct_.mutable_linear());
9997 PropagateDomainsInLinear(-1, &temp_ct_);
10003 std::sort(coeff_counts.begin(), coeff_counts.end());
10005 for (
int i = 0;
i < coeff_counts.size(); ++
i) {
10006 if (new_size > 0 &&
10007 coeff_counts[
i].first == coeff_counts[new_size - 1].first) {
10008 coeff_counts[new_size - 1].second++;
10011 coeff_counts[new_size++] = coeff_counts[
i];
10013 coeff_counts.resize(new_size);
10015 int64_t best_count = 0;
10016 for (
const auto [coeff, count] : coeff_counts) {
10017 if (count > best_count) {
10019 best_count = count;
10023 LinearConstraintProto new_ct = superset_ct->
linear();
10025 for (
int i = 0;
i < new_ct.vars().size(); ++
i) {
10026 const int var = new_ct.vars(
i);
10027 int64_t coeff = new_ct.coeffs(
i);
10028 if (tmp_set->contains(var)) {
10029 if (coeff == best)
continue;
10032 new_ct.set_vars(new_size, var);
10033 new_ct.set_coeffs(new_size, coeff);
10038 new_ct.mutable_coeffs()->Truncate(new_size);
10042 new_ct.coeffs())) {
10044 context_->UpdateConstraintVariableUsage(superset_c);
10045 context_->UpdateRuleStats(
"setppc: reduced linear coefficients");
10064void CpModelPresolver::ProcessSetPPC() {
10065 if (time_limit_->LimitReached())
return;
10066 if (context_->ModelIsUnsat())
return;
10067 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
10068 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10071 CompactVectorVector<int> storage;
10073 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10076 std::vector<int> temp_literals;
10077 const int num_constraints = context_->working_model->constraints_size();
10078 std::vector<int> relevant_constraints;
10079 for (
int c = 0;
c < num_constraints; ++
c) {
10080 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10081 const auto type = ct->constraint_case();
10089 context_->UpdateConstraintVariableUsage(c);
10091 if (context_->ModelIsUnsat())
return;
10093 temp_literals.clear();
10094 for (
const int ref :
10097 : ct->exactly_one().literals()) {
10098 temp_literals.push_back(
10103 relevant_constraints.push_back(c);
10104 detector.AddPotentialSet(storage.Add(temp_literals));
10115 const int size = ct->linear().vars().size();
10116 if (size <= 2)
continue;
10121 temp_literals.clear();
10122 for (
int i = 0;
i < size; ++
i) {
10123 const int var = ct->linear().vars(
i);
10124 if (!context_->CanBeUsedAsLiteral(var))
continue;
10126 temp_literals.push_back(
10127 Literal(BooleanVariable(var),
true).
Index().value());
10129 if (temp_literals.size() > 2) {
10131 relevant_constraints.push_back(c);
10132 detector.AddPotentialSuperset(storage.Add(temp_literals));
10137 absl::flat_hash_set<int> tmp_set;
10138 int64_t num_inclusions = 0;
10139 detector.DetectInclusions([&](
int subset,
int superset) {
10141 bool remove_subset =
false;
10142 bool remove_superset =
false;
10143 bool stop_processing_superset =
false;
10144 const int subset_c = relevant_constraints[subset];
10145 const int superset_c = relevant_constraints[superset];
10146 detector.IncreaseWorkDone(storage[subset].size());
10147 detector.IncreaseWorkDone(storage[superset].size());
10148 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
10149 &remove_superset, &stop_processing_superset)) {
10153 if (remove_subset) {
10154 context_->working_model->mutable_constraints(subset_c)->Clear();
10155 context_->UpdateConstraintVariableUsage(subset_c);
10156 detector.StopProcessingCurrentSubset();
10158 if (remove_superset) {
10159 context_->working_model->mutable_constraints(superset_c)->Clear();
10160 context_->UpdateConstraintVariableUsage(superset_c);
10161 detector.StopProcessingCurrentSuperset();
10163 if (stop_processing_superset) {
10164 context_->UpdateConstraintVariableUsage(superset_c);
10165 detector.StopProcessingCurrentSuperset();
10169 timer.AddToWork(detector.work_done() * 1e-9);
10170 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
10171 timer.AddCounter(
"num_inclusions", num_inclusions);
10174void CpModelPresolver::DetectIncludedEnforcement() {
10175 if (time_limit_->LimitReached())
return;
10176 if (context_->ModelIsUnsat())
return;
10177 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
10178 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10181 std::vector<int> relevant_constraints;
10182 CompactVectorVector<int> storage;
10184 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10186 std::vector<int> temp_literals;
10187 const int num_constraints = context_->working_model->constraints_size();
10188 for (
int c = 0;
c < num_constraints; ++
c) {
10189 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
10190 if (ct->enforcement_literal().size() <= 1)
continue;
10195 context_->UpdateConstraintVariableUsage(c);
10197 if (context_->ModelIsUnsat())
return;
10201 temp_literals.clear();
10202 for (
const int ref : ct->enforcement_literal()) {
10203 temp_literals.push_back(
10208 relevant_constraints.push_back(c);
10213 detector.AddPotentialSet(storage.Add(temp_literals));
10215 detector.AddPotentialSuperset(storage.Add(temp_literals));
10219 int64_t num_inclusions = 0;
10220 detector.DetectInclusions([&](
int subset,
int superset) {
10222 const int subset_c = relevant_constraints[subset];
10223 const int superset_c = relevant_constraints[superset];
10224 ConstraintProto* subset_ct =
10225 context_->working_model->mutable_constraints(subset_c);
10226 ConstraintProto* superset_ct =
10227 context_->working_model->mutable_constraints(superset_c);
10230 context_->tmp_literal_set.clear();
10231 for (
const int ref : subset_ct->bool_and().literals()) {
10232 context_->tmp_literal_set.insert(ref);
10238 for (
const int ref : superset_ct->enforcement_literal()) {
10239 if (context_->tmp_literal_set.contains(ref)) {
10240 context_->UpdateRuleStats(
"bool_and: filtered enforcement");
10241 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
10242 context_->UpdateRuleStats(
"bool_and: never enforced");
10243 superset_ct->Clear();
10244 context_->UpdateConstraintVariableUsage(superset_c);
10245 detector.StopProcessingCurrentSuperset();
10248 superset_ct->set_enforcement_literal(new_size++, ref);
10251 if (new_size < superset_ct->bool_and().literals().size()) {
10252 context_->UpdateConstraintVariableUsage(superset_c);
10253 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
10259 for (
const int ref : superset_ct->bool_and().literals()) {
10260 if (context_->tmp_literal_set.contains(ref)) {
10261 context_->UpdateRuleStats(
"bool_and: filtered literal");
10262 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
10263 if (!MarkConstraintAsFalse(superset_ct,
"bool_and: must be false"))
10265 context_->UpdateConstraintVariableUsage(superset_c);
10266 detector.StopProcessingCurrentSuperset();
10269 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
10272 if (new_size < superset_ct->bool_and().literals().size()) {
10273 context_->UpdateConstraintVariableUsage(superset_c);
10274 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
10279 context_->UpdateRuleStats(
"TODO bool_and enforcement in linear enf");
10283 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
10284 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
10285 timer.AddCounter(
"num_inclusions", num_inclusions);
10296bool CpModelPresolver::ProcessEncodingFromLinear(
10297 const int linear_encoding_ct_index,
10298 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
10299 int64_t* num_multiple_terms) {
10301 bool in_exactly_one =
false;
10302 absl::flat_hash_map<int, int> var_to_ref;
10304 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
10309 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
10311 in_exactly_one =
true;
10312 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
10319 const ConstraintProto& linear_encoding =
10320 context_->working_model->constraints(linear_encoding_ct_index);
10321 int64_t rhs = linear_encoding.linear().domain(0);
10322 int target_ref = std::numeric_limits<int>::min();
10323 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
10324 const int num_terms = linear_encoding.linear().vars().size();
10325 for (
int i = 0;
i < num_terms; ++
i) {
10326 const int ref = linear_encoding.linear().vars(
i);
10327 const int64_t coeff = linear_encoding.linear().coeffs(
i);
10328 const auto it = var_to_ref.find(
PositiveRef(ref));
10330 if (it == var_to_ref.end()) {
10331 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) <<
"Uniqueness";
10332 CHECK_EQ(std::abs(coeff), 1);
10333 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
10339 if (it->second == ref) {
10341 ref_to_coeffs.push_back({ref, coeff});
10345 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
10348 if (target_ref == std::numeric_limits<int>::min() ||
10349 context_->CanBeUsedAsLiteral(target_ref)) {
10353 context_->UpdateRuleStats(
"encoding: candidate linear is all boolean now");
10358 std::vector<int64_t> all_values;
10359 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
10360 for (
const auto& [ref, coeff] : ref_to_coeffs) {
10361 const int64_t value = rhs - coeff;
10362 all_values.push_back(value);
10363 value_to_refs[value].push_back(ref);
10367 for (
const auto& [var, ref] : var_to_ref) {
10368 all_values.push_back(rhs);
10369 value_to_refs[rhs].push_back(ref);
10371 if (!in_exactly_one) {
10374 all_values.push_back(rhs);
10379 bool domain_reduced =
false;
10380 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
10383 if (domain_reduced) {
10384 context_->UpdateRuleStats(
"encoding: reduced target domain");
10387 if (context_->CanBeUsedAsLiteral(target_ref)) {
10389 context_->UpdateRuleStats(
"encoding: candidate linear is all boolean now");
10394 absl::flat_hash_set<int64_t> value_set;
10395 const Domain target_domain =
10397 ? context_->DomainOf(target_ref)
10398 : context_->DomainOf(
NegatedRef(target_ref)).Negation();
10399 for (
const int64_t v : target_domain.Values()) {
10400 value_set.insert(v);
10402 for (
auto& [value, literals] : value_to_refs) {
10404 absl::c_sort(literals);
10407 if (!value_set.contains(value)) {
10408 for (
const int lit : literals) {
10409 if (!context_->SetLiteralToFalse(lit))
return false;
10414 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
10417 ++*num_unique_terms;
10418 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
10422 ++*num_multiple_terms;
10423 const int associated_lit =
10424 context_->GetOrCreateVarValueEncoding(target_ref, value);
10425 for (
const int lit : literals) {
10426 context_->AddImplication(lit, associated_lit);
10431 if (in_exactly_one || value != rhs) {
10437 context_->working_model->add_constraints()->mutable_bool_or();
10438 for (
const int lit : literals) bool_or->add_literals(lit);
10439 bool_or->add_literals(
NegatedRef(associated_lit));
10445 context_->working_model->mutable_constraints(linear_encoding_ct_index)
10447 context_->UpdateNewConstraintsVariableUsage();
10448 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
10466 if (a ==
b)
return true;
10469 return (*
column)[a] == (*column)[
b];
10479 if (time_limit_->LimitReached())
return;
10480 if (context_->ModelIsUnsat())
return;
10481 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
10484 const int num_vars = context_->working_model->variables().size();
10485 const int num_constraints = context_->working_model->constraints().size();
10489 std::vector<int> flat_vars;
10490 std::vector<std::pair<int, int64_t>> flat_terms;
10504 std::vector<bool> appear_in_amo(num_vars,
false);
10505 std::vector<bool> appear_in_bool_constraint(num_vars,
false);
10506 for (
int c = 0; c < num_constraints; ++c) {
10508 absl::Span<const int> literals;
10510 bool is_amo =
false;
10521 if (!literals.empty()) {
10522 for (
const int lit : literals) {
10525 if (is_amo) appear_in_amo[lit] =
true;
10526 appear_in_bool_constraint[lit] =
true;
10527 flat_vars.push_back(lit);
10528 flat_terms.push_back({c, 1});
10534 const int num_terms = ct.
linear().
vars().size();
10535 for (
int i = 0;
i < num_terms; ++
i) {
10538 flat_vars.push_back(var);
10539 flat_terms.push_back({c, coeff});
10551 if (context_->working_model->has_objective()) {
10552 context_->WriteObjectiveToProto();
10553 const int num_terms = context_->working_model->objective().vars().size();
10554 for (
int i = 0;
i < num_terms; ++
i) {
10555 const int var = context_->working_model->objective().vars(
i);
10556 const int64_t coeff = context_->working_model->objective().coeffs(
i);
10557 flat_vars.push_back(var);
10574 std::vector<int> flat_duplicates;
10575 std::vector<int> flat_representatives;
10576 for (
int var = 0; var < var_to_columns.
size(); ++var) {
10577 const int size_seen = var_to_columns[var].
size();
10578 if (size_seen == 0)
continue;
10579 if (size_seen != context_->VarToConstraints(var).size())
continue;
10585 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
10586 context_->UpdateRuleStats(
10587 "TODO duplicate: duplicate columns in Boolean constraints");
10591 const auto [it, inserted] = duplicates.insert({var, var});
10593 flat_duplicates.push_back(var);
10594 flat_representatives.push_back(it->second);
10599 int num_equivalent_classes = 0;
10602 std::vector<std::pair<int, int64_t>> definition;
10603 std::vector<int> var_to_remove;
10604 std::vector<int> var_to_rep(num_vars, -1);
10605 for (
int var = 0; var < rep_to_dups.
size(); ++var) {
10606 if (rep_to_dups[var].empty())
continue;
10615 definition.clear();
10616 definition.push_back({var, 1});
10617 Domain domain = context_->DomainOf(var);
10618 for (
const int other_var : rep_to_dups[var]) {
10619 definition.push_back({other_var, 1});
10620 domain = domain.
AdditionWith(context_->DomainOf(other_var));
10624 context_->UpdateRuleStats(
10625 "TODO duplicate: domain of the sum is too complex");
10628 if (appear_in_amo[var]) {
10631 const int new_var = context_->NewIntVarWithDefinition(
10632 domain, definition,
true);
10633 if (new_var == -1) {
10634 context_->UpdateRuleStats(
"TODO duplicate: possible overflow");
10638 var_to_remove.push_back(var);
10639 CHECK_EQ(var_to_rep[var], -1);
10640 var_to_rep[var] = new_var;
10641 for (
const int other_var : rep_to_dups[var]) {
10642 var_to_remove.push_back(other_var);
10643 CHECK_EQ(var_to_rep[other_var], -1);
10644 var_to_rep[other_var] = new_var;
10648 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
10649 if (obj_coeff != 0) {
10650 context_->RemoveVariableFromObjective(var);
10651 for (
const int other_var : rep_to_dups[var]) {
10652 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
10653 context_->RemoveVariableFromObjective(other_var);
10655 context_->AddToObjective(new_var, obj_coeff);
10658 num_equivalent_classes++;
10663 if (!var_to_remove.empty()) {
10664 absl::flat_hash_set<int> seen;
10665 std::vector<std::pair<int, int64_t>> new_terms;
10666 for (
int c = 0; c < num_constraints; ++c) {
10668 context_->working_model->mutable_constraints(c);
10684 if (mutable_arg !=
nullptr) {
10686 const int num_terms = mutable_arg->
literals().size();
10687 for (
int i = 0;
i < num_terms; ++
i) {
10688 const int lit = mutable_arg->
literals(
i);
10692 const auto [_, inserted] = seen.insert(rep);
10693 if (inserted) new_terms.push_back({rep, 1});
10699 if (new_size == num_terms)
continue;
10703 for (
const auto [var, coeff] : new_terms) {
10706 context_->UpdateConstraintVariableUsage(c);
10714 const int num_terms = mutable_linear->
vars().size();
10715 for (
int i = 0;
i < num_terms; ++
i) {
10716 const int var = mutable_linear->
vars(
i);
10717 const int64_t coeff = mutable_linear->
coeffs(
i);
10718 const int rep = var_to_rep[var];
10720 const auto [_, inserted] = seen.insert(rep);
10721 if (inserted) new_terms.push_back({rep, coeff});
10724 mutable_linear->
set_vars(new_size, var);
10725 mutable_linear->
set_coeffs(new_size, coeff);
10728 if (new_size == num_terms)
continue;
10732 for (
const auto [var, coeff] : new_terms) {
10736 context_->UpdateConstraintVariableUsage(c);
10745 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
10746 for (
const int var : var_to_remove) {
10747 CHECK(context_->VarToConstraints(var).empty());
10748 context_->MarkVariableAsRemoved(var);
10750 if (num_var_reduction > 0) {
10751 context_->UpdateRuleStats(
"duplicate: removed duplicated column",
10752 num_var_reduction);
10755 timer.
AddCounter(
"num_equiv_classes", num_equivalent_classes);
10756 timer.
AddCounter(
"num_removed_vars", num_var_reduction);
10759void CpModelPresolver::DetectDuplicateConstraints() {
10771 std::vector<int> interval_mapping;
10778 const std::vector<std::pair<int, int>> duplicates =
10780 timer.AddCounter(
"duplicates", duplicates.size());
10781 for (
const auto& [dup, rep] : duplicates) {
10793 CHECK_EQ(interval_mapping[rep], -1);
10794 interval_mapping[dup] = rep;
10804 if (rep_domain != d) {
10805 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
10806 const Domain rhs = rep_domain.IntersectionWith(d);
10807 if (rhs.IsEmpty()) {
10808 if (!MarkConstraintAsFalse(
10810 "duplicate: false after merging")) {
10817 context_->UpdateConstraintVariableUsage(rep);
10821 ->mutable_linear());
10826 context_->UpdateRuleStats(
10827 "duplicate: linear constraint parallel to objective");
10828 const Domain objective_domain =
10831 context_->working_model->constraints(dup).linear());
10832 if (objective_domain != d) {
10833 context_->UpdateRuleStats(
"duplicate: updated objective domain");
10834 const Domain new_domain = objective_domain.IntersectionWith(d);
10835 if (new_domain.IsEmpty()) {
10836 return (
void)context_->NotifyThatModelIsUnsat(
10837 "Constraint parallel to the objective makes the objective domain "
10841 context_->working_model->mutable_objective());
10844 context_->ReadObjectiveFromProto();
10849 context_->working_model->mutable_constraints(dup)->Clear();
10850 context_->UpdateConstraintVariableUsage(dup);
10851 context_->UpdateRuleStats(
"duplicate: removed constraint");
10854 if (!interval_mapping.empty()) {
10855 context_->UpdateRuleStats(
"duplicate: remapped duplicate intervals");
10856 const int num_constraints = context_->working_model->constraints().size();
10857 for (
int c = 0;
c < num_constraints; ++
c) {
10858 bool changed =
false;
10860 [&interval_mapping, &changed](
int* ref) {
10861 const int new_ref = interval_mapping[*ref];
10862 if (new_ref != -1) {
10867 context_->working_model->mutable_constraints(c));
10868 if (changed) context_->UpdateConstraintVariableUsage(c);
10873void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
10876 if (time_limit_->LimitReached())
return;
10877 if (context_->ModelIsUnsat())
return;
10878 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10881 if (context_->working_model->has_objective()) {
10882 if (!context_->CanonicalizeObjective())
return;
10883 context_->WriteObjectiveToProto();
10886 absl::flat_hash_set<Literal> enforcement_vars;
10887 std::vector<std::pair<Literal, Literal>> implications_used;
10892 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
10894 timer.AddCounter(
"without_enforcements",
10895 duplicates_without_enforcement.size());
10896 for (
const auto& [dup, rep] : duplicates_without_enforcement) {
10897 auto* dup_ct = context_->working_model->mutable_constraints(dup);
10898 auto* rep_ct = context_->working_model->mutable_constraints(rep);
10901 context_->UpdateRuleStats(
10902 "TODO interval: same interval with different enforcement?");
10908 if (PresolveEnforcementLiteral(dup_ct)) {
10909 context_->UpdateConstraintVariableUsage(dup);
10911 if (PresolveEnforcementLiteral(rep_ct)) {
10912 context_->UpdateConstraintVariableUsage(rep);
10923 if (dup_ct->enforcement_literal().empty() ||
10924 rep_ct->enforcement_literal().empty()) {
10925 context_->UpdateRuleStats(
"duplicate: removed enforced constraint");
10926 rep_ct->mutable_enforcement_literal()->Clear();
10927 context_->UpdateConstraintVariableUsage(rep);
10929 context_->UpdateConstraintVariableUsage(dup);
10933 const int a = rep_ct->enforcement_literal(0);
10934 const int b = dup_ct->enforcement_literal(0);
10936 if (a ==
NegatedRef(
b) && rep_ct->enforcement_literal().size() == 1 &&
10937 dup_ct->enforcement_literal().size() == 1) {
10938 context_->UpdateRuleStats(
10939 "duplicate: both with enforcement and its negation");
10940 rep_ct->mutable_enforcement_literal()->Clear();
10941 context_->UpdateConstraintVariableUsage(rep);
10943 context_->UpdateConstraintVariableUsage(dup);
10960 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
10961 context_->VariableWithCostIsUniqueAndRemovable(
b)) {
10967 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement");
10968 if (!context_->SetLiteralToFalse(a))
return;
10972 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement");
10973 if (!context_->SetLiteralToFalse(
b))
return;
10976 if (skip)
continue;
10987 if (rep_ct->enforcement_literal().size() > 1 ||
10988 dup_ct->enforcement_literal().size() > 1) {
10989 context_->UpdateRuleStats(
10990 "TODO duplicate: identical constraint with unique enforcement "
10997 context_->UpdateRuleStats(
"duplicate: dual equivalence of enforcement");
11003 solution_crush_.UpdateLiteralsToFalseIfDifferent(
NegatedRef(a),
11005 if (!context_->StoreBooleanEqualityRelation(a,
b))
return;
11009 if (dup_ct->enforcement_literal().size() == 1 &&
11010 rep_ct->enforcement_literal().size() == 1) {
11012 context_->UpdateConstraintVariableUsage(dup);
11018 if (implication_graph !=
nullptr && mapping !=
nullptr &&
11019 trail !=
nullptr) {
11020 for (
int i = 0;
i < 2;
i++) {
11024 const int c_a =
i == 0 ? dup : rep;
11025 const int c_b =
i == 0 ? rep : dup;
11026 const auto& ct_a = context_->working_model->constraints(c_a);
11027 const auto& ct_b = context_->working_model->constraints(c_b);
11029 enforcement_vars.clear();
11030 implications_used.clear();
11031 for (
const int proto_lit : ct_b.enforcement_literal()) {
11032 const Literal lit = mapping->Literal(proto_lit);
11033 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
11034 enforcement_vars.insert(lit);
11036 for (
const int proto_lit : ct_a.enforcement_literal()) {
11037 const Literal lit = mapping->Literal(proto_lit);
11038 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
11039 for (
const Literal implication_lit :
11040 implication_graph->DirectImplications(lit)) {
11041 auto extracted = enforcement_vars.extract(implication_lit);
11042 if (!extracted.empty() && lit != implication_lit) {
11043 implications_used.push_back({lit, implication_lit});
11047 if (enforcement_vars.empty()) {
11076 ct_a.linear().vars().size() == 1 &&
11077 ct_a.enforcement_literal().size() == 1) {
11078 const int var = ct_a.linear().vars(0);
11079 const Domain var_domain = context_->DomainOf(var);
11086 if (rhs.IsEmpty()) {
11087 if (!MarkConstraintAsFalse(rep_ct,
11088 "duplicate: linear1 infeasible"))
11090 if (!MarkConstraintAsFalse(dup_ct,
11091 "duplicate: linear1 infeasible"))
11093 context_->UpdateConstraintVariableUsage(rep);
11094 context_->UpdateConstraintVariableUsage(dup);
11097 if (rhs == var_domain) {
11098 context_->UpdateRuleStats(
"duplicate: linear1 always true");
11101 context_->UpdateConstraintVariableUsage(rep);
11102 context_->UpdateConstraintVariableUsage(dup);
11107 if (rhs.IsFixed() ||
11108 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
11109 context_->UpdateRuleStats(
11110 "TODO duplicate: skipped identical encoding constraints");
11116 context_->UpdateRuleStats(
11117 "duplicate: identical constraint with implied enforcements");
11121 rep_ct->Swap(dup_ct);
11122 context_->UpdateConstraintVariableUsage(rep);
11125 context_->UpdateConstraintVariableUsage(dup);
11129 for (
const auto& [a,
b] : implications_used) {
11130 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
11131 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(
b);
11132 context_->AddImplication(proto_lit_a, proto_lit_b);
11134 context_->UpdateNewConstraintsVariableUsage();
11143 if (time_limit_->LimitReached())
return;
11144 if (context_->ModelIsUnsat())
return;
11149 std::vector<std::pair<int, int>> different_vars;
11150 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
11153 const auto process_difference = [&different_vars, &offsets](
int v1,
int v2,
11155 Domain exclusion = d.Complement().PartAroundZero();
11156 if (exclusion.
IsEmpty())
return;
11157 if (
v1 == v2)
return;
11158 std::pair<int, int> key = {
v1, v2};
11160 std::swap(key.first, key.second);
11167 different_vars.push_back(key);
11168 offsets[key] = {exclusion.
Min() == std::numeric_limits<int64_t>::min()
11169 ? std::numeric_limits<int64_t>::max()
11192 bool has_all_diff =
false;
11193 bool has_no_overlap =
false;
11194 std::vector<std::pair<uint64_t, int>> hashes;
11195 const int num_constraints = context_->working_model->constraints_size();
11196 for (
int c = 0; c < num_constraints; ++c) {
11199 has_all_diff =
true;
11203 has_no_overlap =
true;
11207 if (ct.
linear().
vars().size() == 1)
continue;
11229 hashes.push_back({hash, c});
11231 std::sort(hashes.begin(), hashes.end());
11232 for (
int next, start = 0; start < hashes.size(); start = next) {
11234 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
11237 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
11239 if (range.size() <= 1)
continue;
11240 if (range.size() > 10)
continue;
11242 for (
int i = 0;
i < range.size(); ++
i) {
11244 context_->working_model->constraints(range[
i].second);
11245 const int num_terms = ct1.
linear().
vars().size();
11246 for (
int j =
i + 1; j < range.size(); ++j) {
11248 context_->working_model->constraints(range[j].second);
11249 if (ct2.
linear().
vars().size() != num_terms)
continue;
11255 if (absl::MakeSpan(ct1.
linear().
vars().data(), num_terms) !=
11256 absl::MakeSpan(ct2.
linear().
vars().data(), num_terms)) {
11259 if (absl::MakeSpan(ct1.
linear().
coeffs().data(), num_terms) !=
11260 absl::MakeSpan(ct2.
linear().
coeffs().data(), num_terms)) {
11266 (void)context_->NotifyThatModelIsUnsat(
11267 "two incompatible linear constraint");
11271 context_->UpdateRuleStats(
11272 "incompatible linear: set enforcement to false");
11279 context_->UpdateRuleStats(
11280 "incompatible linear: set enforcement to false");
11295 Domain union_of_domain =
11302 std::move(union_of_domain));
11310 context_->UpdateRuleStats(
"incompatible linear: add implication");
11311 context_->AddImplication(lit1,
NegatedRef(lit2));
11332 if (context_->params().infer_all_diffs() && !has_all_diff &&
11333 !has_no_overlap && different_vars.size() > 2) {
11335 local_time.
Start();
11337 std::vector<std::vector<Literal>> cliques;
11338 absl::flat_hash_set<int> used_var;
11341 const int num_variables = context_->working_model->
variables().size();
11344 graph->
Resize(num_variables);
11345 for (
const auto [var1, var2] : different_vars) {
11348 if (var1 == var2) {
11349 (void)context_->NotifyThatModelIsUnsat(
"x != y with x == y");
11354 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
11355 Literal(BooleanVariable(var2), true)}));
11356 if (!used_var.contains(var1)) {
11357 used_var.insert(var1);
11358 cliques.push_back({
Literal(BooleanVariable(var1),
true),
11359 Literal(BooleanVariable(var2),
true)});
11361 if (!used_var.contains(var2)) {
11362 used_var.insert(var2);
11363 cliques.push_back({
Literal(BooleanVariable(var1),
true),
11364 Literal(BooleanVariable(var2),
true)});
11367 CHECK(graph->DetectEquivalences());
11368 graph->TransformIntoMaxCliques(&cliques, 1e8);
11370 int num_cliques = 0;
11371 int64_t cumulative_size = 0;
11372 for (std::vector<Literal>& clique : cliques) {
11373 if (clique.size() <= 2)
continue;
11376 cumulative_size += clique.size();
11377 std::sort(clique.begin(), clique.end());
11383 const int num_terms = clique.size();
11384 std::vector<int64_t> sizes(num_terms,
11385 std::numeric_limits<int64_t>::max());
11386 for (
int i = 0;
i < num_terms; ++
i) {
11387 const int v1 = clique[
i].Variable().value();
11388 for (
int j =
i + 1; j < num_terms; ++j) {
11389 const int v2 = clique[j].Variable().value();
11390 const auto [o1, o2] = offsets.at({
v1, v2});
11391 sizes[
i] = std::min(sizes[
i], o1);
11392 sizes[j] = std::min(sizes[j], o2);
11396 int num_greater_than_one = 0;
11398 for (
int i = 0;
i < num_terms; ++
i) {
11399 CHECK_GE(sizes[
i], 1);
11400 if (sizes[
i] > 1) ++num_greater_than_one;
11405 issue =
CapAdd(issue, sizes[
i]);
11406 if (issue == std::numeric_limits<int64_t>::max()) {
11407 context_->UpdateRuleStats(
"TODO no_overlap: with task always last");
11408 num_greater_than_one = 0;
11413 if (num_greater_than_one > 0) {
11419 context_->UpdateRuleStats(
11420 "no_overlap: inferred from x != y constraints");
11422 std::vector<int> intervals;
11423 for (
int i = 0;
i < num_terms; ++
i) {
11424 intervals.push_back(context_->working_model->constraints().size());
11425 auto* new_interval =
11426 context_->working_model->add_constraints()->mutable_interval();
11427 new_interval->mutable_start()->set_offset(0);
11428 new_interval->mutable_start()->add_coeffs(1);
11429 new_interval->mutable_start()->add_vars(clique[
i].
Variable().value());
11431 new_interval->mutable_size()->set_offset(sizes[
i]);
11433 new_interval->mutable_end()->set_offset(sizes[
i]);
11434 new_interval->mutable_end()->add_coeffs(1);
11435 new_interval->mutable_end()->add_vars(clique[
i].
Variable().value());
11438 context_->working_model->add_constraints()->mutable_no_overlap();
11439 for (
const int interval : intervals) {
11440 new_ct->add_intervals(interval);
11443 context_->UpdateRuleStats(
"all_diff: inferred from x != y constraints");
11445 context_->working_model->add_constraints()->mutable_all_diff();
11446 for (
const Literal l : clique) {
11447 auto* expr = new_ct->add_exprs();
11448 expr->add_vars(l.Variable().value());
11449 expr->add_coeffs(1);
11454 timer.
AddCounter(
"different", different_vars.size());
11459 context_->UpdateNewConstraintsVariableUsage();
11465void Substitute(int64_t factor,
11466 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
11470 const int old_size = mutable_linear->
vars().size();
11471 for (
int i = 0;
i < old_size; ++
i) {
11472 const int var = mutable_linear->
vars(
i);
11473 int64_t coeff = mutable_linear->
coeffs(
i);
11474 const auto it = subset_coeff_map.find(var);
11475 if (it != subset_coeff_map.end()) {
11476 coeff += factor * it->second;
11477 if (coeff == 0)
continue;
11480 mutable_linear->
set_vars(new_size, var);
11481 mutable_linear->
set_coeffs(new_size, coeff);
11493void CpModelPresolver::DetectDominatedLinearConstraints() {
11494 if (time_limit_->LimitReached())
return;
11495 if (context_->ModelIsUnsat())
return;
11496 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11497 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11505 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
11506 int size()
const {
return static_cast<int>(proto_.constraints().size()); }
11507 absl::Span<const int> operator[](
int c)
const {
11508 return absl::MakeSpan(proto_.constraints(c).linear().vars());
11512 const CpModelProto& proto_;
11514 Storage storage(context_->working_model);
11516 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11520 std::vector<int> constraint_indices_to_clean;
11524 absl::flat_hash_map<int, Domain> cached_expr_domain;
11526 const int num_constraints = context_->working_model->constraints().size();
11527 for (
int c = 0;
c < num_constraints; ++
c) {
11528 const ConstraintProto& ct = context_->working_model->constraints(c);
11533 if (!ct.enforcement_literal().empty()) {
11534 if (ct.linear().vars().size() < 3)
continue;
11537 if (!LinearConstraintIsClean(ct.linear())) {
11544 detector.AddPotentialSet(c);
11546 const auto [min_activity, max_activity] =
11547 context_->ComputeMinMaxActivity(ct.linear());
11548 cached_expr_domain[
c] = Domain(min_activity, max_activity);
11551 int64_t num_inclusions = 0;
11552 absl::flat_hash_map<int, int64_t> coeff_map;
11553 detector.DetectInclusions([&](
int subset_c,
int superset_c) {
11557 const ConstraintProto& subset_ct =
11558 context_->working_model->constraints(subset_c);
11559 const LinearConstraintProto& subset_lin = subset_ct.linear();
11561 detector.IncreaseWorkDone(subset_lin.vars().size());
11562 for (
int i = 0;
i < subset_lin.vars().size(); ++
i) {
11563 coeff_map[subset_lin.vars(
i)] = subset_lin.coeffs(
i);
11569 bool perfect_match =
true;
11572 int64_t factor = 0;
11573 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
11574 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
11580 const ConstraintProto& superset_ct =
11581 context_->working_model->constraints(superset_c);
11582 const LinearConstraintProto& superset_lin = superset_ct.linear();
11583 int64_t diff_min_activity = 0;
11584 int64_t diff_max_activity = 0;
11585 detector.IncreaseWorkDone(superset_lin.vars().size());
11586 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
11587 const int var = superset_lin.vars(
i);
11588 int64_t coeff = superset_lin.coeffs(
i);
11589 const auto it = coeff_map.find(var);
11591 if (it != coeff_map.end()) {
11592 const int64_t subset_coeff = it->second;
11594 const int64_t div = coeff / subset_coeff;
11596 min_pos_factor = std::min(div, min_pos_factor);
11598 max_neg_factor = std::max(div, max_neg_factor);
11601 if (perfect_match) {
11602 if (coeff % subset_coeff == 0) {
11606 }
else if (factor != div) {
11607 perfect_match =
false;
11610 perfect_match =
false;
11615 coeff -= subset_coeff;
11617 if (coeff == 0)
continue;
11618 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
11619 &diff_max_activity);
11622 const Domain diff_domain(diff_min_activity, diff_max_activity);
11633 if (subset_ct.enforcement_literal().empty()) {
11634 const Domain implied_superset_domain =
11635 subset_rhs.AdditionWith(diff_domain)
11636 .IntersectionWith(cached_expr_domain[superset_c]);
11637 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
11638 context_->UpdateRuleStats(
11639 "linear inclusion: redundant containing constraint");
11640 context_->working_model->mutable_constraints(superset_c)->Clear();
11641 constraint_indices_to_clean.push_back(superset_c);
11642 detector.StopProcessingCurrentSuperset();
11648 if (superset_ct.enforcement_literal().empty()) {
11649 const Domain implied_subset_domain =
11650 superset_rhs.AdditionWith(diff_domain.Negation())
11651 .IntersectionWith(cached_expr_domain[subset_c]);
11652 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
11653 context_->UpdateRuleStats(
11654 "linear inclusion: redundant included constraint");
11655 context_->working_model->mutable_constraints(subset_c)->Clear();
11656 constraint_indices_to_clean.push_back(subset_c);
11657 detector.StopProcessingCurrentSubset();
11666 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().
empty()) {
11667 const int64_t best_factor =
11668 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
11676 bool is_tigher =
true;
11677 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
11678 max_neg_factor != std::numeric_limits<int64_t>::min()) {
11679 int64_t min_before = 0;
11680 int64_t max_before = 0;
11681 int64_t min_after =
CapProd(best_factor, subset_rhs.FixedValue());
11682 int64_t max_after = min_after;
11683 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
11684 const int var = superset_lin.vars(
i);
11685 const auto it = coeff_map.find(var);
11686 if (it == coeff_map.end())
continue;
11688 const int64_t coeff_before = superset_lin.coeffs(
i);
11689 const int64_t coeff_after = coeff_before - best_factor * it->second;
11690 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
11692 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
11695 is_tigher = min_after >= min_before && max_after <= max_before;
11698 context_->UpdateRuleStats(
"linear inclusion: sparsify superset");
11699 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
11700 context_->working_model->mutable_constraints(superset_c)
11701 ->mutable_linear());
11702 constraint_indices_to_clean.push_back(superset_c);
11703 detector.StopProcessingCurrentSuperset();
11710 if (perfect_match && subset_ct.enforcement_literal().empty() &&
11711 superset_ct.enforcement_literal().empty()) {
11712 CHECK_NE(factor, 0);
11718 auto* mutable_linear = temp_ct_.mutable_linear();
11719 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
11720 const int var = superset_lin.vars(
i);
11721 const int64_t coeff = superset_lin.coeffs(
i);
11722 const auto it = coeff_map.find(var);
11723 if (it != coeff_map.end())
continue;
11728 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
11730 PropagateDomainsInLinear(-1, &temp_ct_);
11731 if (context_->ModelIsUnsat()) detector.Stop();
11733 if (superset_rhs.IsFixed()) {
11734 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
11737 context_->UpdateRuleStats(
11738 "linear inclusion: subset + singleton is equality");
11739 context_->working_model->mutable_constraints(subset_c)->Clear();
11740 constraint_indices_to_clean.push_back(subset_c);
11741 detector.StopProcessingCurrentSubset();
11746 context_->UpdateRuleStats(
11747 "TODO linear inclusion: superset is equality");
11752 for (
const int c : constraint_indices_to_clean) {
11753 context_->UpdateConstraintVariableUsage(c);
11756 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
11757 timer.AddCounter(
"relevant_constraints", detector.num_potential_supersets());
11758 timer.AddCounter(
"num_inclusions", num_inclusions);
11759 timer.AddCounter(
"num_redundant", constraint_indices_to_clean.size());
11764bool CpModelPresolver::RemoveCommonPart(
11765 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
11766 absl::Span<
const std::pair<int, int64_t>> block,
11770 int64_t offset = 0;
11775 int definiting_equation = -1;
11776 for (
const auto [c, multiple] : block) {
11777 const ConstraintProto& ct = context_->working_model->constraints(c);
11778 if (std::abs(multiple) != 1)
continue;
11779 if (!IsLinearEqualityConstraint(ct))
continue;
11780 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1)
continue;
11782 context_->UpdateRuleStats(
11783 "linear matrix: defining equation for common rectangle");
11784 definiting_equation =
c;
11788 const int num_terms = ct.linear().vars().size();
11789 for (
int k = 0; k < num_terms; ++k) {
11790 if (common_var_coeff_map.contains(ct.linear().vars(k)))
continue;
11791 new_var = ct.linear().vars(k);
11792 coeff = ct.linear().coeffs(k);
11795 CHECK_NE(coeff, 0);
11799 gcd = -multiple * coeff;
11800 offset = multiple * ct.linear().domain(0);
11805 if (definiting_equation == -1) {
11807 int64_t min_activity = 0;
11808 int64_t max_activity = 0;
11809 tmp_terms_.clear();
11810 std::vector<std::pair<int, int64_t>> common_part;
11811 for (
const auto [var, coeff] : common_var_coeff_map) {
11812 common_part.push_back({var, coeff});
11813 gcd = std::gcd(gcd, std::abs(coeff));
11814 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
11815 tmp_terms_.push_back({var, coeff});
11819 min_activity += coeff * context_->MinOf(var);
11820 max_activity += coeff * context_->MaxOf(var);
11822 min_activity += coeff * context_->MaxOf(var);
11823 max_activity += coeff * context_->MinOf(var);
11831 if (!tmp_terms_.empty()) {
11832 min_activity += helper->ComputeMinActivity(tmp_terms_);
11833 max_activity += helper->ComputeMaxActivity(tmp_terms_);
11837 min_activity /= gcd;
11838 max_activity /= gcd;
11839 for (
int i = 0;
i < common_part.size(); ++
i) {
11840 common_part[
i].second /= gcd;
11845 std::sort(common_part.begin(), common_part.end());
11846 new_var = context_->NewIntVarWithDefinition(
11847 Domain(min_activity, max_activity), common_part);
11848 if (new_var == -1)
return false;
11852 for (
const auto [c, multiple] : block) {
11853 if (c == definiting_equation)
continue;
11855 auto* mutable_linear =
11856 context_->working_model->mutable_constraints(c)->mutable_linear();
11857 const int num_terms = mutable_linear->
vars().size();
11859 bool new_var_already_seen =
false;
11860 for (
int k = 0; k < num_terms; ++k) {
11861 if (common_var_coeff_map.contains(mutable_linear->
vars(k))) {
11862 CHECK_EQ(common_var_coeff_map.at(mutable_linear->
vars(k)) * multiple,
11863 mutable_linear->
coeffs(k));
11868 int64_t new_coeff = mutable_linear->
coeffs(k);
11869 if (mutable_linear->
vars(k) == new_var) {
11870 new_var_already_seen =
true;
11871 new_coeff += gcd * multiple;
11872 if (new_coeff == 0)
continue;
11875 mutable_linear->
set_vars(new_size, mutable_linear->
vars(k));
11876 mutable_linear->
set_coeffs(new_size, new_coeff);
11881 if (!new_var_already_seen) {
11882 mutable_linear->
add_vars(new_var);
11887 .AdditionWith(Domain(-offset * multiple)),
11890 context_->UpdateConstraintVariableUsage(c);
11898 const int num_terms = ct.linear().vars().size();
11899 for (
int k = 0; k < num_terms; ++k) {
11900 if (ct.linear().vars(k) == var)
return ct.linear().coeffs(k);
11905int64_t ComputeNonZeroReduction(
size_t block_size,
size_t common_part_size) {
11908 return static_cast<int64_t
>(block_size * (common_part_size - 1) -
11909 common_part_size - 1);
11917void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
11919 if (time_limit_->LimitReached())
return;
11920 if (context_->ModelIsUnsat())
return;
11921 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11922 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11924 int64_t num_blocks = 0;
11925 int64_t nz_reduction = 0;
11926 std::vector<int> amo_cts;
11927 std::vector<int> amo_literals;
11929 std::vector<int> common_part;
11930 std::vector<int> best_common_part;
11932 std::vector<bool> common_part_sign;
11933 std::vector<bool> best_common_part_sign;
11936 absl::flat_hash_map<int, bool> var_in_amo;
11938 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
11940 if (helper->NumAmoForVariable(x) == 0)
continue;
11941 if (time_limit_->LimitReached())
break;
11942 if (timer.WorkLimitIsReached())
break;
11945 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11946 for (
const int c : context_->VarToConstraints(x)) {
11947 if (c < 0)
continue;
11948 const ConstraintProto& ct = context_->working_model->constraints(c);
11950 amo_cts.push_back(c);
11952 amo_cts.push_back(c);
11955 if (amo_cts.empty())
continue;
11964 var_in_amo.clear();
11965 amo_literals.clear();
11966 common_part.clear();
11967 common_part_sign.clear();
11971 std::sort(amo_cts.begin(), amo_cts.end());
11972 const int random_c =
11973 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
11974 base_ct_index = amo_cts[random_c];
11975 const ConstraintProto& ct =
11976 context_->working_model->constraints(base_ct_index);
11978 ? ct.at_most_one().literals()
11979 : ct.exactly_one().literals();
11980 timer.TrackSimpleLoop(5 * literals.size());
11981 for (
const int literal : literals) {
11982 amo_literals.push_back(literal);
11985 const auto [_, inserted] =
11991 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11995 std::vector<int> block_cts;
11996 std::vector<int> linear_cts;
11997 int max_common_part = 0;
11998 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11999 for (
const int c : context_->VarToConstraints(x)) {
12000 if (c < 0)
continue;
12001 const ConstraintProto& ct = context_->working_model->constraints(c);
12003 const int num_terms = ct.linear().vars().size();
12004 if (num_terms < 2)
continue;
12006 timer.TrackSimpleLoop(2 * num_terms);
12007 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
12008 if (x_coeff == 0)
continue;
12010 int num_in_amo = 0;
12011 for (
int k = 0; k < num_terms; ++k) {
12012 const int var = ct.linear().vars(k);
12017 const auto it = var_in_amo.find(var);
12018 if (it == var_in_amo.end())
continue;
12019 int64_t coeff = ct.linear().coeffs(k);
12020 if (!it->second) coeff = -coeff;
12021 if (coeff != x_coeff)
continue;
12024 if (num_in_amo < 2)
continue;
12026 max_common_part += num_in_amo;
12027 if (num_in_amo == common_part.size()) {
12029 block_cts.push_back(c);
12031 linear_cts.push_back(c);
12034 if (linear_cts.empty() && block_cts.empty())
continue;
12035 if (max_common_part < 100)
continue;
12039 best_common_part = common_part;
12040 best_common_part_sign = common_part_sign;
12041 int best_block_size = block_cts.size();
12042 int best_saved_nz =
12043 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
12046 std::sort(block_cts.begin(), block_cts.end());
12047 std::sort(linear_cts.begin(), linear_cts.end());
12051 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
12052 for (
const int c : linear_cts) {
12053 const ConstraintProto& ct = context_->working_model->constraints(c);
12054 const int num_terms = ct.linear().vars().size();
12055 timer.TrackSimpleLoop(2 * num_terms);
12056 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
12057 CHECK_NE(x_coeff, 0);
12059 common_part.clear();
12060 common_part_sign.clear();
12061 for (
int k = 0; k < num_terms; ++k) {
12062 const int var = ct.linear().vars(k);
12063 const auto it = var_in_amo.find(var);
12064 if (it == var_in_amo.end())
continue;
12065 int64_t coeff = ct.linear().coeffs(k);
12066 if (!it->second) coeff = -coeff;
12067 if (coeff != x_coeff)
continue;
12068 common_part.push_back(var);
12069 common_part_sign.push_back(it->second);
12071 if (common_part.size() < 2)
continue;
12074 block_cts.push_back(c);
12075 if (common_part.size() < var_in_amo.size()) {
12076 var_in_amo.clear();
12077 for (
int i = 0;
i < common_part.size(); ++
i) {
12078 var_in_amo[common_part[
i]] = common_part_sign[
i];
12085 const int64_t saved_nz =
12086 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
12087 if (saved_nz > best_saved_nz) {
12088 best_block_size = block_cts.size();
12089 best_saved_nz = saved_nz;
12090 best_common_part = common_part;
12091 best_common_part_sign = common_part_sign;
12094 if (best_saved_nz < 100)
continue;
12099 block_cts.resize(best_block_size);
12100 var_in_amo.clear();
12101 for (
int i = 0;
i < best_common_part.size(); ++
i) {
12102 var_in_amo[best_common_part[
i]] = best_common_part_sign[
i];
12106 nz_reduction += best_saved_nz;
12107 context_->UpdateRuleStats(
"linear matrix: common amo rectangle");
12111 for (
const int lit : amo_literals) {
12112 if (!var_in_amo.contains(
PositiveRef(lit)))
continue;
12113 amo_literals[new_size++] = lit;
12115 if (new_size == amo_literals.size()) {
12116 const ConstraintProto& ct =
12117 context_->working_model->constraints(base_ct_index);
12119 context_->UpdateRuleStats(
"TODO linear matrix: constant rectangle!");
12121 context_->UpdateRuleStats(
12122 "TODO linear matrix: reuse defining constraint");
12124 }
else if (new_size + 1 == amo_literals.size()) {
12125 const ConstraintProto& ct =
12126 context_->working_model->constraints(base_ct_index);
12128 context_->UpdateRuleStats(
"TODO linear matrix: reuse exo constraint");
12131 amo_literals.resize(new_size);
12134 const int new_var = context_->NewBoolVarWithClause(amo_literals);
12137 context_->working_model->add_constraints()->mutable_exactly_one();
12138 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
12139 for (
const int lit : amo_literals) {
12140 new_exo->add_literals(lit);
12143 context_->UpdateNewConstraintsVariableUsage();
12148 ConstraintProto* ct =
12149 context_->working_model->mutable_constraints(base_ct_index);
12150 auto* mutable_literals =
12152 ? ct->mutable_at_most_one()->mutable_literals()
12153 : ct->mutable_exactly_one()->mutable_literals();
12155 for (
const int lit : *mutable_literals) {
12156 if (var_in_amo.contains(
PositiveRef(lit)))
continue;
12157 (*mutable_literals)[new_size++] = lit;
12159 (*mutable_literals)[new_size++] = new_var;
12160 mutable_literals->Truncate(new_size);
12161 context_->UpdateConstraintVariableUsage(base_ct_index);
12165 for (
const int c : block_cts) {
12166 auto* mutable_linear =
12167 context_->working_model->mutable_constraints(c)->mutable_linear();
12170 int64_t offset = 0;
12171 int64_t coeff_x = 0;
12174 const int num_terms = mutable_linear->
vars().size();
12175 for (
int k = 0; k < num_terms; ++k) {
12176 const int var = mutable_linear->
vars(k);
12178 int64_t coeff = mutable_linear->
coeffs(k);
12179 const auto it = var_in_amo.find(var);
12180 if (it != var_in_amo.end()) {
12189 if (coeff_x == 0) coeff_x = coeff;
12190 CHECK_EQ(coeff, coeff_x);
12193 mutable_linear->
set_vars(new_size, mutable_linear->
vars(k));
12194 mutable_linear->
set_coeffs(new_size, coeff);
12199 mutable_linear->
set_vars(new_size, new_var);
12200 mutable_linear->
set_coeffs(new_size, coeff_x);
12210 context_->UpdateConstraintVariableUsage(c);
12214 timer.AddCounter(
"blocks", num_blocks);
12215 timer.AddCounter(
"saved_nz", nz_reduction);
12216 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12220void CpModelPresolver::FindBigVerticalLinearOverlap(
12222 if (time_limit_->LimitReached())
return;
12223 if (context_->ModelIsUnsat())
return;
12224 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
12225 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12227 int64_t num_blocks = 0;
12228 int64_t nz_reduction = 0;
12229 absl::flat_hash_map<int, int64_t> coeff_map;
12230 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
12231 if (timer.WorkLimitIsReached())
break;
12233 bool in_enforcement =
false;
12234 std::vector<int> linear_cts;
12235 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
12236 for (
const int c : context_->VarToConstraints(x)) {
12237 if (c < 0)
continue;
12238 const ConstraintProto& ct = context_->working_model->constraints(c);
12241 const int num_terms = ct.linear().vars().size();
12242 if (num_terms < 2)
continue;
12243 bool is_canonical =
true;
12244 timer.TrackSimpleLoop(num_terms);
12245 for (
int k = 0; k < num_terms; ++k) {
12247 is_canonical =
false;
12251 if (!is_canonical)
continue;
12254 timer.TrackSimpleLoop(ct.enforcement_literal().size());
12255 for (
const int lit : ct.enforcement_literal()) {
12257 in_enforcement =
true;
12264 if (in_enforcement)
continue;
12265 linear_cts.push_back(c);
12271 if (in_enforcement)
continue;
12272 if (linear_cts.size() < 10)
continue;
12275 std::sort(linear_cts.begin(), linear_cts.end());
12276 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
12285 std::vector<std::pair<int, int64_t>> block;
12286 std::vector<std::pair<int, int64_t>> common_part;
12287 for (
const int c : linear_cts) {
12288 const ConstraintProto& ct = context_->working_model->constraints(c);
12289 const int num_terms = ct.linear().vars().size();
12290 timer.TrackSimpleLoop(num_terms);
12293 const int64_t x_coeff = FindVarCoeff(x, ct);
12294 if (x_coeff == 0)
continue;
12296 if (block.empty()) {
12299 for (
int k = 0; k < num_terms; ++k) {
12300 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
12302 if (coeff_map.size() < 2)
continue;
12303 block.push_back({
c, x_coeff});
12308 const int64_t gcd =
12309 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
12310 const int64_t multiple_base = coeff_map.at(x) / gcd;
12311 const int64_t multiple_ct = x_coeff / gcd;
12312 common_part.clear();
12313 for (
int k = 0; k < num_terms; ++k) {
12314 const int64_t coeff = ct.linear().coeffs(k);
12315 if (coeff % multiple_ct != 0)
continue;
12317 const auto it = coeff_map.find(ct.linear().vars(k));
12318 if (it == coeff_map.end())
continue;
12319 if (it->second % multiple_base != 0)
continue;
12320 if (it->second / multiple_base != coeff / multiple_ct)
continue;
12322 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
12326 if (common_part.size() < 2)
continue;
12329 block.push_back({
c, x_coeff});
12331 for (
const auto [var, coeff] : common_part) {
12332 coeff_map[var] = coeff;
12337 const int64_t saved_nz =
12338 ComputeNonZeroReduction(block.size(), coeff_map.size());
12339 if (saved_nz < 30)
continue;
12342 const int64_t base_x = coeff_map.at(x);
12343 for (
auto& [c, multipier] : block) {
12344 CHECK_EQ(multipier % base_x, 0);
12345 multipier /= base_x;
12349 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
12351 nz_reduction += saved_nz;
12352 context_->UpdateRuleStats(
"linear matrix: common vertical rectangle");
12355 timer.AddCounter(
"blocks", num_blocks);
12356 timer.AddCounter(
"saved_nz", nz_reduction);
12357 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12367void CpModelPresolver::FindBigHorizontalLinearOverlap(
12369 if (time_limit_->LimitReached())
return;
12370 if (context_->ModelIsUnsat())
return;
12371 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
12372 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12374 const int num_constraints = context_->working_model->constraints_size();
12375 std::vector<std::pair<int, int>> to_sort;
12376 for (
int c = 0;
c < num_constraints; ++
c) {
12377 const ConstraintProto& ct = context_->working_model->constraints(c);
12379 const int size = ct.linear().vars().size();
12380 if (size < 5)
continue;
12381 to_sort.push_back({-size,
c});
12383 std::sort(to_sort.begin(), to_sort.end());
12385 std::vector<int> sorted_linear;
12386 for (
int i = 0;
i < to_sort.size(); ++
i) {
12387 sorted_linear.push_back(to_sort[
i].second);
12392 std::vector<int> var_to_coeff_non_zeros;
12393 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
12396 int64_t num_blocks = 0;
12397 int64_t nz_reduction = 0;
12398 for (
int i = 0;
i < sorted_linear.size(); ++
i) {
12399 const int c = sorted_linear[
i];
12400 if (c < 0)
continue;
12401 if (timer.WorkLimitIsReached())
break;
12403 for (
const int var : var_to_coeff_non_zeros) {
12404 var_to_coeff[var] = 0;
12406 var_to_coeff_non_zeros.clear();
12408 const ConstraintProto& ct = context_->working_model->constraints(c);
12409 const int num_terms = ct.linear().vars().size();
12410 timer.TrackSimpleLoop(num_terms);
12411 for (
int k = 0; k < num_terms; ++k) {
12412 const int var = ct.linear().vars(k);
12413 var_to_coeff[var] = ct.linear().coeffs(k);
12414 var_to_coeff_non_zeros.push_back(var);
12422 int saved_nz = 100;
12423 std::vector<int> used_sorted_linear = {
i};
12424 std::vector<std::pair<int, int64_t>> block = {{
c, 1}};
12425 std::vector<std::pair<int, int64_t>> common_part;
12426 std::vector<std::pair<int, int>> old_matches;
12428 for (
int j = 0; j < sorted_linear.size(); ++j) {
12429 if (
i == j)
continue;
12430 const int other_c = sorted_linear[j];
12431 if (other_c < 0)
continue;
12432 const ConstraintProto& ct = context_->working_model->constraints(other_c);
12435 const int num_terms = ct.linear().vars().size();
12436 const int best_saved_nz =
12437 ComputeNonZeroReduction(block.size() + 1, num_terms);
12438 if (best_saved_nz <= saved_nz)
break;
12441 timer.TrackSimpleLoop(num_terms);
12442 common_part.clear();
12443 for (
int k = 0; k < num_terms; ++k) {
12444 const int var = ct.linear().vars(k);
12445 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
12446 common_part.push_back({var, ct.linear().coeffs(k)});
12455 const int64_t new_saved_nz =
12456 ComputeNonZeroReduction(block.size() + 1, common_part.size());
12457 if (new_saved_nz > saved_nz) {
12458 saved_nz = new_saved_nz;
12459 used_sorted_linear.push_back(j);
12460 block.push_back({other_c, 1});
12464 for (
const int var : var_to_coeff_non_zeros) {
12465 var_to_coeff[var] = 0;
12467 var_to_coeff_non_zeros.clear();
12468 for (
const auto [var, coeff] : common_part) {
12469 var_to_coeff[var] = coeff;
12470 var_to_coeff_non_zeros.push_back(var);
12473 if (common_part.size() > 1) {
12474 old_matches.push_back({j, common_part.size()});
12481 if (block.size() > 1) {
12483 const int match_size = var_to_coeff_non_zeros.size();
12484 for (
const auto [index, old_match_size] : old_matches) {
12485 if (old_match_size < match_size)
continue;
12487 int new_match_size = 0;
12488 const int other_c = sorted_linear[index];
12489 const ConstraintProto& ct =
12490 context_->working_model->constraints(other_c);
12491 const int num_terms = ct.linear().vars().size();
12492 for (
int k = 0; k < num_terms; ++k) {
12493 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
12497 if (new_match_size == match_size) {
12498 context_->UpdateRuleStats(
12499 "linear matrix: common horizontal rectangle extension");
12500 used_sorted_linear.push_back(index);
12501 block.push_back({other_c, 1});
12507 absl::flat_hash_map<int, int64_t> coeff_map;
12508 for (
const int var : var_to_coeff_non_zeros) {
12509 coeff_map[var] = var_to_coeff[var];
12511 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
12514 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
12515 context_->UpdateRuleStats(
"linear matrix: common horizontal rectangle");
12516 for (
const int i : used_sorted_linear) sorted_linear[
i] = -1;
12520 timer.AddCounter(
"blocks", num_blocks);
12521 timer.AddCounter(
"saved_nz", nz_reduction);
12522 timer.AddCounter(
"linears", sorted_linear.size());
12523 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12531void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
12532 if (time_limit_->LimitReached())
return;
12533 if (context_->ModelIsUnsat())
return;
12538 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12541 std::vector<std::pair<int, int>> to_sort;
12542 const int num_constraints = context_->working_model->constraints_size();
12543 for (
int c = 0;
c < num_constraints; ++
c) {
12544 const ConstraintProto& ct = context_->working_model->constraints(c);
12545 if (!IsLinearEqualityConstraint(ct))
continue;
12546 if (ct.linear().vars().size() <= 2)
continue;
12549 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
12553 to_sort.push_back({ct.linear().vars().size(),
c});
12555 std::sort(to_sort.begin(), to_sort.end());
12559 std::vector<int> var_to_clear;
12560 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
12561 const int num_variables = context_->working_model->variables_size();
12562 var_to_ct_coeffs_.resize(num_variables);
12565 int num_tested_pairs = 0;
12566 int num_affine_relations = 0;
12567 for (
int start = 0; start < to_sort.size(); start =
end) {
12570 const int length = to_sort[start].first;
12571 for (;
end < to_sort.size(); ++
end) {
12572 if (to_sort[
end].first != length)
break;
12574 const int span_size =
end - start;
12575 if (span_size == 1)
continue;
12578 for (
const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
12579 var_to_clear.clear();
12580 for (
int i = start;
i <
end; ++
i) {
12581 const int c = to_sort[
i].second;
12582 const LinearConstraintProto& lin =
12583 context_->working_model->constraints(c).linear();
12585 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
12586 const int var = lin.vars(index);
12587 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
12588 var_to_ct_coeffs_[var].push_back({
c, lin.coeffs(index)});
12597 for (
int i1 = start; i1 <
end; ++i1) {
12598 if (timer.WorkLimitIsReached())
break;
12599 const int c1 = to_sort[i1].second;
12600 const LinearConstraintProto& lin1 =
12601 context_->working_model->constraints(c1).linear();
12603 for (
int i = 0; !skip &&
i < lin1.vars().size(); ++
i) {
12604 for (
const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(
i)]) {
12605 if (c2 == c1)
continue;
12608 if (coeff2 != lin1.coeffs(
i))
continue;
12609 if (timer.WorkLimitIsReached())
break;
12612 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12614 const LinearConstraintProto& lin2 =
12615 context_->working_model->constraints(c2).linear();
12616 if (lin2.vars().size() != length)
continue;
12621 timer.TrackSimpleLoop(length);
12623 ++num_tested_pairs;
12628 auto* to_modify = context_->working_model->mutable_constraints(c1);
12630 -1, context_->working_model->constraints(c2), to_modify)) {
12637 DCHECK_LE(to_modify->linear().vars().size(), 2);
12639 ++num_affine_relations;
12640 context_->UpdateRuleStats(
12641 "linear: advanced affine relation from 2 constraints");
12644 DivideLinearByGcd(to_modify);
12645 PresolveSmallLinear(to_modify);
12646 context_->UpdateConstraintVariableUsage(c1);
12655 timer.AddCounter(
"num_tested_pairs", num_tested_pairs);
12656 timer.AddCounter(
"found", num_affine_relations);
12657 DCHECK(context_->ConstraintVariableUsageIsConsistent());
12660void CpModelPresolver::ExtractEncodingFromLinear() {
12661 if (time_limit_->LimitReached())
return;
12662 if (context_->ModelIsUnsat())
return;
12663 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
12664 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12667 std::vector<int> relevant_constraints;
12668 CompactVectorVector<int> storage;
12670 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
12676 std::vector<int> vars;
12677 const int num_constraints = context_->working_model->constraints().size();
12678 for (
int c = 0;
c < num_constraints; ++
c) {
12679 const ConstraintProto& ct = context_->working_model->constraints(c);
12680 switch (ct.constraint_case()) {
12683 for (
const int ref : ct.at_most_one().literals()) {
12686 relevant_constraints.push_back(c);
12687 detector.AddPotentialSuperset(storage.Add(vars));
12692 for (
const int ref : ct.exactly_one().literals()) {
12695 relevant_constraints.push_back(c);
12696 detector.AddPotentialSuperset(storage.Add(vars));
12701 if (!IsLinearEqualityConstraint(ct))
continue;
12705 bool is_candidate =
true;
12706 int num_integers = 0;
12708 const int num_terms = ct.linear().vars().size();
12709 for (
int i = 0;
i < num_terms; ++
i) {
12710 const int ref = ct.linear().vars(
i);
12711 if (context_->CanBeUsedAsLiteral(ref)) {
12715 if (std::abs(ct.linear().coeffs(
i)) != 1) {
12716 is_candidate =
false;
12719 if (num_integers == 2) {
12720 is_candidate =
false;
12728 if (is_candidate && num_integers == 1 && vars.size() > 1) {
12729 relevant_constraints.push_back(c);
12730 detector.AddPotentialSubset(storage.Add(vars));
12740 int64_t num_exactly_one_encodings = 0;
12741 int64_t num_at_most_one_encodings = 0;
12742 int64_t num_literals = 0;
12743 int64_t num_unique_terms = 0;
12744 int64_t num_multiple_terms = 0;
12746 detector.DetectInclusions([&](
int subset,
int superset) {
12747 const int subset_c = relevant_constraints[subset];
12748 const int superset_c = relevant_constraints[superset];
12749 const ConstraintProto& superset_ct =
12750 context_->working_model->constraints(superset_c);
12752 ++num_at_most_one_encodings;
12754 ++num_exactly_one_encodings;
12756 num_literals += storage[subset].size();
12757 context_->UpdateRuleStats(
"encoding: extracted from linear");
12759 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
12760 &num_multiple_terms)) {
12764 detector.StopProcessingCurrentSubset();
12767 timer.AddCounter(
"potential_supersets", detector.num_potential_supersets());
12768 timer.AddCounter(
"potential_subsets", detector.num_potential_subsets());
12769 timer.AddCounter(
"amo_encodings", num_at_most_one_encodings);
12770 timer.AddCounter(
"exo_encodings", num_exactly_one_encodings);
12771 timer.AddCounter(
"unique_terms", num_unique_terms);
12772 timer.AddCounter(
"multiple_terms", num_multiple_terms);
12773 timer.AddCounter(
"literals", num_literals);
12787void CpModelPresolver::LookAtVariableWithDegreeTwo(
int var) {
12789 CHECK(context_->ConstraintVariableGraphIsUpToDate());
12790 if (context_->ModelIsUnsat())
return;
12791 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12792 if (context_->IsFixed(var))
return;
12793 if (!context_->ModelIsExpanded())
return;
12794 if (!context_->CanBeUsedAsLiteral(var))
return;
12801 if (context_->VarToConstraints(var).size() != 2)
return;
12803 bool abort =
false;
12805 Domain union_of_domain;
12806 int num_positive = 0;
12807 std::vector<int> constraint_indices_to_remove;
12808 for (
const int c : context_->VarToConstraints(var)) {
12813 constraint_indices_to_remove.push_back(c);
12814 const ConstraintProto& ct = context_->working_model->constraints(c);
12815 if (ct.enforcement_literal().size() != 1 ||
12818 ct.linear().vars().size() != 1) {
12822 if (ct.enforcement_literal(0) == var) ++num_positive;
12823 if (ct_var != -1 &&
PositiveRef(ct.linear().vars(0)) != ct_var) {
12828 union_of_domain = union_of_domain.UnionWith(
12831 ? ct.linear().coeffs(0)
12832 : -ct.linear().coeffs(0)));
12835 if (num_positive != 1)
return;
12836 if (!context_->IntersectDomainWith(ct_var, union_of_domain))
return;
12838 context_->UpdateRuleStats(
"variables: removable enforcement literal");
12839 absl::c_sort(constraint_indices_to_remove);
12844 for (
const int c : constraint_indices_to_remove) {
12845 context_->NewMappingConstraint(context_->working_model->constraints(c),
12846 __FILE__, __LINE__);
12847 context_->working_model->mutable_constraints(c)->Clear();
12848 context_->UpdateConstraintVariableUsage(c);
12850 context_->MarkVariableAsRemoved(var);
12855absl::Span<const int> AtMostOneOrExactlyOneLiterals(
const ConstraintProto& ct) {
12857 return {ct.at_most_one().literals()};
12859 return {ct.exactly_one().literals()};
12865void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(
int var) {
12867 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12868 if (context_->ModelIsUnsat())
return;
12869 if (context_->IsFixed(var))
return;
12870 if (context_->VariableWasRemoved(var))
return;
12871 if (!context_->ModelIsExpanded())
return;
12872 if (!context_->CanBeUsedAsLiteral(var))
return;
12876 if (context_->VarToConstraints(var).size() != 3)
return;
12877 cost = context_->ObjectiveMap().at(var);
12879 if (context_->VarToConstraints(var).size() != 2)
return;
12887 for (
const int c : context_->VarToConstraints(var)) {
12888 if (c < 0)
continue;
12889 const ConstraintProto& ct = context_->working_model->constraints(c);
12902 if (c1 == -1 || c2 == -1)
return;
12906 if (c1 > c2) std::swap(c1, c2);
12915 context_->tmp_literals.clear();
12916 int c1_ref = std::numeric_limits<int>::min();
12917 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
12918 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1)
return;
12919 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
12923 context_->tmp_literals.push_back(lit);
12926 int c2_ref = std::numeric_limits<int>::min();
12927 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12928 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1)
return;
12929 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
12933 context_->tmp_literals.push_back(lit);
12936 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
12937 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
12942 int64_t cost_shift = 0;
12943 absl::Span<const int> literals;
12946 literals = ct1.exactly_one().literals();
12949 literals = ct2.exactly_one().literals();
12955 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12956 if (context_->params().keep_symmetry_in_presolve())
return;
12957 if (cost != 0 && context_->ObjectiveDomainIsConstraining())
return;
12961 literals = ct1.at_most_one().literals();
12964 literals = ct2.at_most_one().literals();
12968 if (!context_->ShiftCostInExactlyOne(literals, cost_shift))
return;
12969 DCHECK(!context_->ObjectiveMap().contains(var));
12970 context_->NewMappingConstraint(__FILE__, __LINE__)
12971 ->mutable_exactly_one()
12972 ->mutable_literals()
12973 ->Assign(literals.begin(), literals.end());
12976 const int new_ct_index = context_->working_model->constraints().size();
12977 ConstraintProto* new_ct = context_->working_model->add_constraints();
12980 for (
const int lit : context_->tmp_literals) {
12981 new_ct->mutable_exactly_one()->add_literals(lit);
12986 for (
const int lit : context_->tmp_literals) {
12987 new_ct->mutable_at_most_one()->add_literals(lit);
12991 context_->UpdateNewConstraintsVariableUsage();
12992 context_->working_model->mutable_constraints(c1)->Clear();
12993 context_->UpdateConstraintVariableUsage(c1);
12994 context_->working_model->mutable_constraints(c2)->Clear();
12995 context_->UpdateConstraintVariableUsage(c2);
12997 context_->UpdateRuleStats(
12998 "at_most_one: resolved two constraints with opposite literal");
12999 context_->MarkVariableAsRemoved(var);
13005 if (PresolveAtMostOrExactlyOne(new_ct)) {
13006 context_->UpdateConstraintVariableUsage(new_ct_index);
13020void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(
int var) {
13021 if (context_->ModelIsUnsat())
return;
13022 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
13023 if (context_->params().keep_symmetry_in_presolve())
return;
13024 if (context_->IsFixed(var))
return;
13025 if (context_->VariableWasRemoved(var))
return;
13026 if (context_->CanBeUsedAsLiteral(var))
return;
13031 const bool is_only_used_in_linear1 =
13032 context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var);
13033 const bool is_only_used_in_encoding =
13034 context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var);
13035 if (!is_only_used_in_encoding && is_only_used_in_linear1) {
13036 VariableEncodingLocalModel local_model;
13037 local_model.var = var;
13038 local_model.single_constraint_using_the_var_outside_the_local_model = -1;
13039 local_model.var_in_more_than_one_constraint_outside_the_local_model =
false;
13040 for (
const int c : context_->VarToConstraints(var)) {
13042 const ConstraintProto& ct = context_->working_model->constraints(c);
13044 ct.linear().vars().size() == 1 && ct.linear().vars(0) == var) {
13045 local_model.linear1_constraints.push_back(c);
13050 local_model.variable_coeff_in_objective =
13051 context_->ObjectiveMap().at(var);
13053 local_model.single_constraint_using_the_var_outside_the_local_model ==
13057 local_model.single_constraint_using_the_var_outside_the_local_model =
c;
13060 local_model.single_constraint_using_the_var_outside_the_local_model =
13062 local_model.var_in_more_than_one_constraint_outside_the_local_model =
13070 if (!is_only_used_in_encoding)
return;
13073 const int old_size = context_->working_model->constraints_size();
13075 for (
int c = old_size;
c < context_->working_model->constraints_size(); ++
c) {
13077 context_->UpdateConstraintVariableUsage(c);
13082void CpModelPresolver::TryToSimplifyDomain(
int var) {
13084 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
13085 if (context_->ModelIsUnsat())
return;
13086 if (context_->IsFixed(var))
return;
13087 if (context_->VariableWasRemoved(var))
return;
13088 if (context_->VariableIsNotUsedAnymore(var))
return;
13090 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13091 if (r.representative != var)
return;
13094 const Domain& domain = context_->DomainOf(var);
13097 if (domain.Size() == 2 && (domain.
Min() != 0 || domain.
Max() != 1)) {
13098 context_->CanonicalizeDomainOfSizeTwo(var);
13102 if (domain.NumIntervals() != domain.Size())
return;
13104 const int64_t var_min = domain.
Min();
13105 int64_t gcd = domain[1].start - var_min;
13106 for (
int index = 2; index < domain.NumIntervals(); ++index) {
13107 const ClosedInterval&
i = domain[index];
13108 DCHECK_EQ(
i.start,
i.end);
13109 const int64_t shifted_value =
i.start - var_min;
13110 DCHECK_GT(shifted_value, 0);
13112 gcd = std::gcd(gcd, shifted_value);
13113 if (gcd == 1)
break;
13115 if (gcd == 1)
return;
13118 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
13122void CpModelPresolver::EncodeAllAffineRelations() {
13123 int64_t num_added = 0;
13124 for (
int var = 0; var < context_->working_model->variables_size(); ++var) {
13125 if (context_->IsFixed(var))
continue;
13127 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13128 if (r.representative == var)
continue;
13133 if (context_->VariableIsNotUsedAnymore(var))
continue;
13134 if (!PresolveAffineRelationIfAny(var))
break;
13135 if (context_->VariableIsNotUsedAnymore(var))
continue;
13136 if (context_->IsFixed(var))
continue;
13139 ConstraintProto* ct = context_->working_model->add_constraints();
13140 auto* arg = ct->mutable_linear();
13141 arg->add_vars(var);
13142 arg->add_coeffs(1);
13143 arg->add_vars(r.representative);
13144 arg->add_coeffs(-r.coeff);
13145 arg->add_domain(r.offset);
13146 arg->add_domain(r.offset);
13147 context_->UpdateNewConstraintsVariableUsage();
13152 context_->RemoveAllVariablesFromAffineRelationConstraint();
13154 if (num_added > 0) {
13155 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
13160bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
13161 const AffineRelation::Relation r = context_->GetAffineRelation(var);
13162 if (r.representative == var)
return true;
13165 if (!context_->PropagateAffineRelation(var))
return false;
13171 if (context_->IsFixed(var))
return true;
13173 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
13178 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
13183bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
13184 std::deque<int>* queue) {
13186 if (context_->ModelIsUnsat())
return false;
13187 if (time_limit_->LimitReached())
return false;
13188 in_queue->resize(context_->working_model->constraints_size(),
false);
13189 const auto& vector_that_can_grow_during_iter =
13190 context_->modified_domains.PositionsSetAtLeastOnce();
13191 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
13192 const int v = vector_that_can_grow_during_iter[
i];
13193 context_->modified_domains.Clear(v);
13194 if (context_->VariableIsNotUsedAnymore(v))
continue;
13195 if (context_->ModelIsUnsat())
return false;
13196 if (!PresolveAffineRelationIfAny(v))
return false;
13197 if (context_->VariableIsNotUsedAnymore(v))
continue;
13199 TryToSimplifyDomain(v);
13202 if (context_->ModelIsUnsat())
return false;
13203 context_->UpdateNewConstraintsVariableUsage();
13205 if (!context_->CanonicalizeOneObjectiveVariable(v))
return false;
13207 in_queue->resize(context_->working_model->constraints_size(),
false);
13208 const int size_before = queue->size();
13209 for (
const int c : context_->VarToConstraints(v)) {
13210 if (c >= 0 && !(*in_queue)[c]) {
13211 (*in_queue)[
c] =
true;
13212 queue->push_back(c);
13218 std::sort(queue->begin() + size_before, queue->end());
13220 context_->modified_domains.ResetAllToFalse();
13221 return !queue->empty();
13224void CpModelPresolver::PresolveToFixPoint() {
13225 if (time_limit_->LimitReached())
return;
13226 if (context_->ModelIsUnsat())
return;
13227 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13230 int num_dominance_tests = 0;
13231 int num_dual_strengthening = 0;
13234 const int64_t max_num_operations =
13235 context_->params().debug_max_num_presolve_operations() > 0
13236 ? context_->params().debug_max_num_presolve_operations()
13237 : std::numeric_limits<int64_t>::max();
13242 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
13245 std::vector<bool> in_queue(context_->working_model->constraints_size(),
13247 std::deque<int> queue;
13248 for (
int c = 0;
c < in_queue.size(); ++
c) {
13249 if (context_->working_model->constraints(c).constraint_case() !=
13251 in_queue[
c] =
true;
13252 queue.push_back(c);
13260 if (context_->params().permute_presolve_constraint_order()) {
13261 std::shuffle(queue.begin(), queue.end(), *context_->random());
13263 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
13264 const int score_a = context_->ConstraintToVars(a).size();
13265 const int score_b = context_->ConstraintToVars(b).size();
13266 return score_a < score_b || (score_a == score_b && a < b);
13274 constexpr int kMaxNumLoops = 1000;
13275 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
13276 if (time_limit_->LimitReached())
break;
13277 if (context_->ModelIsUnsat())
break;
13278 if (context_->num_presolve_operations > max_num_operations)
break;
13281 while (!queue.empty() && !context_->ModelIsUnsat()) {
13282 if (time_limit_->LimitReached())
break;
13283 if (context_->num_presolve_operations > max_num_operations)
break;
13284 const int c = queue.front();
13285 in_queue[
c] =
false;
13288 const int old_num_constraint =
13289 context_->working_model->constraints_size();
13291 if (context_->ModelIsUnsat()) {
13293 logger_,
"Unsat after presolving constraint #", c,
13294 " (warning, dump might be inconsistent): ",
13299 const int new_num_constraints =
13300 context_->working_model->constraints_size();
13301 if (new_num_constraints > old_num_constraint) {
13302 context_->UpdateNewConstraintsVariableUsage();
13303 in_queue.resize(new_num_constraints,
true);
13304 for (
int c = old_num_constraint;
c < new_num_constraints; ++
c) {
13305 queue.push_back(c);
13312 context_->UpdateConstraintVariableUsage(c);
13316 if (context_->ModelIsUnsat())
return;
13318 in_queue.resize(context_->working_model->constraints_size(),
false);
13319 const auto& vector_that_can_grow_during_iter =
13320 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
13321 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
13322 const int v = vector_that_can_grow_during_iter[
i];
13323 if (context_->VariableIsNotUsedAnymore(v))
continue;
13328 context_->var_with_reduced_small_degree.Clear(v);
13332 if (context_->ModelIsUnsat())
return;
13333 if (!PresolveAffineRelationIfAny(v))
return;
13335 const int degree = context_->VarToConstraints(v).size();
13336 if (degree == 0)
continue;
13337 if (degree == 2) LookAtVariableWithDegreeTwo(v);
13338 if (degree == 2 || degree == 3) {
13340 ProcessVariableInTwoAtMostOrExactlyOne(v);
13341 in_queue.resize(context_->working_model->constraints_size(),
false);
13348 if (degree != 1)
continue;
13349 const int c = *context_->VarToConstraints(v).begin();
13350 if (c < 0)
continue;
13355 if (var_constraint_pair_already_called.contains(
13356 std::pair<int, int>(v, c))) {
13359 var_constraint_pair_already_called.insert({v,
c});
13361 if (!in_queue[c]) {
13362 in_queue[
c] =
true;
13363 queue.push_back(c);
13366 context_->var_with_reduced_small_degree.ResetAllToFalse();
13368 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13370 DCHECK(!context_->HasUnusedAffineVariable());
13373 if (!context_->CanonicalizeObjective())
return;
13374 for (
int v = 0; v < context_->working_model->variables().size(); ++v) {
13375 ProcessVariableOnlyUsedInEncoding(v);
13377 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13383 if (context_->params().keep_all_feasible_solutions_in_presolve())
break;
13384 if (!context_->working_model->assumptions().empty())
break;
13389 for (
int i = 0;
i < 10; ++
i) {
13390 if (context_->ModelIsUnsat())
return;
13391 ++num_dual_strengthening;
13392 DualBoundStrengthening dual_bound_strengthening;
13398 if (!dual_bound_strengthening.Strengthen(context_))
return;
13399 if (ProcessChangedVariables(&in_queue, &queue))
break;
13406 if (dual_bound_strengthening.NumDeletedConstraints() == 0)
break;
13408 if (!queue.empty())
continue;
13413 if (context_->params().keep_symmetry_in_presolve())
break;
13417 if (num_dominance_tests++ < 2) {
13418 if (context_->ModelIsUnsat())
return;
13419 PresolveTimer timer(
"DetectDominanceRelations", logger_, time_limit_);
13420 VarDomination var_dom;
13423 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13427 if (context_->ModelIsUnsat())
return;
13437 if (!time_limit_->LimitReached()) {
13438 const int num_constraints = context_->working_model->constraints_size();
13439 TimeLimitCheckEveryNCalls bool_or_check_time_limit(100, time_limit_);
13440 for (
int c = 0;
c < num_constraints; ++
c) {
13441 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13444 bool check_time_limit =
false;
13446 switch (ct->constraint_case()) {
13449 if (PresolveNoOverlap(ct)) {
13450 context_->UpdateConstraintVariableUsage(c);
13452 check_time_limit =
true;
13456 if (PresolveNoOverlap2D(c, ct)) {
13457 context_->UpdateConstraintVariableUsage(c);
13459 check_time_limit =
true;
13463 if (PresolveCumulative(ct)) {
13464 context_->UpdateConstraintVariableUsage(c);
13466 check_time_limit =
true;
13471 for (
const auto& pair :
13472 context_->deductions.ProcessClause(ct->bool_or().literals())) {
13473 bool modified =
false;
13474 if (!context_->IntersectDomainWith(pair.first, pair.second,
13479 context_->UpdateRuleStats(
"deductions: reduced variable domain");
13482 if (bool_or_check_time_limit.LimitReached()) check_time_limit =
true;
13488 if (check_time_limit && time_limit_->LimitReached())
break;
13492 timer.AddCounter(
"num_loops", num_loops);
13493 timer.AddCounter(
"num_dual_strengthening", num_dual_strengthening);
13494 context_->deductions.MarkProcessingAsDoneForNow();
13504void CpModelPresolver::MergeClauses() {
13505 if (context_->ModelIsUnsat())
return;
13506 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13509 std::vector<int> to_clean;
13512 absl::flat_hash_map<uint64_t, int> bool_and_map;
13518 const int num_variables = context_->working_model->variables_size();
13519 std::vector<int> bool_or_indices;
13520 std::vector<int64_t> literal_score(2 * num_variables, 0);
13521 const auto get_index = [](
int ref) {
13525 int64_t num_collisions = 0;
13526 int64_t num_merges = 0;
13527 int64_t num_saved_literals = 0;
13528 ClauseWithOneMissingHasher hasher(*context_->random());
13529 const int num_constraints = context_->working_model->constraints_size();
13530 for (
int c = 0;
c < num_constraints; ++
c) {
13531 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13533 if (ct->enforcement_literal().size() > 1) {
13535 std::sort(ct->mutable_enforcement_literal()->begin(),
13536 ct->mutable_enforcement_literal()->end(),
13537 std::greater<int>());
13538 const auto [it, inserted] = bool_and_map.insert(
13539 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()),
c});
13541 to_clean.push_back(c);
13544 ConstraintProto* other_ct =
13545 context_->working_model->mutable_constraints(it->second);
13546 const absl::Span<const int> s1(ct->enforcement_literal());
13547 const absl::Span<const int> s2(other_ct->enforcement_literal());
13549 context_->UpdateRuleStats(
13550 "bool_and: merged constraints with same enforcement");
13551 other_ct->mutable_bool_and()->mutable_literals()->Add(
13552 ct->bool_and().literals().begin(),
13553 ct->bool_and().literals().end());
13555 context_->UpdateConstraintVariableUsage(c);
13562 const int size = ct->at_most_one().literals().size();
13563 for (
const int ref : ct->at_most_one().literals()) {
13564 literal_score[get_index(ref)] += size;
13569 const int size = ct->exactly_one().literals().size();
13570 for (
const int ref : ct->exactly_one().literals()) {
13571 literal_score[get_index(ref)] += size;
13579 if (!ct->enforcement_literal().empty())
continue;
13580 if (ct->bool_or().literals().size() <= 2)
continue;
13582 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
13583 ct->mutable_bool_or()->mutable_literals()->end());
13584 hasher.RegisterClause(c, ct->bool_or().literals());
13585 bool_or_indices.push_back(c);
13588 for (
const int c : bool_or_indices) {
13589 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13591 bool merged =
false;
13592 timer.TrackSimpleLoop(ct->bool_or().literals().size());
13593 if (timer.WorkLimitIsReached())
break;
13594 for (
const int ref : ct->bool_or().literals()) {
13595 const uint64_t hash = hasher.HashWithout(c, ref);
13596 const auto it = bool_and_map.find(hash);
13597 if (it != bool_and_map.end()) {
13599 const int base_c = it->second;
13600 auto* and_ct = context_->working_model->mutable_constraints(base_c);
13602 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
13604 num_saved_literals += ct->bool_or().literals().size() - 1;
13606 and_ct->mutable_bool_and()->add_literals(ref);
13608 context_->UpdateConstraintVariableUsage(c);
13616 int best_ref = ct->bool_or().literals(0);
13617 int64_t best_score = literal_score[get_index(
NegatedRef(best_ref))];
13618 for (
const int ref : ct->bool_or().literals()) {
13619 const int64_t score = literal_score[get_index(
NegatedRef(ref))];
13620 if (score > best_score) {
13622 best_score = score;
13626 const uint64_t hash = hasher.HashWithout(c, best_ref);
13627 const auto [_, inserted] = bool_and_map.insert({hash,
c});
13629 to_clean.push_back(c);
13630 context_->tmp_literals.clear();
13631 for (
const int lit : ct->bool_or().literals()) {
13632 if (lit == best_ref)
continue;
13633 context_->tmp_literals.push_back(
NegatedRef(lit));
13636 ct->mutable_enforcement_literal()->Assign(
13637 context_->tmp_literals.begin(), context_->tmp_literals.end());
13638 ct->mutable_bool_and()->add_literals(best_ref);
13644 for (
const int c : to_clean) {
13645 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13646 if (ct->bool_and().literals().size() > 1) {
13647 context_->UpdateConstraintVariableUsage(c);
13652 context_->tmp_literals.clear();
13653 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13654 for (
const int ref : ct->enforcement_literal()) {
13655 context_->tmp_literals.push_back(
NegatedRef(ref));
13658 ct->mutable_bool_or()->mutable_literals()->Assign(
13659 context_->tmp_literals.begin(), context_->tmp_literals.end());
13662 timer.AddCounter(
"num_collisions", num_collisions);
13663 timer.AddCounter(
"num_merges", num_merges);
13664 timer.AddCounter(
"num_saved_literals", num_saved_literals);
13672 std::vector<int>* postsolve_mapping) {
13674 return presolver.Presolve();
13678 std::vector<int>* postsolve_mapping)
13679 : postsolve_mapping_(postsolve_mapping),
13681 solution_crush_(context->solution_crush()),
13682 logger_(context->logger()),
13683 time_limit_(context->time_limit()),
13684 interval_representative_(context->working_model->constraints_size(),
13685 IntervalConstraintHash{context->working_model},
13686 IntervalConstraintEq{context->working_model}) {}
13697void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13698 std::vector<int>* fixed_postsolve_mapping,
13699 CpModelProto* mapping_proto) {
13702 int old_num_variables = mapping_proto->variables().size();
13703 while (fixed_postsolve_mapping->size() < domains.size()) {
13704 mapping_proto->add_variables();
13705 fixed_postsolve_mapping->push_back(old_num_variables++);
13706 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13714 for (
int i = 0;
i < domains.size(); ++
i) {
13716 (*fixed_postsolve_mapping)[
i]));
13725 auto mapping_function = [fixed_postsolve_mapping](
int* ref) {
13726 const int image = (*fixed_postsolve_mapping)[
PositiveRef(*ref)];
13727 CHECK_GE(image, 0);
13730 for (
ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13737void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13738 const int num_constraints_before_expansion =
13739 context_->working_model->constraints_size();
13741 if (context_->ModelIsUnsat())
return;
13747 const int num_constraints = context_->working_model->constraints().size();
13748 for (
int c = num_constraints_before_expansion;
c < num_constraints; ++
c) {
13749 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13750 const auto type = ct->constraint_case();
13754 context_->UpdateConstraintVariableUsage(c);
13756 if (context_->ModelIsUnsat())
return;
13758 bool changed =
false;
13759 if (!CanonicalizeLinear(ct, &changed)) {
13763 context_->UpdateConstraintVariableUsage(c);
13773 if (context->ModelIsUnsat())
return;
13776 if (!crush.SolutionIsLoaded())
return;
13777 const int num_vars = context->working_model->variables().size();
13778 for (
int i = 0;
i < num_vars; ++
i) {
13781 crush.SetOrUpdateVarToDomain(
i, context->DomainOf(
i));
13785 for (
int i = 0;
i < num_vars; ++
i) {
13786 const auto relation = context->GetAffineRelation(
i);
13787 if (relation.representative !=
i) {
13788 crush.SetVarToLinearExpression(
13789 i, {{relation.representative, relation.coeff}}, relation.offset);
13797void CanonicalizeRoutesConstraintNodeExpressions(
PresolveContext* context) {
13802 *ct_ref.mutable_routes()->mutable_dimensions()) {
13804 context->CanonicalizeLinearExpression({}, &expr);
13828 context_->InitializeNewDomains();
13835 if (context_->working_model->has_floating_point_objective()) {
13836 context_->WriteVariableDomainsToProto();
13838 context_->working_model)) {
13840 "The floating point objective cannot be scaled with enough "
13849 *context_->mapping_model->mutable_objective() =
13850 context_->working_model->objective();
13860 std::vector<int> fixed_postsolve_mapping;
13861 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13862 return InfeasibleStatus();
13882 for (ConstraintProto& ct :
13889 if (!solution_crush_.SolutionIsLoaded()) {
13890 context_->LoadSolutionHint();
13892 ExpandCpModelAndCanonicalizeConstraints();
13893 UpdateHintInProto(context_);
13894 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13898 if (context_->working_model->has_objective()) {
13899 context_->WriteObjectiveToProto();
13903 EncodeAllAffineRelations();
13909 context_->WriteVariableDomainsToProto();
13910 InitializeMappingModelVariables(context_->AllDomains(),
13911 &fixed_postsolve_mapping,
13912 context_->mapping_model);
13917 if (!context_->mapping_model->constraints().empty()) {
13918 context_->UpdateRuleStats(
13919 "TODO: mapping model not empty with presolve disabled");
13920 context_->working_model->mutable_constraints()->MergeFrom(
13921 context_->mapping_model->constraints());
13922 context_->mapping_model->clear_constraints();
13925 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13931 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13932 for (
int var = 0; var < context_->working_model->variables().size(); ++var) {
13933 if (context_->VariableIsNotUsedAnymore(var))
continue;
13934 if (!PresolveAffineRelationIfAny(var))
return InfeasibleStatus();
13939 TryToSimplifyDomain(var);
13940 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13941 context_->UpdateNewConstraintsVariableUsage();
13943 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
13946 for (
int iter = 0; iter < context_->params().max_presolve_iterations();
13948 if (time_limit_->LimitReached())
break;
13949 context_->UpdateRuleStats(
"presolve: iteration");
13950 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13953 if (!PropagateObjective())
return InfeasibleStatus();
13959 PresolveToFixPoint();
13962 if (!context_->ModelIsExpanded()) {
13963 ExtractEncodingFromLinear();
13964 ExpandCpModelAndCanonicalizeConstraints();
13965 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13969 const int num_vars = context_->working_model->variables().size();
13970 for (
int var = 0; var < num_vars; ++var) {
13971 if (context_->VarToConstraints(var).size() <= 3) {
13972 context_->var_with_reduced_small_degree.Set(var);
13976 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13987 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13988 !time_limit_->LimitReached()) {
13992 DetectDuplicateConstraints();
13993 if (context_->params().keep_symmetry_in_presolve()) {
13995 if (!context_->working_model->has_symmetry()) {
13997 context_->working_model, logger_,
13998 context_->time_limit());
14007 if (!context_->working_model->has_symmetry()) {
14008 context_->working_model->mutable_symmetry()->Clear();
14010 }
else if (!context_->params()
14011 .keep_all_feasible_solutions_in_presolve()) {
14019 if (context_->params().cp_model_use_sat_presolve()) {
14020 if (!time_limit_->LimitReached()) {
14021 if (!PresolvePureSatPart()) {
14022 (void)context_->NotifyThatModelIsUnsat(
14023 "Proven Infeasible during SAT presolve");
14024 return InfeasibleStatus();
14037 if (!context_->ModelIsUnsat() && iter == 0) {
14038 const int old_size = context_->working_model->constraints_size();
14039 for (
int c = 0;
c < old_size; ++
c) {
14040 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
14042 ExtractAtMostOneFromLinear(ct);
14044 context_->UpdateNewConstraintsVariableUsage();
14047 if (context_->params().cp_model_probing_level() > 0) {
14048 if (!time_limit_->LimitReached()) {
14050 PresolveToFixPoint();
14053 TransformIntoMaxCliques();
14060 ProcessAtMostOneAndLinear();
14061 DetectDuplicateConstraints();
14062 DetectDuplicateConstraintsWithDifferentEnforcements();
14063 DetectDominatedLinearConstraints();
14066 TransformClausesToExactlyOne();
14068 if (!time_limit_->LimitReached()) {
14069 DetectEncodedComplexDomains(context_);
14074 if (context_->params().find_big_linear_overlap() &&
14075 !context_->params().keep_symmetry_in_presolve()) {
14076 FindAlmostIdenticalLinearConstraints();
14078 ActivityBoundHelper activity_amo_helper;
14079 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
14080 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
14085 FindBigVerticalLinearOverlap(&activity_amo_helper);
14086 FindBigHorizontalLinearOverlap(&activity_amo_helper);
14088 if (context_->ModelIsUnsat())
return InfeasibleStatus();
14091 if (!time_limit_->LimitReached()) {
14095 if ( (
false)) DetectIncludedEnforcement();
14100 ConvertToBoolAnd();
14104 PresolveToFixPoint();
14109 const int64_t num_ops =
14110 context_->num_presolve_operations - old_num_presolve_op;
14111 if (num_ops == 0)
break;
14113 if (context_->ModelIsUnsat())
return InfeasibleStatus();
14115 if (!MergeNoOverlapConstraints())
return InfeasibleStatus();
14116 if (!MergeNoOverlap2DConstraints())
return InfeasibleStatus();
14120 if (context_->working_model->has_objective()) {
14121 if (!context_->params().keep_symmetry_in_presolve()) {
14123 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
14126 PresolveToFixPoint();
14128 if (context_->ModelIsUnsat())
return InfeasibleStatus();
14129 ShiftObjectiveWithExactlyOnes();
14130 if (context_->ModelIsUnsat())
return InfeasibleStatus();
14136 for (
int c = 0;
c < context_->working_model->constraints_size(); ++
c) {
14137 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
14138 bool need_canonicalize =
false;
14140 for (
const int v : ct.linear().vars()) {
14141 if (context_->IsFixed(v)) {
14142 need_canonicalize =
true;
14147 if (need_canonicalize) {
14148 bool changed =
false;
14149 if (!CanonicalizeLinear(&ct, &changed)) {
14150 return InfeasibleStatus();
14153 context_->UpdateConstraintVariableUsage(c);
14162 EncodeAllAffineRelations();
14163 if (context_->ModelIsUnsat())
return InfeasibleStatus();
14166 if (context_->working_model->has_symmetry()) {
14168 context_->working_model->mutable_symmetry(), context_)) {
14169 return InfeasibleStatus();
14181 absl::flat_hash_set<int> used_variables;
14182 for (DecisionStrategyProto& strategy :
14183 *context_->working_model->mutable_search_strategy()) {
14184 CHECK(strategy.variables().empty());
14185 if (strategy.exprs().empty())
continue;
14188 ConstraintProto empy_enforcement;
14189 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
14190 CanonicalizeLinearExpression(empy_enforcement, &expr);
14195 for (
const LinearExpressionProto& expr : strategy.exprs()) {
14196 if (context_->IsFixed(expr))
continue;
14198 const auto [_, inserted] = used_variables.insert(expr.vars(0));
14199 if (!inserted)
continue;
14201 *strategy.mutable_exprs(new_size++) = expr;
14207 context_->WriteVariableDomainsToProto();
14211 if (context_->working_model->has_objective()) {
14213 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
14214 context_->WriteObjectiveToProto();
14218 InitializeMappingModelVariables(context_->AllDomains(),
14219 &fixed_postsolve_mapping,
14220 context_->mapping_model);
14223 postsolve_mapping_->clear();
14224 std::vector<int> mapping(context_->working_model->variables_size(), -1);
14225 absl::flat_hash_map<int64_t, int> constant_to_index;
14226 int num_unused_variables = 0;
14227 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
14228 if (mapping[
i] != -1)
continue;
14230 if (context_->VariableWasRemoved(
i)) {
14235 const int r =
PositiveRef(context_->GetAffineRelation(
i).representative);
14236 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
14237 mapping[r] = postsolve_mapping_->size();
14238 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
14247 if (context_->VariableIsNotUsedAnymore(
i) &&
14248 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
14249 context_->IsFixed(
i))) {
14255 ++num_unused_variables;
14257 context_->mapping_model->mutable_variables(
14258 fixed_postsolve_mapping[
i]));
14264 if (context_->IsFixed(
i)) {
14265 auto [it, inserted] = constant_to_index.insert(
14266 {context_->FixedValue(
i), postsolve_mapping_->size()});
14268 mapping[
i] = it->second;
14273 mapping[
i] = postsolve_mapping_->size();
14274 postsolve_mapping_->push_back(fixed_postsolve_mapping[
i]);
14276 context_->UpdateRuleStats(absl::StrCat(
"presolve: ", num_unused_variables,
14277 " unused variables removed."));
14279 if (context_->params().permute_variable_randomly()) {
14281 const int n = postsolve_mapping_->size();
14282 std::vector<int> perm(n);
14283 std::iota(perm.begin(), perm.end(), 0);
14284 std::shuffle(perm.begin(), perm.end(), *context_->random());
14285 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
14286 if (mapping[
i] != -1) mapping[
i] = perm[mapping[
i]];
14288 std::vector<int> new_postsolve_mapping(n);
14289 for (
int i = 0;
i < n; ++
i) {
14290 new_postsolve_mapping[perm[
i]] = (*postsolve_mapping_)[
i];
14292 *postsolve_mapping_ = std::move(new_postsolve_mapping);
14295 DCHECK(context_->ConstraintVariableUsageIsConsistent());
14296 CanonicalizeRoutesConstraintNodeExpressions(context_);
14297 UpdateHintInProto(context_);
14298 const int old_size = postsolve_mapping_->size();
14300 postsolve_mapping_);
14301 CHECK_EQ(old_size, postsolve_mapping_->size());
14307 if (context_->deductions.NumDeductions() > 0) {
14308 context_->UpdateRuleStats(absl::StrCat(
14309 "deductions: ", context_->deductions.NumDeductions(),
" stored"));
14313 if (logger_->LoggingIsEnabled()) context_->LogInfo();
14319 const std::string error =
14321 if (!error.empty()) {
14322 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
14328 if (!error.empty()) {
14330 "Error while validating mapping_model model: ", error);
14339 std::vector<int>* reverse_mapping) {
14342 const auto mapping_function = [&mapping, &reverse_mapping](
int* ref) {
14344 int image = mapping[var];
14347 image = mapping[var] = reverse_mapping->size();
14348 reverse_mapping->push_back(var);
14352 for (ConstraintProto& ct_ref : *cp_model->mutable_constraints()) {
14357 *ct_ref.mutable_routes()->mutable_dimensions()) {
14358 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
14359 if (expr.vars().empty())
continue;
14360 CHECK_EQ(expr.vars().size(), 1);
14362 const int var = expr.vars(0);
14363 const auto& definition = cp_model->variables(var);
14364 const int64_t min = definition.domain(0);
14365 const int64_t max = definition.domain(definition.domain().size() - 1);
14367 expr.set_offset(expr.offset() + min * expr.coeffs(0));
14369 expr.clear_coeffs();
14372 const int image = mapping[var];
14377 expr.clear_coeffs();
14380 expr.set_vars(0, image);
14387 if (cp_model->has_objective()) {
14388 for (
int& mutable_ref : *cp_model->mutable_objective()->mutable_vars()) {
14389 mapping_function(&mutable_ref);
14394 for (
int& mutable_ref : *cp_model->mutable_assumptions()) {
14395 mapping_function(&mutable_ref);
14400 if (cp_model->has_symmetry()) {
14402 *cp_model->mutable_symmetry()->mutable_permutations()) {
14403 for (
int& var : *generator.mutable_support()) {
14404 mapping_function(&var);
14409 cp_model->mutable_symmetry()->clear_orbitopes();
14420 DCHECK_EQ(expr.vars().size(), 1);
14421 const int image = mapping[expr.vars(0)];
14423 expr.set_vars(0, image);
14424 *strategy.mutable_exprs(new_size++) = expr;
14434 if (strategy.exprs().empty())
continue;
14435 *cp_model->mutable_search_strategy(new_size++) = strategy;
14442 if (cp_model->has_solution_hint()) {
14443 auto* mutable_hint = cp_model->mutable_solution_hint();
14448 absl::flat_hash_set<int> hinted_images;
14451 const int old_size = mutable_hint->vars().size();
14452 for (
int i = 0;
i < old_size; ++
i) {
14453 const int hinted_var = mutable_hint->vars(
i);
14454 const int64_t hinted_value = mutable_hint->values(
i);
14455 const int image = mapping[hinted_var];
14457 if (!hinted_images.insert(image).second)
continue;
14458 mutable_hint->set_vars(new_size, image);
14459 mutable_hint->set_values(new_size, hinted_value);
14463 mutable_hint->mutable_vars()->Truncate(new_size);
14464 mutable_hint->mutable_values()->Truncate(new_size);
14468 google::protobuf::RepeatedPtrField<IntegerVariableProto>
14469 new_variables_storage;
14470 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14471 if (cp_model->GetArena() ==
nullptr) {
14472 new_variables = &new_variables_storage;
14474 new_variables = google::protobuf::Arena::Create<
14475 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14476 cp_model->GetArena());
14478 for (
int i = 0;
i < mapping.size(); ++
i) {
14479 const int image = mapping[
i];
14480 if (image < 0)
continue;
14481 while (image >= new_variables->size()) {
14482 new_variables->Add();
14484 (*new_variables)[image].Swap(cp_model->mutable_variables(
i));
14486 cp_model->mutable_variables()->Swap(new_variables);
14490 CHECK_GT(v.domain_size(), 0);
14494bool CpModelPresolver::MaybeRemoveFixedVariables(
14495 std::vector<int>* postsolve_mapping) {
14496 postsolve_mapping->clear();
14497 if (!context_->params().remove_fixed_variables_early())
return true;
14498 if (!context_->params().cp_model_presolve())
return true;
14503 context_->InitializeNewDomains();
14504 if (context_->ModelIsUnsat())
return false;
14507 const int num_vars = context_->working_model->variables().size();
14508 std::vector<int> mapping(num_vars, -1);
14509 for (
int i = 0;
i < num_vars; ++
i) {
14510 if (context_->IsFixed(
i))
continue;
14511 mapping[
i] = postsolve_mapping->size();
14512 postsolve_mapping->push_back(
i);
14516 const int num_fixed = num_vars - postsolve_mapping->size();
14517 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
14518 postsolve_mapping->clear();
14525 if (context_->working_model->has_objective()) {
14526 context_->ReadObjectiveFromProto();
14527 if (!context_->CanonicalizeObjective())
return false;
14528 if (!PropagateObjective())
return false;
14529 if (context_->ModelIsUnsat())
return false;
14530 context_->WriteObjectiveToProto();
14535 context_->WriteVariableDomainsToProto();
14536 *context_->mapping_model->mutable_variables() =
14537 context_->working_model->variables();
14539 SOLVER_LOG(logger_,
"Large number of fixed variables ",
14541 ", doing a first remapping phase to go down to ",
14546 const int old_size = postsolve_mapping->size();
14548 postsolve_mapping);
14549 if (postsolve_mapping->size() > old_size) {
14550 const int new_extra = postsolve_mapping->size() - old_size;
14552 " fixed variables still required in the model!");
14556 context_->ResetAfterCopy();
14567 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
14571struct ConstraintHashForDuplicateDetection {
14572 const CpModelProto* working_model;
14573 bool ignore_enforcement;
14574 ConstraintProto objective_constraint;
14576 ConstraintHashForDuplicateDetection(
const CpModelProto* working_model,
14577 bool ignore_enforcement)
14578 : working_model(working_model),
14579 ignore_enforcement(ignore_enforcement),
14580 objective_constraint(
14581 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14586 std::size_t operator()(
int ct_idx)
const {
14588 ? objective_constraint
14589 : working_model->constraints(ct_idx);
14590 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
14591 type_and_enforcement = {ct.constraint_case(),
14593 ? absl::Span<const int>()
14594 : absl::MakeSpan(ct.enforcement_literal())};
14595 switch (ct.constraint_case()) {
14596 case ConstraintProto::kLinear:
14597 if (ignore_enforcement) {
14598 return absl::HashOf(type_and_enforcement,
14599 absl::MakeSpan(ct.linear().vars()),
14600 absl::MakeSpan(ct.linear().coeffs()),
14601 absl::MakeSpan(ct.linear().domain()));
14605 return absl::HashOf(type_and_enforcement,
14606 absl::MakeSpan(ct.linear().vars()),
14607 absl::MakeSpan(ct.linear().coeffs()));
14610 return absl::HashOf(type_and_enforcement,
14611 absl::MakeSpan(ct.bool_and().literals()));
14613 return absl::HashOf(type_and_enforcement,
14614 absl::MakeSpan(ct.bool_or().literals()));
14616 return absl::HashOf(type_and_enforcement,
14617 absl::MakeSpan(ct.at_most_one().literals()));
14619 return absl::HashOf(type_and_enforcement,
14620 absl::MakeSpan(ct.exactly_one().literals()));
14622 ConstraintProto copy = ct;
14624 if (ignore_enforcement) {
14625 copy.mutable_enforcement_literal()->Clear();
14627 return absl::HashOf(copy.SerializeAsString());
14632struct ConstraintEqForDuplicateDetection {
14633 const CpModelProto* working_model;
14634 bool ignore_enforcement;
14635 ConstraintProto objective_constraint;
14637 ConstraintEqForDuplicateDetection(
const CpModelProto* working_model,
14638 bool ignore_enforcement)
14639 : working_model(working_model),
14640 ignore_enforcement(ignore_enforcement),
14641 objective_constraint(
14642 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14644 bool operator()(
int a,
int b)
const {
14649 ? objective_constraint
14650 : working_model->constraints(a);
14652 ? objective_constraint
14653 : working_model->constraints(
b);
14655 if (ct_a.constraint_case() != ct_b.constraint_case())
return false;
14656 if (!ignore_enforcement) {
14657 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
14658 absl::MakeSpan(ct_b.enforcement_literal())) {
14662 switch (ct_a.constraint_case()) {
14666 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
14667 absl::MakeSpan(ct_b.linear().domain())) {
14670 return absl::MakeSpan(ct_a.linear().vars()) ==
14671 absl::MakeSpan(ct_b.linear().vars()) &&
14672 absl::MakeSpan(ct_a.linear().coeffs()) ==
14673 absl::MakeSpan(ct_b.linear().coeffs());
14675 return absl::MakeSpan(ct_a.bool_and().literals()) ==
14676 absl::MakeSpan(ct_b.bool_and().literals());
14678 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14679 absl::MakeSpan(ct_b.bool_or().literals());
14681 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14682 absl::MakeSpan(ct_b.at_most_one().literals());
14684 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14685 absl::MakeSpan(ct_b.exactly_one().literals());
14688 ConstraintProto copy_a = ct_a;
14689 ConstraintProto copy_b = ct_b;
14690 copy_a.clear_name();
14691 copy_b.clear_name();
14692 if (ignore_enforcement) {
14693 copy_a.mutable_enforcement_literal()->Clear();
14694 copy_b.mutable_enforcement_literal()->Clear();
14696 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14704 const CpModelProto& model_proto,
bool ignore_enforcement) {
14705 std::vector<std::pair<int, int>> result;
14709 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14710 ConstraintEqForDuplicateDetection>
14712 model_proto.constraints_size(),
14713 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14714 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14717 if (model_proto.has_objective() && !ignore_enforcement) {
14721 const int num_constraints = model_proto.constraints().size();
14722 for (
int c = 0; c < num_constraints; ++c) {
14723 const auto type = model_proto.constraints(c).constraint_case();
14729 const auto [it, inserted] = equiv_constraints.insert({c, c});
14730 if (it->second != c) {
14732 result.push_back({c, it->second});
14742 return absl::MakeSpan(a.vars()) == absl::MakeSpan(
b.vars()) &&
14743 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(
b.coeffs()) &&
14744 a.offset() ==
b.offset();
14748 return absl::HashOf(absl::MakeSpan(expr.vars()),
14749 absl::MakeSpan(expr.coeffs()), expr.offset());
14754bool CpModelPresolver::IntervalConstraintEq::operator()(
int a,
int b)
const {
14755 const ConstraintProto& ct_a = working_model->constraints(a);
14756 const ConstraintProto& ct_b = working_model->constraints(
b);
14757 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14758 absl::MakeSpan(ct_b.enforcement_literal()) &&
14759 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14760 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14761 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14764std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14765 int ct_idx)
const {
14766 const ConstraintProto& ct = working_model->constraints(ct_idx);
14767 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14768 LinearExpressionHash(ct.interval().start()),
14769 LinearExpressionHash(ct.interval().size()),
14770 LinearExpressionHash(ct.interval().end()));
const std::vector< Variable * > & variables() const
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
static Domain FromValues(std::vector< int64_t > values)
Domain SquareSuperset() const
Domain IntersectionWith(const Domain &domain) const
Domain QuadraticSuperset(int64_t a, int64_t b, int64_t c, int64_t d) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain UnionWith(const Domain &domain) const
Domain Complement() const
Domain InverseMultiplicationBy(int64_t coeff) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
void AddCounter(std::string name, int64_t count)
bool LoggingIsEnabled() const
void Resize(int num_variables)
::int32_t literals(int index) const
void add_literals(::int32_t value)
void set_literals(int index, ::int32_t value)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_literals()
void ResetFromFlatMapping(Keys keys, Values values, int minimum_num_nodes=0)
ConstraintCase constraint_case() const
::operations_research::sat::CumulativeConstraintProto *PROTOBUF_NONNULL mutable_cumulative()
::int32_t enforcement_literal(int index) const
::operations_research::sat::NoOverlapConstraintProto *PROTOBUF_NONNULL mutable_no_overlap()
void set_enforcement_literal(int index, ::int32_t value)
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
::operations_research::sat::NoOverlap2DConstraintProto *PROTOBUF_NONNULL mutable_no_overlap_2d()
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_div()
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_prod()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
void add_enforcement_literal(::int32_t value)
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_lin_max()
const ::operations_research::sat::BoolArgumentProto & bool_or() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
int enforcement_literal_size() const
::operations_research::sat::LinearArgumentProto *PROTOBUF_NONNULL mutable_int_mod()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_at_most_one()
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
void DetectDifferentVariables()
CpSolverStatus Presolve()
bool PresolveOneConstraint(int c)
void RemoveEmptyConstraints()
void DetectDuplicateColumns()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
const ::operations_research::sat::ConstraintProto & constraints(int index) const
bool has_objective() const
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_intervals()
const ::operations_research::sat::LinearExpressionProto & exprs(int index) const
::int32_t vars(int index) const
void set_vars(int index, ::int32_t value)
::google::protobuf::RepeatedField<::int64_t > *PROTOBUF_NONNULL mutable_coeffs()
void add_vars(::int32_t value)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_vars()
void add_coeffs(::int64_t value)
::int64_t coeffs(int index) const
void set_coeffs(int index, ::int64_t value)
::int64_t coeffs(int index) const
::int32_t vars(int index) const
void set_offset(::int64_t value)
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_y_intervals()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_x_intervals()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_intervals()
const SatParameters & params() const
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
void RegisterVariablesUsedInAssumptions()
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
CpModelProto * working_model
bool VariableIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
void ReadObjectiveFromProto()
void WriteObjectiveToProto() const
bool CanonicalizeLinearConstraint(ConstraintProto *ct, bool *is_impossible=nullptr)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
void InitializeNewDomains()
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void MarkVariableAsRemoved(int ref)
void UpdateNewConstraintsVariableUsage()
bool ConstraintVariableUsageIsConsistent()
const Domain & DomainOf(int var) const
void UpdateRuleStats(std::string_view name, int num_times=1)
bool ModelIsUnsat() const
RoutesConstraintProto_NodeExpressions NodeExpressions
static constexpr SearchBranching FIXED_SEARCH
bool cp_model_presolve() const
void StoreSolutionAsHint(CpModelProto &model) const
void Truncate(RepeatedPtrField< T > *array, int new_size)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
void ApplyToAllVariableIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool RefIsPositive(int ref)
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void ExpandCpModel(PresolveContext *context)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
std::vector< VariableEncodingLocalModel > CreateVariableEncodingLocalModels(PresolveContext *context)
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto &expr, const PresolveContext &context)
int64_t FloorSquareRoot(int64_t a)
void ApplyToAllLiteralIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool HasEnforcementLiteral(const ConstraintProto &ct)
DiophantineSolution SolveDiophantine(absl::Span< const int64_t > coeffs, int64_t rhs, absl::Span< const int64_t > var_lbs, absl::Span< const int64_t > var_ubs)
bool ClauseIsEnforcementImpliesLiteral(absl::Span< const int > clause, absl::Span< const int > enforcement, int literal)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
bool IsNegatableInt64(absl::int128 x)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
int64_t GetInnerVarValue(const LinearExpressionProto &expr, int64_t value)
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
const IntegerVariable kNoIntegerVariable(-1)
bool ScaleFloatingPointObjective(const SatParameters ¶ms, SolverLogger *logger, CpModelProto *proto)
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, CpModelProto *proto, SolverLogger *logger, TimeLimit *time_limit)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset, std::pair< int64_t, int64_t > *implied_domain)
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
bool FilterOrbitOnUnusedOrFixedVariables(SymmetryProto *symmetry, PresolveContext *context)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
void ConstructOverlappingSets(absl::Span< IndexedInterval > intervals, CompactVectorVector< int > *result, absl::Span< const int > order)
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
Disjoint2dPackingResult DetectDisjointRegionIn2dPacking(absl::Span< const RectangleInRange > non_fixed_boxes, absl::Span< const Rectangle > fixed_boxes, int max_num_components)
constexpr int kAffineRelationConstraint
IntegerVariable PositiveVariable(IntegerVariable i)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
void ApplyVariableMapping(absl::Span< int > mapping, CpModelProto *cp_model, std::vector< int > *reverse_mapping)
int64_t SafeDoubleToInt64(double value)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void TryToReplaceVariableByItsEncoding(int var, PresolveContext *context, SolutionCrush &solution_crush)
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
bool MaybeTransferLinear1ToAnotherVariable(VariableEncodingLocalModel &local_model, PresolveContext *context)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
int64_t ClosestMultiple(int64_t value, int64_t base)
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
bool LinearsDifferAtOneTerm(const LinearConstraintProto &lin1, const LinearConstraintProto &lin2)
bool ExpressionContainsSingleRef(const LinearExpressionProto &expr)
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(absl::Span< const Rectangle > rectangles)
void DivideLinearExpression(int64_t divisor, LinearExpressionProto *expr)
bool LinearInequalityCanBeReducedWithClosestMultiple(int64_t base, absl::Span< const int64_t > coeffs, absl::Span< const int64_t > lbs, absl::Span< const int64_t > ubs, int64_t rhs, int64_t *new_rhs)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t > > *states, std::vector< absl::flat_hash_set< int64_t > > *labels)
void GetOverlappingIntervalComponents(std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *components)
void CreateValidModelWithSingleConstraint(const ConstraintProto &ct, const PresolveContext *context, std::vector< int > *variable_mapping, CpModelProto *mini_model)
bool IsAffineIntAbs(const ConstraintProto &ct)
void ApplyToAllIntervalIndices(absl::FunctionRef< void(int *)> f, ConstraintProto *ct)
bool VariableIsPositive(IntegerVariable i)
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
bool DetectAllEncodedComplexDomain(PresolveContext *context, VariableEncodingLocalModel &local_model)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
bool AtMinOrMaxInt64(int64_t x)
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
std::string FormatCounter(int64_t num)
int64_t CapProd(int64_t x, int64_t y)
ClosedInterval::Iterator begin(ClosedInterval interval)
absl::StatusOr< std::vector< typename util::GraphTraits< AdjacencyLists >::NodeIndex > > FastTopologicalSort(const AdjacencyLists &adj)
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
bool Contains(int64_t value) const
CompactVectorVector< int, std::pair< int, int64_t > > * column
bool operator()(int a, int b) const
ColumnEqForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
ColumnHashForDuplicateDetection(CompactVectorVector< int, std::pair< int, int64_t > > *_column)
std::size_t operator()(int c) const
CompactVectorVector< int, std::pair< int, int64_t > > * column
#define SOLVER_LOG(logger,...)