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/flags/flag.h"
39#include "absl/hash/hash.h"
40#include "absl/log/check.h"
41#include "absl/log/log.h"
42#include "absl/log/vlog_is_on.h"
43#include "absl/meta/type_traits.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"
64#include "ortools/sat/cp_model.pb.h"
82#include "ortools/sat/sat_parameters.pb.h"
106 result.vars[0] = IntegerVariable(2 * a);
107 result.vars[1] = IntegerVariable(2 *
b);
108 result.coeffs[0] = IntegerValue(coeff_a);
109 result.coeffs[1] = IntegerValue(coeff_b);
115bool LinearConstraintIsClean(
const LinearConstraintProto& linear) {
116 const int num_vars = linear.vars().size();
117 for (
int i = 0;
i < num_vars; ++
i) {
119 if (linear.coeffs(
i) == 0)
return false;
126bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
137 interval_representative_.clear();
138 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
140 int new_num_constraints = 0;
141 const int old_num_non_empty_constraints =
142 context_->working_model->constraints_size();
143 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
144 const auto type = context_->working_model->constraints(c).constraint_case();
145 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
146 if (type == ConstraintProto::kDummyConstraint)
continue;
147 context_->working_model->mutable_constraints(new_num_constraints)
148 ->Swap(context_->working_model->mutable_constraints(c));
149 if (type == ConstraintProto::kInterval) {
153 const auto [it, inserted] = interval_representative_.insert(
154 {new_num_constraints, new_num_constraints});
155 interval_mapping[c] = it->second;
156 if (it->second != new_num_constraints) {
157 context_->UpdateRuleStats(
158 "intervals: change duplicate index across constraints");
162 new_num_constraints++;
165 context_->working_model->mutable_constraints(), new_num_constraints);
166 for (ConstraintProto& ct_ref :
167 *context_->working_model->mutable_constraints()) {
169 [&interval_mapping](
int* ref) {
170 *ref = interval_mapping[*ref];
177bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
182 const int old_size = ct->enforcement_literal().size();
184 for (
const int literal : ct->enforcement_literal()) {
193 return RemoveConstraint(ct);
200 return RemoveConstraint(ct);
206 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
207 const int64_t obj_coeff =
211 context_->UpdateRuleStats(
"enforcement: literal with unique direction");
212 CHECK(context_->SetLiteralToFalse(literal));
213 return RemoveConstraint(ct);
222 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
224 context_->UpdateRuleStats(
"enforcement: removed duplicate literal");
227 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
228 context_->UpdateRuleStats(
"enforcement: can never be true");
229 return RemoveConstraint(ct);
233 ct->set_enforcement_literal(new_size++, literal);
235 ct->mutable_enforcement_literal()->Truncate(new_size);
236 return new_size != old_size;
239bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
240 if (context_->ModelIsUnsat())
return false;
244 bool changed =
false;
245 int num_true_literals = 0;
246 int true_literal = std::numeric_limits<int32_t>::min();
247 for (
const int literal : ct->bool_xor().literals()) {
254 if (context_->VariableIsUniqueAndRemovable(literal)) {
255 context_->UpdateRuleStats(
"TODO bool_xor: remove constraint");
258 if (context_->LiteralIsFalse(literal)) {
259 context_->UpdateRuleStats(
"bool_xor: remove false literal");
262 }
else if (context_->LiteralIsTrue(literal)) {
263 true_literal = literal;
268 ct->mutable_bool_xor()->set_literals(new_size++, literal);
272 if (num_true_literals % 2 == 0) {
273 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
275 context_->UpdateRuleStats(
"bool_xor: always true");
276 return RemoveConstraint(ct);
278 }
else if (new_size == 1) {
279 if (num_true_literals % 2 == 0) {
280 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
281 return context_->NotifyThatModelIsUnsat(
282 "bool_xor: cannot fix last literal");
285 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
286 return context_->NotifyThatModelIsUnsat(
287 "bool_xor: cannot fix last literal");
290 context_->UpdateRuleStats(
"bool_xor: one active literal");
291 return RemoveConstraint(ct);
292 }
else if (new_size == 2) {
293 const int a = ct->bool_xor().literals(0);
294 const int b = ct->bool_xor().literals(1);
296 if (num_true_literals % 2 == 0) {
297 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
299 context_->UpdateRuleStats(
"bool_xor: always true");
300 return RemoveConstraint(ct);
304 if (num_true_literals % 2 == 1) {
305 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
307 context_->UpdateRuleStats(
"bool_xor: always true");
308 return RemoveConstraint(ct);
311 if (num_true_literals % 2 == 0) {
312 if (!context_->StoreBooleanEqualityRelation(a,
NegatedRef(
b))) {
316 if (!context_->StoreBooleanEqualityRelation(a,
b)) {
320 context_->UpdateNewConstraintsVariableUsage();
321 context_->UpdateRuleStats(
"bool_xor: two active literals");
322 return RemoveConstraint(ct);
325 if (num_true_literals % 2 == 1) {
326 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
327 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
329 if (num_true_literals > 1) {
330 context_->UpdateRuleStats(
"bool_xor: remove even number of true literals");
333 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
337bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
338 if (context_->ModelIsUnsat())
return false;
343 context_->UpdateRuleStats(
"bool_or: removed enforcement literal");
344 for (
const int literal : ct->enforcement_literal()) {
345 ct->mutable_bool_or()->add_literals(
NegatedRef(literal));
347 ct->clear_enforcement_literal();
355 bool changed =
false;
356 context_->tmp_literals.clear();
357 context_->tmp_literal_set.clear();
358 for (
const int literal : ct->bool_or().literals()) {
359 if (context_->LiteralIsFalse(literal)) {
363 if (context_->LiteralIsTrue(literal)) {
364 context_->UpdateRuleStats(
"bool_or: always true");
365 return RemoveConstraint(ct);
370 if (context_->VariableIsUniqueAndRemovable(literal)) {
371 context_->UpdateRuleStats(
"bool_or: singleton");
372 if (!context_->SetLiteralToTrue(literal))
return true;
373 return RemoveConstraint(ct);
375 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
376 context_->UpdateRuleStats(
"bool_or: always true");
377 return RemoveConstraint(ct);
380 if (context_->tmp_literal_set.contains(literal)) {
383 context_->tmp_literal_set.insert(literal);
384 context_->tmp_literals.push_back(literal);
387 context_->tmp_literal_set.clear();
389 if (context_->tmp_literals.empty()) {
390 context_->UpdateRuleStats(
"bool_or: empty");
391 return context_->NotifyThatModelIsUnsat();
393 if (context_->tmp_literals.size() == 1) {
394 context_->UpdateRuleStats(
"bool_or: only one literal");
395 if (!context_->SetLiteralToTrue(context_->tmp_literals[0]))
return true;
396 return RemoveConstraint(ct);
398 if (context_->tmp_literals.size() == 2) {
401 context_->UpdateRuleStats(
"bool_or: implications");
402 ct->add_enforcement_literal(
NegatedRef(context_->tmp_literals[0]));
403 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
408 context_->UpdateRuleStats(
"bool_or: fixed literals");
409 ct->mutable_bool_or()->mutable_literals()->Clear();
410 for (
const int lit : context_->tmp_literals) {
411 ct->mutable_bool_or()->add_literals(lit);
419ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
420 ConstraintProto* ct) {
423 ct->mutable_bool_or()->clear_literals();
424 for (
const int lit : ct->enforcement_literal()) {
425 ct->mutable_bool_or()->add_literals(
NegatedRef(lit));
427 ct->clear_enforcement_literal();
431 return context_->NotifyThatModelIsUnsat();
435bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
436 if (context_->ModelIsUnsat())
return false;
439 context_->UpdateRuleStats(
"bool_and: non-reified.");
440 for (
const int literal : ct->bool_and().literals()) {
441 if (!context_->SetLiteralToTrue(literal))
return true;
443 return RemoveConstraint(ct);
446 bool changed =
false;
447 context_->tmp_literals.clear();
448 context_->tmp_literal_set.clear();
449 const absl::flat_hash_set<int> enforcement_literals_set(
450 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
451 for (
const int literal : ct->bool_and().literals()) {
452 if (context_->LiteralIsFalse(literal)) {
453 context_->UpdateRuleStats(
"bool_and: always false");
454 return MarkConstraintAsFalse(ct);
456 if (context_->LiteralIsTrue(literal)) {
460 if (enforcement_literals_set.contains(literal)) {
461 context_->UpdateRuleStats(
"bool_and: x => x");
465 if (enforcement_literals_set.contains(
NegatedRef(literal))) {
466 context_->UpdateRuleStats(
"bool_and: x => not x");
467 return MarkConstraintAsFalse(ct);
469 if (context_->VariableIsUniqueAndRemovable(literal)) {
472 context_->UpdateRuleStats(
473 "bool_and: setting unused literal in rhs to true");
474 if (!context_->SetLiteralToTrue(literal))
return true;
478 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
479 context_->UpdateRuleStats(
"bool_and: cannot be enforced");
480 return MarkConstraintAsFalse(ct);
483 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
485 context_->tmp_literals.push_back(literal);
488 context_->UpdateRuleStats(
"bool_and: removed duplicate literal");
495 if (context_->tmp_literals.empty())
return RemoveConstraint(ct);
498 ct->mutable_bool_and()->mutable_literals()->Clear();
499 for (
const int lit : context_->tmp_literals) {
500 ct->mutable_bool_and()->add_literals(lit);
502 context_->UpdateRuleStats(
"bool_and: fixed literals");
509 if (ct->enforcement_literal().size() == 1 &&
510 ct->bool_and().literals().size() == 1) {
511 const int enforcement = ct->enforcement_literal(0);
512 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
514 int64_t obj_coeff = context_->ObjectiveMap().at(var);
519 context_->UpdateRuleStats(
"bool_and: dual equality.");
524 const int implied_literal = ct->bool_and().literals(0);
525 solution_crush_.SetLiteralToValueIf(enforcement,
true, implied_literal);
526 if (!context_->StoreBooleanEqualityRelation(enforcement,
537bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
538 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
539 const std::string name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
540 auto* literals = is_at_most_one
541 ? ct->mutable_at_most_one()->mutable_literals()
542 : ct->mutable_exactly_one()->mutable_literals();
546 std::sort(literals->begin(), literals->end());
549 context_->tmp_literal_set.clear();
550 for (
const int literal : *literals) {
551 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
553 if (!context_->SetLiteralToFalse(literal))
return false;
554 context_->UpdateRuleStats(absl::StrCat(name,
"duplicate literals"));
556 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
557 int num_positive = 0;
558 int num_negative = 0;
559 for (
const int other : *literals) {
561 if (!context_->SetLiteralToFalse(other))
return false;
562 context_->UpdateRuleStats(absl::StrCat(name,
"x and not(x)"));
564 if (other == literal) {
574 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
577 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
580 return RemoveConstraint(ct);
586 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
589 bool changed =
false;
590 context_->tmp_literals.clear();
591 for (
const int literal : *literals) {
592 if (context_->LiteralIsTrue(literal)) {
593 context_->UpdateRuleStats(absl::StrCat(name,
"satisfied"));
594 for (
const int other : *literals) {
595 if (other != literal) {
596 if (!context_->SetLiteralToFalse(other))
return false;
599 return RemoveConstraint(ct);
602 if (context_->LiteralIsFalse(literal)) {
608 if (context_->VariableIsUniqueAndRemovable(literal)) {
611 singleton_literal_with_cost.push_back({literal, 0});
614 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
615 const auto it = context_->ObjectiveMap().find(
PositiveRef(literal));
616 DCHECK(it != context_->ObjectiveMap().end());
618 singleton_literal_with_cost.push_back({literal, it->second});
622 singleton_literal_with_cost.push_back({literal, -it->second});
627 context_->tmp_literals.push_back(literal);
630 bool transform_to_at_most_one =
false;
631 if (!singleton_literal_with_cost.empty()) {
635 if (singleton_literal_with_cost.size() > 1) {
637 singleton_literal_with_cost.begin(),
638 singleton_literal_with_cost.end(),
639 [](
const std::pair<int, int64_t>& a,
640 const std::pair<int, int64_t>&
b) { return a.second < b.second; });
641 for (
int i = 1;
i < singleton_literal_with_cost.size(); ++
i) {
642 context_->UpdateRuleStats(
"at_most_one: dominated singleton");
643 if (!context_->SetLiteralToFalse(
644 singleton_literal_with_cost[
i].first)) {
648 singleton_literal_with_cost.resize(1);
651 const int literal = singleton_literal_with_cost[0].first;
652 const int64_t literal_cost = singleton_literal_with_cost[0].second;
653 if (is_at_most_one && literal_cost >= 0) {
655 context_->UpdateRuleStats(
"at_most_one: singleton");
656 if (!context_->SetLiteralToFalse(literal))
return false;
657 }
else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
664 DCHECK(!context_->ObjectiveMap().contains(
PositiveRef(literal)));
666 if (!is_at_most_one) transform_to_at_most_one =
true;
667 is_at_most_one =
true;
669 context_->UpdateRuleStats(
"exactly_one: singleton");
670 context_->MarkVariableAsRemoved(
PositiveRef(literal));
673 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
674 ->mutable_exactly_one();
675 for (
const int lit : context_->tmp_literals) {
676 mapping_exo->add_literals(lit);
678 mapping_exo->add_literals(literal);
681 context_->tmp_literals.push_back(literal);
685 if (!is_at_most_one && !transform_to_at_most_one &&
686 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
687 context_->UpdateRuleStats(
"exactly_one: simplified objective");
690 if (transform_to_at_most_one) {
693 literals = ct->mutable_at_most_one()->mutable_literals();
697 for (
const int lit : context_->tmp_literals) {
700 context_->UpdateRuleStats(absl::StrCat(name,
"removed literals"));
705bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
706 if (context_->ModelIsUnsat())
return false;
709 const bool changed = PresolveAtMostOrExactlyOne(ct);
710 if (ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
713 const auto& literals = ct->at_most_one().literals();
714 if (literals.empty()) {
715 context_->UpdateRuleStats(
"at_most_one: empty or all false");
716 return RemoveConstraint(ct);
720 if (literals.size() == 1) {
721 context_->UpdateRuleStats(
"at_most_one: size one");
722 return RemoveConstraint(ct);
728bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
729 if (context_->ModelIsUnsat())
return false;
731 const bool changed = PresolveAtMostOrExactlyOne(ct);
732 if (ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
735 const auto& literals = ct->exactly_one().literals();
736 if (literals.empty()) {
737 return context_->NotifyThatModelIsUnsat(
"exactly_one: empty or all false");
741 if (literals.size() == 1) {
742 context_->UpdateRuleStats(
"exactly_one: size one");
743 if (!context_->SetLiteralToTrue(literals[0]))
return false;
744 return RemoveConstraint(ct);
748 if (literals.size() == 2) {
749 context_->UpdateRuleStats(
"exactly_one: size two");
750 if (!context_->StoreBooleanEqualityRelation(literals[0],
754 return RemoveConstraint(ct);
760bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto& ct,
761 LinearArgumentProto* proto) {
762 if (context_->ModelIsUnsat())
return false;
765 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
766 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
767 changed |= CanonicalizeLinearExpression(ct, &exp);
774bool CpModelPresolver::DivideLinMaxByGcd(
int c, ConstraintProto* ct) {
775 LinearArgumentProto* lin_max = ct->mutable_lin_max();
779 for (
const LinearExpressionProto& expr : lin_max->exprs()) {
783 if (gcd <= 1)
return true;
787 const LinearExpressionProto& target = lin_max->target();
788 const int64_t old_gcd = gcd;
790 if (gcd != old_gcd) {
791 if (target.vars().empty()) {
792 return context_->NotifyThatModelIsUnsat(
"infeasible lin_max");
797 if (target.vars().size() == 1) {
799 context_->UpdateRuleStats(
"lin_max: canonicalize target using gcd");
800 if (!context_->CanonicalizeAffineVariable(
801 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
804 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
805 context_->UpdateConstraintVariableUsage(c);
808 context_->UpdateRuleStats(
809 "TODO lin_max: lhs not trivially divisible by rhs gcd");
812 if (gcd <= 1)
return true;
814 context_->UpdateRuleStats(
"lin_max: divising by gcd");
816 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
824int64_t EvaluateSingleVariableExpression(
const LinearExpressionProto& expr,
825 int var, int64_t value) {
826 int64_t result = expr.offset();
827 for (
int i = 0;
i < expr.vars().size(); ++
i) {
828 CHECK_EQ(expr.vars(
i), var);
829 result += expr.coeffs(
i) * value;
834template <
class ExpressionList>
835int GetFirstVar(ExpressionList exprs) {
836 for (
const LinearExpressionProto& expr : exprs) {
837 for (
const int var : expr.vars()) {
845bool IsAffineIntAbs(
const ConstraintProto& ct) {
846 if (ct.constraint_case() != ConstraintProto::kLinMax ||
847 ct.lin_max().exprs_size() != 2 || ct.lin_max().target().vars_size() > 1 ||
848 ct.lin_max().exprs(0).vars_size() != 1 ||
849 ct.lin_max().exprs(1).vars_size() != 1) {
853 const LinearArgumentProto& lin_max = ct.lin_max();
854 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
860 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
861 ? lin_max.exprs(0).coeffs(0)
862 : -lin_max.exprs(0).coeffs(0);
863 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
864 ? lin_max.exprs(1).coeffs(0)
865 : -lin_max.exprs(1).coeffs(0);
866 return left_coeff == -right_coeff;
871bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
873 const int unique_var = GetFirstVar(ct->lin_max().exprs());
875 const auto& lin_max = ct->lin_max();
876 const int num_exprs = lin_max.exprs_size();
877 const auto& target = lin_max.target();
878 std::vector<int> num_wins(num_exprs, 0);
879 std::vector<int64_t> reachable_target_values;
880 std::vector<int64_t> valid_variable_values;
881 std::vector<int64_t> tmp_values(num_exprs);
883 const bool target_has_same_unique_var =
884 target.vars_size() == 1 && target.vars(0) == unique_var;
886 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
888 for (
const int64_t value : context_->DomainOf(unique_var).Values()) {
889 int64_t current_max = std::numeric_limits<int64_t>::min();
892 for (
int i = 0;
i < num_exprs; ++
i) {
894 EvaluateSingleVariableExpression(lin_max.exprs(
i), unique_var, value);
895 current_max = std::max(current_max, v);
900 if (!context_->DomainContains(target, current_max))
continue;
904 if (target_has_same_unique_var &&
905 EvaluateSingleVariableExpression(target, unique_var, value) !=
910 valid_variable_values.push_back(value);
911 reachable_target_values.push_back(current_max);
912 for (
int i = 0;
i < num_exprs; ++
i) {
913 DCHECK_LE(tmp_values[
i], current_max);
914 if (tmp_values[
i] == current_max) {
920 if (reachable_target_values.empty() || valid_variable_values.empty()) {
921 context_->UpdateRuleStats(
"lin_max: infeasible affine_max constraint");
922 return MarkConstraintAsFalse(ct);
926 bool reduced =
false;
927 if (!context_->IntersectDomainWith(
932 context_->UpdateRuleStats(
"lin_max: affine_max target domain reduced");
937 bool reduced =
false;
938 if (!context_->IntersectDomainWith(
943 context_->UpdateRuleStats(
944 "lin_max: unique affine_max var domain reduced");
949 for (
int i = 0;
i < num_exprs; ++
i) {
950 if (num_wins[
i] == valid_variable_values.size()) {
951 const LinearExpressionProto winner_expr = lin_max.exprs(
i);
952 ct->mutable_lin_max()->clear_exprs();
953 *ct->mutable_lin_max()->add_exprs() = winner_expr;
958 bool changed =
false;
959 if (ct->lin_max().exprs_size() > 1) {
961 for (
int i = 0;
i < num_exprs; ++
i) {
962 if (num_wins[
i] == 0)
continue;
963 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(
i);
966 if (new_size < ct->lin_max().exprs_size()) {
967 context_->UpdateRuleStats(
"lin_max: removed affine_max exprs");
974 if (context_->IsFixed(target)) {
975 context_->UpdateRuleStats(
"lin_max: fixed affine_max target");
976 return RemoveConstraint(ct);
979 if (target_has_same_unique_var) {
980 context_->UpdateRuleStats(
"lin_max: target_affine(x) = max(affine_i(x))");
981 return RemoveConstraint(ct);
989 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
990 context_->MarkVariableAsRemoved(target.vars(0));
991 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
992 context_->UpdateRuleStats(
"lin_max: unused affine_max target");
993 return RemoveConstraint(ct);
999bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
1000 const LinearExpressionProto& target = ct->lin_max().target();
1005 int64_t infered_min = context_->MinOf(target);
1006 int64_t infered_max = std::numeric_limits<int64_t>::min();
1007 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1008 infered_min = std::max(infered_min, context_->MinOf(expr));
1009 infered_max = std::max(infered_max, context_->MaxOf(expr));
1012 if (target.vars().empty()) {
1013 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
1014 context_->UpdateRuleStats(
"lin_max: infeasible");
1015 return MarkConstraintAsFalse(ct);
1018 if (target.vars().size() <= 1) {
1020 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1021 rhs_domain = rhs_domain.UnionWith(
1022 context_->DomainSuperSetOf(expr).IntersectionWith(
1023 {infered_min, infered_max}));
1025 bool reduced =
false;
1026 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1030 context_->UpdateRuleStats(
"lin_max: target domain reduced");
1036 const int64_t target_min = context_->MinOf(target);
1037 bool changed =
false;
1044 bool has_greater_or_equal_to_target_min =
false;
1045 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1046 int index_to_keep = -1;
1047 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1048 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1049 if (context_->MinOf(expr) >= target_min) {
1050 const int64_t expr_max = context_->MaxOf(expr);
1051 if (expr_max > max_at_index_to_keep) {
1052 max_at_index_to_keep = expr_max;
1055 has_greater_or_equal_to_target_min =
true;
1060 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1061 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1062 const int64_t expr_max = context_->MaxOf(expr);
1065 if (expr_max < target_min)
continue;
1066 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1067 i != index_to_keep) {
1070 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1073 if (new_size < ct->lin_max().exprs_size()) {
1074 context_->UpdateRuleStats(
"lin_max: removed exprs");
1084bool CpModelPresolver::PresolveLinMax(
int c, ConstraintProto* ct) {
1085 if (context_->ModelIsUnsat())
return false;
1087 const LinearExpressionProto& target = ct->lin_max().target();
1090 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1092 for (
const LinearExpressionProto& e : ct->lin_max().exprs()) {
1094 LinearConstraintProto* prec =
1095 context_->working_model->add_constraints()->mutable_linear();
1096 prec->add_domain(0);
1097 prec->add_domain(std::numeric_limits<int64_t>::max());
1101 context_->UpdateRuleStats(
"lin_max: x = max(x, ...)");
1102 return RemoveConstraint(ct);
1106 const bool is_one_var_affine_max =
1108 ct->lin_max().target().vars_size() <= 1;
1109 bool unique_var_is_small_enough =
false;
1110 const bool is_int_abs = IsAffineIntAbs(*ct);
1112 if (is_one_var_affine_max) {
1113 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1114 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1118 if (is_one_var_affine_max && unique_var_is_small_enough) {
1119 changed = PropagateAndReduceAffineMax(ct);
1120 }
else if (is_int_abs) {
1121 changed = PropagateAndReduceIntAbs(ct);
1123 changed = PropagateAndReduceLinMax(ct);
1126 if (context_->ModelIsUnsat())
return false;
1127 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1132 if (ct->lin_max().exprs().empty()) {
1133 context_->UpdateRuleStats(
"lin_max: no exprs");
1134 return MarkConstraintAsFalse(ct);
1138 if (ct->lin_max().exprs().size() < 10) {
1139 const int num_exprs = ct->lin_max().exprs().size();
1141 bool simplified =
false;
1142 std::vector<bool> can_be_removed(num_exprs,
false);
1143 for (
int i = 0;
i < num_exprs; ++
i) {
1144 if (ct->lin_max().exprs(
i).vars().size() != 1)
continue;
1145 for (
int j = 0; j < num_exprs; ++j) {
1146 if (
i == j)
continue;
1147 if (can_be_removed[j])
continue;
1152 if (ct->lin_max().exprs(j).vars().size() != 1)
continue;
1155 const LinearExpression2 expr2 = GetLinearExpression2FromProto(
1156 ct->lin_max().exprs(
i).vars(0), ct->lin_max().exprs(
i).coeffs(0),
1157 ct->lin_max().exprs(j).vars(0), -ct->lin_max().exprs(j).coeffs(0));
1159 const IntegerValue ub(ct->lin_max().exprs(j).offset() -
1160 ct->lin_max().exprs(
i).offset());
1161 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
1164 can_be_removed[
i] =
true;
1171 context_->UpdateRuleStats(
1172 "lin_max: removed expression smaller than others");
1174 for (
int i = 0;
i < num_exprs; ++
i) {
1175 if (can_be_removed[
i])
continue;
1176 *ct->mutable_lin_max()->mutable_exprs(new_size++) =
1177 ct->lin_max().exprs(
i);
1181 context_->UpdateConstraintVariableUsage(c);
1187 if (ct->lin_max().exprs().size() == 1) {
1188 context_->UpdateRuleStats(
"lin_max: converted to equality");
1189 ConstraintProto* new_ct = context_->working_model->add_constraints();
1191 auto* arg = new_ct->mutable_linear();
1192 const LinearExpressionProto& a = ct->lin_max().target();
1193 const LinearExpressionProto&
b = ct->lin_max().exprs(0);
1194 for (
int i = 0;
i < a.vars().size(); ++
i) {
1195 arg->add_vars(a.vars(
i));
1196 arg->add_coeffs(a.coeffs(
i));
1198 for (
int i = 0;
i <
b.vars().size(); ++
i) {
1199 arg->add_vars(
b.vars(
i));
1200 arg->add_coeffs(-
b.coeffs(
i));
1202 arg->add_domain(
b.offset() - a.offset());
1203 arg->add_domain(
b.offset() - a.offset());
1204 context_->UpdateNewConstraintsVariableUsage();
1205 return RemoveConstraint(ct);
1208 if (!DivideLinMaxByGcd(c, ct))
return false;
1213 const int64_t target_min = context_->MinOf(target);
1214 const int64_t target_max = context_->MaxOf(target);
1217 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1218 const int64_t value_min = context_->MinOf(expr);
1219 bool modified =
false;
1220 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1225 context_->UpdateRuleStats(
"lin_max: reduced expression domain.");
1227 const int64_t value_max = context_->MaxOf(expr);
1228 if (value_max > target_max) {
1229 context_->UpdateRuleStats(
"TODO lin_max: linear expression above max.");
1233 if (abort)
return changed;
1237 bool linear_target_domain_contains_max_domain =
false;
1239 int64_t infered_min = std::numeric_limits<int64_t>::min();
1240 int64_t infered_max = std::numeric_limits<int64_t>::min();
1241 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1242 infered_min = std::max(infered_min, context_->MinOf(expr));
1243 infered_max = std::max(infered_max, context_->MaxOf(expr));
1246 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1247 rhs_domain = rhs_domain.UnionWith(
1248 context_->DomainSuperSetOf(expr).IntersectionWith(
1249 {infered_min, infered_max}));
1255 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1256 const Domain target_domain =
1257 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1258 : context_->DomainOf(target.vars(0)).Negation();
1259 linear_target_domain_contains_max_domain =
1260 rhs_domain.IsIncludedIn(target_domain);
1275 if (linear_target_domain_contains_max_domain) {
1276 const int target_var = target.vars(0);
1278 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1279 for (
const int var : expr.vars()) {
1280 if (var == target_var &&
1291 context_->UpdateRuleStats(
1292 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1293 linear_target_domain_contains_max_domain =
false;
1298 if (linear_target_domain_contains_max_domain &&
1299 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1300 context_->UpdateRuleStats(
"lin_max: unused affine target");
1301 context_->MarkVariableAsRemoved(target.vars(0));
1302 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1303 return RemoveConstraint(ct);
1308 if (linear_target_domain_contains_max_domain &&
1309 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1310 (target.coeffs(0) > 0) ==
1311 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1312 context_->UpdateRuleStats(
"lin_max: rewrite with precedences");
1313 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1314 LinearConstraintProto* prec =
1315 context_->working_model->add_constraints()->mutable_linear();
1316 prec->add_domain(0);
1317 prec->add_domain(std::numeric_limits<int64_t>::max());
1321 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1322 return RemoveConstraint(ct);
1326 if (target_min == target_max) {
1327 bool all_booleans =
true;
1328 std::vector<int> literals;
1329 const int64_t fixed_target = target_min;
1330 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1331 const int64_t value_min = context_->MinOf(expr);
1332 const int64_t value_max = context_->MaxOf(expr);
1333 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
1334 if (value_max < fixed_target)
continue;
1336 if (value_min == value_max && value_max == fixed_target) {
1337 context_->UpdateRuleStats(
"lin_max: always satisfied");
1338 return RemoveConstraint(ct);
1340 if (context_->ExpressionIsAffineBoolean(expr)) {
1341 CHECK_EQ(value_max, fixed_target);
1342 literals.push_back(context_->LiteralForExpressionMax(expr));
1344 all_booleans =
false;
1348 if (literals.empty()) {
1349 return MarkConstraintAsFalse(ct);
1353 context_->UpdateRuleStats(
"lin_max: fixed target and all booleans");
1354 for (
const int lit : literals) {
1355 ct->mutable_bool_or()->add_literals(lit);
1362 changed |= PresolveLinMaxWhenAllBoolean(ct);
1367bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1368 if (context_->ModelIsUnsat())
return false;
1371 const LinearExpressionProto& target = ct->lin_max().target();
1372 if (!context_->ExpressionIsAffineBoolean(target))
return false;
1374 const int64_t target_min = context_->MinOf(target);
1375 const int64_t target_max = context_->MaxOf(target);
1376 const int target_ref = context_->LiteralForExpressionMax(target);
1378 bool min_is_reachable =
false;
1379 std::vector<int> min_literals;
1380 std::vector<int> literals_above_min;
1381 std::vector<int> max_literals;
1383 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1384 if (!context_->ExpressionIsAffineBoolean(expr))
return false;
1385 const int64_t value_min = context_->MinOf(expr);
1386 const int64_t value_max = context_->MaxOf(expr);
1387 const int ref = context_->LiteralForExpressionMax(expr);
1391 if (value_min > target_min) {
1392 context_->UpdateRuleStats(
"lin_max: fix target");
1393 (void)context_->SetLiteralToTrue(target_ref);
1396 if (value_max > target_max) {
1397 context_->UpdateRuleStats(
"lin_max: fix bool expr");
1398 (void)context_->SetLiteralToFalse(ref);
1403 if (value_min == value_max) {
1404 if (value_min == target_min) min_is_reachable =
true;
1408 CHECK_LE(value_min, target_min);
1409 if (value_min == target_min) {
1413 CHECK_LE(value_max, target_max);
1414 if (value_max == target_max) {
1415 max_literals.push_back(ref);
1416 literals_above_min.push_back(ref);
1417 }
else if (value_max > target_min) {
1418 literals_above_min.push_back(ref);
1419 }
else if (value_max == target_min) {
1420 min_literals.push_back(ref);
1424 context_->UpdateRuleStats(
"lin_max: all Booleans.");
1427 ConstraintProto* clause = context_->working_model->add_constraints();
1428 clause->add_enforcement_literal(target_ref);
1429 clause->mutable_bool_or();
1430 for (
const int lit : max_literals) {
1431 clause->mutable_bool_or()->add_literals(lit);
1435 for (
const int lit : literals_above_min) {
1436 context_->AddImplication(lit, target_ref);
1439 if (!min_is_reachable) {
1441 ConstraintProto* clause = context_->working_model->add_constraints();
1442 clause->add_enforcement_literal(
NegatedRef(target_ref));
1443 clause->mutable_bool_or();
1444 for (
const int lit : min_literals) {
1445 clause->mutable_bool_or()->add_literals(lit);
1449 context_->UpdateNewConstraintsVariableUsage();
1450 return RemoveConstraint(ct);
1455bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1456 CHECK_EQ(ct->enforcement_literal_size(), 0);
1457 if (context_->ModelIsUnsat())
return false;
1458 const LinearExpressionProto& target_expr = ct->lin_max().target();
1459 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1460 DCHECK_EQ(expr.vars_size(), 1);
1464 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1465 const Domain new_target_domain =
1466 expr_domain.UnionWith(expr_domain.Negation())
1467 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1468 bool target_domain_modified =
false;
1469 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1470 &target_domain_modified)) {
1473 if (expr_domain.IsFixed()) {
1474 context_->UpdateRuleStats(
"lin_max: fixed expression in int_abs");
1475 return RemoveConstraint(ct);
1477 if (target_domain_modified) {
1478 context_->UpdateRuleStats(
"lin_max: propagate domain from x to abs(x)");
1484 const Domain target_domain =
1485 context_->DomainSuperSetOf(target_expr)
1486 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1487 const Domain new_expr_domain =
1488 target_domain.UnionWith(target_domain.Negation());
1489 bool expr_domain_modified =
false;
1490 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1491 &expr_domain_modified)) {
1496 if (context_->IsFixed(target_expr)) {
1497 context_->UpdateRuleStats(
"lin_max: fixed abs target");
1498 return RemoveConstraint(ct);
1500 if (expr_domain_modified) {
1501 context_->UpdateRuleStats(
"lin_max: propagate domain from abs(x) to x");
1506 if (context_->MinOf(expr) >= 0) {
1507 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1508 ConstraintProto* new_ct = context_->working_model->add_constraints();
1509 new_ct->set_name(ct->name());
1510 auto* arg = new_ct->mutable_linear();
1515 bool changed =
false;
1516 if (!CanonicalizeLinear(new_ct, &changed)) {
1519 context_->UpdateNewConstraintsVariableUsage();
1520 return RemoveConstraint(ct);
1523 if (context_->MaxOf(expr) <= 0) {
1524 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1525 ConstraintProto* new_ct = context_->working_model->add_constraints();
1526 new_ct->set_name(ct->name());
1527 auto* arg = new_ct->mutable_linear();
1532 bool changed =
false;
1533 if (!CanonicalizeLinear(new_ct, &changed)) {
1536 context_->UpdateNewConstraintsVariableUsage();
1537 return RemoveConstraint(ct);
1545 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1546 context_->MarkVariableAsRemoved(target_expr.vars(0));
1547 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1548 context_->UpdateRuleStats(
"lin_max: unused abs target");
1549 return RemoveConstraint(ct);
1557 if (expr.exprs().size() == 2) {
1558 const LinearExpressionProto& expr0 = expr.exprs(0);
1559 const LinearExpressionProto& expr1 = expr.exprs(1);
1563 if (expr0.vars().size() == 1 && expr1.vars().size() == 1 &&
1564 expr0.vars(0) == expr1.vars(0)) {
1565 return context.
DomainOf(expr0.vars(0))
1572 for (
const LinearExpressionProto& expr : expr.exprs()) {
1579bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1584 bool domain_modified =
false;
1588 &domain_modified)) {
1599 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1600 const LinearExpressionProto& target = ct->int_prod().target();
1601 if (!absl::c_any_of(ct->int_prod().exprs(),
1602 [&target](
const LinearExpressionProto& expr) {
1603 return absl::c_linear_search(expr.vars(),
1606 const Domain target_domain =
1608 .AdditionWith(context_->
DomainOf(target.vars(0)));
1613 return RemoveConstraint(ct);
1620 int64_t constant_factor = 1;
1622 bool changed =
false;
1623 LinearArgumentProto* proto = ct->mutable_int_prod();
1624 for (
int i = 0;
i < ct->int_prod().exprs().size(); ++
i) {
1625 LinearExpressionProto expr = ct->int_prod().exprs(
i);
1626 if (context_->IsFixed(expr)) {
1627 const int64_t expr_value = context_->FixedValue(expr);
1628 constant_factor =
CapProd(constant_factor, expr_value);
1629 context_->UpdateRuleStats(
"int_prod: removed constant expressions.");
1634 constant_factor =
CapProd(constant_factor, expr_divisor);
1635 *proto->mutable_exprs(new_size++) = expr;
1638 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1639 proto->mutable_exprs()->end());
1641 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1642 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1643 Domain(constant_factor))) {
1646 context_->UpdateRuleStats(
"int_prod: constant product");
1647 return RemoveConstraint(ct);
1651 if (context_->IsFixed(ct->int_prod().target()) &&
1652 context_->FixedValue(ct->int_prod().target()) == 0 &&
1653 constant_factor != 1) {
1654 context_->UpdateRuleStats(
"int_prod: simplify by constant factor");
1655 constant_factor = 1;
1661 context_->UpdateRuleStats(
"int_prod: overflow if non zero");
1662 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1665 constant_factor = 1;
1669 if (ct->int_prod().exprs().size() == 1) {
1670 LinearExpressionProto*
const target =
1671 ct->mutable_int_prod()->mutable_target();
1672 LinearConstraintProto*
const lin =
1673 context_->working_model->add_constraints()->mutable_linear();
1675 if (context_->IsFixed(*target)) {
1676 int64_t target_value = context_->FixedValue(*target);
1677 if (target_value % constant_factor != 0) {
1678 return context_->NotifyThatModelIsUnsat(
1679 "int_prod: product incompatible with fixed target");
1682 lin->add_domain(target_value / constant_factor);
1683 lin->add_domain(target_value / constant_factor);
1685 context_->UpdateNewConstraintsVariableUsage();
1686 context_->UpdateRuleStats(
"int_prod: expression is constant.");
1687 return RemoveConstraint(ct);
1694 std::gcd(
static_cast<uint64_t
>(std::abs(constant_factor)),
1695 static_cast<uint64_t
>(std::abs(target_divisor)));
1697 constant_factor /= gcd;
1705 ct->int_prod().target(), 1, lin) ||
1707 ct->int_prod().exprs(0), -constant_factor, lin);
1712 lin->coeffs(), lin->domain(0))) {
1713 context_->working_model->mutable_constraints()->RemoveLast();
1717 context_->UpdateNewConstraintsVariableUsage();
1718 context_->UpdateRuleStats(
"int_prod: linearize product by constant.");
1719 return RemoveConstraint(ct);
1723 if (constant_factor != 1) {
1731 const LinearExpressionProto old_target = ct->int_prod().target();
1732 if (!context_->IsFixed(old_target)) {
1733 if (
CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1734 -context_->MinOf(old_target))) >=
1735 std::numeric_limits<int64_t>::max() / 2) {
1737 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1738 context_->UpdateRuleStats(
1739 "int_prod: overflow prevented creating a affine relation.");
1742 const int ref = old_target.vars(0);
1743 const int64_t coeff = old_target.coeffs(0);
1744 const int64_t offset = old_target.offset();
1745 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1749 if (context_->IsFixed(ref)) {
1755 if (context_->IsFixed(old_target)) {
1756 const int64_t target_value = context_->FixedValue(old_target);
1757 if (target_value % constant_factor != 0) {
1758 return context_->NotifyThatModelIsUnsat(
1759 "int_prod: constant factor does not divide constant target");
1762 proto->clear_target();
1763 proto->mutable_target()->set_offset(target_value / constant_factor);
1764 context_->UpdateRuleStats(
1765 "int_prod: divide product and fixed target by constant factor");
1768 const AffineRelation::Relation r =
1769 context_->GetAffineRelation(old_target.vars(0));
1770 const absl::int128 temp_coeff =
1771 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1772 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1773 const absl::int128 temp_offset =
1774 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1775 absl::int128(old_target.offset());
1776 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1777 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1778 const absl::int128 new_offset =
1779 temp_offset / absl::int128(constant_factor);
1787 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1788 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1789 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1790 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1791 return context_->NotifyThatModelIsUnsat(
1792 "int_prod: overflow during simplification.");
1796 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1797 proto->mutable_target()->set_vars(0, r.representative);
1798 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1799 context_->UpdateRuleStats(
"int_prod: divide product by constant factor");
1806 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1808 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1809 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1810 &domain_modified)) {
1813 if (domain_modified) {
1814 context_->UpdateRuleStats(absl::StrCat(
1815 is_square ?
"int_square" :
"int_prod",
": reduced target domain."));
1820 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1821 DCHECK_GE(target_max, 0);
1823 bool expr_reduced =
false;
1824 if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1825 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1829 context_->UpdateRuleStats(
"int_square: reduced expr domain.");
1833 if (ct->int_prod().exprs_size() == 2) {
1834 LinearExpressionProto a = ct->int_prod().exprs(0);
1835 LinearExpressionProto
b = ct->int_prod().exprs(1);
1836 const LinearExpressionProto product = ct->int_prod().target();
1840 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1843 context_->UpdateRuleStats(
"int_square: fix variable to zero or one.");
1844 return RemoveConstraint(ct);
1848 if (ct->int_prod().exprs().size() == 2) {
1849 const auto is_boolean_affine =
1850 [context = context_](
const LinearExpressionProto& expr) {
1851 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1852 context->MaxOf(expr.vars(0)) == 1;
1854 const LinearExpressionProto* boolean_linear =
nullptr;
1855 const LinearExpressionProto* other_linear =
nullptr;
1856 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1857 boolean_linear = &ct->int_prod().exprs(0);
1858 other_linear = &ct->int_prod().exprs(1);
1859 }
else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1860 boolean_linear = &ct->int_prod().exprs(1);
1861 other_linear = &ct->int_prod().exprs(0);
1863 if (boolean_linear) {
1870 ConstraintProto* constraint_for_false =
1871 context_->working_model->add_constraints();
1872 ConstraintProto* constraint_for_true =
1873 context_->working_model->add_constraints();
1874 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1875 constraint_for_false->add_enforcement_literal(
1877 LinearConstraintProto* linear_for_false =
1878 constraint_for_false->mutable_linear();
1879 LinearConstraintProto* linear_for_true =
1880 constraint_for_true->mutable_linear();
1882 linear_for_false->add_domain(0);
1883 linear_for_false->add_domain(0);
1885 *other_linear, boolean_linear->offset(), linear_for_false);
1889 linear_for_true->add_domain(0);
1890 linear_for_true->add_domain(0);
1892 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1896 context_->CanonicalizeLinearConstraint(constraint_for_false);
1897 context_->CanonicalizeLinearConstraint(constraint_for_true);
1899 linear_for_false->vars(),
1900 linear_for_false->coeffs()) ||
1902 linear_for_true->vars(),
1903 linear_for_true->coeffs())) {
1904 context_->working_model->mutable_constraints()->RemoveLast();
1905 context_->working_model->mutable_constraints()->RemoveLast();
1907 context_->UpdateRuleStats(
"int_prod: boolean affine term");
1908 context_->UpdateNewConstraintsVariableUsage();
1909 return RemoveConstraint(ct);
1915 const LinearExpressionProto target_expr = ct->int_prod().target();
1917 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1920 std::vector<int> literals;
1921 for (
const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1923 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1926 literals.push_back(lit);
1930 context_->UpdateRuleStats(
"int_prod: all Boolean.");
1932 ConstraintProto* new_ct = context_->working_model->add_constraints();
1933 new_ct->add_enforcement_literal(target);
1934 auto* arg = new_ct->mutable_bool_and();
1935 for (
const int lit : literals) {
1936 arg->add_literals(lit);
1940 ConstraintProto* new_ct = context_->working_model->add_constraints();
1941 auto* arg = new_ct->mutable_bool_or();
1942 arg->add_literals(target);
1943 for (
const int lit : literals) {
1947 context_->UpdateNewConstraintsVariableUsage();
1948 return RemoveConstraint(ct);
1951bool CpModelPresolver::PresolveIntDiv(
int c, ConstraintProto* ct) {
1952 if (context_->ModelIsUnsat())
return false;
1954 const LinearExpressionProto target = ct->int_div().target();
1955 const LinearExpressionProto expr = ct->int_div().exprs(0);
1956 const LinearExpressionProto div = ct->int_div().exprs(1);
1959 if (!context_->IntersectDomainWith(target, Domain(1))) {
1962 context_->UpdateRuleStats(
"int_div: y = x / x");
1963 return RemoveConstraint(ct);
1965 if (!context_->IntersectDomainWith(target, Domain(-1))) {
1968 context_->UpdateRuleStats(
"int_div: y = - x / x");
1969 return RemoveConstraint(ct);
1975 if (ct->enforcement_literal().empty() &&
1976 context_->ConstraintToVars(c).size() == 1) {
1977 const int var = context_->ConstraintToVars(c)[0];
1978 if (context_->DomainOf(var).Size() >= 100) {
1979 context_->UpdateRuleStats(
1980 "TODO int_div: single variable with large domain");
1982 std::vector<int64_t> possible_values;
1983 for (
const int64_t v : context_->DomainOf(var).Values()) {
1984 const int64_t target_v =
1985 EvaluateSingleVariableExpression(target, var, v);
1986 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
1987 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
1988 if (div_v == 0)
continue;
1989 if (target_v == expr_v / div_v) {
1990 possible_values.push_back(v);
1993 (void)context_->IntersectDomainWith(var,
1995 context_->UpdateRuleStats(
"int_div: single variable");
1996 return RemoveConstraint(ct);
2001 if (!context_->IsFixed(div))
return false;
2003 const int64_t divisor = context_->FixedValue(div);
2006 if (divisor == 1 || divisor == -1) {
2007 LinearConstraintProto*
const lin =
2008 context_->working_model->add_constraints()->mutable_linear();
2013 context_->UpdateNewConstraintsVariableUsage();
2014 context_->UpdateRuleStats(
"int_div: rewrite to equality");
2015 return RemoveConstraint(ct);
2020 bool domain_modified =
false;
2021 const Domain target_implied_domain =
2022 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
2024 if (!context_->IntersectDomainWith(target, target_implied_domain,
2025 &domain_modified)) {
2028 if (domain_modified) {
2030 if (target_implied_domain.IsFixed()) {
2031 context_->UpdateRuleStats(
2032 "int_div: target has been fixed by propagating X / cte");
2034 context_->UpdateRuleStats(
2035 "int_div: updated domain of target in target = X / cte");
2041 if (context_->IsFixed(target) &&
2043 1 + std::abs(context_->FixedValue(target)))) !=
2044 std::numeric_limits<int64_t>::max()) {
2045 int64_t t = context_->FixedValue(target);
2046 int64_t d = divisor;
2052 const Domain expr_implied_domain =
2054 ? Domain(t * d, (t + 1) * d - 1)
2055 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
2056 bool domain_modified =
false;
2057 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
2058 &domain_modified)) {
2061 if (domain_modified) {
2062 context_->UpdateRuleStats(
"int_div: target and divisor are fixed");
2064 context_->UpdateRuleStats(
"int_div: always true");
2066 return RemoveConstraint(ct);
2072 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2074 CapProd(divisor, context_->MaxOf(target)) !=
2075 std::numeric_limits<int64_t>::max()) {
2076 LinearConstraintProto*
const lin =
2077 context_->working_model->add_constraints()->mutable_linear();
2079 lin->add_domain(divisor - 1);
2082 context_->UpdateNewConstraintsVariableUsage();
2083 context_->UpdateRuleStats(
2084 "int_div: linearize positive division with a constant divisor");
2086 return RemoveConstraint(ct);
2094bool CpModelPresolver::PresolveIntMod(
int c, ConstraintProto* ct) {
2095 if (context_->ModelIsUnsat())
return false;
2098 const LinearExpressionProto target = ct->int_mod().target();
2099 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2100 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2102 if (context_->MinOf(target) > 0) {
2103 bool domain_changed =
false;
2104 if (!context_->IntersectDomainWith(
2105 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2109 if (domain_changed) {
2110 context_->UpdateRuleStats(
2111 "int_mod: non negative target implies positive expression");
2115 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2116 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2117 return context_->NotifyThatModelIsUnsat(
2118 "int_mod: incompatible target and mod");
2121 if (context_->MaxOf(target) < 0) {
2122 bool domain_changed =
false;
2123 if (!context_->IntersectDomainWith(
2124 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2128 if (domain_changed) {
2129 context_->UpdateRuleStats(
2130 "int_mod: non positive target implies negative expression");
2134 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2135 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2136 expr.vars().size() == 1) {
2138 const int64_t fixed_mod = context_->FixedValue(mod);
2139 const int64_t fixed_target = context_->FixedValue(target);
2141 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2143 fixed_target - expr.offset())) {
2147 context_->UpdateRuleStats(
"int_mod: fixed mod and target");
2148 return RemoveConstraint(ct);
2151 bool domain_changed =
false;
2152 if (!context_->IntersectDomainWith(
2154 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2155 context_->DomainSuperSetOf(mod)),
2160 if (domain_changed) {
2161 context_->UpdateRuleStats(
"int_mod: reduce target domain");
2169 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2170 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2171 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2172 target.vars(0) != expr.vars(0)) {
2173 const int64_t fixed_mod = context_->FixedValue(mod);
2174 std::vector<int64_t> values;
2175 const Domain dom = context_->DomainOf(target.vars(0));
2176 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2177 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2178 const int64_t target_term = rhs - target.offset();
2179 if (target_term % target.coeffs(0) != 0)
continue;
2180 if (dom.
Contains(target_term / target.coeffs(0))) {
2181 values.push_back(v);
2185 context_->UpdateRuleStats(
"int_mod: remove singleton target");
2186 if (!context_->IntersectDomainWith(expr.vars(0),
2190 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2192 context_->UpdateConstraintVariableUsage(c);
2193 context_->MarkVariableAsRemoved(target.vars(0));
2202bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto* ct) {
2203 bool changed =
false;
2208 if (ct->constraint_case() == ConstraintProto::kLinear) {
2209 for (
int& ref : *ct->mutable_enforcement_literal()) {
2210 const int rep = this->context_->GetLiteralRepresentative(ref);
2221 bool work_to_do =
false;
2222 for (
const int var : context_->ConstraintToVars(c)) {
2223 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2224 if (r.representative != var) {
2229 if (!work_to_do)
return false;
2233 [&changed,
this](
int* ref) {
2234 const int rep = this->context_->GetLiteralRepresentative(*ref);
2244bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2245 if (context_->ModelIsUnsat())
return false;
2249 const int num_vars = ct->linear().vars().size();
2250 for (
int i = 0;
i < num_vars; ++
i) {
2251 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2252 gcd = std::gcd(gcd, magnitude);
2253 if (gcd == 1)
break;
2256 context_->UpdateRuleStats(
"linear: divide by GCD");
2257 for (
int i = 0;
i < num_vars; ++
i) {
2258 ct->mutable_linear()->set_coeffs(
i, ct->linear().coeffs(
i) / gcd);
2262 if (ct->linear().domain_size() == 0) {
2263 return MarkConstraintAsFalse(ct);
2269bool CpModelPresolver::CanonicalizeLinearExpression(
2270 const ConstraintProto& ct, LinearExpressionProto* exp) {
2271 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2274bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct,
bool* changed) {
2275 if (ct->constraint_case() != ConstraintProto::kLinear)
return true;
2276 if (context_->ModelIsUnsat())
return false;
2278 if (ct->linear().domain().empty()) {
2279 context_->UpdateRuleStats(
"linear: no domain");
2281 return MarkConstraintAsFalse(ct);
2284 *changed = context_->CanonicalizeLinearConstraint(ct);
2285 *changed |= DivideLinearByGcd(ct);
2291 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2292 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2293 ref_coeff = -ref_coeff;
2296 ct->mutable_linear());
2302bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2303 if (ct->constraint_case() != ConstraintProto::kLinear ||
2304 context_->ModelIsUnsat()) {
2308 absl::btree_set<int> index_to_erase;
2309 const int num_vars = ct->linear().vars().size();
2315 for (
int i = 0;
i < num_vars; ++
i) {
2316 const int var = ct->linear().vars(
i);
2317 const int64_t coeff = ct->linear().coeffs(
i);
2319 if (context_->VariableIsUniqueAndRemovable(var)) {
2324 if (std::abs(coeff) != 1)
continue;
2327 const auto term_domain =
2328 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2329 if (!exact)
continue;
2332 const Domain new_rhs = rhs.AdditionWith(term_domain);
2333 if (new_rhs.NumIntervals() > 100)
continue;
2339 context_->UpdateRuleStats(
"linear: singleton column");
2340 index_to_erase.insert(
i);
2353 if (index_to_erase.empty()) {
2354 int num_singletons = 0;
2355 for (
const int var : ct->linear().vars()) {
2357 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2358 !context_->VariableIsUniqueAndRemovable(var)) {
2363 if (num_singletons == num_vars) {
2365 std::vector<Domain> domains;
2366 std::vector<int64_t> coeffs;
2367 std::vector<int64_t> costs;
2368 for (
int i = 0;
i < num_vars; ++
i) {
2369 const int var = ct->linear().vars(
i);
2371 domains.push_back(context_->DomainOf(var));
2372 coeffs.push_back(ct->linear().coeffs(
i));
2373 costs.push_back(context_->ObjectiveCoeff(var));
2375 BasicKnapsackSolver solver;
2376 const auto& result = solver.Solve(domains, coeffs, costs,
2378 if (!result.solved) {
2379 context_->UpdateRuleStats(
2380 "TODO independent linear: minimize single linear constraint");
2381 }
else if (result.infeasible) {
2382 context_->UpdateRuleStats(
2383 "independent linear: no DP solution to simple constraint");
2384 return MarkConstraintAsFalse(ct);
2386 if (ct->enforcement_literal().empty()) {
2388 context_->UpdateRuleStats(
"independent linear: solved by DP");
2389 for (
int i = 0;
i < num_vars; ++
i) {
2390 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2391 Domain(result.solution[
i]))) {
2395 return RemoveConstraint(ct);
2400 if (ct->enforcement_literal().size() == 1) {
2401 indicator = ct->enforcement_literal(0);
2404 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2405 auto* new_ct = context_->working_model->add_constraints();
2406 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2407 new_ct->mutable_bool_or()->add_literals(indicator);
2408 context_->UpdateNewConstraintsVariableUsage();
2410 for (
int i = 0;
i < num_vars; ++
i) {
2411 const int64_t best_value =
2412 costs[
i] > 0 ? domains[
i].Min() : domains[
i].Max();
2413 const int64_t other_value = result.solution[
i];
2414 if (best_value == other_value) {
2415 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2416 Domain(best_value))) {
2421 solution_crush_.SetVarToConditionalValue(
2422 ct->linear().vars(
i), {indicator}, other_value, best_value);
2424 if (!context_->StoreAffineRelation(ct->linear().vars(
i), indicator,
2425 other_value - best_value,
2430 if (!context_->StoreAffineRelation(
2432 best_value - other_value, other_value)) {
2437 context_->UpdateRuleStats(
2438 "independent linear: with enforcement, but solved by DP");
2439 return RemoveConstraint(ct);
2445 if (index_to_erase.empty()) {
2447 if (context_->params().presolve_substitution_level() <= 0)
return false;
2448 if (!ct->enforcement_literal().empty())
return false;
2452 if (rhs.
Min() != rhs.
Max())
return false;
2454 for (
int i = 0;
i < num_vars; ++
i) {
2455 const int var = ct->linear().vars(
i);
2456 const int64_t coeff = ct->linear().coeffs(
i);
2467 if (!context_->VariableWithCostIsUnique(var))
continue;
2468 DCHECK(context_->ObjectiveMap().contains(var));
2475 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2477 if (objective_coeff % coeff != 0)
continue;
2483 if (std::abs(objective_coeff) != 1)
continue;
2487 const auto term_domain =
2488 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2489 if (!exact)
continue;
2490 const Domain new_rhs = rhs.AdditionWith(term_domain);
2491 if (new_rhs.NumIntervals() > 100)
continue;
2499 if (context_->ObjectiveMap().size() == 1) {
2502 if (!context_->RecomputeSingletonObjectiveDomain()) {
2507 if (context_->IsFixed(var))
continue;
2509 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2510 if (context_->ModelIsUnsat())
return true;
2514 context_->UpdateRuleStats(
"linear: singleton column define objective.");
2515 context_->MarkVariableAsRemoved(var);
2516 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2517 return RemoveConstraint(ct);
2527 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2528 if (context_->ModelIsUnsat())
return true;
2532 context_->UpdateRuleStats(
2533 "linear: singleton column in equality and in objective.");
2535 index_to_erase.insert(
i);
2539 if (index_to_erase.empty())
return false;
2550 if (!ct->enforcement_literal().empty()) {
2551 for (
const int i : index_to_erase) {
2552 const int var = ct->linear().vars(
i);
2554 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2555 new_lin->add_vars(var);
2556 new_lin->add_coeffs(1);
2565 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2566 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2567 const std::string name(new_ct->name());
2569 new_ct->set_name(absl::StrCat(ct->name(),
" copy ", name));
2571 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2575 for (
int i = 0;
i < num_vars; ++
i) {
2576 if (index_to_erase.count(
i)) {
2577 context_->MarkVariableAsRemoved(ct->linear().vars(
i));
2580 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(
i));
2581 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(
i));
2584 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2585 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2587 DivideLinearByGcd(ct);
2593bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2594 int target_index, ConstraintProto* ct) {
2596 const int num_variables = ct->linear().vars().size();
2597 for (
int i = 0;
i < num_variables; ++
i) {
2598 if (
i == target_index)
continue;
2599 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2600 gcd = std::gcd(gcd, magnitude);
2601 if (gcd == 1)
return false;
2607 const int ref = ct->linear().vars(target_index);
2608 const int64_t coeff = ct->linear().coeffs(target_index);
2609 const int64_t rhs = ct->linear().domain(0);
2613 if (coeff % gcd == 0)
return false;
2615 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2621 bool changed =
false;
2622 (void)CanonicalizeLinear(ct, &changed);
2628bool IsLinearEqualityConstraint(
const ConstraintProto& ct) {
2629 return ct.constraint_case() == ConstraintProto::kLinear &&
2630 ct.linear().domain().size() == 2 &&
2631 ct.linear().domain(0) == ct.linear().domain(1) &&
2632 ct.enforcement_literal().
empty();
2648bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2649 if (context_->ModelIsUnsat())
return false;
2650 if (!IsLinearEqualityConstraint(*ct))
return false;
2652 const int num_variables = ct->linear().vars().size();
2653 if (num_variables < 2)
return false;
2655 std::vector<int> mod2_indices;
2656 std::vector<int> mod3_indices;
2657 std::vector<int> mod5_indices;
2659 int64_t min_magnitude;
2660 int num_smallest = 0;
2662 for (
int i = 0;
i < num_variables; ++
i) {
2663 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2664 if (num_smallest == 0 || magnitude < min_magnitude) {
2665 min_magnitude = magnitude;
2668 }
else if (magnitude == min_magnitude) {
2672 if (magnitude % 2 != 0) mod2_indices.push_back(
i);
2673 if (magnitude % 3 != 0) mod3_indices.push_back(
i);
2674 if (magnitude % 5 != 0) mod5_indices.push_back(
i);
2677 if (mod2_indices.size() == 2) {
2679 std::vector<int> literals;
2680 for (
const int i : mod2_indices) {
2681 const int ref = ct->linear().vars(
i);
2682 if (!context_->CanBeUsedAsLiteral(ref)) {
2686 literals.push_back(ref);
2689 const int64_t rhs = std::abs(ct->linear().domain(0));
2690 context_->UpdateRuleStats(
"linear: only two odd Booleans in equality");
2692 if (!context_->StoreBooleanEqualityRelation(literals[0],
2697 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2706 if (mod2_indices.size() == 1) {
2707 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2709 if (mod3_indices.size() == 1) {
2710 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2712 if (mod5_indices.size() == 1) {
2713 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2715 if (num_smallest == 1) {
2716 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2722bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2723 CHECK_EQ(ct->linear().vars().size(), 1);
2726 const int var = ct->linear().vars(0);
2727 const Domain var_domain = context_->DomainOf(var);
2731 if (rhs.IsEmpty()) {
2732 context_->UpdateRuleStats(
"linear1: infeasible");
2733 return MarkConstraintAsFalse(ct);
2735 if (rhs == var_domain) {
2736 context_->UpdateRuleStats(
"linear1: always true");
2737 return RemoveConstraint(ct);
2742 if (ct->linear().coeffs(0) != 1) {
2743 context_->UpdateRuleStats(
"linear1: canonicalized");
2744 ct->mutable_linear()->set_coeffs(0, 1);
2750 context_->UpdateRuleStats(
"linear1: without enforcement");
2751 if (!context_->IntersectDomainWith(var, rhs))
return false;
2752 return RemoveConstraint(ct);
2756 if (context_->CanBeUsedAsLiteral(var)) {
2757 DCHECK(rhs.IsFixed());
2758 if (rhs.FixedValue() == 1) {
2759 ct->mutable_bool_and()->add_literals(var);
2761 CHECK_EQ(rhs.FixedValue(), 0);
2762 ct->mutable_bool_and()->add_literals(
NegatedRef(var));
2771 bool changed =
false;
2772 if (ct->enforcement_literal().size() == 1) {
2775 int lit = ct->enforcement_literal(0);
2780 const int representative = context_->GetLiteralRepresentative(lit);
2781 if (lit != representative) {
2782 lit = representative;
2783 ct->set_enforcement_literal(0, lit);
2784 context_->UpdateRuleStats(
"linear1: remapped enforcement literal");
2788 if (rhs.IsFixed()) {
2789 const int64_t value = rhs.FixedValue();
2791 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2792 if (lit == encoding_lit)
return changed;
2793 context_->AddImplication(lit, encoding_lit);
2794 context_->UpdateNewConstraintsVariableUsage();
2796 context_->UpdateRuleStats(
"linear1: transformed to implication");
2799 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2802 context_->modified_domains.Set(var);
2804 context_->UpdateNewConstraintsVariableUsage();
2809 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2810 if (complement.IsFixed()) {
2811 const int64_t value = complement.FixedValue();
2813 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2814 if (
NegatedRef(lit) == encoding_lit)
return changed;
2815 context_->AddImplication(lit,
NegatedRef(encoding_lit));
2816 context_->UpdateNewConstraintsVariableUsage();
2818 context_->UpdateRuleStats(
"linear1: transformed to implication");
2821 if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2824 context_->modified_domains.Set(var);
2826 context_->UpdateNewConstraintsVariableUsage();
2835bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2836 DCHECK_EQ(ct->linear().vars().size(), 2);
2838 const LinearConstraintProto& arg = ct->linear();
2839 const int var1 = arg.vars(0);
2840 const int var2 = arg.vars(1);
2841 const int64_t coeff1 = arg.coeffs(0);
2842 const int64_t coeff2 = arg.coeffs(1);
2846 const LinearExpression2 expr2 =
2847 GetLinearExpression2FromProto(var1, coeff1, var2, coeff2);
2848 const IntegerValue lb(arg.domain(0));
2849 const IntegerValue ub(arg.domain(arg.domain().size() - 1));
2851 const RelationStatus status = known_linear2_.GetStatus(expr2, lb, ub);
2861 if (!ct->enforcement_literal().empty() &&
2862 ct->linear().domain().size() == 2) {
2863 context_->UpdateRuleStats(
"linear2: already known enforced relation");
2864 return RemoveConstraint(ct);
2867 context_->UpdateRuleStats(
"linear2: infeasible relation");
2868 return MarkConstraintAsFalse(ct);
2869 }
else if (ct->enforcement_literal().empty()) {
2870 known_linear2_.Add(expr2, lb, ub);
2883 const bool is_equality =
2884 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2887 int64_t value_on_true, coeff;
2888 if (context_->CanBeUsedAsLiteral(var1)) {
2890 value_on_true = coeff1;
2893 }
else if (context_->CanBeUsedAsLiteral(var2)) {
2895 value_on_true = coeff2;
2904 const Domain rhs_if_true =
2905 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2906 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2907 const bool implied_false =
2908 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2909 const bool implied_true =
2910 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2911 if (implied_true && implied_false) {
2912 context_->UpdateRuleStats(
"linear2: infeasible.");
2913 return MarkConstraintAsFalse(ct);
2914 }
else if (implied_true) {
2915 context_->UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2918 ConstraintProto* new_ct = context_->working_model->add_constraints();
2919 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2920 new_ct->mutable_bool_and()->add_literals(lit);
2921 context_->UpdateNewConstraintsVariableUsage();
2924 ct->mutable_linear()->Clear();
2925 ct->mutable_linear()->add_vars(var);
2926 ct->mutable_linear()->add_coeffs(1);
2928 return PresolveLinearOfSizeOne(ct) ||
true;
2929 }
else if (implied_false) {
2930 context_->UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2933 ConstraintProto* new_ct = context_->working_model->add_constraints();
2934 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2935 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
2936 context_->UpdateNewConstraintsVariableUsage();
2939 ct->mutable_linear()->Clear();
2940 ct->mutable_linear()->add_vars(var);
2941 ct->mutable_linear()->add_coeffs(1);
2943 return PresolveLinearOfSizeOne(ct) ||
true;
2944 }
else if (ct->enforcement_literal().empty() &&
2945 !context_->CanBeUsedAsLiteral(var)) {
2950 context_->UpdateRuleStats(
"linear2: contains a Boolean.");
2953 const Domain var_domain = context_->DomainOf(var);
2954 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2955 ConstraintProto* new_ct = context_->working_model->add_constraints();
2956 new_ct->add_enforcement_literal(lit);
2957 new_ct->mutable_linear()->add_vars(var);
2958 new_ct->mutable_linear()->add_coeffs(1);
2960 new_ct->mutable_linear());
2964 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2965 ConstraintProto* new_ct = context_->working_model->add_constraints();
2966 new_ct->add_enforcement_literal(
NegatedRef(lit));
2967 new_ct->mutable_linear()->add_vars(var);
2968 new_ct->mutable_linear()->add_coeffs(1);
2970 new_ct->mutable_linear());
2973 context_->UpdateNewConstraintsVariableUsage();
2974 return RemoveConstraint(ct);
2978 context_->UpdateRuleStats(
"TODO linear2: contains a Boolean.");
2983 const int64_t rhs = arg.domain(0);
2984 if (ct->enforcement_literal().empty()) {
2991 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2992 }
else if (coeff2 == 1) {
2993 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2994 }
else if (coeff1 == -1) {
2995 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2996 }
else if (coeff2 == -1) {
2997 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
3006 context_->UpdateRuleStats(
"TODO linear2: ax + by = cte");
3008 if (added)
return RemoveConstraint(ct);
3017 context_->UpdateRuleStats(
3018 "linear2: implied ax + by = cte has no solutions");
3019 return MarkConstraintAsFalse(ct);
3021 const Domain reduced_domain =
3022 context_->DomainOf(var1)
3023 .AdditionWith(Domain(-x0))
3024 .InverseMultiplicationBy(
b)
3025 .IntersectionWith(context_->DomainOf(var2)
3026 .AdditionWith(Domain(-y0))
3027 .InverseMultiplicationBy(-a));
3029 if (reduced_domain.IsEmpty()) {
3030 context_->UpdateRuleStats(
3031 "linear2: implied ax + by = cte has no solutions");
3032 return MarkConstraintAsFalse(ct);
3035 if (reduced_domain.Size() == 1) {
3036 const int64_t z = reduced_domain.FixedValue();
3037 const int64_t value1 = x0 +
b * z;
3038 const int64_t value2 = y0 - a * z;
3040 DCHECK(context_->DomainContains(var1, value1));
3041 DCHECK(context_->DomainContains(var2, value2));
3042 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
3044 ConstraintProto* imply1 = context_->working_model->add_constraints();
3045 *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
3046 imply1->mutable_linear()->add_vars(var1);
3047 imply1->mutable_linear()->add_coeffs(1);
3048 imply1->mutable_linear()->add_domain(value1);
3049 imply1->mutable_linear()->add_domain(value1);
3051 ConstraintProto* imply2 = context_->working_model->add_constraints();
3052 *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
3053 imply2->mutable_linear()->add_vars(var2);
3054 imply2->mutable_linear()->add_coeffs(1);
3055 imply2->mutable_linear()->add_domain(value2);
3056 imply2->mutable_linear()->add_domain(value2);
3057 context_->UpdateRuleStats(
3058 "linear2: implied ax + by = cte has only one solution");
3059 context_->UpdateNewConstraintsVariableUsage();
3060 return RemoveConstraint(ct);
3067bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
3068 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
3069 if (context_->ModelIsUnsat())
return false;
3071 if (ct->linear().vars().empty()) {
3072 context_->UpdateRuleStats(
"linear: empty");
3075 return RemoveConstraint(ct);
3077 return MarkConstraintAsFalse(ct);
3079 }
else if (ct->linear().vars().size() == 1) {
3080 return PresolveLinearOfSizeOne(ct);
3081 }
else if (ct->linear().vars().size() == 2) {
3082 return PresolveLinearOfSizeTwo(ct);
3088bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
3089 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
3090 if (ct->linear().vars().size() <= 1)
return false;
3091 if (context_->ModelIsUnsat())
return false;
3094 if (context_->params().enumerate_all_solutions())
return false;
3096 const LinearConstraintProto& linear_constraint = ct->linear();
3097 if (linear_constraint.domain_size() != 2)
return false;
3098 if (linear_constraint.domain(0) != linear_constraint.domain(1))
return false;
3100 std::vector<int64_t> lbs(linear_constraint.vars_size());
3101 std::vector<int64_t> ubs(linear_constraint.vars_size());
3102 for (
int i = 0;
i < linear_constraint.vars_size(); ++
i) {
3103 lbs[
i] = context_->MinOf(linear_constraint.vars(
i));
3104 ubs[
i] = context_->MaxOf(linear_constraint.vars(
i));
3107 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3109 if (!diophantine_solution.has_solutions) {
3110 context_->UpdateRuleStats(
"diophantine: equality has no solutions");
3111 return MarkConstraintAsFalse(ct);
3113 if (diophantine_solution.no_reformulation_needed)
return false;
3116 for (
const std::vector<absl::int128>&
b : diophantine_solution.kernel_basis) {
3118 context_->UpdateRuleStats(
3119 "diophantine: couldn't apply due to int64_t overflow");
3124 context_->UpdateRuleStats(
3125 "diophantine: couldn't apply due to int64_t overflow");
3129 const int num_replaced_variables =
3130 static_cast<int>(diophantine_solution.special_solution.size());
3131 const int num_new_variables =
3132 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3133 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3134 for (
int i = 0;
i < num_new_variables; ++
i) {
3137 context_->UpdateRuleStats(
3138 "diophantine: couldn't apply due to int64_t overflow");
3148 std::vector<int> new_variables(num_new_variables);
3149 for (
int i = 0;
i < num_new_variables; ++
i) {
3150 new_variables[
i] = context_->working_model->variables_size();
3151 IntegerVariableProto* var = context_->working_model->add_variables();
3153 static_cast<int64_t
>(diophantine_solution.kernel_vars_lbs[
i]));
3155 static_cast<int64_t
>(diophantine_solution.kernel_vars_ubs[
i]));
3156 if (!ct->name().empty()) {
3157 var->set_name(absl::StrCat(
"u_diophantine_", ct->name(),
"_",
i));
3167 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3168 ConstraintProto* identity = context_->working_model->add_constraints();
3169 LinearConstraintProto* lin = identity->mutable_linear();
3170 if (!ct->name().empty()) {
3171 identity->set_name(absl::StrCat(
"c_diophantine_", ct->name(),
"_",
i));
3173 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3175 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
3178 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3180 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3181 for (
int j = std::max(1,
i); j < num_replaced_variables; ++j) {
3182 lin->add_vars(new_variables[j - 1]);
3184 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3186 for (
int j = num_replaced_variables; j < linear_constraint.vars_size();
3189 linear_constraint.vars(diophantine_solution.index_permutation[j]));
3191 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3199 context_->UpdateRuleStats(
3200 "diophantine: couldn't apply due to overflowing activity of new "
3203 context_->working_model->mutable_constraints()->DeleteSubrange(
3204 context_->working_model->constraints_size() -
i - 1,
i + 1);
3205 context_->working_model->mutable_variables()->DeleteSubrange(
3206 context_->working_model->variables_size() - num_new_variables,
3211 context_->InitializeNewDomains();
3215 const int num_constraints = context_->working_model->constraints_size();
3216 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3217 const LinearConstraintProto& linear =
3218 context_->working_model->constraints(num_constraints - 1 -
i).linear();
3219 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3220 solution_crush_.SetVarToLinearConstraintSolution(
3221 std::nullopt, linear.vars(), linear.coeffs(), linear.domain(0));
3224 if (VLOG_IS_ON(2)) {
3225 std::string log_eq = absl::StrCat(linear_constraint.domain(0),
" = ");
3226 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3227 for (
int i = 0;
i < terms_to_show; ++
i) {
3228 if (
i > 0) absl::StrAppend(&log_eq,
" + ");
3231 linear_constraint.coeffs(diophantine_solution.index_permutation[
i]),
3233 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
3235 if (terms_to_show < linear_constraint.vars_size()) {
3236 absl::StrAppend(&log_eq,
"+ ... (", linear_constraint.vars_size(),
3239 VLOG(2) <<
"[Diophantine] " << log_eq;
3242 context_->UpdateRuleStats(
"diophantine: reformulated equality");
3243 context_->UpdateNewConstraintsVariableUsage();
3244 return RemoveConstraint(ct);
3259void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3260 int c, ConstraintProto* ct) {
3261 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3262 if (context_->ModelIsUnsat())
return;
3265 const LinearConstraintProto& lin = ct->linear();
3266 if (lin.domain().size() != 2)
return;
3272 int64_t max_variation = 0;
3274 rd_entries_.clear();
3275 rd_magnitudes_.clear();
3279 int64_t max_magnitude = 0;
3280 const int num_terms = lin.vars().size();
3281 for (
int i = 0;
i < num_terms; ++
i) {
3282 const int64_t coeff = lin.coeffs(
i);
3283 const int64_t magnitude = std::abs(lin.coeffs(
i));
3284 if (magnitude == 0)
continue;
3285 max_magnitude = std::max(max_magnitude, magnitude);
3290 lb = context_->MinOf(lin.vars(
i));
3291 ub = context_->MaxOf(lin.vars(
i));
3293 lb = -context_->MaxOf(lin.vars(
i));
3294 ub = -context_->MinOf(lin.vars(
i));
3296 lb_sum += lb * magnitude;
3297 ub_sum += ub * magnitude;
3300 if (lb == ub)
return;
3302 rd_lbs_.push_back(lb);
3303 rd_ubs_.push_back(ub);
3304 rd_magnitudes_.push_back(magnitude);
3305 rd_entries_.push_back({magnitude, magnitude * (ub - lb),
i});
3306 max_variation += rd_entries_.back().max_variation;
3311 if (lb_sum > rhs.
Max() || rhs.
Min() > ub_sum) {
3312 (void)MarkConstraintAsFalse(ct);
3313 context_->UpdateConstraintVariableUsage(c);
3316 const IntegerValue rhs_ub(
CapSub(rhs.
Max(), lb_sum));
3317 const IntegerValue rhs_lb(
CapSub(ub_sum, rhs.
Min()));
3318 const bool use_ub = max_variation > rhs_ub;
3319 const bool use_lb = max_variation > rhs_lb;
3320 if (!use_ub && !use_lb) {
3321 (void)RemoveConstraint(ct);
3322 context_->UpdateConstraintVariableUsage(c);
3327 if (max_magnitude <= 1)
return;
3332 lb_feasible_.Reset(rhs_lb.value());
3333 lb_infeasible_.Reset(rhs.
Min() - lb_sum - 1);
3336 ub_feasible_.Reset(rhs_ub.value());
3337 ub_infeasible_.Reset(ub_sum - rhs.
Max() - 1);
3343 int64_t max_error = max_variation;
3344 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3345 [](
const RdEntry& a,
const RdEntry&
b) {
3346 return a.magnitude > b.magnitude;
3349 rd_divisors_.clear();
3350 for (
int i = 0;
i < rd_entries_.size(); ++
i) {
3351 const RdEntry& e = rd_entries_[
i];
3352 gcd = std::gcd(gcd, e.magnitude);
3353 max_error -= e.max_variation;
3359 range += e.max_variation / e.magnitude;
3360 if (
i + 1 < rd_entries_.size() &&
3361 e.magnitude == rd_entries_[
i + 1].magnitude) {
3364 const int64_t saved_range = range;
3367 if (e.magnitude > 1) {
3372 rd_divisors_.push_back(e.magnitude);
3376 bool simplify_lb =
false;
3378 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3379 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3382 if (
CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3383 lb_feasible_.Bound()) {
3388 if (
CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3389 lb_infeasible_.Bound()) {
3395 bool simplify_ub =
false;
3397 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3398 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3399 if (
CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3400 ub_feasible_.Bound()) {
3403 if (
CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3404 ub_infeasible_.Bound()) {
3411 if (max_error == 0)
break;
3412 if (simplify_lb && simplify_ub) {
3414 context_->UpdateRuleStats(
"linear: remove irrelevant part");
3415 int64_t shift_lb = 0;
3416 int64_t shift_ub = 0;
3419 for (
int j = 0; j <=
i; ++j) {
3420 const int index = rd_entries_[j].index;
3421 const int64_t m = rd_magnitudes_[index];
3422 shift_lb += rd_lbs_[index] * m;
3423 shift_ub += rd_ubs_[index] * m;
3424 rd_vars_.push_back(lin.vars(index));
3425 rd_coeffs_.push_back(lin.coeffs(index));
3427 LinearConstraintProto* mut_lin = ct->mutable_linear();
3428 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3429 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3435 const int64_t new_rhs_lb =
3436 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3437 const int64_t new_rhs_ub =
3438 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3439 if (new_rhs_lb > new_rhs_ub) {
3440 (void)MarkConstraintAsFalse(ct);
3441 context_->UpdateConstraintVariableUsage(c);
3445 DivideLinearByGcd(ct);
3446 context_->UpdateConstraintVariableUsage(c);
3454 if (DivideLinearByGcd(ct)) {
3455 context_->UpdateConstraintVariableUsage(c);
3462 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3463 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3464 context_->UpdateRuleStats(
"linear: reduce rhs with DP");
3465 const int64_t new_rhs_lb =
3466 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3467 const int64_t new_rhs_ub =
3468 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3469 if (new_rhs_lb > new_rhs_ub) {
3470 (void)MarkConstraintAsFalse(ct);
3471 context_->UpdateConstraintVariableUsage(c);
3478 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3479 for (
const int64_t divisor : rd_divisors_) {
3483 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.
Max(), &new_ub)) {
3488 int64_t minus_new_lb;
3489 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3490 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3491 rd_lbs_[
i] = -rd_lbs_[
i];
3492 rd_ubs_[
i] = -rd_ubs_[
i];
3495 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.
Min(),
3497 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3498 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3499 rd_lbs_[
i] = -rd_lbs_[
i];
3500 rd_ubs_[
i] = -rd_ubs_[
i];
3506 context_->UpdateRuleStats(
"linear: simplify using approximate gcd");
3508 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3509 for (
int i = 0;
i < lin.coeffs().size(); ++
i) {
3510 const int64_t new_coeff =
3512 if (new_coeff == 0)
continue;
3513 mutable_linear->set_vars(new_size, lin.vars(
i));
3514 mutable_linear->set_coeffs(new_size, new_coeff);
3517 mutable_linear->mutable_vars()->Truncate(new_size);
3518 mutable_linear->mutable_coeffs()->Truncate(new_size);
3519 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3520 if (new_rhs.IsEmpty()) {
3521 (void)MarkConstraintAsFalse(ct);
3525 context_->UpdateConstraintVariableUsage(c);
3535bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
3536 const Domain& terms,
const Domain& rhs) {
3537 if (var_domain.NumIntervals() != 1)
return false;
3538 if (std::abs(coeff) != 1)
return false;
3546 if (coeff == 1 && terms.
Max() + var_domain.
Min() <= rhs.
Min()) {
3549 if (coeff == -1 && terms.
Max() - var_domain.
Max() <= rhs.
Min()) {
3555bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
3556 const Domain& terms,
const Domain& rhs) {
3557 if (var_domain.NumIntervals() != 1)
return false;
3558 if (std::abs(coeff) != 1)
return false;
3560 if (coeff == 1 && terms.
Min() + var_domain.
Max() >= rhs.
Max()) {
3563 if (coeff == -1 && terms.
Min() - var_domain.
Min() >= rhs.
Max()) {
3569int FixLiteralFromSet(
const absl::flat_hash_set<int>& literals_at_true,
3570 LinearConstraintProto* linear) {
3573 const int num_terms = linear->vars().size();
3575 for (
int i = 0;
i < num_terms; ++
i) {
3576 const int var = linear->vars(
i);
3577 const int64_t coeff = linear->coeffs(
i);
3578 if (literals_at_true.contains(var)) {
3582 }
else if (!literals_at_true.contains(
NegatedRef(var))) {
3583 linear->set_vars(new_size, var);
3584 linear->set_coeffs(new_size, coeff);
3591 linear->mutable_vars()->Truncate(new_size);
3592 linear->mutable_coeffs()->Truncate(new_size);
3602void CpModelPresolver::ProcessAtMostOneAndLinear() {
3603 if (time_limit_->LimitReached())
return;
3604 if (context_->ModelIsUnsat())
return;
3605 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
3606 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3608 ActivityBoundHelper amo_in_linear;
3609 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3611 int num_changes = 0;
3612 const int num_constraints = context_->working_model->constraints_size();
3613 for (
int c = 0;
c < num_constraints; ++
c) {
3614 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3615 if (ct->constraint_case() != ConstraintProto::kLinear)
continue;
3618 for (
int i = 0;
i < 5; ++
i) {
3619 const int old_size = ct->linear().vars().size();
3620 const int old_enf_size = ct->enforcement_literal().size();
3621 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3622 if (context_->ModelIsUnsat())
return;
3623 if (ct->constraint_case() != ConstraintProto::kLinear)
break;
3624 if (ct->linear().vars().size() == old_size &&
3625 ct->enforcement_literal().size() == old_enf_size) {
3632 timer.AddCounter(
"num_changes", num_changes);
3640void CpModelPresolver::ProcessOneLinearWithAmo(
int ct_index,
3641 ConstraintProto* ct,
3643 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3644 if (ct->linear().vars().size() <= 1)
return;
3651 Domain non_boolean_domain(0);
3652 const int initial_size = ct->linear().vars().size();
3653 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3654 int64_t max_magnitude = 0;
3655 for (
int i = 0;
i < initial_size; ++
i) {
3657 int ref = ct->linear().vars(
i);
3658 int64_t coeff = ct->linear().coeffs(
i);
3663 if (context_->CanBeUsedAsLiteral(ref)) {
3664 tmp_terms_.push_back({ref, coeff});
3665 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3666 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3668 non_boolean_domain =
3671 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3672 .RelaxIfTooComplex();
3673 temp_ct_.mutable_linear()->add_vars(ref);
3674 temp_ct_.mutable_linear()->add_coeffs(coeff);
3679 if (tmp_terms_.empty())
return;
3690 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3691 min_magnitude < max_magnitude) {
3692 int64_t min_activity = 0;
3693 int64_t max_activity = 0;
3694 for (
const auto [ref, coeff] : tmp_terms_) {
3696 max_activity += coeff;
3698 min_activity += coeff;
3701 const int64_t transformed_rhs = rhs.
Max() - min_activity;
3702 if (min_activity >= rhs.
Min() && max_magnitude <= transformed_rhs) {
3703 std::vector<int> literals;
3704 for (
const auto [ref, coeff] : tmp_terms_) {
3705 if (coeff + min_magnitude > transformed_rhs)
continue;
3706 literals.push_back(coeff > 0 ? ref :
NegatedRef(ref));
3708 if (helper->IsAmo(literals)) {
3710 context_->UpdateRuleStats(
"linear + amo: detect hidden AMO");
3712 for (
int i = 0;
i < initial_size; ++
i) {
3714 if (ct->linear().coeffs(
i) > 0) {
3715 ct->mutable_linear()->set_coeffs(
i, 1);
3717 ct->mutable_linear()->set_coeffs(
i, -1);
3728 const int64_t min_bool_activity =
3729 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3730 const int64_t max_bool_activity =
3731 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3735 const Domain activity = non_boolean_domain.AdditionWith(
3736 Domain(min_bool_activity, max_bool_activity));
3737 if (activity.IntersectionWith(rhs).IsEmpty()) {
3739 context_->UpdateRuleStats(
"linear + amo: infeasible linear constraint");
3740 (void)MarkConstraintAsFalse(ct);
3741 context_->UpdateConstraintVariableUsage(ct_index);
3743 }
else if (activity.IsIncludedIn(rhs)) {
3744 context_->UpdateRuleStats(
"linear + amo: trivial linear constraint");
3746 context_->UpdateConstraintVariableUsage(ct_index);
3751 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3754 Domain(min_bool_activity, max_bool_activity).Negation()),
3755 temp_ct_.mutable_linear());
3756 if (!PropagateDomainsInLinear(-1, &temp_ct_)) {
3759 if (context_->ModelIsUnsat())
return;
3774 std::vector<int> new_enforcement;
3775 std::vector<int> must_be_true;
3776 for (
int i = 0;
i < tmp_terms_.size(); ++
i) {
3777 const int ref = tmp_terms_[
i].first;
3779 const Domain bool0(conditional_mins_[
i][0], conditional_maxs_[
i][0]);
3780 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3781 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3783 must_be_true.push_back(ref);
3784 }
else if (activity0.IsIncludedIn(rhs)) {
3786 new_enforcement.push_back(ref);
3789 const Domain bool1(conditional_mins_[
i][1], conditional_maxs_[
i][1]);
3790 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3791 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3794 }
else if (activity1.IsIncludedIn(rhs)) {
3805 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3808 context_->UpdateRuleStats(
"linear + amo: fixed literal",
3809 must_be_true.size());
3810 for (
const int lit : must_be_true) {
3811 if (!context_->SetLiteralToTrue(lit))
return;
3813 bool changed =
false;
3814 if (!CanonicalizeLinear(ct, &changed))
return;
3815 context_->UpdateConstraintVariableUsage(ct_index);
3819 if (!new_enforcement.empty()) {
3820 context_->UpdateRuleStats(
"linear + amo: extracted enforcement literal",
3821 new_enforcement.size());
3822 for (
const int ref : new_enforcement) {
3823 ct->add_enforcement_literal(ref);
3827 if (!ct->enforcement_literal().empty()) {
3828 const int old_enf_size = ct->enforcement_literal().size();
3829 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3830 context_->UpdateRuleStats(
"linear + amo: infeasible enforcement");
3832 context_->UpdateConstraintVariableUsage(ct_index);
3835 if (ct->enforcement_literal().size() < old_enf_size) {
3836 context_->UpdateRuleStats(
"linear + amo: simplified enforcement list");
3837 context_->UpdateConstraintVariableUsage(ct_index);
3840 for (
const int lit : must_be_true) {
3844 context_->UpdateRuleStats(
3845 "linear + amo: advanced infeasible linear constraint");
3846 (void)MarkConstraintAsFalse(ct);
3847 context_->UpdateConstraintVariableUsage(ct_index);
3853 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3857 context_->UpdateRuleStats(
"linear + amo: added implications");
3858 ConstraintProto* new_ct = context_->working_model->add_constraints();
3859 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3860 for (
const int lit : must_be_true) {
3861 new_ct->mutable_bool_and()->add_literals(lit);
3862 temp_set_.insert(lit);
3864 context_->UpdateNewConstraintsVariableUsage();
3867 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3868 if (num_fixed > new_enforcement.size()) {
3869 context_->UpdateRuleStats(
3870 "linear + amo: fixed literal implied by enforcement");
3872 if (num_fixed > 0) {
3873 context_->UpdateConstraintVariableUsage(ct_index);
3877 if (ct->linear().vars().empty()) {
3878 context_->UpdateRuleStats(
"linear + amo: empty after processing");
3879 PresolveSmallLinear(ct);
3880 context_->UpdateConstraintVariableUsage(ct_index);
3885 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
3886 context_->UpdateConstraintVariableUsage(ct_index);
3887 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3895 if (!ct->enforcement_literal().empty()) {
3898 Domain non_boolean_domain(0);
3899 const int num_ct_terms = ct->linear().vars().size();
3900 for (
int i = 0;
i < num_ct_terms; ++
i) {
3901 const int ref = ct->linear().vars(
i);
3902 const int64_t coeff = ct->linear().coeffs(
i);
3904 if (context_->CanBeUsedAsLiteral(ref)) {
3905 tmp_terms_.push_back({ref, coeff});
3907 non_boolean_domain =
3910 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3911 .RelaxIfTooComplex();
3914 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3916 if (num_removed > 0) {
3917 context_->UpdateRuleStats(
"linear + amo: removed enforcement literal",
3919 context_->UpdateConstraintVariableUsage(ct_index);
3924bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
3925 ConstraintProto* ct) {
3926 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
3927 if (context_->ModelIsUnsat())
return false;
3930 int64_t min_activity;
3931 int64_t max_activity;
3934 const int num_vars = ct->linear().vars_size();
3935 auto& term_domains = context_->tmp_term_domains;
3936 auto& left_domains = context_->tmp_left_domains;
3937 const bool slow_mode = num_vars < 10;
3941 term_domains.resize(num_vars + 1);
3942 left_domains.resize(num_vars + 1);
3943 left_domains[0] = Domain(0);
3944 term_domains[num_vars] = Domain(0);
3945 for (
int i = 0;
i < num_vars; ++
i) {
3946 const int var = ct->linear().vars(
i);
3947 const int64_t coeff = ct->linear().coeffs(
i);
3949 term_domains[
i] = context_->DomainOf(var).MultiplicationBy(coeff);
3950 left_domains[
i + 1] =
3951 left_domains[
i].AdditionWith(term_domains[
i]).RelaxIfTooComplex();
3954 std::tie(min_activity, max_activity) =
3955 context_->ComputeMinMaxActivity(ct->linear());
3957 const Domain& implied_rhs =
3958 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3962 if (implied_rhs.IsIncludedIn(old_rhs)) {
3963 if (ct_index != -1) context_->UpdateRuleStats(
"linear: always true");
3964 return RemoveConstraint(ct);
3968 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3969 if (rhs.IsEmpty()) {
3970 context_->UpdateRuleStats(
"linear: infeasible");
3971 return MarkConstraintAsFalse(ct);
3973 if (rhs != old_rhs) {
3974 if (ct_index != -1) context_->UpdateRuleStats(
"linear: simplified rhs");
3979 if (ct->enforcement_literal().size() > 1)
return false;
3981 bool new_bounds =
false;
3982 bool recanonicalize =
false;
3983 Domain negated_rhs = rhs.Negation();
3984 Domain right_domain(0);
3986 Domain activity_minus_term;
3987 for (
int i = num_vars - 1;
i >= 0; --
i) {
3988 const int var = ct->linear().vars(
i);
3989 const int64_t var_coeff = ct->linear().coeffs(
i);
3993 right_domain.AdditionWith(term_domains[
i + 1]).RelaxIfTooComplex();
3994 activity_minus_term = left_domains[
i].AdditionWith(right_domain);
3996 int64_t min_term = var_coeff * context_->MinOf(var);
3997 int64_t max_term = var_coeff * context_->MaxOf(var);
3998 if (var_coeff < 0) std::swap(min_term, max_term);
3999 activity_minus_term =
4000 Domain(min_activity - min_term, max_activity - max_term);
4002 new_domain = activity_minus_term.AdditionWith(negated_rhs)
4003 .InverseMultiplicationBy(-var_coeff);
4005 if (ct->enforcement_literal().empty()) {
4007 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
4010 }
else if (ct->enforcement_literal().size() == 1) {
4013 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
4014 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
4019 if (context_->IsFixed(var)) {
4021 recanonicalize =
true;
4026 if (ct_index == -1)
continue;
4027 if (!ct->enforcement_literal().empty())
continue;
4039 if (rhs.
Min() != rhs.
Max() &&
4040 context_->VariableWithCostIsUniqueAndRemovable(var)) {
4041 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
4042 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
4044 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
4045 activity_minus_term, rhs)) {
4046 rhs = Domain(rhs.
Min());
4049 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
4050 activity_minus_term, rhs)) {
4051 rhs = Domain(rhs.
Max());
4055 context_->UpdateRuleStats(
"linear: tightened into equality");
4057 solution_crush_.SetVarToLinearConstraintSolution(
4058 i, ct->linear().vars(), ct->linear().coeffs(), rhs.FixedValue());
4060 negated_rhs = rhs.Negation();
4064 right_domain = Domain(0);
4078 if (ct->linear().vars().size() <= 2)
continue;
4083 if (rhs.
Min() != rhs.
Max())
continue;
4090 if (context_->params().keep_all_feasible_solutions_in_presolve())
continue;
4096 if (std::abs(var_coeff) != 1)
continue;
4097 if (context_->params().presolve_substitution_level() <= 0)
continue;
4100 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
4102 int col_size = context_->VarToConstraints(var).size();
4103 if (is_in_objective) col_size--;
4104 const int row_size = ct->linear().vars_size();
4108 const int num_entries_added = (row_size - 1) * (col_size - 1);
4109 const int num_entries_removed = col_size + row_size - 1;
4110 if (num_entries_added > num_entries_removed)
continue;
4115 std::vector<int> others;
4117 for (
const int c : context_->VarToConstraints(var)) {
4123 if (c == ct_index)
continue;
4124 if (context_->working_model->constraints(c).constraint_case() !=
4125 ConstraintProto::kLinear) {
4129 for (
const int ref :
4130 context_->working_model->constraints(c).enforcement_literal()) {
4137 others.push_back(c);
4139 if (abort)
continue;
4145 if (context_->DomainOf(var) != new_domain) {
4152 if (others.size() != 1)
continue;
4153 const ConstraintProto& other_ct =
4154 context_->working_model->constraints(others.front());
4155 if (!other_ct.enforcement_literal().empty())
continue;
4159 const LinearConstraintProto& other_lin = other_ct.linear();
4160 if (other_lin.vars().size() > 100)
continue;
4162 int64_t other_coeff = 0;
4163 for (
int i = 0;
i < other_lin.vars().size(); ++
i) {
4164 const int v = other_lin.vars(
i);
4165 const int64_t coeff = other_lin.coeffs(
i);
4169 other_coeff += coeff;
4173 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4174 .RelaxIfTooComplex();
4177 if (other_coeff == 0)
continue;
4178 implied = implied.InverseMultiplicationBy(other_coeff);
4182 if (!context_->IntersectDomainWith(var, implied))
return false;
4183 if (context_->IsFixed(var))
continue;
4184 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4188 context_->UpdateRuleStats(
"linear: doubleton free");
4194 if (is_in_objective &&
4195 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4200 ConstraintProto copy_if_we_abort;
4201 absl::c_sort(others);
4202 for (
const int c : others) {
4206 copy_if_we_abort = context_->working_model->constraints(c);
4214 var, var_coeff, *ct,
4215 context_->working_model->mutable_constraints(c))) {
4222 bool changed =
false;
4223 if (!CanonicalizeLinear(context_->working_model->mutable_constraints(c),
4228 context_->UpdateConstraintVariableUsage(c);
4235 *context_->working_model,
4236 context_->working_model->constraints(c).linear().vars(),
4237 context_->working_model->constraints(c).linear().coeffs())) {
4239 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4245 context_->UpdateConstraintVariableUsage(c);
4247 if (abort)
continue;
4249 context_->UpdateRuleStats(
4250 absl::StrCat(
"linear: variable substitution ", others.size()));
4259 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4260 context_->MarkVariableAsRemoved(var);
4261 ConstraintProto* mapping_ct =
4262 context_->NewMappingConstraint(__FILE__, __LINE__);
4264 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4265 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4266 mapping_linear_ct->mutable_vars()->at(
i));
4267 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4268 mapping_linear_ct->mutable_coeffs()->at(
i));
4269 return RemoveConstraint(ct);
4273 if (ct_index == -1) {
4275 context_->UpdateRuleStats(
4276 "linear: reduced variable domains in derived constraint");
4282 context_->UpdateRuleStats(
"linear: reduced variable domains");
4284 if (recanonicalize) {
4285 bool changed =
false;
4286 (void)CanonicalizeLinear(ct, &changed);
4294void CpModelPresolver::LowerThanCoeffStrengthening(
bool from_lower_bound,
4295 int64_t min_magnitude,
4297 ConstraintProto* ct) {
4298 const LinearConstraintProto& arg = ct->linear();
4299 const int64_t second_threshold = rhs - min_magnitude;
4300 const int num_vars = arg.vars_size();
4313 if (min_magnitude <= second_threshold) {
4315 int64_t max_magnitude_left = 0;
4316 int64_t max_activity_left = 0;
4317 int64_t activity_when_coeff_are_one = 0;
4319 for (
int i = 0;
i < num_vars; ++
i) {
4320 const int64_t magnitude = std::abs(arg.coeffs(
i));
4321 if (magnitude <= second_threshold) {
4322 gcd = std::gcd(gcd, magnitude);
4323 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4324 const int64_t bound_diff =
4325 context_->MaxOf(arg.vars(
i)) - context_->MinOf(arg.vars(
i));
4326 activity_when_coeff_are_one += bound_diff;
4327 max_activity_left += magnitude * bound_diff;
4330 CHECK_GT(min_magnitude, 0);
4331 CHECK_LE(min_magnitude, max_magnitude_left);
4334 int64_t new_rhs = 0;
4335 bool set_all_to_one =
false;
4336 if (max_activity_left <= rhs) {
4338 context_->UpdateRuleStats(
"linear with partial amo: trivial");
4339 new_rhs = activity_when_coeff_are_one;
4340 set_all_to_one =
true;
4341 }
else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4343 context_->UpdateRuleStats(
"linear with partial amo: constant coeff");
4344 new_rhs = rhs / min_magnitude;
4345 set_all_to_one =
true;
4346 }
else if (gcd > 1) {
4348 context_->UpdateRuleStats(
"linear with partial amo: gcd");
4349 new_rhs = rhs / gcd;
4353 int64_t rhs_offset = 0;
4354 for (
int i = 0;
i < num_vars; ++
i) {
4355 const int ref = arg.vars(
i);
4356 const int64_t coeff = from_lower_bound ? arg.coeffs(
i) : -arg.coeffs(
i);
4359 const int64_t magnitude = std::abs(coeff);
4360 if (magnitude > rhs) {
4361 new_coeff = new_rhs + 1;
4362 }
else if (magnitude > second_threshold) {
4363 new_coeff = new_rhs;
4365 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4371 ct->mutable_linear()->set_coeffs(
i, new_coeff);
4372 rhs_offset += new_coeff * context_->MinOf(ref);
4374 ct->mutable_linear()->set_coeffs(
i, -new_coeff);
4375 rhs_offset -= new_coeff * context_->MaxOf(ref);
4379 ct->mutable_linear());
4384 int64_t rhs_offset = 0;
4385 for (
int i = 0;
i < num_vars; ++
i) {
4386 int ref = arg.vars(
i);
4387 int64_t coeff = arg.coeffs(
i);
4394 if (ct->enforcement_literal().empty()) {
4402 context_->UpdateRuleStats(
"linear: fix variable to its bound.");
4403 if (!context_->IntersectDomainWith(
4404 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4405 : context_->MaxOf(ref)))) {
4413 if (coeff > second_threshold && coeff < rhs) {
4414 context_->UpdateRuleStats(
4415 "linear: coefficient strengthening by increasing it.");
4416 if (from_lower_bound) {
4418 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4421 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4423 ct->mutable_linear()->set_coeffs(
i, arg.coeffs(
i) > 0 ? rhs : -rhs);
4426 if (rhs_offset != 0) {
4428 ct->mutable_linear());
4439void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4440 int ct_index, ConstraintProto* ct) {
4441 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
4442 if (context_->ModelIsUnsat())
return;
4444 const LinearConstraintProto& arg = ct->linear();
4445 const int num_vars = arg.vars_size();
4449 if (num_vars <= 1)
return;
4451 int64_t min_sum = 0;
4452 int64_t max_sum = 0;
4453 int64_t max_coeff_magnitude = 0;
4454 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4455 for (
int i = 0;
i < num_vars; ++
i) {
4456 const int ref = arg.vars(
i);
4457 const int64_t coeff = arg.coeffs(
i);
4459 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4460 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4461 min_sum += coeff * context_->MinOf(ref);
4462 max_sum += coeff * context_->MaxOf(ref);
4464 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4465 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4466 min_sum += coeff * context_->MaxOf(ref);
4467 max_sum += coeff * context_->MinOf(ref);
4470 if (max_coeff_magnitude == 1)
return;
4478 const auto& domain = ct->linear().domain();
4479 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4480 const int64_t lb_threshold = max_sum - domain[1];
4481 if (max_coeff_magnitude + min_coeff_magnitude <
4482 std::max(ub_threshold, lb_threshold)) {
4487 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4488 min_coeff_magnitude < max_coeff_magnitude) {
4489 const int64_t rhs_min = domain[0];
4490 const int64_t rhs_max = domain[1];
4491 if (min_sum >= rhs_min &&
4492 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4493 LowerThanCoeffStrengthening(
true,
4494 min_coeff_magnitude, rhs_max - min_sum, ct);
4497 if (max_sum <= rhs_max &&
4498 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4499 LowerThanCoeffStrengthening(
false,
4500 min_coeff_magnitude, max_sum - rhs_min, ct);
4520 const bool lower_bounded = min_sum < rhs_domain.
Min();
4521 const bool upper_bounded = max_sum > rhs_domain.
Max();
4522 if (!lower_bounded && !upper_bounded)
return;
4523 if (lower_bounded && upper_bounded) {
4528 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold))
return;
4530 context_->UpdateRuleStats(
"linear: split boxed constraint");
4531 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4533 if (!ct->name().empty()) {
4534 new_ct1->set_name(absl::StrCat(ct->name(),
" (part 1)"));
4537 new_ct1->mutable_linear());
4539 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4541 if (!ct->name().empty()) {
4542 new_ct2->set_name(absl::StrCat(ct->name(),
" (part 2)"));
4545 new_ct2->mutable_linear());
4547 context_->UpdateNewConstraintsVariableUsage();
4549 context_->UpdateConstraintVariableUsage(ct_index);
4556 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4564 int64_t second_threshold =
4566 threshold - min_coeff_magnitude);
4574 if (rhs_domain.NumIntervals() > 1) {
4575 second_threshold = threshold;
4583 const bool only_extract_booleans =
4584 !context_->params().presolve_extract_integer_enforcement() ||
4585 context_->ModelIsExpanded();
4590 int64_t rhs_offset = 0;
4591 bool some_integer_encoding_were_extracted =
false;
4592 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4593 for (
int i = 0;
i < arg.vars_size(); ++
i) {
4594 int ref = arg.vars(
i);
4595 int64_t coeff = arg.coeffs(
i);
4603 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4604 if (context_->IsFixed(ref) || coeff < threshold ||
4605 (only_extract_booleans && !is_boolean)) {
4606 mutable_arg->set_vars(new_size, mutable_arg->vars(
i));
4608 int64_t new_magnitude = std::abs(arg.coeffs(
i));
4609 if (coeff > threshold) {
4612 new_magnitude = threshold;
4613 context_->UpdateRuleStats(
"linear: coefficient strenghtening.");
4614 }
else if (coeff > second_threshold && coeff < threshold) {
4617 new_magnitude = second_threshold;
4618 context_->UpdateRuleStats(
4619 "linear: advanced coefficient strenghtening.");
4621 if (coeff != new_magnitude) {
4622 if (lower_bounded) {
4624 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4627 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4631 mutable_arg->set_coeffs(
4632 new_size, arg.coeffs(
i) > 0 ? new_magnitude : -new_magnitude);
4638 context_->UpdateRuleStats(
"linear: extracted enforcement literal");
4640 some_integer_encoding_were_extracted =
true;
4641 context_->UpdateRuleStats(
4642 "linear: extracted integer enforcement literal");
4644 if (lower_bounded) {
4645 ct->add_enforcement_literal(is_boolean
4647 : context_->GetOrCreateVarValueEncoding(
4648 ref, context_->MinOf(ref)));
4649 rhs_offset -= coeff * context_->MinOf(ref);
4651 ct->add_enforcement_literal(is_boolean
4653 : context_->GetOrCreateVarValueEncoding(
4654 ref, context_->MaxOf(ref)));
4655 rhs_offset -= coeff * context_->MaxOf(ref);
4658 mutable_arg->mutable_vars()->Truncate(new_size);
4659 mutable_arg->mutable_coeffs()->Truncate(new_size);
4661 if (some_integer_encoding_were_extracted || new_size == 1) {
4662 context_->UpdateConstraintVariableUsage(ct_index);
4663 context_->UpdateNewConstraintsVariableUsage();
4667void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4668 if (context_->ModelIsUnsat())
return;
4672 const LinearConstraintProto& arg = ct->linear();
4673 const int num_vars = arg.vars_size();
4674 int64_t min_sum = 0;
4675 int64_t max_sum = 0;
4676 for (
int i = 0;
i < num_vars; ++
i) {
4677 const int ref = arg.vars(
i);
4678 const int64_t coeff = arg.coeffs(
i);
4679 const int64_t term_a = coeff * context_->MinOf(ref);
4680 const int64_t term_b = coeff * context_->MaxOf(ref);
4681 min_sum += std::min(term_a, term_b);
4682 max_sum += std::max(term_a, term_b);
4684 for (
const int type : {0, 1}) {
4685 std::vector<int> at_most_one;
4686 for (
int i = 0;
i < num_vars; ++
i) {
4687 const int ref = arg.vars(
i);
4688 const int64_t coeff = arg.coeffs(
i);
4689 if (context_->MinOf(ref) != 0)
continue;
4690 if (context_->MaxOf(ref) != 1)
continue;
4695 if (min_sum + 2 * std::abs(coeff) > rhs.
Max()) {
4696 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
4699 if (max_sum - 2 * std::abs(coeff) < rhs.
Min()) {
4700 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
4704 if (at_most_one.size() > 1) {
4706 context_->UpdateRuleStats(
"linear: extracted at most one (max).");
4708 context_->UpdateRuleStats(
"linear: extracted at most one (min).");
4710 ConstraintProto* new_ct = context_->working_model->add_constraints();
4711 new_ct->set_name(ct->name());
4712 for (
const int ref : at_most_one) {
4713 new_ct->mutable_at_most_one()->add_literals(ref);
4715 context_->UpdateNewConstraintsVariableUsage();
4722bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4723 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
4724 if (context_->ModelIsUnsat())
return false;
4727 int64_t sum_of_coeffs = 0;
4728 int num_positive = 0;
4729 int num_negative = 0;
4731 const LinearConstraintProto& arg = ct->linear();
4732 const int num_vars = arg.vars_size();
4733 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4734 int64_t max_coeff = 0;
4735 int64_t min_sum = 0;
4736 int64_t max_sum = 0;
4737 for (
int i = 0;
i < num_vars; ++
i) {
4739 const int var = arg.vars(
i);
4740 const int64_t coeff = arg.coeffs(
i);
4743 if (context_->MinOf(var) != 0)
return false;
4744 if (context_->MaxOf(var) != 1)
return false;
4746 sum_of_coeffs += coeff;
4750 min_coeff = std::min(min_coeff, coeff);
4751 max_coeff = std::max(max_coeff, coeff);
4756 min_coeff = std::min(min_coeff, -coeff);
4757 max_coeff = std::max(max_coeff, -coeff);
4760 CHECK_LE(min_coeff, max_coeff);
4769 if ((!rhs_domain.
Contains(min_sum) &&
4770 min_sum + min_coeff > rhs_domain.
Max()) ||
4772 max_sum - min_coeff < rhs_domain.
Min())) {
4773 context_->UpdateRuleStats(
"linear: all booleans and trivially false");
4774 return MarkConstraintAsFalse(ct);
4776 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4777 context_->UpdateRuleStats(
"linear: all booleans and trivially true");
4778 return RemoveConstraint(ct);
4788 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
4789 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
4790 rhs_domain.FixedValue() == 0) {
4792 context_->UpdateRuleStats(
"linear: all equivalent!");
4793 for (
int i = 1;
i < num_vars; ++
i) {
4794 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
4795 ct->linear().vars(
i))) {
4799 return RemoveConstraint(ct);
4806 DCHECK(!rhs_domain.IsEmpty());
4807 if (min_sum + min_coeff > rhs_domain.
Max()) {
4809 context_->UpdateRuleStats(
"linear: negative reified and");
4810 const auto copy = arg;
4811 ct->mutable_bool_and()->clear_literals();
4812 for (
int i = 0;
i < num_vars; ++
i) {
4813 ct->mutable_bool_and()->add_literals(
4814 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4816 PresolveBoolAnd(ct);
4818 }
else if (max_sum - min_coeff < rhs_domain.
Min()) {
4820 context_->UpdateRuleStats(
"linear: positive reified and");
4821 const auto copy = arg;
4822 ct->mutable_bool_and()->clear_literals();
4823 for (
int i = 0;
i < num_vars; ++
i) {
4824 ct->mutable_bool_and()->add_literals(
4825 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4827 PresolveBoolAnd(ct);
4829 }
else if (min_sum + min_coeff >= rhs_domain.
Min() &&
4830 rhs_domain.front().end >= max_sum) {
4832 context_->UpdateRuleStats(
"linear: positive clause");
4833 const auto copy = arg;
4834 ct->mutable_bool_or()->clear_literals();
4835 for (
int i = 0;
i < num_vars; ++
i) {
4836 ct->mutable_bool_or()->add_literals(
4837 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4841 }
else if (max_sum - min_coeff <= rhs_domain.
Max() &&
4842 rhs_domain.back().start <= min_sum) {
4844 context_->UpdateRuleStats(
"linear: negative clause");
4845 const auto copy = arg;
4846 ct->mutable_bool_or()->clear_literals();
4847 for (
int i = 0;
i < num_vars; ++
i) {
4848 ct->mutable_bool_or()->add_literals(
4849 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4854 min_sum + max_coeff <= rhs_domain.
Max() &&
4855 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
4856 rhs_domain.back().start <= min_sum) {
4859 context_->UpdateRuleStats(
"linear: positive at most one");
4860 const auto copy = arg;
4861 ct->mutable_at_most_one()->clear_literals();
4862 for (
int i = 0;
i < num_vars; ++
i) {
4863 ct->mutable_at_most_one()->add_literals(
4864 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4868 max_sum - max_coeff >= rhs_domain.
Min() &&
4869 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
4870 rhs_domain.front().end >= max_sum) {
4873 context_->UpdateRuleStats(
"linear: negative at most one");
4874 const auto copy = arg;
4875 ct->mutable_at_most_one()->clear_literals();
4876 for (
int i = 0;
i < num_vars; ++
i) {
4877 ct->mutable_at_most_one()->add_literals(
4878 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4882 min_sum < rhs_domain.
Min() &&
4883 min_sum + min_coeff >= rhs_domain.
Min() &&
4884 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
4885 min_sum + max_coeff <= rhs_domain.
Max()) {
4887 context_->UpdateRuleStats(
"linear: positive equal one");
4888 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4889 exactly_one->set_name(ct->name());
4890 for (
int i = 0;
i < num_vars; ++
i) {
4891 exactly_one->mutable_exactly_one()->add_literals(
4894 context_->UpdateNewConstraintsVariableUsage();
4895 return RemoveConstraint(ct);
4897 max_sum > rhs_domain.
Max() &&
4898 max_sum - min_coeff <= rhs_domain.
Max() &&
4899 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
4900 max_sum - max_coeff >= rhs_domain.
Min()) {
4902 context_->UpdateRuleStats(
"linear: negative equal one");
4903 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4904 exactly_one->set_name(ct->name());
4905 for (
int i = 0;
i < num_vars; ++
i) {
4906 exactly_one->mutable_exactly_one()->add_literals(
4909 context_->UpdateNewConstraintsVariableUsage();
4910 return RemoveConstraint(ct);
4917 if (num_vars > 3)
return false;
4918 context_->UpdateRuleStats(
"linear: small Boolean expression");
4922 const int max_mask = (1 << arg.vars_size());
4923 for (
int mask = 0; mask < max_mask; ++mask) {
4925 for (
int i = 0;
i < num_vars; ++
i) {
4926 if ((mask >>
i) & 1) value += arg.coeffs(
i);
4928 if (rhs_domain.
Contains(value))
continue;
4931 ConstraintProto* new_ct = context_->working_model->add_constraints();
4932 auto* new_arg = new_ct->mutable_bool_or();
4934 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4936 for (
int i = 0;
i < num_vars; ++
i) {
4937 new_arg->add_literals(((mask >>
i) & 1) ?
NegatedRef(arg.vars(
i))
4942 context_->UpdateNewConstraintsVariableUsage();
4943 return RemoveConstraint(ct);
4946bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto* ct) {
4947 if (context_->ModelIsUnsat())
return false;
4948 IntervalConstraintProto* interval = ct->mutable_interval();
4951 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4952 context_->UpdateRuleStats(
"interval: negative size implies unperformed");
4953 return MarkConstraintAsFalse(ct);
4956 if (ct->enforcement_literal().empty()) {
4957 bool domain_changed =
false;
4959 if (!context_->IntersectDomainWith(
4960 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4964 if (domain_changed) {
4965 context_->UpdateRuleStats(
4966 "interval: performed intervals must have a positive size");
4972 if (context_->ConstraintVariableGraphIsUpToDate() &&
4973 context_->IntervalUsage(c) == 0) {
4974 context_->UpdateRuleStats(
"intervals: removed unused interval");
4975 return RemoveConstraint(ct);
4978 bool changed =
false;
4979 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4980 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4981 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4986bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4987 const int size = ct->inverse().f_direct().size();
4988 bool changed =
false;
4991 for (
const int ref : ct->inverse().f_direct()) {
4992 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4993 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4997 for (
const int ref : ct->inverse().f_inverse()) {
4998 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4999 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
5009 absl::flat_hash_set<int> direct_vars;
5010 for (
const int ref : ct->inverse().f_direct()) {
5011 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
5013 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
5017 absl::flat_hash_set<int> inverse_vars;
5018 for (
const int ref : ct->inverse().f_inverse()) {
5019 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
5021 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
5029 const auto filter_inverse_domain =
5030 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
5032 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
5033 for (
int i = 0;
i < size; ++
i) {
5034 const Domain domain = context_->DomainOf(inverse[
i]);
5035 for (
const int64_t j : domain.Values()) {
5036 inverse_values[
i].insert(j);
5043 std::vector<int64_t> possible_values;
5044 for (
int i = 0;
i < size; ++
i) {
5045 possible_values.clear();
5046 const Domain domain = context_->DomainOf(direct[
i]);
5047 bool removed_value =
false;
5048 for (
const int64_t j : domain.Values()) {
5049 if (inverse_values[j].contains(
i)) {
5050 possible_values.push_back(j);
5052 removed_value =
true;
5055 if (removed_value) {
5057 if (!context_->IntersectDomainWith(
5059 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
5067 if (!filter_inverse_domain(ct->inverse().f_direct(),
5068 ct->inverse().f_inverse())) {
5072 if (!filter_inverse_domain(ct->inverse().f_inverse(),
5073 ct->inverse().f_direct())) {
5078 context_->UpdateRuleStats(
"inverse: reduce domains");
5084bool CpModelPresolver::PresolveElement(
int c, ConstraintProto* ct) {
5085 if (context_->ModelIsUnsat())
return false;
5087 if (ct->element().exprs().empty()) {
5088 context_->UpdateRuleStats(
"element: empty array");
5089 return context_->NotifyThatModelIsUnsat();
5092 bool changed =
false;
5093 changed |= CanonicalizeLinearExpression(
5094 *ct, ct->mutable_element()->mutable_linear_index());
5095 changed |= CanonicalizeLinearExpression(
5096 *ct, ct->mutable_element()->mutable_linear_target());
5097 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
5098 changed |= CanonicalizeLinearExpression(
5099 *ct, ct->mutable_element()->mutable_exprs(
i));
5102 const LinearExpressionProto& index = ct->element().linear_index();
5103 const LinearExpressionProto& target = ct->element().linear_target();
5110 bool index_modified =
false;
5111 if (!context_->IntersectDomainWith(
5112 index, Domain(0, ct->element().exprs_size() - 1),
5116 if (index_modified) {
5117 context_->UpdateRuleStats(
5118 "element: reduced index domain from array size");
5123 if (context_->IsFixed(index)) {
5124 const int64_t index_value = context_->FixedValue(index);
5125 ConstraintProto* new_ct = context_->working_model->add_constraints();
5126 new_ct->mutable_linear()->add_domain(0);
5127 new_ct->mutable_linear()->add_domain(0);
5130 new_ct->mutable_linear());
5131 context_->CanonicalizeLinearConstraint(new_ct);
5132 context_->UpdateNewConstraintsVariableUsage();
5133 context_->UpdateRuleStats(
"element: fixed index");
5134 return RemoveConstraint(ct);
5138 const int index_var = index.vars(0);
5142 const Domain& index_var_domain = context_->DomainOf(index_var);
5143 std::vector<int64_t> reached_indices(ct->element().exprs_size(),
false);
5144 for (
const int64_t index_var_value : index_var_domain.Values()) {
5145 const int64_t index_value =
5147 reached_indices[index_value] =
true;
5148 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5149 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5150 const int64_t expr_value =
5152 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5153 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5154 ct->mutable_element()
5155 ->mutable_exprs(index_value)
5156 ->set_offset(expr_value);
5158 context_->UpdateRuleStats(
5159 "element: fix expression depending on the index");
5164 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
5165 if (!reached_indices[
i]) {
5166 ct->mutable_element()->mutable_exprs(
i)->Clear();
5174 if (changed) context_->UpdateConstraintVariableUsage(c);
5178 const Domain& index_var_domain = context_->DomainOf(index_var);
5179 const Domain& target_domain = context_->DomainSuperSetOf(target);
5180 std::vector<int64_t> possible_index_var_values;
5181 for (
const int64_t index_var_value : index_var_domain.Values()) {
5182 const int64_t index_value =
5184 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5186 bool is_possible_index;
5187 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5190 is_possible_index = context_->DomainContains(
5193 const Domain target_var_domain =
5194 target.vars_size() == 1 ? context_->DomainOf(target.vars(0))
5196 const Domain expr_var_domain = expr.vars_size() == 1
5197 ? context_->DomainOf(expr.vars(0))
5199 const int64_t target_coeff =
5200 target.vars_size() == 1 ? target.coeffs(0) : 0;
5201 const int64_t expr_coeff = expr.vars_size() == 1 ? expr.coeffs(0) : 0;
5203 target_var_domain, target_coeff, expr_var_domain, -expr_coeff,
5204 -target.offset() + expr.offset());
5207 if (is_possible_index) {
5208 possible_index_var_values.push_back(index_var_value);
5210 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5214 if (possible_index_var_values.size() < index_var_domain.Size()) {
5215 if (!context_->IntersectDomainWith(
5219 context_->UpdateRuleStats(
"element: reduced index domain ");
5221 if (context_->IsFixed(index)) {
5222 ConstraintProto*
const eq = context_->working_model->add_constraints();
5223 eq->mutable_linear()->add_domain(0);
5224 eq->mutable_linear()->add_domain(0);
5227 ct->element().exprs(context_->FixedValue(index)), -1,
5228 eq->mutable_linear());
5229 context_->CanonicalizeLinearConstraint(eq);
5230 context_->UpdateNewConstraintsVariableUsage();
5231 context_->UpdateRuleStats(
"element: fixed index");
5232 return RemoveConstraint(ct);
5237 bool all_included_in_target_domain =
true;
5240 Domain infered_domain;
5241 const Domain& index_var_domain = context_->DomainOf(index_var);
5242 const Domain& target_domain = context_->DomainSuperSetOf(target);
5243 for (
const int64_t index_var_value : index_var_domain.Values()) {
5244 const int64_t index_value =
5246 CHECK_GE(index_value, 0);
5247 CHECK_LT(index_value, ct->element().exprs_size());
5248 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5249 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5250 if (!expr_domain.IsIncludedIn(target_domain)) {
5251 all_included_in_target_domain =
false;
5253 infered_domain = infered_domain.UnionWith(expr_domain);
5256 bool domain_modified =
false;
5257 if (!context_->IntersectDomainWith(target, infered_domain,
5258 &domain_modified)) {
5261 if (domain_modified) {
5262 context_->UpdateRuleStats(
"element: reduce target domain");
5266 bool all_constants =
true;
5268 const Domain& index_var_domain = context_->DomainOf(index_var);
5269 std::vector<int64_t> expr_constants;
5271 for (
const int64_t index_var_value : index_var_domain.Values()) {
5272 const int64_t index_value =
5274 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5275 if (context_->IsFixed(expr)) {
5276 expr_constants.push_back(context_->FixedValue(expr));
5278 all_constants =
false;
5285 if (all_constants) {
5286 if (context_->IsFixed(target)) {
5290 context_->UpdateRuleStats(
"element: one value array");
5291 return RemoveConstraint(ct);
5293 int64_t first_index_var_value;
5294 int64_t first_target_var_value;
5295 int64_t d_index = 0;
5296 int64_t d_target = 0;
5298 bool is_affine =
true;
5299 const Domain& index_var_domain = context_->DomainOf(index_var);
5300 for (
const int64_t index_var_value : index_var_domain.Values()) {
5302 const int64_t index_value =
5304 const int64_t expr_value =
5305 context_->FixedValue(ct->element().exprs(index_value));
5307 if (num_terms == 1) {
5308 first_index_var_value = index_var_value;
5309 first_target_var_value = target_var_value;
5310 }
else if (num_terms == 2) {
5311 d_index = index_var_value - first_index_var_value;
5312 d_target = target_var_value - first_target_var_value;
5313 const int64_t gcd = std::gcd(d_index, d_target);
5317 const int64_t offset =
CapSub(
5318 CapProd(d_index,
CapSub(target_var_value, first_target_var_value)),
5319 CapProd(d_target,
CapSub(index_var_value, first_index_var_value)));
5327 const int64_t offset =
CapSub(
CapProd(first_target_var_value, d_index),
5328 CapProd(first_index_var_value, d_target));
5330 ConstraintProto*
const lin = context_->working_model->add_constraints();
5331 lin->mutable_linear()->add_vars(target.vars(0));
5332 lin->mutable_linear()->add_coeffs(d_index);
5333 lin->mutable_linear()->add_vars(index_var);
5334 lin->mutable_linear()->add_coeffs(-d_target);
5335 lin->mutable_linear()->add_domain(offset);
5336 lin->mutable_linear()->add_domain(offset);
5337 context_->CanonicalizeLinearConstraint(lin);
5338 context_->UpdateNewConstraintsVariableUsage();
5339 context_->UpdateRuleStats(
"element: rewrite as affine constraint");
5340 return RemoveConstraint(ct);
5352 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5354 auto count = [&local_var_occurrence_counter](
5355 const LinearExpressionProto& expr)
mutable {
5356 for (
const int var : expr.vars()) {
5357 local_var_occurrence_counter[var]++;
5362 for (
const int64_t index_var_value :
5363 context_->DomainOf(index_var).Values()) {
5369 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5370 local_var_occurrence_counter.at(index_var) == 1) {
5371 if (all_constants) {
5375 context_->UpdateRuleStats(
5376 "element: removed as the index is not used elsewhere");
5377 context_->MarkVariableAsRemoved(index_var);
5378 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5379 return RemoveConstraint(ct);
5381 context_->UpdateRuleStats(
"TODO element: index not used elsewhere");
5385 if (target.vars_size() == 1 && !context_->IsFixed(target.vars(0)) &&
5386 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5387 local_var_occurrence_counter.at(target.vars(0)) == 1) {
5388 if (all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5389 context_->UpdateRuleStats(
5390 "element: removed as the target is not used elsewhere");
5391 context_->MarkVariableAsRemoved(target.vars(0));
5392 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5393 return RemoveConstraint(ct);
5395 context_->UpdateRuleStats(
"TODO element: target not used elsewhere");
5402bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
5403 if (context_->ModelIsUnsat())
return false;
5405 bool changed =
false;
5406 for (
int i = 0;
i < ct->table().exprs_size(); ++
i) {
5407 changed |= CanonicalizeLinearExpression(
5408 *ct, ct->mutable_table()->mutable_exprs(
i));
5411 const int initial_num_exprs = ct->table().exprs_size();
5413 changed |= (ct->table().exprs_size() != initial_num_exprs);
5415 if (ct->table().exprs().empty()) {
5416 context_->UpdateRuleStats(
"table: no expressions");
5417 return RemoveConstraint(ct);
5420 if (ct->table().values().empty()) {
5421 if (ct->table().negated()) {
5422 context_->UpdateRuleStats(
"table: negative table without tuples");
5423 return RemoveConstraint(ct);
5425 context_->UpdateRuleStats(
"table: positive table without tuples");
5426 return MarkConstraintAsFalse(ct);
5430 int num_fixed_exprs = 0;
5431 for (
const LinearExpressionProto& expr : ct->table().exprs()) {
5432 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5434 if (num_fixed_exprs == ct->table().exprs_size()) {
5435 context_->UpdateRuleStats(
"table: all expressions are fixed");
5436 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5437 if (ct->table().negated() == ct->table().values().empty()) {
5438 context_->UpdateRuleStats(
"table: always true");
5439 return RemoveConstraint(ct);
5441 context_->UpdateRuleStats(
"table: always false");
5442 return MarkConstraintAsFalse(ct);
5444 return RemoveConstraint(ct);
5447 if (num_fixed_exprs > 0) {
5452 if (ct->table().negated())
return changed;
5458 const int num_exprs = ct->table().exprs_size();
5459 const int num_tuples = ct->table().values_size() / num_exprs;
5460 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5461 for (
int e = 0; e < num_exprs; ++e) {
5462 const LinearExpressionProto& expr = ct->table().exprs(e);
5463 if (context_->IsFixed(expr)) {
5464 new_domains[e].push_back(context_->FixedValue(expr));
5468 for (
int t = 0; t < num_tuples; ++t) {
5469 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5472 DCHECK_EQ(1, expr.vars_size());
5473 DCHECK_EQ(1, expr.coeffs(0));
5474 DCHECK_EQ(0, expr.offset());
5475 const int var = expr.vars(0);
5476 bool domain_modified =
false;
5478 &domain_modified)) {
5481 if (domain_modified) {
5482 context_->UpdateRuleStats(
"table: reduce variable domain");
5486 if (num_exprs == 1) {
5489 context_->UpdateRuleStats(
"table: only one column!");
5490 return RemoveConstraint(ct);
5495 for (
int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5496 if (prod ==
static_cast<double>(num_tuples)) {
5497 context_->UpdateRuleStats(
"table: all tuples!");
5498 return RemoveConstraint(ct);
5504 if (
static_cast<double>(num_tuples) > 0.7 * prod) {
5505 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5506 for (
int t = 0; t < num_tuples; ++t) {
5507 current_tuples[t].resize(num_exprs);
5508 for (
int e = 0; e < num_exprs; ++e) {
5509 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5514 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5515 for (
int e = 0; e < num_exprs; ++e) {
5516 var_to_values[e].assign(new_domains[e].
begin(), new_domains[e].
end());
5518 std::vector<std::vector<int64_t>> all_tuples(prod);
5519 for (
int i = 0;
i < prod; ++
i) {
5520 all_tuples[
i].resize(num_exprs);
5522 for (
int j = 0; j < num_exprs; ++j) {
5523 all_tuples[
i][j] = var_to_values[j][index % var_to_values[j].size()];
5524 index /= var_to_values[j].size();
5530 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5531 std::set_difference(all_tuples.begin(), all_tuples.end(),
5532 current_tuples.begin(), current_tuples.end(),
5536 ct->mutable_table()->set_negated(!ct->table().negated());
5537 ct->mutable_table()->clear_values();
5538 for (
const std::vector<int64_t>& t : diff) {
5539 for (
const int64_t v : t) ct->mutable_table()->add_values(v);
5541 context_->UpdateRuleStats(
"table: negated");
5550class UniqueNonNegativeValue {
5552 void Add(
int value) {
5553 DCHECK_GE(value, 0);
5561 bool HasUniqueValue()
const {
return value_ >= 0; }
5563 int64_t value()
const {
5564 DCHECK(HasUniqueValue());
5574bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5575 if (context_->ModelIsUnsat())
return false;
5578 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5580 bool variables_have_changed =
false;
5581 for (LinearExpressionProto& exp :
5582 *(ct->mutable_all_diff()->mutable_exprs())) {
5583 variables_have_changed |= CanonicalizeLinearExpression(*ct, &exp);
5586 const int size = all_diff.exprs_size();
5588 context_->UpdateRuleStats(
"all_diff: empty constraint");
5589 return RemoveConstraint(ct);
5592 context_->UpdateRuleStats(
"all_diff: one expression");
5593 return RemoveConstraint(ct);
5596 absl::flat_hash_set<int64_t> fixed_values;
5598 for (
int i = 0;
i < size; ++
i) {
5599 if (!context_->IsFixed(all_diff.exprs(
i))) {
5600 if (
i != new_size) {
5601 *all_diff.mutable_exprs(new_size) = all_diff.exprs(
i);
5605 const int64_t value = context_->FixedValue(all_diff.exprs(
i));
5606 if (!fixed_values.insert(value).second) {
5607 return context_->NotifyThatModelIsUnsat(
5608 "all_diff: duplicate fixed values");
5613 if (new_size < size) {
5614 all_diff.mutable_exprs()->DeleteSubrange(new_size, size - new_size);
5615 context_->UpdateRuleStats(
"all_diff: remove fixed expressions");
5618 if (!fixed_values.empty()) {
5619 const Domain to_keep =
5622 bool propagated =
false;
5623 for (
int i = 0;
i < all_diff.exprs_size(); ++
i) {
5624 if (!context_->IntersectDomainWith(all_diff.exprs(
i), to_keep,
5630 context_->UpdateRuleStats(
"all_diff: propagate fixed expressions");
5637 absl::btree_map<int, std::vector<std::pair<int64_t, int64_t>>> terms;
5638 std::vector<int64_t> forbidden_values;
5639 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
5640 if (expr.vars_size() != 1)
continue;
5641 terms[expr.vars(0)].push_back(
5642 std::make_pair(expr.coeffs(0), expr.offset()));
5644 for (
auto& [var, terms] : terms) {
5645 if (terms.size() == 1)
continue;
5646 std::sort(terms.begin(), terms.end());
5649 for (
int i = 1;
i < terms.size(); ++
i) {
5650 if (terms[
i] == terms[
i - 1]) {
5651 return context_->NotifyThatModelIsUnsat(
5652 "all_diff: duplicate expressions");
5660 forbidden_values.clear();
5661 for (
int i = 0;
i + 1 < terms.size(); ++
i) {
5662 for (
int j =
i + 1; j < terms.size(); ++j) {
5663 const int64_t coeff = terms[
i].first - terms[j].first;
5664 if (coeff == 0)
continue;
5665 const int64_t offset = terms[j].second - terms[
i].second;
5666 const int64_t value = offset / coeff;
5667 if (value * coeff == offset) {
5668 forbidden_values.push_back(value);
5672 if (!forbidden_values.empty()) {
5674 bool propagated =
false;
5675 if (!context_->IntersectDomainWith(var, to_keep, &propagated)) {
5679 context_->UpdateRuleStats(
5680 "all_diff: propagate expressions with the same variable");
5686 if (all_diff.exprs_size() >= 2 && all_diff.exprs_size() <= 512) {
5687 Domain union_of_domains = context_->DomainSuperSetOf(all_diff.exprs(0));
5688 for (
int i = 1;
i < all_diff.exprs_size(); ++
i) {
5689 union_of_domains = union_of_domains.UnionWith(
5690 context_->DomainSuperSetOf(all_diff.exprs(
i)));
5693 if (union_of_domains.Size() < all_diff.exprs_size()) {
5694 return context_->NotifyThatModelIsUnsat(
5695 "all_diff: more expressions than values");
5698 if (all_diff.exprs_size() == union_of_domains.Size()) {
5699 absl::btree_map<int64_t, UniqueNonNegativeValue> value_to_index;
5700 for (
int i = 0;
i < all_diff.exprs_size(); ++
i) {
5701 const LinearExpressionProto& expr = all_diff.exprs(
i);
5702 DCHECK_EQ(expr.vars_size(), 1);
5703 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5708 bool propagated =
false;
5709 for (
const auto& [value, unique_index] : value_to_index) {
5710 if (!unique_index.HasUniqueValue())
continue;
5712 const LinearExpressionProto& expr =
5713 all_diff.exprs(unique_index.value());
5714 if (!context_->IntersectDomainWith(expr, Domain(value), &propagated)) {
5720 context_->UpdateRuleStats(
5721 "all_diff: propagated mandatory values in permutation");
5726 return variables_have_changed;
5733void AddImplication(
int lhs,
int rhs, CpModelProto* proto,
5734 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5735 if (ref_to_bool_and->contains(lhs)) {
5736 const int ct_index = (*ref_to_bool_and)[lhs];
5737 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
5738 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
5739 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
5740 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
5743 (*ref_to_bool_and)[lhs] = proto->constraints_size();
5744 ConstraintProto* ct = proto->add_constraints();
5745 ct->add_enforcement_literal(lhs);
5746 ct->mutable_bool_and()->add_literals(rhs);
5750template <
typename ClauseContainer>
5751void ExtractClauses(
bool merge_into_bool_and,
5752 absl::Span<const int> index_mapping,
5753 const ClauseContainer& container, CpModelProto* proto,
5754 std::string_view debug_name =
"") {
5761 absl::flat_hash_map<int, int> ref_to_bool_and;
5762 for (
int i = 0;
i < container.NumClauses(); ++
i) {
5763 const std::vector<Literal>& clause = container.Clause(
i);
5764 if (clause.empty())
continue;
5769 if (merge_into_bool_and && clause.size() == 2) {
5770 const int var_a = index_mapping[clause[0].Variable().value()];
5771 const int var_b = index_mapping[clause[1].Variable().value()];
5772 const int ref_a = clause[0].IsPositive() ? var_a :
NegatedRef(var_a);
5773 const int ref_b = clause[1].IsPositive() ? var_b :
NegatedRef(var_b);
5774 AddImplication(
NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
5779 ConstraintProto* ct = proto->add_constraints();
5780 if (!debug_name.empty()) {
5781 ct->set_name(std::string(debug_name));
5783 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5784 for (
const Literal l : clause) {
5785 const int var = index_mapping[l.Variable().value()];
5786 if (l.IsPositive()) {
5787 ct->mutable_bool_or()->add_literals(var);
5789 ct->mutable_bool_or()->add_literals(
NegatedRef(var));
5797bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
5798 if (context_->ModelIsUnsat())
return false;
5799 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
5800 bool changed =
false;
5805 absl::flat_hash_set<int> visited_intervals;
5806 absl::flat_hash_set<int> duplicate_intervals;
5807 for (
const int interval_index : proto->intervals()) {
5808 if (context_->ConstraintIsInactive(interval_index))
continue;
5809 if (!visited_intervals.insert(interval_index).second) {
5810 duplicate_intervals.insert(interval_index);
5814 const int initial_num_intervals = proto->intervals_size();
5816 visited_intervals.clear();
5818 for (
int i = 0;
i < initial_num_intervals; ++
i) {
5819 const int interval_index = proto->intervals(
i);
5820 if (context_->ConstraintIsInactive(interval_index))
continue;
5822 if (duplicate_intervals.contains(interval_index)) {
5824 if (!visited_intervals.insert(interval_index).second)
continue;
5826 ConstraintProto* interval_ct =
5827 context_->working_model->mutable_constraints(interval_index);
5830 if (context_->SizeMin(interval_index) > 0) {
5831 if (!MarkConstraintAsFalse(interval_ct)) {
5834 context_->UpdateConstraintVariableUsage(interval_index);
5835 context_->UpdateRuleStats(
5836 "no_overlap: unperform duplicate non zero-sized intervals");
5842 if (context_->SizeMax(interval_index) > 0) {
5844 if (!context_->ConstraintIsOptional(interval_index)) {
5845 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
5849 context_->UpdateRuleStats(
5850 "no_overlap: zero the size of performed duplicate intervals");
5854 const int performed_literal = interval_ct->enforcement_literal(0);
5855 ConstraintProto* size_eq_zero =
5856 context_->working_model->add_constraints();
5857 size_eq_zero->add_enforcement_literal(performed_literal);
5858 size_eq_zero->mutable_linear()->add_domain(0);
5859 size_eq_zero->mutable_linear()->add_domain(0);
5861 interval_ct->interval().size(), 1,
5862 size_eq_zero->mutable_linear());
5863 context_->UpdateRuleStats(
5864 "no_overlap: make duplicate intervals as unperformed or zero "
5866 context_->UpdateNewConstraintsVariableUsage();
5871 proto->set_intervals(new_size++, interval_index);
5874 if (new_size < initial_num_intervals) {
5875 proto->mutable_intervals()->Truncate(new_size);
5876 context_->UpdateRuleStats(
"no_overlap: removed absent intervals");
5882 if (proto->intervals_size() > 1) {
5883 std::vector<IndexedInterval> indexed_intervals;
5884 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
5885 const int index = proto->intervals(
i);
5886 indexed_intervals.push_back({index,
5887 IntegerValue(context_->StartMin(index)),
5888 IntegerValue(context_->EndMax(index))});
5890 std::vector<std::vector<int>> components;
5893 if (components.size() > 1) {
5894 for (
const std::vector<int>& intervals : components) {
5895 if (intervals.size() <= 1)
continue;
5897 NoOverlapConstraintProto* new_no_overlap =
5898 context_->working_model->add_constraints()->mutable_no_overlap();
5901 for (
const int i : intervals) {
5902 new_no_overlap->add_intervals(
i);
5905 context_->UpdateNewConstraintsVariableUsage();
5906 context_->UpdateRuleStats(
"no_overlap: split into disjoint components");
5907 return RemoveConstraint(ct);
5911 std::vector<int> constant_intervals;
5912 int64_t size_min_of_non_constant_intervals =
5913 std::numeric_limits<int64_t>::max();
5914 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
5915 const int interval_index = proto->intervals(
i);
5916 if (context_->IntervalIsConstant(interval_index)) {
5917 constant_intervals.push_back(interval_index);
5919 size_min_of_non_constant_intervals =
5920 std::min(size_min_of_non_constant_intervals,
5921 context_->SizeMin(interval_index));
5925 bool move_constraint_last =
false;
5926 if (!constant_intervals.empty()) {
5928 std::sort(constant_intervals.begin(), constant_intervals.end(),
5929 [
this](
int i1,
int i2) {
5930 const int64_t s1 = context_->StartMin(i1);
5931 const int64_t e1 = context_->EndMax(i1);
5932 const int64_t s2 = context_->StartMin(i2);
5933 const int64_t e2 = context_->EndMax(i2);
5934 return std::tie(s1, e1) < std::tie(s2, e2);
5940 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5941 if (context_->EndMax(constant_intervals[
i]) >
5942 context_->StartMin(constant_intervals[
i + 1])) {
5943 context_->UpdateRuleStats(
"no_overlap: constant intervals overlap");
5944 return context_->NotifyThatModelIsUnsat();
5948 if (constant_intervals.size() == proto->intervals_size()) {
5949 context_->UpdateRuleStats(
"no_overlap: no variable intervals");
5950 return RemoveConstraint(ct);
5953 absl::flat_hash_set<int> intervals_to_remove;
5957 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5958 const int start =
i;
5959 while (
i + 1 < constant_intervals.size() &&
5960 context_->StartMin(constant_intervals[
i + 1]) -
5961 context_->EndMax(constant_intervals[
i]) <
5962 size_min_of_non_constant_intervals) {
5965 if (
i == start)
continue;
5966 for (
int j = start; j <=
i; ++j) {
5967 intervals_to_remove.insert(constant_intervals[j]);
5969 const int64_t new_start = context_->StartMin(constant_intervals[start]);
5970 const int64_t new_end = context_->EndMax(constant_intervals[
i]);
5971 proto->add_intervals(context_->working_model->constraints_size());
5972 IntervalConstraintProto* new_interval =
5973 context_->working_model->add_constraints()->mutable_interval();
5974 new_interval->mutable_start()->set_offset(new_start);
5975 new_interval->mutable_size()->set_offset(new_end - new_start);
5976 new_interval->mutable_end()->set_offset(new_end);
5977 move_constraint_last =
true;
5981 if (!intervals_to_remove.empty()) {
5983 const int old_size = proto->intervals_size();
5984 for (
int i = 0;
i < old_size; ++
i) {
5985 const int interval_index = proto->intervals(
i);
5986 if (intervals_to_remove.contains(interval_index)) {
5989 proto->set_intervals(new_size++, interval_index);
5991 CHECK_LT(new_size, old_size);
5992 proto->mutable_intervals()->Truncate(new_size);
5993 context_->UpdateRuleStats(
5994 "no_overlap: merge constant contiguous intervals");
5995 intervals_to_remove.clear();
5996 constant_intervals.clear();
5998 context_->UpdateNewConstraintsVariableUsage();
6004 int num_size_zero_or_one = 0;
6005 for (
const int index : proto->intervals()) {
6006 const IntervalConstraintProto& interval =
6007 context_->working_model->constraints(index).interval();
6008 const LinearExpressionProto& size = interval.size();
6009 if (size.vars().empty() && size.offset() >= 0 && size.offset() <= 1) {
6010 ++num_size_zero_or_one;
6015 const int initial_num_intervals = proto->intervals().size();
6016 if (num_size_zero_or_one == initial_num_intervals) {
6020 for (
const int index : proto->intervals()) {
6021 const IntervalConstraintProto& interval =
6022 context_->working_model->constraints(index).interval();
6023 if (interval.size().offset() == 0)
continue;
6024 proto->set_intervals(new_size++, index);
6026 if (new_size < initial_num_intervals) {
6027 proto->mutable_intervals()->Truncate(new_size);
6029 context_->UpdateRuleStats(
"no_overlap: removed size 0 from all diff.");
6031 context_->UpdateRuleStats(
"TODO no_overlap: only size one !");
6035 if (proto->intervals_size() == 1) {
6036 context_->UpdateRuleStats(
"no_overlap: only one interval");
6037 return RemoveConstraint(ct);
6039 if (proto->intervals().empty()) {
6040 context_->UpdateRuleStats(
"no_overlap: no intervals");
6041 return RemoveConstraint(ct);
6047 if (move_constraint_last) {
6049 *context_->working_model->add_constraints() = *ct;
6050 context_->UpdateNewConstraintsVariableUsage();
6051 return RemoveConstraint(ct);
6057bool CpModelPresolver::PresolveNoOverlap2DFramed(
6058 absl::Span<const Rectangle> fixed_boxes,
6059 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6060 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6062 DCHECK(!non_fixed_boxes.empty());
6063 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
6064 for (
const RectangleInRange& box : non_fixed_boxes) {
6065 bounding_box.GrowToInclude(box.bounding_area);
6067 std::vector<Rectangle> espace_for_single_box =
6068 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
6071 std::vector<Rectangle> empty;
6074 if (espace_for_single_box.size() != 1) {
6078 Rectangle fixed_boxes_bb = fixed_boxes.front();
6079 for (
const Rectangle& box : fixed_boxes) {
6080 fixed_boxes_bb.GrowToInclude(box);
6082 const Rectangle framed_region = espace_for_single_box.front();
6083 for (
const RectangleInRange& box : non_fixed_boxes) {
6084 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
6088 if (non_fixed_boxes.size() > 1 &&
6089 (2 * box.x_size <= framed_region.SizeX() ||
6090 2 * box.y_size <= framed_region.SizeY())) {
6095 const int x_interval_index = proto.x_intervals(box.box_index);
6096 const int y_interval_index = proto.y_intervals(box.box_index);
6097 if (!context_->working_model->constraints(x_interval_index)
6098 .enforcement_literal()
6100 !context_->working_model->constraints(y_interval_index)
6101 .enforcement_literal()
6103 if (context_->working_model->constraints(x_interval_index)
6104 .enforcement_literal(0) !=
6105 context_->working_model->constraints(y_interval_index)
6106 .enforcement_literal(0)) {
6116 std::vector<int> enforcement_literals_for_amo;
6117 bool has_mandatory =
false;
6118 for (
const RectangleInRange& box : non_fixed_boxes) {
6119 const int box_index = box.box_index;
6120 const int x_interval_index = proto.x_intervals(box_index);
6121 const int y_interval_index = proto.y_intervals(box_index);
6122 const ConstraintProto& x_interval_ct =
6123 context_->working_model->constraints(x_interval_index);
6124 const ConstraintProto& y_interval_ct =
6125 context_->working_model->constraints(y_interval_index);
6126 if (x_interval_ct.enforcement_literal().empty() &&
6127 y_interval_ct.enforcement_literal().empty()) {
6129 if (has_mandatory) {
6130 return context_->NotifyThatModelIsUnsat(
6131 "Two mandatory boxes in the same space");
6133 has_mandatory =
true;
6134 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
6135 Domain(framed_region.x_min.value(),
6136 framed_region.x_max.value()))) {
6139 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
6140 Domain(framed_region.x_min.value(),
6141 framed_region.x_max.value()))) {
6144 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
6145 Domain(framed_region.y_min.value(),
6146 framed_region.y_max.value()))) {
6149 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
6150 Domain(framed_region.y_min.value(),
6151 framed_region.y_max.value()))) {
6155 auto add_linear_constraint = [&](
const ConstraintProto& interval_ct,
6156 int enforcement_literal,
6157 IntegerValue min, IntegerValue max) {
6160 context_->AddImplyInDomain(enforcement_literal,
6161 interval_ct.interval().start(),
6162 Domain(min.value(), max.value()));
6163 context_->AddImplyInDomain(enforcement_literal,
6164 interval_ct.interval().end(),
6165 Domain(min.value(), max.value()));
6167 const int enforcement_literal =
6168 x_interval_ct.enforcement_literal().empty()
6169 ? y_interval_ct.enforcement_literal(0)
6170 : x_interval_ct.enforcement_literal(0);
6171 enforcement_literals_for_amo.push_back(enforcement_literal);
6172 add_linear_constraint(x_interval_ct, enforcement_literal,
6173 framed_region.x_min, framed_region.x_max);
6174 add_linear_constraint(y_interval_ct, enforcement_literal,
6175 framed_region.y_min, framed_region.y_max);
6178 if (has_mandatory) {
6179 for (
const int lit : enforcement_literals_for_amo) {
6180 if (!context_->SetLiteralToFalse(lit)) {
6184 }
else if (enforcement_literals_for_amo.size() > 1) {
6185 context_->working_model->add_constraints()
6186 ->mutable_at_most_one()
6187 ->mutable_literals()
6188 ->Add(enforcement_literals_for_amo.begin(),
6189 enforcement_literals_for_amo.end());
6191 context_->UpdateRuleStats(
"no_overlap_2d: at most one rectangle in region");
6192 context_->UpdateNewConstraintsVariableUsage();
6193 return RemoveConstraint(ct);
6196bool CpModelPresolver::ExpandEncoded2DBinPacking(
6197 absl::Span<const Rectangle> fixed_boxes,
6198 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6199 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6201 non_fixed_boxes, fixed_boxes,
6203 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6204 if (disjoint_packing_presolve_result.bins.empty())
return false;
6206 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6207 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6208 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6211 for (
int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6212 const int b = non_fixed_boxes[idx].box_index;
6213 const ConstraintProto& x_interval_ct =
6214 context_->working_model->constraints(proto.x_intervals(
b));
6215 const ConstraintProto& y_interval_ct =
6216 context_->working_model->constraints(proto.y_intervals(
b));
6217 if (x_interval_ct.enforcement_literal().empty() &&
6218 y_interval_ct.enforcement_literal().empty()) {
6222 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6223 ? y_interval_ct.enforcement_literal(0)
6224 : x_interval_ct.enforcement_literal(0);
6225 int potentially_other_enforcement_literal =
6226 y_interval_ct.enforcement_literal().empty()
6227 ? x_interval_ct.enforcement_literal(0)
6228 : y_interval_ct.enforcement_literal(0);
6230 if (enforcement_literal == potentially_other_enforcement_literal) {
6232 box_to_presence_literal[idx].push_back(
NegatedRef(enforcement_literal));
6234 const int interval_is_absent_literal =
6235 context_->NewBoolVarWithConjunction(
6236 {enforcement_literal, potentially_other_enforcement_literal});
6238 BoolArgumentProto* bool_or =
6239 context_->working_model->add_constraints()->mutable_bool_or();
6240 bool_or->add_literals(
NegatedRef(interval_is_absent_literal));
6241 for (
const int lit :
6242 {enforcement_literal, potentially_other_enforcement_literal}) {
6243 context_->AddImplication(
NegatedRef(interval_is_absent_literal), lit);
6246 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6250 for (
int bin_index = 0;
6251 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6252 const Disjoint2dPackingResult::Bin& bin =
6253 disjoint_packing_presolve_result.bins[bin_index];
6254 NoOverlap2DConstraintProto new_no_overlap_2d;
6255 for (
const Rectangle& ret : bin.fixed_boxes) {
6256 new_no_overlap_2d.add_x_intervals(
6257 context_->working_model->constraints_size());
6258 new_no_overlap_2d.add_y_intervals(
6259 context_->working_model->constraints_size() + 1);
6260 IntervalConstraintProto* new_interval =
6261 context_->working_model->add_constraints()->mutable_interval();
6262 new_interval->mutable_start()->set_offset(ret.x_min.value());
6263 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6264 new_interval->mutable_end()->set_offset(ret.x_max.value());
6267 context_->working_model->add_constraints()->mutable_interval();
6268 new_interval->mutable_start()->set_offset(ret.y_min.value());
6269 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6270 new_interval->mutable_end()->set_offset(ret.y_max.value());
6272 for (
const int idx : bin.non_fixed_box_indexes) {
6273 int presence_in_box_lit = context_->NewBoolVar(
"binpacking");
6274 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6275 const int b = non_fixed_boxes[idx].box_index;
6276 box_in_area_lits.push_back({.box_index =
b,
6277 .area_index = bin_index,
6278 .literal = presence_in_box_lit});
6279 const ConstraintProto& x_interval_ct =
6280 context_->working_model->constraints(proto.x_intervals(
b));
6281 const ConstraintProto& y_interval_ct =
6282 context_->working_model->constraints(proto.y_intervals(
b));
6283 ConstraintProto* new_interval_x =
6284 context_->working_model->add_constraints();
6285 *new_interval_x = x_interval_ct;
6286 new_interval_x->clear_enforcement_literal();
6287 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6288 ConstraintProto* new_interval_y =
6289 context_->working_model->add_constraints();
6290 *new_interval_y = y_interval_ct;
6291 new_interval_y->clear_enforcement_literal();
6292 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6293 new_no_overlap_2d.add_x_intervals(
6294 context_->working_model->constraints_size() - 2);
6295 new_no_overlap_2d.add_y_intervals(
6296 context_->working_model->constraints_size() - 1);
6298 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6299 &new_no_overlap_2d);
6303 for (
int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6304 const std::vector<int>& presence_literals =
6305 box_to_presence_literal[box_index];
6306 if (presence_literals.empty()) {
6307 return context_->NotifyThatModelIsUnsat(
6308 "A mandatory box cannot be placed in any position");
6311 context_->working_model->add_constraints()->mutable_exactly_one();
6312 for (
const int presence_literal : presence_literals) {
6313 exactly_one->add_literals(presence_literal);
6316 CompactVectorVector<int, Rectangle> areas;
6317 for (
int bin_index = 0;
6318 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6319 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6321 solution_crush_.AssignVariableToPackingArea(
6322 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6324 context_->UpdateNewConstraintsVariableUsage();
6325 context_->UpdateRuleStats(
6326 "no_overlap_2d: fixed boxes partition available space, converted "
6327 "to optional regions");
6328 return RemoveConstraint(ct);
6331bool CpModelPresolver::PresolveNoOverlap2D(
int , ConstraintProto* ct) {
6332 if (context_->ModelIsUnsat()) {
6336 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6337 const int initial_num_boxes = proto.x_intervals_size();
6339 bool x_constant =
true;
6340 bool y_constant =
true;
6341 bool has_zero_sized_interval =
false;
6342 bool has_potential_zero_sized_interval =
false;
6346 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6347 std::vector<RectangleInRange> non_fixed_boxes;
6348 absl::flat_hash_set<int> fixed_item_indexes;
6349 for (
int i = 0;
i < proto.x_intervals_size(); ++
i) {
6350 const int x_interval_index = proto.x_intervals(
i);
6351 const int y_interval_index = proto.y_intervals(
i);
6353 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6354 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6359 for (
const int interval_index : {x_interval_index, y_interval_index}) {
6360 if (context_->StartMin(interval_index) >
6361 context_->EndMax(interval_index)) {
6362 const ConstraintProto* interval_ct =
6363 context_->working_model->mutable_constraints(interval_index);
6364 if (interval_ct->enforcement_literal_size() == 1) {
6365 const int literal = interval_ct->enforcement_literal(0);
6366 if (!context_->SetLiteralToFalse(literal)) {
6370 return context_->NotifyThatModelIsUnsat(
6371 "no_overlap_2d: impossible interval.");
6376 if (context_->ConstraintIsInactive(x_interval_index) ||
6377 context_->ConstraintIsInactive(y_interval_index)) {
6381 bounding_boxes.push_back(
6382 {IntegerValue(context_->StartMin(x_interval_index)),
6383 IntegerValue(context_->EndMax(x_interval_index)),
6384 IntegerValue(context_->StartMin(y_interval_index)),
6385 IntegerValue(context_->EndMax(y_interval_index))});
6386 if (context_->IntervalIsConstant(x_interval_index) &&
6387 context_->IntervalIsConstant(y_interval_index) &&
6388 context_->SizeMax(x_interval_index) > 0 &&
6389 context_->SizeMax(y_interval_index) > 0) {
6390 fixed_boxes.push_back(bounding_boxes.back());
6391 fixed_item_indexes.insert(new_size);
6393 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6394 non_fixed_boxes.push_back(
6395 {.box_index = new_size,
6396 .bounding_area = bounding_boxes.back(),
6397 .x_size = std::max(int64_t{0}, context_->SizeMin(x_interval_index)),
6399 std::max(int64_t{0}, context_->SizeMin(y_interval_index))});
6403 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6406 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6409 if (context_->SizeMax(x_interval_index) <= 0 ||
6410 context_->SizeMax(y_interval_index) <= 0) {
6411 has_zero_sized_interval =
true;
6413 if (context_->SizeMin(x_interval_index) <= 0 ||
6414 context_->SizeMin(y_interval_index) <= 0) {
6415 has_potential_zero_sized_interval =
true;
6419 if (new_size < initial_num_boxes) {
6420 context_->UpdateRuleStats(
"no_overlap_2d: removed inactive boxes");
6421 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6422 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6425 if (new_size == 0) {
6426 context_->UpdateRuleStats(
"no_overlap_2d: no boxes");
6427 return RemoveConstraint(ct);
6430 if (new_size == 1) {
6431 context_->UpdateRuleStats(
"no_overlap_2d: only one box");
6432 return RemoveConstraint(ct);
6435 const CompactVectorVector<int> components =
6437 if (components.size() > 1) {
6438 for (
int i = 0;
i < components.size(); ++
i) {
6439 absl::Span<const int> boxes = components[
i];
6440 if (boxes.size() <= 1)
continue;
6442 NoOverlap2DConstraintProto* new_no_overlap_2d =
6443 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6444 for (
const int b : boxes) {
6445 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6446 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6449 context_->UpdateNewConstraintsVariableUsage();
6450 context_->UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
6451 return RemoveConstraint(ct);
6455 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6456 context_->UpdateRuleStats(
6457 "no_overlap_2d: a dimension is constant, splitting into many "
6459 std::vector<IndexedInterval> indexed_intervals;
6460 for (
int i = 0;
i < new_size; ++
i) {
6461 int x = proto.x_intervals(
i);
6462 int y = proto.y_intervals(
i);
6463 if (x_constant) std::swap(x, y);
6464 indexed_intervals.push_back({
x, IntegerValue(context_->StartMin(y)),
6465 IntegerValue(context_->EndMax(y))});
6467 CompactVectorVector<int> no_overlaps;
6468 absl::c_sort(indexed_intervals, IndexedInterval::ComparatorByStart());
6470 for (
int i = 0;
i < no_overlaps.size(); ++
i) {
6471 ConstraintProto* new_ct = context_->working_model->add_constraints();
6474 for (
const int i : no_overlaps[
i]) {
6475 new_ct->mutable_no_overlap()->add_intervals(
i);
6478 context_->UpdateNewConstraintsVariableUsage();
6479 return RemoveConstraint(ct);
6485 return context_->NotifyThatModelIsUnsat(
6486 "Two fixed boxes in no_overlap_2d overlap");
6489 if (non_fixed_bounding_boxes.empty()) {
6490 context_->UpdateRuleStats(
"no_overlap_2d: all boxes are fixed");
6491 return RemoveConstraint(ct);
6497 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6498 const bool presolved =
6501 NoOverlap2DConstraintProto new_no_overlap_2d;
6504 const int old_size = proto.x_intervals_size();
6505 for (
int i = 0;
i < old_size; ++
i) {
6506 if (fixed_item_indexes.contains(
i)) {
6509 new_no_overlap_2d.add_x_intervals(proto.x_intervals(
i));
6510 new_no_overlap_2d.add_y_intervals(proto.y_intervals(
i));
6512 for (
const Rectangle& fixed_box : fixed_boxes) {
6513 const int item_x_interval =
6514 context_->working_model->constraints().size();
6515 IntervalConstraintProto* new_interval =
6516 context_->working_model->add_constraints()->mutable_interval();
6517 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6518 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6519 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6521 const int item_y_interval =
6522 context_->working_model->constraints().size();
6524 context_->working_model->add_constraints()->mutable_interval();
6525 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6526 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6527 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6529 new_no_overlap_2d.add_x_intervals(item_x_interval);
6530 new_no_overlap_2d.add_y_intervals(item_y_interval);
6532 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6533 &new_no_overlap_2d);
6534 context_->UpdateNewConstraintsVariableUsage();
6535 context_->UpdateRuleStats(
"no_overlap_2d: presolved fixed rectangles");
6536 return RemoveConstraint(ct);
6540 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6541 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6542 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6551 const CompactVectorVector<int> non_fixed_components =
6553 if (non_fixed_components.size() > 1) {
6554 for (
int i = 0;
i < non_fixed_components.size(); ++
i) {
6557 absl::Span<const int> indexes = non_fixed_components[
i];
6559 NoOverlap2DConstraintProto* new_no_overlap_2d =
6560 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6561 for (
const int idx : indexes) {
6562 const int b = non_fixed_boxes[idx].box_index;
6563 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6564 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6566 for (
const int b : fixed_item_indexes) {
6567 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6568 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6571 context_->UpdateNewConstraintsVariableUsage();
6572 context_->UpdateRuleStats(
6573 "no_overlap_2d: split into disjoint components duplicating fixed "
6575 return RemoveConstraint(ct);
6578 if (!has_potential_zero_sized_interval) {
6579 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6583 RunPropagatorsForConstraint(*ct);
6584 return new_size < initial_num_boxes;
6588LinearExpressionProto ConstantExpressionProto(int64_t value) {
6589 LinearExpressionProto expr;
6590 expr.set_offset(value);
6595void CpModelPresolver::DetectDuplicateIntervals(
6596 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6597 interval_representative_.clear();
6598 bool changed =
false;
6599 const int size = intervals->size();
6600 for (
int i = 0;
i < size; ++
i) {
6601 const int index = (*intervals)[
i];
6602 const auto [it, inserted] = interval_representative_.insert({index, index});
6603 if (it->second != index) {
6605 intervals->Set(
i, it->second);
6606 context_->UpdateRuleStats(
6607 "intervals: change duplicate index inside constraint");
6610 if (changed) context_->UpdateConstraintVariableUsage(c);
6613bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
6614 if (context_->ModelIsUnsat())
return false;
6616 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6618 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6619 for (LinearExpressionProto& exp :
6620 *(ct->mutable_cumulative()->mutable_demands())) {
6621 changed |= CanonicalizeLinearExpression(*ct, &exp);
6624 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6628 bool domain_changed =
false;
6629 if (!context_->IntersectDomainWith(
6630 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6633 if (domain_changed) {
6634 context_->UpdateRuleStats(
"cumulative: trimmed negative capacity");
6643 absl::flat_hash_map<int, int> interval_to_i;
6645 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6646 const auto [it, inserted] =
6647 interval_to_i.insert({proto->intervals(
i), new_size});
6649 if (context_->IsFixed(proto->demands(
i))) {
6650 const int old_index = it->second;
6651 proto->mutable_demands(old_index)->set_offset(
6652 proto->demands(old_index).offset() +
6653 context_->FixedValue(proto->demands(
i)));
6654 context_->UpdateRuleStats(
6655 "cumulative: merged demand of identical interval");
6658 context_->UpdateRuleStats(
6659 "TODO cumulative: merged demand of identical interval");
6662 proto->set_intervals(new_size, proto->intervals(
i));
6663 *proto->mutable_demands(new_size) = proto->demands(
i);
6666 if (new_size < proto->intervals_size()) {
6668 proto->mutable_intervals()->Truncate(new_size);
6669 proto->mutable_demands()->erase(
6670 proto->mutable_demands()->begin() + new_size,
6671 proto->mutable_demands()->end());
6679 int num_zero_demand_removed = 0;
6680 int num_zero_size_removed = 0;
6681 int num_incompatible_intervals = 0;
6682 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6683 if (context_->ConstraintIsInactive(proto->intervals(
i)))
continue;
6685 const LinearExpressionProto& demand_expr = proto->demands(
i);
6686 const int64_t demand_max = context_->MaxOf(demand_expr);
6687 if (demand_max == 0) {
6688 num_zero_demand_removed++;
6692 const int interval_index = proto->intervals(
i);
6693 if (context_->SizeMax(interval_index) == 0) {
6695 num_zero_size_removed++;
6700 const int64_t start_min = context_->StartMin(interval_index);
6701 const int64_t end_max = context_->EndMax(interval_index);
6702 if (start_min > end_max) {
6703 if (context_->ConstraintIsOptional(interval_index)) {
6704 ConstraintProto* interval_ct =
6705 context_->working_model->mutable_constraints(interval_index);
6706 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6707 const int literal = interval_ct->enforcement_literal(0);
6708 if (!context_->SetLiteralToFalse(literal)) {
6711 num_incompatible_intervals++;
6714 return context_->NotifyThatModelIsUnsat(
6715 "cumulative: inconsistent intervals cannot be performed.");
6719 if (context_->MinOf(demand_expr) > capacity_max) {
6720 if (context_->ConstraintIsOptional(interval_index)) {
6721 if (context_->SizeMin(interval_index) > 0) {
6722 ConstraintProto* interval_ct =
6723 context_->working_model->mutable_constraints(interval_index);
6724 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6725 const int literal = interval_ct->enforcement_literal(0);
6726 if (!context_->SetLiteralToFalse(literal)) {
6729 num_incompatible_intervals++;
6734 const ConstraintProto& interval_ct =
6735 context_->working_model->constraints(interval_index);
6736 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
6740 context_->UpdateRuleStats(
6741 "cumulative: zero size of performed demand that exceeds "
6743 ++num_zero_demand_removed;
6748 proto->set_intervals(new_size, interval_index);
6749 *proto->mutable_demands(new_size) = proto->demands(
i);
6753 if (new_size < proto->intervals_size()) {
6755 proto->mutable_intervals()->Truncate(new_size);
6756 proto->mutable_demands()->erase(
6757 proto->mutable_demands()->begin() + new_size,
6758 proto->mutable_demands()->end());
6761 if (num_zero_demand_removed > 0) {
6762 context_->UpdateRuleStats(
6763 "cumulative: removed intervals with no demands");
6765 if (num_zero_size_removed > 0) {
6766 context_->UpdateRuleStats(
6767 "cumulative: removed intervals with a size of zero");
6769 if (num_incompatible_intervals > 0) {
6770 context_->UpdateRuleStats(
6771 "cumulative: removed intervals that can't be performed");
6777 for (
int i = 0;
i < proto->demands_size(); ++
i) {
6778 const int interval = proto->intervals(
i);
6779 const LinearExpressionProto& demand_expr = proto->demands(
i);
6780 if (context_->ConstraintIsOptional(interval))
continue;
6781 if (context_->SizeMin(interval) <= 0)
continue;
6782 bool domain_changed =
false;
6783 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
6787 if (domain_changed) {
6788 context_->UpdateRuleStats(
6789 "cumulative: fit demand in [0..capacity_max]");
6801 if (proto->intervals_size() > 1) {
6802 std::vector<IndexedInterval> indexed_intervals;
6803 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
6804 const int index = proto->intervals(
i);
6805 indexed_intervals.push_back({
i, IntegerValue(context_->StartMin(index)),
6806 IntegerValue(context_->EndMax(index))});
6808 std::vector<std::vector<int>> components;
6811 if (components.size() > 1) {
6812 for (
const std::vector<int>& component : components) {
6813 CumulativeConstraintProto* new_cumulative =
6814 context_->working_model->add_constraints()->mutable_cumulative();
6815 for (
const int i : component) {
6816 new_cumulative->add_intervals(proto->intervals(
i));
6817 *new_cumulative->add_demands() = proto->demands(
i);
6819 *new_cumulative->mutable_capacity() = proto->capacity();
6821 context_->UpdateNewConstraintsVariableUsage();
6822 context_->UpdateRuleStats(
"cumulative: split into disjoint components");
6823 return RemoveConstraint(ct);
6831 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6832 const int64_t capacity_min = context_->MinOf(proto->capacity());
6833 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6834 const int interval_index = proto->intervals(
i);
6835 const int64_t demand_max = context_->MaxOf(proto->demands(
i));
6836 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
6837 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
6846 int num_possible_overloads = 0;
6847 int64_t current_load = 0;
6848 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6849 for (
const auto& it : time_to_demand_deltas) {
6850 num_possible_overloads_before[it.first] = num_possible_overloads;
6851 current_load += it.second;
6852 if (current_load > capacity_min) {
6853 ++num_possible_overloads;
6856 CHECK_EQ(current_load, 0);
6859 if (num_possible_overloads == 0) {
6860 context_->UpdateRuleStats(
6861 "cumulative: max profile is always under the min capacity");
6862 return RemoveConstraint(ct);
6872 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6873 const int index = proto->intervals(
i);
6874 const int64_t start_min = context_->StartMin(index);
6875 const int64_t end_max = context_->EndMax(index);
6884 if (start_min >= end_max)
continue;
6890 const int num_diff = num_possible_overloads_before.at(end_max) -
6891 num_possible_overloads_before.at(start_min);
6892 if (num_diff == 0)
continue;
6894 proto->set_intervals(new_size, proto->intervals(
i));
6895 *proto->mutable_demands(new_size) = proto->demands(
i);
6899 if (new_size < proto->intervals_size()) {
6901 proto->mutable_intervals()->Truncate(new_size);
6902 proto->mutable_demands()->erase(
6903 proto->mutable_demands()->begin() + new_size,
6904 proto->mutable_demands()->end());
6905 context_->UpdateRuleStats(
6906 "cumulative: remove never conflicting intervals.");
6910 if (proto->intervals().empty()) {
6911 context_->UpdateRuleStats(
"cumulative: no intervals");
6912 return RemoveConstraint(ct);
6916 int64_t max_of_performed_demand_mins = 0;
6917 int64_t sum_of_max_demands = 0;
6918 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6919 const int interval_index = proto->intervals(
i);
6920 const ConstraintProto& interval_ct =
6921 context_->working_model->constraints(interval_index);
6923 const LinearExpressionProto& demand_expr = proto->demands(
i);
6924 sum_of_max_demands += context_->MaxOf(demand_expr);
6926 if (interval_ct.enforcement_literal().empty() &&
6927 context_->SizeMin(interval_index) > 0) {
6928 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6929 context_->MinOf(demand_expr));
6933 const LinearExpressionProto& capacity_expr = proto->capacity();
6934 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
6935 context_->UpdateRuleStats(
"cumulative: propagate min capacity.");
6936 if (!context_->IntersectDomainWith(
6937 capacity_expr, Domain(max_of_performed_demand_mins,
6938 std::numeric_limits<int64_t>::max()))) {
6943 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
6944 context_->UpdateRuleStats(
"cumulative: cannot fit performed demands");
6945 return context_->NotifyThatModelIsUnsat();
6948 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6949 context_->UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
6950 return RemoveConstraint(ct);
6954 if (context_->IsFixed(proto->capacity())) {
6956 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
6957 const LinearExpressionProto& demand_expr = ct->cumulative().demands(
i);
6958 if (!context_->IsFixed(demand_expr)) {
6963 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
6964 if (gcd == 1)
break;
6968 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
6969 const int64_t demand = context_->MinOf(ct->cumulative().demands(
i));
6970 *proto->mutable_demands(
i) = ConstantExpressionProto(demand / gcd);
6973 const int64_t old_capacity = context_->MinOf(proto->capacity());
6974 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6975 context_->UpdateRuleStats(
6976 "cumulative: divide demands and capacity by gcd");
6980 const int num_intervals = proto->intervals_size();
6981 const LinearExpressionProto& capacity_expr = proto->capacity();
6983 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6985 int num_duration_one = 0;
6986 int num_greater_half_capacity = 0;
6988 bool has_optional_interval =
false;
6989 for (
int i = 0;
i < num_intervals; ++
i) {
6990 const int index = proto->intervals(
i);
6992 if (context_->ConstraintIsOptional(index)) has_optional_interval =
true;
6993 const ConstraintProto& ct =
6994 context_->working_model->constraints(proto->intervals(
i));
6995 const IntervalConstraintProto& interval = ct.interval();
6996 start_exprs[
i] = interval.start();
6998 const LinearExpressionProto& demand_expr = proto->demands(
i);
6999 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
7002 if (context_->SizeMin(index) == 0) {
7008 const int64_t demand_min = context_->MinOf(demand_expr);
7009 const int64_t demand_max = context_->MaxOf(demand_expr);
7010 if (demand_min > capacity_max / 2) {
7011 num_greater_half_capacity++;
7013 if (demand_min > capacity_max) {
7014 context_->UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
7015 if (!context_->ConstraintIsOptional(index)) {
7016 return context_->NotifyThatModelIsUnsat();
7018 CHECK_EQ(ct.enforcement_literal().size(), 1);
7019 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
7024 }
else if (demand_max > capacity_max) {
7025 if (ct.enforcement_literal().empty()) {
7026 context_->UpdateRuleStats(
7027 "cumulative: demand_max exceeds capacity max.");
7028 if (!context_->IntersectDomainWith(
7030 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
7036 context_->UpdateRuleStats(
7037 "cumulative: demand_max of optional interval exceeds capacity.");
7042 if (num_greater_half_capacity == num_intervals) {
7043 if (num_duration_one == num_intervals && !has_optional_interval) {
7044 context_->UpdateRuleStats(
"cumulative: convert to all_different");
7045 ConstraintProto* new_ct = context_->working_model->add_constraints();
7046 auto* arg = new_ct->mutable_all_diff();
7047 for (
const LinearExpressionProto& expr : start_exprs) {
7048 *arg->add_exprs() = expr;
7050 if (!context_->IsFixed(capacity_expr)) {
7051 const int64_t capacity_min = context_->MinOf(capacity_expr);
7052 for (
const LinearExpressionProto& expr : proto->demands()) {
7053 if (capacity_min >= context_->MaxOf(expr))
continue;
7054 LinearConstraintProto* fit =
7055 context_->working_model->add_constraints()->mutable_linear();
7057 fit->add_domain(std::numeric_limits<int64_t>::max());
7062 context_->UpdateNewConstraintsVariableUsage();
7063 return RemoveConstraint(ct);
7065 context_->UpdateRuleStats(
"cumulative: convert to no_overlap");
7068 for (
int i = 0;
i < proto->demands_size(); ++
i) {
7069 const LinearExpressionProto& demand_expr = proto->demands(
i);
7070 const int64_t demand_max = context_->MaxOf(demand_expr);
7071 if (demand_max > context_->MinOf(capacity_expr)) {
7072 ConstraintProto* capacity_gt =
7073 context_->working_model->add_constraints();
7074 *capacity_gt->mutable_enforcement_literal() =
7075 context_->working_model->constraints(proto->intervals(
i))
7076 .enforcement_literal();
7077 capacity_gt->mutable_linear()->add_domain(0);
7078 capacity_gt->mutable_linear()->add_domain(
7079 std::numeric_limits<int64_t>::max());
7081 capacity_gt->mutable_linear());
7083 capacity_gt->mutable_linear());
7087 ConstraintProto* new_ct = context_->working_model->add_constraints();
7088 auto* arg = new_ct->mutable_no_overlap();
7089 for (
const int interval : proto->intervals()) {
7090 arg->add_intervals(interval);
7092 context_->UpdateNewConstraintsVariableUsage();
7093 return RemoveConstraint(ct);
7097 RunPropagatorsForConstraint(*ct);
7101bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
7102 if (context_->ModelIsUnsat())
return false;
7104 RoutesConstraintProto& proto = *ct->mutable_routes();
7106 const int old_size = proto.literals_size();
7108 std::vector<bool> has_incoming_or_outgoing_arcs;
7109 const int num_arcs = proto.literals_size();
7110 for (
int i = 0;
i < num_arcs; ++
i) {
7111 const int ref = proto.literals(
i);
7112 const int tail = proto.tails(
i);
7113 const int head = proto.heads(
i);
7115 if (tail >= has_incoming_or_outgoing_arcs.size()) {
7116 has_incoming_or_outgoing_arcs.resize(tail + 1,
false);
7118 if (head >= has_incoming_or_outgoing_arcs.size()) {
7119 has_incoming_or_outgoing_arcs.resize(head + 1,
false);
7122 if (context_->LiteralIsFalse(ref)) {
7123 context_->UpdateRuleStats(
"routes: removed false arcs");
7126 proto.set_literals(new_size, ref);
7127 proto.set_tails(new_size, tail);
7128 proto.set_heads(new_size, head);
7130 has_incoming_or_outgoing_arcs[tail] =
true;
7131 has_incoming_or_outgoing_arcs[head] =
true;
7134 if (old_size > 0 && new_size == 0) {
7138 return context_->NotifyThatModelIsUnsat(
7139 "routes: graph with nodes and no arcs");
7144 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
7145 if (!has_incoming_or_outgoing_arcs[n]) {
7146 return context_->NotifyThatModelIsUnsat(absl::StrCat(
7147 "routes: node ", n,
" misses incoming or outgoing arcs"));
7151 if (new_size < num_arcs) {
7152 proto.mutable_literals()->Truncate(new_size);
7153 proto.mutable_tails()->Truncate(new_size);
7154 proto.mutable_heads()->Truncate(new_size);
7158 RunPropagatorsForConstraint(*ct);
7162bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
7163 if (context_->ModelIsUnsat())
return false;
7165 CircuitConstraintProto& proto = *ct->mutable_circuit();
7168 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
7169 ct->mutable_circuit()->mutable_heads());
7173 std::vector<std::vector<int>> incoming_arcs;
7174 std::vector<std::vector<int>> outgoing_arcs;
7176 const int num_arcs = proto.literals_size();
7177 for (
int i = 0;
i < num_arcs; ++
i) {
7178 const int ref = proto.literals(
i);
7179 const int tail = proto.tails(
i);
7180 const int head = proto.heads(
i);
7181 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
7182 if (std::max(tail, head) >= incoming_arcs.size()) {
7183 incoming_arcs.resize(std::max(tail, head) + 1);
7184 outgoing_arcs.resize(std::max(tail, head) + 1);
7186 incoming_arcs[head].push_back(ref);
7187 outgoing_arcs[tail].push_back(ref);
7191 for (
int i = 0;
i < num_nodes; ++
i) {
7192 if (incoming_arcs[
i].empty() || outgoing_arcs[
i].empty()) {
7193 return MarkConstraintAsFalse(ct);
7202 bool loop_again =
true;
7203 int num_fixed_at_true = 0;
7204 while (loop_again) {
7206 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7207 for (
const std::vector<int>& refs : *node_to_refs) {
7208 if (refs.size() == 1) {
7209 if (!context_->LiteralIsTrue(refs.front())) {
7210 ++num_fixed_at_true;
7211 if (!context_->SetLiteralToTrue(refs.front()))
return true;
7219 for (
const int ref : refs) {
7220 if (context_->LiteralIsTrue(ref)) {
7227 return context_->NotifyThatModelIsUnsat();
7229 if (num_true == 1) {
7230 for (
const int ref : refs) {
7231 if (ref != true_ref) {
7232 if (!context_->IsFixed(ref)) {
7233 context_->UpdateRuleStats(
"circuit: set literal to false.");
7236 if (!context_->SetLiteralToFalse(ref))
return true;
7243 if (num_fixed_at_true > 0) {
7244 context_->UpdateRuleStats(
"circuit: fixed singleton arcs.");
7250 int circuit_start = -1;
7251 std::vector<int> next(num_nodes, -1);
7252 std::vector<int> new_in_degree(num_nodes, 0);
7253 std::vector<int> new_out_degree(num_nodes, 0);
7254 for (
int i = 0;
i < num_arcs; ++
i) {
7255 const int ref = proto.literals(
i);
7256 if (context_->LiteralIsFalse(ref))
continue;
7257 if (context_->LiteralIsTrue(ref)) {
7258 if (next[proto.tails(
i)] != -1) {
7259 return context_->NotifyThatModelIsUnsat();
7261 next[proto.tails(
i)] = proto.heads(
i);
7262 if (proto.tails(
i) != proto.heads(
i)) {
7263 circuit_start = proto.tails(
i);
7267 ++new_out_degree[proto.tails(
i)];
7268 ++new_in_degree[proto.heads(
i)];
7269 proto.set_tails(new_size, proto.tails(
i));
7270 proto.set_heads(new_size, proto.heads(
i));
7271 proto.set_literals(new_size, ref);
7281 for (
int i = 0;
i < num_nodes; ++
i) {
7282 if (new_in_degree[
i] == 0 || new_out_degree[
i] == 0) {
7283 return context_->NotifyThatModelIsUnsat();
7288 if (circuit_start != -1) {
7289 std::vector<bool> visited(num_nodes,
false);
7290 int current = circuit_start;
7291 while (current != -1 && !visited[current]) {
7292 visited[current] =
true;
7293 current = next[current];
7295 if (current == circuit_start) {
7298 std::vector<bool> has_self_arc(num_nodes,
false);
7299 for (
int i = 0;
i < num_arcs; ++
i) {
7300 if (visited[proto.tails(
i)])
continue;
7301 if (proto.tails(
i) == proto.heads(
i)) {
7302 has_self_arc[proto.tails(
i)] =
true;
7303 if (!context_->SetLiteralToTrue(proto.literals(
i)))
return true;
7305 if (!context_->SetLiteralToFalse(proto.literals(
i)))
return true;
7308 for (
int n = 0; n < num_nodes; ++n) {
7309 if (!visited[n] && !has_self_arc[n]) {
7311 return MarkConstraintAsFalse(ct);
7314 context_->UpdateRuleStats(
"circuit: fully specified.");
7315 return RemoveConstraint(ct);
7319 if (num_true == new_size) {
7320 context_->UpdateRuleStats(
"circuit: empty circuit.");
7321 return RemoveConstraint(ct);
7327 for (
int i = 0;
i < num_nodes; ++
i) {
7328 for (
const std::vector<int>* arc_literals :
7329 {&incoming_arcs[
i], &outgoing_arcs[
i]}) {
7330 std::vector<int> literals;
7331 for (
const int ref : *arc_literals) {
7332 if (context_->LiteralIsFalse(ref))
continue;
7333 if (context_->LiteralIsTrue(ref)) {
7337 literals.push_back(ref);
7339 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
7340 context_->UpdateRuleStats(
"circuit: degree 2");
7341 if (!context_->StoreBooleanEqualityRelation(literals[0],
7350 if (new_size < num_arcs) {
7351 proto.mutable_tails()->Truncate(new_size);
7352 proto.mutable_heads()->Truncate(new_size);
7353 proto.mutable_literals()->Truncate(new_size);
7354 context_->UpdateRuleStats(
"circuit: removed false arcs.");
7357 RunPropagatorsForConstraint(*ct);
7361bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
7362 if (context_->ModelIsUnsat())
return false;
7365 AutomatonConstraintProto* proto = ct->mutable_automaton();
7366 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7370 bool changed =
false;
7371 for (
int i = 0;
i < proto->exprs_size(); ++
i) {
7372 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(
i));
7375 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7376 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7380 for (
int time = 0; time < reachable_labels.size(); ++time) {
7381 const LinearExpressionProto& expr = proto->exprs(time);
7382 if (context_->IsFixed(expr)) {
7383 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7384 return MarkConstraintAsFalse(ct);
7387 std::vector<int64_t> unscaled_reachable_labels;
7388 for (
const int64_t label : reachable_labels[time]) {
7391 bool removed_values =
false;
7392 if (!context_->IntersectDomainWith(
7397 if (removed_values) {
7398 context_->UpdateRuleStats(
"automaton: reduce variable domain");
7406bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
7407 if (context_->ModelIsUnsat())
return false;
7410 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7411 bool changed =
false;
7412 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7413 changed |= CanonicalizeLinearExpression(*ct, &exp);
7415 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7416 changed |= CanonicalizeLinearExpression(*ct, &exp);
7419 if (proto.active_literals().empty()) {
7420 const int true_literal = context_->GetTrueLiteral();
7421 for (
int i = 0;
i < proto.time_exprs_size(); ++
i) {
7422 proto.add_active_literals(true_literal);
7427 const auto& demand_is_null = [&](
int i) {
7428 return (context_->IsFixed(proto.level_changes(
i)) &&
7429 context_->FixedValue(proto.level_changes(
i)) == 0) ||
7430 context_->LiteralIsFalse(proto.active_literals(
i));
7435 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7436 if (demand_is_null(
i)) num_zeros++;
7439 if (num_zeros > 0) {
7442 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7443 if (demand_is_null(
i))
continue;
7444 *proto.mutable_level_changes(new_size) = proto.level_changes(
i);
7445 *proto.mutable_time_exprs(new_size) = proto.time_exprs(
i);
7446 proto.set_active_literals(new_size, proto.active_literals(
i));
7450 proto.mutable_level_changes()->erase(
7451 proto.mutable_level_changes()->begin() + new_size,
7452 proto.mutable_level_changes()->end());
7453 proto.mutable_time_exprs()->erase(
7454 proto.mutable_time_exprs()->begin() + new_size,
7455 proto.mutable_time_exprs()->end());
7456 proto.mutable_active_literals()->Truncate(new_size);
7458 context_->UpdateRuleStats(
7459 "reservoir: remove zero level_changes or inactive events.");
7463 for (
const LinearExpressionProto& level_change : proto.level_changes()) {
7464 if (!context_->IsFixed(level_change))
return changed;
7467 const int num_events = proto.level_changes_size();
7468 int64_t gcd = proto.level_changes().empty()
7470 : std::abs(context_->FixedValue(proto.level_changes(0)));
7471 int num_positives = 0;
7472 int num_negatives = 0;
7473 int64_t max_sum_of_positive_level_changes = 0;
7474 int64_t min_sum_of_negative_level_changes = 0;
7475 for (
int i = 0;
i < num_events; ++
i) {
7476 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7477 gcd = std::gcd(gcd, std::abs(demand));
7480 max_sum_of_positive_level_changes += demand;
7482 DCHECK_LT(demand, 0);
7484 min_sum_of_negative_level_changes += demand;
7488 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7489 max_sum_of_positive_level_changes <= proto.max_level()) {
7490 context_->UpdateRuleStats(
"reservoir: always feasible");
7491 return RemoveConstraint(ct);
7494 if (min_sum_of_negative_level_changes > proto.max_level() ||
7495 max_sum_of_positive_level_changes < proto.min_level()) {
7496 context_->UpdateRuleStats(
"reservoir: trivially infeasible");
7497 return context_->NotifyThatModelIsUnsat();
7500 if (min_sum_of_negative_level_changes > proto.min_level()) {
7501 proto.set_min_level(min_sum_of_negative_level_changes);
7502 context_->UpdateRuleStats(
7503 "reservoir: increase min_level to reachable value");
7506 if (max_sum_of_positive_level_changes < proto.max_level()) {
7507 proto.set_max_level(max_sum_of_positive_level_changes);
7508 context_->UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
7511 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7512 (num_positives == 0 || num_negatives == 0)) {
7515 auto*
const sum_ct = context_->working_model->add_constraints();
7516 auto*
const sum = sum_ct->mutable_linear();
7517 int64_t fixed_contrib = 0;
7518 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7519 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7520 DCHECK_NE(demand, 0);
7522 const int active = proto.active_literals(
i);
7524 sum->add_vars(active);
7525 sum->add_coeffs(demand);
7528 sum->add_coeffs(-demand);
7529 fixed_contrib += demand;
7532 sum->add_domain(proto.min_level() - fixed_contrib);
7533 sum->add_domain(proto.max_level() - fixed_contrib);
7534 context_->UpdateRuleStats(
"reservoir: converted to linear");
7535 bool changed =
false;
7536 if (!CanonicalizeLinear(sum_ct, &changed)) {
7539 return RemoveConstraint(ct);
7543 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7544 proto.mutable_level_changes(
i)->set_offset(
7545 context_->FixedValue(proto.level_changes(
i)) / gcd);
7546 proto.mutable_level_changes(
i)->clear_vars();
7547 proto.mutable_level_changes(
i)->clear_coeffs();
7553 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7554 .InverseMultiplicationBy(gcd);
7555 proto.set_min_level(reduced_domain.
Min());
7556 proto.set_max_level(reduced_domain.
Max());
7557 context_->UpdateRuleStats(
7558 "reservoir: simplify level_changes and levels by gcd.");
7561 if (num_positives == 1 && num_negatives > 0) {
7562 context_->UpdateRuleStats(
7563 "TODO reservoir: one producer, multiple consumers.");
7566 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7567 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7568 const LinearExpressionProto& time = proto.time_exprs(
i);
7569 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7571 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7572 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7574 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7575 proto.active_literals(
i));
7576 if (time_active_set.contains(key)) {
7577 context_->UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
7580 time_active_set.insert(key);
7584 RunPropagatorsForConstraint(*ct);
7591void CpModelPresolver::ConvertToBoolAnd() {
7592 absl::flat_hash_map<int, int> ref_to_bool_and;
7593 const int num_constraints = context_->working_model->constraints_size();
7594 std::vector<int> to_remove;
7595 for (
int c = 0;
c < num_constraints; ++
c) {
7596 const ConstraintProto& ct = context_->working_model->constraints(c);
7599 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
7600 ct.bool_or().literals().size() == 2) {
7601 AddImplication(
NegatedRef(ct.bool_or().literals(0)),
7602 ct.bool_or().literals(1), context_->working_model,
7604 to_remove.push_back(c);
7608 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
7609 ct.at_most_one().literals().size() == 2) {
7610 AddImplication(ct.at_most_one().literals(0),
7612 context_->working_model, &ref_to_bool_and);
7613 to_remove.push_back(c);
7618 context_->UpdateNewConstraintsVariableUsage();
7619 for (
const int c : to_remove) {
7620 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7621 CHECK(RemoveConstraint(ct));
7622 context_->UpdateConstraintVariableUsage(c);
7626void CpModelPresolver::RunPropagatorsForConstraint(
const ConstraintProto& ct) {
7627 if (context_->ModelIsUnsat())
return;
7634 SatParameters local_params;
7635 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(
true);
7636 local_params.set_exploit_all_precedences(
true);
7637 local_params.set_use_hard_precedences_in_cumulative(
true);
7638 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
7639 local_params.set_use_overload_checker_in_cumulative(
true);
7640 local_params.set_use_strong_propagation_in_disjunctive(
true);
7641 local_params.set_use_timetable_edge_finding_in_cumulative(
true);
7642 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
7643 local_params.set_use_timetabling_in_no_overlap_2d(
true);
7644 local_params.set_use_energetic_reasoning_in_no_overlap_2d(
true);
7645 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(
true);
7646 local_params.set_use_conservative_scale_overload_checker(
true);
7647 local_params.set_use_dual_scheduling_heuristics(
true);
7649 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7650 std::vector<int> variable_mapping;
7655 &model,
"single constraint")) {
7659 time_limit_->AdvanceDeterministicTime(
7660 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7661 auto* mapping = model.GetOrCreate<CpModelMapping>();
7662 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7663 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7664 auto* trail = model.GetOrCreate<Trail>();
7667 int num_changed_bounds = 0;
7668 int num_fixed_bools = 0;
7669 for (
int var = 0; var < variable_mapping.size(); ++var) {
7670 const int proto_var = variable_mapping[var];
7671 if (mapping->IsBoolean(var)) {
7672 const Literal l = mapping->Literal(var);
7673 if (trail->Assignment().LiteralIsFalse(l)) {
7674 if (!context_->SetLiteralToFalse(proto_var))
return;
7677 }
else if (trail->Assignment().LiteralIsTrue(l)) {
7678 if (!context_->SetLiteralToTrue(proto_var))
return;
7683 const Literal r = implication_graph->RepresentativeOf(l);
7687 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7688 if (r_var < 0)
continue;
7689 if (!context_->StoreBooleanEqualityRelation(
7690 proto_var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
7696 bool changed =
false;
7697 if (!context_->IntersectDomainWith(
7699 integer_trail->InitialVariableDomain(mapping->Integer(var)),
7703 if (changed) ++num_changed_bounds;
7706 if (num_changed_bounds > 0) {
7707 context_->UpdateRuleStats(
"propagators: changed bounds",
7708 num_changed_bounds);
7710 if (num_fixed_bools > 0) {
7711 context_->UpdateRuleStats(
"propagators: fixed booleans", num_fixed_bools);
7717void CpModelPresolver::Probe() {
7718 auto probing_timer =
7719 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
7728 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7729 auto* sat_solver = model.GetOrCreate<SatSolver>();
7730 auto* mapping = model.GetOrCreate<CpModelMapping>();
7731 auto* prober = model.GetOrCreate<Prober>();
7744 const auto& assignment = sat_solver->Assignment();
7745 prober->SetPropagationCallback([&](Literal decision) {
7746 if (probing_timer->WorkLimitIsReached())
return;
7747 const int decision_var =
7748 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
7749 if (decision_var < 0)
return;
7750 probing_timer->TrackSimpleLoop(
7751 context_->VarToConstraints(decision_var).size());
7752 std::vector<int> to_update;
7753 for (
const int c : context_->VarToConstraints(decision_var)) {
7754 if (c < 0)
continue;
7755 const ConstraintProto& ct = context_->working_model->constraints(c);
7756 if (ct.enforcement_literal().size() > 2) {
7770 bool decision_is_positive =
false;
7771 bool has_false_literal =
false;
7772 bool simplification_possible =
false;
7773 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
7774 for (
const int ref : ct.enforcement_literal()) {
7775 const Literal lit = mapping->Literal(ref);
7778 decision_is_positive = assignment.LiteralIsTrue(lit);
7779 if (!decision_is_positive)
break;
7782 if (assignment.LiteralIsFalse(lit)) {
7784 has_false_literal =
true;
7785 }
else if (assignment.LiteralIsTrue(lit)) {
7787 simplification_possible =
true;
7790 if (!decision_is_positive)
continue;
7792 if (has_false_literal) {
7794 auto* mutable_ct = context_->working_model->mutable_constraints(c);
7795 mutable_ct->Clear();
7796 mutable_ct->add_enforcement_literal(decision_ref);
7797 mutable_ct->mutable_bool_and()->add_literals(
NegatedRef(false_ref));
7798 context_->UpdateRuleStats(
7799 "probing: reduced enforced constraint to implication.");
7800 to_update.push_back(c);
7804 if (simplification_possible) {
7806 auto* mutable_enforcements =
7807 context_->working_model->mutable_constraints(c)
7808 ->mutable_enforcement_literal();
7809 for (
const int ref : ct.enforcement_literal()) {
7811 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7814 mutable_enforcements->Set(new_size++, ref);
7816 mutable_enforcements->Truncate(new_size);
7817 context_->UpdateRuleStats(
"probing: simplified enforcement list.");
7818 to_update.push_back(c);
7823 if (ct.constraint_case() != ConstraintProto::kBoolOr)
continue;
7824 if (ct.bool_or().literals().size() <= 2)
continue;
7828 bool decision_is_negative =
false;
7829 bool has_true_literal =
false;
7830 bool simplification_possible =
false;
7831 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
7832 for (
const int ref : ct.bool_or().literals()) {
7833 const Literal lit = mapping->Literal(ref);
7836 decision_is_negative = assignment.LiteralIsFalse(lit);
7837 if (!decision_is_negative)
break;
7840 if (assignment.LiteralIsTrue(lit)) {
7842 has_true_literal =
true;
7843 }
else if (assignment.LiteralIsFalse(lit)) {
7845 simplification_possible =
true;
7848 if (!decision_is_negative)
continue;
7850 if (has_true_literal) {
7853 auto* mutable_bool_or =
7854 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7855 mutable_bool_or->mutable_literals()->Clear();
7856 mutable_bool_or->add_literals(decision_ref);
7857 mutable_bool_or->add_literals(true_ref);
7858 context_->UpdateRuleStats(
"probing: bool_or reduced to implication");
7859 to_update.push_back(c);
7863 if (simplification_possible) {
7865 auto* mutable_bool_or =
7866 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7867 for (
const int ref : ct.bool_or().literals()) {
7869 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7872 mutable_bool_or->set_literals(new_size++, ref);
7874 mutable_bool_or->mutable_literals()->Truncate(new_size);
7875 context_->UpdateRuleStats(
"probing: simplified clauses.");
7876 to_update.push_back(c);
7880 absl::c_sort(to_update);
7881 for (
const int c : to_update) {
7882 context_->UpdateConstraintVariableUsage(c);
7886 prober->ProbeBooleanVariables(
7887 context_->params().probing_deterministic_time_limit());
7889 probing_timer->AddCounter(
"probed", prober->num_decisions());
7890 probing_timer->AddToWork(
7891 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7892 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7893 return (
void)context_->NotifyThatModelIsUnsat(
"during probing");
7896 time_limit_->ResetHistory();
7900 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7901 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
7902 const Literal l = sat_solver->LiteralTrail()[
i];
7903 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7905 const int ref = l.IsPositive() ? var :
NegatedRef(var);
7906 if (context_->IsFixed(ref))
continue;
7908 if (!context_->SetLiteralToTrue(ref))
return;
7911 probing_timer->AddCounter(
"fixed_bools", num_fixed);
7914 int num_changed_bounds = 0;
7915 const int num_variables = context_->working_model->variables().size();
7916 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7917 for (
int var = 0; var < num_variables; ++var) {
7920 if (!mapping->IsBoolean(var)) {
7921 bool changed =
false;
7922 if (!context_->IntersectDomainWith(
7923 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
7927 if (changed) ++num_changed_bounds;
7932 const Literal l = mapping->Literal(var);
7933 const Literal r = implication_graph->RepresentativeOf(l);
7937 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7939 if (!context_->StoreBooleanEqualityRelation(
7940 var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
7945 probing_timer->AddCounter(
"new_bounds", num_changed_bounds);
7946 probing_timer->AddCounter(
"equiv", num_equiv);
7947 probing_timer->AddCounter(
"new_binary_clauses",
7948 prober->num_new_binary_clauses());
7953 DetectDuplicateConstraintsWithDifferentEnforcements(
7954 mapping, implication_graph, model.GetOrCreate<Trail>());
7957 probing_timer.reset();
7960 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
7961 PresolveTimer timer(
"MaxClique", logger_, time_limit_);
7962 std::vector<std::vector<Literal>> cliques;
7963 std::vector<int> clique_ct_index;
7967 int64_t num_literals_before = 0;
7968 const int num_constraints = context_->working_model->constraints_size();
7969 for (
int c = 0;
c < num_constraints; ++
c) {
7970 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7971 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7972 std::vector<Literal> clique;
7973 for (
const int ref : ct->at_most_one().literals()) {
7974 clique.push_back(mapping->Literal(ref));
7976 num_literals_before += clique.size();
7977 cliques.push_back(clique);
7979 context_->UpdateConstraintVariableUsage(c);
7980 }
else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7981 if (ct->enforcement_literal().size() != 1)
continue;
7982 const Literal enforcement =
7983 mapping->Literal(ct->enforcement_literal(0));
7984 for (
const int ref : ct->bool_and().literals()) {
7985 if (ref == ct->enforcement_literal(0))
continue;
7986 num_literals_before += 2;
7987 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7990 context_->UpdateConstraintVariableUsage(c);
7993 const int64_t num_old_cliques = cliques.size();
7998 double limit = context_->params().merge_at_most_one_work_limit();
7999 if (num_literals_before > 1e6) {
8000 limit *= num_literals_before / 1e6;
8004 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
8006 timer.AddToWork(dtime);
8011 int num_new_cliques = 0;
8012 int64_t num_literals_after = 0;
8013 for (
const std::vector<Literal>& clique : cliques) {
8014 if (clique.empty())
continue;
8016 num_literals_after += clique.size();
8017 ConstraintProto* ct = context_->working_model->add_constraints();
8018 for (
const Literal literal : clique) {
8020 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
8021 if (var < 0)
continue;
8022 if (literal.IsPositive()) {
8023 ct->mutable_at_most_one()->add_literals(var);
8025 ct->mutable_at_most_one()->add_literals(
NegatedRef(var));
8030 PresolveAtMostOne(ct);
8032 context_->UpdateNewConstraintsVariableUsage();
8033 if (num_new_cliques != num_old_cliques) {
8034 context_->UpdateRuleStats(
"at_most_one: transformed into max clique.");
8037 if (num_old_cliques != num_new_cliques ||
8038 num_literals_before != num_literals_after) {
8039 timer.AddMessage(absl::StrCat(
8043 FormatCounter(num_literals_after),
" literals) at_most_ones. "));
8051 absl::Span<const int> var_mapping,
8053 const int num_vars = assignment.NumberOfVariables();
8054 for (
int i = 0;
i < num_vars; ++
i) {
8055 const Literal lit(BooleanVariable(
i),
true);
8056 const int ref = var_mapping[
i];
8057 if (assignment.LiteralIsTrue(lit)) {
8058 if (!context->SetLiteralToTrue(ref))
return false;
8059 }
else if (assignment.LiteralIsFalse(lit)) {
8060 if (!context->SetLiteralToFalse(ref))
return false;
8070bool CpModelPresolver::PresolvePureSatPart() {
8073 if (context_->ModelIsUnsat())
return true;
8074 if (context_->params().keep_all_feasible_solutions_in_presolve())
return true;
8077 int num_variables = 0;
8078 int num_ignored_variables = 0;
8079 const int total_num_vars = context_->working_model->variables().size();
8080 std::vector<int> new_index(total_num_vars, -1);
8081 std::vector<int> new_to_old_index;
8082 for (
int i = 0;
i < total_num_vars; ++
i) {
8083 if (!context_->CanBeUsedAsLiteral(
i)) {
8084 ++num_ignored_variables;
8089 if (context_->VarToConstraints(
i).empty())
continue;
8091 new_to_old_index.push_back(
i);
8092 new_index[
i] = num_variables++;
8093 DCHECK_EQ(num_variables, new_to_old_index.size());
8097 auto convert = [&new_index](
int ref) {
8099 DCHECK_NE(index, -1);
8115 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8116 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
8117 sat_solver->SetNumVariables(num_variables);
8122 for (
const int var : new_to_old_index) {
8123 if (context_->IsFixed(var)) {
8124 if (context_->LiteralIsTrue(var)) {
8125 if (!sat_solver->AddUnitClause({convert(var)}))
return false;
8127 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
8134 std::vector<Literal> clause;
8135 int num_removed_constraints = 0;
8136 int num_ignored_constraints = 0;
8137 for (
int i = 0;
i < context_->working_model->constraints_size(); ++
i) {
8138 const ConstraintProto& ct = context_->working_model->constraints(
i);
8140 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
8141 ++num_removed_constraints;
8143 for (
const int ref : ct.bool_or().literals()) {
8144 clause.push_back(convert(ref));
8146 for (
const int ref : ct.enforcement_literal()) {
8147 clause.push_back(convert(ref).Negated());
8149 sat_solver->AddProblemClause(clause);
8151 context_->working_model->mutable_constraints(
i)->Clear();
8152 context_->UpdateConstraintVariableUsage(
i);
8156 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
8159 const int left_size = ct.enforcement_literal().size();
8160 const int right_size = ct.bool_and().literals().size();
8161 if (left_size > 1 && right_size > 1 &&
8162 (left_size + 1) * right_size > 10'000) {
8163 ++num_ignored_constraints;
8167 ++num_removed_constraints;
8168 std::vector<Literal> clause;
8169 for (
const int ref : ct.enforcement_literal()) {
8170 clause.push_back(convert(ref).Negated());
8173 for (
const int ref : ct.bool_and().literals()) {
8174 clause.back() = convert(ref);
8175 sat_solver->AddProblemClause(clause);
8178 context_->working_model->mutable_constraints(
i)->Clear();
8179 context_->UpdateConstraintVariableUsage(
i);
8183 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8187 ++num_ignored_constraints;
8189 if (sat_solver->ModelIsUnsat())
return false;
8192 if (num_removed_constraints == 0)
return true;
8202 int num_in_extra_constraints = 0;
8203 std::vector<bool> can_be_removed(num_variables,
false);
8204 for (
int i = 0;
i < num_variables; ++
i) {
8205 const int var = new_to_old_index[
i];
8206 if (context_->VarToConstraints(var).empty()) {
8207 can_be_removed[
i] =
true;
8211 ++num_in_extra_constraints;
8224 SatParameters sat_params = context_->params();
8225 if (sat_params.debug_postsolve_with_full_solver() ||
8226 sat_params.fill_tightened_domains_in_response()) {
8227 sat_params.set_presolve_blocked_clause(
false);
8231 if (!sat_params.debug_postsolve_with_full_solver()) {
8232 sat_params.set_filter_sat_postsolve_clauses(
true);
8235 SatPostsolver sat_postsolver(num_variables);
8245 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8246 if (!context_->params().debug_postsolve_with_full_solver() &&
8247 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8248 num_in_extra_constraints == 0) {
8254 &local_model, logger_)) {
8257 if (sat_solver->LiteralTrail().Index() == num_variables) {
8259 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8264 SatPresolveOptions options;
8265 options.log_info =
true;
8266 options.extract_binary_clauses_in_probing =
false;
8267 options.use_transitive_reduction =
false;
8268 options.deterministic_time_limit =
8269 context_->params().presolve_probing_deterministic_time_limit();
8271 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8272 inprocessing->ProvideLogger(logger_);
8273 if (!inprocessing->PresolveLoop(options))
return false;
8274 for (
const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8275 sat_postsolver.Add(c[0], c);
8281 nullptr, &equiv_map,
8283 if (sat_solver->ModelIsUnsat())
return false;
8288 sat_params.set_presolve_use_bva(
false);
8296 if (context_->params().keep_symmetry_in_presolve()) {
8297 sat_params.set_presolve_use_bva(
false);
8301 if (!sat_solver->ResetToLevelZero())
return false;
8302 time_limit_->AdvanceDeterministicTime(
8303 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8306 SatPresolver sat_presolver(&sat_postsolver, logger_);
8307 sat_presolver.SetNumVariables(num_variables);
8308 if (!equiv_map.empty()) {
8309 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8311 sat_presolver.SetTimeLimit(time_limit_);
8312 sat_presolver.SetParameters(sat_params);
8316 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
8317 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[
i]);
8319 if (!sat_solver->ExtractClauses(&sat_presolver))
return false;
8323 for (
int i = 0;
i < 1; ++
i) {
8324 const int old_num_clause = sat_postsolver.NumClauses();
8325 if (!sat_presolver.Presolve(can_be_removed))
return false;
8326 if (old_num_clause == sat_postsolver.NumClauses())
break;
8330 const int new_num_variables = sat_presolver.NumVariables();
8331 if (new_num_variables > num_variables) {
8332 VLOG(1) <<
"New variables added by the SAT presolver.";
8333 for (
int i = num_variables;
i < new_num_variables; ++
i) {
8334 new_to_old_index.push_back(context_->working_model->variables().size());
8335 IntegerVariableProto* var_proto =
8336 context_->working_model->add_variables();
8337 var_proto->add_domain(0);
8338 var_proto->add_domain(1);
8340 context_->InitializeNewDomains();
8344 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8350 ExtractClauses(
true, new_to_old_index, sat_presolver,
8351 context_->working_model);
8354 context_->UpdateNewConstraintsVariableUsage();
8359 for (
int i = 0;
i < num_variables; ++
i) {
8360 const int var = new_to_old_index[
i];
8361 if (context_->VarToConstraints(var).empty()) {
8362 context_->MarkVariableAsRemoved(var);
8367 const std::string name =
8368 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ?
"sat_postsolver" :
"";
8369 ExtractClauses(
false, new_to_old_index,
8370 sat_postsolver, context_->mapping_model, name);
8374void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8375 if (context_->ModelIsUnsat())
return;
8379 if (!context_->CanonicalizeObjective()) {
8383 std::vector<int> exos;
8384 const int num_constraints = context_->working_model->constraints_size();
8385 for (
int c = 0;
c < num_constraints; ++
c) {
8386 const ConstraintProto& ct = context_->working_model->constraints(c);
8387 if (!ct.enforcement_literal().empty())
continue;
8388 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8405 for (
int i = 0;
i < 3; ++
i) {
8406 for (
const int c : exos) {
8407 const ConstraintProto& ct = context_->working_model->constraints(c);
8408 const int num_terms = ct.exactly_one().literals().size();
8409 if (num_terms <= 1)
continue;
8410 int64_t min_obj = std::numeric_limits<int64_t>::max();
8411 int64_t second_min = std::numeric_limits<int64_t>::max();
8412 for (
int i = 0;
i < num_terms; ++
i) {
8413 const int literal = ct.exactly_one().literals(
i);
8414 const int64_t var_obj = context_->ObjectiveCoeff(
PositiveRef(literal));
8415 const int64_t obj =
RefIsPositive(literal) ? var_obj : -var_obj;
8416 if (obj < min_obj) {
8417 second_min = min_obj;
8419 }
else if (obj < second_min) {
8423 if (second_min == 0)
continue;
8425 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8427 if (context_->ModelIsUnsat())
return;
8432 if (num_shifts > 0) {
8433 context_->UpdateRuleStats(
"objective: shifted cost with exactly ones",
8438bool CpModelPresolver::PropagateObjective() {
8439 if (!context_->working_model->has_objective())
return true;
8440 if (context_->ModelIsUnsat())
return false;
8441 context_->WriteObjectiveToProto();
8443 int64_t min_activity = 0;
8444 int64_t max_variation = 0;
8445 const CpObjectiveProto& objective = context_->working_model->objective();
8446 const int num_terms = objective.vars().size();
8447 for (
int i = 0;
i < num_terms; ++
i) {
8448 const int var = objective.vars(
i);
8449 const int64_t coeff = objective.coeffs(
i);
8453 const int64_t domain_min = context_->MinOf(var);
8454 const int64_t domain_max = context_->MaxOf(var);
8456 min_activity += coeff * domain_min;
8458 min_activity += coeff * domain_max;
8460 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8461 max_variation = std::max(max_variation, variation);
8465 const int64_t slack =
8468 return context_->NotifyThatModelIsUnsat(
8469 "infeasible while propagating objective");
8473 if (max_variation <= slack)
return true;
8475 int num_propagations = 0;
8476 for (
int i = 0;
i < num_terms; ++
i) {
8477 const int var = objective.vars(
i);
8478 const int64_t coeff = objective.coeffs(
i);
8479 const int64_t domain_min = context_->MinOf(var);
8480 const int64_t domain_max = context_->MaxOf(var);
8482 const int64_t new_diff = slack / std::abs(coeff);
8483 if (new_diff >= domain_max - domain_min)
continue;
8487 if (!context_->IntersectDomainWith(
8488 var, Domain(domain_min, domain_min + new_diff))) {
8492 if (!context_->IntersectDomainWith(
8493 var, Domain(domain_max - new_diff, domain_max))) {
8498 CHECK_GT(num_propagations, 0);
8500 context_->UpdateRuleStats(
"objective: restricted var domains by propagation",
8519void CpModelPresolver::ExpandObjective() {
8520 if (time_limit_->LimitReached())
return;
8521 if (context_->ModelIsUnsat())
return;
8522 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8526 if (!context_->CanonicalizeObjective()) {
8530 const int num_variables = context_->working_model->variables_size();
8531 const int num_constraints = context_->working_model->constraints_size();
8534 const auto get_index = [](
int var,
bool to_lb) {
8535 return 2 * var + (to_lb ? 0 : 1);
8537 const auto get_lit_index = [](
int lit) {
8540 const int num_nodes = 2 * num_variables;
8541 std::vector<std::vector<int>> index_graph(num_nodes);
8545 std::vector<int> index_to_best_c(num_nodes, -1);
8546 std::vector<int> index_to_best_size(num_nodes, 0);
8550 int num_entries = 0;
8551 int num_propagations = 0;
8552 int num_tight_variables = 0;
8553 int num_tight_constraints = 0;
8554 const int kNumEntriesThreshold = 1e8;
8555 for (
int c = 0;
c < num_constraints; ++
c) {
8556 if (num_entries > kNumEntriesThreshold)
break;
8558 const ConstraintProto& ct = context_->working_model->constraints(c);
8559 if (!ct.enforcement_literal().empty())
continue;
8567 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8568 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
8569 context_->UpdateConstraintVariableUsage(c);
8572 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8573 const int num_terms = ct.exactly_one().literals().size();
8574 ++num_tight_constraints;
8575 num_tight_variables += num_terms;
8576 for (
int i = 0;
i < num_terms; ++
i) {
8577 if (num_entries > kNumEntriesThreshold)
break;
8578 const int neg_index = get_lit_index(ct.exactly_one().literals(
i)) ^ 1;
8580 const int old_c = index_to_best_c[neg_index];
8581 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8582 index_to_best_c[neg_index] =
c;
8583 index_to_best_size[neg_index] = num_terms;
8586 for (
int j = 0; j < num_terms; ++j) {
8587 if (j ==
i)
continue;
8588 const int other_index = get_lit_index(ct.exactly_one().literals(j));
8590 index_graph[neg_index].push_back(other_index);
8597 if (!IsLinearEqualityConstraint(ct))
continue;
8601 const auto [min_activity, max_activity] =
8602 context_->ComputeMinMaxActivity(ct.linear());
8604 bool is_tight =
false;
8605 const int64_t rhs = ct.linear().domain(0);
8606 const int num_terms = ct.linear().vars_size();
8607 for (
int i = 0;
i < num_terms; ++
i) {
8608 const int var = ct.linear().vars(
i);
8609 const int64_t coeff = ct.linear().coeffs(
i);
8610 if (std::abs(coeff) != 1)
continue;
8611 if (num_entries > kNumEntriesThreshold)
break;
8613 const int index = get_index(var, coeff > 0);
8615 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
8616 const int64_t implied_shifted_ub = rhs - min_activity;
8617 if (implied_shifted_ub <= var_range) {
8618 if (implied_shifted_ub < var_range) ++num_propagations;
8620 ++num_tight_variables;
8622 const int neg_index = index ^ 1;
8623 const int old_c = index_to_best_c[neg_index];
8624 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8625 index_to_best_c[neg_index] =
c;
8626 index_to_best_size[neg_index] = num_terms;
8629 for (
int j = 0; j < num_terms; ++j) {
8630 if (j ==
i)
continue;
8631 const int other_index =
8632 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
8634 index_graph[neg_index].push_back(other_index);
8637 const int64_t implied_shifted_lb = max_activity - rhs;
8638 if (implied_shifted_lb <= var_range) {
8639 if (implied_shifted_lb < var_range) ++num_propagations;
8641 ++num_tight_variables;
8643 const int old_c = index_to_best_c[index];
8644 if (old_c == -1 || num_terms > index_to_best_size[index]) {
8645 index_to_best_c[index] =
c;
8646 index_to_best_size[index] = num_terms;
8649 for (
int j = 0; j < num_terms; ++j) {
8650 if (j ==
i)
continue;
8651 const int other_index =
8652 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
8654 index_graph[index].push_back(other_index);
8658 if (is_tight) ++num_tight_constraints;
8664 if (num_propagations > 0) {
8665 context_->UpdateRuleStats(
"TODO objective: propagation possible!");
8684 if (!topo_order.ok()) {
8687 std::vector<int64_t> var_min(num_variables);
8688 std::vector<int64_t> var_max(num_variables);
8689 for (
int var = 0; var < num_variables; ++var) {
8690 var_min[var] = context_->MinOf(var);
8691 var_max[var] = context_->MaxOf(var);
8694 std::vector<std::vector<int>> components;
8696 index_graph, &components);
8697 for (
const std::vector<int>& compo : components) {
8698 if (compo.size() == 1)
continue;
8700 const int rep_var = compo[0] / 2;
8701 const bool rep_to_lp = (compo[0] % 2) == 0;
8702 for (
int i = 1;
i < compo.size(); ++
i) {
8703 const int var = compo[
i] / 2;
8704 const bool to_lb = (compo[
i] % 2) == 0;
8708 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
8709 const int64_t var_coeff = to_lb ? 1 : -1;
8710 const int64_t offset =
8711 (to_lb ? -var_min[var] : var_max[var]) -
8712 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
8713 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
8714 rep_coeff * offset)) {
8718 context_->UpdateRuleStats(
"objective: detected equivalence",
8727 int num_expands = 0;
8729 for (
const int index : *topo_order) {
8730 if (index_graph[index].empty())
continue;
8732 const int var = index / 2;
8733 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
8734 if (obj_coeff == 0)
continue;
8736 const bool to_lb = (index % 2) == 0;
8737 if (obj_coeff > 0 == to_lb) {
8738 const ConstraintProto& ct =
8739 context_->working_model->constraints(index_to_best_c[index]);
8740 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8742 for (
const int lit : ct.exactly_one().literals()) {
8752 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8754 if (context_->ModelIsUnsat())
return;
8758 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
8763 int64_t objective_coeff_in_expanded_constraint = 0;
8764 const int num_terms = ct.linear().vars().size();
8765 for (
int i = 0;
i < num_terms; ++
i) {
8766 if (ct.linear().vars(
i) == var) {
8767 objective_coeff_in_expanded_constraint = ct.linear().coeffs(
i);
8771 if (objective_coeff_in_expanded_constraint == 0) {
8776 if (!context_->SubstituteVariableInObjective(
8777 var, objective_coeff_in_expanded_constraint, ct)) {
8778 if (context_->ModelIsUnsat())
return;
8787 if (num_expands > 0) {
8788 context_->UpdateRuleStats(
"objective: expanded via tight equality",
8792 timer.AddCounter(
"propagations", num_propagations);
8793 timer.AddCounter(
"entries", num_entries);
8794 timer.AddCounter(
"tight_variables", num_tight_variables);
8795 timer.AddCounter(
"tight_constraints", num_tight_constraints);
8796 timer.AddCounter(
"expands", num_expands);
8797 timer.AddCounter(
"issues", num_issues);
8800void CpModelPresolver::MergeNoOverlapConstraints() {
8801 if (context_->ModelIsUnsat())
return;
8802 if (time_limit_->LimitReached())
return;
8804 const int num_constraints = context_->working_model->constraints_size();
8805 int old_num_no_overlaps = 0;
8806 int old_num_intervals = 0;
8809 std::vector<int> disjunctive_index;
8810 std::vector<std::vector<Literal>> cliques;
8811 for (
int c = 0;
c < num_constraints; ++
c) {
8812 const ConstraintProto& ct = context_->working_model->constraints(c);
8813 if (ct.constraint_case() != ConstraintProto::kNoOverlap)
continue;
8814 std::vector<Literal> clique;
8815 for (
const int i : ct.no_overlap().intervals()) {
8816 clique.push_back(Literal(BooleanVariable(
i),
true));
8818 cliques.push_back(clique);
8819 disjunctive_index.push_back(c);
8821 old_num_no_overlaps++;
8822 old_num_intervals += clique.size();
8824 if (old_num_no_overlaps == 0)
return;
8828 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
8829 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
8830 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8831 graph->Resize(num_constraints);
8832 for (
const std::vector<Literal>& clique : cliques) {
8835 CHECK(graph->AddAtMostOne(clique));
8837 CHECK(graph->DetectEquivalences());
8838 graph->TransformIntoMaxCliques(
8843 int new_num_no_overlaps = 0;
8844 int new_num_intervals = 0;
8845 for (
int i = 0;
i < cliques.size(); ++
i) {
8846 const int ct_index = disjunctive_index[
i];
8847 ConstraintProto* ct =
8848 context_->working_model->mutable_constraints(ct_index);
8850 if (cliques[
i].empty())
continue;
8851 for (
const Literal l : cliques[
i]) {
8852 CHECK(l.IsPositive());
8853 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
8855 new_num_no_overlaps++;
8856 new_num_intervals += cliques[
i].size();
8858 if (old_num_intervals != new_num_intervals ||
8859 old_num_no_overlaps != new_num_no_overlaps) {
8860 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
8861 old_num_intervals,
" intervals) into ",
8862 new_num_no_overlaps,
" no-overlaps (",
8863 new_num_intervals,
" intervals).");
8864 context_->UpdateRuleStats(
"no_overlap: merged constraints");
8866 time_limit_->ResetHistory();
8873void CpModelPresolver::TransformIntoMaxCliques() {
8874 if (context_->ModelIsUnsat())
return;
8875 if (context_->params().merge_at_most_one_work_limit() <= 0.0)
return;
8877 auto convert = [](
int ref) {
8878 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
8879 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
8881 const int num_constraints = context_->working_model->constraints_size();
8885 std::vector<std::vector<Literal>> cliques;
8887 for (
int c = 0;
c < num_constraints; ++
c) {
8888 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8889 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
8890 std::vector<Literal> clique;
8891 for (
const int ref : ct->at_most_one().literals()) {
8892 clique.push_back(convert(ref));
8894 cliques.push_back(clique);
8895 if (RemoveConstraint(ct)) {
8896 context_->UpdateConstraintVariableUsage(c);
8898 }
else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8899 if (ct->enforcement_literal().size() != 1)
continue;
8900 const Literal enforcement = convert(ct->enforcement_literal(0));
8901 for (
const int ref : ct->bool_and().literals()) {
8902 if (ref == ct->enforcement_literal(0))
continue;
8903 cliques.push_back({enforcement, convert(ref).Negated()});
8905 if (RemoveConstraint(ct)) {
8906 context_->UpdateConstraintVariableUsage(c);
8911 int64_t num_literals_before = 0;
8912 const int num_old_cliques = cliques.size();
8916 const int num_variables = context_->working_model->
variables().size();
8917 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8918 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8919 graph->Resize(num_variables);
8920 for (
const std::vector<Literal>& clique : cliques) {
8921 num_literals_before += clique.size();
8922 if (!graph->AddAtMostOne(clique)) {
8923 return (
void)context_->NotifyThatModelIsUnsat();
8926 if (!graph->DetectEquivalences()) {
8927 return (
void)context_->NotifyThatModelIsUnsat();
8929 graph->MergeAtMostOnes(
8930 absl::MakeSpan(cliques),
8936 for (
int var = 0; var < num_variables; ++var) {
8937 const Literal l = Literal(BooleanVariable(var),
true);
8938 if (graph->RepresentativeOf(l) != l) {
8939 const Literal r = graph->RepresentativeOf(l);
8940 if (!context_->StoreBooleanEqualityRelation(
8941 var, r.IsPositive() ? r.Variable().value()
8948 int num_new_cliques = 0;
8949 int64_t num_literals_after = 0;
8950 for (
const std::vector<Literal>& clique : cliques) {
8951 if (clique.empty())
continue;
8953 num_literals_after += clique.size();
8954 ConstraintProto* ct = context_->working_model->add_constraints();
8955 for (
const Literal literal : clique) {
8956 if (literal.IsPositive()) {
8957 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
8959 ct->mutable_at_most_one()->add_literals(
8965 PresolveAtMostOne(ct);
8967 context_->UpdateNewConstraintsVariableUsage();
8968 if (num_new_cliques != num_old_cliques) {
8969 context_->UpdateRuleStats(
"at_most_one: transformed into max clique.");
8972 if (num_old_cliques != num_new_cliques ||
8973 num_literals_before != num_literals_after) {
8974 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
8975 num_literals_before,
" literals) into ", num_new_cliques,
"(",
8976 num_literals_after,
" literals) at_most_ones.");
8981 if (context_->ModelIsUnsat())
return false;
8982 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8985 if (ExploitEquivalenceRelations(c, ct)) {
8986 context_->UpdateConstraintVariableUsage(c);
8990 if (PresolveEnforcementLiteral(ct)) {
8991 context_->UpdateConstraintVariableUsage(c);
8995 switch (ct->constraint_case()) {
8996 case ConstraintProto::kBoolOr:
8997 return PresolveBoolOr(ct);
8998 case ConstraintProto::kBoolAnd:
8999 return PresolveBoolAnd(ct);
9000 case ConstraintProto::kAtMostOne:
9001 return PresolveAtMostOne(ct);
9002 case ConstraintProto::kExactlyOne:
9003 return PresolveExactlyOne(ct);
9004 case ConstraintProto::kBoolXor:
9005 return PresolveBoolXor(ct);
9006 case ConstraintProto::kLinMax:
9007 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
9008 context_->UpdateConstraintVariableUsage(c);
9010 return PresolveLinMax(c, ct);
9011 case ConstraintProto::kIntProd:
9012 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
9013 context_->UpdateConstraintVariableUsage(c);
9015 return PresolveIntProd(ct);
9016 case ConstraintProto::kIntDiv:
9017 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
9018 context_->UpdateConstraintVariableUsage(c);
9020 return PresolveIntDiv(c, ct);
9021 case ConstraintProto::kIntMod:
9022 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
9023 context_->UpdateConstraintVariableUsage(c);
9025 return PresolveIntMod(c, ct);
9026 case ConstraintProto::kLinear: {
9027 bool changed =
false;
9028 if (!CanonicalizeLinear(ct, &changed)) {
9032 context_->UpdateConstraintVariableUsage(c);
9034 if (PropagateDomainsInLinear(c, ct)) {
9035 context_->UpdateConstraintVariableUsage(c);
9037 if (PresolveSmallLinear(ct)) {
9038 context_->UpdateConstraintVariableUsage(c);
9040 if (PresolveLinearEqualityWithModulo(ct)) {
9041 context_->UpdateConstraintVariableUsage(c);
9044 if (RemoveSingletonInLinear(ct)) {
9045 context_->UpdateConstraintVariableUsage(c);
9049 if (PresolveSmallLinear(ct)) {
9050 context_->UpdateConstraintVariableUsage(c);
9053 if (PresolveSmallLinear(ct)) {
9054 context_->UpdateConstraintVariableUsage(c);
9056 if (PresolveLinearOnBooleans(ct)) {
9057 context_->UpdateConstraintVariableUsage(c);
9061 const int old_num_enforcement_literals = ct->enforcement_literal_size();
9062 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
9063 if (context_->ModelIsUnsat())
return false;
9064 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
9065 if (DivideLinearByGcd(ct)) {
9066 context_->UpdateConstraintVariableUsage(c);
9068 if (PresolveSmallLinear(ct)) {
9069 context_->UpdateConstraintVariableUsage(c);
9073 if (PresolveDiophantine(ct)) {
9074 context_->UpdateConstraintVariableUsage(c);
9077 TryToReduceCoefficientsOfLinearConstraint(c, ct);
9080 case ConstraintProto::kInterval:
9081 return PresolveInterval(c, ct);
9082 case ConstraintProto::kInverse:
9083 return PresolveInverse(ct);
9084 case ConstraintProto::kElement:
9085 return PresolveElement(c, ct);
9086 case ConstraintProto::kTable:
9087 return PresolveTable(ct);
9088 case ConstraintProto::kAllDiff:
9089 return PresolveAllDiff(ct);
9090 case ConstraintProto::kNoOverlap:
9091 DetectDuplicateIntervals(c,
9092 ct->mutable_no_overlap()->mutable_intervals());
9093 return PresolveNoOverlap(ct);
9094 case ConstraintProto::kNoOverlap2D: {
9095 const bool changed = PresolveNoOverlap2D(c, ct);
9096 if (ct->constraint_case() == ConstraintProto::kNoOverlap2D) {
9101 DetectDuplicateIntervals(
9102 c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
9103 DetectDuplicateIntervals(
9104 c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
9108 case ConstraintProto::kCumulative:
9109 DetectDuplicateIntervals(c,
9110 ct->mutable_cumulative()->mutable_intervals());
9111 return PresolveCumulative(ct);
9112 case ConstraintProto::kCircuit:
9113 return PresolveCircuit(ct);
9114 case ConstraintProto::kRoutes:
9115 return PresolveRoutes(ct);
9116 case ConstraintProto::kAutomaton:
9117 return PresolveAutomaton(ct);
9118 case ConstraintProto::kReservoir:
9119 return PresolveReservoir(ct);
9126bool CpModelPresolver::ProcessSetPPCSubset(
int subset_c,
int superset_c,
9127 absl::flat_hash_set<int>* tmp_set,
9128 bool* remove_subset,
9129 bool* remove_superset,
9130 bool* stop_processing_superset) {
9131 ConstraintProto* subset_ct =
9133 ConstraintProto* superset_ct =
9136 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9137 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
9138 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9139 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9143 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9144 tmp_set->insert(subset_ct->bool_or().literals().begin(),
9145 subset_ct->bool_or().literals().end());
9147 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9148 subset_ct->exactly_one().literals().end());
9153 for (
const int literal :
9154 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
9155 ? superset_ct->at_most_one().literals()
9156 : superset_ct->exactly_one().literals()) {
9157 if (tmp_set->contains(literal))
continue;
9163 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
9164 ConstraintProto copy = *superset_ct;
9165 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
9166 copy.at_most_one().literals();
9169 *remove_subset =
true;
9173 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
9174 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
9175 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
9176 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
9177 *remove_superset =
true;
9181 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
9182 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
9183 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
9184 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
9185 *remove_subset =
true;
9191 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
9192 superset_ct->constraint_case() == ConstraintProto::kLinear) {
9194 int64_t min_sum = std::numeric_limits<int64_t>::max();
9195 int64_t max_sum = std::numeric_limits<int64_t>::min();
9196 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
9197 subset_ct->exactly_one().literals().end());
9200 int num_matches = 0;
9202 Domain reachable(0);
9203 std::vector<std::pair<int64_t, int>> coeff_counts;
9204 for (
int i = 0;
i < superset_ct->linear().vars().size(); ++
i) {
9205 const int var = superset_ct->linear().vars(
i);
9206 const int64_t coeff = superset_ct->linear().coeffs(
i);
9207 if (tmp_set->contains(var)) {
9209 min_sum = std::min(min_sum, coeff);
9210 max_sum = std::max(max_sum, coeff);
9211 coeff_counts.push_back({superset_ct->linear().coeffs(
i), 1});
9216 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9217 .RelaxIfTooComplex();
9218 temp_ct_.mutable_linear()->add_vars(var);
9219 temp_ct_.mutable_linear()->add_coeffs(coeff);
9229 if (num_matches != tmp_set->size())
return true;
9230 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
9231 context_->UpdateRuleStats(
"setppc: exactly_one included in linear");
9233 context_->UpdateRuleStats(
"setppc: at_most_one included in linear");
9236 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9238 if (reachable.IsIncludedIn(superset_rhs)) {
9240 context_->UpdateRuleStats(
"setppc: removed trivial linear constraint");
9241 *remove_superset =
true;
9244 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9246 context_->UpdateRuleStats(
"setppc: removed infeasible linear constraint");
9247 *stop_processing_superset =
true;
9248 return MarkConstraintAsFalse(superset_ct);
9253 if (superset_ct->enforcement_literal().empty()) {
9254 CHECK_GT(num_matches, 0);
9257 temp_ct_.mutable_linear());
9258 PropagateDomainsInLinear(-1, &temp_ct_);
9264 std::sort(coeff_counts.begin(), coeff_counts.end());
9266 for (
int i = 0;
i < coeff_counts.size(); ++
i) {
9268 coeff_counts[
i].first == coeff_counts[new_size - 1].first) {
9269 coeff_counts[new_size - 1].second++;
9272 coeff_counts[new_size++] = coeff_counts[
i];
9274 coeff_counts.resize(new_size);
9276 int64_t best_count = 0;
9277 for (
const auto [coeff, count] : coeff_counts) {
9278 if (count > best_count) {
9284 LinearConstraintProto new_ct = superset_ct->linear();
9286 for (
int i = 0;
i < new_ct.vars().size(); ++
i) {
9287 const int var = new_ct.vars(
i);
9288 int64_t coeff = new_ct.coeffs(
i);
9289 if (tmp_set->contains(var)) {
9290 if (coeff == best)
continue;
9293 new_ct.set_vars(new_size, var);
9294 new_ct.set_coeffs(new_size, coeff);
9298 new_ct.mutable_vars()->Truncate(new_size);
9299 new_ct.mutable_coeffs()->Truncate(new_size);
9304 *superset_ct->mutable_linear() = std::move(new_ct);
9305 context_->UpdateConstraintVariableUsage(superset_c);
9306 context_->UpdateRuleStats(
"setppc: reduced linear coefficients");
9321void CpModelPresolver::ProcessSetPPC() {
9322 if (time_limit_->LimitReached())
return;
9323 if (context_->ModelIsUnsat())
return;
9324 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
9325 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9328 CompactVectorVector<int> storage;
9330 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9333 std::vector<int> temp_literals;
9334 const int num_constraints = context_->working_model->constraints_size();
9335 std::vector<int> relevant_constraints;
9336 for (
int c = 0;
c < num_constraints; ++
c) {
9337 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9338 const auto type = ct->constraint_case();
9339 if (type == ConstraintProto::kBoolOr ||
9340 type == ConstraintProto::kAtMostOne ||
9341 type == ConstraintProto::kExactlyOne) {
9346 context_->UpdateConstraintVariableUsage(c);
9348 if (context_->ModelIsUnsat())
return;
9350 temp_literals.clear();
9351 for (
const int ref :
9352 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
9353 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
9354 : ct->exactly_one().literals()) {
9355 temp_literals.push_back(
9360 relevant_constraints.push_back(c);
9361 detector.AddPotentialSet(storage.Add(temp_literals));
9362 }
else if (type == ConstraintProto::kLinear) {
9372 const int size = ct->linear().vars().size();
9373 if (size <= 2)
continue;
9378 temp_literals.clear();
9379 for (
int i = 0;
i < size; ++
i) {
9380 const int var = ct->linear().vars(
i);
9381 if (!context_->CanBeUsedAsLiteral(var))
continue;
9383 temp_literals.push_back(
9384 Literal(BooleanVariable(var),
true).
Index().value());
9386 if (temp_literals.size() > 2) {
9388 relevant_constraints.push_back(c);
9389 detector.AddPotentialSuperset(storage.Add(temp_literals));
9394 absl::flat_hash_set<int> tmp_set;
9395 int64_t num_inclusions = 0;
9396 detector.DetectInclusions([&](
int subset,
int superset) {
9398 bool remove_subset =
false;
9399 bool remove_superset =
false;
9400 bool stop_processing_superset =
false;
9401 const int subset_c = relevant_constraints[subset];
9402 const int superset_c = relevant_constraints[superset];
9403 detector.IncreaseWorkDone(storage[subset].size());
9404 detector.IncreaseWorkDone(storage[superset].size());
9405 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
9406 &remove_superset, &stop_processing_superset)) {
9410 if (remove_subset) {
9411 context_->working_model->mutable_constraints(subset_c)->Clear();
9412 context_->UpdateConstraintVariableUsage(subset_c);
9413 detector.StopProcessingCurrentSubset();
9415 if (remove_superset) {
9416 context_->working_model->mutable_constraints(superset_c)->Clear();
9417 context_->UpdateConstraintVariableUsage(superset_c);
9418 detector.StopProcessingCurrentSuperset();
9420 if (stop_processing_superset) {
9421 context_->UpdateConstraintVariableUsage(superset_c);
9422 detector.StopProcessingCurrentSuperset();
9426 timer.AddToWork(detector.work_done() * 1e-9);
9427 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
9428 timer.AddCounter(
"num_inclusions", num_inclusions);
9431void CpModelPresolver::DetectIncludedEnforcement() {
9432 if (time_limit_->LimitReached())
return;
9433 if (context_->ModelIsUnsat())
return;
9434 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
9435 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9438 std::vector<int> relevant_constraints;
9439 CompactVectorVector<int> storage;
9441 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9443 std::vector<int> temp_literals;
9444 const int num_constraints = context_->working_model->constraints_size();
9445 for (
int c = 0;
c < num_constraints; ++
c) {
9446 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9447 if (ct->enforcement_literal().size() <= 1)
continue;
9450 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9452 context_->UpdateConstraintVariableUsage(c);
9454 if (context_->ModelIsUnsat())
return;
9458 temp_literals.clear();
9459 for (
const int ref : ct->enforcement_literal()) {
9460 temp_literals.push_back(
9465 relevant_constraints.push_back(c);
9469 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9470 detector.AddPotentialSet(storage.Add(temp_literals));
9472 detector.AddPotentialSuperset(storage.Add(temp_literals));
9476 int64_t num_inclusions = 0;
9477 detector.DetectInclusions([&](
int subset,
int superset) {
9479 const int subset_c = relevant_constraints[subset];
9480 const int superset_c = relevant_constraints[superset];
9481 ConstraintProto* subset_ct =
9482 context_->working_model->mutable_constraints(subset_c);
9483 ConstraintProto* superset_ct =
9484 context_->working_model->mutable_constraints(superset_c);
9485 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd)
return;
9487 context_->tmp_literal_set.clear();
9488 for (
const int ref : subset_ct->bool_and().literals()) {
9489 context_->tmp_literal_set.insert(ref);
9495 for (
const int ref : superset_ct->enforcement_literal()) {
9496 if (context_->tmp_literal_set.contains(ref)) {
9497 context_->UpdateRuleStats(
"bool_and: filtered enforcement");
9498 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
9499 context_->UpdateRuleStats(
"bool_and: never enforced");
9500 superset_ct->Clear();
9501 context_->UpdateConstraintVariableUsage(superset_c);
9502 detector.StopProcessingCurrentSuperset();
9505 superset_ct->set_enforcement_literal(new_size++, ref);
9508 if (new_size < superset_ct->bool_and().literals().size()) {
9509 context_->UpdateConstraintVariableUsage(superset_c);
9510 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
9514 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
9516 for (
const int ref : superset_ct->bool_and().literals()) {
9517 if (context_->tmp_literal_set.contains(ref)) {
9518 context_->UpdateRuleStats(
"bool_and: filtered literal");
9519 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
9520 context_->UpdateRuleStats(
"bool_and: must be false");
9521 if (!MarkConstraintAsFalse(superset_ct))
return;
9522 context_->UpdateConstraintVariableUsage(superset_c);
9523 detector.StopProcessingCurrentSuperset();
9526 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
9529 if (new_size < superset_ct->bool_and().literals().size()) {
9530 context_->UpdateConstraintVariableUsage(superset_c);
9531 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
9535 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
9536 context_->UpdateRuleStats(
"TODO bool_and enforcement in linear enf");
9540 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
9541 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
9542 timer.AddCounter(
"num_inclusions", num_inclusions);
9553bool CpModelPresolver::ProcessEncodingFromLinear(
9554 const int linear_encoding_ct_index,
9555 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
9556 int64_t* num_multiple_terms) {
9558 bool in_exactly_one =
false;
9559 absl::flat_hash_map<int, int> var_to_ref;
9560 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
9561 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
9566 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
9567 ConstraintProto::kExactlyOne);
9568 in_exactly_one =
true;
9569 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
9576 const ConstraintProto& linear_encoding =
9577 context_->working_model->constraints(linear_encoding_ct_index);
9578 int64_t rhs = linear_encoding.linear().domain(0);
9579 int target_ref = std::numeric_limits<int>::min();
9580 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
9581 const int num_terms = linear_encoding.linear().vars().size();
9582 for (
int i = 0;
i < num_terms; ++
i) {
9583 const int ref = linear_encoding.linear().vars(
i);
9584 const int64_t coeff = linear_encoding.linear().coeffs(
i);
9585 const auto it = var_to_ref.find(
PositiveRef(ref));
9587 if (it == var_to_ref.end()) {
9588 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) <<
"Uniqueness";
9589 CHECK_EQ(std::abs(coeff), 1);
9590 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
9596 if (it->second == ref) {
9598 ref_to_coeffs.push_back({ref, coeff});
9602 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
9605 if (target_ref == std::numeric_limits<int>::min() ||
9606 context_->CanBeUsedAsLiteral(target_ref)) {
9610 context_->UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
9615 std::vector<int64_t> all_values;
9616 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
9617 for (
const auto& [ref, coeff] : ref_to_coeffs) {
9618 const int64_t value = rhs - coeff;
9619 all_values.push_back(value);
9620 value_to_refs[value].push_back(ref);
9624 for (
const auto& [var, ref] : var_to_ref) {
9625 all_values.push_back(rhs);
9626 value_to_refs[rhs].push_back(ref);
9628 if (!in_exactly_one) {
9631 all_values.push_back(rhs);
9636 bool domain_reduced =
false;
9637 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
9640 if (domain_reduced) {
9641 context_->UpdateRuleStats(
"encoding: reduced target domain");
9644 if (context_->CanBeUsedAsLiteral(target_ref)) {
9646 context_->UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
9651 absl::flat_hash_set<int64_t> value_set;
9652 for (
const int64_t v : context_->DomainOf(target_ref).Values()) {
9653 value_set.insert(v);
9655 for (
auto& [value, literals] : value_to_refs) {
9657 absl::c_sort(literals);
9660 if (!value_set.contains(value)) {
9661 for (
const int lit : literals) {
9662 if (!context_->SetLiteralToFalse(lit))
return false;
9667 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
9670 ++*num_unique_terms;
9671 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
9675 ++*num_multiple_terms;
9676 const int associated_lit =
9677 context_->GetOrCreateVarValueEncoding(target_ref, value);
9678 for (
const int lit : literals) {
9679 context_->AddImplication(lit, associated_lit);
9684 if (in_exactly_one || value != rhs) {
9690 context_->working_model->add_constraints()->mutable_bool_or();
9691 for (
const int lit : literals) bool_or->add_literals(lit);
9692 bool_or->add_literals(
NegatedRef(associated_lit));
9698 context_->working_model->mutable_constraints(linear_encoding_ct_index)
9700 context_->UpdateNewConstraintsVariableUsage();
9701 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
9719 if (a ==
b)
return true;
9722 return (*
column)[a] == (*column)[
b];
9732 if (time_limit_->LimitReached())
return;
9733 if (context_->ModelIsUnsat())
return;
9734 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
9737 const int num_vars = context_->working_model->variables().size();
9738 const int num_constraints = context_->working_model->constraints().size();
9742 std::vector<int> flat_vars;
9743 std::vector<std::pair<int, int64_t>> flat_terms;
9757 std::vector<bool> appear_in_amo(num_vars,
false);
9758 std::vector<bool> appear_in_bool_constraint(num_vars,
false);
9759 for (
int c = 0; c < num_constraints; ++c) {
9760 const ConstraintProto& ct = context_->working_model->constraints(c);
9761 absl::Span<const int> literals;
9763 bool is_amo =
false;
9764 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
9766 literals = ct.at_most_one().literals();
9767 }
else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9769 literals = ct.exactly_one().literals();
9770 }
else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
9771 literals = ct.bool_or().literals();
9774 if (!literals.empty()) {
9775 for (
const int lit : literals) {
9778 if (is_amo) appear_in_amo[lit] =
true;
9779 appear_in_bool_constraint[lit] =
true;
9780 flat_vars.push_back(lit);
9781 flat_terms.push_back({c, 1});
9786 if (ct.constraint_case() == ConstraintProto::kLinear) {
9787 const int num_terms = ct.linear().vars().size();
9788 for (
int i = 0;
i < num_terms; ++
i) {
9789 const int var = ct.linear().vars(
i);
9790 const int64_t coeff = ct.linear().coeffs(
i);
9791 flat_vars.push_back(var);
9792 flat_terms.push_back({c, coeff});
9804 if (context_->working_model->has_objective()) {
9805 context_->WriteObjectiveToProto();
9806 const int num_terms = context_->working_model->objective().vars().size();
9807 for (
int i = 0;
i < num_terms; ++
i) {
9808 const int var = context_->working_model->objective().vars(
i);
9809 const int64_t coeff = context_->working_model->objective().coeffs(
i);
9810 flat_vars.push_back(var);
9827 std::vector<int> flat_duplicates;
9828 std::vector<int> flat_representatives;
9829 for (
int var = 0; var < var_to_columns.
size(); ++var) {
9830 const int size_seen = var_to_columns[var].
size();
9831 if (size_seen == 0)
continue;
9832 if (size_seen != context_->VarToConstraints(var).size())
continue;
9838 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
9839 context_->UpdateRuleStats(
9840 "TODO duplicate: duplicate columns in Boolean constraints");
9844 const auto [it, inserted] = duplicates.insert({var, var});
9846 flat_duplicates.push_back(var);
9847 flat_representatives.push_back(it->second);
9852 int num_equivalent_classes = 0;
9855 std::vector<std::pair<int, int64_t>> definition;
9856 std::vector<int> var_to_remove;
9857 std::vector<int> var_to_rep(num_vars, -1);
9858 for (
int var = 0; var < rep_to_dups.
size(); ++var) {
9859 if (rep_to_dups[var].empty())
continue;
9869 definition.push_back({var, 1});
9870 Domain domain = context_->DomainOf(var);
9871 for (
const int other_var : rep_to_dups[var]) {
9872 definition.push_back({other_var, 1});
9873 domain = domain.
AdditionWith(context_->DomainOf(other_var));
9877 context_->UpdateRuleStats(
9878 "TODO duplicate: domain of the sum is too complex");
9881 if (appear_in_amo[var]) {
9884 const int new_var = context_->NewIntVarWithDefinition(
9885 domain, definition,
true);
9886 if (new_var == -1) {
9887 context_->UpdateRuleStats(
"TODO duplicate: possible overflow");
9891 var_to_remove.push_back(var);
9892 CHECK_EQ(var_to_rep[var], -1);
9893 var_to_rep[var] = new_var;
9894 for (
const int other_var : rep_to_dups[var]) {
9895 var_to_remove.push_back(other_var);
9896 CHECK_EQ(var_to_rep[other_var], -1);
9897 var_to_rep[other_var] = new_var;
9901 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9902 if (obj_coeff != 0) {
9903 context_->RemoveVariableFromObjective(var);
9904 for (
const int other_var : rep_to_dups[var]) {
9905 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
9906 context_->RemoveVariableFromObjective(other_var);
9908 context_->AddToObjective(new_var, obj_coeff);
9911 num_equivalent_classes++;
9916 if (!var_to_remove.empty()) {
9917 absl::flat_hash_set<int> seen;
9918 std::vector<std::pair<int, int64_t>> new_terms;
9919 for (
int c = 0; c < num_constraints; ++c) {
9920 ConstraintProto* mutable_ct =
9921 context_->working_model->mutable_constraints(c);
9928 BoolArgumentProto* mutable_arg =
nullptr;
9929 if (mutable_ct->constraint_case() == ConstraintProto::kAtMostOne) {
9930 mutable_arg = mutable_ct->mutable_at_most_one();
9931 }
else if (mutable_ct->constraint_case() ==
9932 ConstraintProto::kExactlyOne) {
9933 mutable_arg = mutable_ct->mutable_exactly_one();
9934 }
else if (mutable_ct->constraint_case() == ConstraintProto::kBoolOr) {
9935 mutable_arg = mutable_ct->mutable_bool_or();
9937 if (mutable_arg !=
nullptr) {
9939 const int num_terms = mutable_arg->literals().size();
9940 for (
int i = 0;
i < num_terms; ++
i) {
9941 const int lit = mutable_arg->literals(
i);
9945 const auto [_, inserted] = seen.insert(rep);
9946 if (inserted) new_terms.push_back({rep, 1});
9949 mutable_arg->set_literals(new_size, lit);
9952 if (new_size == num_terms)
continue;
9955 mutable_arg->mutable_literals()->Truncate(new_size);
9956 for (
const auto [var, coeff] : new_terms) {
9957 mutable_arg->add_literals(var);
9959 context_->UpdateConstraintVariableUsage(c);
9964 if (mutable_ct->constraint_case() == ConstraintProto::kLinear) {
9966 LinearConstraintProto* mutable_linear = mutable_ct->mutable_linear();
9967 const int num_terms = mutable_linear->vars().size();
9968 for (
int i = 0;
i < num_terms; ++
i) {
9969 const int var = mutable_linear->vars(
i);
9970 const int64_t coeff = mutable_linear->coeffs(
i);
9971 const int rep = var_to_rep[var];
9973 const auto [_, inserted] = seen.insert(rep);
9974 if (inserted) new_terms.push_back({rep, coeff});
9977 mutable_linear->set_vars(new_size, var);
9978 mutable_linear->set_coeffs(new_size, coeff);
9981 if (new_size == num_terms)
continue;
9983 mutable_linear->mutable_vars()->Truncate(new_size);
9984 mutable_linear->mutable_coeffs()->Truncate(new_size);
9985 for (
const auto [var, coeff] : new_terms) {
9986 mutable_linear->add_vars(var);
9987 mutable_linear->add_coeffs(coeff);
9989 context_->UpdateConstraintVariableUsage(c);
9998 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
9999 for (
const int var : var_to_remove) {
10000 CHECK(context_->VarToConstraints(var).empty());
10001 context_->MarkVariableAsRemoved(var);
10003 if (num_var_reduction > 0) {
10004 context_->UpdateRuleStats(
"duplicate: removed duplicated column",
10005 num_var_reduction);
10008 timer.
AddCounter(
"num_equiv_classes", num_equivalent_classes);
10009 timer.
AddCounter(
"num_removed_vars", num_var_reduction);
10012void CpModelPresolver::DetectDuplicateConstraints() {
10024 std::vector<int> interval_mapping;
10031 const std::vector<std::pair<int, int>> duplicates =
10033 timer.AddCounter(
"duplicates", duplicates.size());
10034 for (
const auto& [dup, rep] : duplicates) {
10041 : context_->
working_model->constraints(rep).constraint_case();
10043 if (type == ConstraintProto::kInterval) {
10044 interval_mapping.resize(context_->
working_model->constraints().size(),
10046 CHECK_EQ(interval_mapping[rep], -1);
10047 interval_mapping[dup] = rep;
10052 if (type == ConstraintProto::kLinear) {
10057 if (rep_domain != d) {
10058 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
10059 const Domain rhs = rep_domain.IntersectionWith(d);
10060 if (rhs.IsEmpty()) {
10061 if (!MarkConstraintAsFalse(
10063 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
10070 context_->UpdateConstraintVariableUsage(rep);
10074 ->mutable_linear());
10079 context_->UpdateRuleStats(
10080 "duplicate: linear constraint parallel to objective");
10081 const Domain objective_domain =
10084 context_->working_model->constraints(dup).linear());
10085 if (objective_domain != d) {
10086 context_->UpdateRuleStats(
"duplicate: updated objective domain");
10087 const Domain new_domain = objective_domain.IntersectionWith(d);
10088 if (new_domain.IsEmpty()) {
10089 return (
void)context_->NotifyThatModelIsUnsat(
10090 "Constraint parallel to the objective makes the objective domain "
10094 context_->working_model->mutable_objective());
10097 context_->ReadObjectiveFromProto();
10102 context_->working_model->mutable_constraints(dup)->Clear();
10103 context_->UpdateConstraintVariableUsage(dup);
10104 context_->UpdateRuleStats(
"duplicate: removed constraint");
10107 if (!interval_mapping.empty()) {
10108 context_->UpdateRuleStats(
"duplicate: remapped duplicate intervals");
10109 const int num_constraints = context_->working_model->constraints().size();
10110 for (
int c = 0;
c < num_constraints; ++
c) {
10111 bool changed =
false;
10113 [&interval_mapping, &changed](
int* ref) {
10114 const int new_ref = interval_mapping[*ref];
10115 if (new_ref != -1) {
10120 context_->working_model->mutable_constraints(c));
10121 if (changed) context_->UpdateConstraintVariableUsage(c);
10126void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
10129 if (time_limit_->LimitReached())
return;
10130 if (context_->ModelIsUnsat())
return;
10131 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10134 if (context_->working_model->has_objective()) {
10135 if (!context_->CanonicalizeObjective())
return;
10136 context_->WriteObjectiveToProto();
10139 absl::flat_hash_set<Literal> enforcement_vars;
10140 std::vector<std::pair<Literal, Literal>> implications_used;
10145 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
10147 timer.AddCounter(
"without_enforcements",
10148 duplicates_without_enforcement.size());
10149 for (
const auto& [dup, rep] : duplicates_without_enforcement) {
10150 auto* dup_ct = context_->working_model->mutable_constraints(dup);
10151 auto* rep_ct = context_->working_model->mutable_constraints(rep);
10153 if (dup_ct->constraint_case() == ConstraintProto::kInterval) {
10154 context_->UpdateRuleStats(
10155 "TODO interval: same interval with different enforcement?");
10161 if (PresolveEnforcementLiteral(dup_ct)) {
10162 context_->UpdateConstraintVariableUsage(dup);
10164 if (PresolveEnforcementLiteral(rep_ct)) {
10165 context_->UpdateConstraintVariableUsage(rep);
10169 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET ||
10170 dup_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
10176 if (dup_ct->enforcement_literal().empty() ||
10177 rep_ct->enforcement_literal().empty()) {
10178 context_->UpdateRuleStats(
"duplicate: removed enforced constraint");
10179 rep_ct->mutable_enforcement_literal()->Clear();
10180 context_->UpdateConstraintVariableUsage(rep);
10182 context_->UpdateConstraintVariableUsage(dup);
10186 const int a = rep_ct->enforcement_literal(0);
10187 const int b = dup_ct->enforcement_literal(0);
10189 if (a ==
NegatedRef(
b) && rep_ct->enforcement_literal().size() == 1 &&
10190 dup_ct->enforcement_literal().size() == 1) {
10191 context_->UpdateRuleStats(
10192 "duplicate: both with enforcement and its negation");
10193 rep_ct->mutable_enforcement_literal()->Clear();
10194 context_->UpdateConstraintVariableUsage(rep);
10196 context_->UpdateConstraintVariableUsage(dup);
10213 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
10214 context_->VariableWithCostIsUniqueAndRemovable(
b)) {
10220 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement.");
10221 if (!context_->SetLiteralToFalse(a))
return;
10225 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement.");
10226 if (!context_->SetLiteralToFalse(
b))
return;
10229 if (skip)
continue;
10240 if (rep_ct->enforcement_literal().size() > 1 ||
10241 dup_ct->enforcement_literal().size() > 1) {
10242 context_->UpdateRuleStats(
10243 "TODO duplicate: identical constraint with unique enforcement "
10250 context_->UpdateRuleStats(
"duplicate: dual equivalence of enforcement");
10256 solution_crush_.UpdateLiteralsToFalseIfDifferent(
NegatedRef(a),
10258 if (!context_->StoreBooleanEqualityRelation(a,
b))
return;
10262 if (dup_ct->enforcement_literal().size() == 1 &&
10263 rep_ct->enforcement_literal().size() == 1) {
10265 context_->UpdateConstraintVariableUsage(dup);
10271 if (implication_graph !=
nullptr && mapping !=
nullptr &&
10272 trail !=
nullptr) {
10273 for (
int i = 0;
i < 2;
i++) {
10277 const int c_a =
i == 0 ? dup : rep;
10278 const int c_b =
i == 0 ? rep : dup;
10279 const auto& ct_a = context_->working_model->constraints(c_a);
10280 const auto& ct_b = context_->working_model->constraints(c_b);
10282 enforcement_vars.clear();
10283 implications_used.clear();
10284 for (
const int proto_lit : ct_b.enforcement_literal()) {
10285 const Literal lit = mapping->Literal(proto_lit);
10286 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10287 enforcement_vars.insert(lit);
10289 for (
const int proto_lit : ct_a.enforcement_literal()) {
10290 const Literal lit = mapping->Literal(proto_lit);
10291 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10292 for (
const Literal implication_lit :
10293 implication_graph->DirectImplications(lit)) {
10294 auto extracted = enforcement_vars.extract(implication_lit);
10295 if (!extracted.empty() && lit != implication_lit) {
10296 implications_used.push_back({lit, implication_lit});
10300 if (enforcement_vars.empty()) {
10328 if (ct_a.constraint_case() == ConstraintProto::kLinear &&
10329 ct_a.linear().vars().size() == 1 &&
10330 ct_a.enforcement_literal().size() == 1) {
10331 const int var = ct_a.linear().vars(0);
10332 const Domain var_domain = context_->DomainOf(var);
10339 if (rhs.IsEmpty()) {
10340 context_->UpdateRuleStats(
"duplicate: linear1 infeasible");
10341 if (!MarkConstraintAsFalse(rep_ct))
return;
10342 if (!MarkConstraintAsFalse(dup_ct))
return;
10343 context_->UpdateConstraintVariableUsage(rep);
10344 context_->UpdateConstraintVariableUsage(dup);
10347 if (rhs == var_domain) {
10348 context_->UpdateRuleStats(
"duplicate: linear1 always true");
10351 context_->UpdateConstraintVariableUsage(rep);
10352 context_->UpdateConstraintVariableUsage(dup);
10357 if (rhs.IsFixed() ||
10358 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
10359 context_->UpdateRuleStats(
10360 "TODO duplicate: skipped identical encoding constraints");
10366 context_->UpdateRuleStats(
10367 "duplicate: identical constraint with implied enforcements");
10371 rep_ct->Swap(dup_ct);
10372 context_->UpdateConstraintVariableUsage(rep);
10375 context_->UpdateConstraintVariableUsage(dup);
10379 for (
const auto& [a,
b] : implications_used) {
10380 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
10381 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(
b);
10382 context_->AddImplication(proto_lit_a, proto_lit_b);
10384 context_->UpdateNewConstraintsVariableUsage();
10393 if (time_limit_->LimitReached())
return;
10394 if (context_->ModelIsUnsat())
return;
10399 std::vector<std::pair<int, int>> different_vars;
10400 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
10403 const auto process_difference = [&different_vars, &offsets](
int v1,
int v2,
10405 Domain exclusion = d.Complement().PartAroundZero();
10406 if (exclusion.
IsEmpty())
return;
10407 if (v1 == v2)
return;
10408 std::pair<int, int> key = {v1, v2};
10410 std::swap(key.first, key.second);
10417 different_vars.push_back(key);
10418 offsets[key] = {exclusion.
Min() == std::numeric_limits<int64_t>::min()
10419 ? std::numeric_limits<int64_t>::max()
10442 bool has_all_diff =
false;
10443 bool has_no_overlap =
false;
10444 std::vector<std::pair<uint64_t, int>> hashes;
10445 const int num_constraints = context_->working_model->constraints_size();
10446 for (
int c = 0; c < num_constraints; ++c) {
10447 const ConstraintProto& ct = context_->working_model->constraints(c);
10448 if (ct.constraint_case() == ConstraintProto::kAllDiff) {
10449 has_all_diff =
true;
10452 if (ct.constraint_case() == ConstraintProto::kNoOverlap) {
10453 has_no_overlap =
true;
10456 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
10457 if (ct.linear().vars().size() == 1)
continue;
10461 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
10462 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
10464 if (ct.linear().coeffs(0) == 1) {
10465 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10467 }
else if (ct.linear().coeffs(0) == -1) {
10468 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10474 if (ct.enforcement_literal().size() > 1)
continue;
10479 hashes.push_back({hash, c});
10481 std::sort(hashes.begin(), hashes.end());
10482 for (
int next, start = 0; start < hashes.size(); start = next) {
10484 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
10487 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
10489 if (range.size() <= 1)
continue;
10490 if (range.size() > 10)
continue;
10492 for (
int i = 0;
i < range.size(); ++
i) {
10493 const ConstraintProto& ct1 =
10494 context_->working_model->constraints(range[
i].second);
10495 const int num_terms = ct1.linear().vars().size();
10496 for (
int j =
i + 1; j < range.size(); ++j) {
10497 const ConstraintProto& ct2 =
10498 context_->working_model->constraints(range[j].second);
10499 if (ct2.linear().vars().size() != num_terms)
continue;
10505 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
10506 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
10509 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
10510 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
10514 if (ct1.enforcement_literal().empty() &&
10515 ct2.enforcement_literal().empty()) {
10516 (void)context_->NotifyThatModelIsUnsat(
10517 "two incompatible linear constraint");
10520 if (ct1.enforcement_literal().empty()) {
10521 context_->UpdateRuleStats(
10522 "incompatible linear: set enforcement to false");
10523 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
10528 if (ct2.enforcement_literal().empty()) {
10529 context_->UpdateRuleStats(
10530 "incompatible linear: set enforcement to false");
10531 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
10537 const int lit1 = ct1.enforcement_literal(0);
10538 const int lit2 = ct2.enforcement_literal(0);
10541 if (ct1.linear().vars().size() == 2 &&
10542 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
10545 Domain union_of_domain =
10550 if (ct1.linear().coeffs(0) == 1) {
10551 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10552 std::move(union_of_domain));
10553 }
else if (ct1.linear().coeffs(0) == -1) {
10554 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10560 context_->UpdateRuleStats(
"incompatible linear: add implication");
10561 context_->AddImplication(lit1,
NegatedRef(lit2));
10582 if (context_->params().infer_all_diffs() && !has_all_diff &&
10583 !has_no_overlap && different_vars.size() > 2) {
10585 local_time.
Start();
10587 std::vector<std::vector<Literal>> cliques;
10588 absl::flat_hash_set<int> used_var;
10591 const int num_variables = context_->working_model->
variables().size();
10594 graph->
Resize(num_variables);
10595 for (
const auto [var1, var2] : different_vars) {
10598 if (var1 == var2) {
10599 (void)context_->NotifyThatModelIsUnsat(
"x != y with x == y");
10604 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
10605 Literal(BooleanVariable(var2), true)}));
10606 if (!used_var.contains(var1)) {
10607 used_var.insert(var1);
10608 cliques.push_back({
Literal(BooleanVariable(var1),
true),
10609 Literal(BooleanVariable(var2),
true)});
10611 if (!used_var.contains(var2)) {
10612 used_var.insert(var2);
10613 cliques.push_back({
Literal(BooleanVariable(var1),
true),
10614 Literal(BooleanVariable(var2),
true)});
10617 CHECK(graph->DetectEquivalences());
10618 graph->TransformIntoMaxCliques(&cliques, 1e8);
10620 int num_cliques = 0;
10621 int64_t cumulative_size = 0;
10622 for (std::vector<Literal>& clique : cliques) {
10623 if (clique.size() <= 2)
continue;
10626 cumulative_size += clique.size();
10627 std::sort(clique.begin(), clique.end());
10633 const int num_terms = clique.size();
10634 std::vector<int64_t> sizes(num_terms,
10635 std::numeric_limits<int64_t>::max());
10636 for (
int i = 0;
i < num_terms; ++
i) {
10637 const int v1 = clique[
i].Variable().value();
10638 for (
int j =
i + 1; j < num_terms; ++j) {
10639 const int v2 = clique[j].Variable().value();
10640 const auto [o1, o2] = offsets.at({v1, v2});
10641 sizes[
i] = std::min(sizes[
i], o1);
10642 sizes[j] = std::min(sizes[j], o2);
10646 int num_greater_than_one = 0;
10648 for (
int i = 0;
i < num_terms; ++
i) {
10649 CHECK_GE(sizes[
i], 1);
10650 if (sizes[
i] > 1) ++num_greater_than_one;
10655 issue =
CapAdd(issue, sizes[
i]);
10656 if (issue == std::numeric_limits<int64_t>::max()) {
10657 context_->UpdateRuleStats(
"TODO no_overlap: with task always last");
10658 num_greater_than_one = 0;
10663 if (num_greater_than_one > 0) {
10669 context_->UpdateRuleStats(
10670 "no_overlap: inferred from x != y constraints");
10672 std::vector<int> intervals;
10673 for (
int i = 0;
i < num_terms; ++
i) {
10674 intervals.push_back(context_->working_model->constraints().size());
10675 auto* new_interval =
10676 context_->working_model->add_constraints()->mutable_interval();
10677 new_interval->mutable_start()->set_offset(0);
10678 new_interval->mutable_start()->add_coeffs(1);
10679 new_interval->mutable_start()->add_vars(clique[
i].
Variable().value());
10681 new_interval->mutable_size()->set_offset(sizes[
i]);
10683 new_interval->mutable_end()->set_offset(sizes[
i]);
10684 new_interval->mutable_end()->add_coeffs(1);
10685 new_interval->mutable_end()->add_vars(clique[
i].
Variable().value());
10688 context_->working_model->add_constraints()->mutable_no_overlap();
10689 for (
const int interval : intervals) {
10690 new_ct->add_intervals(interval);
10693 context_->UpdateRuleStats(
"all_diff: inferred from x != y constraints");
10695 context_->working_model->add_constraints()->mutable_all_diff();
10696 for (
const Literal l : clique) {
10697 auto* expr = new_ct->add_exprs();
10698 expr->add_vars(l.Variable().value());
10699 expr->add_coeffs(1);
10704 timer.
AddCounter(
"different", different_vars.size());
10709 context_->UpdateNewConstraintsVariableUsage();
10715void Substitute(int64_t factor,
10716 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
10718 LinearConstraintProto* mutable_linear) {
10720 const int old_size = mutable_linear->vars().size();
10721 for (
int i = 0;
i < old_size; ++
i) {
10722 const int var = mutable_linear->vars(
i);
10723 int64_t coeff = mutable_linear->coeffs(
i);
10724 const auto it = subset_coeff_map.find(var);
10725 if (it != subset_coeff_map.end()) {
10726 coeff += factor * it->second;
10727 if (coeff == 0)
continue;
10730 mutable_linear->set_vars(new_size, var);
10731 mutable_linear->set_coeffs(new_size, coeff);
10734 mutable_linear->mutable_vars()->Truncate(new_size);
10735 mutable_linear->mutable_coeffs()->Truncate(new_size);
10743void CpModelPresolver::DetectDominatedLinearConstraints() {
10744 if (time_limit_->LimitReached())
return;
10745 if (context_->ModelIsUnsat())
return;
10746 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
10747 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10755 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
10756 int size()
const {
return static_cast<int>(proto_.constraints().size()); }
10757 absl::Span<const int> operator[](
int c)
const {
10758 return absl::MakeSpan(proto_.constraints(c).linear().vars());
10762 const CpModelProto& proto_;
10764 Storage storage(context_->working_model);
10766 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10770 std::vector<int> constraint_indices_to_clean;
10774 absl::flat_hash_map<int, Domain> cached_expr_domain;
10776 const int num_constraints = context_->working_model->constraints().size();
10777 for (
int c = 0;
c < num_constraints; ++
c) {
10778 const ConstraintProto& ct = context_->working_model->constraints(c);
10779 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
10783 if (!ct.enforcement_literal().empty()) {
10784 if (ct.linear().vars().size() < 3)
continue;
10787 if (!LinearConstraintIsClean(ct.linear())) {
10794 detector.AddPotentialSet(c);
10796 const auto [min_activity, max_activity] =
10797 context_->ComputeMinMaxActivity(ct.linear());
10798 cached_expr_domain[
c] = Domain(min_activity, max_activity);
10801 int64_t num_inclusions = 0;
10802 absl::flat_hash_map<int, int64_t> coeff_map;
10803 detector.DetectInclusions([&](
int subset_c,
int superset_c) {
10807 const ConstraintProto& subset_ct =
10808 context_->working_model->constraints(subset_c);
10809 const LinearConstraintProto& subset_lin = subset_ct.linear();
10811 detector.IncreaseWorkDone(subset_lin.vars().size());
10812 for (
int i = 0;
i < subset_lin.vars().size(); ++
i) {
10813 coeff_map[subset_lin.vars(
i)] = subset_lin.coeffs(
i);
10819 bool perfect_match =
true;
10822 int64_t factor = 0;
10823 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
10824 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
10830 const ConstraintProto& superset_ct =
10831 context_->working_model->constraints(superset_c);
10832 const LinearConstraintProto& superset_lin = superset_ct.linear();
10833 int64_t diff_min_activity = 0;
10834 int64_t diff_max_activity = 0;
10835 detector.IncreaseWorkDone(superset_lin.vars().size());
10836 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10837 const int var = superset_lin.vars(
i);
10838 int64_t coeff = superset_lin.coeffs(
i);
10839 const auto it = coeff_map.find(var);
10841 if (it != coeff_map.end()) {
10842 const int64_t subset_coeff = it->second;
10844 const int64_t div = coeff / subset_coeff;
10846 min_pos_factor = std::min(div, min_pos_factor);
10848 max_neg_factor = std::max(div, max_neg_factor);
10851 if (perfect_match) {
10852 if (coeff % subset_coeff == 0) {
10856 }
else if (factor != div) {
10857 perfect_match =
false;
10860 perfect_match =
false;
10865 coeff -= subset_coeff;
10867 if (coeff == 0)
continue;
10868 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
10869 &diff_max_activity);
10872 const Domain diff_domain(diff_min_activity, diff_max_activity);
10883 if (subset_ct.enforcement_literal().empty()) {
10884 const Domain implied_superset_domain =
10885 subset_rhs.AdditionWith(diff_domain)
10886 .IntersectionWith(cached_expr_domain[superset_c]);
10887 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
10888 context_->UpdateRuleStats(
10889 "linear inclusion: redundant containing constraint");
10890 context_->working_model->mutable_constraints(superset_c)->Clear();
10891 constraint_indices_to_clean.push_back(superset_c);
10892 detector.StopProcessingCurrentSuperset();
10898 if (superset_ct.enforcement_literal().empty()) {
10899 const Domain implied_subset_domain =
10900 superset_rhs.AdditionWith(diff_domain.Negation())
10901 .IntersectionWith(cached_expr_domain[subset_c]);
10902 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
10903 context_->UpdateRuleStats(
10904 "linear inclusion: redundant included constraint");
10905 context_->working_model->mutable_constraints(subset_c)->Clear();
10906 constraint_indices_to_clean.push_back(subset_c);
10907 detector.StopProcessingCurrentSubset();
10916 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().
empty()) {
10917 const int64_t best_factor =
10918 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
10926 bool is_tigher =
true;
10927 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
10928 max_neg_factor != std::numeric_limits<int64_t>::min()) {
10929 int64_t min_before = 0;
10930 int64_t max_before = 0;
10931 int64_t min_after =
CapProd(best_factor, subset_rhs.FixedValue());
10932 int64_t max_after = min_after;
10933 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10934 const int var = superset_lin.vars(
i);
10935 const auto it = coeff_map.find(var);
10936 if (it == coeff_map.end())
continue;
10938 const int64_t coeff_before = superset_lin.coeffs(
i);
10939 const int64_t coeff_after = coeff_before - best_factor * it->second;
10940 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
10942 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
10945 is_tigher = min_after >= min_before && max_after <= max_before;
10948 context_->UpdateRuleStats(
"linear inclusion: sparsify superset");
10949 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
10950 context_->working_model->mutable_constraints(superset_c)
10951 ->mutable_linear());
10952 constraint_indices_to_clean.push_back(superset_c);
10953 detector.StopProcessingCurrentSuperset();
10960 if (perfect_match && subset_ct.enforcement_literal().empty() &&
10961 superset_ct.enforcement_literal().empty()) {
10962 CHECK_NE(factor, 0);
10968 auto* mutable_linear = temp_ct_.mutable_linear();
10969 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10970 const int var = superset_lin.vars(
i);
10971 const int64_t coeff = superset_lin.coeffs(
i);
10972 const auto it = coeff_map.find(var);
10973 if (it != coeff_map.end())
continue;
10974 mutable_linear->add_vars(var);
10975 mutable_linear->add_coeffs(coeff);
10978 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
10980 PropagateDomainsInLinear(-1, &temp_ct_);
10981 if (context_->ModelIsUnsat()) detector.Stop();
10983 if (superset_rhs.IsFixed()) {
10984 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
10987 context_->UpdateRuleStats(
10988 "linear inclusion: subset + singleton is equality");
10989 context_->working_model->mutable_constraints(subset_c)->Clear();
10990 constraint_indices_to_clean.push_back(subset_c);
10991 detector.StopProcessingCurrentSubset();
10996 context_->UpdateRuleStats(
10997 "TODO linear inclusion: superset is equality");
11002 for (
const int c : constraint_indices_to_clean) {
11003 context_->UpdateConstraintVariableUsage(c);
11006 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
11007 timer.AddCounter(
"relevant_constraints", detector.num_potential_supersets());
11008 timer.AddCounter(
"num_inclusions", num_inclusions);
11009 timer.AddCounter(
"num_redundant", constraint_indices_to_clean.size());
11014bool CpModelPresolver::RemoveCommonPart(
11015 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
11016 absl::Span<
const std::pair<int, int64_t>> block,
11020 int64_t offset = 0;
11025 int definiting_equation = -1;
11026 for (
const auto [c, multiple] : block) {
11027 const ConstraintProto& ct = context_->working_model->constraints(c);
11028 if (std::abs(multiple) != 1)
continue;
11029 if (!IsLinearEqualityConstraint(ct))
continue;
11030 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1)
continue;
11032 context_->UpdateRuleStats(
11033 "linear matrix: defining equation for common rectangle");
11034 definiting_equation =
c;
11038 const int num_terms = ct.linear().vars().size();
11039 for (
int k = 0; k < num_terms; ++k) {
11040 if (common_var_coeff_map.contains(ct.linear().vars(k)))
continue;
11041 new_var = ct.linear().vars(k);
11042 coeff = ct.linear().coeffs(k);
11045 CHECK_NE(coeff, 0);
11049 gcd = -multiple * coeff;
11050 offset = multiple * ct.linear().domain(0);
11055 if (definiting_equation == -1) {
11057 int64_t min_activity = 0;
11058 int64_t max_activity = 0;
11059 tmp_terms_.clear();
11060 std::vector<std::pair<int, int64_t>> common_part;
11061 for (
const auto [var, coeff] : common_var_coeff_map) {
11062 common_part.push_back({var, coeff});
11063 gcd = std::gcd(gcd, std::abs(coeff));
11064 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
11065 tmp_terms_.push_back({var, coeff});
11069 min_activity += coeff * context_->MinOf(var);
11070 max_activity += coeff * context_->MaxOf(var);
11072 min_activity += coeff * context_->MaxOf(var);
11073 max_activity += coeff * context_->MinOf(var);
11081 if (!tmp_terms_.empty()) {
11082 min_activity += helper->ComputeMinActivity(tmp_terms_);
11083 max_activity += helper->ComputeMaxActivity(tmp_terms_);
11087 min_activity /= gcd;
11088 max_activity /= gcd;
11089 for (
int i = 0;
i < common_part.size(); ++
i) {
11090 common_part[
i].second /= gcd;
11095 std::sort(common_part.begin(), common_part.end());
11096 new_var = context_->NewIntVarWithDefinition(
11097 Domain(min_activity, max_activity), common_part);
11098 if (new_var == -1)
return false;
11102 for (
const auto [c, multiple] : block) {
11103 if (c == definiting_equation)
continue;
11105 auto* mutable_linear =
11106 context_->working_model->mutable_constraints(c)->mutable_linear();
11107 const int num_terms = mutable_linear->vars().size();
11109 bool new_var_already_seen =
false;
11110 for (
int k = 0; k < num_terms; ++k) {
11111 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
11112 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
11113 mutable_linear->coeffs(k));
11118 int64_t new_coeff = mutable_linear->coeffs(k);
11119 if (mutable_linear->vars(k) == new_var) {
11120 new_var_already_seen =
true;
11121 new_coeff += gcd * multiple;
11122 if (new_coeff == 0)
continue;
11125 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11126 mutable_linear->set_coeffs(new_size, new_coeff);
11129 mutable_linear->mutable_vars()->Truncate(new_size);
11130 mutable_linear->mutable_coeffs()->Truncate(new_size);
11131 if (!new_var_already_seen) {
11132 mutable_linear->add_vars(new_var);
11133 mutable_linear->add_coeffs(gcd * multiple);
11137 .AdditionWith(Domain(-offset * multiple)),
11140 context_->UpdateConstraintVariableUsage(c);
11147int64_t FindVarCoeff(
int var,
const ConstraintProto& ct) {
11148 const int num_terms = ct.linear().vars().size();
11149 for (
int k = 0; k < num_terms; ++k) {
11150 if (ct.linear().vars(k) == var)
return ct.linear().coeffs(k);
11155int64_t ComputeNonZeroReduction(
size_t block_size,
size_t common_part_size) {
11158 return static_cast<int64_t
>(block_size * (common_part_size - 1) -
11159 common_part_size - 1);
11167void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
11169 if (time_limit_->LimitReached())
return;
11170 if (context_->ModelIsUnsat())
return;
11171 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11172 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11174 int64_t num_blocks = 0;
11175 int64_t nz_reduction = 0;
11176 std::vector<int> amo_cts;
11177 std::vector<int> amo_literals;
11179 std::vector<int> common_part;
11180 std::vector<int> best_common_part;
11182 std::vector<bool> common_part_sign;
11183 std::vector<bool> best_common_part_sign;
11186 absl::flat_hash_map<int, bool> var_in_amo;
11188 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
11190 if (time_limit_->LimitReached())
break;
11191 if (timer.WorkLimitIsReached())
break;
11192 if (helper->NumAmoForVariable(x) == 0)
continue;
11195 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11196 for (
const int c : context_->VarToConstraints(x)) {
11197 if (c < 0)
continue;
11198 const ConstraintProto& ct = context_->working_model->constraints(c);
11199 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
11200 amo_cts.push_back(c);
11201 }
else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11202 amo_cts.push_back(c);
11205 if (amo_cts.empty())
continue;
11214 var_in_amo.clear();
11215 amo_literals.clear();
11216 common_part.clear();
11217 common_part_sign.clear();
11221 std::sort(amo_cts.begin(), amo_cts.end());
11222 const int random_c =
11223 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
11224 base_ct_index = amo_cts[random_c];
11225 const ConstraintProto& ct =
11226 context_->working_model->constraints(base_ct_index);
11227 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
11228 ? ct.at_most_one().literals()
11229 : ct.exactly_one().literals();
11230 timer.TrackSimpleLoop(5 * literals.size());
11231 for (
const int literal : literals) {
11232 amo_literals.push_back(literal);
11235 const auto [_, inserted] =
11241 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11245 std::vector<int> block_cts;
11246 std::vector<int> linear_cts;
11247 int max_common_part = 0;
11248 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11249 for (
const int c : context_->VarToConstraints(x)) {
11250 if (c < 0)
continue;
11251 const ConstraintProto& ct = context_->working_model->constraints(c);
11252 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11253 const int num_terms = ct.linear().vars().size();
11254 if (num_terms < 2)
continue;
11256 timer.TrackSimpleLoop(2 * num_terms);
11257 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11258 if (x_coeff == 0)
continue;
11260 int num_in_amo = 0;
11261 for (
int k = 0; k < num_terms; ++k) {
11262 const int var = ct.linear().vars(k);
11267 const auto it = var_in_amo.find(var);
11268 if (it == var_in_amo.end())
continue;
11269 int64_t coeff = ct.linear().coeffs(k);
11270 if (!it->second) coeff = -coeff;
11271 if (coeff != x_coeff)
continue;
11274 if (num_in_amo < 2)
continue;
11276 max_common_part += num_in_amo;
11277 if (num_in_amo == common_part.size()) {
11279 block_cts.push_back(c);
11281 linear_cts.push_back(c);
11284 if (linear_cts.empty() && block_cts.empty())
continue;
11285 if (max_common_part < 100)
continue;
11289 best_common_part = common_part;
11290 best_common_part_sign = common_part_sign;
11291 int best_block_size = block_cts.size();
11292 int best_saved_nz =
11293 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11296 std::sort(block_cts.begin(), block_cts.end());
11297 std::sort(linear_cts.begin(), linear_cts.end());
11301 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11302 for (
const int c : linear_cts) {
11303 const ConstraintProto& ct = context_->working_model->constraints(c);
11304 const int num_terms = ct.linear().vars().size();
11305 timer.TrackSimpleLoop(2 * num_terms);
11306 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11307 CHECK_NE(x_coeff, 0);
11309 common_part.clear();
11310 common_part_sign.clear();
11311 for (
int k = 0; k < num_terms; ++k) {
11312 const int var = ct.linear().vars(k);
11313 const auto it = var_in_amo.find(var);
11314 if (it == var_in_amo.end())
continue;
11315 int64_t coeff = ct.linear().coeffs(k);
11316 if (!it->second) coeff = -coeff;
11317 if (coeff != x_coeff)
continue;
11318 common_part.push_back(var);
11319 common_part_sign.push_back(it->second);
11321 if (common_part.size() < 2)
continue;
11324 block_cts.push_back(c);
11325 if (common_part.size() < var_in_amo.size()) {
11326 var_in_amo.clear();
11327 for (
int i = 0;
i < common_part.size(); ++
i) {
11328 var_in_amo[common_part[
i]] = common_part_sign[
i];
11335 const int64_t saved_nz =
11336 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11337 if (saved_nz > best_saved_nz) {
11338 best_block_size = block_cts.size();
11339 best_saved_nz = saved_nz;
11340 best_common_part = common_part;
11341 best_common_part_sign = common_part_sign;
11344 if (best_saved_nz < 100)
continue;
11349 block_cts.resize(best_block_size);
11350 var_in_amo.clear();
11351 for (
int i = 0;
i < best_common_part.size(); ++
i) {
11352 var_in_amo[best_common_part[
i]] = best_common_part_sign[
i];
11356 nz_reduction += best_saved_nz;
11357 context_->UpdateRuleStats(
"linear matrix: common amo rectangle");
11361 for (
const int lit : amo_literals) {
11362 if (!var_in_amo.contains(
PositiveRef(lit)))
continue;
11363 amo_literals[new_size++] = lit;
11365 if (new_size == amo_literals.size()) {
11366 const ConstraintProto& ct =
11367 context_->working_model->constraints(base_ct_index);
11368 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11369 context_->UpdateRuleStats(
"TODO linear matrix: constant rectangle!");
11371 context_->UpdateRuleStats(
11372 "TODO linear matrix: reuse defining constraint");
11374 }
else if (new_size + 1 == amo_literals.size()) {
11375 const ConstraintProto& ct =
11376 context_->working_model->constraints(base_ct_index);
11377 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11378 context_->UpdateRuleStats(
"TODO linear matrix: reuse exo constraint");
11381 amo_literals.resize(new_size);
11384 const int new_var = context_->NewBoolVarWithClause(amo_literals);
11387 context_->working_model->add_constraints()->mutable_exactly_one();
11388 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
11389 for (
const int lit : amo_literals) {
11390 new_exo->add_literals(lit);
11393 context_->UpdateNewConstraintsVariableUsage();
11398 ConstraintProto* ct =
11399 context_->working_model->mutable_constraints(base_ct_index);
11400 auto* mutable_literals =
11401 ct->constraint_case() == ConstraintProto::kAtMostOne
11402 ? ct->mutable_at_most_one()->mutable_literals()
11403 : ct->mutable_exactly_one()->mutable_literals();
11405 for (
const int lit : *mutable_literals) {
11406 if (var_in_amo.contains(
PositiveRef(lit)))
continue;
11407 (*mutable_literals)[new_size++] = lit;
11409 (*mutable_literals)[new_size++] = new_var;
11410 mutable_literals->Truncate(new_size);
11411 context_->UpdateConstraintVariableUsage(base_ct_index);
11415 for (
const int c : block_cts) {
11416 auto* mutable_linear =
11417 context_->working_model->mutable_constraints(c)->mutable_linear();
11420 int64_t offset = 0;
11421 int64_t coeff_x = 0;
11424 const int num_terms = mutable_linear->vars().size();
11425 for (
int k = 0; k < num_terms; ++k) {
11426 const int var = mutable_linear->vars(k);
11428 int64_t coeff = mutable_linear->coeffs(k);
11429 const auto it = var_in_amo.find(var);
11430 if (it != var_in_amo.end()) {
11439 if (coeff_x == 0) coeff_x = coeff;
11440 CHECK_EQ(coeff, coeff_x);
11443 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11444 mutable_linear->set_coeffs(new_size, coeff);
11449 mutable_linear->set_vars(new_size, new_var);
11450 mutable_linear->set_coeffs(new_size, coeff_x);
11453 mutable_linear->mutable_vars()->Truncate(new_size);
11454 mutable_linear->mutable_coeffs()->Truncate(new_size);
11460 context_->UpdateConstraintVariableUsage(c);
11464 timer.AddCounter(
"blocks", num_blocks);
11465 timer.AddCounter(
"saved_nz", nz_reduction);
11466 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11470void CpModelPresolver::FindBigVerticalLinearOverlap(
11472 if (time_limit_->LimitReached())
return;
11473 if (context_->ModelIsUnsat())
return;
11474 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11475 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11477 int64_t num_blocks = 0;
11478 int64_t nz_reduction = 0;
11479 absl::flat_hash_map<int, int64_t> coeff_map;
11480 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
11481 if (timer.WorkLimitIsReached())
break;
11483 bool in_enforcement =
false;
11484 std::vector<int> linear_cts;
11485 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11486 for (
const int c : context_->VarToConstraints(x)) {
11487 if (c < 0)
continue;
11488 const ConstraintProto& ct = context_->working_model->constraints(c);
11489 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11491 const int num_terms = ct.linear().vars().size();
11492 if (num_terms < 2)
continue;
11493 bool is_canonical =
true;
11494 timer.TrackSimpleLoop(num_terms);
11495 for (
int k = 0; k < num_terms; ++k) {
11497 is_canonical =
false;
11501 if (!is_canonical)
continue;
11504 timer.TrackSimpleLoop(ct.enforcement_literal().size());
11505 for (
const int lit : ct.enforcement_literal()) {
11507 in_enforcement =
true;
11514 if (in_enforcement)
continue;
11515 linear_cts.push_back(c);
11521 if (in_enforcement)
continue;
11522 if (linear_cts.size() < 10)
continue;
11525 std::sort(linear_cts.begin(), linear_cts.end());
11526 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11535 std::vector<std::pair<int, int64_t>> block;
11536 std::vector<std::pair<int, int64_t>> common_part;
11537 for (
const int c : linear_cts) {
11538 const ConstraintProto& ct = context_->working_model->constraints(c);
11539 const int num_terms = ct.linear().vars().size();
11540 timer.TrackSimpleLoop(num_terms);
11543 const int64_t x_coeff = FindVarCoeff(x, ct);
11544 if (x_coeff == 0)
continue;
11546 if (block.empty()) {
11549 for (
int k = 0; k < num_terms; ++k) {
11550 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
11552 if (coeff_map.size() < 2)
continue;
11553 block.push_back({
c, x_coeff});
11558 const int64_t gcd =
11559 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
11560 const int64_t multiple_base = coeff_map.at(x) / gcd;
11561 const int64_t multiple_ct = x_coeff / gcd;
11562 common_part.clear();
11563 for (
int k = 0; k < num_terms; ++k) {
11564 const int64_t coeff = ct.linear().coeffs(k);
11565 if (coeff % multiple_ct != 0)
continue;
11567 const auto it = coeff_map.find(ct.linear().vars(k));
11568 if (it == coeff_map.end())
continue;
11569 if (it->second % multiple_base != 0)
continue;
11570 if (it->second / multiple_base != coeff / multiple_ct)
continue;
11572 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
11576 if (common_part.size() < 2)
continue;
11579 block.push_back({
c, x_coeff});
11581 for (
const auto [var, coeff] : common_part) {
11582 coeff_map[var] = coeff;
11587 const int64_t saved_nz =
11588 ComputeNonZeroReduction(block.size(), coeff_map.size());
11589 if (saved_nz < 30)
continue;
11592 const int64_t base_x = coeff_map.at(x);
11593 for (
auto& [c, multipier] : block) {
11594 CHECK_EQ(multipier % base_x, 0);
11595 multipier /= base_x;
11599 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
11601 nz_reduction += saved_nz;
11602 context_->UpdateRuleStats(
"linear matrix: common vertical rectangle");
11605 timer.AddCounter(
"blocks", num_blocks);
11606 timer.AddCounter(
"saved_nz", nz_reduction);
11607 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11617void CpModelPresolver::FindBigHorizontalLinearOverlap(
11619 if (time_limit_->LimitReached())
return;
11620 if (context_->ModelIsUnsat())
return;
11621 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11622 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11624 const int num_constraints = context_->working_model->constraints_size();
11625 std::vector<std::pair<int, int>> to_sort;
11626 for (
int c = 0;
c < num_constraints; ++
c) {
11627 const ConstraintProto& ct = context_->working_model->constraints(c);
11628 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11629 const int size = ct.linear().vars().size();
11630 if (size < 5)
continue;
11631 to_sort.push_back({-size,
c});
11633 std::sort(to_sort.begin(), to_sort.end());
11635 std::vector<int> sorted_linear;
11636 for (
int i = 0;
i < to_sort.size(); ++
i) {
11637 sorted_linear.push_back(to_sort[
i].second);
11642 std::vector<int> var_to_coeff_non_zeros;
11643 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
11646 int64_t num_blocks = 0;
11647 int64_t nz_reduction = 0;
11648 for (
int i = 0;
i < sorted_linear.size(); ++
i) {
11649 const int c = sorted_linear[
i];
11650 if (c < 0)
continue;
11651 if (timer.WorkLimitIsReached())
break;
11653 for (
const int var : var_to_coeff_non_zeros) {
11654 var_to_coeff[var] = 0;
11656 var_to_coeff_non_zeros.clear();
11658 const ConstraintProto& ct = context_->working_model->constraints(c);
11659 const int num_terms = ct.linear().vars().size();
11660 timer.TrackSimpleLoop(num_terms);
11661 for (
int k = 0; k < num_terms; ++k) {
11662 const int var = ct.linear().vars(k);
11663 var_to_coeff[var] = ct.linear().coeffs(k);
11664 var_to_coeff_non_zeros.push_back(var);
11672 int saved_nz = 100;
11673 std::vector<int> used_sorted_linear = {
i};
11674 std::vector<std::pair<int, int64_t>> block = {{
c, 1}};
11675 std::vector<std::pair<int, int64_t>> common_part;
11676 std::vector<std::pair<int, int>> old_matches;
11678 for (
int j = 0; j < sorted_linear.size(); ++j) {
11679 if (
i == j)
continue;
11680 const int other_c = sorted_linear[j];
11681 if (other_c < 0)
continue;
11682 const ConstraintProto& ct = context_->working_model->constraints(other_c);
11685 const int num_terms = ct.linear().vars().size();
11686 const int best_saved_nz =
11687 ComputeNonZeroReduction(block.size() + 1, num_terms);
11688 if (best_saved_nz <= saved_nz)
break;
11691 timer.TrackSimpleLoop(num_terms);
11692 common_part.clear();
11693 for (
int k = 0; k < num_terms; ++k) {
11694 const int var = ct.linear().vars(k);
11695 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
11696 common_part.push_back({var, ct.linear().coeffs(k)});
11705 const int64_t new_saved_nz =
11706 ComputeNonZeroReduction(block.size() + 1, common_part.size());
11707 if (new_saved_nz > saved_nz) {
11708 saved_nz = new_saved_nz;
11709 used_sorted_linear.push_back(j);
11710 block.push_back({other_c, 1});
11714 for (
const int var : var_to_coeff_non_zeros) {
11715 var_to_coeff[var] = 0;
11717 var_to_coeff_non_zeros.clear();
11718 for (
const auto [var, coeff] : common_part) {
11719 var_to_coeff[var] = coeff;
11720 var_to_coeff_non_zeros.push_back(var);
11723 if (common_part.size() > 1) {
11724 old_matches.push_back({j, common_part.size()});
11731 if (block.size() > 1) {
11733 const int match_size = var_to_coeff_non_zeros.size();
11734 for (
const auto [index, old_match_size] : old_matches) {
11735 if (old_match_size < match_size)
continue;
11737 int new_match_size = 0;
11738 const int other_c = sorted_linear[index];
11739 const ConstraintProto& ct =
11740 context_->working_model->constraints(other_c);
11741 const int num_terms = ct.linear().vars().size();
11742 for (
int k = 0; k < num_terms; ++k) {
11743 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
11747 if (new_match_size == match_size) {
11748 context_->UpdateRuleStats(
11749 "linear matrix: common horizontal rectangle extension");
11750 used_sorted_linear.push_back(index);
11751 block.push_back({other_c, 1});
11757 absl::flat_hash_map<int, int64_t> coeff_map;
11758 for (
const int var : var_to_coeff_non_zeros) {
11759 coeff_map[var] = var_to_coeff[var];
11761 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
11764 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
11765 context_->UpdateRuleStats(
"linear matrix: common horizontal rectangle");
11766 for (
const int i : used_sorted_linear) sorted_linear[
i] = -1;
11770 timer.AddCounter(
"blocks", num_blocks);
11771 timer.AddCounter(
"saved_nz", nz_reduction);
11772 timer.AddCounter(
"linears", sorted_linear.size());
11773 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11781void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
11782 if (time_limit_->LimitReached())
return;
11783 if (context_->ModelIsUnsat())
return;
11788 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11791 std::vector<std::pair<int, int>> to_sort;
11792 const int num_constraints = context_->working_model->constraints_size();
11793 for (
int c = 0;
c < num_constraints; ++
c) {
11794 const ConstraintProto& ct = context_->working_model->constraints(c);
11795 if (!IsLinearEqualityConstraint(ct))
continue;
11796 if (ct.linear().vars().size() <= 2)
continue;
11799 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
11803 to_sort.push_back({ct.linear().vars().size(),
c});
11805 std::sort(to_sort.begin(), to_sort.end());
11809 std::vector<int> var_to_clear;
11810 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
11811 const int num_variables = context_->working_model->variables_size();
11812 var_to_ct_coeffs_.resize(num_variables);
11815 int num_tested_pairs = 0;
11816 int num_affine_relations = 0;
11817 for (
int start = 0; start < to_sort.size(); start =
end) {
11820 const int length = to_sort[start].first;
11821 for (;
end < to_sort.size(); ++
end) {
11822 if (to_sort[
end].first != length)
break;
11824 const int span_size =
end - start;
11825 if (span_size == 1)
continue;
11828 for (
const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
11829 var_to_clear.clear();
11830 for (
int i = start;
i <
end; ++
i) {
11831 const int c = to_sort[
i].second;
11832 const LinearConstraintProto& lin =
11833 context_->working_model->constraints(c).linear();
11835 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
11836 const int var = lin.vars(index);
11837 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
11838 var_to_ct_coeffs_[var].push_back({
c, lin.coeffs(index)});
11847 for (
int i1 = start; i1 <
end; ++i1) {
11848 if (timer.WorkLimitIsReached())
break;
11849 const int c1 = to_sort[i1].second;
11850 const LinearConstraintProto& lin1 =
11851 context_->working_model->constraints(c1).linear();
11853 for (
int i = 0; !skip &&
i < lin1.vars().size(); ++
i) {
11854 for (
const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(
i)]) {
11855 if (c2 == c1)
continue;
11858 if (coeff2 != lin1.coeffs(
i))
continue;
11859 if (timer.WorkLimitIsReached())
break;
11862 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11863 if (ct2.constraint_case() != ConstraintProto::kLinear)
continue;
11864 const LinearConstraintProto& lin2 =
11865 context_->working_model->constraints(c2).linear();
11866 if (lin2.vars().size() != length)
continue;
11871 timer.TrackSimpleLoop(length);
11873 ++num_tested_pairs;
11878 auto* to_modify = context_->working_model->mutable_constraints(c1);
11880 -1, context_->working_model->constraints(c2), to_modify)) {
11887 DCHECK_LE(to_modify->linear().vars().size(), 2);
11889 ++num_affine_relations;
11890 context_->UpdateRuleStats(
11891 "linear: advanced affine relation from 2 constraints.");
11894 DivideLinearByGcd(to_modify);
11895 PresolveSmallLinear(to_modify);
11896 context_->UpdateConstraintVariableUsage(c1);
11905 timer.AddCounter(
"num_tested_pairs", num_tested_pairs);
11906 timer.AddCounter(
"found", num_affine_relations);
11907 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11910void CpModelPresolver::ExtractEncodingFromLinear() {
11911 if (time_limit_->LimitReached())
return;
11912 if (context_->ModelIsUnsat())
return;
11913 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11914 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11917 std::vector<int> relevant_constraints;
11918 CompactVectorVector<int> storage;
11920 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11926 std::vector<int> vars;
11927 const int num_constraints = context_->working_model->constraints().size();
11928 for (
int c = 0;
c < num_constraints; ++
c) {
11929 const ConstraintProto& ct = context_->working_model->constraints(c);
11930 switch (ct.constraint_case()) {
11931 case ConstraintProto::kAtMostOne: {
11933 for (
const int ref : ct.at_most_one().literals()) {
11936 relevant_constraints.push_back(c);
11937 detector.AddPotentialSuperset(storage.Add(vars));
11940 case ConstraintProto::kExactlyOne: {
11942 for (
const int ref : ct.exactly_one().literals()) {
11945 relevant_constraints.push_back(c);
11946 detector.AddPotentialSuperset(storage.Add(vars));
11949 case ConstraintProto::kLinear: {
11951 if (!IsLinearEqualityConstraint(ct))
continue;
11955 bool is_candidate =
true;
11956 int num_integers = 0;
11958 const int num_terms = ct.linear().vars().size();
11959 for (
int i = 0;
i < num_terms; ++
i) {
11960 const int ref = ct.linear().vars(
i);
11961 if (context_->CanBeUsedAsLiteral(ref)) {
11965 if (std::abs(ct.linear().coeffs(
i)) != 1) {
11966 is_candidate =
false;
11969 if (num_integers == 2) {
11970 is_candidate =
false;
11978 if (is_candidate && num_integers == 1 && vars.size() > 1) {
11979 relevant_constraints.push_back(c);
11980 detector.AddPotentialSubset(storage.Add(vars));
11990 int64_t num_exactly_one_encodings = 0;
11991 int64_t num_at_most_one_encodings = 0;
11992 int64_t num_literals = 0;
11993 int64_t num_unique_terms = 0;
11994 int64_t num_multiple_terms = 0;
11996 detector.DetectInclusions([&](
int subset,
int superset) {
11997 const int subset_c = relevant_constraints[subset];
11998 const int superset_c = relevant_constraints[superset];
11999 const ConstraintProto& superset_ct =
12000 context_->working_model->constraints(superset_c);
12001 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
12002 ++num_at_most_one_encodings;
12004 ++num_exactly_one_encodings;
12006 num_literals += storage[subset].size();
12007 context_->UpdateRuleStats(
"encoding: extracted from linear");
12009 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
12010 &num_multiple_terms)) {
12014 detector.StopProcessingCurrentSubset();
12017 timer.AddCounter(
"potential_supersets", detector.num_potential_supersets());
12018 timer.AddCounter(
"potential_subsets", detector.num_potential_subsets());
12019 timer.AddCounter(
"amo_encodings", num_at_most_one_encodings);
12020 timer.AddCounter(
"exo_encodings", num_exactly_one_encodings);
12021 timer.AddCounter(
"unique_terms", num_unique_terms);
12022 timer.AddCounter(
"multiple_terms", num_multiple_terms);
12023 timer.AddCounter(
"literals", num_literals);
12037void CpModelPresolver::LookAtVariableWithDegreeTwo(
int var) {
12039 CHECK(context_->ConstraintVariableGraphIsUpToDate());
12040 if (context_->ModelIsUnsat())
return;
12041 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12042 if (context_->IsFixed(var))
return;
12043 if (!context_->ModelIsExpanded())
return;
12044 if (!context_->CanBeUsedAsLiteral(var))
return;
12051 if (context_->VarToConstraints(var).size() != 2)
return;
12053 bool abort =
false;
12055 Domain union_of_domain;
12056 int num_positive = 0;
12057 std::vector<int> constraint_indices_to_remove;
12058 for (
const int c : context_->VarToConstraints(var)) {
12063 constraint_indices_to_remove.push_back(c);
12064 const ConstraintProto& ct = context_->working_model->constraints(c);
12065 if (ct.enforcement_literal().size() != 1 ||
12067 ct.constraint_case() != ConstraintProto::kLinear ||
12068 ct.linear().vars().size() != 1) {
12072 if (ct.enforcement_literal(0) == var) ++num_positive;
12073 if (ct_var != -1 &&
PositiveRef(ct.linear().vars(0)) != ct_var) {
12078 union_of_domain = union_of_domain.UnionWith(
12081 ? ct.linear().coeffs(0)
12082 : -ct.linear().coeffs(0)));
12085 if (num_positive != 1)
return;
12086 if (!context_->IntersectDomainWith(ct_var, union_of_domain))
return;
12088 context_->UpdateRuleStats(
"variables: removable enforcement literal");
12089 absl::c_sort(constraint_indices_to_remove);
12094 for (
const int c : constraint_indices_to_remove) {
12095 context_->NewMappingConstraint(context_->working_model->constraints(c),
12096 __FILE__, __LINE__);
12097 context_->working_model->mutable_constraints(c)->Clear();
12098 context_->UpdateConstraintVariableUsage(c);
12100 context_->MarkVariableAsRemoved(var);
12105absl::Span<const int> AtMostOneOrExactlyOneLiterals(
const ConstraintProto& ct) {
12106 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
12107 return {ct.at_most_one().literals()};
12109 return {ct.exactly_one().literals()};
12115void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(
int var) {
12117 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12118 if (context_->ModelIsUnsat())
return;
12119 if (context_->IsFixed(var))
return;
12120 if (context_->VariableWasRemoved(var))
return;
12121 if (!context_->ModelIsExpanded())
return;
12122 if (!context_->CanBeUsedAsLiteral(var))
return;
12126 if (context_->VarToConstraints(var).size() != 3)
return;
12127 cost = context_->ObjectiveMap().at(var);
12129 if (context_->VarToConstraints(var).size() != 2)
return;
12137 for (
const int c : context_->VarToConstraints(var)) {
12138 if (c < 0)
continue;
12139 const ConstraintProto& ct = context_->working_model->constraints(c);
12140 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
12141 ct.constraint_case() != ConstraintProto::kExactlyOne) {
12152 if (c1 == -1 || c2 == -1)
return;
12156 if (c1 > c2) std::swap(c1, c2);
12165 context_->tmp_literals.clear();
12166 int c1_ref = std::numeric_limits<int>::min();
12167 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
12168 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1)
return;
12169 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
12173 context_->tmp_literals.push_back(lit);
12176 int c2_ref = std::numeric_limits<int>::min();
12177 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
12178 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1)
return;
12179 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
12183 context_->tmp_literals.push_back(lit);
12186 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
12187 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
12192 int64_t cost_shift = 0;
12193 absl::Span<const int> literals;
12194 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
12196 literals = ct1.exactly_one().literals();
12197 }
else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12199 literals = ct2.exactly_one().literals();
12205 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12206 if (context_->params().keep_symmetry_in_presolve())
return;
12207 if (cost != 0 && context_->ObjectiveDomainIsConstraining())
return;
12211 literals = ct1.at_most_one().literals();
12214 literals = ct2.at_most_one().literals();
12218 if (!context_->ShiftCostInExactlyOne(literals, cost_shift))
return;
12219 DCHECK(!context_->ObjectiveMap().contains(var));
12220 context_->NewMappingConstraint(__FILE__, __LINE__)
12221 ->mutable_exactly_one()
12222 ->mutable_literals()
12223 ->Assign(literals.begin(), literals.end());
12226 const int new_ct_index = context_->working_model->constraints().size();
12227 ConstraintProto* new_ct = context_->working_model->add_constraints();
12228 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
12229 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
12230 for (
const int lit : context_->tmp_literals) {
12231 new_ct->mutable_exactly_one()->add_literals(lit);
12236 for (
const int lit : context_->tmp_literals) {
12237 new_ct->mutable_at_most_one()->add_literals(lit);
12241 context_->UpdateNewConstraintsVariableUsage();
12242 context_->working_model->mutable_constraints(c1)->Clear();
12243 context_->UpdateConstraintVariableUsage(c1);
12244 context_->working_model->mutable_constraints(c2)->Clear();
12245 context_->UpdateConstraintVariableUsage(c2);
12247 context_->UpdateRuleStats(
12248 "at_most_one: resolved two constraints with opposite literal");
12249 context_->MarkVariableAsRemoved(var);
12254 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
12255 if (PresolveAtMostOrExactlyOne(new_ct)) {
12256 context_->UpdateConstraintVariableUsage(new_ct_index);
12271void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(
int var) {
12274 int num_others = 0;
12275 std::vector<int> to_rewrite;
12276 for (
const int c : context_->VarToConstraints(var)) {
12278 const ConstraintProto& ct = context_->working_model->constraints(c);
12279 if (ct.constraint_case() == ConstraintProto::kLinear &&
12280 ct.linear().vars().size() == 1) {
12281 to_rewrite.push_back(c);
12288 if (num_others != 1)
return;
12289 if (other_c < 0)
return;
12294 const auto& other_ct = context_->working_model->constraints(other_c);
12295 if (context_->ConstraintToVars(other_c).size() != 2 ||
12296 !other_ct.enforcement_literal().empty() ||
12297 other_ct.constraint_case() == ConstraintProto::kLinear) {
12303 std::function<std::pair<int, Domain>(
const Domain& implied)> transfer_f =
12311 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
12312 other_ct.lin_max().target().vars().size() == 1 &&
12313 other_ct.lin_max().target().vars(0) == var &&
12314 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
12315 IsAffineIntAbs(other_ct)) {
12316 context_->UpdateRuleStats(
"linear1: transferred from abs(X) to X");
12317 const LinearExpressionProto& target = other_ct.lin_max().target();
12318 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
12319 transfer_f = [target = target, expr = expr](
const Domain& implied) {
12320 Domain target_domain =
12321 implied.ContinuousMultiplicationBy(target.coeffs(0))
12322 .AdditionWith(Domain(target.offset()));
12323 target_domain = target_domain.IntersectionWith(
12324 Domain(0, std::numeric_limits<int64_t>::max()));
12327 const Domain expr_domain =
12328 target_domain.UnionWith(target_domain.Negation());
12329 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
12330 .InverseMultiplicationBy(expr.coeffs(0));
12331 return std::make_pair(expr.vars(0), new_domain);
12335 if (transfer_f ==
nullptr) {
12336 context_->UpdateRuleStats(
12337 "TODO linear1: appear in only one extra 2-var constraint");
12342 std::sort(to_rewrite.begin(), to_rewrite.end());
12343 const Domain var_domain = context_->DomainOf(var);
12344 for (
const int c : to_rewrite) {
12345 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12346 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
12348 LOG(INFO) <<
"Aborted in MaybeTransferLinear1ToAnotherVariable()";
12352 const Domain implied =
12354 auto [new_var, new_domain] = transfer_f(implied);
12355 const Domain current = context_->DomainOf(new_var);
12356 new_domain = new_domain.IntersectionWith(current);
12357 if (new_domain.IsEmpty()) {
12358 if (!MarkConstraintAsFalse(ct))
return;
12359 }
else if (new_domain == current) {
12362 ct->mutable_linear()->set_vars(0, new_var);
12365 context_->UpdateConstraintVariableUsage(c);
12369 context_->NewMappingConstraint(other_ct, __FILE__, __LINE__);
12370 context_->working_model->mutable_constraints(other_c)->Clear();
12371 context_->UpdateConstraintVariableUsage(other_c);
12372 context_->MarkVariableAsRemoved(var);
12385void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(
int var) {
12386 if (context_->ModelIsUnsat())
return;
12387 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12388 if (context_->params().keep_symmetry_in_presolve())
return;
12389 if (context_->IsFixed(var))
return;
12390 if (context_->VariableWasRemoved(var))
return;
12391 if (context_->CanBeUsedAsLiteral(var))
return;
12392 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
12396 if (!context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var)) {
12397 if (context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var)) {
12398 MaybeTransferLinear1ToAnotherVariable(var);
12414 if (context_->VariableWithCostIsUniqueAndRemovable(var)) {
12416 for (
const int c : context_->VarToConstraints(var)) {
12417 if (c < 0)
continue;
12418 CHECK_EQ(unique_c, -1);
12421 CHECK_NE(unique_c, -1);
12422 const ConstraintProto& ct = context_->working_model->constraints(unique_c);
12423 const int64_t cost = context_->ObjectiveCoeff(var);
12424 if (ct.linear().vars(0) == var) {
12428 if (implied.IsEmpty()) {
12429 if (!MarkConstraintAsFalse(
12430 context_->working_model->mutable_constraints(unique_c))) {
12433 context_->UpdateConstraintVariableUsage(unique_c);
12437 int64_t value1, value2;
12439 context_->UpdateRuleStats(
"variables: fix singleton var in linear1");
12440 return (
void)context_->IntersectDomainWith(var, Domain(implied.
Min()));
12441 }
else if (cost > 0) {
12442 value1 = context_->MinOf(var);
12443 value2 = implied.
Min();
12445 value1 = context_->MaxOf(var);
12446 value2 = implied.
Max();
12451 context_->UpdateRuleStats(
"variables: reduced domain to two values");
12458 solution_crush_.SetVarToConditionalValue(var, ct.enforcement_literal(),
12460 return (
void)context_->IntersectDomainWith(
12471 absl::flat_hash_set<int64_t> values_set;
12472 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
12473 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
12474 bool abort =
false;
12475 for (
const int c : context_->VarToConstraints(var)) {
12476 if (c < 0)
continue;
12477 const ConstraintProto& ct = context_->working_model->constraints(c);
12478 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
12479 CHECK_EQ(ct.linear().vars().size(), 1);
12480 int64_t coeff = ct.linear().coeffs(0);
12481 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
12486 const int var =
PositiveRef(ct.linear().vars(0));
12487 const Domain var_domain = context_->DomainOf(var);
12491 if (rhs.IsEmpty()) {
12492 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12496 }
else if (rhs.IsFixed()) {
12497 if (!var_domain.
Contains(rhs.FixedValue())) {
12498 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12502 values_set.insert(rhs.FixedValue());
12503 value_to_equal_literals[rhs.FixedValue()].push_back(
12504 ct.enforcement_literal(0));
12507 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
12508 if (complement.IsEmpty()) {
12513 if (complement.IsFixed()) {
12514 if (var_domain.
Contains(complement.FixedValue())) {
12515 values_set.insert(complement.FixedValue());
12516 value_to_not_equal_literals[complement.FixedValue()].push_back(
12517 ct.enforcement_literal(0));
12526 context_->UpdateRuleStats(
"TODO variables: only used in linear1.");
12528 }
else if (value_to_not_equal_literals.empty() &&
12529 value_to_equal_literals.empty()) {
12536 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
12537 std::sort(encoded_values.begin(), encoded_values.end());
12538 CHECK(!encoded_values.empty());
12539 const bool is_fully_encoded =
12540 encoded_values.size() == context_->DomainOf(var).Size();
12545 for (
const int64_t v : encoded_values) {
12546 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
12547 const auto eq_it = value_to_equal_literals.find(v);
12548 if (eq_it != value_to_equal_literals.end()) {
12549 absl::c_sort(eq_it->second);
12550 for (
const int lit : eq_it->second) {
12551 context_->AddImplication(lit, encoding_lit);
12554 const auto neq_it = value_to_not_equal_literals.find(v);
12555 if (neq_it != value_to_not_equal_literals.end()) {
12556 absl::c_sort(neq_it->second);
12557 for (
const int lit : neq_it->second) {
12558 context_->AddImplication(lit,
NegatedRef(encoding_lit));
12562 context_->UpdateNewConstraintsVariableUsage();
12565 Domain other_values;
12566 if (!is_fully_encoded) {
12567 other_values = context_->DomainOf(var).IntersectionWith(
12575 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
12576 if (is_fully_encoded) {
12580 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
12584 if (context_->ObjectiveDomainIsConstraining() &&
12585 !other_values.IsFixed()) {
12586 context_->UpdateRuleStats(
12587 "TODO variables: only used in objective and in encoding");
12598 Domain(obj_coeff > 0 ? other_values.
Min() : other_values.
Max());
12599 min_value = other_values.FixedValue();
12604 int64_t accumulated = std::abs(min_value);
12605 for (
const int64_t value : encoded_values) {
12606 accumulated =
CapAdd(accumulated, std::abs(
CapSub(value, min_value)));
12607 if (accumulated == std::numeric_limits<int64_t>::max()) {
12608 context_->UpdateRuleStats(
12609 "TODO variables: only used in objective and in encoding");
12614 ConstraintProto encoding_ct;
12615 LinearConstraintProto* linear = encoding_ct.mutable_linear();
12616 const int64_t coeff_in_equality = -1;
12617 linear->add_vars(var);
12618 linear->add_coeffs(coeff_in_equality);
12620 linear->add_domain(-min_value);
12621 linear->add_domain(-min_value);
12622 for (
const int64_t value : encoded_values) {
12623 if (value == min_value)
continue;
12624 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
12625 const int64_t coeff = value - min_value;
12627 linear->add_vars(enf);
12628 linear->add_coeffs(coeff);
12631 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
12632 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
12634 linear->add_coeffs(-coeff);
12637 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
12639 context_->UpdateRuleStats(
12640 "TODO variables: only used in objective and in encoding");
12643 context_->UpdateRuleStats(
12644 "variables: only used in objective and in encoding");
12646 context_->UpdateRuleStats(
"variables: only used in encoding");
12651 std::vector<int> to_clear;
12652 for (
const int c : context_->VarToConstraints(var)) {
12653 if (c >= 0) to_clear.push_back(c);
12655 absl::c_sort(to_clear);
12656 for (
const int c : to_clear) {
12657 if (c < 0)
continue;
12658 context_->working_model->mutable_constraints(c)->Clear();
12659 context_->UpdateConstraintVariableUsage(c);
12664 int64_t special_value = 0;
12667 ConstraintProto* new_ct = context_->working_model->add_constraints();
12668 if (is_fully_encoded) {
12670 for (
const int64_t value : encoded_values) {
12671 new_ct->mutable_exactly_one()->add_literals(
12672 context_->GetOrCreateVarValueEncoding(var, value));
12674 PresolveExactlyOne(new_ct);
12677 for (
const int64_t value : encoded_values) {
12678 new_ct->mutable_at_most_one()->add_literals(
12679 context_->GetOrCreateVarValueEncoding(var, value));
12681 PresolveAtMostOne(new_ct);
12685 special_value = other_values.SmallestValue();
12687 if (context_->ModelIsUnsat())
return;
12691 ConstraintProto* mapping_ct =
12692 context_->NewMappingConstraint(__FILE__, __LINE__);
12693 mapping_ct->mutable_linear()->add_vars(var);
12694 mapping_ct->mutable_linear()->add_coeffs(1);
12695 int64_t offset = special_value;
12696 for (
const int64_t value : encoded_values) {
12697 const int literal = context_->GetOrCreateVarValueEncoding(var, value);
12698 const int64_t coeff = (value - special_value);
12700 mapping_ct->mutable_linear()->add_vars(literal);
12701 mapping_ct->mutable_linear()->add_coeffs(-coeff);
12704 mapping_ct->mutable_linear()->add_vars(
PositiveRef(literal));
12705 mapping_ct->mutable_linear()->add_coeffs(coeff);
12708 mapping_ct->mutable_linear()->add_domain(offset);
12709 mapping_ct->mutable_linear()->add_domain(offset);
12711 context_->UpdateNewConstraintsVariableUsage();
12712 context_->MarkVariableAsRemoved(var);
12715void CpModelPresolver::TryToSimplifyDomain(
int var) {
12717 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12718 if (context_->ModelIsUnsat())
return;
12719 if (context_->IsFixed(var))
return;
12720 if (context_->VariableWasRemoved(var))
return;
12721 if (context_->VariableIsNotUsedAnymore(var))
return;
12723 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12724 if (r.representative != var)
return;
12727 const Domain& domain = context_->DomainOf(var);
12730 if (domain.Size() == 2 && (domain.
Min() != 0 || domain.
Max() != 1)) {
12731 context_->CanonicalizeDomainOfSizeTwo(var);
12735 if (domain.NumIntervals() != domain.Size())
return;
12737 const int64_t var_min = domain.
Min();
12738 int64_t gcd = domain[1].start - var_min;
12739 for (
int index = 2; index < domain.NumIntervals(); ++index) {
12740 const ClosedInterval&
i = domain[index];
12741 DCHECK_EQ(
i.start,
i.end);
12742 const int64_t shifted_value =
i.start - var_min;
12743 DCHECK_GT(shifted_value, 0);
12745 gcd = std::gcd(gcd, shifted_value);
12746 if (gcd == 1)
break;
12748 if (gcd == 1)
return;
12751 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
12755void CpModelPresolver::EncodeAllAffineRelations() {
12756 int64_t num_added = 0;
12757 for (
int var = 0; var < context_->working_model->variables_size(); ++var) {
12758 if (context_->IsFixed(var))
continue;
12760 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12761 if (r.representative == var)
continue;
12766 if (context_->VariableIsNotUsedAnymore(var))
continue;
12767 if (!PresolveAffineRelationIfAny(var))
break;
12768 if (context_->VariableIsNotUsedAnymore(var))
continue;
12769 if (context_->IsFixed(var))
continue;
12772 ConstraintProto* ct = context_->working_model->add_constraints();
12773 auto* arg = ct->mutable_linear();
12774 arg->add_vars(var);
12775 arg->add_coeffs(1);
12776 arg->add_vars(r.representative);
12777 arg->add_coeffs(-r.coeff);
12778 arg->add_domain(r.offset);
12779 arg->add_domain(r.offset);
12780 context_->UpdateNewConstraintsVariableUsage();
12785 context_->RemoveAllVariablesFromAffineRelationConstraint();
12787 if (num_added > 0) {
12788 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
12793bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
12794 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12795 if (r.representative == var)
return true;
12798 if (!context_->PropagateAffineRelation(var))
return false;
12804 if (context_->IsFixed(var))
return true;
12806 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
12811 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
12816bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
12817 std::deque<int>* queue) {
12819 if (context_->ModelIsUnsat())
return false;
12820 if (time_limit_->LimitReached())
return false;
12821 in_queue->resize(context_->working_model->constraints_size(),
false);
12822 const auto& vector_that_can_grow_during_iter =
12823 context_->modified_domains.PositionsSetAtLeastOnce();
12824 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
12825 const int v = vector_that_can_grow_during_iter[
i];
12826 context_->modified_domains.Clear(v);
12827 if (context_->VariableIsNotUsedAnymore(v))
continue;
12828 if (context_->ModelIsUnsat())
return false;
12829 if (!PresolveAffineRelationIfAny(v))
return false;
12830 if (context_->VariableIsNotUsedAnymore(v))
continue;
12832 TryToSimplifyDomain(v);
12835 if (context_->ModelIsUnsat())
return false;
12836 context_->UpdateNewConstraintsVariableUsage();
12838 if (!context_->CanonicalizeOneObjectiveVariable(v))
return false;
12840 in_queue->resize(context_->working_model->constraints_size(),
false);
12841 for (
const int c : context_->VarToConstraints(v)) {
12842 if (c >= 0 && !(*in_queue)[c]) {
12843 (*in_queue)[
c] =
true;
12844 queue->push_back(c);
12848 context_->modified_domains.ResetAllToFalse();
12852 std::sort(queue->begin(), queue->end());
12853 return !queue->empty();
12856void CpModelPresolver::PresolveToFixPoint() {
12857 if (time_limit_->LimitReached())
return;
12858 if (context_->ModelIsUnsat())
return;
12859 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12862 int num_dominance_tests = 0;
12863 int num_dual_strengthening = 0;
12866 const int64_t max_num_operations =
12867 context_->params().debug_max_num_presolve_operations() > 0
12868 ? context_->params().debug_max_num_presolve_operations()
12869 : std::numeric_limits<int64_t>::max();
12874 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
12877 std::vector<bool> in_queue(context_->working_model->constraints_size(),
12879 std::deque<int> queue;
12880 for (
int c = 0;
c < in_queue.size(); ++
c) {
12881 if (context_->working_model->constraints(c).constraint_case() !=
12882 ConstraintProto::CONSTRAINT_NOT_SET) {
12883 in_queue[
c] =
true;
12884 queue.push_back(c);
12892 if (context_->params().permute_presolve_constraint_order()) {
12893 std::shuffle(queue.begin(), queue.end(), *context_->random());
12895 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
12896 const int score_a = context_->ConstraintToVars(a).size();
12897 const int score_b = context_->ConstraintToVars(b).size();
12898 return score_a < score_b || (score_a == score_b && a < b);
12906 constexpr int kMaxNumLoops = 1000;
12907 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
12908 if (time_limit_->LimitReached())
break;
12909 if (context_->ModelIsUnsat())
break;
12910 if (context_->num_presolve_operations > max_num_operations)
break;
12913 while (!queue.empty() && !context_->ModelIsUnsat()) {
12914 if (time_limit_->LimitReached())
break;
12915 if (context_->num_presolve_operations > max_num_operations)
break;
12916 const int c = queue.front();
12917 in_queue[
c] =
false;
12920 const int old_num_constraint =
12921 context_->working_model->constraints_size();
12923 if (context_->ModelIsUnsat()) {
12925 logger_,
"Unsat after presolving constraint #", c,
12926 " (warning, dump might be inconsistent): ",
12931 const int new_num_constraints =
12932 context_->working_model->constraints_size();
12933 if (new_num_constraints > old_num_constraint) {
12934 context_->UpdateNewConstraintsVariableUsage();
12935 in_queue.resize(new_num_constraints,
true);
12936 for (
int c = old_num_constraint;
c < new_num_constraints; ++
c) {
12937 queue.push_back(c);
12944 context_->UpdateConstraintVariableUsage(c);
12948 if (context_->ModelIsUnsat())
return;
12950 in_queue.resize(context_->working_model->constraints_size(),
false);
12951 const auto& vector_that_can_grow_during_iter =
12952 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
12953 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
12954 const int v = vector_that_can_grow_during_iter[
i];
12955 if (context_->VariableIsNotUsedAnymore(v))
continue;
12960 context_->var_with_reduced_small_degree.Clear(v);
12964 if (context_->ModelIsUnsat())
return;
12965 if (!PresolveAffineRelationIfAny(v))
return;
12967 const int degree = context_->VarToConstraints(v).size();
12968 if (degree == 0)
continue;
12969 if (degree == 2) LookAtVariableWithDegreeTwo(v);
12970 if (degree == 2 || degree == 3) {
12972 ProcessVariableInTwoAtMostOrExactlyOne(v);
12973 in_queue.resize(context_->working_model->constraints_size(),
false);
12980 if (degree != 1)
continue;
12981 const int c = *context_->VarToConstraints(v).begin();
12982 if (c < 0)
continue;
12987 if (var_constraint_pair_already_called.contains(
12988 std::pair<int, int>(v, c))) {
12991 var_constraint_pair_already_called.insert({v,
c});
12993 if (!in_queue[c]) {
12994 in_queue[
c] =
true;
12995 queue.push_back(c);
12998 context_->var_with_reduced_small_degree.ResetAllToFalse();
13000 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13002 DCHECK(!context_->HasUnusedAffineVariable());
13005 for (
int v = 0; v < context_->working_model->variables().size(); ++v) {
13006 ProcessVariableOnlyUsedInEncoding(v);
13008 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13014 if (context_->params().keep_all_feasible_solutions_in_presolve())
break;
13015 if (!context_->working_model->assumptions().empty())
break;
13020 for (
int i = 0;
i < 10; ++
i) {
13021 if (context_->ModelIsUnsat())
return;
13022 ++num_dual_strengthening;
13023 DualBoundStrengthening dual_bound_strengthening;
13029 if (!dual_bound_strengthening.Strengthen(context_))
return;
13030 if (ProcessChangedVariables(&in_queue, &queue))
break;
13037 if (dual_bound_strengthening.NumDeletedConstraints() == 0)
break;
13039 if (!queue.empty())
continue;
13044 if (context_->params().keep_symmetry_in_presolve())
break;
13048 if (num_dominance_tests++ < 2) {
13049 if (context_->ModelIsUnsat())
return;
13050 PresolveTimer timer(
"DetectDominanceRelations", logger_, time_limit_);
13051 VarDomination var_dom;
13054 if (ProcessChangedVariables(&in_queue, &queue))
continue;
13058 if (context_->ModelIsUnsat())
return;
13068 const int num_constraints = context_->working_model->constraints_size();
13069 for (
int c = 0;
c < num_constraints; ++
c) {
13070 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13071 switch (ct->constraint_case()) {
13072 case ConstraintProto::kNoOverlap:
13074 if (PresolveNoOverlap(ct)) {
13075 context_->UpdateConstraintVariableUsage(c);
13078 case ConstraintProto::kNoOverlap2D:
13080 if (PresolveNoOverlap2D(c, ct)) {
13081 context_->UpdateConstraintVariableUsage(c);
13084 case ConstraintProto::kCumulative:
13086 if (PresolveCumulative(ct)) {
13087 context_->UpdateConstraintVariableUsage(c);
13090 case ConstraintProto::kBoolOr: {
13093 for (
const auto& pair :
13094 context_->deductions.ProcessClause(ct->bool_or().literals())) {
13095 bool modified =
false;
13096 if (!context_->IntersectDomainWith(pair.first, pair.second,
13101 context_->UpdateRuleStats(
"deductions: reduced variable domain");
13111 timer.AddCounter(
"num_loops", num_loops);
13112 timer.AddCounter(
"num_dual_strengthening", num_dual_strengthening);
13113 context_->deductions.MarkProcessingAsDoneForNow();
13123void CpModelPresolver::MergeClauses() {
13124 if (context_->ModelIsUnsat())
return;
13125 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
13128 std::vector<int> to_clean;
13131 absl::flat_hash_map<uint64_t, int> bool_and_map;
13137 const int num_variables = context_->working_model->variables_size();
13138 std::vector<int> bool_or_indices;
13139 std::vector<int64_t> literal_score(2 * num_variables, 0);
13140 const auto get_index = [](
int ref) {
13144 int64_t num_collisions = 0;
13145 int64_t num_merges = 0;
13146 int64_t num_saved_literals = 0;
13147 ClauseWithOneMissingHasher hasher(*context_->random());
13148 const int num_constraints = context_->working_model->constraints_size();
13149 for (
int c = 0;
c < num_constraints; ++
c) {
13150 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13151 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
13152 if (ct->enforcement_literal().size() > 1) {
13154 std::sort(ct->mutable_enforcement_literal()->begin(),
13155 ct->mutable_enforcement_literal()->end(),
13156 std::greater<int>());
13157 const auto [it, inserted] = bool_and_map.insert(
13158 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()),
c});
13160 to_clean.push_back(c);
13163 ConstraintProto* other_ct =
13164 context_->working_model->mutable_constraints(it->second);
13165 const absl::Span<const int> s1(ct->enforcement_literal());
13166 const absl::Span<const int> s2(other_ct->enforcement_literal());
13168 context_->UpdateRuleStats(
13169 "bool_and: merged constraints with same enforcement");
13170 other_ct->mutable_bool_and()->mutable_literals()->Add(
13171 ct->bool_and().literals().begin(),
13172 ct->bool_and().literals().end());
13174 context_->UpdateConstraintVariableUsage(c);
13180 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
13181 const int size = ct->at_most_one().literals().size();
13182 for (
const int ref : ct->at_most_one().literals()) {
13183 literal_score[get_index(ref)] += size;
13187 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
13188 const int size = ct->exactly_one().literals().size();
13189 for (
const int ref : ct->exactly_one().literals()) {
13190 literal_score[get_index(ref)] += size;
13195 if (ct->constraint_case() != ConstraintProto::kBoolOr)
continue;
13198 if (!ct->enforcement_literal().empty())
continue;
13199 if (ct->bool_or().literals().size() <= 2)
continue;
13201 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
13202 ct->mutable_bool_or()->mutable_literals()->end());
13203 hasher.RegisterClause(c, ct->bool_or().literals());
13204 bool_or_indices.push_back(c);
13207 for (
const int c : bool_or_indices) {
13208 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13210 bool merged =
false;
13211 timer.TrackSimpleLoop(ct->bool_or().literals().size());
13212 if (timer.WorkLimitIsReached())
break;
13213 for (
const int ref : ct->bool_or().literals()) {
13214 const uint64_t hash = hasher.HashWithout(c, ref);
13215 const auto it = bool_and_map.find(hash);
13216 if (it != bool_and_map.end()) {
13218 const int base_c = it->second;
13219 auto* and_ct = context_->working_model->mutable_constraints(base_c);
13221 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
13223 num_saved_literals += ct->bool_or().literals().size() - 1;
13225 and_ct->mutable_bool_and()->add_literals(ref);
13227 context_->UpdateConstraintVariableUsage(c);
13235 int best_ref = ct->bool_or().literals(0);
13236 int64_t best_score = literal_score[get_index(
NegatedRef(best_ref))];
13237 for (
const int ref : ct->bool_or().literals()) {
13238 const int64_t score = literal_score[get_index(
NegatedRef(ref))];
13239 if (score > best_score) {
13241 best_score = score;
13245 const uint64_t hash = hasher.HashWithout(c, best_ref);
13246 const auto [_, inserted] = bool_and_map.insert({hash,
c});
13248 to_clean.push_back(c);
13249 context_->tmp_literals.clear();
13250 for (
const int lit : ct->bool_or().literals()) {
13251 if (lit == best_ref)
continue;
13252 context_->tmp_literals.push_back(
NegatedRef(lit));
13255 ct->mutable_enforcement_literal()->Assign(
13256 context_->tmp_literals.begin(), context_->tmp_literals.end());
13257 ct->mutable_bool_and()->add_literals(best_ref);
13263 for (
const int c : to_clean) {
13264 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13265 if (ct->bool_and().literals().size() > 1) {
13266 context_->UpdateConstraintVariableUsage(c);
13271 context_->tmp_literals.clear();
13272 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13273 for (
const int ref : ct->enforcement_literal()) {
13274 context_->tmp_literals.push_back(
NegatedRef(ref));
13277 ct->mutable_bool_or()->mutable_literals()->Assign(
13278 context_->tmp_literals.begin(), context_->tmp_literals.end());
13281 timer.AddCounter(
"num_collisions", num_collisions);
13282 timer.AddCounter(
"num_merges", num_merges);
13283 timer.AddCounter(
"num_saved_literals", num_saved_literals);
13291 std::vector<int>* postsolve_mapping) {
13293 return presolver.Presolve();
13297 std::vector<int>* postsolve_mapping)
13298 : postsolve_mapping_(postsolve_mapping),
13300 solution_crush_(context->solution_crush()),
13301 logger_(context->logger()),
13303 interval_representative_(context->working_model->constraints_size(),
13304 IntervalConstraintHash{context->working_model},
13305 IntervalConstraintEq{context->working_model}) {}
13307CpSolverStatus CpModelPresolver::InfeasibleStatus() {
13309 return CpSolverStatus::INFEASIBLE;
13316void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13317 std::vector<int>* fixed_postsolve_mapping,
13318 CpModelProto* mapping_proto) {
13321 int old_num_variables = mapping_proto->variables().size();
13322 while (fixed_postsolve_mapping->size() < domains.size()) {
13323 mapping_proto->add_variables();
13324 fixed_postsolve_mapping->push_back(old_num_variables++);
13325 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13333 for (
int i = 0;
i < domains.size(); ++
i) {
13335 (*fixed_postsolve_mapping)[
i]));
13344 auto mapping_function = [fixed_postsolve_mapping](
int* ref) {
13345 const int image = (*fixed_postsolve_mapping)[
PositiveRef(*ref)];
13346 CHECK_GE(image, 0);
13349 for (ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13356void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13357 const int num_constraints_before_expansion =
13358 context_->working_model->constraints_size();
13360 if (context_->ModelIsUnsat())
return;
13366 const int num_constraints = context_->working_model->constraints().size();
13367 for (
int c = num_constraints_before_expansion;
c < num_constraints; ++
c) {
13368 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13369 const auto type = ct->constraint_case();
13370 if (type == ConstraintProto::kAtMostOne ||
13371 type == ConstraintProto::kExactlyOne) {
13373 context_->UpdateConstraintVariableUsage(c);
13375 if (context_->ModelIsUnsat())
return;
13376 }
else if (type == ConstraintProto::kLinear) {
13377 bool changed =
false;
13378 if (!CanonicalizeLinear(ct, &changed)) {
13382 context_->UpdateConstraintVariableUsage(c);
13392 if (context->ModelIsUnsat())
return;
13395 if (!crush.SolutionIsLoaded())
return;
13396 const int num_vars = context->working_model->variables().size();
13397 for (
int i = 0;
i < num_vars; ++
i) {
13400 crush.SetOrUpdateVarToDomain(
i, context->DomainOf(
i));
13404 for (
int i = 0;
i < num_vars; ++
i) {
13405 const auto relation = context->GetAffineRelation(
i);
13406 if (relation.representative !=
i) {
13407 crush.SetVarToLinearExpression(
13408 i, {{relation.representative, relation.coeff}}, relation.offset);
13416void CanonicalizeRoutesConstraintNodeExpressions(
PresolveContext* context) {
13417 CpModelProto& proto = *context->working_model;
13418 for (ConstraintProto& ct_ref : *proto.mutable_constraints()) {
13419 if (ct_ref.constraint_case() != ConstraintProto::kRoutes)
continue;
13420 for (RoutesConstraintProto::NodeExpressions& node_exprs :
13421 *ct_ref.mutable_routes()->mutable_dimensions()) {
13422 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
13423 context->CanonicalizeLinearExpression({}, &expr);
13447 context_->InitializeNewDomains();
13454 if (context_->working_model->has_floating_point_objective()) {
13455 context_->WriteVariableDomainsToProto();
13457 context_->working_model)) {
13459 "The floating point objective cannot be scaled with enough "
13461 return CpSolverStatus::MODEL_INVALID;
13468 *context_->mapping_model->mutable_objective() =
13469 context_->working_model->objective();
13479 std::vector<int> fixed_postsolve_mapping;
13480 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13481 return InfeasibleStatus();
13500 if (!context_->
params().cp_model_presolve()) {
13501 for (ConstraintProto& ct :
13503 if (ct.constraint_case() == ConstraintProto::kLinear) {
13508 if (!solution_crush_.SolutionIsLoaded()) {
13509 context_->LoadSolutionHint();
13511 ExpandCpModelAndCanonicalizeConstraints();
13512 UpdateHintInProto(context_);
13513 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13517 if (context_->working_model->has_objective()) {
13518 context_->WriteObjectiveToProto();
13522 EncodeAllAffineRelations();
13528 context_->WriteVariableDomainsToProto();
13529 InitializeMappingModelVariables(context_->AllDomains(),
13530 &fixed_postsolve_mapping,
13531 context_->mapping_model);
13536 if (!context_->mapping_model->constraints().empty()) {
13537 context_->UpdateRuleStats(
13538 "TODO: mapping model not empty with presolve disabled");
13539 context_->working_model->mutable_constraints()->MergeFrom(
13540 context_->mapping_model->constraints());
13541 context_->mapping_model->clear_constraints();
13544 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13545 return CpSolverStatus::UNKNOWN;
13550 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13551 for (
int var = 0; var < context_->working_model->variables().size(); ++var) {
13552 if (context_->VariableIsNotUsedAnymore(var))
continue;
13553 if (!PresolveAffineRelationIfAny(var))
return InfeasibleStatus();
13558 TryToSimplifyDomain(var);
13559 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13560 context_->UpdateNewConstraintsVariableUsage();
13562 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
13565 for (
int iter = 0; iter < context_->params().max_presolve_iterations();
13567 if (time_limit_->LimitReached())
break;
13568 context_->UpdateRuleStats(
"presolve: iteration");
13569 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13572 if (!PropagateObjective())
return InfeasibleStatus();
13578 PresolveToFixPoint();
13581 if (!context_->ModelIsExpanded()) {
13582 ExtractEncodingFromLinear();
13583 ExpandCpModelAndCanonicalizeConstraints();
13584 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13587 const int num_vars = context_->working_model->variables().size();
13588 for (
int var = 0; var < num_vars; ++var) {
13589 if (context_->VarToConstraints(var).size() <= 3) {
13590 context_->var_with_reduced_small_degree.Set(var);
13594 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13605 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13606 !time_limit_->LimitReached()) {
13610 DetectDuplicateConstraints();
13611 if (context_->params().keep_symmetry_in_presolve()) {
13613 if (!context_->working_model->has_symmetry()) {
13615 context_->working_model, logger_,
13616 context_->time_limit());
13625 if (!context_->working_model->has_symmetry()) {
13626 context_->working_model->mutable_symmetry()->Clear();
13628 }
else if (!context_->params()
13629 .keep_all_feasible_solutions_in_presolve()) {
13637 if (context_->params().cp_model_use_sat_presolve()) {
13638 if (!time_limit_->LimitReached()) {
13639 if (!PresolvePureSatPart()) {
13640 (void)context_->NotifyThatModelIsUnsat(
13641 "Proven Infeasible during SAT presolve");
13642 return InfeasibleStatus();
13655 if (!context_->ModelIsUnsat() && iter == 0) {
13656 const int old_size = context_->working_model->constraints_size();
13657 for (
int c = 0;
c < old_size; ++
c) {
13658 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13659 if (ct->constraint_case() != ConstraintProto::kLinear)
continue;
13660 ExtractAtMostOneFromLinear(ct);
13662 context_->UpdateNewConstraintsVariableUsage();
13665 if (context_->params().cp_model_probing_level() > 0) {
13666 if (!time_limit_->LimitReached()) {
13668 PresolveToFixPoint();
13671 TransformIntoMaxCliques();
13678 ProcessAtMostOneAndLinear();
13679 DetectDuplicateConstraints();
13680 DetectDuplicateConstraintsWithDifferentEnforcements();
13681 DetectDominatedLinearConstraints();
13687 if (context_->params().find_big_linear_overlap() &&
13688 !context_->params().keep_symmetry_in_presolve()) {
13689 FindAlmostIdenticalLinearConstraints();
13691 ActivityBoundHelper activity_amo_helper;
13692 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
13693 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
13698 FindBigVerticalLinearOverlap(&activity_amo_helper);
13699 FindBigHorizontalLinearOverlap(&activity_amo_helper);
13701 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13704 if (!time_limit_->LimitReached()) {
13708 if ( (
false)) DetectIncludedEnforcement();
13713 ConvertToBoolAnd();
13717 PresolveToFixPoint();
13722 const int64_t num_ops =
13723 context_->num_presolve_operations - old_num_presolve_op;
13724 if (num_ops == 0)
break;
13726 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13729 MergeNoOverlapConstraints();
13730 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13734 if (context_->working_model->has_objective()) {
13735 if (!context_->params().keep_symmetry_in_presolve()) {
13737 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
13740 PresolveToFixPoint();
13742 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13743 ShiftObjectiveWithExactlyOnes();
13744 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13748 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
13749 context_->WriteObjectiveToProto();
13754 for (
int c = 0;
c < context_->working_model->constraints_size(); ++
c) {
13755 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
13756 bool need_canonicalize =
false;
13757 if (ct.constraint_case() == ConstraintProto::kLinear) {
13758 for (
const int v : ct.linear().vars()) {
13759 if (context_->IsFixed(v)) {
13760 need_canonicalize =
true;
13765 if (need_canonicalize) {
13766 bool changed =
false;
13767 if (!CanonicalizeLinear(&ct, &changed)) {
13768 return InfeasibleStatus();
13771 context_->UpdateConstraintVariableUsage(c);
13780 EncodeAllAffineRelations();
13781 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13784 if (context_->working_model->has_symmetry()) {
13786 context_->working_model->mutable_symmetry(), context_)) {
13787 return InfeasibleStatus();
13799 absl::flat_hash_set<int> used_variables;
13800 for (DecisionStrategyProto& strategy :
13801 *context_->working_model->mutable_search_strategy()) {
13802 CHECK(strategy.variables().empty());
13803 if (strategy.exprs().empty())
continue;
13806 ConstraintProto empy_enforcement;
13807 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
13808 CanonicalizeLinearExpression(empy_enforcement, &expr);
13813 for (
const LinearExpressionProto& expr : strategy.exprs()) {
13814 if (context_->IsFixed(expr))
continue;
13816 const auto [_, inserted] = used_variables.insert(expr.vars(0));
13817 if (!inserted)
continue;
13819 *strategy.mutable_exprs(new_size++) = expr;
13825 context_->WriteVariableDomainsToProto();
13826 InitializeMappingModelVariables(context_->AllDomains(),
13827 &fixed_postsolve_mapping,
13828 context_->mapping_model);
13831 postsolve_mapping_->clear();
13832 std::vector<int> mapping(context_->working_model->variables_size(), -1);
13833 absl::flat_hash_map<int64_t, int> constant_to_index;
13834 int num_unused_variables = 0;
13835 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
13836 if (mapping[
i] != -1)
continue;
13838 if (context_->VariableWasRemoved(
i)) {
13843 const int r =
PositiveRef(context_->GetAffineRelation(
i).representative);
13844 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
13845 mapping[r] = postsolve_mapping_->size();
13846 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
13855 if (context_->VariableIsNotUsedAnymore(
i) &&
13856 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
13857 context_->IsFixed(
i))) {
13863 ++num_unused_variables;
13865 context_->mapping_model->mutable_variables(
13866 fixed_postsolve_mapping[
i]));
13872 if (context_->IsFixed(
i)) {
13873 auto [it, inserted] = constant_to_index.insert(
13874 {context_->FixedValue(
i), postsolve_mapping_->size()});
13876 mapping[
i] = it->second;
13881 mapping[
i] = postsolve_mapping_->size();
13882 postsolve_mapping_->push_back(fixed_postsolve_mapping[
i]);
13884 context_->UpdateRuleStats(absl::StrCat(
"presolve: ", num_unused_variables,
13885 " unused variables removed."));
13887 if (context_->params().permute_variable_randomly()) {
13889 const int n = postsolve_mapping_->size();
13890 std::vector<int> perm(n);
13891 std::iota(perm.begin(), perm.end(), 0);
13892 std::shuffle(perm.begin(), perm.end(), *context_->random());
13893 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
13894 if (mapping[
i] != -1) mapping[
i] = perm[mapping[
i]];
13896 std::vector<int> new_postsolve_mapping(n);
13897 for (
int i = 0;
i < n; ++
i) {
13898 new_postsolve_mapping[perm[
i]] = (*postsolve_mapping_)[
i];
13900 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13903 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13904 CanonicalizeRoutesConstraintNodeExpressions(context_);
13905 UpdateHintInProto(context_);
13906 const int old_size = postsolve_mapping_->size();
13908 CHECK_EQ(old_size, postsolve_mapping_->size());
13914 if (context_->deductions.NumDeductions() > 0) {
13915 context_->UpdateRuleStats(absl::StrCat(
13916 "deductions: ", context_->deductions.NumDeductions(),
" stored"));
13920 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13926 const std::string error =
13928 if (!error.empty()) {
13929 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
13930 return CpSolverStatus::MODEL_INVALID;
13935 if (!error.empty()) {
13937 "Error while validating mapping_model model: ", error);
13938 return CpSolverStatus::MODEL_INVALID;
13942 return CpSolverStatus::UNKNOWN;
13946 std::vector<int>* reverse_mapping) {
13947 CpModelProto* proto = context->working_model;
13950 auto mapping_function = [mapping, reverse_mapping](
int* ref)
mutable {
13952 int image = mapping[var];
13955 image = mapping[var] = reverse_mapping->size();
13956 reverse_mapping->push_back(var);
13960 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
13963 if (ct_ref.constraint_case() == ConstraintProto::kRoutes) {
13964 for (RoutesConstraintProto::NodeExpressions& node_exprs :
13965 *ct_ref.mutable_routes()->mutable_dimensions()) {
13966 for (LinearExpressionProto& expr : *node_exprs.mutable_exprs()) {
13967 if (expr.vars().empty())
continue;
13968 DCHECK_EQ(expr.vars().size(), 1);
13969 const int ref = expr.vars(0);
13970 if (context->IsFixed(ref)) {
13971 expr.set_offset(expr.offset() +
13972 context->FixedValue(ref) * expr.coeffs(0));
13974 expr.clear_coeffs();
13982 expr.clear_coeffs();
13992 if (proto->has_objective()) {
13993 for (
int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
13994 mapping_function(&mutable_ref);
13999 for (
int& mutable_ref : *proto->mutable_assumptions()) {
14000 mapping_function(&mutable_ref);
14005 if (proto->has_symmetry()) {
14006 for (SparsePermutationProto& generator :
14007 *proto->mutable_symmetry()->mutable_permutations()) {
14008 for (
int& var : *generator.mutable_support()) {
14009 mapping_function(&var);
14014 proto->mutable_symmetry()->clear_orbitopes();
14022 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
14024 for (LinearExpressionProto expr : strategy.exprs()) {
14025 DCHECK_EQ(expr.vars().size(), 1);
14026 const int image = mapping[expr.vars(0)];
14028 expr.set_vars(0, image);
14029 *strategy.mutable_exprs(new_size++) = expr;
14038 for (
const DecisionStrategyProto& strategy : proto->search_strategy()) {
14039 if (strategy.exprs().empty())
continue;
14040 *proto->mutable_search_strategy(new_size++) = strategy;
14047 if (proto->has_solution_hint()) {
14048 auto* mutable_hint = proto->mutable_solution_hint();
14053 absl::flat_hash_set<int> hinted_images;
14056 const int old_size = mutable_hint->vars().size();
14057 for (
int i = 0;
i < old_size; ++
i) {
14058 const int hinted_var = mutable_hint->vars(
i);
14059 const int64_t hinted_value = mutable_hint->values(
i);
14060 const int image = mapping[hinted_var];
14062 if (!hinted_images.insert(image).second)
continue;
14063 mutable_hint->set_vars(new_size, image);
14064 mutable_hint->set_values(new_size, hinted_value);
14068 mutable_hint->mutable_vars()->Truncate(new_size);
14069 mutable_hint->mutable_values()->Truncate(new_size);
14073 google::protobuf::RepeatedPtrField<IntegerVariableProto>
14074 new_variables_storage;
14075 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14076 if (proto->GetArena() ==
nullptr) {
14077 new_variables = &new_variables_storage;
14079 new_variables = google::protobuf::Arena::Create<
14080 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14081 proto->GetArena());
14083 for (
int i = 0;
i < mapping.size(); ++
i) {
14084 const int image = mapping[
i];
14085 if (image < 0)
continue;
14086 while (image >= new_variables->size()) {
14087 new_variables->Add();
14089 (*new_variables)[image].Swap(proto->mutable_variables(
i));
14091 proto->mutable_variables()->Swap(new_variables);
14094 for (
const IntegerVariableProto& v : proto->variables()) {
14095 CHECK_GT(v.domain_size(), 0);
14099bool CpModelPresolver::MaybeRemoveFixedVariables(
14100 std::vector<int>* postsolve_mapping) {
14101 postsolve_mapping->clear();
14102 if (!context_->params().remove_fixed_variables_early())
return true;
14103 if (!context_->params().cp_model_presolve())
return true;
14108 context_->InitializeNewDomains();
14109 if (context_->ModelIsUnsat())
return false;
14112 const int num_vars = context_->working_model->variables().size();
14113 std::vector<int> mapping(num_vars, -1);
14114 for (
int i = 0;
i < num_vars; ++
i) {
14115 if (context_->IsFixed(
i))
continue;
14116 mapping[
i] = postsolve_mapping->size();
14117 postsolve_mapping->push_back(
i);
14121 const int num_fixed = num_vars - postsolve_mapping->size();
14122 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
14123 postsolve_mapping->clear();
14130 if (context_->working_model->has_objective()) {
14131 context_->ReadObjectiveFromProto();
14132 if (!context_->CanonicalizeObjective())
return false;
14133 if (!PropagateObjective())
return false;
14134 if (context_->ModelIsUnsat())
return false;
14135 context_->WriteObjectiveToProto();
14140 context_->WriteVariableDomainsToProto();
14141 *context_->mapping_model->mutable_variables() =
14142 context_->working_model->variables();
14144 SOLVER_LOG(logger_,
"Large number of fixed variables ",
14146 ", doing a first remapping phase to go down to ",
14151 const int old_size = postsolve_mapping->size();
14153 if (postsolve_mapping->size() > old_size) {
14154 const int new_extra = postsolve_mapping->size() - old_size;
14156 " fixed variables still required in the model!");
14160 context_->ResetAfterCopy();
14167ConstraintProto CopyObjectiveForDuplicateDetection(
14168 const CpObjectiveProto& objective) {
14169 ConstraintProto copy;
14170 *copy.mutable_linear()->mutable_vars() = objective.vars();
14171 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
14175struct ConstraintHashForDuplicateDetection {
14176 const CpModelProto* working_model;
14177 bool ignore_enforcement;
14178 ConstraintProto objective_constraint;
14180 ConstraintHashForDuplicateDetection(
const CpModelProto* working_model,
14181 bool ignore_enforcement)
14182 : working_model(working_model),
14183 ignore_enforcement(ignore_enforcement),
14184 objective_constraint(
14185 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14190 std::size_t operator()(
int ct_idx)
const {
14192 ? objective_constraint
14193 : working_model->constraints(ct_idx);
14194 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
14195 type_and_enforcement = {ct.constraint_case(),
14197 ? absl::Span<const int>()
14198 : absl::MakeSpan(ct.enforcement_literal())};
14199 switch (ct.constraint_case()) {
14200 case ConstraintProto::kLinear:
14201 if (ignore_enforcement) {
14202 return absl::HashOf(type_and_enforcement,
14203 absl::MakeSpan(ct.linear().vars()),
14204 absl::MakeSpan(ct.linear().coeffs()),
14205 absl::MakeSpan(ct.linear().domain()));
14209 return absl::HashOf(type_and_enforcement,
14210 absl::MakeSpan(ct.linear().vars()),
14211 absl::MakeSpan(ct.linear().coeffs()));
14213 case ConstraintProto::kBoolAnd:
14214 return absl::HashOf(type_and_enforcement,
14215 absl::MakeSpan(ct.bool_and().literals()));
14216 case ConstraintProto::kBoolOr:
14217 return absl::HashOf(type_and_enforcement,
14218 absl::MakeSpan(ct.bool_or().literals()));
14219 case ConstraintProto::kAtMostOne:
14220 return absl::HashOf(type_and_enforcement,
14221 absl::MakeSpan(ct.at_most_one().literals()));
14222 case ConstraintProto::kExactlyOne:
14223 return absl::HashOf(type_and_enforcement,
14224 absl::MakeSpan(ct.exactly_one().literals()));
14226 ConstraintProto copy = ct;
14228 if (ignore_enforcement) {
14229 copy.mutable_enforcement_literal()->Clear();
14231 return absl::HashOf(copy.SerializeAsString());
14236struct ConstraintEqForDuplicateDetection {
14237 const CpModelProto* working_model;
14238 bool ignore_enforcement;
14239 ConstraintProto objective_constraint;
14241 ConstraintEqForDuplicateDetection(
const CpModelProto* working_model,
14242 bool ignore_enforcement)
14243 : working_model(working_model),
14244 ignore_enforcement(ignore_enforcement),
14245 objective_constraint(
14246 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
14248 bool operator()(
int a,
int b)
const {
14253 ? objective_constraint
14254 : working_model->constraints(a);
14256 ? objective_constraint
14257 : working_model->constraints(
b);
14259 if (ct_a.constraint_case() != ct_b.constraint_case())
return false;
14260 if (!ignore_enforcement) {
14261 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
14262 absl::MakeSpan(ct_b.enforcement_literal())) {
14266 switch (ct_a.constraint_case()) {
14267 case ConstraintProto::kLinear:
14270 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
14271 absl::MakeSpan(ct_b.linear().domain())) {
14274 return absl::MakeSpan(ct_a.linear().vars()) ==
14275 absl::MakeSpan(ct_b.linear().vars()) &&
14276 absl::MakeSpan(ct_a.linear().coeffs()) ==
14277 absl::MakeSpan(ct_b.linear().coeffs());
14278 case ConstraintProto::kBoolAnd:
14279 return absl::MakeSpan(ct_a.bool_and().literals()) ==
14280 absl::MakeSpan(ct_b.bool_and().literals());
14281 case ConstraintProto::kBoolOr:
14282 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14283 absl::MakeSpan(ct_b.bool_or().literals());
14284 case ConstraintProto::kAtMostOne:
14285 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14286 absl::MakeSpan(ct_b.at_most_one().literals());
14287 case ConstraintProto::kExactlyOne:
14288 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14289 absl::MakeSpan(ct_b.exactly_one().literals());
14292 ConstraintProto copy_a = ct_a;
14293 ConstraintProto copy_b = ct_b;
14294 copy_a.clear_name();
14295 copy_b.clear_name();
14296 if (ignore_enforcement) {
14297 copy_a.mutable_enforcement_literal()->Clear();
14298 copy_b.mutable_enforcement_literal()->Clear();
14300 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14308 const CpModelProto& model_proto,
bool ignore_enforcement) {
14309 std::vector<std::pair<int, int>> result;
14313 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14314 ConstraintEqForDuplicateDetection>
14316 model_proto.constraints_size(),
14317 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14318 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14321 if (model_proto.has_objective() && !ignore_enforcement) {
14325 const int num_constraints = model_proto.constraints().size();
14326 for (
int c = 0; c < num_constraints; ++c) {
14327 const auto type = model_proto.constraints(c).constraint_case();
14328 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
14331 if (ignore_enforcement && type == ConstraintProto::kBoolAnd)
continue;
14333 const auto [it, inserted] = equiv_constraints.insert({c, c});
14334 if (it->second != c) {
14336 result.push_back({c, it->second});
14344bool SimpleLinearExprEq(
const LinearExpressionProto& a,
14345 const LinearExpressionProto&
b) {
14346 return absl::MakeSpan(a.vars()) == absl::MakeSpan(
b.vars()) &&
14347 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(
b.coeffs()) &&
14348 a.offset() ==
b.offset();
14351std::size_t LinearExpressionHash(
const LinearExpressionProto& expr) {
14352 return absl::HashOf(absl::MakeSpan(expr.vars()),
14353 absl::MakeSpan(expr.coeffs()), expr.offset());
14358bool CpModelPresolver::IntervalConstraintEq::operator()(
int a,
int b)
const {
14359 const ConstraintProto& ct_a = working_model->constraints(a);
14360 const ConstraintProto& ct_b = working_model->constraints(
b);
14361 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14362 absl::MakeSpan(ct_b.enforcement_literal()) &&
14363 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14364 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14365 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14368std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14369 int ct_idx)
const {
14370 const ConstraintProto& ct = working_model->constraints(ct_idx);
14371 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14372 LinearExpressionHash(ct.interval().start()),
14373 LinearExpressionHash(ct.interval().size()),
14374 LinearExpressionHash(ct.interval().end()));
const std::vector< Variable * > & variables() const
--— Accessors and mutators --—
void Start()
When Start() is called multiple times, only the most recent is used.
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)
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
void Resize(int num_variables)
Resizes the data structure.
void ResetFromFlatMapping(Keys keys, Values values, int minimum_num_nodes=0)
size_t size() const
Size of the "key" space, always in [0, size()).
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
void DetectDifferentVariables()
Detects variable that must take different values.
CpSolverStatus Presolve()
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Visible for testing.
void DetectDuplicateColumns()
Domain DomainOf(int ref) const
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
bool LiteralIsFalse(int lit) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
void ReadObjectiveFromProto()
void WriteObjectiveToProto() const
void LogInfo()
Logs stats to the logger.
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
absl::flat_hash_set< int > tmp_literal_set
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
void UpdateRuleStats(const std::string &name, int num_times=1)
void MarkVariableAsRemoved(int ref)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
bool ConstraintVariableUsageIsConsistent()
bool ModelIsUnsat() const
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
bool LiteralIsTrue(int lit) const
void AddCounter(std::string name, int64_t count)
void StoreSolutionAsHint(CpModelProto &model) const
Stores the solution as a hint in the given model.
void Truncate(RepeatedPtrField< T > *array, int new_size)
RepeatedPtrField version.
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
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
bool RefIsPositive(int ref)
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)
CompactVectorVector< int > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles)
Domain EvaluateImpliedIntProdDomain(const LinearArgumentProto &expr, const PresolveContext &context)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
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)
void ApplyToAllIntervalIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool SafeAddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
Same method, but returns if the addition was possible without overflowing.
bool IsNegatableInt64(absl::int128 x)
Tells whether a int128 can be casted to a int64_t that can be negated.
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)
Convenient wrapper to call the full presolve.
bool ScaleFloatingPointObjective(const SatParameters ¶ms, SolverLogger *logger, CpModelProto *proto)
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)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, DratProofHandler *drat_proof_handler, util_intops::StrongVector< LiteralIndex, LiteralIndex > *mapping, SolverLogger *logger)
bool LookForTrivialSatSolution(double deterministic_time_limit, Model *model, SolverLogger *logger)
void ScanModelForDualBoundStrengthening(const PresolveContext &context, DualBoundStrengthening *dual_bound_strengthening)
Scan the model so that dual_bound_strengthening.Strenghten() works.
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
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
int64_t SafeDoubleToInt64(double value)
std::string FormatCounter(int64_t num)
Prints a positive number with separators for easier reading (ex: 1'348'065).
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int kObjectiveConstraint
We use some special constraint index in our variable <-> constraint graph.
bool DiophantineEquationOfSizeTwoHasSolutionInDomain(const Domain &x, int64_t a, const Domain &y, int64_t b, int64_t cte)
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
Evaluates an affine expression at the given value.
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
void ApplyVariableMapping(PresolveContext *context, absl::Span< int > mapping, std::vector< int > *reverse_mapping)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
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)
Returns true if a linear expression can be reduced to a single ref.
int64_t LinearExpressionGcd(const LinearExpressionProto &expr, int64_t gcd)
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, CpModelProto *proto, SolverLogger *logger, TimeLimit *time_limit)
Detects symmetries and fill the symmetry field.
void ApplyToAllLiteralIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
InclusionDetector(const Storage &storage) -> InclusionDetector< Storage >
Deduction guide.
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)
Fills and propagates the set of reachable states/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)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
bool LinearExpressionProtosAreEqual(const LinearExpressionProto &a, const LinearExpressionProto &b, int64_t b_scaling)
Returns true iff a == b * b_scaling.
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
void ApplyToAllVariableIndices(const std::function< void(int *)> &f, ConstraintProto *ct)
bool LoadModelForPresolve(const CpModelProto &model_proto, SatParameters params, PresolveContext *context, Model *local_model, absl::string_view name_for_logging)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
In SWIG mode, we don't want anything besides these top-level includes.
bool AtMinOrMaxInt64(int64_t x)
Checks if x is equal to the min or the max value of an int64_t.
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)
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)
Simple wrapper function for most usage.
int64_t Max() const
Returns the max of the domain.
int64_t Min() const
Returns the min of the domain.
bool Contains(int64_t value) const
Various inclusion tests on a domain.
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,...)