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/meta/type_traits.h"
42#include "absl/numeric/int128.h"
43#include "absl/random/distributions.h"
44#include "absl/status/statusor.h"
45#include "absl/strings/str_cat.h"
46#include "absl/types/span.h"
47#include "google/protobuf/arena.h"
48#include "google/protobuf/repeated_field.h"
49#include "google/protobuf/repeated_ptr_field.h"
62#include "ortools/sat/cp_model.pb.h"
80#include "ortools/sat/sat_parameters.pb.h"
101bool LinearConstraintIsClean(
const LinearConstraintProto& linear) {
102 const int num_vars = linear.vars().size();
103 for (
int i = 0;
i < num_vars; ++
i) {
105 if (linear.coeffs(
i) == 0)
return false;
112bool CpModelPresolver::RemoveConstraint(ConstraintProto* ct) {
123 interval_representative_.clear();
124 std::vector<int> interval_mapping(context_->working_model->constraints_size(),
126 int new_num_constraints = 0;
127 const int old_num_non_empty_constraints =
128 context_->working_model->constraints_size();
129 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
130 const auto type = context_->working_model->constraints(c).constraint_case();
131 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
132 if (type == ConstraintProto::kDummyConstraint)
continue;
133 context_->working_model->mutable_constraints(new_num_constraints)
134 ->Swap(context_->working_model->mutable_constraints(c));
135 if (type == ConstraintProto::kInterval) {
139 const auto [it, inserted] = interval_representative_.insert(
140 {new_num_constraints, new_num_constraints});
141 interval_mapping[c] = it->second;
142 if (it->second != new_num_constraints) {
143 context_->UpdateRuleStats(
144 "intervals: change duplicate index across constraints");
148 new_num_constraints++;
151 context_->working_model->mutable_constraints(), new_num_constraints);
152 for (ConstraintProto& ct_ref :
153 *context_->working_model->mutable_constraints()) {
155 [&interval_mapping](
int* ref) {
156 *ref = interval_mapping[*ref];
163bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto* ct) {
168 const int old_size = ct->enforcement_literal().size();
170 for (
const int literal : ct->enforcement_literal()) {
179 return RemoveConstraint(ct);
186 return RemoveConstraint(ct);
192 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
193 const int64_t obj_coeff =
197 context_->UpdateRuleStats(
"enforcement: literal with unique direction");
198 CHECK(context_->SetLiteralToFalse(literal));
199 return RemoveConstraint(ct);
208 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
210 context_->UpdateRuleStats(
"enforcement: removed duplicate literal");
213 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
214 context_->UpdateRuleStats(
"enforcement: can never be true");
215 return RemoveConstraint(ct);
219 ct->set_enforcement_literal(new_size++, literal);
221 ct->mutable_enforcement_literal()->Truncate(new_size);
222 return new_size != old_size;
225bool CpModelPresolver::PresolveBoolXor(ConstraintProto* ct) {
226 if (context_->ModelIsUnsat())
return false;
230 bool changed =
false;
231 int num_true_literals = 0;
232 int true_literal = std::numeric_limits<int32_t>::min();
233 for (
const int literal : ct->bool_xor().literals()) {
240 if (context_->VariableIsUniqueAndRemovable(literal)) {
241 context_->UpdateRuleStats(
"TODO bool_xor: remove constraint");
244 if (context_->LiteralIsFalse(literal)) {
245 context_->UpdateRuleStats(
"bool_xor: remove false literal");
248 }
else if (context_->LiteralIsTrue(literal)) {
249 true_literal = literal;
254 ct->mutable_bool_xor()->set_literals(new_size++, literal);
258 if (num_true_literals % 2 == 0) {
259 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
261 context_->UpdateRuleStats(
"bool_xor: always true");
262 return RemoveConstraint(ct);
264 }
else if (new_size == 1) {
265 if (num_true_literals % 2 == 0) {
266 if (!context_->SetLiteralToTrue(ct->bool_xor().literals(0))) {
267 return context_->NotifyThatModelIsUnsat(
268 "bool_xor: cannot fix last literal");
271 if (!context_->SetLiteralToFalse(ct->bool_xor().literals(0))) {
272 return context_->NotifyThatModelIsUnsat(
273 "bool_xor: cannot fix last literal");
276 context_->UpdateRuleStats(
"bool_xor: one active literal");
277 return RemoveConstraint(ct);
278 }
else if (new_size == 2) {
279 const int a = ct->bool_xor().literals(0);
280 const int b = ct->bool_xor().literals(1);
282 if (num_true_literals % 2 == 0) {
283 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
285 context_->UpdateRuleStats(
"bool_xor: always true");
286 return RemoveConstraint(ct);
290 if (num_true_literals % 2 == 1) {
291 return context_->NotifyThatModelIsUnsat(
"bool_xor: always false");
293 context_->UpdateRuleStats(
"bool_xor: always true");
294 return RemoveConstraint(ct);
297 if (num_true_literals % 2 == 0) {
298 if (!context_->StoreBooleanEqualityRelation(a,
NegatedRef(
b))) {
302 if (!context_->StoreBooleanEqualityRelation(a,
b)) {
306 context_->UpdateNewConstraintsVariableUsage();
307 context_->UpdateRuleStats(
"bool_xor: two active literals");
308 return RemoveConstraint(ct);
311 if (num_true_literals % 2 == 1) {
312 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
313 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
315 if (num_true_literals > 1) {
316 context_->UpdateRuleStats(
"bool_xor: remove even number of true literals");
319 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
323bool CpModelPresolver::PresolveBoolOr(ConstraintProto* ct) {
324 if (context_->ModelIsUnsat())
return false;
329 context_->UpdateRuleStats(
"bool_or: removed enforcement literal");
330 for (
const int literal : ct->enforcement_literal()) {
331 ct->mutable_bool_or()->add_literals(
NegatedRef(literal));
333 ct->clear_enforcement_literal();
341 bool changed =
false;
342 context_->tmp_literals.clear();
343 context_->tmp_literal_set.clear();
344 for (
const int literal : ct->bool_or().literals()) {
345 if (context_->LiteralIsFalse(literal)) {
349 if (context_->LiteralIsTrue(literal)) {
350 context_->UpdateRuleStats(
"bool_or: always true");
351 return RemoveConstraint(ct);
356 if (context_->VariableIsUniqueAndRemovable(literal)) {
357 context_->UpdateRuleStats(
"bool_or: singleton");
358 if (!context_->SetLiteralToTrue(literal))
return true;
359 return RemoveConstraint(ct);
361 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
362 context_->UpdateRuleStats(
"bool_or: always true");
363 return RemoveConstraint(ct);
366 if (context_->tmp_literal_set.contains(literal)) {
369 context_->tmp_literal_set.insert(literal);
370 context_->tmp_literals.push_back(literal);
373 context_->tmp_literal_set.clear();
375 if (context_->tmp_literals.empty()) {
376 context_->UpdateRuleStats(
"bool_or: empty");
377 return context_->NotifyThatModelIsUnsat();
379 if (context_->tmp_literals.size() == 1) {
380 context_->UpdateRuleStats(
"bool_or: only one literal");
381 if (!context_->SetLiteralToTrue(context_->tmp_literals[0]))
return true;
382 return RemoveConstraint(ct);
384 if (context_->tmp_literals.size() == 2) {
387 context_->UpdateRuleStats(
"bool_or: implications");
388 ct->add_enforcement_literal(
NegatedRef(context_->tmp_literals[0]));
389 ct->mutable_bool_and()->add_literals(context_->tmp_literals[1]);
394 context_->UpdateRuleStats(
"bool_or: fixed literals");
395 ct->mutable_bool_or()->mutable_literals()->Clear();
396 for (
const int lit : context_->tmp_literals) {
397 ct->mutable_bool_or()->add_literals(lit);
405ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
406 ConstraintProto* ct) {
409 ct->mutable_bool_or()->clear_literals();
410 for (
const int lit : ct->enforcement_literal()) {
411 ct->mutable_bool_or()->add_literals(
NegatedRef(lit));
413 ct->clear_enforcement_literal();
417 return context_->NotifyThatModelIsUnsat();
421bool CpModelPresolver::PresolveBoolAnd(ConstraintProto* ct) {
422 if (context_->ModelIsUnsat())
return false;
425 context_->UpdateRuleStats(
"bool_and: non-reified.");
426 for (
const int literal : ct->bool_and().literals()) {
427 if (!context_->SetLiteralToTrue(literal))
return true;
429 return RemoveConstraint(ct);
432 bool changed =
false;
433 context_->tmp_literals.clear();
434 context_->tmp_literal_set.clear();
435 const absl::flat_hash_set<int> enforcement_literals_set(
436 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
437 for (
const int literal : ct->bool_and().literals()) {
438 if (context_->LiteralIsFalse(literal)) {
439 context_->UpdateRuleStats(
"bool_and: always false");
440 return MarkConstraintAsFalse(ct);
442 if (context_->LiteralIsTrue(literal)) {
446 if (enforcement_literals_set.contains(literal)) {
447 context_->UpdateRuleStats(
"bool_and: x => x");
451 if (enforcement_literals_set.contains(
NegatedRef(literal))) {
452 context_->UpdateRuleStats(
"bool_and: x => not x");
453 return MarkConstraintAsFalse(ct);
455 if (context_->VariableIsUniqueAndRemovable(literal)) {
458 context_->UpdateRuleStats(
459 "bool_and: setting unused literal in rhs to true");
460 if (!context_->SetLiteralToTrue(literal))
return true;
464 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
465 context_->UpdateRuleStats(
"bool_and: cannot be enforced");
466 return MarkConstraintAsFalse(ct);
469 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
471 context_->tmp_literals.push_back(literal);
474 context_->UpdateRuleStats(
"bool_and: removed duplicate literal");
481 if (context_->tmp_literals.empty())
return RemoveConstraint(ct);
484 ct->mutable_bool_and()->mutable_literals()->Clear();
485 for (
const int lit : context_->tmp_literals) {
486 ct->mutable_bool_and()->add_literals(lit);
488 context_->UpdateRuleStats(
"bool_and: fixed literals");
495 if (ct->enforcement_literal().size() == 1 &&
496 ct->bool_and().literals().size() == 1) {
497 const int enforcement = ct->enforcement_literal(0);
498 if (context_->VariableWithCostIsUniqueAndRemovable(enforcement)) {
500 int64_t obj_coeff = context_->ObjectiveMap().at(var);
505 context_->UpdateRuleStats(
"bool_and: dual equality.");
510 const int implied_literal = ct->bool_and().literals(0);
511 solution_crush_.SetLiteralToValueIf(enforcement,
true, implied_literal);
512 if (!context_->StoreBooleanEqualityRelation(enforcement,
523bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto* ct) {
524 bool is_at_most_one = ct->constraint_case() == ConstraintProto::kAtMostOne;
525 const std::string name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
526 auto* literals = is_at_most_one
527 ? ct->mutable_at_most_one()->mutable_literals()
528 : ct->mutable_exactly_one()->mutable_literals();
532 std::sort(literals->begin(), literals->end());
535 context_->tmp_literal_set.clear();
536 for (
const int literal : *literals) {
537 const auto [_, inserted] = context_->tmp_literal_set.insert(literal);
539 if (!context_->SetLiteralToFalse(literal))
return false;
540 context_->UpdateRuleStats(absl::StrCat(name,
"duplicate literals"));
542 if (context_->tmp_literal_set.contains(
NegatedRef(literal))) {
543 int num_positive = 0;
544 int num_negative = 0;
545 for (
const int other : *literals) {
547 if (!context_->SetLiteralToFalse(other))
return false;
548 context_->UpdateRuleStats(absl::StrCat(name,
"x and not(x)"));
550 if (other == literal) {
560 if (num_positive > 1 && !context_->SetLiteralToFalse(literal)) {
563 if (num_negative > 1 && !context_->SetLiteralToTrue(literal)) {
566 return RemoveConstraint(ct);
572 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
575 bool changed =
false;
576 context_->tmp_literals.clear();
577 for (
const int literal : *literals) {
578 if (context_->LiteralIsTrue(literal)) {
579 context_->UpdateRuleStats(absl::StrCat(name,
"satisfied"));
580 for (
const int other : *literals) {
581 if (other != literal) {
582 if (!context_->SetLiteralToFalse(other))
return false;
585 return RemoveConstraint(ct);
588 if (context_->LiteralIsFalse(literal)) {
594 if (context_->VariableIsUniqueAndRemovable(literal)) {
595 singleton_literal_with_cost.push_back({literal, 0});
598 if (context_->VariableWithCostIsUniqueAndRemovable(literal)) {
599 const auto it = context_->ObjectiveMap().find(
PositiveRef(literal));
600 DCHECK(it != context_->ObjectiveMap().end());
602 singleton_literal_with_cost.push_back({literal, it->second});
606 singleton_literal_with_cost.push_back({literal, -it->second});
611 context_->tmp_literals.push_back(literal);
614 bool transform_to_at_most_one =
false;
615 if (!singleton_literal_with_cost.empty()) {
619 if (singleton_literal_with_cost.size() > 1) {
621 singleton_literal_with_cost.begin(),
622 singleton_literal_with_cost.end(),
623 [](
const std::pair<int, int64_t>& a,
624 const std::pair<int, int64_t>&
b) { return a.second < b.second; });
625 for (
int i = 1;
i < singleton_literal_with_cost.size(); ++
i) {
626 context_->UpdateRuleStats(
"at_most_one: dominated singleton");
627 if (!context_->SetLiteralToFalse(
628 singleton_literal_with_cost[
i].first)) {
632 singleton_literal_with_cost.resize(1);
635 const int literal = singleton_literal_with_cost[0].first;
636 const int64_t literal_cost = singleton_literal_with_cost[0].second;
637 if (is_at_most_one && literal_cost >= 0) {
639 context_->UpdateRuleStats(
"at_most_one: singleton");
640 if (!context_->SetLiteralToFalse(literal))
return false;
641 }
else if (context_->ShiftCostInExactlyOne(*literals, literal_cost)) {
648 DCHECK(!context_->ObjectiveMap().contains(
PositiveRef(literal)));
650 if (!is_at_most_one) transform_to_at_most_one =
true;
651 is_at_most_one =
true;
653 context_->UpdateRuleStats(
"exactly_one: singleton");
654 context_->MarkVariableAsRemoved(
PositiveRef(literal));
657 auto* mapping_exo = context_->NewMappingConstraint(__FILE__, __LINE__)
658 ->mutable_exactly_one();
659 for (
const int lit : context_->tmp_literals) {
660 mapping_exo->add_literals(lit);
662 mapping_exo->add_literals(literal);
666 if (!is_at_most_one && !transform_to_at_most_one &&
667 context_->ExploitExactlyOneInObjective(context_->tmp_literals)) {
668 context_->UpdateRuleStats(
"exactly_one: simplified objective");
671 if (transform_to_at_most_one) {
674 literals = ct->mutable_at_most_one()->mutable_literals();
678 for (
const int lit : context_->tmp_literals) {
681 context_->UpdateRuleStats(absl::StrCat(name,
"removed literals"));
686bool CpModelPresolver::PresolveAtMostOne(ConstraintProto* ct) {
687 if (context_->ModelIsUnsat())
return false;
690 const bool changed = PresolveAtMostOrExactlyOne(ct);
691 if (ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
694 const auto& literals = ct->at_most_one().literals();
695 if (literals.empty()) {
696 context_->UpdateRuleStats(
"at_most_one: empty or all false");
697 return RemoveConstraint(ct);
701 if (literals.size() == 1) {
702 context_->UpdateRuleStats(
"at_most_one: size one");
703 return RemoveConstraint(ct);
709bool CpModelPresolver::PresolveExactlyOne(ConstraintProto* ct) {
710 if (context_->ModelIsUnsat())
return false;
712 const bool changed = PresolveAtMostOrExactlyOne(ct);
713 if (ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
716 const auto& literals = ct->exactly_one().literals();
717 if (literals.empty()) {
718 return context_->NotifyThatModelIsUnsat(
"exactly_one: empty or all false");
722 if (literals.size() == 1) {
723 context_->UpdateRuleStats(
"exactly_one: size one");
724 if (!context_->SetLiteralToTrue(literals[0]))
return false;
725 return RemoveConstraint(ct);
729 if (literals.size() == 2) {
730 context_->UpdateRuleStats(
"exactly_one: size two");
731 if (!context_->StoreBooleanEqualityRelation(literals[0],
735 return RemoveConstraint(ct);
741bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto& ct,
742 LinearArgumentProto* proto) {
743 if (context_->ModelIsUnsat())
return false;
746 bool changed = CanonicalizeLinearExpression(ct, proto->mutable_target());
747 for (LinearExpressionProto& exp : *(proto->mutable_exprs())) {
748 changed |= CanonicalizeLinearExpression(ct, &exp);
755bool CpModelPresolver::DivideLinMaxByGcd(
int c, ConstraintProto* ct) {
756 LinearArgumentProto* lin_max = ct->mutable_lin_max();
760 for (
const LinearExpressionProto& expr : lin_max->exprs()) {
764 if (gcd <= 1)
return true;
768 const LinearExpressionProto& target = lin_max->target();
769 const int64_t old_gcd = gcd;
771 if (gcd != old_gcd) {
772 if (target.vars().empty()) {
773 return context_->NotifyThatModelIsUnsat(
"infeasible lin_max");
778 if (target.vars().size() == 1) {
780 context_->UpdateRuleStats(
"lin_max: canonicalize target using gcd");
781 if (!context_->CanonicalizeAffineVariable(
782 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
785 CanonicalizeLinearExpression(*ct, lin_max->mutable_target());
786 context_->UpdateConstraintVariableUsage(c);
789 context_->UpdateRuleStats(
790 "TODO lin_max: lhs not trivially divisible by rhs gcd");
793 if (gcd <= 1)
return true;
795 context_->UpdateRuleStats(
"lin_max: divising by gcd");
797 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
805int64_t EvaluateSingleVariableExpression(
const LinearExpressionProto& expr,
806 int var, int64_t value) {
807 int64_t result = expr.offset();
808 for (
int i = 0;
i < expr.vars().size(); ++
i) {
809 CHECK_EQ(expr.vars(
i), var);
810 result += expr.coeffs(
i) * value;
815template <
class ExpressionList>
816int GetFirstVar(ExpressionList exprs) {
817 for (
const LinearExpressionProto& expr : exprs) {
818 for (
const int var : expr.vars()) {
826bool IsAffineIntAbs(
const ConstraintProto& ct) {
827 if (ct.constraint_case() != ConstraintProto::kLinMax ||
828 ct.lin_max().exprs_size() != 2 || ct.lin_max().target().vars_size() > 1 ||
829 ct.lin_max().exprs(0).vars_size() != 1 ||
830 ct.lin_max().exprs(1).vars_size() != 1) {
834 const LinearArgumentProto& lin_max = ct.lin_max();
835 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
841 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
842 ? lin_max.exprs(0).coeffs(0)
843 : -lin_max.exprs(0).coeffs(0);
844 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
845 ? lin_max.exprs(1).coeffs(0)
846 : -lin_max.exprs(1).coeffs(0);
847 return left_coeff == -right_coeff;
852bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto* ct) {
854 const int unique_var = GetFirstVar(ct->lin_max().exprs());
856 const auto& lin_max = ct->lin_max();
857 const int num_exprs = lin_max.exprs_size();
858 const auto& target = lin_max.target();
859 std::vector<int> num_wins(num_exprs, 0);
860 std::vector<int64_t> reachable_target_values;
861 std::vector<int64_t> valid_variable_values;
862 std::vector<int64_t> tmp_values(num_exprs);
864 const bool target_has_same_unique_var =
865 target.vars_size() == 1 && target.vars(0) == unique_var;
867 CHECK_LE(context_->DomainOf(unique_var).Size(), 1000);
869 for (
const int64_t value : context_->DomainOf(unique_var).Values()) {
870 int64_t current_max = std::numeric_limits<int64_t>::min();
873 for (
int i = 0;
i < num_exprs; ++
i) {
875 EvaluateSingleVariableExpression(lin_max.exprs(
i), unique_var, value);
876 current_max = std::max(current_max, v);
881 if (!context_->DomainContains(target, current_max))
continue;
885 if (target_has_same_unique_var &&
886 EvaluateSingleVariableExpression(target, unique_var, value) !=
891 valid_variable_values.push_back(value);
892 reachable_target_values.push_back(current_max);
893 for (
int i = 0;
i < num_exprs; ++
i) {
894 DCHECK_LE(tmp_values[
i], current_max);
895 if (tmp_values[
i] == current_max) {
901 if (reachable_target_values.empty() || valid_variable_values.empty()) {
902 context_->UpdateRuleStats(
"lin_max: infeasible affine_max constraint");
903 return MarkConstraintAsFalse(ct);
907 bool reduced =
false;
908 if (!context_->IntersectDomainWith(
913 context_->UpdateRuleStats(
"lin_max: affine_max target domain reduced");
918 bool reduced =
false;
919 if (!context_->IntersectDomainWith(
924 context_->UpdateRuleStats(
925 "lin_max: unique affine_max var domain reduced");
930 for (
int i = 0;
i < num_exprs; ++
i) {
931 if (num_wins[
i] == valid_variable_values.size()) {
932 const LinearExpressionProto winner_expr = lin_max.exprs(
i);
933 ct->mutable_lin_max()->clear_exprs();
934 *ct->mutable_lin_max()->add_exprs() = winner_expr;
939 bool changed =
false;
940 if (ct->lin_max().exprs_size() > 1) {
942 for (
int i = 0;
i < num_exprs; ++
i) {
943 if (num_wins[
i] == 0)
continue;
944 *ct->mutable_lin_max()->mutable_exprs(new_size) = ct->lin_max().exprs(
i);
947 if (new_size < ct->lin_max().exprs_size()) {
948 context_->UpdateRuleStats(
"lin_max: removed affine_max exprs");
955 if (context_->IsFixed(target)) {
956 context_->UpdateRuleStats(
"lin_max: fixed affine_max target");
957 return RemoveConstraint(ct);
960 if (target_has_same_unique_var) {
961 context_->UpdateRuleStats(
"lin_max: target_affine(x) = max(affine_i(x))");
962 return RemoveConstraint(ct);
970 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
971 context_->MarkVariableAsRemoved(target.vars(0));
972 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
973 context_->UpdateRuleStats(
"lin_max: unused affine_max target");
974 return RemoveConstraint(ct);
980bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto* ct) {
981 const LinearExpressionProto& target = ct->lin_max().target();
986 int64_t infered_min = context_->MinOf(target);
987 int64_t infered_max = std::numeric_limits<int64_t>::min();
988 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
989 infered_min = std::max(infered_min, context_->MinOf(expr));
990 infered_max = std::max(infered_max, context_->MaxOf(expr));
993 if (target.vars().empty()) {
994 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
995 context_->UpdateRuleStats(
"lin_max: infeasible");
996 return MarkConstraintAsFalse(ct);
999 if (target.vars().size() <= 1) {
1001 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1002 rhs_domain = rhs_domain.UnionWith(
1003 context_->DomainSuperSetOf(expr).IntersectionWith(
1004 {infered_min, infered_max}));
1006 bool reduced =
false;
1007 if (!context_->IntersectDomainWith(target, rhs_domain, &reduced)) {
1011 context_->UpdateRuleStats(
"lin_max: target domain reduced");
1017 const int64_t target_min = context_->MinOf(target);
1018 bool changed =
false;
1025 bool has_greater_or_equal_to_target_min =
false;
1026 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1027 int index_to_keep = -1;
1028 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1029 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1030 if (context_->MinOf(expr) >= target_min) {
1031 const int64_t expr_max = context_->MaxOf(expr);
1032 if (expr_max > max_at_index_to_keep) {
1033 max_at_index_to_keep = expr_max;
1036 has_greater_or_equal_to_target_min =
true;
1041 for (
int i = 0;
i < ct->lin_max().exprs_size(); ++
i) {
1042 const LinearExpressionProto& expr = ct->lin_max().exprs(
i);
1043 const int64_t expr_max = context_->MaxOf(expr);
1046 if (expr_max < target_min)
continue;
1047 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1048 i != index_to_keep) {
1051 *ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1054 if (new_size < ct->lin_max().exprs_size()) {
1055 context_->UpdateRuleStats(
"lin_max: removed exprs");
1065bool CpModelPresolver::PresolveLinMax(
int c, ConstraintProto* ct) {
1066 if (context_->ModelIsUnsat())
return false;
1068 const LinearExpressionProto& target = ct->lin_max().target();
1071 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1073 for (
const LinearExpressionProto& e : ct->lin_max().exprs()) {
1075 LinearConstraintProto* prec =
1076 context_->working_model->add_constraints()->mutable_linear();
1077 prec->add_domain(0);
1078 prec->add_domain(std::numeric_limits<int64_t>::max());
1082 context_->UpdateRuleStats(
"lin_max: x = max(x, ...)");
1083 return RemoveConstraint(ct);
1087 const bool is_one_var_affine_max =
1089 ct->lin_max().target().vars_size() <= 1;
1090 bool unique_var_is_small_enough =
false;
1091 const bool is_int_abs = IsAffineIntAbs(*ct);
1093 if (is_one_var_affine_max) {
1094 const int unique_var = GetFirstVar(ct->lin_max().exprs());
1095 unique_var_is_small_enough = context_->DomainOf(unique_var).Size() <= 1000;
1101 if (is_one_var_affine_max && unique_var_is_small_enough) {
1102 changed = PropagateAndReduceAffineMax(ct);
1103 }
else if (is_int_abs) {
1104 changed = PropagateAndReduceIntAbs(ct);
1106 changed = PropagateAndReduceLinMax(ct);
1109 if (context_->ModelIsUnsat())
return false;
1110 if (ct->constraint_case() != ConstraintProto::kLinMax) {
1115 if (ct->lin_max().exprs().empty()) {
1116 context_->UpdateRuleStats(
"lin_max: no exprs");
1117 return MarkConstraintAsFalse(ct);
1122 if (ct->lin_max().exprs().size() == 1) {
1123 context_->UpdateRuleStats(
"lin_max: converted to equality");
1124 ConstraintProto* new_ct = context_->working_model->add_constraints();
1126 auto* arg = new_ct->mutable_linear();
1127 const LinearExpressionProto& a = ct->lin_max().target();
1128 const LinearExpressionProto&
b = ct->lin_max().exprs(0);
1129 for (
int i = 0;
i < a.vars().size(); ++
i) {
1130 arg->add_vars(a.vars(
i));
1131 arg->add_coeffs(a.coeffs(
i));
1133 for (
int i = 0;
i <
b.vars().size(); ++
i) {
1134 arg->add_vars(
b.vars(
i));
1135 arg->add_coeffs(-
b.coeffs(
i));
1137 arg->add_domain(
b.offset() - a.offset());
1138 arg->add_domain(
b.offset() - a.offset());
1139 context_->UpdateNewConstraintsVariableUsage();
1140 return RemoveConstraint(ct);
1143 if (!DivideLinMaxByGcd(c, ct))
return false;
1148 const int64_t target_min = context_->MinOf(target);
1149 const int64_t target_max = context_->MaxOf(target);
1152 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1153 const int64_t value_min = context_->MinOf(expr);
1154 bool modified =
false;
1155 if (!context_->IntersectDomainWith(expr, Domain(value_min, target_max),
1160 context_->UpdateRuleStats(
"lin_max: reduced expression domain.");
1162 const int64_t value_max = context_->MaxOf(expr);
1163 if (value_max > target_max) {
1164 context_->UpdateRuleStats(
"TODO lin_max: linear expression above max.");
1168 if (abort)
return changed;
1172 bool linear_target_domain_contains_max_domain =
false;
1174 int64_t infered_min = std::numeric_limits<int64_t>::min();
1175 int64_t infered_max = std::numeric_limits<int64_t>::min();
1176 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1177 infered_min = std::max(infered_min, context_->MinOf(expr));
1178 infered_max = std::max(infered_max, context_->MaxOf(expr));
1181 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1182 rhs_domain = rhs_domain.UnionWith(
1183 context_->DomainSuperSetOf(expr).IntersectionWith(
1184 {infered_min, infered_max}));
1190 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1191 const Domain target_domain =
1192 target.coeffs(0) == 1 ? context_->DomainOf(target.vars(0))
1193 : context_->DomainOf(target.vars(0)).Negation();
1194 linear_target_domain_contains_max_domain =
1195 rhs_domain.IsIncludedIn(target_domain);
1210 if (linear_target_domain_contains_max_domain) {
1211 const int target_var = target.vars(0);
1213 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1214 for (
const int var : expr.vars()) {
1215 if (var == target_var &&
1226 context_->UpdateRuleStats(
1227 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1228 linear_target_domain_contains_max_domain =
false;
1233 if (linear_target_domain_contains_max_domain &&
1234 context_->VariableIsUniqueAndRemovable(target.vars(0))) {
1235 context_->UpdateRuleStats(
"lin_max: unused affine target");
1236 context_->MarkVariableAsRemoved(target.vars(0));
1237 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1238 return RemoveConstraint(ct);
1243 if (linear_target_domain_contains_max_domain &&
1244 context_->VariableWithCostIsUniqueAndRemovable(target.vars(0)) &&
1245 (target.coeffs(0) > 0) ==
1246 (context_->ObjectiveCoeff(target.vars(0)) > 0)) {
1247 context_->UpdateRuleStats(
"lin_max: rewrite with precedences");
1248 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1249 LinearConstraintProto* prec =
1250 context_->working_model->add_constraints()->mutable_linear();
1251 prec->add_domain(0);
1252 prec->add_domain(std::numeric_limits<int64_t>::max());
1256 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1257 return RemoveConstraint(ct);
1261 if (target_min == target_max) {
1262 bool all_booleans =
true;
1263 std::vector<int> literals;
1264 const int64_t fixed_target = target_min;
1265 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1266 const int64_t value_min = context_->MinOf(expr);
1267 const int64_t value_max = context_->MaxOf(expr);
1268 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
1269 if (value_max < fixed_target)
continue;
1271 if (value_min == value_max && value_max == fixed_target) {
1272 context_->UpdateRuleStats(
"lin_max: always satisfied");
1273 return RemoveConstraint(ct);
1275 if (context_->ExpressionIsAffineBoolean(expr)) {
1276 CHECK_EQ(value_max, fixed_target);
1277 literals.push_back(context_->LiteralForExpressionMax(expr));
1279 all_booleans =
false;
1283 if (literals.empty()) {
1284 return MarkConstraintAsFalse(ct);
1288 context_->UpdateRuleStats(
"lin_max: fixed target and all booleans");
1289 for (
const int lit : literals) {
1290 ct->mutable_bool_or()->add_literals(lit);
1297 changed |= PresolveLinMaxWhenAllBoolean(ct);
1302bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto* ct) {
1303 if (context_->ModelIsUnsat())
return false;
1306 const LinearExpressionProto& target = ct->lin_max().target();
1307 if (!context_->ExpressionIsAffineBoolean(target))
return false;
1309 const int64_t target_min = context_->MinOf(target);
1310 const int64_t target_max = context_->MaxOf(target);
1311 const int target_ref = context_->LiteralForExpressionMax(target);
1313 bool min_is_reachable =
false;
1314 std::vector<int> min_literals;
1315 std::vector<int> literals_above_min;
1316 std::vector<int> max_literals;
1318 for (
const LinearExpressionProto& expr : ct->lin_max().exprs()) {
1319 if (!context_->ExpressionIsAffineBoolean(expr))
return false;
1320 const int64_t value_min = context_->MinOf(expr);
1321 const int64_t value_max = context_->MaxOf(expr);
1322 const int ref = context_->LiteralForExpressionMax(expr);
1326 if (value_min > target_min) {
1327 context_->UpdateRuleStats(
"lin_max: fix target");
1328 (void)context_->SetLiteralToTrue(target_ref);
1331 if (value_max > target_max) {
1332 context_->UpdateRuleStats(
"lin_max: fix bool expr");
1333 (void)context_->SetLiteralToFalse(ref);
1338 if (value_min == value_max) {
1339 if (value_min == target_min) min_is_reachable =
true;
1343 CHECK_LE(value_min, target_min);
1344 if (value_min == target_min) {
1348 CHECK_LE(value_max, target_max);
1349 if (value_max == target_max) {
1350 max_literals.push_back(ref);
1351 literals_above_min.push_back(ref);
1352 }
else if (value_max > target_min) {
1353 literals_above_min.push_back(ref);
1354 }
else if (value_max == target_min) {
1355 min_literals.push_back(ref);
1359 context_->UpdateRuleStats(
"lin_max: all Booleans.");
1362 ConstraintProto* clause = context_->working_model->add_constraints();
1363 clause->add_enforcement_literal(target_ref);
1364 clause->mutable_bool_or();
1365 for (
const int lit : max_literals) {
1366 clause->mutable_bool_or()->add_literals(lit);
1370 for (
const int lit : literals_above_min) {
1371 context_->AddImplication(lit, target_ref);
1374 if (!min_is_reachable) {
1376 ConstraintProto* clause = context_->working_model->add_constraints();
1377 clause->add_enforcement_literal(
NegatedRef(target_ref));
1378 clause->mutable_bool_or();
1379 for (
const int lit : min_literals) {
1380 clause->mutable_bool_or()->add_literals(lit);
1384 context_->UpdateNewConstraintsVariableUsage();
1385 return RemoveConstraint(ct);
1390bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto* ct) {
1391 CHECK_EQ(ct->enforcement_literal_size(), 0);
1392 if (context_->ModelIsUnsat())
return false;
1393 const LinearExpressionProto& target_expr = ct->lin_max().target();
1394 const LinearExpressionProto& expr = ct->lin_max().exprs(0);
1395 DCHECK_EQ(expr.vars_size(), 1);
1399 const Domain expr_domain = context_->DomainSuperSetOf(expr);
1400 const Domain new_target_domain =
1401 expr_domain.UnionWith(expr_domain.Negation())
1402 .IntersectionWith({0, std::numeric_limits<int64_t>::max()});
1403 bool target_domain_modified =
false;
1404 if (!context_->IntersectDomainWith(target_expr, new_target_domain,
1405 &target_domain_modified)) {
1408 if (expr_domain.IsFixed()) {
1409 context_->UpdateRuleStats(
"lin_max: fixed expression in int_abs");
1410 return RemoveConstraint(ct);
1412 if (target_domain_modified) {
1413 context_->UpdateRuleStats(
"lin_max: propagate domain from x to abs(x)");
1419 const Domain target_domain =
1420 context_->DomainSuperSetOf(target_expr)
1421 .IntersectionWith(Domain(0, std::numeric_limits<int64_t>::max()));
1422 const Domain new_expr_domain =
1423 target_domain.UnionWith(target_domain.Negation());
1424 bool expr_domain_modified =
false;
1425 if (!context_->IntersectDomainWith(expr, new_expr_domain,
1426 &expr_domain_modified)) {
1431 if (context_->IsFixed(target_expr)) {
1432 context_->UpdateRuleStats(
"lin_max: fixed abs target");
1433 return RemoveConstraint(ct);
1435 if (expr_domain_modified) {
1436 context_->UpdateRuleStats(
"lin_max: propagate domain from abs(x) to x");
1441 if (context_->MinOf(expr) >= 0) {
1442 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1443 ConstraintProto* new_ct = context_->working_model->add_constraints();
1444 new_ct->set_name(ct->name());
1445 auto* arg = new_ct->mutable_linear();
1450 CanonicalizeLinear(new_ct);
1451 context_->UpdateNewConstraintsVariableUsage();
1452 return RemoveConstraint(ct);
1455 if (context_->MaxOf(expr) <= 0) {
1456 context_->UpdateRuleStats(
"lin_max: converted abs to equality");
1457 ConstraintProto* new_ct = context_->working_model->add_constraints();
1458 new_ct->set_name(ct->name());
1459 auto* arg = new_ct->mutable_linear();
1464 CanonicalizeLinear(new_ct);
1465 context_->UpdateNewConstraintsVariableUsage();
1466 return RemoveConstraint(ct);
1474 context_->VariableIsUniqueAndRemovable(target_expr.vars(0))) {
1475 context_->MarkVariableAsRemoved(target_expr.vars(0));
1476 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
1477 context_->UpdateRuleStats(
"lin_max: unused abs target");
1478 return RemoveConstraint(ct);
1486 if (expr.exprs().size() == 2) {
1487 const LinearExpressionProto& expr0 = expr.exprs(0);
1488 const LinearExpressionProto& expr1 = expr.exprs(1);
1492 if (expr0.vars().size() == 1 && expr1.vars().size() == 1 &&
1493 expr0.vars(0) == expr1.vars(0)) {
1494 return context.
DomainOf(expr0.vars(0))
1501 for (
const LinearExpressionProto& expr : expr.exprs()) {
1508bool CpModelPresolver::PresolveIntProd(ConstraintProto* ct) {
1513 bool domain_modified =
false;
1517 &domain_modified)) {
1528 std::abs(ct->int_prod().target().coeffs(0)) == 1) {
1529 const LinearExpressionProto& target = ct->int_prod().target();
1530 if (!absl::c_any_of(ct->int_prod().exprs(),
1531 [&target](
const LinearExpressionProto& expr) {
1532 return absl::c_linear_search(expr.vars(),
1535 const Domain target_domain =
1537 .AdditionWith(context_->
DomainOf(target.vars(0)));
1542 return RemoveConstraint(ct);
1549 int64_t constant_factor = 1;
1551 bool changed =
false;
1552 LinearArgumentProto* proto = ct->mutable_int_prod();
1553 for (
int i = 0;
i < ct->int_prod().exprs().size(); ++
i) {
1554 LinearExpressionProto expr = ct->int_prod().exprs(
i);
1555 if (context_->IsFixed(expr)) {
1556 const int64_t expr_value = context_->FixedValue(expr);
1557 constant_factor =
CapProd(constant_factor, expr_value);
1558 context_->UpdateRuleStats(
"int_prod: removed constant expressions.");
1563 constant_factor =
CapProd(constant_factor, expr_divisor);
1564 *proto->mutable_exprs(new_size++) = expr;
1567 proto->mutable_exprs()->erase(proto->mutable_exprs()->begin() + new_size,
1568 proto->mutable_exprs()->end());
1570 if (ct->int_prod().exprs().empty() || constant_factor == 0) {
1571 if (!context_->IntersectDomainWith(ct->int_prod().target(),
1572 Domain(constant_factor))) {
1575 context_->UpdateRuleStats(
"int_prod: constant product");
1576 return RemoveConstraint(ct);
1580 if (context_->IsFixed(ct->int_prod().target()) &&
1581 context_->FixedValue(ct->int_prod().target()) == 0 &&
1582 constant_factor != 1) {
1583 context_->UpdateRuleStats(
"int_prod: simplify by constant factor");
1584 constant_factor = 1;
1590 context_->UpdateRuleStats(
"int_prod: overflow if non zero");
1591 if (!context_->IntersectDomainWith(ct->int_prod().target(), Domain(0))) {
1594 constant_factor = 1;
1598 if (ct->int_prod().exprs().size() == 1) {
1599 LinearExpressionProto*
const target =
1600 ct->mutable_int_prod()->mutable_target();
1601 LinearConstraintProto*
const lin =
1602 context_->working_model->add_constraints()->mutable_linear();
1604 if (context_->IsFixed(*target)) {
1605 int64_t target_value = context_->FixedValue(*target);
1606 if (target_value % constant_factor != 0) {
1607 return context_->NotifyThatModelIsUnsat(
1608 "int_prod: product incompatible with fixed target");
1611 lin->add_domain(target_value / constant_factor);
1612 lin->add_domain(target_value / constant_factor);
1614 context_->UpdateNewConstraintsVariableUsage();
1615 context_->UpdateRuleStats(
"int_prod: expression is constant.");
1616 return RemoveConstraint(ct);
1623 std::gcd(
static_cast<uint64_t
>(std::abs(constant_factor)),
1624 static_cast<uint64_t
>(std::abs(target_divisor)));
1626 constant_factor /= gcd;
1634 ct->int_prod().target(), 1, lin) ||
1636 ct->int_prod().exprs(0), -constant_factor, lin);
1641 lin->coeffs(), lin->domain(0))) {
1642 context_->working_model->mutable_constraints()->RemoveLast();
1646 context_->UpdateNewConstraintsVariableUsage();
1647 context_->UpdateRuleStats(
"int_prod: linearize product by constant.");
1648 return RemoveConstraint(ct);
1652 if (constant_factor != 1) {
1660 const LinearExpressionProto old_target = ct->int_prod().target();
1661 if (!context_->IsFixed(old_target)) {
1662 if (
CapProd(constant_factor, std::max(context_->MaxOf(old_target),
1663 -context_->MinOf(old_target))) >=
1664 std::numeric_limits<int64_t>::max() / 2) {
1666 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1667 context_->UpdateRuleStats(
1668 "int_prod: overflow prevented creating a affine relation.");
1671 const int ref = old_target.vars(0);
1672 const int64_t coeff = old_target.coeffs(0);
1673 const int64_t offset = old_target.offset();
1674 if (!context_->CanonicalizeAffineVariable(ref, coeff, constant_factor,
1678 if (context_->IsFixed(ref)) {
1684 if (context_->IsFixed(old_target)) {
1685 const int64_t target_value = context_->FixedValue(old_target);
1686 if (target_value % constant_factor != 0) {
1687 return context_->NotifyThatModelIsUnsat(
1688 "int_prod: constant factor does not divide constant target");
1691 proto->clear_target();
1692 proto->mutable_target()->set_offset(target_value / constant_factor);
1693 context_->UpdateRuleStats(
1694 "int_prod: divide product and fixed target by constant factor");
1697 const AffineRelation::Relation r =
1698 context_->GetAffineRelation(old_target.vars(0));
1699 const absl::int128 temp_coeff =
1700 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1701 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1702 const absl::int128 temp_offset =
1703 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1704 absl::int128(old_target.offset());
1705 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1706 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1707 const absl::int128 new_offset =
1708 temp_offset / absl::int128(constant_factor);
1716 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1717 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1718 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1719 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1720 return context_->NotifyThatModelIsUnsat(
1721 "int_prod: overflow during simplification.");
1725 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1726 proto->mutable_target()->set_vars(0, r.representative);
1727 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1728 context_->UpdateRuleStats(
"int_prod: divide product by constant factor");
1735 const bool is_square = ct->int_prod().exprs_size() == 2 &&
1737 ct->int_prod().exprs(0), ct->int_prod().exprs(1));
1738 if (!context_->IntersectDomainWith(ct->int_prod().target(), implied_domain,
1739 &domain_modified)) {
1742 if (domain_modified) {
1743 context_->UpdateRuleStats(absl::StrCat(
1744 is_square ?
"int_square" :
"int_prod",
": reduced target domain."));
1749 const int64_t target_max = context_->MaxOf(ct->int_prod().target());
1750 DCHECK_GE(target_max, 0);
1752 bool expr_reduced =
false;
1753 if (!context_->IntersectDomainWith(ct->int_prod().exprs(0),
1754 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1758 context_->UpdateRuleStats(
"int_square: reduced expr domain.");
1762 if (ct->int_prod().exprs_size() == 2) {
1763 LinearExpressionProto a = ct->int_prod().exprs(0);
1764 LinearExpressionProto
b = ct->int_prod().exprs(1);
1765 const LinearExpressionProto product = ct->int_prod().target();
1769 if (!context_->IntersectDomainWith(product, Domain(0, 1))) {
1772 context_->UpdateRuleStats(
"int_square: fix variable to zero or one.");
1773 return RemoveConstraint(ct);
1777 if (ct->int_prod().exprs().size() == 2) {
1778 const auto is_boolean_affine =
1779 [context = context_](
const LinearExpressionProto& expr) {
1780 return expr.vars().size() == 1 && context->MinOf(expr.vars(0)) == 0 &&
1781 context->MaxOf(expr.vars(0)) == 1;
1783 const LinearExpressionProto* boolean_linear =
nullptr;
1784 const LinearExpressionProto* other_linear =
nullptr;
1785 if (is_boolean_affine(ct->int_prod().exprs(0))) {
1786 boolean_linear = &ct->int_prod().exprs(0);
1787 other_linear = &ct->int_prod().exprs(1);
1788 }
else if (is_boolean_affine(ct->int_prod().exprs(1))) {
1789 boolean_linear = &ct->int_prod().exprs(1);
1790 other_linear = &ct->int_prod().exprs(0);
1792 if (boolean_linear) {
1799 ConstraintProto* constraint_for_false =
1800 context_->working_model->add_constraints();
1801 ConstraintProto* constraint_for_true =
1802 context_->working_model->add_constraints();
1803 constraint_for_true->add_enforcement_literal(boolean_linear->vars(0));
1804 constraint_for_false->add_enforcement_literal(
1806 LinearConstraintProto* linear_for_false =
1807 constraint_for_false->mutable_linear();
1808 LinearConstraintProto* linear_for_true =
1809 constraint_for_true->mutable_linear();
1811 linear_for_false->add_domain(0);
1812 linear_for_false->add_domain(0);
1814 *other_linear, boolean_linear->offset(), linear_for_false);
1818 linear_for_true->add_domain(0);
1819 linear_for_true->add_domain(0);
1821 *other_linear, boolean_linear->offset() + boolean_linear->coeffs(0),
1825 context_->CanonicalizeLinearConstraint(constraint_for_false);
1826 context_->CanonicalizeLinearConstraint(constraint_for_true);
1828 linear_for_false->vars(),
1829 linear_for_false->coeffs()) ||
1831 linear_for_true->vars(),
1832 linear_for_true->coeffs())) {
1833 context_->working_model->mutable_constraints()->RemoveLast();
1834 context_->working_model->mutable_constraints()->RemoveLast();
1836 context_->UpdateRuleStats(
"int_prod: boolean affine term");
1837 context_->UpdateNewConstraintsVariableUsage();
1838 return RemoveConstraint(ct);
1844 const LinearExpressionProto target_expr = ct->int_prod().target();
1846 if (!context_->ExpressionIsALiteral(target_expr, &target)) {
1849 std::vector<int> literals;
1850 for (
const LinearExpressionProto& expr : ct->int_prod().exprs()) {
1852 if (!context_->ExpressionIsALiteral(expr, &lit)) {
1855 literals.push_back(lit);
1859 context_->UpdateRuleStats(
"int_prod: all Boolean.");
1861 ConstraintProto* new_ct = context_->working_model->add_constraints();
1862 new_ct->add_enforcement_literal(target);
1863 auto* arg = new_ct->mutable_bool_and();
1864 for (
const int lit : literals) {
1865 arg->add_literals(lit);
1869 ConstraintProto* new_ct = context_->working_model->add_constraints();
1870 auto* arg = new_ct->mutable_bool_or();
1871 arg->add_literals(target);
1872 for (
const int lit : literals) {
1876 context_->UpdateNewConstraintsVariableUsage();
1877 return RemoveConstraint(ct);
1880bool CpModelPresolver::PresolveIntDiv(
int c, ConstraintProto* ct) {
1881 if (context_->ModelIsUnsat())
return false;
1883 const LinearExpressionProto target = ct->int_div().target();
1884 const LinearExpressionProto expr = ct->int_div().exprs(0);
1885 const LinearExpressionProto div = ct->int_div().exprs(1);
1888 if (!context_->IntersectDomainWith(target, Domain(1))) {
1891 context_->UpdateRuleStats(
"int_div: y = x / x");
1892 return RemoveConstraint(ct);
1894 if (!context_->IntersectDomainWith(target, Domain(-1))) {
1897 context_->UpdateRuleStats(
"int_div: y = - x / x");
1898 return RemoveConstraint(ct);
1904 if (ct->enforcement_literal().empty() &&
1905 context_->ConstraintToVars(c).size() == 1) {
1906 const int var = context_->ConstraintToVars(c)[0];
1907 if (context_->DomainOf(var).Size() >= 100) {
1908 context_->UpdateRuleStats(
1909 "TODO int_div: single variable with large domain");
1911 std::vector<int64_t> possible_values;
1912 for (
const int64_t v : context_->DomainOf(var).Values()) {
1913 const int64_t target_v =
1914 EvaluateSingleVariableExpression(target, var, v);
1915 const int64_t expr_v = EvaluateSingleVariableExpression(expr, var, v);
1916 const int64_t div_v = EvaluateSingleVariableExpression(div, var, v);
1917 if (div_v == 0)
continue;
1918 if (target_v == expr_v / div_v) {
1919 possible_values.push_back(v);
1922 (void)context_->IntersectDomainWith(var,
1924 context_->UpdateRuleStats(
"int_div: single variable");
1925 return RemoveConstraint(ct);
1930 if (!context_->IsFixed(div))
return false;
1932 const int64_t divisor = context_->FixedValue(div);
1935 if (divisor == 1 || divisor == -1) {
1936 LinearConstraintProto*
const lin =
1937 context_->working_model->add_constraints()->mutable_linear();
1942 context_->UpdateNewConstraintsVariableUsage();
1943 context_->UpdateRuleStats(
"int_div: rewrite to equality");
1944 return RemoveConstraint(ct);
1949 bool domain_modified =
false;
1950 const Domain target_implied_domain =
1951 context_->DomainSuperSetOf(expr).DivisionBy(divisor);
1953 if (!context_->IntersectDomainWith(target, target_implied_domain,
1954 &domain_modified)) {
1957 if (domain_modified) {
1959 if (target_implied_domain.IsFixed()) {
1960 context_->UpdateRuleStats(
1961 "int_div: target has been fixed by propagating X / cte");
1963 context_->UpdateRuleStats(
1964 "int_div: updated domain of target in target = X / cte");
1970 if (context_->IsFixed(target) &&
1972 1 + std::abs(context_->FixedValue(target)))) !=
1973 std::numeric_limits<int64_t>::max()) {
1974 int64_t t = context_->FixedValue(target);
1975 int64_t d = divisor;
1981 const Domain expr_implied_domain =
1983 ? Domain(t * d, (t + 1) * d - 1)
1984 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
1985 bool domain_modified =
false;
1986 if (!context_->IntersectDomainWith(expr, expr_implied_domain,
1987 &domain_modified)) {
1990 if (domain_modified) {
1991 context_->UpdateRuleStats(
"int_div: target and divisor are fixed");
1993 context_->UpdateRuleStats(
"int_div: always true");
1995 return RemoveConstraint(ct);
2001 if (context_->MinOf(target) >= 0 && context_->MinOf(expr) >= 0 &&
2003 CapProd(divisor, context_->MaxOf(target)) !=
2004 std::numeric_limits<int64_t>::max()) {
2005 LinearConstraintProto*
const lin =
2006 context_->working_model->add_constraints()->mutable_linear();
2008 lin->add_domain(divisor - 1);
2011 context_->UpdateNewConstraintsVariableUsage();
2012 context_->UpdateRuleStats(
2013 "int_div: linearize positive division with a constant divisor");
2015 return RemoveConstraint(ct);
2023bool CpModelPresolver::PresolveIntMod(
int c, ConstraintProto* ct) {
2024 if (context_->ModelIsUnsat())
return false;
2027 const LinearExpressionProto target = ct->int_mod().target();
2028 const LinearExpressionProto expr = ct->int_mod().exprs(0);
2029 const LinearExpressionProto mod = ct->int_mod().exprs(1);
2031 if (context_->MinOf(target) > 0) {
2032 bool domain_changed =
false;
2033 if (!context_->IntersectDomainWith(
2034 expr, Domain(0, std::numeric_limits<int64_t>::max()),
2038 if (domain_changed) {
2039 context_->UpdateRuleStats(
2040 "int_mod: non negative target implies positive expression");
2044 if (context_->MinOf(target) >= context_->MaxOf(mod) ||
2045 context_->MaxOf(target) <= -context_->MaxOf(mod)) {
2046 return context_->NotifyThatModelIsUnsat(
2047 "int_mod: incompatible target and mod");
2050 if (context_->MaxOf(target) < 0) {
2051 bool domain_changed =
false;
2052 if (!context_->IntersectDomainWith(
2053 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
2057 if (domain_changed) {
2058 context_->UpdateRuleStats(
2059 "int_mod: non positive target implies negative expression");
2063 if (context_->IsFixed(target) && context_->IsFixed(mod) &&
2064 context_->FixedValue(mod) > 1 && ct->enforcement_literal().empty() &&
2065 expr.vars().size() == 1) {
2067 const int64_t fixed_mod = context_->FixedValue(mod);
2068 const int64_t fixed_target = context_->FixedValue(target);
2070 if (!context_->CanonicalizeAffineVariable(expr.vars(0), expr.coeffs(0),
2072 fixed_target - expr.offset())) {
2076 context_->UpdateRuleStats(
"int_mod: fixed mod and target");
2077 return RemoveConstraint(ct);
2080 bool domain_changed =
false;
2081 if (!context_->IntersectDomainWith(
2083 context_->DomainSuperSetOf(expr).PositiveModuloBySuperset(
2084 context_->DomainSuperSetOf(mod)),
2089 if (domain_changed) {
2090 context_->UpdateRuleStats(
"int_mod: reduce target domain");
2098 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
2099 context_->DomainOf(expr.vars(0)).Size() < 100 && context_->IsFixed(mod) &&
2100 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
2101 target.vars(0) != expr.vars(0)) {
2102 const int64_t fixed_mod = context_->FixedValue(mod);
2103 std::vector<int64_t> values;
2104 const Domain dom = context_->DomainOf(target.vars(0));
2105 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
2106 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
2107 const int64_t target_term = rhs - target.offset();
2108 if (target_term % target.coeffs(0) != 0)
continue;
2109 if (dom.
Contains(target_term / target.coeffs(0))) {
2110 values.push_back(v);
2114 context_->UpdateRuleStats(
"int_mod: remove singleton target");
2115 if (!context_->IntersectDomainWith(expr.vars(0),
2119 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2121 context_->UpdateConstraintVariableUsage(c);
2122 context_->MarkVariableAsRemoved(target.vars(0));
2131bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto* ct) {
2132 bool changed =
false;
2137 if (ct->constraint_case() == ConstraintProto::kLinear) {
2138 for (
int& ref : *ct->mutable_enforcement_literal()) {
2139 const int rep = this->context_->GetLiteralRepresentative(ref);
2150 bool work_to_do =
false;
2151 for (
const int var : context_->ConstraintToVars(c)) {
2152 const AffineRelation::Relation r = context_->GetAffineRelation(var);
2153 if (r.representative != var) {
2158 if (!work_to_do)
return false;
2162 [&changed,
this](
int* ref) {
2163 const int rep = this->context_->GetLiteralRepresentative(*ref);
2173bool CpModelPresolver::DivideLinearByGcd(ConstraintProto* ct) {
2174 if (context_->ModelIsUnsat())
return false;
2178 const int num_vars = ct->linear().vars().size();
2179 for (
int i = 0;
i < num_vars; ++
i) {
2180 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2181 gcd = std::gcd(gcd, magnitude);
2182 if (gcd == 1)
break;
2185 context_->UpdateRuleStats(
"linear: divide by GCD");
2186 for (
int i = 0;
i < num_vars; ++
i) {
2187 ct->mutable_linear()->set_coeffs(
i, ct->linear().coeffs(
i) / gcd);
2191 if (ct->linear().domain_size() == 0) {
2192 return MarkConstraintAsFalse(ct);
2198bool CpModelPresolver::CanonicalizeLinearExpression(
2199 const ConstraintProto& ct, LinearExpressionProto* exp) {
2200 return context_->CanonicalizeLinearExpression(ct.enforcement_literal(), exp);
2203bool CpModelPresolver::CanonicalizeLinear(ConstraintProto* ct) {
2204 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
2205 if (context_->ModelIsUnsat())
return false;
2207 if (ct->linear().domain().empty()) {
2208 context_->UpdateRuleStats(
"linear: no domain");
2209 return MarkConstraintAsFalse(ct);
2212 bool changed = context_->CanonicalizeLinearConstraint(ct);
2213 changed |= DivideLinearByGcd(ct);
2219 if (!ct->linear().coeffs().empty() && ct->linear().coeffs(0) < 0) {
2220 for (int64_t& ref_coeff : *ct->mutable_linear()->mutable_coeffs()) {
2221 ref_coeff = -ref_coeff;
2224 ct->mutable_linear());
2230bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto* ct) {
2231 if (ct->constraint_case() != ConstraintProto::kLinear ||
2232 context_->ModelIsUnsat()) {
2236 absl::btree_set<int> index_to_erase;
2237 const int num_vars = ct->linear().vars().size();
2243 for (
int i = 0;
i < num_vars; ++
i) {
2244 const int var = ct->linear().vars(
i);
2245 const int64_t coeff = ct->linear().coeffs(
i);
2247 if (context_->VariableIsUniqueAndRemovable(var)) {
2252 if (std::abs(coeff) != 1)
continue;
2255 const auto term_domain =
2256 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2257 if (!exact)
continue;
2260 const Domain new_rhs = rhs.AdditionWith(term_domain);
2261 if (new_rhs.NumIntervals() > 100)
continue;
2267 context_->UpdateRuleStats(
"linear: singleton column");
2268 index_to_erase.insert(
i);
2281 if (index_to_erase.empty()) {
2282 int num_singletons = 0;
2283 for (
const int var : ct->linear().vars()) {
2285 if (!context_->VariableWithCostIsUniqueAndRemovable(var) &&
2286 !context_->VariableIsUniqueAndRemovable(var)) {
2291 if (num_singletons == num_vars) {
2293 std::vector<Domain> domains;
2294 std::vector<int64_t> coeffs;
2295 std::vector<int64_t> costs;
2296 for (
int i = 0;
i < num_vars; ++
i) {
2297 const int var = ct->linear().vars(
i);
2299 domains.push_back(context_->DomainOf(var));
2300 coeffs.push_back(ct->linear().coeffs(
i));
2301 costs.push_back(context_->ObjectiveCoeff(var));
2303 BasicKnapsackSolver solver;
2304 const auto& result = solver.Solve(domains, coeffs, costs,
2306 if (!result.solved) {
2307 context_->UpdateRuleStats(
2308 "TODO independent linear: minimize single linear constraint");
2309 }
else if (result.infeasible) {
2310 context_->UpdateRuleStats(
2311 "independent linear: no DP solution to simple constraint");
2312 return MarkConstraintAsFalse(ct);
2314 if (ct->enforcement_literal().empty()) {
2316 context_->UpdateRuleStats(
"independent linear: solved by DP");
2317 for (
int i = 0;
i < num_vars; ++
i) {
2318 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2319 Domain(result.solution[
i]))) {
2323 return RemoveConstraint(ct);
2328 if (ct->enforcement_literal().size() == 1) {
2329 indicator = ct->enforcement_literal(0);
2332 context_->NewBoolVarWithConjunction(ct->enforcement_literal());
2333 auto* new_ct = context_->working_model->add_constraints();
2334 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2335 new_ct->mutable_bool_or()->add_literals(indicator);
2336 context_->UpdateNewConstraintsVariableUsage();
2338 for (
int i = 0;
i < num_vars; ++
i) {
2339 const int64_t best_value =
2340 costs[
i] > 0 ? domains[
i].Min() : domains[
i].Max();
2341 const int64_t other_value = result.solution[
i];
2342 if (best_value == other_value) {
2343 if (!context_->IntersectDomainWith(ct->linear().vars(
i),
2344 Domain(best_value))) {
2349 solution_crush_.SetVarToConditionalValue(
2350 ct->linear().vars(
i), {indicator}, other_value, best_value);
2352 if (!context_->StoreAffineRelation(ct->linear().vars(
i), indicator,
2353 other_value - best_value,
2358 if (!context_->StoreAffineRelation(
2360 best_value - other_value, other_value)) {
2365 context_->UpdateRuleStats(
2366 "independent linear: with enforcement, but solved by DP");
2367 return RemoveConstraint(ct);
2373 if (index_to_erase.empty()) {
2375 if (context_->params().presolve_substitution_level() <= 0)
return false;
2376 if (!ct->enforcement_literal().empty())
return false;
2380 if (rhs.
Min() != rhs.
Max())
return false;
2382 for (
int i = 0;
i < num_vars; ++
i) {
2383 const int var = ct->linear().vars(
i);
2384 const int64_t coeff = ct->linear().coeffs(
i);
2395 if (!context_->VariableWithCostIsUnique(var))
continue;
2396 DCHECK(context_->ObjectiveMap().contains(var));
2403 const int64_t objective_coeff = context_->ObjectiveMap().at(var);
2405 if (objective_coeff % coeff != 0)
continue;
2411 if (std::abs(objective_coeff) != 1)
continue;
2415 const auto term_domain =
2416 context_->DomainOf(var).MultiplicationBy(-coeff, &exact);
2417 if (!exact)
continue;
2418 const Domain new_rhs = rhs.AdditionWith(term_domain);
2419 if (new_rhs.NumIntervals() > 100)
continue;
2427 if (context_->ObjectiveMap().size() == 1) {
2430 if (!context_->RecomputeSingletonObjectiveDomain()) {
2435 if (context_->IsFixed(var))
continue;
2437 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2438 if (context_->ModelIsUnsat())
return true;
2442 context_->UpdateRuleStats(
"linear: singleton column define objective.");
2443 context_->MarkVariableAsRemoved(var);
2444 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2445 return RemoveConstraint(ct);
2455 if (!context_->SubstituteVariableInObjective(var, coeff, *ct)) {
2456 if (context_->ModelIsUnsat())
return true;
2460 context_->UpdateRuleStats(
2461 "linear: singleton column in equality and in objective.");
2463 index_to_erase.insert(
i);
2467 if (index_to_erase.empty())
return false;
2478 if (!ct->enforcement_literal().empty()) {
2479 for (
const int i : index_to_erase) {
2480 const int var = ct->linear().vars(
i);
2482 context_->NewMappingConstraint(__FILE__, __LINE__)->mutable_linear();
2483 new_lin->add_vars(var);
2484 new_lin->add_coeffs(1);
2493 if (absl::GetFlag(FLAGS_cp_model_debug_postsolve)) {
2494 auto* new_ct = context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
2495 const std::string name = new_ct->name();
2497 new_ct->set_name(absl::StrCat(ct->name(),
" copy ", name));
2499 *context_->NewMappingConstraint(*ct, __FILE__, __LINE__) = *ct;
2503 for (
int i = 0;
i < num_vars; ++
i) {
2504 if (index_to_erase.count(
i)) {
2505 context_->MarkVariableAsRemoved(ct->linear().vars(
i));
2508 ct->mutable_linear()->set_coeffs(new_size, ct->linear().coeffs(
i));
2509 ct->mutable_linear()->set_vars(new_size, ct->linear().vars(
i));
2512 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2513 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2515 DivideLinearByGcd(ct);
2521bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2522 int target_index, ConstraintProto* ct) {
2524 const int num_variables = ct->linear().vars().size();
2525 for (
int i = 0;
i < num_variables; ++
i) {
2526 if (
i == target_index)
continue;
2527 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2528 gcd = std::gcd(gcd, magnitude);
2529 if (gcd == 1)
return false;
2535 const int ref = ct->linear().vars(target_index);
2536 const int64_t coeff = ct->linear().coeffs(target_index);
2537 const int64_t rhs = ct->linear().domain(0);
2541 if (coeff % gcd == 0)
return false;
2543 if (!context_->CanonicalizeAffineVariable(ref, coeff, gcd, rhs)) {
2549 return CanonicalizeLinear(ct);
2554bool IsLinearEqualityConstraint(
const ConstraintProto& ct) {
2555 return ct.constraint_case() == ConstraintProto::kLinear &&
2556 ct.linear().domain().size() == 2 &&
2557 ct.linear().domain(0) == ct.linear().domain(1) &&
2558 ct.enforcement_literal().empty();
2574bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto* ct) {
2575 if (context_->ModelIsUnsat())
return false;
2576 if (!IsLinearEqualityConstraint(*ct))
return false;
2578 const int num_variables = ct->linear().vars().size();
2579 if (num_variables < 2)
return false;
2581 std::vector<int> mod2_indices;
2582 std::vector<int> mod3_indices;
2583 std::vector<int> mod5_indices;
2585 int64_t min_magnitude;
2586 int num_smallest = 0;
2588 for (
int i = 0;
i < num_variables; ++
i) {
2589 const int64_t magnitude = std::abs(ct->linear().coeffs(
i));
2590 if (num_smallest == 0 || magnitude < min_magnitude) {
2591 min_magnitude = magnitude;
2594 }
else if (magnitude == min_magnitude) {
2598 if (magnitude % 2 != 0) mod2_indices.push_back(
i);
2599 if (magnitude % 3 != 0) mod3_indices.push_back(
i);
2600 if (magnitude % 5 != 0) mod5_indices.push_back(
i);
2603 if (mod2_indices.size() == 2) {
2605 std::vector<int> literals;
2606 for (
const int i : mod2_indices) {
2607 const int ref = ct->linear().vars(
i);
2608 if (!context_->CanBeUsedAsLiteral(ref)) {
2612 literals.push_back(ref);
2615 const int64_t rhs = std::abs(ct->linear().domain(0));
2616 context_->UpdateRuleStats(
"linear: only two odd Booleans in equality");
2618 if (!context_->StoreBooleanEqualityRelation(literals[0],
2623 if (!context_->StoreBooleanEqualityRelation(literals[0], literals[1])) {
2632 if (mod2_indices.size() == 1) {
2633 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0], ct);
2635 if (mod3_indices.size() == 1) {
2636 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0], ct);
2638 if (mod5_indices.size() == 1) {
2639 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0], ct);
2641 if (num_smallest == 1) {
2642 return AddVarAffineRepresentativeFromLinearEquality(smallest_index, ct);
2648bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto* ct) {
2649 CHECK_EQ(ct->linear().vars().size(), 1);
2652 const int var = ct->linear().vars(0);
2653 const Domain var_domain = context_->DomainOf(var);
2657 if (rhs.IsEmpty()) {
2658 context_->UpdateRuleStats(
"linear1: infeasible");
2659 return MarkConstraintAsFalse(ct);
2661 if (rhs == var_domain) {
2662 context_->UpdateRuleStats(
"linear1: always true");
2663 return RemoveConstraint(ct);
2668 if (ct->linear().coeffs(0) != 1) {
2669 context_->UpdateRuleStats(
"linear1: canonicalized");
2670 ct->mutable_linear()->set_coeffs(0, 1);
2676 context_->UpdateRuleStats(
"linear1: without enforcement");
2677 if (!context_->IntersectDomainWith(var, rhs))
return false;
2678 return RemoveConstraint(ct);
2682 if (context_->CanBeUsedAsLiteral(var)) {
2683 DCHECK(rhs.IsFixed());
2684 if (rhs.FixedValue() == 1) {
2685 ct->mutable_bool_and()->add_literals(var);
2687 CHECK_EQ(rhs.FixedValue(), 0);
2688 ct->mutable_bool_and()->add_literals(
NegatedRef(var));
2697 bool changed =
false;
2698 if (ct->enforcement_literal().size() == 1) {
2701 int lit = ct->enforcement_literal(0);
2706 const int representative = context_->GetLiteralRepresentative(lit);
2707 if (lit != representative) {
2708 lit = representative;
2709 ct->set_enforcement_literal(0, lit);
2710 context_->UpdateRuleStats(
"linear1: remapped enforcement literal");
2714 if (rhs.IsFixed()) {
2715 const int64_t value = rhs.FixedValue();
2717 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2718 if (lit == encoding_lit)
return changed;
2719 context_->AddImplication(lit, encoding_lit);
2720 context_->UpdateNewConstraintsVariableUsage();
2722 context_->UpdateRuleStats(
"linear1: transformed to implication");
2725 if (context_->StoreLiteralImpliesVarEqValue(lit, var, value)) {
2728 context_->modified_domains.Set(var);
2730 context_->UpdateNewConstraintsVariableUsage();
2735 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2736 if (complement.IsFixed()) {
2737 const int64_t value = complement.FixedValue();
2739 if (context_->HasVarValueEncoding(var, value, &encoding_lit)) {
2740 if (
NegatedRef(lit) == encoding_lit)
return changed;
2741 context_->AddImplication(lit,
NegatedRef(encoding_lit));
2742 context_->UpdateNewConstraintsVariableUsage();
2744 context_->UpdateRuleStats(
"linear1: transformed to implication");
2747 if (context_->StoreLiteralImpliesVarNEqValue(lit, var, value)) {
2750 context_->modified_domains.Set(var);
2752 context_->UpdateNewConstraintsVariableUsage();
2761bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto* ct) {
2762 DCHECK_EQ(ct->linear().vars().size(), 2);
2764 const LinearConstraintProto& arg = ct->linear();
2765 const int var1 = arg.vars(0);
2766 const int var2 = arg.vars(1);
2767 const int64_t coeff1 = arg.coeffs(0);
2768 const int64_t coeff2 = arg.coeffs(1);
2779 const bool is_equality =
2780 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2783 int64_t value_on_true, coeff;
2784 if (context_->CanBeUsedAsLiteral(var1)) {
2786 value_on_true = coeff1;
2789 }
else if (context_->CanBeUsedAsLiteral(var2)) {
2791 value_on_true = coeff2;
2800 const Domain rhs_if_true =
2801 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2802 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2803 const bool implied_false =
2804 context_->DomainOf(var).IntersectionWith(rhs_if_true).IsEmpty();
2805 const bool implied_true =
2806 context_->DomainOf(var).IntersectionWith(rhs_if_false).IsEmpty();
2807 if (implied_true && implied_false) {
2808 context_->UpdateRuleStats(
"linear2: infeasible.");
2809 return MarkConstraintAsFalse(ct);
2810 }
else if (implied_true) {
2811 context_->UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2814 ConstraintProto* new_ct = context_->working_model->add_constraints();
2815 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2816 new_ct->mutable_bool_and()->add_literals(lit);
2817 context_->UpdateNewConstraintsVariableUsage();
2820 ct->mutable_linear()->Clear();
2821 ct->mutable_linear()->add_vars(var);
2822 ct->mutable_linear()->add_coeffs(1);
2824 return PresolveLinearOfSizeOne(ct) ||
true;
2825 }
else if (implied_false) {
2826 context_->UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2829 ConstraintProto* new_ct = context_->working_model->add_constraints();
2830 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
2831 new_ct->mutable_bool_and()->add_literals(
NegatedRef(lit));
2832 context_->UpdateNewConstraintsVariableUsage();
2835 ct->mutable_linear()->Clear();
2836 ct->mutable_linear()->add_vars(var);
2837 ct->mutable_linear()->add_coeffs(1);
2839 return PresolveLinearOfSizeOne(ct) ||
true;
2840 }
else if (ct->enforcement_literal().empty() &&
2841 !context_->CanBeUsedAsLiteral(var)) {
2846 context_->UpdateRuleStats(
"linear2: contains a Boolean.");
2849 const Domain var_domain = context_->DomainOf(var);
2850 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2851 ConstraintProto* new_ct = context_->working_model->add_constraints();
2852 new_ct->add_enforcement_literal(lit);
2853 new_ct->mutable_linear()->add_vars(var);
2854 new_ct->mutable_linear()->add_coeffs(1);
2856 new_ct->mutable_linear());
2860 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2861 ConstraintProto* new_ct = context_->working_model->add_constraints();
2862 new_ct->add_enforcement_literal(
NegatedRef(lit));
2863 new_ct->mutable_linear()->add_vars(var);
2864 new_ct->mutable_linear()->add_coeffs(1);
2866 new_ct->mutable_linear());
2869 context_->UpdateNewConstraintsVariableUsage();
2870 return RemoveConstraint(ct);
2874 context_->UpdateRuleStats(
"TODO linear2: contains a Boolean.");
2879 const int64_t rhs = arg.domain(0);
2880 if (ct->enforcement_literal().empty()) {
2887 added = context_->StoreAffineRelation(var1, var2, -coeff2, rhs);
2888 }
else if (coeff2 == 1) {
2889 added = context_->StoreAffineRelation(var2, var1, -coeff1, rhs);
2890 }
else if (coeff1 == -1) {
2891 added = context_->StoreAffineRelation(var1, var2, coeff2, -rhs);
2892 }
else if (coeff2 == -1) {
2893 added = context_->StoreAffineRelation(var2, var1, coeff1, -rhs);
2902 context_->UpdateRuleStats(
"TODO linear2: ax + by = cte");
2904 if (added)
return RemoveConstraint(ct);
2913 context_->UpdateRuleStats(
2914 "linear2: implied ax + by = cte has no solutions");
2915 return MarkConstraintAsFalse(ct);
2917 const Domain reduced_domain =
2918 context_->DomainOf(var1)
2919 .AdditionWith(Domain(-x0))
2920 .InverseMultiplicationBy(
b)
2921 .IntersectionWith(context_->DomainOf(var2)
2922 .AdditionWith(Domain(-y0))
2923 .InverseMultiplicationBy(-a));
2925 if (reduced_domain.IsEmpty()) {
2926 context_->UpdateRuleStats(
2927 "linear2: implied ax + by = cte has no solutions");
2928 return MarkConstraintAsFalse(ct);
2931 if (reduced_domain.Size() == 1) {
2932 const int64_t z = reduced_domain.FixedValue();
2933 const int64_t value1 = x0 +
b * z;
2934 const int64_t value2 = y0 - a * z;
2936 DCHECK(context_->DomainContains(var1, value1));
2937 DCHECK(context_->DomainContains(var2, value2));
2938 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2940 ConstraintProto* imply1 = context_->working_model->add_constraints();
2941 *imply1->mutable_enforcement_literal() = ct->enforcement_literal();
2942 imply1->mutable_linear()->add_vars(var1);
2943 imply1->mutable_linear()->add_coeffs(1);
2944 imply1->mutable_linear()->add_domain(value1);
2945 imply1->mutable_linear()->add_domain(value1);
2947 ConstraintProto* imply2 = context_->working_model->add_constraints();
2948 *imply2->mutable_enforcement_literal() = ct->enforcement_literal();
2949 imply2->mutable_linear()->add_vars(var2);
2950 imply2->mutable_linear()->add_coeffs(1);
2951 imply2->mutable_linear()->add_domain(value2);
2952 imply2->mutable_linear()->add_domain(value2);
2953 context_->UpdateRuleStats(
2954 "linear2: implied ax + by = cte has only one solution");
2955 context_->UpdateNewConstraintsVariableUsage();
2956 return RemoveConstraint(ct);
2963bool CpModelPresolver::PresolveSmallLinear(ConstraintProto* ct) {
2964 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
2965 if (context_->ModelIsUnsat())
return false;
2967 if (ct->linear().vars().empty()) {
2968 context_->UpdateRuleStats(
"linear: empty");
2971 return RemoveConstraint(ct);
2973 return MarkConstraintAsFalse(ct);
2975 }
else if (ct->linear().vars().size() == 1) {
2976 return PresolveLinearOfSizeOne(ct);
2977 }
else if (ct->linear().vars().size() == 2) {
2978 return PresolveLinearOfSizeTwo(ct);
2984bool CpModelPresolver::PresolveDiophantine(ConstraintProto* ct) {
2985 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
2986 if (ct->linear().vars().size() <= 1)
return false;
2988 if (context_->ModelIsUnsat())
return false;
2990 const LinearConstraintProto& linear_constraint = ct->linear();
2991 if (linear_constraint.domain_size() != 2)
return false;
2992 if (linear_constraint.domain(0) != linear_constraint.domain(1))
return false;
2994 std::vector<int64_t> lbs(linear_constraint.vars_size());
2995 std::vector<int64_t> ubs(linear_constraint.vars_size());
2996 for (
int i = 0;
i < linear_constraint.vars_size(); ++
i) {
2997 lbs[
i] = context_->MinOf(linear_constraint.vars(
i));
2998 ubs[
i] = context_->MaxOf(linear_constraint.vars(
i));
3001 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
3003 if (!diophantine_solution.has_solutions) {
3004 context_->UpdateRuleStats(
"diophantine: equality has no solutions");
3005 return MarkConstraintAsFalse(ct);
3007 if (diophantine_solution.no_reformulation_needed)
return false;
3010 for (
const std::vector<absl::int128>&
b : diophantine_solution.kernel_basis) {
3012 context_->UpdateRuleStats(
3013 "diophantine: couldn't apply due to int64_t overflow");
3018 context_->UpdateRuleStats(
3019 "diophantine: couldn't apply due to int64_t overflow");
3023 const int num_replaced_variables =
3024 static_cast<int>(diophantine_solution.special_solution.size());
3025 const int num_new_variables =
3026 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
3027 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
3028 for (
int i = 0;
i < num_new_variables; ++
i) {
3031 context_->UpdateRuleStats(
3032 "diophantine: couldn't apply due to int64_t overflow");
3042 std::vector<int> new_variables(num_new_variables);
3043 for (
int i = 0;
i < num_new_variables; ++
i) {
3044 new_variables[
i] = context_->working_model->variables_size();
3045 IntegerVariableProto* var = context_->working_model->add_variables();
3047 static_cast<int64_t
>(diophantine_solution.kernel_vars_lbs[
i]));
3049 static_cast<int64_t
>(diophantine_solution.kernel_vars_ubs[
i]));
3050 if (!ct->name().empty()) {
3051 var->set_name(absl::StrCat(
"u_diophantine_", ct->name(),
"_",
i));
3061 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3062 ConstraintProto* identity = context_->working_model->add_constraints();
3063 LinearConstraintProto* lin = identity->mutable_linear();
3064 if (!ct->name().empty()) {
3065 identity->set_name(absl::StrCat(
"c_diophantine_", ct->name(),
"_",
i));
3067 *identity->mutable_enforcement_literal() = ct->enforcement_literal();
3069 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
3072 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3074 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
3075 for (
int j = std::max(1,
i); j < num_replaced_variables; ++j) {
3076 lin->add_vars(new_variables[j - 1]);
3078 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3080 for (
int j = num_replaced_variables; j < linear_constraint.vars_size();
3083 linear_constraint.vars(diophantine_solution.index_permutation[j]));
3085 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
3093 context_->UpdateRuleStats(
3094 "diophantine: couldn't apply due to overflowing activity of new "
3097 context_->working_model->mutable_constraints()->DeleteSubrange(
3098 context_->working_model->constraints_size() -
i - 1,
i + 1);
3099 context_->working_model->mutable_variables()->DeleteSubrange(
3100 context_->working_model->variables_size() - num_new_variables,
3105 context_->InitializeNewDomains();
3109 const int num_constraints = context_->working_model->constraints_size();
3110 for (
int i = 0;
i < num_replaced_variables; ++
i) {
3111 const LinearConstraintProto& linear =
3112 context_->working_model->constraints(num_constraints - 1 -
i).linear();
3113 DCHECK(linear.domain_size() == 2 && linear.domain(0) == linear.domain(1));
3114 solution_crush_.SetVarToLinearConstraintSolution(
3115 std::nullopt, linear.vars(), linear.coeffs(), linear.domain(0));
3118 if (VLOG_IS_ON(2)) {
3119 std::string log_eq = absl::StrCat(linear_constraint.domain(0),
" = ");
3120 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
3121 for (
int i = 0;
i < terms_to_show; ++
i) {
3122 if (
i > 0) absl::StrAppend(&log_eq,
" + ");
3125 linear_constraint.coeffs(diophantine_solution.index_permutation[
i]),
3127 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
3129 if (terms_to_show < linear_constraint.vars_size()) {
3130 absl::StrAppend(&log_eq,
"+ ... (", linear_constraint.vars_size(),
3133 VLOG(2) <<
"[Diophantine] " << log_eq;
3136 context_->UpdateRuleStats(
"diophantine: reformulated equality");
3137 context_->UpdateNewConstraintsVariableUsage();
3138 return RemoveConstraint(ct);
3153void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
3154 int c, ConstraintProto* ct) {
3155 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3156 if (context_->ModelIsUnsat())
return;
3159 const LinearConstraintProto& lin = ct->linear();
3160 if (lin.domain().size() != 2)
return;
3166 int64_t max_variation = 0;
3168 rd_entries_.clear();
3169 rd_magnitudes_.clear();
3173 int64_t max_magnitude = 0;
3174 const int num_terms = lin.vars().size();
3175 for (
int i = 0;
i < num_terms; ++
i) {
3176 const int64_t coeff = lin.coeffs(
i);
3177 const int64_t magnitude = std::abs(lin.coeffs(
i));
3178 if (magnitude == 0)
continue;
3179 max_magnitude = std::max(max_magnitude, magnitude);
3184 lb = context_->MinOf(lin.vars(
i));
3185 ub = context_->MaxOf(lin.vars(
i));
3187 lb = -context_->MaxOf(lin.vars(
i));
3188 ub = -context_->MinOf(lin.vars(
i));
3190 lb_sum += lb * magnitude;
3191 ub_sum += ub * magnitude;
3194 if (lb == ub)
return;
3196 rd_lbs_.push_back(lb);
3197 rd_ubs_.push_back(ub);
3198 rd_magnitudes_.push_back(magnitude);
3199 rd_entries_.push_back({magnitude, magnitude * (ub - lb),
i});
3200 max_variation += rd_entries_.back().max_variation;
3205 if (lb_sum > rhs.
Max() || rhs.
Min() > ub_sum) {
3206 (void)MarkConstraintAsFalse(ct);
3207 context_->UpdateConstraintVariableUsage(c);
3210 const IntegerValue rhs_ub(
CapSub(rhs.
Max(), lb_sum));
3211 const IntegerValue rhs_lb(
CapSub(ub_sum, rhs.
Min()));
3212 const bool use_ub = max_variation > rhs_ub;
3213 const bool use_lb = max_variation > rhs_lb;
3214 if (!use_ub && !use_lb) {
3215 (void)RemoveConstraint(ct);
3216 context_->UpdateConstraintVariableUsage(c);
3221 if (max_magnitude <= 1)
return;
3226 lb_feasible_.Reset(rhs_lb.value());
3227 lb_infeasible_.Reset(rhs.
Min() - lb_sum - 1);
3230 ub_feasible_.Reset(rhs_ub.value());
3231 ub_infeasible_.Reset(ub_sum - rhs.
Max() - 1);
3237 int64_t max_error = max_variation;
3238 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3239 [](
const RdEntry& a,
const RdEntry&
b) {
3240 return a.magnitude > b.magnitude;
3243 rd_divisors_.clear();
3244 for (
int i = 0;
i < rd_entries_.size(); ++
i) {
3245 const RdEntry& e = rd_entries_[
i];
3246 gcd = std::gcd(gcd, e.magnitude);
3247 max_error -= e.max_variation;
3253 range += e.max_variation / e.magnitude;
3254 if (
i + 1 < rd_entries_.size() &&
3255 e.magnitude == rd_entries_[
i + 1].magnitude) {
3258 const int64_t saved_range = range;
3261 if (e.magnitude > 1) {
3266 rd_divisors_.push_back(e.magnitude);
3270 bool simplify_lb =
false;
3272 lb_feasible_.AddMultiples(e.magnitude, saved_range);
3273 lb_infeasible_.AddMultiples(e.magnitude, saved_range);
3276 if (
CapAdd(lb_feasible_.CurrentMax(), max_error) <=
3277 lb_feasible_.Bound()) {
3282 if (
CapAdd(lb_infeasible_.CurrentMax(), max_error) <=
3283 lb_infeasible_.Bound()) {
3289 bool simplify_ub =
false;
3291 ub_feasible_.AddMultiples(e.magnitude, saved_range);
3292 ub_infeasible_.AddMultiples(e.magnitude, saved_range);
3293 if (
CapAdd(ub_feasible_.CurrentMax(), max_error) <=
3294 ub_feasible_.Bound()) {
3297 if (
CapAdd(ub_infeasible_.CurrentMax(), max_error) <=
3298 ub_infeasible_.Bound()) {
3305 if (max_error == 0)
break;
3306 if (simplify_lb && simplify_ub) {
3308 context_->UpdateRuleStats(
"linear: remove irrelevant part");
3309 int64_t shift_lb = 0;
3310 int64_t shift_ub = 0;
3313 for (
int j = 0; j <=
i; ++j) {
3314 const int index = rd_entries_[j].index;
3315 const int64_t m = rd_magnitudes_[index];
3316 shift_lb += rd_lbs_[index] * m;
3317 shift_ub += rd_ubs_[index] * m;
3318 rd_vars_.push_back(lin.vars(index));
3319 rd_coeffs_.push_back(lin.coeffs(index));
3321 LinearConstraintProto* mut_lin = ct->mutable_linear();
3322 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3323 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3329 const int64_t new_rhs_lb =
3330 use_lb ? shift_ub - lb_feasible_.CurrentMax() : shift_lb;
3331 const int64_t new_rhs_ub =
3332 use_ub ? shift_lb + ub_feasible_.CurrentMax() : shift_ub;
3333 if (new_rhs_lb > new_rhs_ub) {
3334 (void)MarkConstraintAsFalse(ct);
3335 context_->UpdateConstraintVariableUsage(c);
3339 DivideLinearByGcd(ct);
3340 context_->UpdateConstraintVariableUsage(c);
3348 if (DivideLinearByGcd(ct)) {
3349 context_->UpdateConstraintVariableUsage(c);
3356 if ((use_lb && lb_feasible_.CurrentMax() < lb_feasible_.Bound()) ||
3357 (use_ub && ub_feasible_.CurrentMax() < ub_feasible_.Bound())) {
3358 context_->UpdateRuleStats(
"linear: reduce rhs with DP");
3359 const int64_t new_rhs_lb =
3360 use_lb ? ub_sum - lb_feasible_.CurrentMax() : lb_sum;
3361 const int64_t new_rhs_ub =
3362 use_ub ? lb_sum + ub_feasible_.CurrentMax() : ub_sum;
3363 if (new_rhs_lb > new_rhs_ub) {
3364 (void)MarkConstraintAsFalse(ct);
3365 context_->UpdateConstraintVariableUsage(c);
3372 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3373 for (
const int64_t divisor : rd_divisors_) {
3377 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.
Max(), &new_ub)) {
3382 int64_t minus_new_lb;
3383 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3384 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3385 rd_lbs_[
i] = -rd_lbs_[
i];
3386 rd_ubs_[
i] = -rd_ubs_[
i];
3389 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.
Min(),
3391 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3392 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3393 rd_lbs_[
i] = -rd_lbs_[
i];
3394 rd_ubs_[
i] = -rd_ubs_[
i];
3400 context_->UpdateRuleStats(
"linear: simplify using approximate gcd");
3402 LinearConstraintProto* mutable_linear = ct->mutable_linear();
3403 for (
int i = 0;
i < lin.coeffs().size(); ++
i) {
3404 const int64_t new_coeff =
3406 if (new_coeff == 0)
continue;
3407 mutable_linear->set_vars(new_size, lin.vars(
i));
3408 mutable_linear->set_coeffs(new_size, new_coeff);
3411 mutable_linear->mutable_vars()->Truncate(new_size);
3412 mutable_linear->mutable_coeffs()->Truncate(new_size);
3413 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3414 if (new_rhs.IsEmpty()) {
3415 (void)MarkConstraintAsFalse(ct);
3419 context_->UpdateConstraintVariableUsage(c);
3429bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
3430 const Domain& terms,
const Domain& rhs) {
3431 if (var_domain.NumIntervals() != 1)
return false;
3432 if (std::abs(coeff) != 1)
return false;
3440 if (coeff == 1 && terms.
Max() + var_domain.
Min() <= rhs.
Min()) {
3443 if (coeff == -1 && terms.
Max() - var_domain.
Max() <= rhs.
Min()) {
3449bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
3450 const Domain& terms,
const Domain& rhs) {
3451 if (var_domain.NumIntervals() != 1)
return false;
3452 if (std::abs(coeff) != 1)
return false;
3454 if (coeff == 1 && terms.
Min() + var_domain.
Max() >= rhs.
Max()) {
3457 if (coeff == -1 && terms.
Min() - var_domain.
Min() >= rhs.
Max()) {
3463int FixLiteralFromSet(
const absl::flat_hash_set<int>& literals_at_true,
3464 LinearConstraintProto* linear) {
3467 const int num_terms = linear->vars().size();
3469 for (
int i = 0;
i < num_terms; ++
i) {
3470 const int var = linear->vars(
i);
3471 const int64_t coeff = linear->coeffs(
i);
3472 if (literals_at_true.contains(var)) {
3476 }
else if (!literals_at_true.contains(
NegatedRef(var))) {
3477 linear->set_vars(new_size, var);
3478 linear->set_coeffs(new_size, coeff);
3485 linear->mutable_vars()->Truncate(new_size);
3486 linear->mutable_coeffs()->Truncate(new_size);
3496void CpModelPresolver::ProcessAtMostOneAndLinear() {
3497 if (time_limit_->LimitReached())
return;
3498 if (context_->ModelIsUnsat())
return;
3499 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
3500 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3502 ActivityBoundHelper amo_in_linear;
3503 amo_in_linear.AddAllAtMostOnes(*context_->working_model);
3505 int num_changes = 0;
3506 const int num_constraints = context_->working_model->constraints_size();
3507 for (
int c = 0;
c < num_constraints; ++
c) {
3508 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
3509 if (ct->constraint_case() != ConstraintProto::kLinear)
continue;
3512 for (
int i = 0;
i < 5; ++
i) {
3513 const int old_size = ct->linear().vars().size();
3514 const int old_enf_size = ct->enforcement_literal().size();
3515 ProcessOneLinearWithAmo(c, ct, &amo_in_linear);
3516 if (context_->ModelIsUnsat())
return;
3517 if (ct->constraint_case() != ConstraintProto::kLinear)
break;
3518 if (ct->linear().vars().size() == old_size &&
3519 ct->enforcement_literal().size() == old_enf_size) {
3526 timer.AddCounter(
"num_changes", num_changes);
3534void CpModelPresolver::ProcessOneLinearWithAmo(
int ct_index,
3535 ConstraintProto* ct,
3537 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3538 if (ct->linear().vars().size() <= 1)
return;
3545 Domain non_boolean_domain(0);
3546 const int initial_size = ct->linear().vars().size();
3547 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3548 int64_t max_magnitude = 0;
3549 for (
int i = 0;
i < initial_size; ++
i) {
3551 int ref = ct->linear().vars(
i);
3552 int64_t coeff = ct->linear().coeffs(
i);
3557 if (context_->CanBeUsedAsLiteral(ref)) {
3558 tmp_terms_.push_back({ref, coeff});
3559 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3560 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3562 non_boolean_domain =
3565 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3566 .RelaxIfTooComplex();
3567 temp_ct_.mutable_linear()->add_vars(ref);
3568 temp_ct_.mutable_linear()->add_coeffs(coeff);
3573 if (tmp_terms_.empty())
return;
3584 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3585 min_magnitude < max_magnitude) {
3586 int64_t min_activity = 0;
3587 int64_t max_activity = 0;
3588 for (
const auto [ref, coeff] : tmp_terms_) {
3590 max_activity += coeff;
3592 min_activity += coeff;
3595 const int64_t transformed_rhs = rhs.
Max() - min_activity;
3596 if (min_activity >= rhs.
Min() && max_magnitude <= transformed_rhs) {
3597 std::vector<int> literals;
3598 for (
const auto [ref, coeff] : tmp_terms_) {
3599 if (coeff + min_magnitude > transformed_rhs)
continue;
3600 literals.push_back(coeff > 0 ? ref :
NegatedRef(ref));
3602 if (helper->IsAmo(literals)) {
3604 context_->UpdateRuleStats(
"linear + amo: detect hidden AMO");
3606 for (
int i = 0;
i < initial_size; ++
i) {
3608 if (ct->linear().coeffs(
i) > 0) {
3609 ct->mutable_linear()->set_coeffs(
i, 1);
3611 ct->mutable_linear()->set_coeffs(
i, -1);
3622 const int64_t min_bool_activity =
3623 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3624 const int64_t max_bool_activity =
3625 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3629 const Domain activity = non_boolean_domain.AdditionWith(
3630 Domain(min_bool_activity, max_bool_activity));
3631 if (activity.IntersectionWith(rhs).IsEmpty()) {
3633 context_->UpdateRuleStats(
"linear + amo: infeasible linear constraint");
3634 (void)MarkConstraintAsFalse(ct);
3635 context_->UpdateConstraintVariableUsage(ct_index);
3637 }
else if (activity.IsIncludedIn(rhs)) {
3638 context_->UpdateRuleStats(
"linear + amo: trivial linear constraint");
3640 context_->UpdateConstraintVariableUsage(ct_index);
3645 if (ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3648 Domain(min_bool_activity, max_bool_activity).Negation()),
3649 temp_ct_.mutable_linear());
3650 if (!PropagateDomainsInLinear(-1, &temp_ct_)) {
3653 if (context_->ModelIsUnsat())
return;
3668 std::vector<int> new_enforcement;
3669 std::vector<int> must_be_true;
3670 for (
int i = 0;
i < tmp_terms_.size(); ++
i) {
3671 const int ref = tmp_terms_[
i].first;
3673 const Domain bool0(conditional_mins_[
i][0], conditional_maxs_[
i][0]);
3674 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3675 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3677 must_be_true.push_back(ref);
3678 }
else if (activity0.IsIncludedIn(rhs)) {
3680 new_enforcement.push_back(ref);
3683 const Domain bool1(conditional_mins_[
i][1], conditional_maxs_[
i][1]);
3684 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3685 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3688 }
else if (activity1.IsIncludedIn(rhs)) {
3699 if (ct->enforcement_literal().empty() && !must_be_true.empty()) {
3702 context_->UpdateRuleStats(
"linear + amo: fixed literal",
3703 must_be_true.size());
3704 for (
const int lit : must_be_true) {
3705 if (!context_->SetLiteralToTrue(lit))
return;
3707 CanonicalizeLinear(ct);
3708 context_->UpdateConstraintVariableUsage(ct_index);
3712 if (!new_enforcement.empty()) {
3713 context_->UpdateRuleStats(
"linear + amo: extracted enforcement literal",
3714 new_enforcement.size());
3715 for (
const int ref : new_enforcement) {
3716 ct->add_enforcement_literal(ref);
3720 if (!ct->enforcement_literal().empty()) {
3721 const int old_enf_size = ct->enforcement_literal().size();
3722 if (!helper->PresolveEnforcement(ct->linear().vars(), ct, &temp_set_)) {
3723 context_->UpdateRuleStats(
"linear + amo: infeasible enforcement");
3725 context_->UpdateConstraintVariableUsage(ct_index);
3728 if (ct->enforcement_literal().size() < old_enf_size) {
3729 context_->UpdateRuleStats(
"linear + amo: simplified enforcement list");
3730 context_->UpdateConstraintVariableUsage(ct_index);
3733 for (
const int lit : must_be_true) {
3737 context_->UpdateRuleStats(
3738 "linear + amo: advanced infeasible linear constraint");
3739 (void)MarkConstraintAsFalse(ct);
3740 context_->UpdateConstraintVariableUsage(ct_index);
3746 if (ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3750 context_->UpdateRuleStats(
"linear + amo: added implications");
3751 ConstraintProto* new_ct = context_->working_model->add_constraints();
3752 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
3753 for (
const int lit : must_be_true) {
3754 new_ct->mutable_bool_and()->add_literals(lit);
3755 temp_set_.insert(lit);
3757 context_->UpdateNewConstraintsVariableUsage();
3760 const int num_fixed = FixLiteralFromSet(temp_set_, ct->mutable_linear());
3761 if (num_fixed > new_enforcement.size()) {
3762 context_->UpdateRuleStats(
3763 "linear + amo: fixed literal implied by enforcement");
3765 if (num_fixed > 0) {
3766 context_->UpdateConstraintVariableUsage(ct_index);
3770 if (ct->linear().vars().empty()) {
3771 context_->UpdateRuleStats(
"linear + amo: empty after processing");
3772 PresolveSmallLinear(ct);
3773 context_->UpdateConstraintVariableUsage(ct_index);
3778 if (initial_size != ct->linear().vars().size() && PresolveSmallLinear(ct)) {
3779 context_->UpdateConstraintVariableUsage(ct_index);
3780 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
3788 if (!ct->enforcement_literal().empty()) {
3791 Domain non_boolean_domain(0);
3792 const int num_ct_terms = ct->linear().vars().size();
3793 for (
int i = 0;
i < num_ct_terms; ++
i) {
3794 const int ref = ct->linear().vars(
i);
3795 const int64_t coeff = ct->linear().coeffs(
i);
3797 if (context_->CanBeUsedAsLiteral(ref)) {
3798 tmp_terms_.push_back({ref, coeff});
3800 non_boolean_domain =
3803 context_->DomainOf(ref).ContinuousMultiplicationBy(coeff))
3804 .RelaxIfTooComplex();
3807 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3809 if (num_removed > 0) {
3810 context_->UpdateRuleStats(
"linear + amo: removed enforcement literal",
3812 context_->UpdateConstraintVariableUsage(ct_index);
3817bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
3818 ConstraintProto* ct) {
3819 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
3820 if (context_->ModelIsUnsat())
return false;
3823 int64_t min_activity;
3824 int64_t max_activity;
3827 const int num_vars = ct->linear().vars_size();
3828 auto& term_domains = context_->tmp_term_domains;
3829 auto& left_domains = context_->tmp_left_domains;
3830 const bool slow_mode = num_vars < 10;
3834 term_domains.resize(num_vars + 1);
3835 left_domains.resize(num_vars + 1);
3836 left_domains[0] = Domain(0);
3837 term_domains[num_vars] = Domain(0);
3838 for (
int i = 0;
i < num_vars; ++
i) {
3839 const int var = ct->linear().vars(
i);
3840 const int64_t coeff = ct->linear().coeffs(
i);
3842 term_domains[
i] = context_->DomainOf(var).MultiplicationBy(coeff);
3843 left_domains[
i + 1] =
3844 left_domains[
i].AdditionWith(term_domains[
i]).RelaxIfTooComplex();
3847 std::tie(min_activity, max_activity) =
3848 context_->ComputeMinMaxActivity(ct->linear());
3850 const Domain& implied_rhs =
3851 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3855 if (implied_rhs.IsIncludedIn(old_rhs)) {
3856 if (ct_index != -1) context_->UpdateRuleStats(
"linear: always true");
3857 return RemoveConstraint(ct);
3861 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3862 if (rhs.IsEmpty()) {
3863 context_->UpdateRuleStats(
"linear: infeasible");
3864 return MarkConstraintAsFalse(ct);
3866 if (rhs != old_rhs) {
3867 if (ct_index != -1) context_->UpdateRuleStats(
"linear: simplified rhs");
3872 if (ct->enforcement_literal().size() > 1)
return false;
3874 bool new_bounds =
false;
3875 bool recanonicalize =
false;
3876 Domain negated_rhs = rhs.Negation();
3877 Domain right_domain(0);
3879 Domain activity_minus_term;
3880 for (
int i = num_vars - 1;
i >= 0; --
i) {
3881 const int var = ct->linear().vars(
i);
3882 const int64_t var_coeff = ct->linear().coeffs(
i);
3886 right_domain.AdditionWith(term_domains[
i + 1]).RelaxIfTooComplex();
3887 activity_minus_term = left_domains[
i].AdditionWith(right_domain);
3889 int64_t min_term = var_coeff * context_->MinOf(var);
3890 int64_t max_term = var_coeff * context_->MaxOf(var);
3891 if (var_coeff < 0) std::swap(min_term, max_term);
3892 activity_minus_term =
3893 Domain(min_activity - min_term, max_activity - max_term);
3895 new_domain = activity_minus_term.AdditionWith(negated_rhs)
3896 .InverseMultiplicationBy(-var_coeff);
3898 if (ct->enforcement_literal().empty()) {
3900 if (!context_->IntersectDomainWith(var, new_domain, &new_bounds)) {
3903 }
else if (ct->enforcement_literal().size() == 1) {
3906 if (!context_->DomainOfVarIsIncludedIn(var, new_domain)) {
3907 context_->deductions.AddDeduction(ct->enforcement_literal(0), var,
3912 if (context_->IsFixed(var)) {
3914 recanonicalize =
true;
3919 if (ct_index == -1)
continue;
3920 if (!ct->enforcement_literal().empty())
continue;
3932 if (rhs.
Min() != rhs.
Max() &&
3933 context_->VariableWithCostIsUniqueAndRemovable(var)) {
3934 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
3935 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
3937 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->DomainOf(var),
3938 activity_minus_term, rhs)) {
3939 rhs = Domain(rhs.
Min());
3942 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->DomainOf(var),
3943 activity_minus_term, rhs)) {
3944 rhs = Domain(rhs.
Max());
3948 context_->UpdateRuleStats(
"linear: tightened into equality");
3950 solution_crush_.SetVarToLinearConstraintSolution(
3951 i, ct->linear().vars(), ct->linear().coeffs(), rhs.FixedValue());
3953 negated_rhs = rhs.Negation();
3957 right_domain = Domain(0);
3971 if (ct->linear().vars().size() <= 2)
continue;
3976 if (rhs.
Min() != rhs.
Max())
continue;
3983 if (context_->params().keep_all_feasible_solutions_in_presolve())
continue;
3989 if (std::abs(var_coeff) != 1)
continue;
3990 if (context_->params().presolve_substitution_level() <= 0)
continue;
3993 const bool is_in_objective = context_->VarToConstraints(var).contains(-1);
3995 int col_size = context_->VarToConstraints(var).size();
3996 if (is_in_objective) col_size--;
3997 const int row_size = ct->linear().vars_size();
4001 const int num_entries_added = (row_size - 1) * (col_size - 1);
4002 const int num_entries_removed = col_size + row_size - 1;
4003 if (num_entries_added > num_entries_removed)
continue;
4008 std::vector<int> others;
4010 for (
const int c : context_->VarToConstraints(var)) {
4016 if (c == ct_index)
continue;
4017 if (context_->working_model->constraints(c).constraint_case() !=
4018 ConstraintProto::kLinear) {
4022 for (
const int ref :
4023 context_->working_model->constraints(c).enforcement_literal()) {
4030 others.push_back(c);
4032 if (abort)
continue;
4038 if (context_->DomainOf(var) != new_domain) {
4045 if (others.size() != 1)
continue;
4046 const ConstraintProto& other_ct =
4047 context_->working_model->constraints(others.front());
4048 if (!other_ct.enforcement_literal().empty())
continue;
4052 const LinearConstraintProto& other_lin = other_ct.linear();
4053 if (other_lin.vars().size() > 100)
continue;
4055 int64_t other_coeff = 0;
4056 for (
int i = 0;
i < other_lin.vars().size(); ++
i) {
4057 const int v = other_lin.vars(
i);
4058 const int64_t coeff = other_lin.coeffs(
i);
4062 other_coeff += coeff;
4066 .AdditionWith(context_->DomainOf(v).MultiplicationBy(-coeff))
4067 .RelaxIfTooComplex();
4070 if (other_coeff == 0)
continue;
4071 implied = implied.InverseMultiplicationBy(other_coeff);
4075 if (!context_->IntersectDomainWith(var, implied))
return false;
4076 if (context_->IsFixed(var))
continue;
4077 if (new_domain.IntersectionWith(implied) != context_->DomainOf(var)) {
4081 context_->UpdateRuleStats(
"linear: doubleton free");
4087 if (is_in_objective &&
4088 !context_->SubstituteVariableInObjective(var, var_coeff, *ct)) {
4093 ConstraintProto copy_if_we_abort;
4094 absl::c_sort(others);
4095 for (
const int c : others) {
4099 copy_if_we_abort = context_->working_model->constraints(c);
4107 var, var_coeff, *ct,
4108 context_->working_model->mutable_constraints(c))) {
4115 if (CanonicalizeLinear(
4116 context_->working_model->mutable_constraints(c))) {
4117 context_->UpdateConstraintVariableUsage(c);
4124 *context_->working_model,
4125 context_->working_model->constraints(c).linear().vars(),
4126 context_->working_model->constraints(c).linear().coeffs())) {
4128 *context_->working_model->mutable_constraints(c) = copy_if_we_abort;
4134 context_->UpdateConstraintVariableUsage(c);
4136 if (abort)
continue;
4138 context_->UpdateRuleStats(
4139 absl::StrCat(
"linear: variable substitution ", others.size()));
4148 CHECK_EQ(context_->VarToConstraints(var).size(), 1);
4149 context_->MarkVariableAsRemoved(var);
4150 ConstraintProto* mapping_ct =
4151 context_->NewMappingConstraint(__FILE__, __LINE__);
4153 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
4154 std::swap(mapping_linear_ct->mutable_vars()->at(0),
4155 mapping_linear_ct->mutable_vars()->at(
i));
4156 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
4157 mapping_linear_ct->mutable_coeffs()->at(
i));
4158 return RemoveConstraint(ct);
4162 if (ct_index == -1) {
4164 context_->UpdateRuleStats(
4165 "linear: reduced variable domains in derived constraint");
4171 context_->UpdateRuleStats(
"linear: reduced variable domains");
4173 if (recanonicalize)
return CanonicalizeLinear(ct);
4179void CpModelPresolver::LowerThanCoeffStrengthening(
bool from_lower_bound,
4180 int64_t min_magnitude,
4182 ConstraintProto* ct) {
4183 const LinearConstraintProto& arg = ct->linear();
4184 const int64_t second_threshold = rhs - min_magnitude;
4185 const int num_vars = arg.vars_size();
4198 if (min_magnitude <= second_threshold) {
4200 int64_t max_magnitude_left = 0;
4201 int64_t max_activity_left = 0;
4202 int64_t activity_when_coeff_are_one = 0;
4204 for (
int i = 0;
i < num_vars; ++
i) {
4205 const int64_t magnitude = std::abs(arg.coeffs(
i));
4206 if (magnitude <= second_threshold) {
4207 gcd = std::gcd(gcd, magnitude);
4208 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4209 const int64_t bound_diff =
4210 context_->MaxOf(arg.vars(
i)) - context_->MinOf(arg.vars(
i));
4211 activity_when_coeff_are_one += bound_diff;
4212 max_activity_left += magnitude * bound_diff;
4215 CHECK_GT(min_magnitude, 0);
4216 CHECK_LE(min_magnitude, max_magnitude_left);
4219 int64_t new_rhs = 0;
4220 bool set_all_to_one =
false;
4221 if (max_activity_left <= rhs) {
4223 context_->UpdateRuleStats(
"linear with partial amo: trivial");
4224 new_rhs = activity_when_coeff_are_one;
4225 set_all_to_one =
true;
4226 }
else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4228 context_->UpdateRuleStats(
"linear with partial amo: constant coeff");
4229 new_rhs = rhs / min_magnitude;
4230 set_all_to_one =
true;
4231 }
else if (gcd > 1) {
4233 context_->UpdateRuleStats(
"linear with partial amo: gcd");
4234 new_rhs = rhs / gcd;
4238 int64_t rhs_offset = 0;
4239 for (
int i = 0;
i < num_vars; ++
i) {
4240 const int ref = arg.vars(
i);
4241 const int64_t coeff = from_lower_bound ? arg.coeffs(
i) : -arg.coeffs(
i);
4244 const int64_t magnitude = std::abs(coeff);
4245 if (magnitude > rhs) {
4246 new_coeff = new_rhs + 1;
4247 }
else if (magnitude > second_threshold) {
4248 new_coeff = new_rhs;
4250 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4256 ct->mutable_linear()->set_coeffs(
i, new_coeff);
4257 rhs_offset += new_coeff * context_->MinOf(ref);
4259 ct->mutable_linear()->set_coeffs(
i, -new_coeff);
4260 rhs_offset -= new_coeff * context_->MaxOf(ref);
4264 ct->mutable_linear());
4269 int64_t rhs_offset = 0;
4270 for (
int i = 0;
i < num_vars; ++
i) {
4271 int ref = arg.vars(
i);
4272 int64_t coeff = arg.coeffs(
i);
4279 if (ct->enforcement_literal().empty()) {
4287 context_->UpdateRuleStats(
"linear: fix variable to its bound.");
4288 if (!context_->IntersectDomainWith(
4289 ref, Domain(from_lower_bound ? context_->MinOf(ref)
4290 : context_->MaxOf(ref)))) {
4298 if (coeff > second_threshold && coeff < rhs) {
4299 context_->UpdateRuleStats(
4300 "linear: coefficient strengthening by increasing it.");
4301 if (from_lower_bound) {
4303 rhs_offset -= (coeff - rhs) * context_->MinOf(ref);
4306 rhs_offset -= (coeff - rhs) * context_->MaxOf(ref);
4308 ct->mutable_linear()->set_coeffs(
i, arg.coeffs(
i) > 0 ? rhs : -rhs);
4311 if (rhs_offset != 0) {
4313 ct->mutable_linear());
4324void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4325 int ct_index, ConstraintProto* ct) {
4326 if (ct->constraint_case() != ConstraintProto::kLinear)
return;
4327 if (context_->ModelIsUnsat())
return;
4329 const LinearConstraintProto& arg = ct->linear();
4330 const int num_vars = arg.vars_size();
4334 if (num_vars <= 1)
return;
4336 int64_t min_sum = 0;
4337 int64_t max_sum = 0;
4338 int64_t max_coeff_magnitude = 0;
4339 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4340 for (
int i = 0;
i < num_vars; ++
i) {
4341 const int ref = arg.vars(
i);
4342 const int64_t coeff = arg.coeffs(
i);
4344 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4345 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4346 min_sum += coeff * context_->MinOf(ref);
4347 max_sum += coeff * context_->MaxOf(ref);
4349 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4350 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4351 min_sum += coeff * context_->MaxOf(ref);
4352 max_sum += coeff * context_->MinOf(ref);
4355 if (max_coeff_magnitude == 1)
return;
4363 const auto& domain = ct->linear().domain();
4364 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4365 const int64_t lb_threshold = max_sum - domain[1];
4366 if (max_coeff_magnitude + min_coeff_magnitude <
4367 std::max(ub_threshold, lb_threshold)) {
4372 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4373 min_coeff_magnitude < max_coeff_magnitude) {
4374 const int64_t rhs_min = domain[0];
4375 const int64_t rhs_max = domain[1];
4376 if (min_sum >= rhs_min &&
4377 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4378 LowerThanCoeffStrengthening(
true,
4379 min_coeff_magnitude, rhs_max - min_sum, ct);
4382 if (max_sum <= rhs_max &&
4383 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4384 LowerThanCoeffStrengthening(
false,
4385 min_coeff_magnitude, max_sum - rhs_min, ct);
4405 const bool lower_bounded = min_sum < rhs_domain.
Min();
4406 const bool upper_bounded = max_sum > rhs_domain.
Max();
4407 if (!lower_bounded && !upper_bounded)
return;
4408 if (lower_bounded && upper_bounded) {
4413 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold))
return;
4415 context_->UpdateRuleStats(
"linear: split boxed constraint");
4416 ConstraintProto* new_ct1 = context_->working_model->add_constraints();
4418 if (!ct->name().empty()) {
4419 new_ct1->set_name(absl::StrCat(ct->name(),
" (part 1)"));
4422 new_ct1->mutable_linear());
4424 ConstraintProto* new_ct2 = context_->working_model->add_constraints();
4426 if (!ct->name().empty()) {
4427 new_ct2->set_name(absl::StrCat(ct->name(),
" (part 2)"));
4430 new_ct2->mutable_linear());
4432 context_->UpdateNewConstraintsVariableUsage();
4434 context_->UpdateConstraintVariableUsage(ct_index);
4441 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4449 int64_t second_threshold =
4451 threshold - min_coeff_magnitude);
4459 if (rhs_domain.NumIntervals() > 1) {
4460 second_threshold = threshold;
4468 const bool only_extract_booleans =
4469 !context_->params().presolve_extract_integer_enforcement() ||
4470 context_->ModelIsExpanded();
4475 int64_t rhs_offset = 0;
4476 bool some_integer_encoding_were_extracted =
false;
4477 LinearConstraintProto* mutable_arg = ct->mutable_linear();
4478 for (
int i = 0;
i < arg.vars_size(); ++
i) {
4479 int ref = arg.vars(
i);
4480 int64_t coeff = arg.coeffs(
i);
4488 const bool is_boolean = context_->CanBeUsedAsLiteral(ref);
4489 if (context_->IsFixed(ref) || coeff < threshold ||
4490 (only_extract_booleans && !is_boolean)) {
4491 mutable_arg->set_vars(new_size, mutable_arg->vars(
i));
4493 int64_t new_magnitude = std::abs(arg.coeffs(
i));
4494 if (coeff > threshold) {
4497 new_magnitude = threshold;
4498 context_->UpdateRuleStats(
"linear: coefficient strenghtening.");
4499 }
else if (coeff > second_threshold && coeff < threshold) {
4502 new_magnitude = second_threshold;
4503 context_->UpdateRuleStats(
4504 "linear: advanced coefficient strenghtening.");
4506 if (coeff != new_magnitude) {
4507 if (lower_bounded) {
4509 rhs_offset -= (coeff - new_magnitude) * context_->MinOf(ref);
4512 rhs_offset -= (coeff - new_magnitude) * context_->MaxOf(ref);
4516 mutable_arg->set_coeffs(
4517 new_size, arg.coeffs(
i) > 0 ? new_magnitude : -new_magnitude);
4523 context_->UpdateRuleStats(
"linear: extracted enforcement literal");
4525 some_integer_encoding_were_extracted =
true;
4526 context_->UpdateRuleStats(
4527 "linear: extracted integer enforcement literal");
4529 if (lower_bounded) {
4530 ct->add_enforcement_literal(is_boolean
4532 : context_->GetOrCreateVarValueEncoding(
4533 ref, context_->MinOf(ref)));
4534 rhs_offset -= coeff * context_->MinOf(ref);
4536 ct->add_enforcement_literal(is_boolean
4538 : context_->GetOrCreateVarValueEncoding(
4539 ref, context_->MaxOf(ref)));
4540 rhs_offset -= coeff * context_->MaxOf(ref);
4543 mutable_arg->mutable_vars()->Truncate(new_size);
4544 mutable_arg->mutable_coeffs()->Truncate(new_size);
4546 if (some_integer_encoding_were_extracted || new_size == 1) {
4547 context_->UpdateConstraintVariableUsage(ct_index);
4548 context_->UpdateNewConstraintsVariableUsage();
4552void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto* ct) {
4553 if (context_->ModelIsUnsat())
return;
4557 const LinearConstraintProto& arg = ct->linear();
4558 const int num_vars = arg.vars_size();
4559 int64_t min_sum = 0;
4560 int64_t max_sum = 0;
4561 for (
int i = 0;
i < num_vars; ++
i) {
4562 const int ref = arg.vars(
i);
4563 const int64_t coeff = arg.coeffs(
i);
4564 const int64_t term_a = coeff * context_->MinOf(ref);
4565 const int64_t term_b = coeff * context_->MaxOf(ref);
4566 min_sum += std::min(term_a, term_b);
4567 max_sum += std::max(term_a, term_b);
4569 for (
const int type : {0, 1}) {
4570 std::vector<int> at_most_one;
4571 for (
int i = 0;
i < num_vars; ++
i) {
4572 const int ref = arg.vars(
i);
4573 const int64_t coeff = arg.coeffs(
i);
4574 if (context_->MinOf(ref) != 0)
continue;
4575 if (context_->MaxOf(ref) != 1)
continue;
4580 if (min_sum + 2 * std::abs(coeff) > rhs.
Max()) {
4581 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
4584 if (max_sum - 2 * std::abs(coeff) < rhs.
Min()) {
4585 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
4589 if (at_most_one.size() > 1) {
4591 context_->UpdateRuleStats(
"linear: extracted at most one (max).");
4593 context_->UpdateRuleStats(
"linear: extracted at most one (min).");
4595 ConstraintProto* new_ct = context_->working_model->add_constraints();
4596 new_ct->set_name(ct->name());
4597 for (
const int ref : at_most_one) {
4598 new_ct->mutable_at_most_one()->add_literals(ref);
4600 context_->UpdateNewConstraintsVariableUsage();
4607bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto* ct) {
4608 if (ct->constraint_case() != ConstraintProto::kLinear)
return false;
4609 if (context_->ModelIsUnsat())
return false;
4612 int64_t sum_of_coeffs = 0;
4613 int num_positive = 0;
4614 int num_negative = 0;
4616 const LinearConstraintProto& arg = ct->linear();
4617 const int num_vars = arg.vars_size();
4618 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4619 int64_t max_coeff = 0;
4620 int64_t min_sum = 0;
4621 int64_t max_sum = 0;
4622 for (
int i = 0;
i < num_vars; ++
i) {
4624 const int var = arg.vars(
i);
4625 const int64_t coeff = arg.coeffs(
i);
4628 if (context_->MinOf(var) != 0)
return false;
4629 if (context_->MaxOf(var) != 1)
return false;
4631 sum_of_coeffs += coeff;
4635 min_coeff = std::min(min_coeff, coeff);
4636 max_coeff = std::max(max_coeff, coeff);
4641 min_coeff = std::min(min_coeff, -coeff);
4642 max_coeff = std::max(max_coeff, -coeff);
4645 CHECK_LE(min_coeff, max_coeff);
4654 if ((!rhs_domain.
Contains(min_sum) &&
4655 min_sum + min_coeff > rhs_domain.
Max()) ||
4657 max_sum - min_coeff < rhs_domain.
Min())) {
4658 context_->UpdateRuleStats(
"linear: all booleans and trivially false");
4659 return MarkConstraintAsFalse(ct);
4661 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4662 context_->UpdateRuleStats(
"linear: all booleans and trivially true");
4663 return RemoveConstraint(ct);
4673 if (ct->enforcement_literal().empty() && sum_of_coeffs == 0 &&
4674 (num_negative == 1 || num_positive == 1) && rhs_domain.IsFixed() &&
4675 rhs_domain.FixedValue() == 0) {
4677 context_->UpdateRuleStats(
"linear: all equivalent!");
4678 for (
int i = 1;
i < num_vars; ++
i) {
4679 if (!context_->StoreBooleanEqualityRelation(ct->linear().vars(0),
4680 ct->linear().vars(
i))) {
4684 return RemoveConstraint(ct);
4691 DCHECK(!rhs_domain.IsEmpty());
4692 if (min_sum + min_coeff > rhs_domain.
Max()) {
4694 context_->UpdateRuleStats(
"linear: negative reified and");
4695 const auto copy = arg;
4696 ct->mutable_bool_and()->clear_literals();
4697 for (
int i = 0;
i < num_vars; ++
i) {
4698 ct->mutable_bool_and()->add_literals(
4699 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4701 PresolveBoolAnd(ct);
4703 }
else if (max_sum - min_coeff < rhs_domain.
Min()) {
4705 context_->UpdateRuleStats(
"linear: positive reified and");
4706 const auto copy = arg;
4707 ct->mutable_bool_and()->clear_literals();
4708 for (
int i = 0;
i < num_vars; ++
i) {
4709 ct->mutable_bool_and()->add_literals(
4710 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4712 PresolveBoolAnd(ct);
4714 }
else if (min_sum + min_coeff >= rhs_domain.
Min() &&
4715 rhs_domain.front().end >= max_sum) {
4717 context_->UpdateRuleStats(
"linear: positive clause");
4718 const auto copy = arg;
4719 ct->mutable_bool_or()->clear_literals();
4720 for (
int i = 0;
i < num_vars; ++
i) {
4721 ct->mutable_bool_or()->add_literals(
4722 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4726 }
else if (max_sum - min_coeff <= rhs_domain.
Max() &&
4727 rhs_domain.back().start <= min_sum) {
4729 context_->UpdateRuleStats(
"linear: negative clause");
4730 const auto copy = arg;
4731 ct->mutable_bool_or()->clear_literals();
4732 for (
int i = 0;
i < num_vars; ++
i) {
4733 ct->mutable_bool_or()->add_literals(
4734 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4739 min_sum + max_coeff <= rhs_domain.
Max() &&
4740 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
4741 rhs_domain.back().start <= min_sum) {
4744 context_->UpdateRuleStats(
"linear: positive at most one");
4745 const auto copy = arg;
4746 ct->mutable_at_most_one()->clear_literals();
4747 for (
int i = 0;
i < num_vars; ++
i) {
4748 ct->mutable_at_most_one()->add_literals(
4749 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4753 max_sum - max_coeff >= rhs_domain.
Min() &&
4754 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
4755 rhs_domain.front().end >= max_sum) {
4758 context_->UpdateRuleStats(
"linear: negative at most one");
4759 const auto copy = arg;
4760 ct->mutable_at_most_one()->clear_literals();
4761 for (
int i = 0;
i < num_vars; ++
i) {
4762 ct->mutable_at_most_one()->add_literals(
4763 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4767 min_sum < rhs_domain.
Min() &&
4768 min_sum + min_coeff >= rhs_domain.
Min() &&
4769 min_sum + 2 * min_coeff > rhs_domain.
Max() &&
4770 min_sum + max_coeff <= rhs_domain.
Max()) {
4772 context_->UpdateRuleStats(
"linear: positive equal one");
4773 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4774 exactly_one->set_name(ct->name());
4775 for (
int i = 0;
i < num_vars; ++
i) {
4776 exactly_one->mutable_exactly_one()->add_literals(
4779 context_->UpdateNewConstraintsVariableUsage();
4780 return RemoveConstraint(ct);
4782 max_sum > rhs_domain.
Max() &&
4783 max_sum - min_coeff <= rhs_domain.
Max() &&
4784 max_sum - 2 * min_coeff < rhs_domain.
Min() &&
4785 max_sum - max_coeff >= rhs_domain.
Min()) {
4787 context_->UpdateRuleStats(
"linear: negative equal one");
4788 ConstraintProto* exactly_one = context_->working_model->add_constraints();
4789 exactly_one->set_name(ct->name());
4790 for (
int i = 0;
i < num_vars; ++
i) {
4791 exactly_one->mutable_exactly_one()->add_literals(
4794 context_->UpdateNewConstraintsVariableUsage();
4795 return RemoveConstraint(ct);
4802 if (num_vars > 3)
return false;
4803 context_->UpdateRuleStats(
"linear: small Boolean expression");
4807 const int max_mask = (1 << arg.vars_size());
4808 for (
int mask = 0; mask < max_mask; ++mask) {
4810 for (
int i = 0;
i < num_vars; ++
i) {
4811 if ((mask >>
i) & 1) value += arg.coeffs(
i);
4813 if (rhs_domain.
Contains(value))
continue;
4816 ConstraintProto* new_ct = context_->working_model->add_constraints();
4817 auto* new_arg = new_ct->mutable_bool_or();
4819 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
4821 for (
int i = 0;
i < num_vars; ++
i) {
4822 new_arg->add_literals(((mask >>
i) & 1) ?
NegatedRef(arg.vars(
i))
4827 context_->UpdateNewConstraintsVariableUsage();
4828 return RemoveConstraint(ct);
4831bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto* ct) {
4832 if (context_->ModelIsUnsat())
return false;
4833 IntervalConstraintProto* interval = ct->mutable_interval();
4836 if (!ct->enforcement_literal().empty() && context_->SizeMax(c) < 0) {
4837 context_->UpdateRuleStats(
"interval: negative size implies unperformed");
4838 return MarkConstraintAsFalse(ct);
4841 if (ct->enforcement_literal().empty()) {
4842 bool domain_changed =
false;
4844 if (!context_->IntersectDomainWith(
4845 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4849 if (domain_changed) {
4850 context_->UpdateRuleStats(
4851 "interval: performed intervals must have a positive size");
4857 if (context_->ConstraintVariableGraphIsUpToDate() &&
4858 context_->IntervalUsage(c) == 0) {
4859 context_->UpdateRuleStats(
"intervals: removed unused interval");
4860 return RemoveConstraint(ct);
4863 bool changed =
false;
4864 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_start());
4865 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_size());
4866 changed |= CanonicalizeLinearExpression(*ct, interval->mutable_end());
4871bool CpModelPresolver::PresolveInverse(ConstraintProto* ct) {
4872 const int size = ct->inverse().f_direct().size();
4873 bool changed =
false;
4876 for (
const int ref : ct->inverse().f_direct()) {
4877 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4878 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4882 for (
const int ref : ct->inverse().f_inverse()) {
4883 if (!context_->IntersectDomainWith(ref, Domain(0, size - 1), &changed)) {
4884 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4894 absl::flat_hash_set<int> direct_vars;
4895 for (
const int ref : ct->inverse().f_direct()) {
4896 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
4898 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
4902 absl::flat_hash_set<int> inverse_vars;
4903 for (
const int ref : ct->inverse().f_inverse()) {
4904 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
4906 return context_->NotifyThatModelIsUnsat(
"inverse: duplicated variable");
4914 const auto filter_inverse_domain =
4915 [
this, size, &changed](
const auto& direct,
const auto& inverse) {
4917 std::vector<absl::flat_hash_set<int64_t>> inverse_values(size);
4918 for (
int i = 0;
i < size; ++
i) {
4919 const Domain domain = context_->DomainOf(inverse[
i]);
4920 for (
const int64_t j : domain.Values()) {
4921 inverse_values[
i].insert(j);
4928 std::vector<int64_t> possible_values;
4929 for (
int i = 0;
i < size; ++
i) {
4930 possible_values.clear();
4931 const Domain domain = context_->DomainOf(direct[
i]);
4932 bool removed_value =
false;
4933 for (
const int64_t j : domain.Values()) {
4934 if (inverse_values[j].contains(
i)) {
4935 possible_values.push_back(j);
4937 removed_value =
true;
4940 if (removed_value) {
4942 if (!context_->IntersectDomainWith(
4944 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4952 if (!filter_inverse_domain(ct->inverse().f_direct(),
4953 ct->inverse().f_inverse())) {
4957 if (!filter_inverse_domain(ct->inverse().f_inverse(),
4958 ct->inverse().f_direct())) {
4963 context_->UpdateRuleStats(
"inverse: reduce domains");
4969bool CpModelPresolver::PresolveElement(
int c, ConstraintProto* ct) {
4970 if (context_->ModelIsUnsat())
return false;
4972 if (ct->element().exprs().empty()) {
4973 context_->UpdateRuleStats(
"element: empty array");
4974 return context_->NotifyThatModelIsUnsat();
4977 bool changed =
false;
4978 changed |= CanonicalizeLinearExpression(
4979 *ct, ct->mutable_element()->mutable_linear_index());
4980 changed |= CanonicalizeLinearExpression(
4981 *ct, ct->mutable_element()->mutable_linear_target());
4982 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
4983 changed |= CanonicalizeLinearExpression(
4984 *ct, ct->mutable_element()->mutable_exprs(
i));
4987 const LinearExpressionProto& index = ct->element().linear_index();
4988 const LinearExpressionProto& target = ct->element().linear_target();
4995 bool index_modified =
false;
4996 if (!context_->IntersectDomainWith(
4997 index, Domain(0, ct->element().exprs_size() - 1),
5001 if (index_modified) {
5002 context_->UpdateRuleStats(
5003 "element: reduced index domain from array size");
5008 if (context_->IsFixed(index)) {
5009 const int64_t index_value = context_->FixedValue(index);
5010 ConstraintProto* new_ct = context_->working_model->add_constraints();
5011 new_ct->mutable_linear()->add_domain(0);
5012 new_ct->mutable_linear()->add_domain(0);
5015 new_ct->mutable_linear());
5016 context_->CanonicalizeLinearConstraint(new_ct);
5017 context_->UpdateNewConstraintsVariableUsage();
5018 context_->UpdateRuleStats(
"element: fixed index");
5019 return RemoveConstraint(ct);
5023 const int index_var = index.vars(0);
5027 const Domain& index_var_domain = context_->DomainOf(index_var);
5028 std::vector<int64_t> reached_indices(ct->element().exprs_size(),
false);
5029 for (
const int64_t index_var_value : index_var_domain.Values()) {
5030 const int64_t index_value =
5032 reached_indices[index_value] =
true;
5033 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5034 if (expr.vars_size() == 1 && expr.vars(0) == index_var) {
5035 const int64_t expr_value =
5037 ct->mutable_element()->mutable_exprs(index_value)->clear_vars();
5038 ct->mutable_element()->mutable_exprs(index_value)->clear_coeffs();
5039 ct->mutable_element()
5040 ->mutable_exprs(index_value)
5041 ->set_offset(expr_value);
5043 context_->UpdateRuleStats(
5044 "element: fix expression depending on the index");
5049 for (
int i = 0;
i < ct->element().exprs_size(); ++
i) {
5050 if (!reached_indices[
i]) {
5051 ct->mutable_element()->mutable_exprs(
i)->Clear();
5059 if (changed) context_->UpdateConstraintVariableUsage(c);
5063 const Domain& index_var_domain = context_->DomainOf(index_var);
5064 const Domain& target_domain = context_->DomainSuperSetOf(target);
5065 std::vector<int64_t> possible_index_var_values;
5066 for (
const int64_t index_var_value : index_var_domain.Values()) {
5067 const int64_t index_value =
5069 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5071 bool is_possible_index;
5072 if (target.vars_size() == 1 && target.vars(0) == index_var) {
5075 is_possible_index = context_->DomainContains(
5078 const Domain target_var_domain =
5079 target.vars_size() == 1 ? context_->DomainOf(target.vars(0))
5081 const Domain expr_var_domain = expr.vars_size() == 1
5082 ? context_->DomainOf(expr.vars(0))
5084 const int64_t target_coeff =
5085 target.vars_size() == 1 ? target.coeffs(0) : 0;
5086 const int64_t expr_coeff = expr.vars_size() == 1 ? expr.coeffs(0) : 0;
5088 target_var_domain, target_coeff, expr_var_domain, -expr_coeff,
5089 -target.offset() + expr.offset());
5092 if (is_possible_index) {
5093 possible_index_var_values.push_back(index_var_value);
5095 ct->mutable_element()->mutable_exprs(index_value)->Clear();
5099 if (possible_index_var_values.size() < index_var_domain.Size()) {
5100 if (!context_->IntersectDomainWith(
5104 context_->UpdateRuleStats(
"element: reduced index domain ");
5106 if (context_->IsFixed(index)) {
5107 ConstraintProto*
const eq = context_->working_model->add_constraints();
5108 eq->mutable_linear()->add_domain(0);
5109 eq->mutable_linear()->add_domain(0);
5112 ct->element().exprs(context_->FixedValue(index)), -1,
5113 eq->mutable_linear());
5114 context_->CanonicalizeLinearConstraint(eq);
5115 context_->UpdateNewConstraintsVariableUsage();
5116 context_->UpdateRuleStats(
"element: fixed index");
5117 return RemoveConstraint(ct);
5122 bool all_included_in_target_domain =
true;
5125 Domain infered_domain;
5126 const Domain& index_var_domain = context_->DomainOf(index_var);
5127 const Domain& target_domain = context_->DomainSuperSetOf(target);
5128 for (
const int64_t index_var_value : index_var_domain.Values()) {
5129 const int64_t index_value =
5131 CHECK_GE(index_value, 0);
5132 CHECK_LT(index_value, ct->element().exprs_size());
5133 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5134 const Domain expr_domain = context_->DomainSuperSetOf(expr);
5135 if (!expr_domain.IsIncludedIn(target_domain)) {
5136 all_included_in_target_domain =
false;
5138 infered_domain = infered_domain.UnionWith(expr_domain);
5141 bool domain_modified =
false;
5142 if (!context_->IntersectDomainWith(target, infered_domain,
5143 &domain_modified)) {
5146 if (domain_modified) {
5147 context_->UpdateRuleStats(
"element: reduce target domain");
5151 bool all_constants =
true;
5153 const Domain& index_var_domain = context_->DomainOf(index_var);
5154 std::vector<int64_t> expr_constants;
5156 for (
const int64_t index_var_value : index_var_domain.Values()) {
5157 const int64_t index_value =
5159 const LinearExpressionProto& expr = ct->element().exprs(index_value);
5160 if (context_->IsFixed(expr)) {
5161 expr_constants.push_back(context_->FixedValue(expr));
5163 all_constants =
false;
5170 if (all_constants) {
5171 if (context_->IsFixed(target)) {
5175 context_->UpdateRuleStats(
"element: one value array");
5176 return RemoveConstraint(ct);
5178 int64_t first_index_var_value;
5179 int64_t first_target_var_value;
5180 int64_t d_index = 0;
5181 int64_t d_target = 0;
5183 bool is_affine =
true;
5184 const Domain& index_var_domain = context_->DomainOf(index_var);
5185 for (
const int64_t index_var_value : index_var_domain.Values()) {
5187 const int64_t index_value =
5189 const int64_t expr_value =
5190 context_->FixedValue(ct->element().exprs(index_value));
5192 if (num_terms == 1) {
5193 first_index_var_value = index_var_value;
5194 first_target_var_value = target_var_value;
5195 }
else if (num_terms == 2) {
5196 d_index = index_var_value - first_index_var_value;
5197 d_target = target_var_value - first_target_var_value;
5198 const int64_t gcd = std::gcd(d_index, d_target);
5202 const int64_t offset =
CapSub(
5203 CapProd(d_index,
CapSub(target_var_value, first_target_var_value)),
5204 CapProd(d_target,
CapSub(index_var_value, first_index_var_value)));
5212 const int64_t offset =
CapSub(
CapProd(first_target_var_value, d_index),
5213 CapProd(first_index_var_value, d_target));
5215 ConstraintProto*
const lin = context_->working_model->add_constraints();
5216 lin->mutable_linear()->add_vars(target.vars(0));
5217 lin->mutable_linear()->add_coeffs(d_index);
5218 lin->mutable_linear()->add_vars(index_var);
5219 lin->mutable_linear()->add_coeffs(-d_target);
5220 lin->mutable_linear()->add_domain(offset);
5221 lin->mutable_linear()->add_domain(offset);
5222 context_->CanonicalizeLinearConstraint(lin);
5223 context_->UpdateNewConstraintsVariableUsage();
5224 context_->UpdateRuleStats(
"element: rewrite as affine constraint");
5225 return RemoveConstraint(ct);
5237 absl::flat_hash_map<int, int> local_var_occurrence_counter;
5239 auto count = [&local_var_occurrence_counter](
5240 const LinearExpressionProto& expr)
mutable {
5241 for (
const int var : expr.vars()) {
5242 local_var_occurrence_counter[var]++;
5247 for (
const int64_t index_var_value :
5248 context_->DomainOf(index_var).Values()) {
5254 if (context_->VariableIsUniqueAndRemovable(index_var) &&
5255 local_var_occurrence_counter.at(index_var) == 1) {
5256 if (all_constants) {
5260 context_->UpdateRuleStats(
5261 "element: removed as the index is not used elsewhere");
5262 context_->MarkVariableAsRemoved(index_var);
5263 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5264 return RemoveConstraint(ct);
5266 context_->UpdateRuleStats(
"TODO element: index not used elsewhere");
5270 if (target.vars_size() == 1 && !context_->IsFixed(target.vars(0)) &&
5271 context_->VariableIsUniqueAndRemovable(target.vars(0)) &&
5272 local_var_occurrence_counter.at(target.vars(0)) == 1) {
5273 if (all_included_in_target_domain && std::abs(target.coeffs(0)) == 1) {
5274 context_->UpdateRuleStats(
5275 "element: removed as the target is not used elsewhere");
5276 context_->MarkVariableAsRemoved(target.vars(0));
5277 context_->NewMappingConstraint(*ct, __FILE__, __LINE__);
5278 return RemoveConstraint(ct);
5280 context_->UpdateRuleStats(
"TODO element: target not used elsewhere");
5287bool CpModelPresolver::PresolveTable(ConstraintProto* ct) {
5288 if (context_->ModelIsUnsat())
return false;
5290 bool changed =
false;
5291 for (
int i = 0;
i < ct->table().exprs_size(); ++
i) {
5292 changed |= CanonicalizeLinearExpression(
5293 *ct, ct->mutable_table()->mutable_exprs(
i));
5296 const int initial_num_exprs = ct->table().exprs_size();
5298 changed |= (ct->table().exprs_size() != initial_num_exprs);
5300 if (ct->table().exprs().empty()) {
5301 context_->UpdateRuleStats(
"table: no expressions");
5302 return RemoveConstraint(ct);
5305 if (ct->table().values().empty()) {
5306 if (ct->table().negated()) {
5307 context_->UpdateRuleStats(
"table: negative table without tuples");
5308 return RemoveConstraint(ct);
5310 context_->UpdateRuleStats(
"table: positive table without tuples");
5311 return MarkConstraintAsFalse(ct);
5315 int num_fixed_exprs = 0;
5316 for (
const LinearExpressionProto& expr : ct->table().exprs()) {
5317 if (context_->IsFixed(expr)) ++num_fixed_exprs;
5319 if (num_fixed_exprs == ct->table().exprs_size()) {
5320 context_->UpdateRuleStats(
"table: all expressions are fixed");
5321 DCHECK_LE(ct->table().values_size(), num_fixed_exprs);
5322 if (ct->table().negated() == ct->table().values().empty()) {
5323 context_->UpdateRuleStats(
"table: always true");
5324 return RemoveConstraint(ct);
5326 context_->UpdateRuleStats(
"table: always false");
5327 return MarkConstraintAsFalse(ct);
5329 return RemoveConstraint(ct);
5332 if (num_fixed_exprs > 0) {
5337 if (ct->table().negated())
return changed;
5343 const int num_exprs = ct->table().exprs_size();
5344 const int num_tuples = ct->table().values_size() / num_exprs;
5345 std::vector<std::vector<int64_t>> new_domains(num_exprs);
5346 for (
int e = 0; e < num_exprs; ++e) {
5347 const LinearExpressionProto& expr = ct->table().exprs(e);
5348 if (context_->IsFixed(expr)) {
5349 new_domains[e].push_back(context_->FixedValue(expr));
5353 for (
int t = 0; t < num_tuples; ++t) {
5354 new_domains[e].push_back(ct->table().values(t * num_exprs + e));
5357 DCHECK_EQ(1, expr.vars_size());
5358 DCHECK_EQ(1, expr.coeffs(0));
5359 DCHECK_EQ(0, expr.offset());
5360 const int var = expr.vars(0);
5361 bool domain_modified =
false;
5363 &domain_modified)) {
5366 if (domain_modified) {
5367 context_->UpdateRuleStats(
"table: reduce variable domain");
5371 if (num_exprs == 1) {
5374 context_->UpdateRuleStats(
"table: only one column!");
5375 return RemoveConstraint(ct);
5380 for (
int e = 0; e < num_exprs; ++e) prod *= new_domains[e].size();
5381 if (prod ==
static_cast<double>(num_tuples)) {
5382 context_->UpdateRuleStats(
"table: all tuples!");
5383 return RemoveConstraint(ct);
5389 if (
static_cast<double>(num_tuples) > 0.7 * prod) {
5390 std::vector<std::vector<int64_t>> current_tuples(num_tuples);
5391 for (
int t = 0; t < num_tuples; ++t) {
5392 current_tuples[t].resize(num_exprs);
5393 for (
int e = 0; e < num_exprs; ++e) {
5394 current_tuples[t][e] = ct->table().values(t * num_exprs + e);
5399 std::vector<std::vector<int64_t>> var_to_values(num_exprs);
5400 for (
int e = 0; e < num_exprs; ++e) {
5401 var_to_values[e].assign(new_domains[e].begin(), new_domains[e].end());
5403 std::vector<std::vector<int64_t>> all_tuples(prod);
5404 for (
int i = 0;
i < prod; ++
i) {
5405 all_tuples[
i].resize(num_exprs);
5407 for (
int j = 0; j < num_exprs; ++j) {
5408 all_tuples[
i][j] = var_to_values[j][index % var_to_values[j].size()];
5409 index /= var_to_values[j].size();
5415 std::vector<std::vector<int64_t>> diff(prod - num_tuples);
5416 std::set_difference(all_tuples.begin(), all_tuples.end(),
5417 current_tuples.begin(), current_tuples.end(),
5421 ct->mutable_table()->set_negated(!ct->table().negated());
5422 ct->mutable_table()->clear_values();
5423 for (
const std::vector<int64_t>& t : diff) {
5424 for (
const int64_t v : t) ct->mutable_table()->add_values(v);
5426 context_->UpdateRuleStats(
"table: negated");
5432bool CpModelPresolver::PresolveAllDiff(ConstraintProto* ct) {
5433 if (context_->ModelIsUnsat())
return false;
5436 AllDifferentConstraintProto& all_diff = *ct->mutable_all_diff();
5438 bool constraint_has_changed =
false;
5439 for (LinearExpressionProto& exp :
5440 *(ct->mutable_all_diff()->mutable_exprs())) {
5441 constraint_has_changed |= CanonicalizeLinearExpression(*ct, &exp);
5445 const int size = all_diff.exprs_size();
5447 context_->UpdateRuleStats(
"all_diff: empty constraint");
5448 return RemoveConstraint(ct);
5451 context_->UpdateRuleStats(
"all_diff: only one variable");
5452 return RemoveConstraint(ct);
5455 bool something_was_propagated =
false;
5456 std::vector<LinearExpressionProto> kept_expressions;
5457 for (
int i = 0;
i < size; ++
i) {
5458 if (!context_->IsFixed(all_diff.exprs(
i))) {
5459 kept_expressions.push_back(all_diff.exprs(
i));
5463 const int64_t value = context_->MinOf(all_diff.exprs(
i));
5464 bool propagated =
false;
5465 for (
int j = 0; j < size; ++j) {
5466 if (
i == j)
continue;
5467 if (context_->DomainContains(all_diff.exprs(j), value)) {
5468 if (!context_->IntersectDomainWith(all_diff.exprs(j),
5469 Domain(value).Complement())) {
5476 context_->UpdateRuleStats(
"all_diff: propagated fixed expressions");
5477 something_was_propagated =
true;
5484 kept_expressions.begin(), kept_expressions.end(),
5485 [](
const LinearExpressionProto& expr_a,
5486 const LinearExpressionProto& expr_b) {
5487 DCHECK_EQ(expr_a.vars_size(), 1);
5488 DCHECK_EQ(expr_b.vars_size(), 1);
5489 const int ref_a = expr_a.vars(0);
5490 const int ref_b = expr_b.vars(0);
5491 const int64_t coeff_a = expr_a.coeffs(0);
5492 const int64_t coeff_b = expr_b.coeffs(0);
5493 const int64_t abs_coeff_a = std::abs(coeff_a);
5494 const int64_t abs_coeff_b = std::abs(coeff_b);
5495 const int64_t offset_a = expr_a.offset();
5496 const int64_t offset_b = expr_b.offset();
5497 const int64_t abs_offset_a = std::abs(offset_a);
5498 const int64_t abs_offset_b = std::abs(offset_b);
5499 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
5500 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
5506 for (
int i = 1;
i < kept_expressions.size(); ++
i) {
5508 kept_expressions[
i - 1], 1)) {
5509 return context_->NotifyThatModelIsUnsat(
5510 "Duplicate variable in all_diff");
5513 kept_expressions[
i - 1], -1)) {
5514 bool domain_modified =
false;
5515 if (!context_->IntersectDomainWith(kept_expressions[
i],
5516 Domain(0).Complement(),
5517 &domain_modified)) {
5520 if (domain_modified) {
5521 context_->UpdateRuleStats(
5522 "all_diff: remove 0 from expression appearing with its "
5528 if (kept_expressions.size() < all_diff.exprs_size()) {
5529 all_diff.clear_exprs();
5530 for (
const LinearExpressionProto& expr : kept_expressions) {
5531 *all_diff.add_exprs() = expr;
5533 context_->UpdateRuleStats(
"all_diff: removed fixed variables");
5534 something_was_propagated =
true;
5535 constraint_has_changed =
true;
5536 if (kept_expressions.size() <= 1)
continue;
5540 CHECK_GE(all_diff.exprs_size(), 2);
5541 Domain domain = context_->DomainSuperSetOf(all_diff.exprs(0));
5542 for (
int i = 1;
i < all_diff.exprs_size(); ++
i) {
5543 domain = domain.UnionWith(context_->DomainSuperSetOf(all_diff.exprs(
i)));
5545 if (all_diff.exprs_size() == domain.Size()) {
5546 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
5548 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
5549 for (
const int64_t v : context_->DomainOf(expr.vars(0)).Values()) {
5550 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
5553 bool propagated =
false;
5554 for (
const auto& it : value_to_exprs) {
5555 if (it.second.size() == 1 && !context_->IsFixed(it.second.front())) {
5556 const LinearExpressionProto& expr = it.second.front();
5557 if (!context_->IntersectDomainWith(expr, Domain(it.first))) {
5564 context_->UpdateRuleStats(
5565 "all_diff: propagated mandatory values in permutation");
5566 something_was_propagated =
true;
5569 if (!something_was_propagated)
break;
5572 return constraint_has_changed;
5579void AddImplication(
int lhs,
int rhs, CpModelProto* proto,
5580 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5581 if (ref_to_bool_and->contains(lhs)) {
5582 const int ct_index = (*ref_to_bool_and)[lhs];
5583 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(rhs);
5584 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
5585 const int ct_index = (*ref_to_bool_and)[
NegatedRef(rhs)];
5586 proto->mutable_constraints(ct_index)->mutable_bool_and()->add_literals(
5589 (*ref_to_bool_and)[lhs] = proto->constraints_size();
5590 ConstraintProto* ct = proto->add_constraints();
5591 ct->add_enforcement_literal(lhs);
5592 ct->mutable_bool_and()->add_literals(rhs);
5596template <
typename ClauseContainer>
5597void ExtractClauses(
bool merge_into_bool_and,
5598 absl::Span<const int> index_mapping,
5599 const ClauseContainer& container, CpModelProto* proto,
5600 std::string_view debug_name =
"") {
5607 absl::flat_hash_map<int, int> ref_to_bool_and;
5608 for (
int i = 0;
i < container.NumClauses(); ++
i) {
5609 const std::vector<Literal>& clause = container.Clause(
i);
5610 if (clause.empty())
continue;
5615 if (merge_into_bool_and && clause.size() == 2) {
5616 const int var_a = index_mapping[clause[0].Variable().value()];
5617 const int var_b = index_mapping[clause[1].Variable().value()];
5618 const int ref_a = clause[0].IsPositive() ? var_a :
NegatedRef(var_a);
5619 const int ref_b = clause[1].IsPositive() ? var_b :
NegatedRef(var_b);
5620 AddImplication(
NegatedRef(ref_a), ref_b, proto, &ref_to_bool_and);
5625 ConstraintProto* ct = proto->add_constraints();
5626 if (!debug_name.empty()) {
5627 ct->set_name(std::string(debug_name));
5629 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5630 for (
const Literal l : clause) {
5631 const int var = index_mapping[l.Variable().value()];
5632 if (l.IsPositive()) {
5633 ct->mutable_bool_or()->add_literals(var);
5635 ct->mutable_bool_or()->add_literals(
NegatedRef(var));
5643bool CpModelPresolver::PresolveNoOverlap(ConstraintProto* ct) {
5644 if (context_->ModelIsUnsat())
return false;
5645 NoOverlapConstraintProto* proto = ct->mutable_no_overlap();
5646 bool changed =
false;
5651 absl::flat_hash_set<int> visited_intervals;
5652 absl::flat_hash_set<int> duplicate_intervals;
5653 for (
const int interval_index : proto->intervals()) {
5654 if (context_->ConstraintIsInactive(interval_index))
continue;
5655 if (!visited_intervals.insert(interval_index).second) {
5656 duplicate_intervals.insert(interval_index);
5660 const int initial_num_intervals = proto->intervals_size();
5662 visited_intervals.clear();
5664 for (
int i = 0;
i < initial_num_intervals; ++
i) {
5665 const int interval_index = proto->intervals(
i);
5666 if (context_->ConstraintIsInactive(interval_index))
continue;
5668 if (duplicate_intervals.contains(interval_index)) {
5670 if (!visited_intervals.insert(interval_index).second)
continue;
5672 ConstraintProto* interval_ct =
5673 context_->working_model->mutable_constraints(interval_index);
5676 if (context_->SizeMin(interval_index) > 0) {
5677 if (!MarkConstraintAsFalse(interval_ct)) {
5680 context_->UpdateConstraintVariableUsage(interval_index);
5681 context_->UpdateRuleStats(
5682 "no_overlap: unperform duplicate non zero-sized intervals");
5688 if (context_->SizeMax(interval_index) > 0) {
5690 if (!context_->ConstraintIsOptional(interval_index)) {
5691 if (!context_->IntersectDomainWith(interval_ct->interval().size(),
5695 context_->UpdateRuleStats(
5696 "no_overlap: zero the size of performed duplicate intervals");
5700 const int performed_literal = interval_ct->enforcement_literal(0);
5701 ConstraintProto* size_eq_zero =
5702 context_->working_model->add_constraints();
5703 size_eq_zero->add_enforcement_literal(performed_literal);
5704 size_eq_zero->mutable_linear()->add_domain(0);
5705 size_eq_zero->mutable_linear()->add_domain(0);
5707 interval_ct->interval().size(), 1,
5708 size_eq_zero->mutable_linear());
5709 context_->UpdateRuleStats(
5710 "no_overlap: make duplicate intervals as unperformed or zero "
5712 context_->UpdateNewConstraintsVariableUsage();
5717 proto->set_intervals(new_size++, interval_index);
5720 if (new_size < initial_num_intervals) {
5721 proto->mutable_intervals()->Truncate(new_size);
5722 context_->UpdateRuleStats(
"no_overlap: removed absent intervals");
5728 if (proto->intervals_size() > 1) {
5729 std::vector<IndexedInterval> indexed_intervals;
5730 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
5731 const int index = proto->intervals(
i);
5732 indexed_intervals.push_back({index,
5733 IntegerValue(context_->StartMin(index)),
5734 IntegerValue(context_->EndMax(index))});
5736 std::vector<std::vector<int>> components;
5739 if (components.size() > 1) {
5740 for (
const std::vector<int>& intervals : components) {
5741 if (intervals.size() <= 1)
continue;
5743 NoOverlapConstraintProto* new_no_overlap =
5744 context_->working_model->add_constraints()->mutable_no_overlap();
5747 for (
const int i : intervals) {
5748 new_no_overlap->add_intervals(
i);
5751 context_->UpdateNewConstraintsVariableUsage();
5752 context_->UpdateRuleStats(
"no_overlap: split into disjoint components");
5753 return RemoveConstraint(ct);
5757 std::vector<int> constant_intervals;
5758 int64_t size_min_of_non_constant_intervals =
5759 std::numeric_limits<int64_t>::max();
5760 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
5761 const int interval_index = proto->intervals(
i);
5762 if (context_->IntervalIsConstant(interval_index)) {
5763 constant_intervals.push_back(interval_index);
5765 size_min_of_non_constant_intervals =
5766 std::min(size_min_of_non_constant_intervals,
5767 context_->SizeMin(interval_index));
5771 bool move_constraint_last =
false;
5772 if (!constant_intervals.empty()) {
5774 std::sort(constant_intervals.begin(), constant_intervals.end(),
5775 [
this](
int i1,
int i2) {
5776 const int64_t s1 = context_->StartMin(i1);
5777 const int64_t e1 = context_->EndMax(i1);
5778 const int64_t s2 = context_->StartMin(i2);
5779 const int64_t e2 = context_->EndMax(i2);
5780 return std::tie(s1, e1) < std::tie(s2, e2);
5786 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5787 if (context_->EndMax(constant_intervals[
i]) >
5788 context_->StartMin(constant_intervals[
i + 1])) {
5789 context_->UpdateRuleStats(
"no_overlap: constant intervals overlap");
5790 return context_->NotifyThatModelIsUnsat();
5794 if (constant_intervals.size() == proto->intervals_size()) {
5795 context_->UpdateRuleStats(
"no_overlap: no variable intervals");
5796 return RemoveConstraint(ct);
5799 absl::flat_hash_set<int> intervals_to_remove;
5803 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5804 const int start =
i;
5805 while (
i + 1 < constant_intervals.size() &&
5806 context_->StartMin(constant_intervals[
i + 1]) -
5807 context_->EndMax(constant_intervals[
i]) <
5808 size_min_of_non_constant_intervals) {
5811 if (
i == start)
continue;
5812 for (
int j = start; j <=
i; ++j) {
5813 intervals_to_remove.insert(constant_intervals[j]);
5815 const int64_t new_start = context_->StartMin(constant_intervals[start]);
5816 const int64_t new_end = context_->EndMax(constant_intervals[
i]);
5817 proto->add_intervals(context_->working_model->constraints_size());
5818 IntervalConstraintProto* new_interval =
5819 context_->working_model->add_constraints()->mutable_interval();
5820 new_interval->mutable_start()->set_offset(new_start);
5821 new_interval->mutable_size()->set_offset(new_end - new_start);
5822 new_interval->mutable_end()->set_offset(new_end);
5823 move_constraint_last =
true;
5827 if (!intervals_to_remove.empty()) {
5829 const int old_size = proto->intervals_size();
5830 for (
int i = 0;
i < old_size; ++
i) {
5831 const int interval_index = proto->intervals(
i);
5832 if (intervals_to_remove.contains(interval_index)) {
5835 proto->set_intervals(new_size++, interval_index);
5837 CHECK_LT(new_size, old_size);
5838 proto->mutable_intervals()->Truncate(new_size);
5839 context_->UpdateRuleStats(
5840 "no_overlap: merge constant contiguous intervals");
5841 intervals_to_remove.clear();
5842 constant_intervals.clear();
5844 context_->UpdateNewConstraintsVariableUsage();
5848 if (proto->intervals_size() == 1) {
5849 context_->UpdateRuleStats(
"no_overlap: only one interval");
5850 return RemoveConstraint(ct);
5852 if (proto->intervals().empty()) {
5853 context_->UpdateRuleStats(
"no_overlap: no intervals");
5854 return RemoveConstraint(ct);
5860 if (move_constraint_last) {
5862 *context_->working_model->add_constraints() = *ct;
5863 context_->UpdateNewConstraintsVariableUsage();
5864 return RemoveConstraint(ct);
5870bool CpModelPresolver::PresolveNoOverlap2DFramed(
5871 absl::Span<const Rectangle> fixed_boxes,
5872 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
5873 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
5875 DCHECK(!non_fixed_boxes.empty());
5876 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
5877 for (
const RectangleInRange& box : non_fixed_boxes) {
5878 bounding_box.GrowToInclude(box.bounding_area);
5880 std::vector<Rectangle> espace_for_single_box =
5881 FindEmptySpaces(bounding_box, {fixed_boxes.begin(), fixed_boxes.end()});
5884 std::vector<Rectangle> empty;
5887 if (espace_for_single_box.size() != 1) {
5891 Rectangle fixed_boxes_bb = fixed_boxes.front();
5892 for (
const Rectangle& box : fixed_boxes) {
5893 fixed_boxes_bb.GrowToInclude(box);
5895 const Rectangle framed_region = espace_for_single_box.front();
5896 for (
const RectangleInRange& box : non_fixed_boxes) {
5897 if (!box.bounding_area.IsInsideOf(fixed_boxes_bb)) {
5901 if (non_fixed_boxes.size() > 1 &&
5902 (2 * box.x_size <= framed_region.SizeX() ||
5903 2 * box.y_size <= framed_region.SizeY())) {
5908 const int x_interval_index = proto.x_intervals(box.box_index);
5909 const int y_interval_index = proto.y_intervals(box.box_index);
5910 if (!context_->working_model->constraints(x_interval_index)
5911 .enforcement_literal()
5913 !context_->working_model->constraints(y_interval_index)
5914 .enforcement_literal()
5916 if (context_->working_model->constraints(x_interval_index)
5917 .enforcement_literal(0) !=
5918 context_->working_model->constraints(y_interval_index)
5919 .enforcement_literal(0)) {
5929 std::vector<int> enforcement_literals_for_amo;
5930 bool has_mandatory =
false;
5931 for (
const RectangleInRange& box : non_fixed_boxes) {
5932 const int box_index = box.box_index;
5933 const int x_interval_index = proto.x_intervals(box_index);
5934 const int y_interval_index = proto.y_intervals(box_index);
5935 const ConstraintProto& x_interval_ct =
5936 context_->working_model->constraints(x_interval_index);
5937 const ConstraintProto& y_interval_ct =
5938 context_->working_model->constraints(y_interval_index);
5939 if (x_interval_ct.enforcement_literal().empty() &&
5940 y_interval_ct.enforcement_literal().empty()) {
5942 if (has_mandatory) {
5943 return context_->NotifyThatModelIsUnsat(
5944 "Two mandatory boxes in the same space");
5946 has_mandatory =
true;
5947 if (!context_->IntersectDomainWith(x_interval_ct.interval().start(),
5948 Domain(framed_region.x_min.value(),
5949 framed_region.x_max.value()))) {
5952 if (!context_->IntersectDomainWith(x_interval_ct.interval().end(),
5953 Domain(framed_region.x_min.value(),
5954 framed_region.x_max.value()))) {
5957 if (!context_->IntersectDomainWith(y_interval_ct.interval().start(),
5958 Domain(framed_region.y_min.value(),
5959 framed_region.y_max.value()))) {
5962 if (!context_->IntersectDomainWith(y_interval_ct.interval().end(),
5963 Domain(framed_region.y_min.value(),
5964 framed_region.y_max.value()))) {
5968 auto add_linear_constraint = [&](
const ConstraintProto& interval_ct,
5969 int enforcement_literal,
5970 IntegerValue min, IntegerValue max) {
5973 context_->AddImplyInDomain(enforcement_literal,
5974 interval_ct.interval().start(),
5975 Domain(min.value(), max.value()));
5976 context_->AddImplyInDomain(enforcement_literal,
5977 interval_ct.interval().end(),
5978 Domain(min.value(), max.value()));
5980 const int enforcement_literal =
5981 x_interval_ct.enforcement_literal().empty()
5982 ? y_interval_ct.enforcement_literal(0)
5983 : x_interval_ct.enforcement_literal(0);
5984 enforcement_literals_for_amo.push_back(enforcement_literal);
5985 add_linear_constraint(x_interval_ct, enforcement_literal,
5986 framed_region.x_min, framed_region.x_max);
5987 add_linear_constraint(y_interval_ct, enforcement_literal,
5988 framed_region.y_min, framed_region.y_max);
5991 if (has_mandatory) {
5992 for (
const int lit : enforcement_literals_for_amo) {
5993 if (!context_->SetLiteralToFalse(lit)) {
5997 }
else if (enforcement_literals_for_amo.size() > 1) {
5998 context_->working_model->add_constraints()
5999 ->mutable_at_most_one()
6000 ->mutable_literals()
6001 ->Add(enforcement_literals_for_amo.begin(),
6002 enforcement_literals_for_amo.end());
6004 context_->UpdateRuleStats(
"no_overlap_2d: at most one rectangle in region");
6005 context_->UpdateNewConstraintsVariableUsage();
6006 return RemoveConstraint(ct);
6009bool CpModelPresolver::ExpandEncoded2DBinPacking(
6010 absl::Span<const Rectangle> fixed_boxes,
6011 absl::Span<const RectangleInRange> non_fixed_boxes, ConstraintProto* ct) {
6012 const Disjoint2dPackingResult disjoint_packing_presolve_result =
6014 non_fixed_boxes, fixed_boxes,
6016 .maximum_regions_to_split_in_disconnected_no_overlap_2d());
6017 if (disjoint_packing_presolve_result.bins.empty())
return false;
6019 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6020 std::vector<SolutionCrush::BoxInAreaLiteral> box_in_area_lits;
6021 absl::flat_hash_map<int, std::vector<int>> box_to_presence_literal;
6024 for (
int idx = 0; idx < non_fixed_boxes.size(); ++idx) {
6025 const int b = non_fixed_boxes[idx].box_index;
6026 const ConstraintProto& x_interval_ct =
6027 context_->working_model->constraints(proto.x_intervals(
b));
6028 const ConstraintProto& y_interval_ct =
6029 context_->working_model->constraints(proto.y_intervals(
b));
6030 if (x_interval_ct.enforcement_literal().empty() &&
6031 y_interval_ct.enforcement_literal().empty()) {
6035 int enforcement_literal = x_interval_ct.enforcement_literal().empty()
6036 ? y_interval_ct.enforcement_literal(0)
6037 : x_interval_ct.enforcement_literal(0);
6038 int potentially_other_enforcement_literal =
6039 y_interval_ct.enforcement_literal().empty()
6040 ? x_interval_ct.enforcement_literal(0)
6041 : y_interval_ct.enforcement_literal(0);
6043 if (enforcement_literal == potentially_other_enforcement_literal) {
6045 box_to_presence_literal[idx].push_back(
NegatedRef(enforcement_literal));
6047 const int interval_is_absent_literal =
6048 context_->NewBoolVarWithConjunction(
6049 {enforcement_literal, potentially_other_enforcement_literal});
6051 BoolArgumentProto* bool_or =
6052 context_->working_model->add_constraints()->mutable_bool_or();
6053 bool_or->add_literals(
NegatedRef(interval_is_absent_literal));
6054 for (
const int lit :
6055 {enforcement_literal, potentially_other_enforcement_literal}) {
6056 context_->AddImplication(
NegatedRef(interval_is_absent_literal), lit);
6059 box_to_presence_literal[idx].push_back(interval_is_absent_literal);
6063 for (
int bin_index = 0;
6064 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6065 const Disjoint2dPackingResult::Bin& bin =
6066 disjoint_packing_presolve_result.bins[bin_index];
6067 NoOverlap2DConstraintProto new_no_overlap_2d;
6068 for (
const Rectangle& ret : bin.fixed_boxes) {
6069 new_no_overlap_2d.add_x_intervals(
6070 context_->working_model->constraints_size());
6071 new_no_overlap_2d.add_y_intervals(
6072 context_->working_model->constraints_size() + 1);
6073 IntervalConstraintProto* new_interval =
6074 context_->working_model->add_constraints()->mutable_interval();
6075 new_interval->mutable_start()->set_offset(ret.x_min.value());
6076 new_interval->mutable_size()->set_offset(ret.SizeX().value());
6077 new_interval->mutable_end()->set_offset(ret.x_max.value());
6080 context_->working_model->add_constraints()->mutable_interval();
6081 new_interval->mutable_start()->set_offset(ret.y_min.value());
6082 new_interval->mutable_size()->set_offset(ret.SizeY().value());
6083 new_interval->mutable_end()->set_offset(ret.y_max.value());
6085 for (
const int idx : bin.non_fixed_box_indexes) {
6086 int presence_in_box_lit = context_->NewBoolVar(
"binpacking");
6087 box_to_presence_literal[idx].push_back(presence_in_box_lit);
6088 const int b = non_fixed_boxes[idx].box_index;
6089 box_in_area_lits.push_back({.box_index =
b,
6090 .area_index = bin_index,
6091 .literal = presence_in_box_lit});
6092 const ConstraintProto& x_interval_ct =
6093 context_->working_model->constraints(proto.x_intervals(
b));
6094 const ConstraintProto& y_interval_ct =
6095 context_->working_model->constraints(proto.y_intervals(
b));
6096 ConstraintProto* new_interval_x =
6097 context_->working_model->add_constraints();
6098 *new_interval_x = x_interval_ct;
6099 new_interval_x->clear_enforcement_literal();
6100 new_interval_x->add_enforcement_literal(presence_in_box_lit);
6101 ConstraintProto* new_interval_y =
6102 context_->working_model->add_constraints();
6103 *new_interval_y = y_interval_ct;
6104 new_interval_y->clear_enforcement_literal();
6105 new_interval_y->add_enforcement_literal(presence_in_box_lit);
6106 new_no_overlap_2d.add_x_intervals(
6107 context_->working_model->constraints_size() - 2);
6108 new_no_overlap_2d.add_y_intervals(
6109 context_->working_model->constraints_size() - 1);
6111 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6112 &new_no_overlap_2d);
6116 for (
int box_index = 0; box_index < non_fixed_boxes.size(); ++box_index) {
6117 const std::vector<int>& presence_literals =
6118 box_to_presence_literal[box_index];
6119 if (presence_literals.empty()) {
6120 return context_->NotifyThatModelIsUnsat(
6121 "A mandatory box cannot be placed in any position");
6124 context_->working_model->add_constraints()->mutable_exactly_one();
6125 for (
const int presence_literal : presence_literals) {
6126 exactly_one->add_literals(presence_literal);
6129 CompactVectorVector<int, Rectangle> areas;
6130 for (
int bin_index = 0;
6131 bin_index < disjoint_packing_presolve_result.bins.size(); ++bin_index) {
6132 areas.Add(disjoint_packing_presolve_result.bins[bin_index].bin_area);
6134 solution_crush_.AssignVariableToPackingArea(
6135 areas, *context_->working_model, proto.x_intervals(), proto.y_intervals(),
6137 context_->UpdateNewConstraintsVariableUsage();
6138 context_->UpdateRuleStats(
6139 "no_overlap_2d: fixed boxes partition available space, converted "
6140 "to optional regions");
6141 return RemoveConstraint(ct);
6144bool CpModelPresolver::PresolveNoOverlap2D(
int , ConstraintProto* ct) {
6145 if (context_->ModelIsUnsat()) {
6149 const NoOverlap2DConstraintProto& proto = ct->no_overlap_2d();
6150 const int initial_num_boxes = proto.x_intervals_size();
6152 bool x_constant =
true;
6153 bool y_constant =
true;
6154 bool has_zero_sized_interval =
false;
6155 bool has_potential_zero_sized_interval =
false;
6159 std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes;
6160 std::vector<RectangleInRange> non_fixed_boxes;
6161 absl::flat_hash_set<int> fixed_item_indexes;
6162 for (
int i = 0;
i < proto.x_intervals_size(); ++
i) {
6163 const int x_interval_index = proto.x_intervals(
i);
6164 const int y_interval_index = proto.y_intervals(
i);
6166 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
6167 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
6172 for (
const int interval_index : {x_interval_index, y_interval_index}) {
6173 if (context_->StartMin(interval_index) >
6174 context_->EndMax(interval_index)) {
6175 const ConstraintProto* interval_ct =
6176 context_->working_model->mutable_constraints(interval_index);
6177 if (interval_ct->enforcement_literal_size() == 1) {
6178 const int literal = interval_ct->enforcement_literal(0);
6179 if (!context_->SetLiteralToFalse(literal)) {
6183 return context_->NotifyThatModelIsUnsat(
6184 "no_overlap_2d: impossible interval.");
6189 if (context_->ConstraintIsInactive(x_interval_index) ||
6190 context_->ConstraintIsInactive(y_interval_index)) {
6194 bounding_boxes.push_back(
6195 {IntegerValue(context_->StartMin(x_interval_index)),
6196 IntegerValue(context_->EndMax(x_interval_index)),
6197 IntegerValue(context_->StartMin(y_interval_index)),
6198 IntegerValue(context_->EndMax(y_interval_index))});
6199 if (context_->IntervalIsConstant(x_interval_index) &&
6200 context_->IntervalIsConstant(y_interval_index) &&
6201 context_->SizeMax(x_interval_index) > 0 &&
6202 context_->SizeMax(y_interval_index) > 0) {
6203 fixed_boxes.push_back(bounding_boxes.back());
6204 fixed_item_indexes.insert(new_size);
6206 non_fixed_bounding_boxes.push_back(bounding_boxes.back());
6207 non_fixed_boxes.push_back(
6208 {.box_index = new_size,
6209 .bounding_area = bounding_boxes.back(),
6210 .x_size = context_->SizeMin(x_interval_index),
6211 .y_size = context_->SizeMin(y_interval_index)});
6215 if (x_constant && !context_->IntervalIsConstant(x_interval_index)) {
6218 if (y_constant && !context_->IntervalIsConstant(y_interval_index)) {
6221 if (context_->SizeMax(x_interval_index) == 0 ||
6222 context_->SizeMax(y_interval_index) == 0) {
6223 has_zero_sized_interval =
true;
6225 if (context_->SizeMin(x_interval_index) == 0 ||
6226 context_->SizeMin(y_interval_index) == 0) {
6227 has_potential_zero_sized_interval =
true;
6231 if (new_size < initial_num_boxes) {
6232 context_->UpdateRuleStats(
"no_overlap_2d: removed inactive boxes");
6233 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
6234 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
6237 if (new_size == 0) {
6238 context_->UpdateRuleStats(
"no_overlap_2d: no boxes");
6239 return RemoveConstraint(ct);
6242 if (new_size == 1) {
6243 context_->UpdateRuleStats(
"no_overlap_2d: only one box");
6244 return RemoveConstraint(ct);
6247 const CompactVectorVector<int> components =
6249 if (components.size() > 1) {
6250 for (
int i = 0;
i < components.size(); ++
i) {
6251 absl::Span<const int> boxes = components[
i];
6252 if (boxes.size() <= 1)
continue;
6254 NoOverlap2DConstraintProto* new_no_overlap_2d =
6255 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6256 for (
const int b : boxes) {
6257 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6258 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6261 context_->UpdateNewConstraintsVariableUsage();
6262 context_->UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
6263 return RemoveConstraint(ct);
6267 if (!has_zero_sized_interval && (x_constant || y_constant)) {
6268 context_->UpdateRuleStats(
6269 "no_overlap_2d: a dimension is constant, splitting into many "
6271 std::vector<IndexedInterval> indexed_intervals;
6272 for (
int i = 0;
i < new_size; ++
i) {
6273 int x = proto.x_intervals(
i);
6274 int y = proto.y_intervals(
i);
6275 if (x_constant) std::swap(x, y);
6276 indexed_intervals.push_back({
x, IntegerValue(context_->StartMin(y)),
6277 IntegerValue(context_->EndMax(y))});
6279 CompactVectorVector<int> no_overlaps;
6280 absl::c_sort(indexed_intervals, IndexedInterval::ComparatorByStart());
6282 for (
int i = 0;
i < no_overlaps.size(); ++
i) {
6283 ConstraintProto* new_ct = context_->working_model->add_constraints();
6286 for (
const int i : no_overlaps[
i]) {
6287 new_ct->mutable_no_overlap()->add_intervals(
i);
6290 context_->UpdateNewConstraintsVariableUsage();
6291 return RemoveConstraint(ct);
6297 return context_->NotifyThatModelIsUnsat(
6298 "Two fixed boxes in no_overlap_2d overlap");
6301 if (non_fixed_bounding_boxes.empty()) {
6302 context_->UpdateRuleStats(
"no_overlap_2d: all boxes are fixed");
6303 return RemoveConstraint(ct);
6309 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
6310 const bool presolved =
6313 NoOverlap2DConstraintProto new_no_overlap_2d;
6316 const int old_size = proto.x_intervals_size();
6317 for (
int i = 0;
i < old_size; ++
i) {
6318 if (fixed_item_indexes.contains(
i)) {
6321 new_no_overlap_2d.add_x_intervals(proto.x_intervals(
i));
6322 new_no_overlap_2d.add_y_intervals(proto.y_intervals(
i));
6324 for (
const Rectangle& fixed_box : fixed_boxes) {
6325 const int item_x_interval =
6326 context_->working_model->constraints().size();
6327 IntervalConstraintProto* new_interval =
6328 context_->working_model->add_constraints()->mutable_interval();
6329 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
6330 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
6331 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
6333 const int item_y_interval =
6334 context_->working_model->constraints().size();
6336 context_->working_model->add_constraints()->mutable_interval();
6337 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
6338 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
6339 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
6341 new_no_overlap_2d.add_x_intervals(item_x_interval);
6342 new_no_overlap_2d.add_y_intervals(item_y_interval);
6344 context_->working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
6345 &new_no_overlap_2d);
6346 context_->UpdateNewConstraintsVariableUsage();
6347 context_->UpdateRuleStats(
"no_overlap_2d: presolved fixed rectangles");
6348 return RemoveConstraint(ct);
6352 if (!fixed_boxes.empty() && fixed_boxes.size() <= 4 &&
6353 !non_fixed_boxes.empty() && !has_potential_zero_sized_interval) {
6354 if (PresolveNoOverlap2DFramed(fixed_boxes, non_fixed_boxes, ct)) {
6363 const CompactVectorVector<int> non_fixed_components =
6365 if (non_fixed_components.size() > 1) {
6366 for (
int i = 0;
i < non_fixed_components.size(); ++
i) {
6369 absl::Span<const int> indexes = non_fixed_components[
i];
6371 NoOverlap2DConstraintProto* new_no_overlap_2d =
6372 context_->working_model->add_constraints()->mutable_no_overlap_2d();
6373 for (
const int idx : indexes) {
6374 const int b = non_fixed_boxes[idx].box_index;
6375 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6376 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6378 for (
const int b : fixed_item_indexes) {
6379 new_no_overlap_2d->add_x_intervals(proto.x_intervals(
b));
6380 new_no_overlap_2d->add_y_intervals(proto.y_intervals(
b));
6383 context_->UpdateNewConstraintsVariableUsage();
6384 context_->UpdateRuleStats(
6385 "no_overlap_2d: split into disjoint components duplicating fixed "
6387 return RemoveConstraint(ct);
6390 if (!has_potential_zero_sized_interval) {
6391 if (ExpandEncoded2DBinPacking(fixed_boxes, non_fixed_boxes, ct)) {
6395 RunPropagatorsForConstraint(*ct);
6396 return new_size < initial_num_boxes;
6400LinearExpressionProto ConstantExpressionProto(int64_t value) {
6401 LinearExpressionProto expr;
6402 expr.set_offset(value);
6407void CpModelPresolver::DetectDuplicateIntervals(
6408 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
6409 interval_representative_.clear();
6410 bool changed =
false;
6411 const int size = intervals->size();
6412 for (
int i = 0;
i < size; ++
i) {
6413 const int index = (*intervals)[
i];
6414 const auto [it, inserted] = interval_representative_.insert({index, index});
6415 if (it->second != index) {
6417 intervals->Set(
i, it->second);
6418 context_->UpdateRuleStats(
6419 "intervals: change duplicate index inside constraint");
6422 if (changed) context_->UpdateConstraintVariableUsage(c);
6425bool CpModelPresolver::PresolveCumulative(ConstraintProto* ct) {
6426 if (context_->ModelIsUnsat())
return false;
6428 CumulativeConstraintProto* proto = ct->mutable_cumulative();
6430 bool changed = CanonicalizeLinearExpression(*ct, proto->mutable_capacity());
6431 for (LinearExpressionProto& exp :
6432 *(ct->mutable_cumulative()->mutable_demands())) {
6433 changed |= CanonicalizeLinearExpression(*ct, &exp);
6436 const int64_t capacity_max = context_->MaxOf(proto->capacity());
6440 bool domain_changed =
false;
6441 if (!context_->IntersectDomainWith(
6442 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
6445 if (domain_changed) {
6446 context_->UpdateRuleStats(
"cumulative: trimmed negative capacity");
6455 absl::flat_hash_map<int, int> interval_to_i;
6457 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6458 const auto [it, inserted] =
6459 interval_to_i.insert({proto->intervals(
i), new_size});
6461 if (context_->IsFixed(proto->demands(
i))) {
6462 const int old_index = it->second;
6463 proto->mutable_demands(old_index)->set_offset(
6464 proto->demands(old_index).offset() +
6465 context_->FixedValue(proto->demands(
i)));
6466 context_->UpdateRuleStats(
6467 "cumulative: merged demand of identical interval");
6470 context_->UpdateRuleStats(
6471 "TODO cumulative: merged demand of identical interval");
6474 proto->set_intervals(new_size, proto->intervals(
i));
6475 *proto->mutable_demands(new_size) = proto->demands(
i);
6478 if (new_size < proto->intervals_size()) {
6480 proto->mutable_intervals()->Truncate(new_size);
6481 proto->mutable_demands()->erase(
6482 proto->mutable_demands()->begin() + new_size,
6483 proto->mutable_demands()->end());
6491 int num_zero_demand_removed = 0;
6492 int num_zero_size_removed = 0;
6493 int num_incompatible_intervals = 0;
6494 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6495 if (context_->ConstraintIsInactive(proto->intervals(
i)))
continue;
6497 const LinearExpressionProto& demand_expr = proto->demands(
i);
6498 const int64_t demand_max = context_->MaxOf(demand_expr);
6499 if (demand_max == 0) {
6500 num_zero_demand_removed++;
6504 const int interval_index = proto->intervals(
i);
6505 if (context_->SizeMax(interval_index) == 0) {
6507 num_zero_size_removed++;
6512 const int64_t start_min = context_->StartMin(interval_index);
6513 const int64_t end_max = context_->EndMax(interval_index);
6514 if (start_min > end_max) {
6515 if (context_->ConstraintIsOptional(interval_index)) {
6516 ConstraintProto* interval_ct =
6517 context_->working_model->mutable_constraints(interval_index);
6518 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6519 const int literal = interval_ct->enforcement_literal(0);
6520 if (!context_->SetLiteralToFalse(literal)) {
6523 num_incompatible_intervals++;
6526 return context_->NotifyThatModelIsUnsat(
6527 "cumulative: inconsistent intervals cannot be performed.");
6531 if (context_->MinOf(demand_expr) > capacity_max) {
6532 if (context_->ConstraintIsOptional(interval_index)) {
6533 if (context_->SizeMin(interval_index) > 0) {
6534 ConstraintProto* interval_ct =
6535 context_->working_model->mutable_constraints(interval_index);
6536 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
6537 const int literal = interval_ct->enforcement_literal(0);
6538 if (!context_->SetLiteralToFalse(literal)) {
6541 num_incompatible_intervals++;
6546 const ConstraintProto& interval_ct =
6547 context_->working_model->constraints(interval_index);
6548 if (!context_->IntersectDomainWith(interval_ct.interval().size(),
6552 context_->UpdateRuleStats(
6553 "cumulative: zero size of performed demand that exceeds "
6555 ++num_zero_demand_removed;
6560 proto->set_intervals(new_size, interval_index);
6561 *proto->mutable_demands(new_size) = proto->demands(
i);
6565 if (new_size < proto->intervals_size()) {
6567 proto->mutable_intervals()->Truncate(new_size);
6568 proto->mutable_demands()->erase(
6569 proto->mutable_demands()->begin() + new_size,
6570 proto->mutable_demands()->end());
6573 if (num_zero_demand_removed > 0) {
6574 context_->UpdateRuleStats(
6575 "cumulative: removed intervals with no demands");
6577 if (num_zero_size_removed > 0) {
6578 context_->UpdateRuleStats(
6579 "cumulative: removed intervals with a size of zero");
6581 if (num_incompatible_intervals > 0) {
6582 context_->UpdateRuleStats(
6583 "cumulative: removed intervals that can't be performed");
6589 for (
int i = 0;
i < proto->demands_size(); ++
i) {
6590 const int interval = proto->intervals(
i);
6591 const LinearExpressionProto& demand_expr = proto->demands(
i);
6592 if (context_->ConstraintIsOptional(interval))
continue;
6593 if (context_->SizeMin(interval) == 0)
continue;
6594 bool domain_changed =
false;
6595 if (!context_->IntersectDomainWith(demand_expr, {0, capacity_max},
6599 if (domain_changed) {
6600 context_->UpdateRuleStats(
6601 "cumulative: fit demand in [0..capacity_max]");
6613 if (proto->intervals_size() > 1) {
6614 std::vector<IndexedInterval> indexed_intervals;
6615 for (
int i = 0;
i < proto->intervals().size(); ++
i) {
6616 const int index = proto->intervals(
i);
6617 indexed_intervals.push_back({
i, IntegerValue(context_->StartMin(index)),
6618 IntegerValue(context_->EndMax(index))});
6620 std::vector<std::vector<int>> components;
6623 if (components.size() > 1) {
6624 for (
const std::vector<int>& component : components) {
6625 CumulativeConstraintProto* new_cumulative =
6626 context_->working_model->add_constraints()->mutable_cumulative();
6627 for (
const int i : component) {
6628 new_cumulative->add_intervals(proto->intervals(
i));
6629 *new_cumulative->add_demands() = proto->demands(
i);
6631 *new_cumulative->mutable_capacity() = proto->capacity();
6633 context_->UpdateNewConstraintsVariableUsage();
6634 context_->UpdateRuleStats(
"cumulative: split into disjoint components");
6635 return RemoveConstraint(ct);
6643 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6644 const int64_t capacity_min = context_->MinOf(proto->capacity());
6645 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6646 const int interval_index = proto->intervals(
i);
6647 const int64_t demand_max = context_->MaxOf(proto->demands(
i));
6648 time_to_demand_deltas[context_->StartMin(interval_index)] += demand_max;
6649 time_to_demand_deltas[context_->EndMax(interval_index)] -= demand_max;
6658 int num_possible_overloads = 0;
6659 int64_t current_load = 0;
6660 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6661 for (
const auto& it : time_to_demand_deltas) {
6662 num_possible_overloads_before[it.first] = num_possible_overloads;
6663 current_load += it.second;
6664 if (current_load > capacity_min) {
6665 ++num_possible_overloads;
6668 CHECK_EQ(current_load, 0);
6671 if (num_possible_overloads == 0) {
6672 context_->UpdateRuleStats(
6673 "cumulative: max profile is always under the min capacity");
6674 return RemoveConstraint(ct);
6684 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6685 const int index = proto->intervals(
i);
6686 const int64_t start_min = context_->StartMin(index);
6687 const int64_t end_max = context_->EndMax(index);
6696 if (start_min >= end_max)
continue;
6702 const int num_diff = num_possible_overloads_before.at(end_max) -
6703 num_possible_overloads_before.at(start_min);
6704 if (num_diff == 0)
continue;
6706 proto->set_intervals(new_size, proto->intervals(
i));
6707 *proto->mutable_demands(new_size) = proto->demands(
i);
6711 if (new_size < proto->intervals_size()) {
6713 proto->mutable_intervals()->Truncate(new_size);
6714 proto->mutable_demands()->erase(
6715 proto->mutable_demands()->begin() + new_size,
6716 proto->mutable_demands()->end());
6717 context_->UpdateRuleStats(
6718 "cumulative: remove never conflicting intervals.");
6722 if (proto->intervals().empty()) {
6723 context_->UpdateRuleStats(
"cumulative: no intervals");
6724 return RemoveConstraint(ct);
6728 int64_t max_of_performed_demand_mins = 0;
6729 int64_t sum_of_max_demands = 0;
6730 for (
int i = 0;
i < proto->intervals_size(); ++
i) {
6731 const int interval_index = proto->intervals(
i);
6732 const ConstraintProto& interval_ct =
6733 context_->working_model->constraints(interval_index);
6735 const LinearExpressionProto& demand_expr = proto->demands(
i);
6736 sum_of_max_demands += context_->MaxOf(demand_expr);
6738 if (interval_ct.enforcement_literal().empty() &&
6739 context_->SizeMin(interval_index) > 0) {
6740 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6741 context_->MinOf(demand_expr));
6745 const LinearExpressionProto& capacity_expr = proto->capacity();
6746 if (max_of_performed_demand_mins > context_->MinOf(capacity_expr)) {
6747 context_->UpdateRuleStats(
"cumulative: propagate min capacity.");
6748 if (!context_->IntersectDomainWith(
6749 capacity_expr, Domain(max_of_performed_demand_mins,
6750 std::numeric_limits<int64_t>::max()))) {
6755 if (max_of_performed_demand_mins > context_->MaxOf(capacity_expr)) {
6756 context_->UpdateRuleStats(
"cumulative: cannot fit performed demands");
6757 return context_->NotifyThatModelIsUnsat();
6760 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6761 context_->UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
6762 return RemoveConstraint(ct);
6766 if (context_->IsFixed(proto->capacity())) {
6768 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
6769 const LinearExpressionProto& demand_expr = ct->cumulative().demands(
i);
6770 if (!context_->IsFixed(demand_expr)) {
6775 gcd = std::gcd(gcd, context_->MinOf(demand_expr));
6776 if (gcd == 1)
break;
6780 for (
int i = 0;
i < ct->cumulative().demands_size(); ++
i) {
6781 const int64_t demand = context_->MinOf(ct->cumulative().demands(
i));
6782 *proto->mutable_demands(
i) = ConstantExpressionProto(demand / gcd);
6785 const int64_t old_capacity = context_->MinOf(proto->capacity());
6786 *proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6787 context_->UpdateRuleStats(
6788 "cumulative: divide demands and capacity by gcd");
6792 const int num_intervals = proto->intervals_size();
6793 const LinearExpressionProto& capacity_expr = proto->capacity();
6795 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6797 int num_duration_one = 0;
6798 int num_greater_half_capacity = 0;
6800 bool has_optional_interval =
false;
6801 for (
int i = 0;
i < num_intervals; ++
i) {
6802 const int index = proto->intervals(
i);
6804 if (context_->ConstraintIsOptional(index)) has_optional_interval =
true;
6805 const ConstraintProto& ct =
6806 context_->working_model->constraints(proto->intervals(
i));
6807 const IntervalConstraintProto& interval = ct.interval();
6808 start_exprs[
i] = interval.start();
6810 const LinearExpressionProto& demand_expr = proto->demands(
i);
6811 if (context_->SizeMin(index) == 1 && context_->SizeMax(index) == 1) {
6814 if (context_->SizeMin(index) == 0) {
6820 const int64_t demand_min = context_->MinOf(demand_expr);
6821 const int64_t demand_max = context_->MaxOf(demand_expr);
6822 if (demand_min > capacity_max / 2) {
6823 num_greater_half_capacity++;
6825 if (demand_min > capacity_max) {
6826 context_->UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
6827 if (!context_->ConstraintIsOptional(index)) {
6828 return context_->NotifyThatModelIsUnsat();
6830 CHECK_EQ(ct.enforcement_literal().size(), 1);
6831 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
6836 }
else if (demand_max > capacity_max) {
6837 if (ct.enforcement_literal().empty()) {
6838 context_->UpdateRuleStats(
6839 "cumulative: demand_max exceeds capacity max.");
6840 if (!context_->IntersectDomainWith(
6842 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
6848 context_->UpdateRuleStats(
6849 "cumulative: demand_max of optional interval exceeds capacity.");
6854 if (num_greater_half_capacity == num_intervals) {
6855 if (num_duration_one == num_intervals && !has_optional_interval) {
6856 context_->UpdateRuleStats(
"cumulative: convert to all_different");
6857 ConstraintProto* new_ct = context_->working_model->add_constraints();
6858 auto* arg = new_ct->mutable_all_diff();
6859 for (
const LinearExpressionProto& expr : start_exprs) {
6860 *arg->add_exprs() = expr;
6862 if (!context_->IsFixed(capacity_expr)) {
6863 const int64_t capacity_min = context_->MinOf(capacity_expr);
6864 for (
const LinearExpressionProto& expr : proto->demands()) {
6865 if (capacity_min >= context_->MaxOf(expr))
continue;
6866 LinearConstraintProto* fit =
6867 context_->working_model->add_constraints()->mutable_linear();
6869 fit->add_domain(std::numeric_limits<int64_t>::max());
6874 context_->UpdateNewConstraintsVariableUsage();
6875 return RemoveConstraint(ct);
6877 context_->UpdateRuleStats(
"cumulative: convert to no_overlap");
6880 for (
int i = 0;
i < proto->demands_size(); ++
i) {
6881 const LinearExpressionProto& demand_expr = proto->demands(
i);
6882 const int64_t demand_max = context_->MaxOf(demand_expr);
6883 if (demand_max > context_->MinOf(capacity_expr)) {
6884 ConstraintProto* capacity_gt =
6885 context_->working_model->add_constraints();
6886 *capacity_gt->mutable_enforcement_literal() =
6887 context_->working_model->constraints(proto->intervals(
i))
6888 .enforcement_literal();
6889 capacity_gt->mutable_linear()->add_domain(0);
6890 capacity_gt->mutable_linear()->add_domain(
6891 std::numeric_limits<int64_t>::max());
6893 capacity_gt->mutable_linear());
6895 capacity_gt->mutable_linear());
6899 ConstraintProto* new_ct = context_->working_model->add_constraints();
6900 auto* arg = new_ct->mutable_no_overlap();
6901 for (
const int interval : proto->intervals()) {
6902 arg->add_intervals(interval);
6904 context_->UpdateNewConstraintsVariableUsage();
6905 return RemoveConstraint(ct);
6909 RunPropagatorsForConstraint(*ct);
6913bool CpModelPresolver::PresolveRoutes(ConstraintProto* ct) {
6914 if (context_->ModelIsUnsat())
return false;
6916 RoutesConstraintProto& proto = *ct->mutable_routes();
6918 const int old_size = proto.literals_size();
6920 std::vector<bool> has_incoming_or_outgoing_arcs;
6921 const int num_arcs = proto.literals_size();
6922 for (
int i = 0;
i < num_arcs; ++
i) {
6923 const int ref = proto.literals(
i);
6924 const int tail = proto.tails(
i);
6925 const int head = proto.heads(
i);
6927 if (tail >= has_incoming_or_outgoing_arcs.size()) {
6928 has_incoming_or_outgoing_arcs.resize(tail + 1,
false);
6930 if (head >= has_incoming_or_outgoing_arcs.size()) {
6931 has_incoming_or_outgoing_arcs.resize(head + 1,
false);
6934 if (context_->LiteralIsFalse(ref)) {
6935 context_->UpdateRuleStats(
"routes: removed false arcs");
6938 proto.set_literals(new_size, ref);
6939 proto.set_tails(new_size, tail);
6940 proto.set_heads(new_size, head);
6942 has_incoming_or_outgoing_arcs[tail] =
true;
6943 has_incoming_or_outgoing_arcs[head] =
true;
6946 if (old_size > 0 && new_size == 0) {
6950 return context_->NotifyThatModelIsUnsat(
6951 "routes: graph with nodes and no arcs");
6956 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
6957 if (!has_incoming_or_outgoing_arcs[n]) {
6958 return context_->NotifyThatModelIsUnsat(absl::StrCat(
6959 "routes: node ", n,
" misses incoming or outgoing arcs"));
6963 if (new_size < num_arcs) {
6964 proto.mutable_literals()->Truncate(new_size);
6965 proto.mutable_tails()->Truncate(new_size);
6966 proto.mutable_heads()->Truncate(new_size);
6970 RunPropagatorsForConstraint(*ct);
6974bool CpModelPresolver::PresolveCircuit(ConstraintProto* ct) {
6975 if (context_->ModelIsUnsat())
return false;
6977 CircuitConstraintProto& proto = *ct->mutable_circuit();
6980 ReindexArcs(ct->mutable_circuit()->mutable_tails(),
6981 ct->mutable_circuit()->mutable_heads());
6985 std::vector<std::vector<int>> incoming_arcs;
6986 std::vector<std::vector<int>> outgoing_arcs;
6988 const int num_arcs = proto.literals_size();
6989 for (
int i = 0;
i < num_arcs; ++
i) {
6990 const int ref = proto.literals(
i);
6991 const int tail = proto.tails(
i);
6992 const int head = proto.heads(
i);
6993 num_nodes = std::max(num_nodes, std::max(tail, head) + 1);
6994 if (std::max(tail, head) >= incoming_arcs.size()) {
6995 incoming_arcs.resize(std::max(tail, head) + 1);
6996 outgoing_arcs.resize(std::max(tail, head) + 1);
6998 incoming_arcs[head].push_back(ref);
6999 outgoing_arcs[tail].push_back(ref);
7003 for (
int i = 0;
i < num_nodes; ++
i) {
7004 if (incoming_arcs[
i].empty() || outgoing_arcs[
i].empty()) {
7005 return MarkConstraintAsFalse(ct);
7014 bool loop_again =
true;
7015 int num_fixed_at_true = 0;
7016 while (loop_again) {
7018 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
7019 for (
const std::vector<int>& refs : *node_to_refs) {
7020 if (refs.size() == 1) {
7021 if (!context_->LiteralIsTrue(refs.front())) {
7022 ++num_fixed_at_true;
7023 if (!context_->SetLiteralToTrue(refs.front()))
return true;
7031 for (
const int ref : refs) {
7032 if (context_->LiteralIsTrue(ref)) {
7039 return context_->NotifyThatModelIsUnsat();
7041 if (num_true == 1) {
7042 for (
const int ref : refs) {
7043 if (ref != true_ref) {
7044 if (!context_->IsFixed(ref)) {
7045 context_->UpdateRuleStats(
"circuit: set literal to false.");
7048 if (!context_->SetLiteralToFalse(ref))
return true;
7055 if (num_fixed_at_true > 0) {
7056 context_->UpdateRuleStats(
"circuit: fixed singleton arcs.");
7062 int circuit_start = -1;
7063 std::vector<int> next(num_nodes, -1);
7064 std::vector<int> new_in_degree(num_nodes, 0);
7065 std::vector<int> new_out_degree(num_nodes, 0);
7066 for (
int i = 0;
i < num_arcs; ++
i) {
7067 const int ref = proto.literals(
i);
7068 if (context_->LiteralIsFalse(ref))
continue;
7069 if (context_->LiteralIsTrue(ref)) {
7070 if (next[proto.tails(
i)] != -1) {
7071 return context_->NotifyThatModelIsUnsat();
7073 next[proto.tails(
i)] = proto.heads(
i);
7074 if (proto.tails(
i) != proto.heads(
i)) {
7075 circuit_start = proto.tails(
i);
7079 ++new_out_degree[proto.tails(
i)];
7080 ++new_in_degree[proto.heads(
i)];
7081 proto.set_tails(new_size, proto.tails(
i));
7082 proto.set_heads(new_size, proto.heads(
i));
7083 proto.set_literals(new_size, ref);
7093 for (
int i = 0;
i < num_nodes; ++
i) {
7094 if (new_in_degree[
i] == 0 || new_out_degree[
i] == 0) {
7095 return context_->NotifyThatModelIsUnsat();
7100 if (circuit_start != -1) {
7101 std::vector<bool> visited(num_nodes,
false);
7102 int current = circuit_start;
7103 while (current != -1 && !visited[current]) {
7104 visited[current] =
true;
7105 current = next[current];
7107 if (current == circuit_start) {
7110 std::vector<bool> has_self_arc(num_nodes,
false);
7111 for (
int i = 0;
i < num_arcs; ++
i) {
7112 if (visited[proto.tails(
i)])
continue;
7113 if (proto.tails(
i) == proto.heads(
i)) {
7114 has_self_arc[proto.tails(
i)] =
true;
7115 if (!context_->SetLiteralToTrue(proto.literals(
i)))
return true;
7117 if (!context_->SetLiteralToFalse(proto.literals(
i)))
return true;
7120 for (
int n = 0; n < num_nodes; ++n) {
7121 if (!visited[n] && !has_self_arc[n]) {
7123 return MarkConstraintAsFalse(ct);
7126 context_->UpdateRuleStats(
"circuit: fully specified.");
7127 return RemoveConstraint(ct);
7131 if (num_true == new_size) {
7132 context_->UpdateRuleStats(
"circuit: empty circuit.");
7133 return RemoveConstraint(ct);
7139 for (
int i = 0;
i < num_nodes; ++
i) {
7140 for (
const std::vector<int>* arc_literals :
7141 {&incoming_arcs[
i], &outgoing_arcs[
i]}) {
7142 std::vector<int> literals;
7143 for (
const int ref : *arc_literals) {
7144 if (context_->LiteralIsFalse(ref))
continue;
7145 if (context_->LiteralIsTrue(ref)) {
7149 literals.push_back(ref);
7151 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
7152 context_->UpdateRuleStats(
"circuit: degree 2");
7153 if (!context_->StoreBooleanEqualityRelation(literals[0],
7162 if (new_size < num_arcs) {
7163 proto.mutable_tails()->Truncate(new_size);
7164 proto.mutable_heads()->Truncate(new_size);
7165 proto.mutable_literals()->Truncate(new_size);
7166 context_->UpdateRuleStats(
"circuit: removed false arcs.");
7169 RunPropagatorsForConstraint(*ct);
7173bool CpModelPresolver::PresolveAutomaton(ConstraintProto* ct) {
7174 if (context_->ModelIsUnsat())
return false;
7177 AutomatonConstraintProto* proto = ct->mutable_automaton();
7178 if (proto->exprs_size() == 0 || proto->transition_label_size() == 0) {
7182 bool changed =
false;
7183 for (
int i = 0;
i < proto->exprs_size(); ++
i) {
7184 changed |= CanonicalizeLinearExpression(*ct, proto->mutable_exprs(
i));
7187 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
7188 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
7192 for (
int time = 0; time < reachable_labels.size(); ++time) {
7193 const LinearExpressionProto& expr = proto->exprs(time);
7194 if (context_->IsFixed(expr)) {
7195 if (!reachable_labels[time].contains(context_->FixedValue(expr))) {
7196 return MarkConstraintAsFalse(ct);
7199 std::vector<int64_t> unscaled_reachable_labels;
7200 for (
const int64_t label : reachable_labels[time]) {
7203 bool removed_values =
false;
7204 if (!context_->IntersectDomainWith(
7209 if (removed_values) {
7210 context_->UpdateRuleStats(
"automaton: reduce variable domain");
7218bool CpModelPresolver::PresolveReservoir(ConstraintProto* ct) {
7219 if (context_->ModelIsUnsat())
return false;
7222 ReservoirConstraintProto& proto = *ct->mutable_reservoir();
7223 bool changed =
false;
7224 for (LinearExpressionProto& exp : *(proto.mutable_time_exprs())) {
7225 changed |= CanonicalizeLinearExpression(*ct, &exp);
7227 for (LinearExpressionProto& exp : *(proto.mutable_level_changes())) {
7228 changed |= CanonicalizeLinearExpression(*ct, &exp);
7231 if (proto.active_literals().empty()) {
7232 const int true_literal = context_->GetTrueLiteral();
7233 for (
int i = 0;
i < proto.time_exprs_size(); ++
i) {
7234 proto.add_active_literals(true_literal);
7239 const auto& demand_is_null = [&](
int i) {
7240 return (context_->IsFixed(proto.level_changes(
i)) &&
7241 context_->FixedValue(proto.level_changes(
i)) == 0) ||
7242 context_->LiteralIsFalse(proto.active_literals(
i));
7247 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7248 if (demand_is_null(
i)) num_zeros++;
7251 if (num_zeros > 0) {
7254 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7255 if (demand_is_null(
i))
continue;
7256 *proto.mutable_level_changes(new_size) = proto.level_changes(
i);
7257 *proto.mutable_time_exprs(new_size) = proto.time_exprs(
i);
7258 proto.set_active_literals(new_size, proto.active_literals(
i));
7262 proto.mutable_level_changes()->erase(
7263 proto.mutable_level_changes()->begin() + new_size,
7264 proto.mutable_level_changes()->end());
7265 proto.mutable_time_exprs()->erase(
7266 proto.mutable_time_exprs()->begin() + new_size,
7267 proto.mutable_time_exprs()->end());
7268 proto.mutable_active_literals()->Truncate(new_size);
7270 context_->UpdateRuleStats(
7271 "reservoir: remove zero level_changes or inactive events.");
7275 for (
const LinearExpressionProto& level_change : proto.level_changes()) {
7276 if (!context_->IsFixed(level_change))
return changed;
7279 const int num_events = proto.level_changes_size();
7280 int64_t gcd = proto.level_changes().empty()
7282 : std::abs(context_->FixedValue(proto.level_changes(0)));
7283 int num_positives = 0;
7284 int num_negatives = 0;
7285 int64_t max_sum_of_positive_level_changes = 0;
7286 int64_t min_sum_of_negative_level_changes = 0;
7287 for (
int i = 0;
i < num_events; ++
i) {
7288 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7289 gcd = std::gcd(gcd, std::abs(demand));
7292 max_sum_of_positive_level_changes += demand;
7294 DCHECK_LT(demand, 0);
7296 min_sum_of_negative_level_changes += demand;
7300 if (min_sum_of_negative_level_changes >= proto.min_level() &&
7301 max_sum_of_positive_level_changes <= proto.max_level()) {
7302 context_->UpdateRuleStats(
"reservoir: always feasible");
7303 return RemoveConstraint(ct);
7306 if (min_sum_of_negative_level_changes > proto.max_level() ||
7307 max_sum_of_positive_level_changes < proto.min_level()) {
7308 context_->UpdateRuleStats(
"reservoir: trivially infeasible");
7309 return context_->NotifyThatModelIsUnsat();
7312 if (min_sum_of_negative_level_changes > proto.min_level()) {
7313 proto.set_min_level(min_sum_of_negative_level_changes);
7314 context_->UpdateRuleStats(
7315 "reservoir: increase min_level to reachable value");
7318 if (max_sum_of_positive_level_changes < proto.max_level()) {
7319 proto.set_max_level(max_sum_of_positive_level_changes);
7320 context_->UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
7323 if (proto.min_level() <= 0 && proto.max_level() >= 0 &&
7324 (num_positives == 0 || num_negatives == 0)) {
7327 auto*
const sum_ct = context_->working_model->add_constraints();
7328 auto*
const sum = sum_ct->mutable_linear();
7329 int64_t fixed_contrib = 0;
7330 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7331 const int64_t demand = context_->FixedValue(proto.level_changes(
i));
7332 DCHECK_NE(demand, 0);
7334 const int active = proto.active_literals(
i);
7336 sum->add_vars(active);
7337 sum->add_coeffs(demand);
7340 sum->add_coeffs(-demand);
7341 fixed_contrib += demand;
7344 sum->add_domain(proto.min_level() - fixed_contrib);
7345 sum->add_domain(proto.max_level() - fixed_contrib);
7346 CanonicalizeLinear(sum_ct);
7347 context_->UpdateRuleStats(
"reservoir: converted to linear");
7348 return RemoveConstraint(ct);
7352 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7353 proto.mutable_level_changes(
i)->set_offset(
7354 context_->FixedValue(proto.level_changes(
i)) / gcd);
7355 proto.mutable_level_changes(
i)->clear_vars();
7356 proto.mutable_level_changes(
i)->clear_coeffs();
7362 const Domain reduced_domain = Domain({proto.min_level(), proto.max_level()})
7363 .InverseMultiplicationBy(gcd);
7364 proto.set_min_level(reduced_domain.
Min());
7365 proto.set_max_level(reduced_domain.
Max());
7366 context_->UpdateRuleStats(
7367 "reservoir: simplify level_changes and levels by gcd.");
7370 if (num_positives == 1 && num_negatives > 0) {
7371 context_->UpdateRuleStats(
7372 "TODO reservoir: one producer, multiple consumers.");
7375 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
7376 for (
int i = 0;
i < proto.level_changes_size(); ++
i) {
7377 const LinearExpressionProto& time = proto.time_exprs(
i);
7378 const int var = context_->IsFixed(time) ? std::numeric_limits<int>::min()
7380 const int64_t coeff = context_->IsFixed(time) ? 0 : time.coeffs(0);
7381 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
7383 context_->IsFixed(time) ? context_->FixedValue(time) : time.offset(),
7384 proto.active_literals(
i));
7385 if (time_active_set.contains(key)) {
7386 context_->UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
7389 time_active_set.insert(key);
7393 RunPropagatorsForConstraint(*ct);
7400void CpModelPresolver::ConvertToBoolAnd() {
7401 absl::flat_hash_map<int, int> ref_to_bool_and;
7402 const int num_constraints = context_->working_model->constraints_size();
7403 std::vector<int> to_remove;
7404 for (
int c = 0;
c < num_constraints; ++
c) {
7405 const ConstraintProto& ct = context_->working_model->constraints(c);
7408 if (ct.constraint_case() == ConstraintProto::kBoolOr &&
7409 ct.bool_or().literals().size() == 2) {
7410 AddImplication(
NegatedRef(ct.bool_or().literals(0)),
7411 ct.bool_or().literals(1), context_->working_model,
7413 to_remove.push_back(c);
7417 if (ct.constraint_case() == ConstraintProto::kAtMostOne &&
7418 ct.at_most_one().literals().size() == 2) {
7419 AddImplication(ct.at_most_one().literals(0),
7421 context_->working_model, &ref_to_bool_and);
7422 to_remove.push_back(c);
7427 context_->UpdateNewConstraintsVariableUsage();
7428 for (
const int c : to_remove) {
7429 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7430 CHECK(RemoveConstraint(ct));
7431 context_->UpdateConstraintVariableUsage(c);
7435void CpModelPresolver::RunPropagatorsForConstraint(
const ConstraintProto& ct) {
7436 if (context_->ModelIsUnsat())
return;
7443 SatParameters local_params;
7444 local_params.set_use_try_edge_reasoning_in_no_overlap_2d(
true);
7445 local_params.set_exploit_all_precedences(
true);
7446 local_params.set_use_hard_precedences_in_cumulative(
true);
7447 local_params.set_max_num_intervals_for_timetable_edge_finding(1000);
7448 local_params.set_use_overload_checker_in_cumulative(
true);
7449 local_params.set_use_strong_propagation_in_disjunctive(
true);
7450 local_params.set_use_timetable_edge_finding_in_cumulative(
true);
7451 local_params.set_max_pairs_pairwise_reasoning_in_no_overlap_2d(50000);
7452 local_params.set_use_timetabling_in_no_overlap_2d(
true);
7453 local_params.set_use_energetic_reasoning_in_no_overlap_2d(
true);
7454 local_params.set_use_area_energetic_reasoning_in_no_overlap_2d(
true);
7455 local_params.set_use_conservative_scale_overload_checker(
true);
7456 local_params.set_use_dual_scheduling_heuristics(
true);
7458 model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7459 std::vector<int> variable_mapping;
7464 &model,
"single constraint")) {
7468 time_limit_->AdvanceDeterministicTime(
7469 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7470 auto* mapping = model.GetOrCreate<CpModelMapping>();
7471 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7472 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7473 auto* trail = model.GetOrCreate<Trail>();
7476 int num_changed_bounds = 0;
7477 int num_fixed_bools = 0;
7478 for (
int var = 0; var < variable_mapping.size(); ++var) {
7479 const int proto_var = variable_mapping[var];
7480 if (mapping->IsBoolean(var)) {
7481 const Literal l = mapping->Literal(var);
7482 if (trail->Assignment().LiteralIsFalse(l)) {
7483 if (!context_->SetLiteralToFalse(proto_var))
return;
7486 }
else if (trail->Assignment().LiteralIsTrue(l)) {
7487 if (!context_->SetLiteralToTrue(proto_var))
return;
7492 const Literal r = implication_graph->RepresentativeOf(l);
7496 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7497 if (r_var < 0)
continue;
7498 if (!context_->StoreBooleanEqualityRelation(
7499 proto_var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
7505 bool changed =
false;
7506 if (!context_->IntersectDomainWith(
7508 integer_trail->InitialVariableDomain(mapping->Integer(var)),
7512 if (changed) ++num_changed_bounds;
7515 if (num_changed_bounds > 0) {
7516 context_->UpdateRuleStats(
"propagators: changed bounds",
7517 num_changed_bounds);
7519 if (num_fixed_bools > 0) {
7520 context_->UpdateRuleStats(
"propagators: fixed booleans", num_fixed_bools);
7526void CpModelPresolver::Probe() {
7527 auto probing_timer =
7528 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
7537 auto* implication_graph = model.GetOrCreate<BinaryImplicationGraph>();
7538 auto* sat_solver = model.GetOrCreate<SatSolver>();
7539 auto* mapping = model.GetOrCreate<CpModelMapping>();
7540 auto* prober = model.GetOrCreate<Prober>();
7553 const auto& assignment = sat_solver->Assignment();
7554 prober->SetPropagationCallback([&](Literal decision) {
7555 if (probing_timer->WorkLimitIsReached())
return;
7556 const int decision_var =
7557 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
7558 if (decision_var < 0)
return;
7559 probing_timer->TrackSimpleLoop(
7560 context_->VarToConstraints(decision_var).size());
7561 std::vector<int> to_update;
7562 for (
const int c : context_->VarToConstraints(decision_var)) {
7563 if (c < 0)
continue;
7564 const ConstraintProto& ct = context_->working_model->constraints(c);
7565 if (ct.enforcement_literal().size() > 2) {
7579 bool decision_is_positive =
false;
7580 bool has_false_literal =
false;
7581 bool simplification_possible =
false;
7582 probing_timer->TrackSimpleLoop(ct.enforcement_literal().size());
7583 for (
const int ref : ct.enforcement_literal()) {
7584 const Literal lit = mapping->Literal(ref);
7587 decision_is_positive = assignment.LiteralIsTrue(lit);
7588 if (!decision_is_positive)
break;
7591 if (assignment.LiteralIsFalse(lit)) {
7593 has_false_literal =
true;
7594 }
else if (assignment.LiteralIsTrue(lit)) {
7596 simplification_possible =
true;
7599 if (!decision_is_positive)
continue;
7601 if (has_false_literal) {
7603 auto* mutable_ct = context_->working_model->mutable_constraints(c);
7604 mutable_ct->Clear();
7605 mutable_ct->add_enforcement_literal(decision_ref);
7606 mutable_ct->mutable_bool_and()->add_literals(
NegatedRef(false_ref));
7607 context_->UpdateRuleStats(
7608 "probing: reduced enforced constraint to implication.");
7609 to_update.push_back(c);
7613 if (simplification_possible) {
7615 auto* mutable_enforcements =
7616 context_->working_model->mutable_constraints(c)
7617 ->mutable_enforcement_literal();
7618 for (
const int ref : ct.enforcement_literal()) {
7620 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7623 mutable_enforcements->Set(new_size++, ref);
7625 mutable_enforcements->Truncate(new_size);
7626 context_->UpdateRuleStats(
"probing: simplified enforcement list.");
7627 to_update.push_back(c);
7632 if (ct.constraint_case() != ConstraintProto::kBoolOr)
continue;
7633 if (ct.bool_or().literals().size() <= 2)
continue;
7637 bool decision_is_negative =
false;
7638 bool has_true_literal =
false;
7639 bool simplification_possible =
false;
7640 probing_timer->TrackSimpleLoop(ct.bool_or().literals().size());
7641 for (
const int ref : ct.bool_or().literals()) {
7642 const Literal lit = mapping->Literal(ref);
7645 decision_is_negative = assignment.LiteralIsFalse(lit);
7646 if (!decision_is_negative)
break;
7649 if (assignment.LiteralIsTrue(lit)) {
7651 has_true_literal =
true;
7652 }
else if (assignment.LiteralIsFalse(lit)) {
7654 simplification_possible =
true;
7657 if (!decision_is_negative)
continue;
7659 if (has_true_literal) {
7662 auto* mutable_bool_or =
7663 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7664 mutable_bool_or->mutable_literals()->Clear();
7665 mutable_bool_or->add_literals(decision_ref);
7666 mutable_bool_or->add_literals(true_ref);
7667 context_->UpdateRuleStats(
"probing: bool_or reduced to implication");
7668 to_update.push_back(c);
7672 if (simplification_possible) {
7674 auto* mutable_bool_or =
7675 context_->working_model->mutable_constraints(c)->mutable_bool_or();
7676 for (
const int ref : ct.bool_or().literals()) {
7678 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7681 mutable_bool_or->set_literals(new_size++, ref);
7683 mutable_bool_or->mutable_literals()->Truncate(new_size);
7684 context_->UpdateRuleStats(
"probing: simplified clauses.");
7685 to_update.push_back(c);
7689 absl::c_sort(to_update);
7690 for (
const int c : to_update) {
7691 context_->UpdateConstraintVariableUsage(c);
7695 prober->ProbeBooleanVariables(
7696 context_->params().probing_deterministic_time_limit());
7698 probing_timer->AddCounter(
"probed", prober->num_decisions());
7699 probing_timer->AddToWork(
7700 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7701 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7702 return (
void)context_->NotifyThatModelIsUnsat(
"during probing");
7707 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7708 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
7709 const Literal l = sat_solver->LiteralTrail()[
i];
7710 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7712 const int ref = l.IsPositive() ? var :
NegatedRef(var);
7713 if (context_->IsFixed(ref))
continue;
7715 if (!context_->SetLiteralToTrue(ref))
return;
7718 probing_timer->AddCounter(
"fixed_bools", num_fixed);
7721 int num_changed_bounds = 0;
7722 const int num_variables = context_->working_model->variables().size();
7723 auto* integer_trail = model.GetOrCreate<IntegerTrail>();
7724 for (
int var = 0; var < num_variables; ++var) {
7727 if (!mapping->IsBoolean(var)) {
7728 bool changed =
false;
7729 if (!context_->IntersectDomainWith(
7730 var, integer_trail->InitialVariableDomain(mapping->Integer(var)),
7734 if (changed) ++num_changed_bounds;
7739 const Literal l = mapping->Literal(var);
7740 const Literal r = implication_graph->RepresentativeOf(l);
7744 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7746 if (!context_->StoreBooleanEqualityRelation(
7747 var, r.IsPositive() ? r_var :
NegatedRef(r_var))) {
7752 probing_timer->AddCounter(
"new_bounds", num_changed_bounds);
7753 probing_timer->AddCounter(
"equiv", num_equiv);
7754 probing_timer->AddCounter(
"new_binary_clauses",
7755 prober->num_new_binary_clauses());
7760 DetectDuplicateConstraintsWithDifferentEnforcements(
7761 mapping, implication_graph, model.GetOrCreate<Trail>());
7764 probing_timer.reset();
7767 if (context_->params().merge_at_most_one_work_limit() > 0.0) {
7768 PresolveTimer timer(
"MaxClique", logger_, time_limit_);
7769 std::vector<std::vector<Literal>> cliques;
7770 std::vector<int> clique_ct_index;
7774 int64_t num_literals_before = 0;
7775 const int num_constraints = context_->working_model->constraints_size();
7776 for (
int c = 0;
c < num_constraints; ++
c) {
7777 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
7778 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
7779 std::vector<Literal> clique;
7780 for (
const int ref : ct->at_most_one().literals()) {
7781 clique.push_back(mapping->Literal(ref));
7783 num_literals_before += clique.size();
7784 cliques.push_back(clique);
7786 context_->UpdateConstraintVariableUsage(c);
7787 }
else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
7788 if (ct->enforcement_literal().size() != 1)
continue;
7789 const Literal enforcement =
7790 mapping->Literal(ct->enforcement_literal(0));
7791 for (
const int ref : ct->bool_and().literals()) {
7792 if (ref == ct->enforcement_literal(0))
continue;
7793 num_literals_before += 2;
7794 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7797 context_->UpdateConstraintVariableUsage(c);
7800 const int64_t num_old_cliques = cliques.size();
7805 double limit = context_->params().merge_at_most_one_work_limit();
7806 if (num_literals_before > 1e6) {
7807 limit *= num_literals_before / 1e6;
7811 implication_graph->MergeAtMostOnes(absl::MakeSpan(cliques),
7813 timer.AddToWork(dtime);
7818 int num_new_cliques = 0;
7819 int64_t num_literals_after = 0;
7820 for (
const std::vector<Literal>& clique : cliques) {
7821 if (clique.empty())
continue;
7823 num_literals_after += clique.size();
7824 ConstraintProto* ct = context_->working_model->add_constraints();
7825 for (
const Literal literal : clique) {
7827 mapping->GetProtoVariableFromBooleanVariable(literal.Variable());
7828 if (var < 0)
continue;
7829 if (literal.IsPositive()) {
7830 ct->mutable_at_most_one()->add_literals(var);
7832 ct->mutable_at_most_one()->add_literals(
NegatedRef(var));
7837 PresolveAtMostOne(ct);
7839 context_->UpdateNewConstraintsVariableUsage();
7840 if (num_new_cliques != num_old_cliques) {
7841 context_->UpdateRuleStats(
"at_most_one: transformed into max clique.");
7844 if (num_old_cliques != num_new_cliques ||
7845 num_literals_before != num_literals_after) {
7846 timer.AddMessage(absl::StrCat(
7850 FormatCounter(num_literals_after),
" literals) at_most_ones. "));
7858 absl::Span<const int> var_mapping,
7860 const int num_vars = assignment.NumberOfVariables();
7861 for (
int i = 0;
i < num_vars; ++
i) {
7862 const Literal lit(BooleanVariable(
i),
true);
7863 const int ref = var_mapping[
i];
7864 if (assignment.LiteralIsTrue(lit)) {
7865 if (!context->SetLiteralToTrue(ref))
return false;
7866 }
else if (assignment.LiteralIsFalse(lit)) {
7867 if (!context->SetLiteralToFalse(ref))
return false;
7877bool CpModelPresolver::PresolvePureSatPart() {
7880 if (context_->ModelIsUnsat())
return true;
7881 if (context_->params().keep_all_feasible_solutions_in_presolve())
return true;
7884 int num_variables = 0;
7885 int num_ignored_variables = 0;
7886 const int total_num_vars = context_->working_model->variables().size();
7887 std::vector<int> new_index(total_num_vars, -1);
7888 std::vector<int> new_to_old_index;
7889 for (
int i = 0;
i < total_num_vars; ++
i) {
7890 if (!context_->CanBeUsedAsLiteral(
i)) {
7891 ++num_ignored_variables;
7896 if (context_->VarToConstraints(
i).empty())
continue;
7898 new_to_old_index.push_back(
i);
7899 new_index[
i] = num_variables++;
7900 DCHECK_EQ(num_variables, new_to_old_index.size());
7904 auto convert = [&new_index](
int ref) {
7906 DCHECK_NE(index, -1);
7922 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7923 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
7924 sat_solver->SetNumVariables(num_variables);
7929 for (
const int var : new_to_old_index) {
7930 if (context_->IsFixed(var)) {
7931 if (context_->LiteralIsTrue(var)) {
7932 if (!sat_solver->AddUnitClause({convert(var)}))
return false;
7934 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
7941 std::vector<Literal> clause;
7942 int num_removed_constraints = 0;
7943 int num_ignored_constraints = 0;
7944 for (
int i = 0;
i < context_->working_model->constraints_size(); ++
i) {
7945 const ConstraintProto& ct = context_->working_model->constraints(
i);
7947 if (ct.constraint_case() == ConstraintProto::kBoolOr) {
7948 ++num_removed_constraints;
7950 for (
const int ref : ct.bool_or().literals()) {
7951 clause.push_back(convert(ref));
7953 for (
const int ref : ct.enforcement_literal()) {
7954 clause.push_back(convert(ref).Negated());
7956 sat_solver->AddProblemClause(clause);
7958 context_->working_model->mutable_constraints(
i)->Clear();
7959 context_->UpdateConstraintVariableUsage(
i);
7963 if (ct.constraint_case() == ConstraintProto::kBoolAnd) {
7966 const int left_size = ct.enforcement_literal().size();
7967 const int right_size = ct.bool_and().literals().size();
7968 if (left_size > 1 && right_size > 1 &&
7969 (left_size + 1) * right_size > 10'000) {
7970 ++num_ignored_constraints;
7974 ++num_removed_constraints;
7975 std::vector<Literal> clause;
7976 for (
const int ref : ct.enforcement_literal()) {
7977 clause.push_back(convert(ref).Negated());
7980 for (
const int ref : ct.bool_and().literals()) {
7981 clause.back() = convert(ref);
7982 sat_solver->AddProblemClause(clause);
7985 context_->working_model->mutable_constraints(
i)->Clear();
7986 context_->UpdateConstraintVariableUsage(
i);
7990 if (ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
7994 ++num_ignored_constraints;
7996 if (sat_solver->ModelIsUnsat())
return false;
7999 if (num_removed_constraints == 0)
return true;
8009 int num_in_extra_constraints = 0;
8010 std::vector<bool> can_be_removed(num_variables,
false);
8011 for (
int i = 0;
i < num_variables; ++
i) {
8012 const int var = new_to_old_index[
i];
8013 if (context_->VarToConstraints(var).empty()) {
8014 can_be_removed[
i] =
true;
8018 ++num_in_extra_constraints;
8031 SatParameters params = context_->params();
8032 if (params.debug_postsolve_with_full_solver() ||
8033 params.fill_tightened_domains_in_response()) {
8034 params.set_presolve_blocked_clause(
false);
8037 SatPostsolver sat_postsolver(num_variables);
8047 util_intops::StrongVector<LiteralIndex, LiteralIndex> equiv_map;
8048 if (!context_->params().debug_postsolve_with_full_solver() &&
8049 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
8050 num_in_extra_constraints == 0) {
8056 &local_model, logger_)) {
8059 if (sat_solver->LiteralTrail().Index() == num_variables) {
8061 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
8066 SatPresolveOptions options;
8067 options.log_info =
true;
8068 options.extract_binary_clauses_in_probing =
false;
8069 options.use_transitive_reduction =
false;
8070 options.deterministic_time_limit =
8071 context_->params().presolve_probing_deterministic_time_limit();
8073 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
8074 inprocessing->ProvideLogger(logger_);
8075 if (!inprocessing->PresolveLoop(options))
return false;
8076 for (
const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
8077 sat_postsolver.Add(c[0], c);
8083 nullptr, &equiv_map,
8085 if (sat_solver->ModelIsUnsat())
return false;
8090 params.set_presolve_use_bva(
false);
8098 if (context_->params().keep_symmetry_in_presolve()) {
8099 params.set_presolve_use_bva(
false);
8103 if (!sat_solver->ResetToLevelZero())
return false;
8104 time_limit_->AdvanceDeterministicTime(
8105 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
8108 SatPresolver sat_presolver(&sat_postsolver, logger_);
8109 sat_presolver.SetNumVariables(num_variables);
8110 if (!equiv_map.empty()) {
8111 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
8113 sat_presolver.SetTimeLimit(time_limit_);
8114 sat_presolver.SetParameters(params);
8118 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
8119 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[
i]);
8121 sat_solver->ExtractClauses(&sat_presolver);
8125 for (
int i = 0;
i < 1; ++
i) {
8126 const int old_num_clause = sat_postsolver.NumClauses();
8127 if (!sat_presolver.Presolve(can_be_removed))
return false;
8128 if (old_num_clause == sat_postsolver.NumClauses())
break;
8132 const int new_num_variables = sat_presolver.NumVariables();
8133 if (new_num_variables > num_variables) {
8134 VLOG(1) <<
"New variables added by the SAT presolver.";
8135 for (
int i = num_variables;
i < new_num_variables; ++
i) {
8136 new_to_old_index.push_back(context_->working_model->variables().size());
8137 IntegerVariableProto* var_proto =
8138 context_->working_model->add_variables();
8139 var_proto->add_domain(0);
8140 var_proto->add_domain(1);
8142 context_->InitializeNewDomains();
8146 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
8152 ExtractClauses(
true, new_to_old_index, sat_presolver,
8153 context_->working_model);
8156 context_->UpdateNewConstraintsVariableUsage();
8161 for (
int i = 0;
i < num_variables; ++
i) {
8162 const int var = new_to_old_index[
i];
8163 if (context_->VarToConstraints(var).empty()) {
8166 if (!context_->IsFixed(var)) {
8167 CHECK(context_->IntersectDomainWith(
8168 var, Domain(context_->DomainOf(var).SmallestValue())));
8170 context_->MarkVariableAsRemoved(var);
8175 const std::string name =
8176 absl::GetFlag(FLAGS_cp_model_debug_postsolve) ?
"sat_postsolver" :
"";
8177 ExtractClauses(
false, new_to_old_index,
8178 sat_postsolver, context_->mapping_model, name);
8182void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
8183 if (context_->ModelIsUnsat())
return;
8187 if (!context_->CanonicalizeObjective()) {
8191 std::vector<int> exos;
8192 const int num_constraints = context_->working_model->constraints_size();
8193 for (
int c = 0;
c < num_constraints; ++
c) {
8194 const ConstraintProto& ct = context_->working_model->constraints(c);
8195 if (!ct.enforcement_literal().empty())
continue;
8196 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8213 for (
int i = 0;
i < 3; ++
i) {
8214 for (
const int c : exos) {
8215 const ConstraintProto& ct = context_->working_model->constraints(c);
8216 const int num_terms = ct.exactly_one().literals().size();
8217 if (num_terms <= 1)
continue;
8218 int64_t min_obj = std::numeric_limits<int64_t>::max();
8219 int64_t second_min = std::numeric_limits<int64_t>::max();
8220 for (
int i = 0;
i < num_terms; ++
i) {
8221 const int literal = ct.exactly_one().literals(
i);
8222 const int64_t var_obj = context_->ObjectiveCoeff(
PositiveRef(literal));
8223 const int64_t obj =
RefIsPositive(literal) ? var_obj : -var_obj;
8224 if (obj < min_obj) {
8225 second_min = min_obj;
8227 }
else if (obj < second_min) {
8231 if (second_min == 0)
continue;
8233 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8235 if (context_->ModelIsUnsat())
return;
8240 if (num_shifts > 0) {
8241 context_->UpdateRuleStats(
"objective: shifted cost with exactly ones",
8246bool CpModelPresolver::PropagateObjective() {
8247 if (!context_->working_model->has_objective())
return true;
8248 if (context_->ModelIsUnsat())
return false;
8249 context_->WriteObjectiveToProto();
8251 int64_t min_activity = 0;
8252 int64_t max_variation = 0;
8253 const CpObjectiveProto& objective = context_->working_model->objective();
8254 const int num_terms = objective.vars().size();
8255 for (
int i = 0;
i < num_terms; ++
i) {
8256 const int var = objective.vars(
i);
8257 const int64_t coeff = objective.coeffs(
i);
8261 const int64_t domain_min = context_->MinOf(var);
8262 const int64_t domain_max = context_->MaxOf(var);
8264 min_activity += coeff * domain_min;
8266 min_activity += coeff * domain_max;
8268 const int64_t variation = std::abs(coeff) * (domain_max - domain_min);
8269 max_variation = std::max(max_variation, variation);
8273 const int64_t slack =
8276 return context_->NotifyThatModelIsUnsat(
8277 "infeasible while propagating objective");
8281 if (max_variation <= slack)
return true;
8283 int num_propagations = 0;
8284 for (
int i = 0;
i < num_terms; ++
i) {
8285 const int var = objective.vars(
i);
8286 const int64_t coeff = objective.coeffs(
i);
8287 const int64_t domain_min = context_->MinOf(var);
8288 const int64_t domain_max = context_->MaxOf(var);
8290 const int64_t new_diff = slack / std::abs(coeff);
8291 if (new_diff >= domain_max - domain_min)
continue;
8295 if (!context_->IntersectDomainWith(
8296 var, Domain(domain_min, domain_min + new_diff))) {
8300 if (!context_->IntersectDomainWith(
8301 var, Domain(domain_max - new_diff, domain_max))) {
8306 CHECK_GT(num_propagations, 0);
8308 context_->UpdateRuleStats(
"objective: restricted var domains by propagation",
8327void CpModelPresolver::ExpandObjective() {
8328 if (time_limit_->LimitReached())
return;
8329 if (context_->ModelIsUnsat())
return;
8330 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8334 if (!context_->CanonicalizeObjective()) {
8338 const int num_variables = context_->working_model->variables_size();
8339 const int num_constraints = context_->working_model->constraints_size();
8342 const auto get_index = [](
int var,
bool to_lb) {
8343 return 2 * var + (to_lb ? 0 : 1);
8345 const auto get_lit_index = [](
int lit) {
8348 const int num_nodes = 2 * num_variables;
8349 std::vector<std::vector<int>> index_graph(num_nodes);
8353 std::vector<int> index_to_best_c(num_nodes, -1);
8354 std::vector<int> index_to_best_size(num_nodes, 0);
8358 int num_entries = 0;
8359 int num_propagations = 0;
8360 int num_tight_variables = 0;
8361 int num_tight_constraints = 0;
8362 const int kNumEntriesThreshold = 1e8;
8363 for (
int c = 0;
c < num_constraints; ++
c) {
8364 if (num_entries > kNumEntriesThreshold)
break;
8366 const ConstraintProto& ct = context_->working_model->constraints(c);
8367 if (!ct.enforcement_literal().empty())
continue;
8375 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8376 if (PresolveExactlyOne(context_->working_model->mutable_constraints(c))) {
8377 context_->UpdateConstraintVariableUsage(c);
8380 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8381 const int num_terms = ct.exactly_one().literals().size();
8382 ++num_tight_constraints;
8383 num_tight_variables += num_terms;
8384 for (
int i = 0;
i < num_terms; ++
i) {
8385 if (num_entries > kNumEntriesThreshold)
break;
8386 const int neg_index = get_lit_index(ct.exactly_one().literals(
i)) ^ 1;
8388 const int old_c = index_to_best_c[neg_index];
8389 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8390 index_to_best_c[neg_index] =
c;
8391 index_to_best_size[neg_index] = num_terms;
8394 for (
int j = 0; j < num_terms; ++j) {
8395 if (j ==
i)
continue;
8396 const int other_index = get_lit_index(ct.exactly_one().literals(j));
8398 index_graph[neg_index].push_back(other_index);
8405 if (!IsLinearEqualityConstraint(ct))
continue;
8409 const auto [min_activity, max_activity] =
8410 context_->ComputeMinMaxActivity(ct.linear());
8412 bool is_tight =
false;
8413 const int64_t rhs = ct.linear().domain(0);
8414 const int num_terms = ct.linear().vars_size();
8415 for (
int i = 0;
i < num_terms; ++
i) {
8416 const int var = ct.linear().vars(
i);
8417 const int64_t coeff = ct.linear().coeffs(
i);
8418 if (std::abs(coeff) != 1)
continue;
8419 if (num_entries > kNumEntriesThreshold)
break;
8421 const int index = get_index(var, coeff > 0);
8423 const int64_t var_range = context_->MaxOf(var) - context_->MinOf(var);
8424 const int64_t implied_shifted_ub = rhs - min_activity;
8425 if (implied_shifted_ub <= var_range) {
8426 if (implied_shifted_ub < var_range) ++num_propagations;
8428 ++num_tight_variables;
8430 const int neg_index = index ^ 1;
8431 const int old_c = index_to_best_c[neg_index];
8432 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
8433 index_to_best_c[neg_index] =
c;
8434 index_to_best_size[neg_index] = num_terms;
8437 for (
int j = 0; j < num_terms; ++j) {
8438 if (j ==
i)
continue;
8439 const int other_index =
8440 get_index(ct.linear().vars(j), ct.linear().coeffs(j) > 0);
8442 index_graph[neg_index].push_back(other_index);
8445 const int64_t implied_shifted_lb = max_activity - rhs;
8446 if (implied_shifted_lb <= var_range) {
8447 if (implied_shifted_lb < var_range) ++num_propagations;
8449 ++num_tight_variables;
8451 const int old_c = index_to_best_c[index];
8452 if (old_c == -1 || num_terms > index_to_best_size[index]) {
8453 index_to_best_c[index] =
c;
8454 index_to_best_size[index] = num_terms;
8457 for (
int j = 0; j < num_terms; ++j) {
8458 if (j ==
i)
continue;
8459 const int other_index =
8460 get_index(ct.linear().vars(j), ct.linear().coeffs(j) < 0);
8462 index_graph[index].push_back(other_index);
8466 if (is_tight) ++num_tight_constraints;
8472 if (num_propagations > 0) {
8473 context_->UpdateRuleStats(
"TODO objective: propagation possible!");
8492 if (!topo_order.ok()) {
8495 std::vector<int64_t> var_min(num_variables);
8496 std::vector<int64_t> var_max(num_variables);
8497 for (
int var = 0; var < num_variables; ++var) {
8498 var_min[var] = context_->MinOf(var);
8499 var_max[var] = context_->MaxOf(var);
8502 std::vector<std::vector<int>> components;
8504 index_graph, &components);
8505 for (
const std::vector<int>& compo : components) {
8506 if (compo.size() == 1)
continue;
8508 const int rep_var = compo[0] / 2;
8509 const bool rep_to_lp = (compo[0] % 2) == 0;
8510 for (
int i = 1;
i < compo.size(); ++
i) {
8511 const int var = compo[
i] / 2;
8512 const bool to_lb = (compo[
i] % 2) == 0;
8516 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
8517 const int64_t var_coeff = to_lb ? 1 : -1;
8518 const int64_t offset =
8519 (to_lb ? -var_min[var] : var_max[var]) -
8520 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
8521 if (!context_->StoreAffineRelation(rep_var, var, rep_coeff * var_coeff,
8522 rep_coeff * offset)) {
8526 context_->UpdateRuleStats(
"objective: detected equivalence",
8535 int num_expands = 0;
8537 for (
const int index : *topo_order) {
8538 if (index_graph[index].empty())
continue;
8540 const int var = index / 2;
8541 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
8542 if (obj_coeff == 0)
continue;
8544 const bool to_lb = (index % 2) == 0;
8545 if (obj_coeff > 0 == to_lb) {
8546 const ConstraintProto& ct =
8547 context_->working_model->constraints(index_to_best_c[index]);
8548 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
8550 for (
const int lit : ct.exactly_one().literals()) {
8560 if (!context_->ShiftCostInExactlyOne(ct.exactly_one().literals(),
8562 if (context_->ModelIsUnsat())
return;
8566 CHECK_EQ(context_->ObjectiveCoeff(var), 0);
8571 int64_t objective_coeff_in_expanded_constraint = 0;
8572 const int num_terms = ct.linear().vars().size();
8573 for (
int i = 0;
i < num_terms; ++
i) {
8574 if (ct.linear().vars(
i) == var) {
8575 objective_coeff_in_expanded_constraint = ct.linear().coeffs(
i);
8579 if (objective_coeff_in_expanded_constraint == 0) {
8584 if (!context_->SubstituteVariableInObjective(
8585 var, objective_coeff_in_expanded_constraint, ct)) {
8586 if (context_->ModelIsUnsat())
return;
8595 if (num_expands > 0) {
8596 context_->UpdateRuleStats(
"objective: expanded via tight equality",
8600 timer.AddCounter(
"propagations", num_propagations);
8601 timer.AddCounter(
"entries", num_entries);
8602 timer.AddCounter(
"tight_variables", num_tight_variables);
8603 timer.AddCounter(
"tight_constraints", num_tight_constraints);
8604 timer.AddCounter(
"expands", num_expands);
8605 timer.AddCounter(
"issues", num_issues);
8608void CpModelPresolver::MergeNoOverlapConstraints() {
8609 if (context_->ModelIsUnsat())
return;
8611 const int num_constraints = context_->working_model->constraints_size();
8612 int old_num_no_overlaps = 0;
8613 int old_num_intervals = 0;
8616 std::vector<int> disjunctive_index;
8617 std::vector<std::vector<Literal>> cliques;
8618 for (
int c = 0;
c < num_constraints; ++
c) {
8619 const ConstraintProto& ct = context_->working_model->constraints(c);
8620 if (ct.constraint_case() != ConstraintProto::kNoOverlap)
continue;
8621 std::vector<Literal> clique;
8622 for (
const int i : ct.no_overlap().intervals()) {
8623 clique.push_back(Literal(BooleanVariable(
i),
true));
8625 cliques.push_back(clique);
8626 disjunctive_index.push_back(c);
8628 old_num_no_overlaps++;
8629 old_num_intervals += clique.size();
8631 if (old_num_no_overlaps == 0)
return;
8635 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
8636 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8637 graph->Resize(num_constraints);
8638 for (
const std::vector<Literal>& clique : cliques) {
8641 CHECK(graph->AddAtMostOne(clique));
8643 CHECK(graph->DetectEquivalences());
8644 graph->TransformIntoMaxCliques(
8649 int new_num_no_overlaps = 0;
8650 int new_num_intervals = 0;
8651 for (
int i = 0;
i < cliques.size(); ++
i) {
8652 const int ct_index = disjunctive_index[
i];
8653 ConstraintProto* ct =
8654 context_->working_model->mutable_constraints(ct_index);
8656 if (cliques[
i].empty())
continue;
8657 for (
const Literal l : cliques[
i]) {
8658 CHECK(l.IsPositive());
8659 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
8661 new_num_no_overlaps++;
8662 new_num_intervals += cliques[
i].size();
8664 if (old_num_intervals != new_num_intervals ||
8665 old_num_no_overlaps != new_num_no_overlaps) {
8666 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
8667 old_num_intervals,
" intervals) into ",
8668 new_num_no_overlaps,
" no-overlaps (",
8669 new_num_intervals,
" intervals).");
8670 context_->UpdateRuleStats(
"no_overlap: merged constraints");
8678void CpModelPresolver::TransformIntoMaxCliques() {
8679 if (context_->ModelIsUnsat())
return;
8680 if (context_->params().merge_at_most_one_work_limit() <= 0.0)
return;
8682 auto convert = [](
int ref) {
8683 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
8684 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
8686 const int num_constraints = context_->working_model->constraints_size();
8690 std::vector<std::vector<Literal>> cliques;
8692 for (
int c = 0;
c < num_constraints; ++
c) {
8693 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8694 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
8695 std::vector<Literal> clique;
8696 for (
const int ref : ct->at_most_one().literals()) {
8697 clique.push_back(convert(ref));
8699 cliques.push_back(clique);
8700 if (RemoveConstraint(ct)) {
8701 context_->UpdateConstraintVariableUsage(c);
8703 }
else if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
8704 if (ct->enforcement_literal().size() != 1)
continue;
8705 const Literal enforcement = convert(ct->enforcement_literal(0));
8706 for (
const int ref : ct->bool_and().literals()) {
8707 if (ref == ct->enforcement_literal(0))
continue;
8708 cliques.push_back({enforcement, convert(ref).Negated()});
8710 if (RemoveConstraint(ct)) {
8711 context_->UpdateConstraintVariableUsage(c);
8716 int64_t num_literals_before = 0;
8717 const int num_old_cliques = cliques.size();
8721 const int num_variables = context_->working_model->
variables().size();
8722 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8723 auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8724 graph->Resize(num_variables);
8725 for (
const std::vector<Literal>& clique : cliques) {
8726 num_literals_before += clique.size();
8727 if (!graph->AddAtMostOne(clique)) {
8728 return (
void)context_->NotifyThatModelIsUnsat();
8731 if (!graph->DetectEquivalences()) {
8732 return (
void)context_->NotifyThatModelIsUnsat();
8734 graph->MergeAtMostOnes(
8735 absl::MakeSpan(cliques),
8741 for (
int var = 0; var < num_variables; ++var) {
8742 const Literal l = Literal(BooleanVariable(var),
true);
8743 if (graph->RepresentativeOf(l) != l) {
8744 const Literal r = graph->RepresentativeOf(l);
8745 if (!context_->StoreBooleanEqualityRelation(
8746 var, r.IsPositive() ? r.Variable().value()
8753 int num_new_cliques = 0;
8754 int64_t num_literals_after = 0;
8755 for (
const std::vector<Literal>& clique : cliques) {
8756 if (clique.empty())
continue;
8758 num_literals_after += clique.size();
8759 ConstraintProto* ct = context_->working_model->add_constraints();
8760 for (
const Literal literal : clique) {
8761 if (literal.IsPositive()) {
8762 ct->mutable_at_most_one()->add_literals(literal.Variable().value());
8764 ct->mutable_at_most_one()->add_literals(
8770 PresolveAtMostOne(ct);
8772 context_->UpdateNewConstraintsVariableUsage();
8773 if (num_new_cliques != num_old_cliques) {
8774 context_->UpdateRuleStats(
"at_most_one: transformed into max clique.");
8777 if (num_old_cliques != num_new_cliques ||
8778 num_literals_before != num_literals_after) {
8779 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
8780 num_literals_before,
" literals) into ", num_new_cliques,
"(",
8781 num_literals_after,
" literals) at_most_ones.");
8786 if (context_->ModelIsUnsat())
return false;
8787 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
8790 if (ExploitEquivalenceRelations(c, ct)) {
8791 context_->UpdateConstraintVariableUsage(c);
8795 if (PresolveEnforcementLiteral(ct)) {
8796 context_->UpdateConstraintVariableUsage(c);
8800 switch (ct->constraint_case()) {
8801 case ConstraintProto::kBoolOr:
8802 return PresolveBoolOr(ct);
8803 case ConstraintProto::kBoolAnd:
8804 return PresolveBoolAnd(ct);
8805 case ConstraintProto::kAtMostOne:
8806 return PresolveAtMostOne(ct);
8807 case ConstraintProto::kExactlyOne:
8808 return PresolveExactlyOne(ct);
8809 case ConstraintProto::kBoolXor:
8810 return PresolveBoolXor(ct);
8811 case ConstraintProto::kLinMax:
8812 if (CanonicalizeLinearArgument(*ct, ct->mutable_lin_max())) {
8813 context_->UpdateConstraintVariableUsage(c);
8815 return PresolveLinMax(c, ct);
8816 case ConstraintProto::kIntProd:
8817 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_prod())) {
8818 context_->UpdateConstraintVariableUsage(c);
8820 return PresolveIntProd(ct);
8821 case ConstraintProto::kIntDiv:
8822 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_div())) {
8823 context_->UpdateConstraintVariableUsage(c);
8825 return PresolveIntDiv(c, ct);
8826 case ConstraintProto::kIntMod:
8827 if (CanonicalizeLinearArgument(*ct, ct->mutable_int_mod())) {
8828 context_->UpdateConstraintVariableUsage(c);
8830 return PresolveIntMod(c, ct);
8831 case ConstraintProto::kLinear: {
8832 if (CanonicalizeLinear(ct)) {
8833 context_->UpdateConstraintVariableUsage(c);
8835 if (PropagateDomainsInLinear(c, ct)) {
8836 context_->UpdateConstraintVariableUsage(c);
8838 if (PresolveSmallLinear(ct)) {
8839 context_->UpdateConstraintVariableUsage(c);
8841 if (PresolveLinearEqualityWithModulo(ct)) {
8842 context_->UpdateConstraintVariableUsage(c);
8845 if (RemoveSingletonInLinear(ct)) {
8846 context_->UpdateConstraintVariableUsage(c);
8850 if (PresolveSmallLinear(ct)) {
8851 context_->UpdateConstraintVariableUsage(c);
8854 if (PresolveSmallLinear(ct)) {
8855 context_->UpdateConstraintVariableUsage(c);
8857 if (PresolveLinearOnBooleans(ct)) {
8858 context_->UpdateConstraintVariableUsage(c);
8862 const int old_num_enforcement_literals = ct->enforcement_literal_size();
8863 ExtractEnforcementLiteralFromLinearConstraint(c, ct);
8864 if (context_->ModelIsUnsat())
return false;
8865 if (ct->enforcement_literal_size() > old_num_enforcement_literals) {
8866 if (DivideLinearByGcd(ct)) {
8867 context_->UpdateConstraintVariableUsage(c);
8869 if (PresolveSmallLinear(ct)) {
8870 context_->UpdateConstraintVariableUsage(c);
8874 if (PresolveDiophantine(ct)) {
8875 context_->UpdateConstraintVariableUsage(c);
8878 TryToReduceCoefficientsOfLinearConstraint(c, ct);
8881 case ConstraintProto::kInterval:
8882 return PresolveInterval(c, ct);
8883 case ConstraintProto::kInverse:
8884 return PresolveInverse(ct);
8885 case ConstraintProto::kElement:
8886 return PresolveElement(c, ct);
8887 case ConstraintProto::kTable:
8888 return PresolveTable(ct);
8889 case ConstraintProto::kAllDiff:
8890 return PresolveAllDiff(ct);
8891 case ConstraintProto::kNoOverlap:
8892 DetectDuplicateIntervals(c,
8893 ct->mutable_no_overlap()->mutable_intervals());
8894 return PresolveNoOverlap(ct);
8895 case ConstraintProto::kNoOverlap2D: {
8896 const bool changed = PresolveNoOverlap2D(c, ct);
8897 if (ct->constraint_case() == ConstraintProto::kNoOverlap2D) {
8902 DetectDuplicateIntervals(
8903 c, ct->mutable_no_overlap_2d()->mutable_x_intervals());
8904 DetectDuplicateIntervals(
8905 c, ct->mutable_no_overlap_2d()->mutable_y_intervals());
8909 case ConstraintProto::kCumulative:
8910 DetectDuplicateIntervals(c,
8911 ct->mutable_cumulative()->mutable_intervals());
8912 return PresolveCumulative(ct);
8913 case ConstraintProto::kCircuit:
8914 return PresolveCircuit(ct);
8915 case ConstraintProto::kRoutes:
8916 return PresolveRoutes(ct);
8917 case ConstraintProto::kAutomaton:
8918 return PresolveAutomaton(ct);
8919 case ConstraintProto::kReservoir:
8920 return PresolveReservoir(ct);
8927bool CpModelPresolver::ProcessSetPPCSubset(
int subset_c,
int superset_c,
8928 absl::flat_hash_set<int>* tmp_set,
8929 bool* remove_subset,
8930 bool* remove_superset,
8931 bool* stop_processing_superset) {
8932 ConstraintProto* subset_ct =
8934 ConstraintProto* superset_ct =
8937 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8938 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8939 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8940 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8944 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8945 tmp_set->insert(subset_ct->bool_or().literals().begin(),
8946 subset_ct->bool_or().literals().end());
8948 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8949 subset_ct->exactly_one().literals().end());
8954 for (
const int literal :
8955 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
8956 ? superset_ct->at_most_one().literals()
8957 : superset_ct->exactly_one().literals()) {
8958 if (tmp_set->contains(literal))
continue;
8964 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
8965 ConstraintProto copy = *superset_ct;
8966 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
8967 copy.at_most_one().literals();
8970 *remove_subset =
true;
8974 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8975 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8976 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8977 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
8978 *remove_superset =
true;
8982 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
8983 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8984 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8985 context_->UpdateRuleStats(
"setppc: removed dominated constraints");
8986 *remove_subset =
true;
8992 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
8993 superset_ct->constraint_case() == ConstraintProto::kLinear) {
8995 int64_t min_sum = std::numeric_limits<int64_t>::max();
8996 int64_t max_sum = std::numeric_limits<int64_t>::min();
8997 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8998 subset_ct->exactly_one().literals().end());
9001 int num_matches = 0;
9003 Domain reachable(0);
9004 std::vector<std::pair<int64_t, int>> coeff_counts;
9005 for (
int i = 0;
i < superset_ct->linear().vars().size(); ++
i) {
9006 const int var = superset_ct->linear().vars(
i);
9007 const int64_t coeff = superset_ct->linear().coeffs(
i);
9008 if (tmp_set->contains(var)) {
9010 min_sum = std::min(min_sum, coeff);
9011 max_sum = std::max(max_sum, coeff);
9012 coeff_counts.push_back({superset_ct->linear().coeffs(
i), 1});
9017 context_->DomainOf(var).ContinuousMultiplicationBy(coeff))
9018 .RelaxIfTooComplex();
9019 temp_ct_.mutable_linear()->add_vars(var);
9020 temp_ct_.mutable_linear()->add_coeffs(coeff);
9030 if (num_matches != tmp_set->size())
return true;
9031 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
9032 context_->UpdateRuleStats(
"setppc: exactly_one included in linear");
9034 context_->UpdateRuleStats(
"setppc: at_most_one included in linear");
9037 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
9039 if (reachable.IsIncludedIn(superset_rhs)) {
9041 context_->UpdateRuleStats(
"setppc: removed trivial linear constraint");
9042 *remove_superset =
true;
9045 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
9047 context_->UpdateRuleStats(
"setppc: removed infeasible linear constraint");
9048 *stop_processing_superset =
true;
9049 return MarkConstraintAsFalse(superset_ct);
9054 if (superset_ct->enforcement_literal().empty()) {
9055 CHECK_GT(num_matches, 0);
9058 temp_ct_.mutable_linear());
9059 PropagateDomainsInLinear(-1, &temp_ct_);
9065 std::sort(coeff_counts.begin(), coeff_counts.end());
9067 for (
int i = 0;
i < coeff_counts.size(); ++
i) {
9069 coeff_counts[
i].first == coeff_counts[new_size - 1].first) {
9070 coeff_counts[new_size - 1].second++;
9073 coeff_counts[new_size++] = coeff_counts[
i];
9075 coeff_counts.resize(new_size);
9077 int64_t best_count = 0;
9078 for (
const auto [coeff, count] : coeff_counts) {
9079 if (count > best_count) {
9085 LinearConstraintProto new_ct = superset_ct->linear();
9087 for (
int i = 0;
i < new_ct.vars().size(); ++
i) {
9088 const int var = new_ct.vars(
i);
9089 int64_t coeff = new_ct.coeffs(
i);
9090 if (tmp_set->contains(var)) {
9091 if (coeff == best)
continue;
9094 new_ct.set_vars(new_size, var);
9095 new_ct.set_coeffs(new_size, coeff);
9099 new_ct.mutable_vars()->Truncate(new_size);
9100 new_ct.mutable_coeffs()->Truncate(new_size);
9105 *superset_ct->mutable_linear() = std::move(new_ct);
9106 context_->UpdateConstraintVariableUsage(superset_c);
9107 context_->UpdateRuleStats(
"setppc: reduced linear coefficients");
9122void CpModelPresolver::ProcessSetPPC() {
9123 if (time_limit_->LimitReached())
return;
9124 if (context_->ModelIsUnsat())
return;
9125 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
9126 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9129 CompactVectorVector<int> storage;
9131 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9134 std::vector<int> temp_literals;
9135 const int num_constraints = context_->working_model->constraints_size();
9136 std::vector<int> relevant_constraints;
9137 for (
int c = 0;
c < num_constraints; ++
c) {
9138 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9139 const auto type = ct->constraint_case();
9140 if (type == ConstraintProto::kBoolOr ||
9141 type == ConstraintProto::kAtMostOne ||
9142 type == ConstraintProto::kExactlyOne) {
9147 context_->UpdateConstraintVariableUsage(c);
9149 if (context_->ModelIsUnsat())
return;
9151 temp_literals.clear();
9152 for (
const int ref :
9153 type == ConstraintProto::kAtMostOne ? ct->at_most_one().literals()
9154 : type == ConstraintProto::kBoolOr ? ct->bool_or().literals()
9155 : ct->exactly_one().literals()) {
9156 temp_literals.push_back(
9161 relevant_constraints.push_back(c);
9162 detector.AddPotentialSet(storage.Add(temp_literals));
9163 }
else if (type == ConstraintProto::kLinear) {
9173 const int size = ct->linear().vars().size();
9174 if (size <= 2)
continue;
9179 temp_literals.clear();
9180 for (
int i = 0;
i < size; ++
i) {
9181 const int var = ct->linear().vars(
i);
9182 if (!context_->CanBeUsedAsLiteral(var))
continue;
9184 temp_literals.push_back(
9185 Literal(BooleanVariable(var),
true).
Index().value());
9187 if (temp_literals.size() > 2) {
9189 relevant_constraints.push_back(c);
9190 detector.AddPotentialSuperset(storage.Add(temp_literals));
9195 absl::flat_hash_set<int> tmp_set;
9196 int64_t num_inclusions = 0;
9197 detector.DetectInclusions([&](
int subset,
int superset) {
9199 bool remove_subset =
false;
9200 bool remove_superset =
false;
9201 bool stop_processing_superset =
false;
9202 const int subset_c = relevant_constraints[subset];
9203 const int superset_c = relevant_constraints[superset];
9204 detector.IncreaseWorkDone(storage[subset].size());
9205 detector.IncreaseWorkDone(storage[superset].size());
9206 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
9207 &remove_superset, &stop_processing_superset)) {
9211 if (remove_subset) {
9212 context_->working_model->mutable_constraints(subset_c)->Clear();
9213 context_->UpdateConstraintVariableUsage(subset_c);
9214 detector.StopProcessingCurrentSubset();
9216 if (remove_superset) {
9217 context_->working_model->mutable_constraints(superset_c)->Clear();
9218 context_->UpdateConstraintVariableUsage(superset_c);
9219 detector.StopProcessingCurrentSuperset();
9221 if (stop_processing_superset) {
9222 context_->UpdateConstraintVariableUsage(superset_c);
9223 detector.StopProcessingCurrentSuperset();
9227 timer.AddToWork(detector.work_done() * 1e-9);
9228 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
9229 timer.AddCounter(
"num_inclusions", num_inclusions);
9232void CpModelPresolver::DetectIncludedEnforcement() {
9233 if (time_limit_->LimitReached())
return;
9234 if (context_->ModelIsUnsat())
return;
9235 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
9236 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9239 std::vector<int> relevant_constraints;
9240 CompactVectorVector<int> storage;
9242 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
9244 std::vector<int> temp_literals;
9245 const int num_constraints = context_->working_model->constraints_size();
9246 for (
int c = 0;
c < num_constraints; ++
c) {
9247 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
9248 if (ct->enforcement_literal().size() <= 1)
continue;
9251 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9253 context_->UpdateConstraintVariableUsage(c);
9255 if (context_->ModelIsUnsat())
return;
9259 temp_literals.clear();
9260 for (
const int ref : ct->enforcement_literal()) {
9261 temp_literals.push_back(
9266 relevant_constraints.push_back(c);
9270 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
9271 detector.AddPotentialSet(storage.Add(temp_literals));
9273 detector.AddPotentialSuperset(storage.Add(temp_literals));
9277 int64_t num_inclusions = 0;
9278 detector.DetectInclusions([&](
int subset,
int superset) {
9280 const int subset_c = relevant_constraints[subset];
9281 const int superset_c = relevant_constraints[superset];
9282 ConstraintProto* subset_ct =
9283 context_->working_model->mutable_constraints(subset_c);
9284 ConstraintProto* superset_ct =
9285 context_->working_model->mutable_constraints(superset_c);
9286 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd)
return;
9288 context_->tmp_literal_set.clear();
9289 for (
const int ref : subset_ct->bool_and().literals()) {
9290 context_->tmp_literal_set.insert(ref);
9296 for (
const int ref : superset_ct->enforcement_literal()) {
9297 if (context_->tmp_literal_set.contains(ref)) {
9298 context_->UpdateRuleStats(
"bool_and: filtered enforcement");
9299 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
9300 context_->UpdateRuleStats(
"bool_and: never enforced");
9301 superset_ct->Clear();
9302 context_->UpdateConstraintVariableUsage(superset_c);
9303 detector.StopProcessingCurrentSuperset();
9306 superset_ct->set_enforcement_literal(new_size++, ref);
9309 if (new_size < superset_ct->bool_and().literals().size()) {
9310 context_->UpdateConstraintVariableUsage(superset_c);
9311 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
9315 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
9317 for (
const int ref : superset_ct->bool_and().literals()) {
9318 if (context_->tmp_literal_set.contains(ref)) {
9319 context_->UpdateRuleStats(
"bool_and: filtered literal");
9320 }
else if (context_->tmp_literal_set.contains(
NegatedRef(ref))) {
9321 context_->UpdateRuleStats(
"bool_and: must be false");
9322 if (!MarkConstraintAsFalse(superset_ct))
return;
9323 context_->UpdateConstraintVariableUsage(superset_c);
9324 detector.StopProcessingCurrentSuperset();
9327 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
9330 if (new_size < superset_ct->bool_and().literals().size()) {
9331 context_->UpdateConstraintVariableUsage(superset_c);
9332 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
9336 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
9337 context_->UpdateRuleStats(
"TODO bool_and enforcement in linear enf");
9341 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
9342 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
9343 timer.AddCounter(
"num_inclusions", num_inclusions);
9354bool CpModelPresolver::ProcessEncodingFromLinear(
9355 const int linear_encoding_ct_index,
9356 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
9357 int64_t* num_multiple_terms) {
9359 bool in_exactly_one =
false;
9360 absl::flat_hash_map<int, int> var_to_ref;
9361 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
9362 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
9367 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
9368 ConstraintProto::kExactlyOne);
9369 in_exactly_one =
true;
9370 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
9377 const ConstraintProto& linear_encoding =
9378 context_->working_model->constraints(linear_encoding_ct_index);
9379 int64_t rhs = linear_encoding.linear().domain(0);
9380 int target_ref = std::numeric_limits<int>::min();
9381 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
9382 const int num_terms = linear_encoding.linear().vars().size();
9383 for (
int i = 0;
i < num_terms; ++
i) {
9384 const int ref = linear_encoding.linear().vars(
i);
9385 const int64_t coeff = linear_encoding.linear().coeffs(
i);
9386 const auto it = var_to_ref.find(
PositiveRef(ref));
9388 if (it == var_to_ref.end()) {
9389 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) <<
"Uniqueness";
9390 CHECK_EQ(std::abs(coeff), 1);
9391 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
9397 if (it->second == ref) {
9399 ref_to_coeffs.push_back({ref, coeff});
9403 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
9406 if (target_ref == std::numeric_limits<int>::min() ||
9407 context_->CanBeUsedAsLiteral(target_ref)) {
9411 context_->UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
9416 std::vector<int64_t> all_values;
9417 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
9418 for (
const auto& [ref, coeff] : ref_to_coeffs) {
9419 const int64_t value = rhs - coeff;
9420 all_values.push_back(value);
9421 value_to_refs[value].push_back(ref);
9425 for (
const auto& [var, ref] : var_to_ref) {
9426 all_values.push_back(rhs);
9427 value_to_refs[rhs].push_back(ref);
9429 if (!in_exactly_one) {
9432 all_values.push_back(rhs);
9437 bool domain_reduced =
false;
9438 if (!context_->IntersectDomainWith(target_ref, new_domain, &domain_reduced)) {
9441 if (domain_reduced) {
9442 context_->UpdateRuleStats(
"encoding: reduced target domain");
9445 if (context_->CanBeUsedAsLiteral(target_ref)) {
9447 context_->UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
9452 absl::flat_hash_set<int64_t> value_set;
9453 for (
const int64_t v : context_->DomainOf(target_ref).Values()) {
9454 value_set.insert(v);
9456 for (
auto& [value, literals] : value_to_refs) {
9458 absl::c_sort(literals);
9461 if (!value_set.contains(value)) {
9462 for (
const int lit : literals) {
9463 if (!context_->SetLiteralToFalse(lit))
return false;
9468 if (literals.size() == 1 && (in_exactly_one || value != rhs)) {
9471 ++*num_unique_terms;
9472 if (!context_->InsertVarValueEncoding(literals[0], target_ref, value)) {
9476 ++*num_multiple_terms;
9477 const int associated_lit =
9478 context_->GetOrCreateVarValueEncoding(target_ref, value);
9479 for (
const int lit : literals) {
9480 context_->AddImplication(lit, associated_lit);
9485 if (in_exactly_one || value != rhs) {
9491 context_->working_model->add_constraints()->mutable_bool_or();
9492 for (
const int lit : literals) bool_or->add_literals(lit);
9493 bool_or->add_literals(
NegatedRef(associated_lit));
9499 context_->working_model->mutable_constraints(linear_encoding_ct_index)
9501 context_->UpdateNewConstraintsVariableUsage();
9502 context_->UpdateConstraintVariableUsage(linear_encoding_ct_index);
9520 if (a ==
b)
return true;
9523 return (*
column)[a] == (*column)[
b];
9533 if (time_limit_->LimitReached())
return;
9534 if (context_->ModelIsUnsat())
return;
9535 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
9538 const int num_vars = context_->working_model->variables().size();
9539 const int num_constraints = context_->working_model->constraints().size();
9543 std::vector<int> flat_vars;
9544 std::vector<std::pair<int, int64_t>> flat_terms;
9558 std::vector<bool> appear_in_amo(num_vars,
false);
9559 std::vector<bool> appear_in_bool_constraint(num_vars,
false);
9560 for (
int c = 0; c < num_constraints; ++c) {
9561 const ConstraintProto& ct = context_->working_model->constraints(c);
9562 absl::Span<const int> literals;
9564 bool is_amo =
false;
9565 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
9567 literals = ct.at_most_one().literals();
9568 }
else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
9570 literals = ct.exactly_one().literals();
9571 }
else if (ct.constraint_case() == ConstraintProto::kBoolOr) {
9572 literals = ct.bool_or().literals();
9575 if (!literals.empty()) {
9576 for (
const int lit : literals) {
9579 if (is_amo) appear_in_amo[lit] =
true;
9580 appear_in_bool_constraint[lit] =
true;
9581 flat_vars.push_back(lit);
9582 flat_terms.push_back({c, 1});
9587 if (ct.constraint_case() == ConstraintProto::kLinear) {
9588 const int num_terms = ct.linear().vars().size();
9589 for (
int i = 0;
i < num_terms; ++
i) {
9590 const int var = ct.linear().vars(
i);
9591 const int64_t coeff = ct.linear().coeffs(
i);
9592 flat_vars.push_back(var);
9593 flat_terms.push_back({c, coeff});
9605 if (context_->working_model->has_objective()) {
9606 context_->WriteObjectiveToProto();
9607 const int num_terms = context_->working_model->objective().vars().size();
9608 for (
int i = 0;
i < num_terms; ++
i) {
9609 const int var = context_->working_model->objective().vars(
i);
9610 const int64_t coeff = context_->working_model->objective().coeffs(
i);
9611 flat_vars.push_back(var);
9628 std::vector<int> flat_duplicates;
9629 std::vector<int> flat_representatives;
9630 for (
int var = 0; var < var_to_columns.
size(); ++var) {
9631 const int size_seen = var_to_columns[var].
size();
9632 if (size_seen == 0)
continue;
9633 if (size_seen != context_->VarToConstraints(var).size())
continue;
9639 if (appear_in_bool_constraint[var] && !appear_in_amo[var]) {
9640 context_->UpdateRuleStats(
9641 "TODO duplicate: duplicate columns in Boolean constraints");
9645 const auto [it, inserted] = duplicates.insert({var, var});
9647 flat_duplicates.push_back(var);
9648 flat_representatives.push_back(it->second);
9653 int num_equivalent_classes = 0;
9656 std::vector<std::pair<int, int64_t>> definition;
9657 std::vector<int> var_to_remove;
9658 std::vector<int> var_to_rep(num_vars, -1);
9659 for (
int var = 0; var < rep_to_dups.
size(); ++var) {
9660 if (rep_to_dups[var].empty())
continue;
9670 definition.push_back({var, 1});
9671 Domain domain = context_->DomainOf(var);
9672 for (
const int other_var : rep_to_dups[var]) {
9673 definition.push_back({other_var, 1});
9674 domain = domain.
AdditionWith(context_->DomainOf(other_var));
9678 context_->UpdateRuleStats(
9679 "TODO duplicate: domain of the sum is too complex");
9682 if (appear_in_amo[var]) {
9685 const int new_var = context_->NewIntVarWithDefinition(
9686 domain, definition,
true);
9687 if (new_var == -1) {
9688 context_->UpdateRuleStats(
"TODO duplicate: possible overflow");
9692 var_to_remove.push_back(var);
9693 CHECK_EQ(var_to_rep[var], -1);
9694 var_to_rep[var] = new_var;
9695 for (
const int other_var : rep_to_dups[var]) {
9696 var_to_remove.push_back(other_var);
9697 CHECK_EQ(var_to_rep[other_var], -1);
9698 var_to_rep[other_var] = new_var;
9702 const int64_t obj_coeff = context_->ObjectiveCoeff(var);
9703 if (obj_coeff != 0) {
9704 context_->RemoveVariableFromObjective(var);
9705 for (
const int other_var : rep_to_dups[var]) {
9706 CHECK_EQ(context_->ObjectiveCoeff(other_var), obj_coeff);
9707 context_->RemoveVariableFromObjective(other_var);
9709 context_->AddToObjective(new_var, obj_coeff);
9712 num_equivalent_classes++;
9717 if (!var_to_remove.empty()) {
9718 absl::flat_hash_set<int> seen;
9719 std::vector<std::pair<int, int64_t>> new_terms;
9720 for (
int c = 0; c < num_constraints; ++c) {
9721 ConstraintProto* mutable_ct =
9722 context_->working_model->mutable_constraints(c);
9729 BoolArgumentProto* mutable_arg =
nullptr;
9730 if (mutable_ct->constraint_case() == ConstraintProto::kAtMostOne) {
9731 mutable_arg = mutable_ct->mutable_at_most_one();
9732 }
else if (mutable_ct->constraint_case() ==
9733 ConstraintProto::kExactlyOne) {
9734 mutable_arg = mutable_ct->mutable_exactly_one();
9735 }
else if (mutable_ct->constraint_case() == ConstraintProto::kBoolOr) {
9736 mutable_arg = mutable_ct->mutable_bool_or();
9738 if (mutable_arg !=
nullptr) {
9740 const int num_terms = mutable_arg->literals().size();
9741 for (
int i = 0;
i < num_terms; ++
i) {
9742 const int lit = mutable_arg->literals(
i);
9746 const auto [_, inserted] = seen.insert(rep);
9747 if (inserted) new_terms.push_back({rep, 1});
9750 mutable_arg->set_literals(new_size, lit);
9753 if (new_size == num_terms)
continue;
9756 mutable_arg->mutable_literals()->Truncate(new_size);
9757 for (
const auto [var, coeff] : new_terms) {
9758 mutable_arg->add_literals(var);
9760 context_->UpdateConstraintVariableUsage(c);
9765 if (mutable_ct->constraint_case() == ConstraintProto::kLinear) {
9767 LinearConstraintProto* mutable_linear = mutable_ct->mutable_linear();
9768 const int num_terms = mutable_linear->vars().size();
9769 for (
int i = 0;
i < num_terms; ++
i) {
9770 const int var = mutable_linear->vars(
i);
9771 const int64_t coeff = mutable_linear->coeffs(
i);
9772 const int rep = var_to_rep[var];
9774 const auto [_, inserted] = seen.insert(rep);
9775 if (inserted) new_terms.push_back({rep, coeff});
9778 mutable_linear->set_vars(new_size, var);
9779 mutable_linear->set_coeffs(new_size, coeff);
9782 if (new_size == num_terms)
continue;
9784 mutable_linear->mutable_vars()->Truncate(new_size);
9785 mutable_linear->mutable_coeffs()->Truncate(new_size);
9786 for (
const auto [var, coeff] : new_terms) {
9787 mutable_linear->add_vars(var);
9788 mutable_linear->add_coeffs(coeff);
9790 context_->UpdateConstraintVariableUsage(c);
9799 const int num_var_reduction = var_to_remove.size() - num_equivalent_classes;
9800 for (
const int var : var_to_remove) {
9801 CHECK(context_->VarToConstraints(var).empty());
9802 context_->MarkVariableAsRemoved(var);
9804 if (num_var_reduction > 0) {
9805 context_->UpdateRuleStats(
"duplicate: removed duplicated column",
9809 timer.
AddCounter(
"num_equiv_classes", num_equivalent_classes);
9810 timer.
AddCounter(
"num_removed_vars", num_var_reduction);
9813void CpModelPresolver::DetectDuplicateConstraints() {
9829 const std::vector<std::pair<int, int>> duplicates =
9831 timer.AddCounter(
"duplicates", duplicates.size());
9832 for (
const auto& [dup, rep] : duplicates) {
9839 : context_->
working_model->constraints(rep).constraint_case();
9843 if (type == ConstraintProto::kLinear) {
9848 if (rep_domain != d) {
9849 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
9852 if (!MarkConstraintAsFalse(
9854 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
9865 ->mutable_linear());
9870 context_->UpdateRuleStats(
9871 "duplicate: linear constraint parallel to objective");
9872 const Domain objective_domain =
9875 context_->working_model->constraints(dup).linear());
9876 if (objective_domain != d) {
9877 context_->UpdateRuleStats(
"duplicate: updated objective domain");
9878 const Domain new_domain = objective_domain.IntersectionWith(d);
9879 if (new_domain.IsEmpty()) {
9880 return (
void)context_->NotifyThatModelIsUnsat(
9881 "Constraint parallel to the objective makes the objective domain "
9885 context_->working_model->mutable_objective());
9888 context_->ReadObjectiveFromProto();
9891 context_->working_model->mutable_constraints(dup)->Clear();
9892 context_->UpdateConstraintVariableUsage(dup);
9893 context_->UpdateRuleStats(
"duplicate: removed constraint");
9897void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
9900 if (time_limit_->LimitReached())
return;
9901 if (context_->ModelIsUnsat())
return;
9902 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9905 if (context_->working_model->has_objective()) {
9906 if (!context_->CanonicalizeObjective())
return;
9907 context_->WriteObjectiveToProto();
9910 absl::flat_hash_set<Literal> enforcement_vars;
9911 std::vector<std::pair<Literal, Literal>> implications_used;
9916 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
9918 timer.AddCounter(
"without_enforcements",
9919 duplicates_without_enforcement.size());
9920 for (
const auto& [dup, rep] : duplicates_without_enforcement) {
9921 auto* dup_ct = context_->working_model->mutable_constraints(dup);
9922 auto* rep_ct = context_->working_model->mutable_constraints(rep);
9926 if (PresolveEnforcementLiteral(dup_ct)) {
9927 context_->UpdateConstraintVariableUsage(dup);
9929 if (PresolveEnforcementLiteral(rep_ct)) {
9930 context_->UpdateConstraintVariableUsage(rep);
9934 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET ||
9935 dup_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
9941 if (dup_ct->enforcement_literal().empty() ||
9942 rep_ct->enforcement_literal().empty()) {
9943 context_->UpdateRuleStats(
"duplicate: removed enforced constraint");
9944 rep_ct->mutable_enforcement_literal()->Clear();
9945 context_->UpdateConstraintVariableUsage(rep);
9947 context_->UpdateConstraintVariableUsage(dup);
9951 const int a = rep_ct->enforcement_literal(0);
9952 const int b = dup_ct->enforcement_literal(0);
9954 if (a ==
NegatedRef(
b) && rep_ct->enforcement_literal().size() == 1 &&
9955 dup_ct->enforcement_literal().size() == 1) {
9956 context_->UpdateRuleStats(
9957 "duplicate: both with enforcement and its negation");
9958 rep_ct->mutable_enforcement_literal()->Clear();
9959 context_->UpdateConstraintVariableUsage(rep);
9961 context_->UpdateConstraintVariableUsage(dup);
9978 if (context_->VariableWithCostIsUniqueAndRemovable(a) &&
9979 context_->VariableWithCostIsUniqueAndRemovable(
b)) {
9985 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement.");
9986 if (!context_->SetLiteralToFalse(a))
return;
9990 context_->UpdateRuleStats(
"duplicate: dual fixing enforcement.");
9991 if (!context_->SetLiteralToFalse(
b))
return;
10005 if (rep_ct->enforcement_literal().size() > 1 ||
10006 dup_ct->enforcement_literal().size() > 1) {
10007 context_->UpdateRuleStats(
10008 "TODO duplicate: identical constraint with unique enforcement "
10015 context_->UpdateRuleStats(
"duplicate: dual equivalence of enforcement");
10021 solution_crush_.UpdateLiteralsToFalseIfDifferent(
NegatedRef(a),
10023 if (!context_->StoreBooleanEqualityRelation(a,
b))
return;
10027 if (dup_ct->enforcement_literal().size() == 1 &&
10028 rep_ct->enforcement_literal().size() == 1) {
10030 context_->UpdateConstraintVariableUsage(dup);
10036 if (implication_graph !=
nullptr && mapping !=
nullptr &&
10037 trail !=
nullptr) {
10038 for (
int i = 0;
i < 2;
i++) {
10042 const int c_a =
i == 0 ? dup : rep;
10043 const int c_b =
i == 0 ? rep : dup;
10044 const auto& ct_a = context_->working_model->constraints(c_a);
10045 const auto& ct_b = context_->working_model->constraints(c_b);
10047 enforcement_vars.clear();
10048 implications_used.clear();
10049 for (
const int proto_lit : ct_b.enforcement_literal()) {
10050 const Literal lit = mapping->Literal(proto_lit);
10051 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10052 enforcement_vars.insert(lit);
10054 for (
const int proto_lit : ct_a.enforcement_literal()) {
10055 const Literal lit = mapping->Literal(proto_lit);
10056 DCHECK(!trail->Assignment().LiteralIsAssigned(lit));
10057 for (
const Literal implication_lit :
10058 implication_graph->DirectImplications(lit)) {
10059 auto extracted = enforcement_vars.extract(implication_lit);
10060 if (!extracted.empty() && lit != implication_lit) {
10061 implications_used.push_back({lit, implication_lit});
10065 if (enforcement_vars.empty()) {
10093 if (ct_a.constraint_case() == ConstraintProto::kLinear &&
10094 ct_a.linear().vars().size() == 1 &&
10095 ct_a.enforcement_literal().size() == 1) {
10096 const int var = ct_a.linear().vars(0);
10097 const Domain var_domain = context_->DomainOf(var);
10104 if (rhs.IsEmpty()) {
10105 context_->UpdateRuleStats(
"duplicate: linear1 infeasible");
10106 if (!MarkConstraintAsFalse(rep_ct))
return;
10107 if (!MarkConstraintAsFalse(dup_ct))
return;
10108 context_->UpdateConstraintVariableUsage(rep);
10109 context_->UpdateConstraintVariableUsage(dup);
10112 if (rhs == var_domain) {
10113 context_->UpdateRuleStats(
"duplicate: linear1 always true");
10116 context_->UpdateConstraintVariableUsage(rep);
10117 context_->UpdateConstraintVariableUsage(dup);
10122 if (rhs.IsFixed() ||
10123 rhs.Complement().IntersectionWith(var_domain).IsFixed()) {
10124 context_->UpdateRuleStats(
10125 "TODO duplicate: skipped identical encoding constraints");
10131 context_->UpdateRuleStats(
10132 "duplicate: identical constraint with implied enforcements");
10136 rep_ct->Swap(dup_ct);
10137 context_->UpdateConstraintVariableUsage(rep);
10140 context_->UpdateConstraintVariableUsage(dup);
10144 for (
const auto& [a,
b] : implications_used) {
10145 const int proto_lit_a = mapping->GetProtoLiteralFromLiteral(a);
10146 const int proto_lit_b = mapping->GetProtoLiteralFromLiteral(
b);
10147 context_->AddImplication(proto_lit_a, proto_lit_b);
10149 context_->UpdateNewConstraintsVariableUsage();
10158 if (time_limit_->LimitReached())
return;
10159 if (context_->ModelIsUnsat())
return;
10164 std::vector<std::pair<int, int>> different_vars;
10165 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
10168 const auto process_difference = [&different_vars, &offsets](
int v1,
int v2,
10170 Domain exclusion = d.Complement().PartAroundZero();
10171 if (exclusion.
IsEmpty())
return;
10172 if (v1 == v2)
return;
10173 std::pair<int, int> key = {v1, v2};
10175 std::swap(key.first, key.second);
10182 different_vars.push_back(key);
10183 offsets[key] = {exclusion.
Min() == std::numeric_limits<int64_t>::min()
10184 ? std::numeric_limits<int64_t>::max()
10207 bool has_all_diff =
false;
10208 bool has_no_overlap =
false;
10209 std::vector<std::pair<uint64_t, int>> hashes;
10210 const int num_constraints = context_->working_model->constraints_size();
10211 for (
int c = 0; c < num_constraints; ++c) {
10212 const ConstraintProto& ct = context_->working_model->constraints(c);
10213 if (ct.constraint_case() == ConstraintProto::kAllDiff) {
10214 has_all_diff =
true;
10217 if (ct.constraint_case() == ConstraintProto::kNoOverlap) {
10218 has_no_overlap =
true;
10221 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
10222 if (ct.linear().vars().size() == 1)
continue;
10226 if (ct.linear().vars().size() == 2 && ct.enforcement_literal().empty() &&
10227 ct.linear().coeffs(0) == -ct.linear().coeffs(1)) {
10229 if (ct.linear().coeffs(0) == 1) {
10230 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10232 }
else if (ct.linear().coeffs(0) == -1) {
10233 process_difference(ct.linear().vars(0), ct.linear().vars(1),
10239 if (ct.enforcement_literal().size() > 1)
continue;
10244 hashes.push_back({hash, c});
10246 std::sort(hashes.begin(), hashes.end());
10247 for (
int next, start = 0; start < hashes.size(); start = next) {
10249 while (next < hashes.size() && hashes[next].first == hashes[start].first) {
10252 absl::Span<const std::pair<uint64_t, int>> range(&hashes[start],
10254 if (range.size() <= 1)
continue;
10255 if (range.size() > 10)
continue;
10257 for (
int i = 0;
i < range.size(); ++
i) {
10258 const ConstraintProto& ct1 =
10259 context_->working_model->constraints(range[
i].second);
10260 const int num_terms = ct1.linear().vars().size();
10261 for (
int j =
i + 1; j < range.size(); ++j) {
10262 const ConstraintProto& ct2 =
10263 context_->working_model->constraints(range[j].second);
10264 if (ct2.linear().vars().size() != num_terms)
continue;
10270 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
10271 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
10274 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
10275 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
10279 if (ct1.enforcement_literal().empty() &&
10280 ct2.enforcement_literal().empty()) {
10281 (void)context_->NotifyThatModelIsUnsat(
10282 "two incompatible linear constraint");
10285 if (ct1.enforcement_literal().empty()) {
10286 context_->UpdateRuleStats(
10287 "incompatible linear: set enforcement to false");
10288 if (!context_->SetLiteralToFalse(ct2.enforcement_literal(0))) {
10293 if (ct2.enforcement_literal().empty()) {
10294 context_->UpdateRuleStats(
10295 "incompatible linear: set enforcement to false");
10296 if (!context_->SetLiteralToFalse(ct1.enforcement_literal(0))) {
10302 const int lit1 = ct1.enforcement_literal(0);
10303 const int lit2 = ct2.enforcement_literal(0);
10306 if (ct1.linear().vars().size() == 2 &&
10307 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
10310 Domain union_of_domain =
10315 if (ct1.linear().coeffs(0) == 1) {
10316 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10317 std::move(union_of_domain));
10318 }
else if (ct1.linear().coeffs(0) == -1) {
10319 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
10325 context_->UpdateRuleStats(
"incompatible linear: add implication");
10326 context_->AddImplication(lit1,
NegatedRef(lit2));
10347 if (context_->params().infer_all_diffs() && !has_all_diff &&
10348 !has_no_overlap && different_vars.size() > 2) {
10350 local_time.
Start();
10352 std::vector<std::vector<Literal>> cliques;
10353 absl::flat_hash_set<int> used_var;
10356 const int num_variables = context_->working_model->
variables().size();
10359 graph->
Resize(num_variables);
10360 for (
const auto [var1, var2] : different_vars) {
10363 if (var1 == var2) {
10364 (void)context_->NotifyThatModelIsUnsat(
"x != y with x == y");
10369 CHECK(graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
10370 Literal(BooleanVariable(var2), true)}));
10371 if (!used_var.contains(var1)) {
10372 used_var.insert(var1);
10373 cliques.push_back({
Literal(BooleanVariable(var1),
true),
10374 Literal(BooleanVariable(var2),
true)});
10376 if (!used_var.contains(var2)) {
10377 used_var.insert(var2);
10378 cliques.push_back({
Literal(BooleanVariable(var1),
true),
10379 Literal(BooleanVariable(var2),
true)});
10382 CHECK(graph->DetectEquivalences());
10383 graph->TransformIntoMaxCliques(&cliques, 1e8);
10385 int num_cliques = 0;
10386 int64_t cumulative_size = 0;
10387 for (std::vector<Literal>& clique : cliques) {
10388 if (clique.size() <= 2)
continue;
10391 cumulative_size += clique.size();
10392 std::sort(clique.begin(), clique.end());
10398 const int num_terms = clique.size();
10399 std::vector<int64_t> sizes(num_terms,
10400 std::numeric_limits<int64_t>::max());
10401 for (
int i = 0;
i < num_terms; ++
i) {
10402 const int v1 = clique[
i].Variable().value();
10403 for (
int j =
i + 1; j < num_terms; ++j) {
10404 const int v2 = clique[j].Variable().value();
10405 const auto [o1, o2] = offsets.at({v1, v2});
10406 sizes[
i] = std::min(sizes[
i], o1);
10407 sizes[j] = std::min(sizes[j], o2);
10411 int num_greater_than_one = 0;
10413 for (
int i = 0;
i < num_terms; ++
i) {
10414 CHECK_GE(sizes[
i], 1);
10415 if (sizes[
i] > 1) ++num_greater_than_one;
10420 issue =
CapAdd(issue, sizes[
i]);
10421 if (issue == std::numeric_limits<int64_t>::max()) {
10422 context_->UpdateRuleStats(
"TODO no_overlap: with task always last");
10423 num_greater_than_one = 0;
10428 if (num_greater_than_one > 0) {
10434 context_->UpdateRuleStats(
10435 "no_overlap: inferred from x != y constraints");
10437 std::vector<int> intervals;
10438 for (
int i = 0;
i < num_terms; ++
i) {
10439 intervals.push_back(context_->working_model->constraints().size());
10440 auto* new_interval =
10441 context_->working_model->add_constraints()->mutable_interval();
10442 new_interval->mutable_start()->set_offset(0);
10443 new_interval->mutable_start()->add_coeffs(1);
10444 new_interval->mutable_start()->add_vars(clique[
i].
Variable().value());
10446 new_interval->mutable_size()->set_offset(sizes[
i]);
10448 new_interval->mutable_end()->set_offset(sizes[
i]);
10449 new_interval->mutable_end()->add_coeffs(1);
10450 new_interval->mutable_end()->add_vars(clique[
i].
Variable().value());
10453 context_->working_model->add_constraints()->mutable_no_overlap();
10454 for (
const int interval : intervals) {
10455 new_ct->add_intervals(interval);
10458 context_->UpdateRuleStats(
"all_diff: inferred from x != y constraints");
10460 context_->working_model->add_constraints()->mutable_all_diff();
10461 for (
const Literal l : clique) {
10462 auto* expr = new_ct->add_exprs();
10463 expr->add_vars(l.Variable().value());
10464 expr->add_coeffs(1);
10469 timer.
AddCounter(
"different", different_vars.size());
10474 context_->UpdateNewConstraintsVariableUsage();
10480void Substitute(int64_t factor,
10481 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
10483 LinearConstraintProto* mutable_linear) {
10485 const int old_size = mutable_linear->vars().size();
10486 for (
int i = 0;
i < old_size; ++
i) {
10487 const int var = mutable_linear->vars(
i);
10488 int64_t coeff = mutable_linear->coeffs(
i);
10489 const auto it = subset_coeff_map.find(var);
10490 if (it != subset_coeff_map.end()) {
10491 coeff += factor * it->second;
10492 if (coeff == 0)
continue;
10495 mutable_linear->set_vars(new_size, var);
10496 mutable_linear->set_coeffs(new_size, coeff);
10499 mutable_linear->mutable_vars()->Truncate(new_size);
10500 mutable_linear->mutable_coeffs()->Truncate(new_size);
10508void CpModelPresolver::DetectDominatedLinearConstraints() {
10509 if (time_limit_->LimitReached())
return;
10510 if (context_->ModelIsUnsat())
return;
10511 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
10512 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10520 explicit Storage(CpModelProto* proto) : proto_(*proto) {}
10521 int size()
const {
return static_cast<int>(proto_.constraints().size()); }
10522 absl::Span<const int> operator[](
int c)
const {
10523 return absl::MakeSpan(proto_.constraints(c).linear().vars());
10527 const CpModelProto& proto_;
10529 Storage storage(context_->working_model);
10531 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
10535 std::vector<int> constraint_indices_to_clean;
10539 absl::flat_hash_map<int, Domain> cached_expr_domain;
10541 const int num_constraints = context_->working_model->constraints().size();
10542 for (
int c = 0;
c < num_constraints; ++
c) {
10543 const ConstraintProto& ct = context_->working_model->constraints(c);
10544 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
10548 if (!ct.enforcement_literal().empty()) {
10549 if (ct.linear().vars().size() < 3)
continue;
10552 if (!LinearConstraintIsClean(ct.linear())) {
10559 detector.AddPotentialSet(c);
10561 const auto [min_activity, max_activity] =
10562 context_->ComputeMinMaxActivity(ct.linear());
10563 cached_expr_domain[
c] = Domain(min_activity, max_activity);
10566 int64_t num_inclusions = 0;
10567 absl::flat_hash_map<int, int64_t> coeff_map;
10568 detector.DetectInclusions([&](
int subset_c,
int superset_c) {
10572 const ConstraintProto& subset_ct =
10573 context_->working_model->constraints(subset_c);
10574 const LinearConstraintProto& subset_lin = subset_ct.linear();
10576 detector.IncreaseWorkDone(subset_lin.vars().size());
10577 for (
int i = 0;
i < subset_lin.vars().size(); ++
i) {
10578 coeff_map[subset_lin.vars(
i)] = subset_lin.coeffs(
i);
10584 bool perfect_match =
true;
10587 int64_t factor = 0;
10588 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
10589 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
10595 const ConstraintProto& superset_ct =
10596 context_->working_model->constraints(superset_c);
10597 const LinearConstraintProto& superset_lin = superset_ct.linear();
10598 int64_t diff_min_activity = 0;
10599 int64_t diff_max_activity = 0;
10600 detector.IncreaseWorkDone(superset_lin.vars().size());
10601 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10602 const int var = superset_lin.vars(
i);
10603 int64_t coeff = superset_lin.coeffs(
i);
10604 const auto it = coeff_map.find(var);
10606 if (it != coeff_map.end()) {
10607 const int64_t subset_coeff = it->second;
10609 const int64_t div = coeff / subset_coeff;
10611 min_pos_factor = std::min(div, min_pos_factor);
10613 max_neg_factor = std::max(div, max_neg_factor);
10616 if (perfect_match) {
10617 if (coeff % subset_coeff == 0) {
10621 }
else if (factor != div) {
10622 perfect_match =
false;
10625 perfect_match =
false;
10630 coeff -= subset_coeff;
10632 if (coeff == 0)
continue;
10633 context_->CappedUpdateMinMaxActivity(var, coeff, &diff_min_activity,
10634 &diff_max_activity);
10637 const Domain diff_domain(diff_min_activity, diff_max_activity);
10648 if (subset_ct.enforcement_literal().empty()) {
10649 const Domain implied_superset_domain =
10650 subset_rhs.AdditionWith(diff_domain)
10651 .IntersectionWith(cached_expr_domain[superset_c]);
10652 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
10653 context_->UpdateRuleStats(
10654 "linear inclusion: redundant containing constraint");
10655 context_->working_model->mutable_constraints(superset_c)->Clear();
10656 constraint_indices_to_clean.push_back(superset_c);
10657 detector.StopProcessingCurrentSuperset();
10663 if (superset_ct.enforcement_literal().empty()) {
10664 const Domain implied_subset_domain =
10665 superset_rhs.AdditionWith(diff_domain.Negation())
10666 .IntersectionWith(cached_expr_domain[subset_c]);
10667 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
10668 context_->UpdateRuleStats(
10669 "linear inclusion: redundant included constraint");
10670 context_->working_model->mutable_constraints(subset_c)->Clear();
10671 constraint_indices_to_clean.push_back(subset_c);
10672 detector.StopProcessingCurrentSubset();
10681 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().
empty()) {
10682 const int64_t best_factor =
10683 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
10691 bool is_tigher =
true;
10692 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
10693 max_neg_factor != std::numeric_limits<int64_t>::min()) {
10694 int64_t min_before = 0;
10695 int64_t max_before = 0;
10696 int64_t min_after =
CapProd(best_factor, subset_rhs.FixedValue());
10697 int64_t max_after = min_after;
10698 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10699 const int var = superset_lin.vars(
i);
10700 const auto it = coeff_map.find(var);
10701 if (it == coeff_map.end())
continue;
10703 const int64_t coeff_before = superset_lin.coeffs(
i);
10704 const int64_t coeff_after = coeff_before - best_factor * it->second;
10705 context_->CappedUpdateMinMaxActivity(var, coeff_before, &min_before,
10707 context_->CappedUpdateMinMaxActivity(var, coeff_after, &min_after,
10710 is_tigher = min_after >= min_before && max_after <= max_before;
10713 context_->UpdateRuleStats(
"linear inclusion: sparsify superset");
10714 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
10715 context_->working_model->mutable_constraints(superset_c)
10716 ->mutable_linear());
10717 constraint_indices_to_clean.push_back(superset_c);
10718 detector.StopProcessingCurrentSuperset();
10725 if (perfect_match && subset_ct.enforcement_literal().empty() &&
10726 superset_ct.enforcement_literal().empty()) {
10727 CHECK_NE(factor, 0);
10733 auto* mutable_linear = temp_ct_.mutable_linear();
10734 for (
int i = 0;
i < superset_lin.vars().size(); ++
i) {
10735 const int var = superset_lin.vars(
i);
10736 const int64_t coeff = superset_lin.coeffs(
i);
10737 const auto it = coeff_map.find(var);
10738 if (it != coeff_map.end())
continue;
10739 mutable_linear->add_vars(var);
10740 mutable_linear->add_coeffs(coeff);
10743 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
10745 PropagateDomainsInLinear(-1, &temp_ct_);
10746 if (context_->ModelIsUnsat()) detector.Stop();
10748 if (superset_rhs.IsFixed()) {
10749 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
10752 context_->UpdateRuleStats(
10753 "linear inclusion: subset + singleton is equality");
10754 context_->working_model->mutable_constraints(subset_c)->Clear();
10755 constraint_indices_to_clean.push_back(subset_c);
10756 detector.StopProcessingCurrentSubset();
10761 context_->UpdateRuleStats(
10762 "TODO linear inclusion: superset is equality");
10767 for (
const int c : constraint_indices_to_clean) {
10768 context_->UpdateConstraintVariableUsage(c);
10771 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
10772 timer.AddCounter(
"relevant_constraints", detector.num_potential_supersets());
10773 timer.AddCounter(
"num_inclusions", num_inclusions);
10774 timer.AddCounter(
"num_redundant", constraint_indices_to_clean.size());
10779bool CpModelPresolver::RemoveCommonPart(
10780 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
10781 absl::Span<
const std::pair<int, int64_t>> block,
10785 int64_t offset = 0;
10790 int definiting_equation = -1;
10791 for (
const auto [c, multiple] : block) {
10792 const ConstraintProto& ct = context_->working_model->constraints(c);
10793 if (std::abs(multiple) != 1)
continue;
10794 if (!IsLinearEqualityConstraint(ct))
continue;
10795 if (ct.linear().vars().size() != common_var_coeff_map.size() + 1)
continue;
10797 context_->UpdateRuleStats(
10798 "linear matrix: defining equation for common rectangle");
10799 definiting_equation =
c;
10803 const int num_terms = ct.linear().vars().size();
10804 for (
int k = 0; k < num_terms; ++k) {
10805 if (common_var_coeff_map.contains(ct.linear().vars(k)))
continue;
10806 new_var = ct.linear().vars(k);
10807 coeff = ct.linear().coeffs(k);
10810 CHECK_NE(coeff, 0);
10814 gcd = -multiple * coeff;
10815 offset = multiple * ct.linear().domain(0);
10820 if (definiting_equation == -1) {
10822 int64_t min_activity = 0;
10823 int64_t max_activity = 0;
10824 tmp_terms_.clear();
10825 std::vector<std::pair<int, int64_t>> common_part;
10826 for (
const auto [var, coeff] : common_var_coeff_map) {
10827 common_part.push_back({var, coeff});
10828 gcd = std::gcd(gcd, std::abs(coeff));
10829 if (context_->CanBeUsedAsLiteral(var) && !context_->IsFixed(var)) {
10830 tmp_terms_.push_back({var, coeff});
10834 min_activity += coeff * context_->MinOf(var);
10835 max_activity += coeff * context_->MaxOf(var);
10837 min_activity += coeff * context_->MaxOf(var);
10838 max_activity += coeff * context_->MinOf(var);
10846 if (!tmp_terms_.empty()) {
10847 min_activity += helper->ComputeMinActivity(tmp_terms_);
10848 max_activity += helper->ComputeMaxActivity(tmp_terms_);
10852 min_activity /= gcd;
10853 max_activity /= gcd;
10854 for (
int i = 0;
i < common_part.size(); ++
i) {
10855 common_part[
i].second /= gcd;
10860 std::sort(common_part.begin(), common_part.end());
10861 new_var = context_->NewIntVarWithDefinition(
10862 Domain(min_activity, max_activity), common_part);
10863 if (new_var == -1)
return false;
10867 for (
const auto [c, multiple] : block) {
10868 if (c == definiting_equation)
continue;
10870 auto* mutable_linear =
10871 context_->working_model->mutable_constraints(c)->mutable_linear();
10872 const int num_terms = mutable_linear->vars().size();
10874 bool new_var_already_seen =
false;
10875 for (
int k = 0; k < num_terms; ++k) {
10876 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
10877 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
10878 mutable_linear->coeffs(k));
10883 int64_t new_coeff = mutable_linear->coeffs(k);
10884 if (mutable_linear->vars(k) == new_var) {
10885 new_var_already_seen =
true;
10886 new_coeff += gcd * multiple;
10887 if (new_coeff == 0)
continue;
10890 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
10891 mutable_linear->set_coeffs(new_size, new_coeff);
10894 mutable_linear->mutable_vars()->Truncate(new_size);
10895 mutable_linear->mutable_coeffs()->Truncate(new_size);
10896 if (!new_var_already_seen) {
10897 mutable_linear->add_vars(new_var);
10898 mutable_linear->add_coeffs(gcd * multiple);
10902 .AdditionWith(Domain(-offset * multiple)),
10905 context_->UpdateConstraintVariableUsage(c);
10912int64_t FindVarCoeff(
int var,
const ConstraintProto& ct) {
10913 const int num_terms = ct.linear().vars().size();
10914 for (
int k = 0; k < num_terms; ++k) {
10915 if (ct.linear().vars(k) == var)
return ct.linear().coeffs(k);
10920int64_t ComputeNonZeroReduction(
size_t block_size,
size_t common_part_size) {
10923 return static_cast<int64_t
>(block_size * (common_part_size - 1) -
10924 common_part_size - 1);
10932void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
10934 if (time_limit_->LimitReached())
return;
10935 if (context_->ModelIsUnsat())
return;
10936 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
10937 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10939 int64_t num_blocks = 0;
10940 int64_t nz_reduction = 0;
10941 std::vector<int> amo_cts;
10942 std::vector<int> amo_literals;
10944 std::vector<int> common_part;
10945 std::vector<int> best_common_part;
10947 std::vector<bool> common_part_sign;
10948 std::vector<bool> best_common_part_sign;
10951 absl::flat_hash_map<int, bool> var_in_amo;
10953 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
10955 if (time_limit_->LimitReached())
break;
10956 if (timer.WorkLimitIsReached())
break;
10957 if (helper->NumAmoForVariable(x) == 0)
continue;
10960 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
10961 for (
const int c : context_->VarToConstraints(x)) {
10962 if (c < 0)
continue;
10963 const ConstraintProto& ct = context_->working_model->constraints(c);
10964 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
10965 amo_cts.push_back(c);
10966 }
else if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
10967 amo_cts.push_back(c);
10970 if (amo_cts.empty())
continue;
10979 var_in_amo.clear();
10980 amo_literals.clear();
10981 common_part.clear();
10982 common_part_sign.clear();
10986 std::sort(amo_cts.begin(), amo_cts.end());
10987 const int random_c =
10988 absl::Uniform<int>(*context_->random(), 0, amo_cts.size());
10989 base_ct_index = amo_cts[random_c];
10990 const ConstraintProto& ct =
10991 context_->working_model->constraints(base_ct_index);
10992 const auto& literals = ct.constraint_case() == ConstraintProto::kAtMostOne
10993 ? ct.at_most_one().literals()
10994 : ct.exactly_one().literals();
10995 timer.TrackSimpleLoop(5 * literals.size());
10996 for (
const int literal : literals) {
10997 amo_literals.push_back(literal);
11000 const auto [_, inserted] =
11006 const int64_t x_multiplier = var_in_amo.at(x) ? 1 : -1;
11010 std::vector<int> block_cts;
11011 std::vector<int> linear_cts;
11012 int max_common_part = 0;
11013 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11014 for (
const int c : context_->VarToConstraints(x)) {
11015 if (c < 0)
continue;
11016 const ConstraintProto& ct = context_->working_model->constraints(c);
11017 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11018 const int num_terms = ct.linear().vars().size();
11019 if (num_terms < 2)
continue;
11021 timer.TrackSimpleLoop(2 * num_terms);
11022 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11023 if (x_coeff == 0)
continue;
11025 int num_in_amo = 0;
11026 for (
int k = 0; k < num_terms; ++k) {
11027 const int var = ct.linear().vars(k);
11032 const auto it = var_in_amo.find(var);
11033 if (it == var_in_amo.end())
continue;
11034 int64_t coeff = ct.linear().coeffs(k);
11035 if (!it->second) coeff = -coeff;
11036 if (coeff != x_coeff)
continue;
11039 if (num_in_amo < 2)
continue;
11041 max_common_part += num_in_amo;
11042 if (num_in_amo == common_part.size()) {
11044 block_cts.push_back(c);
11046 linear_cts.push_back(c);
11049 if (linear_cts.empty() && block_cts.empty())
continue;
11050 if (max_common_part < 100)
continue;
11054 best_common_part = common_part;
11055 best_common_part_sign = common_part_sign;
11056 int best_block_size = block_cts.size();
11057 int best_saved_nz =
11058 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11061 std::sort(block_cts.begin(), block_cts.end());
11062 std::sort(linear_cts.begin(), linear_cts.end());
11066 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11067 for (
const int c : linear_cts) {
11068 const ConstraintProto& ct = context_->working_model->constraints(c);
11069 const int num_terms = ct.linear().vars().size();
11070 timer.TrackSimpleLoop(2 * num_terms);
11071 const int64_t x_coeff = x_multiplier * FindVarCoeff(x, ct);
11072 CHECK_NE(x_coeff, 0);
11074 common_part.clear();
11075 common_part_sign.clear();
11076 for (
int k = 0; k < num_terms; ++k) {
11077 const int var = ct.linear().vars(k);
11078 const auto it = var_in_amo.find(var);
11079 if (it == var_in_amo.end())
continue;
11080 int64_t coeff = ct.linear().coeffs(k);
11081 if (!it->second) coeff = -coeff;
11082 if (coeff != x_coeff)
continue;
11083 common_part.push_back(var);
11084 common_part_sign.push_back(it->second);
11086 if (common_part.size() < 2)
continue;
11089 block_cts.push_back(c);
11090 if (common_part.size() < var_in_amo.size()) {
11091 var_in_amo.clear();
11092 for (
int i = 0;
i < common_part.size(); ++
i) {
11093 var_in_amo[common_part[
i]] = common_part_sign[
i];
11100 const int64_t saved_nz =
11101 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
11102 if (saved_nz > best_saved_nz) {
11103 best_block_size = block_cts.size();
11104 best_saved_nz = saved_nz;
11105 best_common_part = common_part;
11106 best_common_part_sign = common_part_sign;
11109 if (best_saved_nz < 100)
continue;
11114 block_cts.resize(best_block_size);
11115 var_in_amo.clear();
11116 for (
int i = 0;
i < best_common_part.size(); ++
i) {
11117 var_in_amo[best_common_part[
i]] = best_common_part_sign[
i];
11121 nz_reduction += best_saved_nz;
11122 context_->UpdateRuleStats(
"linear matrix: common amo rectangle");
11126 for (
const int lit : amo_literals) {
11127 if (!var_in_amo.contains(
PositiveRef(lit)))
continue;
11128 amo_literals[new_size++] = lit;
11130 if (new_size == amo_literals.size()) {
11131 const ConstraintProto& ct =
11132 context_->working_model->constraints(base_ct_index);
11133 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11134 context_->UpdateRuleStats(
"TODO linear matrix: constant rectangle!");
11136 context_->UpdateRuleStats(
11137 "TODO linear matrix: reuse defining constraint");
11139 }
else if (new_size + 1 == amo_literals.size()) {
11140 const ConstraintProto& ct =
11141 context_->working_model->constraints(base_ct_index);
11142 if (ct.constraint_case() == ConstraintProto::kExactlyOne) {
11143 context_->UpdateRuleStats(
"TODO linear matrix: reuse exo constraint");
11146 amo_literals.resize(new_size);
11149 const int new_var = context_->NewBoolVarWithClause(amo_literals);
11152 context_->working_model->add_constraints()->mutable_exactly_one();
11153 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
11154 for (
const int lit : amo_literals) {
11155 new_exo->add_literals(lit);
11158 context_->UpdateNewConstraintsVariableUsage();
11163 ConstraintProto* ct =
11164 context_->working_model->mutable_constraints(base_ct_index);
11165 auto* mutable_literals =
11166 ct->constraint_case() == ConstraintProto::kAtMostOne
11167 ? ct->mutable_at_most_one()->mutable_literals()
11168 : ct->mutable_exactly_one()->mutable_literals();
11170 for (
const int lit : *mutable_literals) {
11171 if (var_in_amo.contains(
PositiveRef(lit)))
continue;
11172 (*mutable_literals)[new_size++] = lit;
11174 (*mutable_literals)[new_size++] = new_var;
11175 mutable_literals->Truncate(new_size);
11176 context_->UpdateConstraintVariableUsage(base_ct_index);
11180 for (
const int c : block_cts) {
11181 auto* mutable_linear =
11182 context_->working_model->mutable_constraints(c)->mutable_linear();
11185 int64_t offset = 0;
11186 int64_t coeff_x = 0;
11189 const int num_terms = mutable_linear->vars().size();
11190 for (
int k = 0; k < num_terms; ++k) {
11191 const int var = mutable_linear->vars(k);
11193 int64_t coeff = mutable_linear->coeffs(k);
11194 const auto it = var_in_amo.find(var);
11195 if (it != var_in_amo.end()) {
11204 if (coeff_x == 0) coeff_x = coeff;
11205 CHECK_EQ(coeff, coeff_x);
11208 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
11209 mutable_linear->set_coeffs(new_size, coeff);
11214 mutable_linear->set_vars(new_size, new_var);
11215 mutable_linear->set_coeffs(new_size, coeff_x);
11218 mutable_linear->mutable_vars()->Truncate(new_size);
11219 mutable_linear->mutable_coeffs()->Truncate(new_size);
11225 context_->UpdateConstraintVariableUsage(c);
11229 timer.AddCounter(
"blocks", num_blocks);
11230 timer.AddCounter(
"saved_nz", nz_reduction);
11231 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11235void CpModelPresolver::FindBigVerticalLinearOverlap(
11237 if (time_limit_->LimitReached())
return;
11238 if (context_->ModelIsUnsat())
return;
11239 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11240 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11242 int64_t num_blocks = 0;
11243 int64_t nz_reduction = 0;
11244 absl::flat_hash_map<int, int64_t> coeff_map;
11245 for (
int x = 0;
x < context_->working_model->variables().size(); ++
x) {
11246 if (timer.WorkLimitIsReached())
break;
11248 bool in_enforcement =
false;
11249 std::vector<int> linear_cts;
11250 timer.TrackSimpleLoop(context_->VarToConstraints(x).size());
11251 for (
const int c : context_->VarToConstraints(x)) {
11252 if (c < 0)
continue;
11253 const ConstraintProto& ct = context_->working_model->constraints(c);
11254 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11256 const int num_terms = ct.linear().vars().size();
11257 if (num_terms < 2)
continue;
11258 bool is_canonical =
true;
11259 timer.TrackSimpleLoop(num_terms);
11260 for (
int k = 0; k < num_terms; ++k) {
11262 is_canonical =
false;
11266 if (!is_canonical)
continue;
11269 timer.TrackSimpleLoop(ct.enforcement_literal().size());
11270 for (
const int lit : ct.enforcement_literal()) {
11272 in_enforcement =
true;
11279 if (in_enforcement)
continue;
11280 linear_cts.push_back(c);
11286 if (in_enforcement)
continue;
11287 if (linear_cts.size() < 10)
continue;
11290 std::sort(linear_cts.begin(), linear_cts.end());
11291 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->random());
11300 std::vector<std::pair<int, int64_t>> block;
11301 std::vector<std::pair<int, int64_t>> common_part;
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(num_terms);
11308 const int64_t x_coeff = FindVarCoeff(x, ct);
11309 if (x_coeff == 0)
continue;
11311 if (block.empty()) {
11314 for (
int k = 0; k < num_terms; ++k) {
11315 coeff_map[ct.linear().vars(k)] = ct.linear().coeffs(k);
11317 if (coeff_map.size() < 2)
continue;
11318 block.push_back({
c, x_coeff});
11323 const int64_t gcd =
11324 std::gcd(std::abs(coeff_map.at(x)), std::abs(x_coeff));
11325 const int64_t multiple_base = coeff_map.at(x) / gcd;
11326 const int64_t multiple_ct = x_coeff / gcd;
11327 common_part.clear();
11328 for (
int k = 0; k < num_terms; ++k) {
11329 const int64_t coeff = ct.linear().coeffs(k);
11330 if (coeff % multiple_ct != 0)
continue;
11332 const auto it = coeff_map.find(ct.linear().vars(k));
11333 if (it == coeff_map.end())
continue;
11334 if (it->second % multiple_base != 0)
continue;
11335 if (it->second / multiple_base != coeff / multiple_ct)
continue;
11337 common_part.push_back({ct.linear().vars(k), coeff / multiple_ct});
11341 if (common_part.size() < 2)
continue;
11344 block.push_back({
c, x_coeff});
11346 for (
const auto [var, coeff] : common_part) {
11347 coeff_map[var] = coeff;
11352 const int64_t saved_nz =
11353 ComputeNonZeroReduction(block.size(), coeff_map.size());
11354 if (saved_nz < 30)
continue;
11357 const int64_t base_x = coeff_map.at(x);
11358 for (
auto& [c, multipier] : block) {
11359 CHECK_EQ(multipier % base_x, 0);
11360 multipier /= base_x;
11364 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
11366 nz_reduction += saved_nz;
11367 context_->UpdateRuleStats(
"linear matrix: common vertical rectangle");
11370 timer.AddCounter(
"blocks", num_blocks);
11371 timer.AddCounter(
"saved_nz", nz_reduction);
11372 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11382void CpModelPresolver::FindBigHorizontalLinearOverlap(
11384 if (time_limit_->LimitReached())
return;
11385 if (context_->ModelIsUnsat())
return;
11386 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11387 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11389 const int num_constraints = context_->working_model->constraints_size();
11390 std::vector<std::pair<int, int>> to_sort;
11391 for (
int c = 0;
c < num_constraints; ++
c) {
11392 const ConstraintProto& ct = context_->working_model->constraints(c);
11393 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
11394 const int size = ct.linear().vars().size();
11395 if (size < 5)
continue;
11396 to_sort.push_back({-size,
c});
11398 std::sort(to_sort.begin(), to_sort.end());
11400 std::vector<int> sorted_linear;
11401 for (
int i = 0;
i < to_sort.size(); ++
i) {
11402 sorted_linear.push_back(to_sort[
i].second);
11407 std::vector<int> var_to_coeff_non_zeros;
11408 std::vector<int64_t> var_to_coeff(context_->working_model->variables_size(),
11411 int64_t num_blocks = 0;
11412 int64_t nz_reduction = 0;
11413 for (
int i = 0;
i < sorted_linear.size(); ++
i) {
11414 const int c = sorted_linear[
i];
11415 if (c < 0)
continue;
11416 if (timer.WorkLimitIsReached())
break;
11418 for (
const int var : var_to_coeff_non_zeros) {
11419 var_to_coeff[var] = 0;
11421 var_to_coeff_non_zeros.clear();
11423 const ConstraintProto& ct = context_->working_model->constraints(c);
11424 const int num_terms = ct.linear().vars().size();
11425 timer.TrackSimpleLoop(num_terms);
11426 for (
int k = 0; k < num_terms; ++k) {
11427 const int var = ct.linear().vars(k);
11428 var_to_coeff[var] = ct.linear().coeffs(k);
11429 var_to_coeff_non_zeros.push_back(var);
11437 int saved_nz = 100;
11438 std::vector<int> used_sorted_linear = {
i};
11439 std::vector<std::pair<int, int64_t>> block = {{
c, 1}};
11440 std::vector<std::pair<int, int64_t>> common_part;
11441 std::vector<std::pair<int, int>> old_matches;
11443 for (
int j = 0; j < sorted_linear.size(); ++j) {
11444 if (
i == j)
continue;
11445 const int other_c = sorted_linear[j];
11446 if (other_c < 0)
continue;
11447 const ConstraintProto& ct = context_->working_model->constraints(other_c);
11450 const int num_terms = ct.linear().vars().size();
11451 const int best_saved_nz =
11452 ComputeNonZeroReduction(block.size() + 1, num_terms);
11453 if (best_saved_nz <= saved_nz)
break;
11456 timer.TrackSimpleLoop(num_terms);
11457 common_part.clear();
11458 for (
int k = 0; k < num_terms; ++k) {
11459 const int var = ct.linear().vars(k);
11460 if (var_to_coeff[var] == ct.linear().coeffs(k)) {
11461 common_part.push_back({var, ct.linear().coeffs(k)});
11470 const int64_t new_saved_nz =
11471 ComputeNonZeroReduction(block.size() + 1, common_part.size());
11472 if (new_saved_nz > saved_nz) {
11473 saved_nz = new_saved_nz;
11474 used_sorted_linear.push_back(j);
11475 block.push_back({other_c, 1});
11479 for (
const int var : var_to_coeff_non_zeros) {
11480 var_to_coeff[var] = 0;
11482 var_to_coeff_non_zeros.clear();
11483 for (
const auto [var, coeff] : common_part) {
11484 var_to_coeff[var] = coeff;
11485 var_to_coeff_non_zeros.push_back(var);
11488 if (common_part.size() > 1) {
11489 old_matches.push_back({j, common_part.size()});
11496 if (block.size() > 1) {
11498 const int match_size = var_to_coeff_non_zeros.size();
11499 for (
const auto [index, old_match_size] : old_matches) {
11500 if (old_match_size < match_size)
continue;
11502 int new_match_size = 0;
11503 const int other_c = sorted_linear[index];
11504 const ConstraintProto& ct =
11505 context_->working_model->constraints(other_c);
11506 const int num_terms = ct.linear().vars().size();
11507 for (
int k = 0; k < num_terms; ++k) {
11508 if (var_to_coeff[ct.linear().vars(k)] == ct.linear().coeffs(k)) {
11512 if (new_match_size == match_size) {
11513 context_->UpdateRuleStats(
11514 "linear matrix: common horizontal rectangle extension");
11515 used_sorted_linear.push_back(index);
11516 block.push_back({other_c, 1});
11522 absl::flat_hash_map<int, int64_t> coeff_map;
11523 for (
const int var : var_to_coeff_non_zeros) {
11524 coeff_map[var] = var_to_coeff[var];
11526 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
11529 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
11530 context_->UpdateRuleStats(
"linear matrix: common horizontal rectangle");
11531 for (
const int i : used_sorted_linear) sorted_linear[
i] = -1;
11535 timer.AddCounter(
"blocks", num_blocks);
11536 timer.AddCounter(
"saved_nz", nz_reduction);
11537 timer.AddCounter(
"linears", sorted_linear.size());
11538 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11546void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
11547 if (time_limit_->LimitReached())
return;
11548 if (context_->ModelIsUnsat())
return;
11553 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11556 std::vector<std::pair<int, int>> to_sort;
11557 const int num_constraints = context_->working_model->constraints_size();
11558 for (
int c = 0;
c < num_constraints; ++
c) {
11559 const ConstraintProto& ct = context_->working_model->constraints(c);
11560 if (!IsLinearEqualityConstraint(ct))
continue;
11561 if (ct.linear().vars().size() <= 2)
continue;
11564 if (!std::is_sorted(ct.linear().vars().begin(), ct.linear().vars().end())) {
11568 to_sort.push_back({ct.linear().vars().size(),
c});
11570 std::sort(to_sort.begin(), to_sort.end());
11574 std::vector<int> var_to_clear;
11575 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
11576 const int num_variables = context_->working_model->variables_size();
11577 var_to_ct_coeffs_.resize(num_variables);
11580 int num_tested_pairs = 0;
11581 int num_affine_relations = 0;
11582 for (
int start = 0; start < to_sort.size(); start = end) {
11585 const int length = to_sort[start].first;
11586 for (; end < to_sort.size(); ++end) {
11587 if (to_sort[end].first != length)
break;
11589 const int span_size = end - start;
11590 if (span_size == 1)
continue;
11593 for (
const int var : var_to_clear) var_to_ct_coeffs_[var].clear();
11594 var_to_clear.clear();
11595 for (
int i = start;
i < end; ++
i) {
11596 const int c = to_sort[
i].second;
11597 const LinearConstraintProto& lin =
11598 context_->working_model->constraints(c).linear();
11600 absl::Uniform<int>(*context_->random(), 0, lin.vars().size());
11601 const int var = lin.vars(index);
11602 if (var_to_ct_coeffs_[var].empty()) var_to_clear.push_back(var);
11603 var_to_ct_coeffs_[var].push_back({
c, lin.coeffs(index)});
11612 for (
int i1 = start; i1 < end; ++i1) {
11613 if (timer.WorkLimitIsReached())
break;
11614 const int c1 = to_sort[i1].second;
11615 const LinearConstraintProto& lin1 =
11616 context_->working_model->constraints(c1).linear();
11618 for (
int i = 0; !skip &&
i < lin1.vars().size(); ++
i) {
11619 for (
const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(
i)]) {
11620 if (c2 == c1)
continue;
11623 if (coeff2 != lin1.coeffs(
i))
continue;
11624 if (timer.WorkLimitIsReached())
break;
11627 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11628 if (ct2.constraint_case() != ConstraintProto::kLinear)
continue;
11629 const LinearConstraintProto& lin2 =
11630 context_->working_model->constraints(c2).linear();
11631 if (lin2.vars().size() != length)
continue;
11636 timer.TrackSimpleLoop(length);
11638 ++num_tested_pairs;
11643 auto* to_modify = context_->working_model->mutable_constraints(c1);
11645 -1, context_->working_model->constraints(c2), to_modify)) {
11652 DCHECK_LE(to_modify->linear().vars().size(), 2);
11654 ++num_affine_relations;
11655 context_->UpdateRuleStats(
11656 "linear: advanced affine relation from 2 constraints.");
11659 DivideLinearByGcd(to_modify);
11660 PresolveSmallLinear(to_modify);
11661 context_->UpdateConstraintVariableUsage(c1);
11670 timer.AddCounter(
"num_tested_pairs", num_tested_pairs);
11671 timer.AddCounter(
"found", num_affine_relations);
11672 DCHECK(context_->ConstraintVariableUsageIsConsistent());
11675void CpModelPresolver::ExtractEncodingFromLinear() {
11676 if (time_limit_->LimitReached())
return;
11677 if (context_->ModelIsUnsat())
return;
11678 if (context_->params().presolve_inclusion_work_limit() == 0)
return;
11679 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11682 std::vector<int> relevant_constraints;
11683 CompactVectorVector<int> storage;
11685 detector.SetWorkLimit(context_->params().presolve_inclusion_work_limit());
11691 std::vector<int> vars;
11692 const int num_constraints = context_->working_model->constraints().size();
11693 for (
int c = 0;
c < num_constraints; ++
c) {
11694 const ConstraintProto& ct = context_->working_model->constraints(c);
11695 switch (ct.constraint_case()) {
11696 case ConstraintProto::kAtMostOne: {
11698 for (
const int ref : ct.at_most_one().literals()) {
11701 relevant_constraints.push_back(c);
11702 detector.AddPotentialSuperset(storage.Add(vars));
11705 case ConstraintProto::kExactlyOne: {
11707 for (
const int ref : ct.exactly_one().literals()) {
11710 relevant_constraints.push_back(c);
11711 detector.AddPotentialSuperset(storage.Add(vars));
11714 case ConstraintProto::kLinear: {
11716 if (!IsLinearEqualityConstraint(ct))
continue;
11720 bool is_candidate =
true;
11721 int num_integers = 0;
11723 const int num_terms = ct.linear().vars().size();
11724 for (
int i = 0;
i < num_terms; ++
i) {
11725 const int ref = ct.linear().vars(
i);
11726 if (context_->CanBeUsedAsLiteral(ref)) {
11730 if (std::abs(ct.linear().coeffs(
i)) != 1) {
11731 is_candidate =
false;
11734 if (num_integers == 2) {
11735 is_candidate =
false;
11743 if (is_candidate && num_integers == 1 && vars.size() > 1) {
11744 relevant_constraints.push_back(c);
11745 detector.AddPotentialSubset(storage.Add(vars));
11755 int64_t num_exactly_one_encodings = 0;
11756 int64_t num_at_most_one_encodings = 0;
11757 int64_t num_literals = 0;
11758 int64_t num_unique_terms = 0;
11759 int64_t num_multiple_terms = 0;
11761 detector.DetectInclusions([&](
int subset,
int superset) {
11762 const int subset_c = relevant_constraints[subset];
11763 const int superset_c = relevant_constraints[superset];
11764 const ConstraintProto& superset_ct =
11765 context_->working_model->constraints(superset_c);
11766 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
11767 ++num_at_most_one_encodings;
11769 ++num_exactly_one_encodings;
11771 num_literals += storage[subset].size();
11772 context_->UpdateRuleStats(
"encoding: extracted from linear");
11774 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
11775 &num_multiple_terms)) {
11779 detector.StopProcessingCurrentSubset();
11782 timer.AddCounter(
"potential_supersets", detector.num_potential_supersets());
11783 timer.AddCounter(
"potential_subsets", detector.num_potential_subsets());
11784 timer.AddCounter(
"amo_encodings", num_at_most_one_encodings);
11785 timer.AddCounter(
"exo_encodings", num_exactly_one_encodings);
11786 timer.AddCounter(
"unique_terms", num_unique_terms);
11787 timer.AddCounter(
"multiple_terms", num_multiple_terms);
11788 timer.AddCounter(
"literals", num_literals);
11802void CpModelPresolver::LookAtVariableWithDegreeTwo(
int var) {
11804 CHECK(context_->ConstraintVariableGraphIsUpToDate());
11805 if (context_->ModelIsUnsat())
return;
11806 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
11807 if (context_->IsFixed(var))
return;
11808 if (!context_->ModelIsExpanded())
return;
11809 if (!context_->CanBeUsedAsLiteral(var))
return;
11816 if (context_->VarToConstraints(var).size() != 2)
return;
11818 bool abort =
false;
11820 Domain union_of_domain;
11821 int num_positive = 0;
11822 std::vector<int> constraint_indices_to_remove;
11823 for (
const int c : context_->VarToConstraints(var)) {
11828 constraint_indices_to_remove.push_back(c);
11829 const ConstraintProto& ct = context_->working_model->constraints(c);
11830 if (ct.enforcement_literal().size() != 1 ||
11832 ct.constraint_case() != ConstraintProto::kLinear ||
11833 ct.linear().vars().size() != 1) {
11837 if (ct.enforcement_literal(0) == var) ++num_positive;
11838 if (ct_var != -1 &&
PositiveRef(ct.linear().vars(0)) != ct_var) {
11843 union_of_domain = union_of_domain.UnionWith(
11846 ? ct.linear().coeffs(0)
11847 : -ct.linear().coeffs(0)));
11850 if (num_positive != 1)
return;
11851 if (!context_->IntersectDomainWith(ct_var, union_of_domain))
return;
11853 context_->UpdateRuleStats(
"variables: removable enforcement literal");
11854 absl::c_sort(constraint_indices_to_remove);
11859 for (
const int c : constraint_indices_to_remove) {
11860 context_->NewMappingConstraint(context_->working_model->constraints(c),
11861 __FILE__, __LINE__);
11862 context_->working_model->mutable_constraints(c)->Clear();
11863 context_->UpdateConstraintVariableUsage(c);
11865 context_->MarkVariableAsRemoved(var);
11870absl::Span<const int> AtMostOneOrExactlyOneLiterals(
const ConstraintProto& ct) {
11871 if (ct.constraint_case() == ConstraintProto::kAtMostOne) {
11872 return {ct.at_most_one().literals()};
11874 return {ct.exactly_one().literals()};
11880void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(
int var) {
11882 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
11883 if (context_->ModelIsUnsat())
return;
11884 if (context_->IsFixed(var))
return;
11885 if (context_->VariableWasRemoved(var))
return;
11886 if (!context_->ModelIsExpanded())
return;
11887 if (!context_->CanBeUsedAsLiteral(var))
return;
11891 if (context_->VarToConstraints(var).size() != 3)
return;
11892 cost = context_->ObjectiveMap().at(var);
11894 if (context_->VarToConstraints(var).size() != 2)
return;
11902 for (
const int c : context_->VarToConstraints(var)) {
11903 if (c < 0)
continue;
11904 const ConstraintProto& ct = context_->working_model->constraints(c);
11905 if (ct.constraint_case() != ConstraintProto::kAtMostOne &&
11906 ct.constraint_case() != ConstraintProto::kExactlyOne) {
11917 if (c1 == -1 || c2 == -1)
return;
11921 if (c1 > c2) std::swap(c1, c2);
11930 context_->tmp_literals.clear();
11931 int c1_ref = std::numeric_limits<int>::min();
11932 const ConstraintProto& ct1 = context_->working_model->constraints(c1);
11933 if (AtMostOneOrExactlyOneLiterals(ct1).size() <= 1)
return;
11934 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
11938 context_->tmp_literals.push_back(lit);
11941 int c2_ref = std::numeric_limits<int>::min();
11942 const ConstraintProto& ct2 = context_->working_model->constraints(c2);
11943 if (AtMostOneOrExactlyOneLiterals(ct2).size() <= 1)
return;
11944 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
11948 context_->tmp_literals.push_back(lit);
11951 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
11952 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
11957 int64_t cost_shift = 0;
11958 absl::Span<const int> literals;
11959 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
11961 literals = ct1.exactly_one().literals();
11962 }
else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
11964 literals = ct2.exactly_one().literals();
11970 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
11971 if (context_->params().keep_symmetry_in_presolve())
return;
11972 if (cost != 0 && context_->ObjectiveDomainIsConstraining())
return;
11976 literals = ct1.at_most_one().literals();
11979 literals = ct2.at_most_one().literals();
11983 if (!context_->ShiftCostInExactlyOne(literals, cost_shift))
return;
11984 DCHECK(!context_->ObjectiveMap().contains(var));
11985 context_->NewMappingConstraint(__FILE__, __LINE__)
11986 ->mutable_exactly_one()
11987 ->mutable_literals()
11988 ->Assign(literals.begin(), literals.end());
11991 const int new_ct_index = context_->working_model->constraints().size();
11992 ConstraintProto* new_ct = context_->working_model->add_constraints();
11993 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
11994 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
11995 for (
const int lit : context_->tmp_literals) {
11996 new_ct->mutable_exactly_one()->add_literals(lit);
12001 for (
const int lit : context_->tmp_literals) {
12002 new_ct->mutable_at_most_one()->add_literals(lit);
12006 context_->UpdateNewConstraintsVariableUsage();
12007 context_->working_model->mutable_constraints(c1)->Clear();
12008 context_->UpdateConstraintVariableUsage(c1);
12009 context_->working_model->mutable_constraints(c2)->Clear();
12010 context_->UpdateConstraintVariableUsage(c2);
12012 context_->UpdateRuleStats(
12013 "at_most_one: resolved two constraints with opposite literal");
12014 context_->MarkVariableAsRemoved(var);
12019 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
12020 if (PresolveAtMostOrExactlyOne(new_ct)) {
12021 context_->UpdateConstraintVariableUsage(new_ct_index);
12036void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(
int var) {
12039 int num_others = 0;
12040 std::vector<int> to_rewrite;
12041 for (
const int c : context_->VarToConstraints(var)) {
12043 const ConstraintProto& ct = context_->working_model->constraints(c);
12044 if (ct.constraint_case() == ConstraintProto::kLinear &&
12045 ct.linear().vars().size() == 1) {
12046 to_rewrite.push_back(c);
12053 if (num_others != 1)
return;
12054 if (other_c < 0)
return;
12059 const auto& other_ct = context_->working_model->constraints(other_c);
12060 if (context_->ConstraintToVars(other_c).size() != 2 ||
12061 !other_ct.enforcement_literal().empty() ||
12062 other_ct.constraint_case() == ConstraintProto::kLinear) {
12068 std::function<std::pair<int, Domain>(
const Domain& implied)> transfer_f =
12076 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
12077 other_ct.lin_max().target().vars().size() == 1 &&
12078 other_ct.lin_max().target().vars(0) == var &&
12079 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
12080 IsAffineIntAbs(other_ct)) {
12081 context_->UpdateRuleStats(
"linear1: transferred from abs(X) to X");
12082 const LinearExpressionProto& target = other_ct.lin_max().target();
12083 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
12084 transfer_f = [target = target, expr = expr](
const Domain& implied) {
12085 Domain target_domain =
12086 implied.ContinuousMultiplicationBy(target.coeffs(0))
12087 .AdditionWith(Domain(target.offset()));
12088 target_domain = target_domain.IntersectionWith(
12089 Domain(0, std::numeric_limits<int64_t>::max()));
12092 const Domain expr_domain =
12093 target_domain.UnionWith(target_domain.Negation());
12094 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
12095 .InverseMultiplicationBy(expr.coeffs(0));
12096 return std::make_pair(expr.vars(0), new_domain);
12100 if (transfer_f ==
nullptr) {
12101 context_->UpdateRuleStats(
12102 "TODO linear1: appear in only one extra 2-var constraint");
12107 std::sort(to_rewrite.begin(), to_rewrite.end());
12108 const Domain var_domain = context_->DomainOf(var);
12109 for (
const int c : to_rewrite) {
12110 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12111 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
12113 LOG(INFO) <<
"Aborted in MaybeTransferLinear1ToAnotherVariable()";
12117 const Domain implied =
12119 auto [new_var, new_domain] = transfer_f(implied);
12120 const Domain current = context_->DomainOf(new_var);
12121 new_domain = new_domain.IntersectionWith(current);
12122 if (new_domain.IsEmpty()) {
12123 if (!MarkConstraintAsFalse(ct))
return;
12124 }
else if (new_domain == current) {
12127 ct->mutable_linear()->set_vars(0, new_var);
12130 context_->UpdateConstraintVariableUsage(c);
12134 context_->NewMappingConstraint(other_ct, __FILE__, __LINE__);
12135 context_->working_model->mutable_constraints(other_c)->Clear();
12136 context_->UpdateConstraintVariableUsage(other_c);
12137 context_->MarkVariableAsRemoved(var);
12150void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(
int var) {
12151 if (context_->ModelIsUnsat())
return;
12152 if (context_->params().keep_all_feasible_solutions_in_presolve())
return;
12153 if (context_->params().keep_symmetry_in_presolve())
return;
12154 if (context_->IsFixed(var))
return;
12155 if (context_->VariableWasRemoved(var))
return;
12156 if (context_->CanBeUsedAsLiteral(var))
return;
12157 if (context_->params().search_branching() == SatParameters::FIXED_SEARCH) {
12161 if (!context_->VariableIsOnlyUsedInEncodingAndMaybeInObjective(var)) {
12162 if (context_->VariableIsOnlyUsedInLinear1AndOneExtraConstraint(var)) {
12163 MaybeTransferLinear1ToAnotherVariable(var);
12179 if (context_->VariableWithCostIsUniqueAndRemovable(var)) {
12181 for (
const int c : context_->VarToConstraints(var)) {
12182 if (c < 0)
continue;
12183 CHECK_EQ(unique_c, -1);
12186 CHECK_NE(unique_c, -1);
12187 const ConstraintProto& ct = context_->working_model->constraints(unique_c);
12188 const int64_t cost = context_->ObjectiveCoeff(var);
12189 if (ct.linear().vars(0) == var) {
12193 if (implied.IsEmpty()) {
12194 if (!MarkConstraintAsFalse(
12195 context_->working_model->mutable_constraints(unique_c))) {
12198 context_->UpdateConstraintVariableUsage(unique_c);
12202 int64_t value1, value2;
12204 context_->UpdateRuleStats(
"variables: fix singleton var in linear1");
12205 return (
void)context_->IntersectDomainWith(var, Domain(implied.
Min()));
12206 }
else if (cost > 0) {
12207 value1 = context_->MinOf(var);
12208 value2 = implied.
Min();
12210 value1 = context_->MaxOf(var);
12211 value2 = implied.
Max();
12216 context_->UpdateRuleStats(
"variables: reduced domain to two values");
12223 solution_crush_.SetVarToConditionalValue(var, ct.enforcement_literal(),
12225 return (
void)context_->IntersectDomainWith(
12236 absl::flat_hash_set<int64_t> values_set;
12237 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
12238 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
12239 bool abort =
false;
12240 for (
const int c : context_->VarToConstraints(var)) {
12241 if (c < 0)
continue;
12242 const ConstraintProto& ct = context_->working_model->constraints(c);
12243 CHECK_EQ(ct.constraint_case(), ConstraintProto::kLinear);
12244 CHECK_EQ(ct.linear().vars().size(), 1);
12245 int64_t coeff = ct.linear().coeffs(0);
12246 if (std::abs(coeff) != 1 || ct.enforcement_literal().size() != 1) {
12251 const int var =
PositiveRef(ct.linear().vars(0));
12252 const Domain var_domain = context_->DomainOf(var);
12256 if (rhs.IsEmpty()) {
12257 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12261 }
else if (rhs.IsFixed()) {
12262 if (!var_domain.
Contains(rhs.FixedValue())) {
12263 if (!context_->SetLiteralToFalse(ct.enforcement_literal(0))) {
12267 values_set.insert(rhs.FixedValue());
12268 value_to_equal_literals[rhs.FixedValue()].push_back(
12269 ct.enforcement_literal(0));
12272 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
12273 if (complement.IsEmpty()) {
12278 if (complement.IsFixed()) {
12279 if (var_domain.
Contains(complement.FixedValue())) {
12280 values_set.insert(complement.FixedValue());
12281 value_to_not_equal_literals[complement.FixedValue()].push_back(
12282 ct.enforcement_literal(0));
12291 context_->UpdateRuleStats(
"TODO variables: only used in linear1.");
12293 }
else if (value_to_not_equal_literals.empty() &&
12294 value_to_equal_literals.empty()) {
12301 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
12302 std::sort(encoded_values.begin(), encoded_values.end());
12303 CHECK(!encoded_values.empty());
12304 const bool is_fully_encoded =
12305 encoded_values.size() == context_->DomainOf(var).Size();
12310 for (
const int64_t v : encoded_values) {
12311 const int encoding_lit = context_->GetOrCreateVarValueEncoding(var, v);
12312 const auto eq_it = value_to_equal_literals.find(v);
12313 if (eq_it != value_to_equal_literals.end()) {
12314 absl::c_sort(eq_it->second);
12315 for (
const int lit : eq_it->second) {
12316 context_->AddImplication(lit, encoding_lit);
12319 const auto neq_it = value_to_not_equal_literals.find(v);
12320 if (neq_it != value_to_not_equal_literals.end()) {
12321 absl::c_sort(neq_it->second);
12322 for (
const int lit : neq_it->second) {
12323 context_->AddImplication(lit,
NegatedRef(encoding_lit));
12327 context_->UpdateNewConstraintsVariableUsage();
12330 Domain other_values;
12331 if (!is_fully_encoded) {
12332 other_values = context_->DomainOf(var).IntersectionWith(
12340 const int64_t obj_coeff = context_->ObjectiveMap().at(var);
12341 if (is_fully_encoded) {
12345 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
12349 if (context_->ObjectiveDomainIsConstraining() &&
12350 !other_values.IsFixed()) {
12351 context_->UpdateRuleStats(
12352 "TODO variables: only used in objective and in encoding");
12363 Domain(obj_coeff > 0 ? other_values.
Min() : other_values.
Max());
12364 min_value = other_values.FixedValue();
12369 int64_t accumulated = std::abs(min_value);
12370 for (
const int64_t value : encoded_values) {
12371 accumulated =
CapAdd(accumulated, std::abs(
CapSub(value, min_value)));
12372 if (accumulated == std::numeric_limits<int64_t>::max()) {
12373 context_->UpdateRuleStats(
12374 "TODO variables: only used in objective and in encoding");
12379 ConstraintProto encoding_ct;
12380 LinearConstraintProto* linear = encoding_ct.mutable_linear();
12381 const int64_t coeff_in_equality = -1;
12382 linear->add_vars(var);
12383 linear->add_coeffs(coeff_in_equality);
12385 linear->add_domain(-min_value);
12386 linear->add_domain(-min_value);
12387 for (
const int64_t value : encoded_values) {
12388 if (value == min_value)
continue;
12389 const int enf = context_->GetOrCreateVarValueEncoding(var, value);
12390 const int64_t coeff = value - min_value;
12392 linear->add_vars(enf);
12393 linear->add_coeffs(coeff);
12396 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
12397 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
12399 linear->add_coeffs(-coeff);
12402 if (!context_->SubstituteVariableInObjective(var, coeff_in_equality,
12404 context_->UpdateRuleStats(
12405 "TODO variables: only used in objective and in encoding");
12408 context_->UpdateRuleStats(
12409 "variables: only used in objective and in encoding");
12411 context_->UpdateRuleStats(
"variables: only used in encoding");
12416 std::vector<int> to_clear;
12417 for (
const int c : context_->VarToConstraints(var)) {
12418 if (c >= 0) to_clear.push_back(c);
12420 absl::c_sort(to_clear);
12421 for (
const int c : to_clear) {
12422 if (c < 0)
continue;
12423 context_->working_model->mutable_constraints(c)->Clear();
12424 context_->UpdateConstraintVariableUsage(c);
12429 int64_t special_value = 0;
12432 ConstraintProto* new_ct = context_->working_model->add_constraints();
12433 if (is_fully_encoded) {
12435 for (
const int64_t value : encoded_values) {
12436 new_ct->mutable_exactly_one()->add_literals(
12437 context_->GetOrCreateVarValueEncoding(var, value));
12439 PresolveExactlyOne(new_ct);
12442 for (
const int64_t value : encoded_values) {
12443 new_ct->mutable_at_most_one()->add_literals(
12444 context_->GetOrCreateVarValueEncoding(var, value));
12446 PresolveAtMostOne(new_ct);
12450 special_value = other_values.SmallestValue();
12452 if (context_->ModelIsUnsat())
return;
12456 ConstraintProto* mapping_ct =
12457 context_->NewMappingConstraint(__FILE__, __LINE__);
12458 mapping_ct->mutable_linear()->add_vars(var);
12459 mapping_ct->mutable_linear()->add_coeffs(1);
12460 int64_t offset = special_value;
12461 for (
const int64_t value : encoded_values) {
12462 const int literal = context_->GetOrCreateVarValueEncoding(var, value);
12463 const int64_t coeff = (value - special_value);
12465 mapping_ct->mutable_linear()->add_vars(literal);
12466 mapping_ct->mutable_linear()->add_coeffs(-coeff);
12469 mapping_ct->mutable_linear()->add_vars(
PositiveRef(literal));
12470 mapping_ct->mutable_linear()->add_coeffs(coeff);
12473 mapping_ct->mutable_linear()->add_domain(offset);
12474 mapping_ct->mutable_linear()->add_domain(offset);
12476 context_->UpdateNewConstraintsVariableUsage();
12477 context_->MarkVariableAsRemoved(var);
12480void CpModelPresolver::TryToSimplifyDomain(
int var) {
12482 DCHECK(context_->ConstraintVariableGraphIsUpToDate());
12483 if (context_->ModelIsUnsat())
return;
12484 if (context_->IsFixed(var))
return;
12485 if (context_->VariableWasRemoved(var))
return;
12486 if (context_->VariableIsNotUsedAnymore(var))
return;
12488 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12489 if (r.representative != var)
return;
12492 const Domain& domain = context_->DomainOf(var);
12495 if (domain.Size() == 2 && (domain.
Min() != 0 || domain.
Max() != 1)) {
12496 context_->CanonicalizeDomainOfSizeTwo(var);
12500 if (domain.NumIntervals() != domain.Size())
return;
12502 const int64_t var_min = domain.
Min();
12503 int64_t gcd = domain[1].start - var_min;
12504 for (
int index = 2; index < domain.NumIntervals(); ++index) {
12505 const ClosedInterval&
i = domain[index];
12506 DCHECK_EQ(
i.start,
i.end);
12507 const int64_t shifted_value =
i.start - var_min;
12508 DCHECK_GT(shifted_value, 0);
12510 gcd = std::gcd(gcd, shifted_value);
12511 if (gcd == 1)
break;
12513 if (gcd == 1)
return;
12516 context_->CanonicalizeAffineVariable(var, 1, gcd, var_min);
12520void CpModelPresolver::EncodeAllAffineRelations() {
12521 int64_t num_added = 0;
12522 for (
int var = 0; var < context_->working_model->variables_size(); ++var) {
12523 if (context_->IsFixed(var))
continue;
12525 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12526 if (r.representative == var)
continue;
12531 if (context_->VariableIsNotUsedAnymore(var))
continue;
12532 if (!PresolveAffineRelationIfAny(var))
break;
12533 if (context_->VariableIsNotUsedAnymore(var))
continue;
12534 if (context_->IsFixed(var))
continue;
12537 ConstraintProto* ct = context_->working_model->add_constraints();
12538 auto* arg = ct->mutable_linear();
12539 arg->add_vars(var);
12540 arg->add_coeffs(1);
12541 arg->add_vars(r.representative);
12542 arg->add_coeffs(-r.coeff);
12543 arg->add_domain(r.offset);
12544 arg->add_domain(r.offset);
12545 context_->UpdateNewConstraintsVariableUsage();
12550 context_->RemoveAllVariablesFromAffineRelationConstraint();
12552 if (num_added > 0) {
12553 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
12558bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
12559 const AffineRelation::Relation r = context_->GetAffineRelation(var);
12560 if (r.representative == var)
return true;
12563 if (!context_->PropagateAffineRelation(var))
return false;
12569 if (context_->IsFixed(var))
return true;
12571 DCHECK(!context_->VariableIsNotUsedAnymore(r.representative));
12576 context_->RemoveNonRepresentativeAffineVariableIfUnused(var);
12581bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
12582 std::deque<int>* queue) {
12584 if (context_->ModelIsUnsat())
return false;
12585 if (time_limit_->LimitReached())
return false;
12586 in_queue->resize(context_->working_model->constraints_size(),
false);
12587 const auto& vector_that_can_grow_during_iter =
12588 context_->modified_domains.PositionsSetAtLeastOnce();
12589 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
12590 const int v = vector_that_can_grow_during_iter[
i];
12591 context_->modified_domains.Clear(v);
12592 if (context_->VariableIsNotUsedAnymore(v))
continue;
12593 if (context_->ModelIsUnsat())
return false;
12594 if (!PresolveAffineRelationIfAny(v))
return false;
12595 if (context_->VariableIsNotUsedAnymore(v))
continue;
12597 TryToSimplifyDomain(v);
12600 if (context_->ModelIsUnsat())
return false;
12601 context_->UpdateNewConstraintsVariableUsage();
12603 if (!context_->CanonicalizeOneObjectiveVariable(v))
return false;
12605 in_queue->resize(context_->working_model->constraints_size(),
false);
12606 for (
const int c : context_->VarToConstraints(v)) {
12607 if (c >= 0 && !(*in_queue)[c]) {
12608 (*in_queue)[
c] =
true;
12609 queue->push_back(c);
12613 context_->modified_domains.SparseClearAll();
12617 std::sort(queue->begin(), queue->end());
12618 return !queue->empty();
12621void CpModelPresolver::PresolveToFixPoint() {
12622 if (time_limit_->LimitReached())
return;
12623 if (context_->ModelIsUnsat())
return;
12624 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12627 int num_dominance_tests = 0;
12628 int num_dual_strengthening = 0;
12631 const int64_t max_num_operations =
12632 context_->params().debug_max_num_presolve_operations() > 0
12633 ? context_->params().debug_max_num_presolve_operations()
12634 : std::numeric_limits<int64_t>::max();
12639 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
12642 std::vector<bool> in_queue(context_->working_model->constraints_size(),
12644 std::deque<int> queue;
12645 for (
int c = 0;
c < in_queue.size(); ++
c) {
12646 if (context_->working_model->constraints(c).constraint_case() !=
12647 ConstraintProto::CONSTRAINT_NOT_SET) {
12648 in_queue[
c] =
true;
12649 queue.push_back(c);
12657 if (context_->params().permute_presolve_constraint_order()) {
12658 std::shuffle(queue.begin(), queue.end(), *context_->random());
12660 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
12661 const int score_a = context_->ConstraintToVars(a).size();
12662 const int score_b = context_->ConstraintToVars(b).size();
12663 return score_a < score_b || (score_a == score_b && a < b);
12671 constexpr int kMaxNumLoops = 1000;
12672 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
12673 if (time_limit_->LimitReached())
break;
12674 if (context_->ModelIsUnsat())
break;
12675 if (context_->num_presolve_operations > max_num_operations)
break;
12678 while (!queue.empty() && !context_->ModelIsUnsat()) {
12679 if (time_limit_->LimitReached())
break;
12680 if (context_->num_presolve_operations > max_num_operations)
break;
12681 const int c = queue.front();
12682 in_queue[
c] =
false;
12685 const int old_num_constraint =
12686 context_->working_model->constraints_size();
12688 if (context_->ModelIsUnsat()) {
12690 logger_,
"Unsat after presolving constraint #", c,
12691 " (warning, dump might be inconsistent): ",
12696 const int new_num_constraints =
12697 context_->working_model->constraints_size();
12698 if (new_num_constraints > old_num_constraint) {
12699 context_->UpdateNewConstraintsVariableUsage();
12700 in_queue.resize(new_num_constraints,
true);
12701 for (
int c = old_num_constraint;
c < new_num_constraints; ++
c) {
12702 queue.push_back(c);
12709 context_->UpdateConstraintVariableUsage(c);
12713 if (context_->ModelIsUnsat())
return;
12715 in_queue.resize(context_->working_model->constraints_size(),
false);
12716 const auto& vector_that_can_grow_during_iter =
12717 context_->var_with_reduced_small_degree.PositionsSetAtLeastOnce();
12718 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
12719 const int v = vector_that_can_grow_during_iter[
i];
12720 if (context_->VariableIsNotUsedAnymore(v))
continue;
12725 context_->var_with_reduced_small_degree.Clear(v);
12729 if (context_->ModelIsUnsat())
return;
12730 if (!PresolveAffineRelationIfAny(v))
return;
12732 const int degree = context_->VarToConstraints(v).size();
12733 if (degree == 0)
continue;
12734 if (degree == 2) LookAtVariableWithDegreeTwo(v);
12735 if (degree == 2 || degree == 3) {
12737 ProcessVariableInTwoAtMostOrExactlyOne(v);
12738 in_queue.resize(context_->working_model->constraints_size(),
false);
12745 if (degree != 1)
continue;
12746 const int c = *context_->VarToConstraints(v).begin();
12747 if (c < 0)
continue;
12752 if (var_constraint_pair_already_called.contains(
12753 std::pair<int, int>(v, c))) {
12756 var_constraint_pair_already_called.insert({v,
c});
12758 if (!in_queue[c]) {
12759 in_queue[
c] =
true;
12760 queue.push_back(c);
12763 context_->var_with_reduced_small_degree.SparseClearAll();
12765 if (ProcessChangedVariables(&in_queue, &queue))
continue;
12767 DCHECK(!context_->HasUnusedAffineVariable());
12770 for (
int v = 0; v < context_->working_model->variables().size(); ++v) {
12771 ProcessVariableOnlyUsedInEncoding(v);
12773 if (ProcessChangedVariables(&in_queue, &queue))
continue;
12779 if (context_->params().keep_all_feasible_solutions_in_presolve())
break;
12780 if (!context_->working_model->assumptions().empty())
break;
12785 for (
int i = 0;
i < 10; ++
i) {
12786 if (context_->ModelIsUnsat())
return;
12787 ++num_dual_strengthening;
12788 DualBoundStrengthening dual_bound_strengthening;
12794 if (!dual_bound_strengthening.Strengthen(context_))
return;
12795 if (ProcessChangedVariables(&in_queue, &queue))
break;
12802 if (dual_bound_strengthening.NumDeletedConstraints() == 0)
break;
12804 if (!queue.empty())
continue;
12809 if (context_->params().keep_symmetry_in_presolve())
break;
12813 if (num_dominance_tests++ < 2) {
12814 if (context_->ModelIsUnsat())
return;
12815 PresolveTimer timer(
"DetectDominanceRelations", logger_, time_limit_);
12816 VarDomination var_dom;
12819 if (ProcessChangedVariables(&in_queue, &queue))
continue;
12823 if (context_->ModelIsUnsat())
return;
12833 const int num_constraints = context_->working_model->constraints_size();
12834 for (
int c = 0;
c < num_constraints; ++
c) {
12835 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12836 switch (ct->constraint_case()) {
12837 case ConstraintProto::kNoOverlap:
12839 if (PresolveNoOverlap(ct)) {
12840 context_->UpdateConstraintVariableUsage(c);
12843 case ConstraintProto::kNoOverlap2D:
12845 if (PresolveNoOverlap2D(c, ct)) {
12846 context_->UpdateConstraintVariableUsage(c);
12849 case ConstraintProto::kCumulative:
12851 if (PresolveCumulative(ct)) {
12852 context_->UpdateConstraintVariableUsage(c);
12855 case ConstraintProto::kBoolOr: {
12858 for (
const auto& pair :
12859 context_->deductions.ProcessClause(ct->bool_or().literals())) {
12860 bool modified =
false;
12861 if (!context_->IntersectDomainWith(pair.first, pair.second,
12866 context_->UpdateRuleStats(
"deductions: reduced variable domain");
12876 timer.AddCounter(
"num_loops", num_loops);
12877 timer.AddCounter(
"num_dual_strengthening", num_dual_strengthening);
12878 context_->deductions.MarkProcessingAsDoneForNow();
12888void CpModelPresolver::MergeClauses() {
12889 if (context_->ModelIsUnsat())
return;
12890 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12893 std::vector<int> to_clean;
12896 absl::flat_hash_map<uint64_t, int> bool_and_map;
12902 const int num_variables = context_->working_model->variables_size();
12903 std::vector<int> bool_or_indices;
12904 std::vector<int64_t> literal_score(2 * num_variables, 0);
12905 const auto get_index = [](
int ref) {
12909 int64_t num_collisions = 0;
12910 int64_t num_merges = 0;
12911 int64_t num_saved_literals = 0;
12912 ClauseWithOneMissingHasher hasher(*context_->random());
12913 const int num_constraints = context_->working_model->constraints_size();
12914 for (
int c = 0;
c < num_constraints; ++
c) {
12915 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12916 if (ct->constraint_case() == ConstraintProto::kBoolAnd) {
12917 if (ct->enforcement_literal().size() > 1) {
12919 std::sort(ct->mutable_enforcement_literal()->begin(),
12920 ct->mutable_enforcement_literal()->end(),
12921 std::greater<int>());
12922 const auto [it, inserted] = bool_and_map.insert(
12923 {hasher.HashOfNegatedLiterals(ct->enforcement_literal()),
c});
12925 to_clean.push_back(c);
12928 ConstraintProto* other_ct =
12929 context_->working_model->mutable_constraints(it->second);
12930 const absl::Span<const int> s1(ct->enforcement_literal());
12931 const absl::Span<const int> s2(other_ct->enforcement_literal());
12933 context_->UpdateRuleStats(
12934 "bool_and: merged constraints with same enforcement");
12935 other_ct->mutable_bool_and()->mutable_literals()->Add(
12936 ct->bool_and().literals().begin(),
12937 ct->bool_and().literals().end());
12939 context_->UpdateConstraintVariableUsage(c);
12945 if (ct->constraint_case() == ConstraintProto::kAtMostOne) {
12946 const int size = ct->at_most_one().literals().size();
12947 for (
const int ref : ct->at_most_one().literals()) {
12948 literal_score[get_index(ref)] += size;
12952 if (ct->constraint_case() == ConstraintProto::kExactlyOne) {
12953 const int size = ct->exactly_one().literals().size();
12954 for (
const int ref : ct->exactly_one().literals()) {
12955 literal_score[get_index(ref)] += size;
12960 if (ct->constraint_case() != ConstraintProto::kBoolOr)
continue;
12963 if (!ct->enforcement_literal().empty())
continue;
12964 if (ct->bool_or().literals().size() <= 2)
continue;
12966 std::sort(ct->mutable_bool_or()->mutable_literals()->begin(),
12967 ct->mutable_bool_or()->mutable_literals()->end());
12968 hasher.RegisterClause(c, ct->bool_or().literals());
12969 bool_or_indices.push_back(c);
12972 for (
const int c : bool_or_indices) {
12973 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
12975 bool merged =
false;
12976 timer.TrackSimpleLoop(ct->bool_or().literals().size());
12977 if (timer.WorkLimitIsReached())
break;
12978 for (
const int ref : ct->bool_or().literals()) {
12979 const uint64_t hash = hasher.HashWithout(c, ref);
12980 const auto it = bool_and_map.find(hash);
12981 if (it != bool_and_map.end()) {
12983 const int base_c = it->second;
12984 auto* and_ct = context_->working_model->mutable_constraints(base_c);
12986 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
12988 num_saved_literals += ct->bool_or().literals().size() - 1;
12990 and_ct->mutable_bool_and()->add_literals(ref);
12992 context_->UpdateConstraintVariableUsage(c);
13000 int best_ref = ct->bool_or().literals(0);
13001 int64_t best_score = literal_score[get_index(
NegatedRef(best_ref))];
13002 for (
const int ref : ct->bool_or().literals()) {
13003 const int64_t score = literal_score[get_index(
NegatedRef(ref))];
13004 if (score > best_score) {
13006 best_score = score;
13010 const uint64_t hash = hasher.HashWithout(c, best_ref);
13011 const auto [_, inserted] = bool_and_map.insert({hash,
c});
13013 to_clean.push_back(c);
13014 context_->tmp_literals.clear();
13015 for (
const int lit : ct->bool_or().literals()) {
13016 if (lit == best_ref)
continue;
13017 context_->tmp_literals.push_back(
NegatedRef(lit));
13020 ct->mutable_enforcement_literal()->Assign(
13021 context_->tmp_literals.begin(), context_->tmp_literals.end());
13022 ct->mutable_bool_and()->add_literals(best_ref);
13028 for (
const int c : to_clean) {
13029 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13030 if (ct->bool_and().literals().size() > 1) {
13031 context_->UpdateConstraintVariableUsage(c);
13036 context_->tmp_literals.clear();
13037 context_->tmp_literals.push_back(ct->bool_and().literals(0));
13038 for (
const int ref : ct->enforcement_literal()) {
13039 context_->tmp_literals.push_back(
NegatedRef(ref));
13042 ct->mutable_bool_or()->mutable_literals()->Assign(
13043 context_->tmp_literals.begin(), context_->tmp_literals.end());
13046 timer.AddCounter(
"num_collisions", num_collisions);
13047 timer.AddCounter(
"num_merges", num_merges);
13048 timer.AddCounter(
"num_saved_literals", num_saved_literals);
13056 std::vector<int>* postsolve_mapping) {
13058 return presolver.Presolve();
13062 std::vector<int>* postsolve_mapping)
13063 : postsolve_mapping_(postsolve_mapping),
13065 solution_crush_(context->solution_crush()),
13066 logger_(context->logger()),
13068 interval_representative_(context->working_model->constraints_size(),
13069 IntervalConstraintHash{context->working_model},
13070 IntervalConstraintEq{context->working_model}) {}
13072CpSolverStatus CpModelPresolver::InfeasibleStatus() {
13074 return CpSolverStatus::INFEASIBLE;
13081void InitializeMappingModelVariables(absl::Span<const Domain> domains,
13082 std::vector<int>* fixed_postsolve_mapping,
13083 CpModelProto* mapping_proto) {
13086 int old_num_variables = mapping_proto->variables().size();
13087 while (fixed_postsolve_mapping->size() < domains.size()) {
13088 mapping_proto->add_variables();
13089 fixed_postsolve_mapping->push_back(old_num_variables++);
13090 DCHECK_EQ(old_num_variables, mapping_proto->variables().size());
13098 for (
int i = 0;
i < domains.size(); ++
i) {
13100 (*fixed_postsolve_mapping)[
i]));
13109 auto mapping_function = [fixed_postsolve_mapping](
int* ref) {
13110 const int image = (*fixed_postsolve_mapping)[
PositiveRef(*ref)];
13111 CHECK_GE(image, 0);
13114 for (ConstraintProto& ct_ref : *mapping_proto->mutable_constraints()) {
13121void CpModelPresolver::ExpandCpModelAndCanonicalizeConstraints() {
13122 const int num_constraints_before_expansion =
13123 context_->working_model->constraints_size();
13125 if (context_->ModelIsUnsat())
return;
13131 const int num_constraints = context_->working_model->constraints().size();
13132 for (
int c = num_constraints_before_expansion;
c < num_constraints; ++
c) {
13133 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13134 const auto type = ct->constraint_case();
13135 if (type == ConstraintProto::kAtMostOne ||
13136 type == ConstraintProto::kExactlyOne) {
13138 context_->UpdateConstraintVariableUsage(c);
13140 if (context_->ModelIsUnsat())
return;
13141 }
else if (type == ConstraintProto::kLinear) {
13142 if (CanonicalizeLinear(ct)) {
13143 context_->UpdateConstraintVariableUsage(c);
13153 CpModelProto* proto = context->working_model;
13154 if (!proto->has_solution_hint())
return;
13155 if (context->ModelIsUnsat())
return;
13158 const int num_vars = context->working_model->variables().size();
13159 for (
int i = 0;
i < num_vars; ++
i) {
13162 crush.SetOrUpdateVarToDomain(
i, context->DomainOf(
i));
13166 for (
int i = 0;
i < num_vars; ++
i) {
13167 const auto relation = context->GetAffineRelation(
i);
13168 if (relation.representative !=
i) {
13169 crush.SetVarToLinearExpression(
13170 i, {{relation.representative, relation.coeff}}, relation.offset);
13194 context_->InitializeNewDomains();
13201 if (context_->working_model->has_floating_point_objective()) {
13202 context_->WriteVariableDomainsToProto();
13204 context_->working_model)) {
13206 "The floating point objective cannot be scaled with enough "
13208 return CpSolverStatus::MODEL_INVALID;
13215 *context_->mapping_model->mutable_objective() =
13216 context_->working_model->objective();
13226 std::vector<int> fixed_postsolve_mapping;
13227 if (!MaybeRemoveFixedVariables(&fixed_postsolve_mapping)) {
13228 return InfeasibleStatus();
13247 if (!context_->
params().cp_model_presolve()) {
13248 for (ConstraintProto& ct :
13250 if (ct.constraint_case() == ConstraintProto::kLinear) {
13255 if (!solution_crush_.SolutionIsLoaded()) {
13256 context_->LoadSolutionHint();
13258 ExpandCpModelAndCanonicalizeConstraints();
13259 UpdateHintInProto(context_);
13260 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13264 if (context_->working_model->has_objective()) {
13265 context_->WriteObjectiveToProto();
13269 EncodeAllAffineRelations();
13275 context_->WriteVariableDomainsToProto();
13276 InitializeMappingModelVariables(context_->AllDomains(),
13277 &fixed_postsolve_mapping,
13278 context_->mapping_model);
13283 if (!context_->mapping_model->constraints().empty()) {
13284 context_->UpdateRuleStats(
13285 "TODO: mapping model not empty with presolve disabled");
13286 context_->working_model->mutable_constraints()->MergeFrom(
13287 context_->mapping_model->constraints());
13288 context_->mapping_model->clear_constraints();
13291 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13292 return CpSolverStatus::UNKNOWN;
13297 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13298 for (
int var = 0; var < context_->working_model->variables().size(); ++var) {
13299 if (context_->VariableIsNotUsedAnymore(var))
continue;
13300 if (!PresolveAffineRelationIfAny(var))
return InfeasibleStatus();
13305 TryToSimplifyDomain(var);
13306 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13307 context_->UpdateNewConstraintsVariableUsage();
13309 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
13312 for (
int iter = 0; iter < context_->params().max_presolve_iterations();
13314 if (time_limit_->LimitReached())
break;
13315 context_->UpdateRuleStats(
"presolve: iteration");
13316 const int64_t old_num_presolve_op = context_->num_presolve_operations;
13319 if (!PropagateObjective())
return InfeasibleStatus();
13325 PresolveToFixPoint();
13328 if (!context_->ModelIsExpanded()) {
13329 ExtractEncodingFromLinear();
13330 ExpandCpModelAndCanonicalizeConstraints();
13331 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13334 const int num_vars = context_->working_model->variables().size();
13335 for (
int var = 0; var < num_vars; ++var) {
13336 if (context_->VarToConstraints(var).size() <= 3) {
13337 context_->var_with_reduced_small_degree.Set(var);
13341 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13352 if (context_->params().symmetry_level() > 0 && !context_->ModelIsUnsat() &&
13353 !time_limit_->LimitReached()) {
13357 DetectDuplicateConstraints();
13358 if (context_->params().keep_symmetry_in_presolve()) {
13360 if (!context_->working_model->has_symmetry()) {
13362 context_->working_model, logger_);
13371 if (!context_->working_model->has_symmetry()) {
13372 context_->working_model->mutable_symmetry()->Clear();
13374 }
else if (!context_->params()
13375 .keep_all_feasible_solutions_in_presolve()) {
13383 if (context_->params().cp_model_use_sat_presolve()) {
13384 if (!time_limit_->LimitReached()) {
13385 if (!PresolvePureSatPart()) {
13386 (void)context_->NotifyThatModelIsUnsat(
13387 "Proven Infeasible during SAT presolve");
13388 return InfeasibleStatus();
13401 if (!context_->ModelIsUnsat() && iter == 0) {
13402 const int old_size = context_->working_model->constraints_size();
13403 for (
int c = 0;
c < old_size; ++
c) {
13404 ConstraintProto* ct = context_->working_model->mutable_constraints(c);
13405 if (ct->constraint_case() != ConstraintProto::kLinear)
continue;
13406 ExtractAtMostOneFromLinear(ct);
13408 context_->UpdateNewConstraintsVariableUsage();
13411 if (context_->params().cp_model_probing_level() > 0) {
13412 if (!time_limit_->LimitReached()) {
13414 PresolveToFixPoint();
13417 TransformIntoMaxCliques();
13424 ProcessAtMostOneAndLinear();
13425 DetectDuplicateConstraints();
13426 DetectDuplicateConstraintsWithDifferentEnforcements();
13427 DetectDominatedLinearConstraints();
13433 if (context_->params().find_big_linear_overlap() &&
13434 !context_->params().keep_symmetry_in_presolve()) {
13435 FindAlmostIdenticalLinearConstraints();
13437 ActivityBoundHelper activity_amo_helper;
13438 activity_amo_helper.AddAllAtMostOnes(*context_->working_model);
13439 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
13444 FindBigVerticalLinearOverlap(&activity_amo_helper);
13445 FindBigHorizontalLinearOverlap(&activity_amo_helper);
13447 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13450 if (!time_limit_->LimitReached()) {
13454 if ( (
false)) DetectIncludedEnforcement();
13459 ConvertToBoolAnd();
13463 PresolveToFixPoint();
13468 const int64_t num_ops =
13469 context_->num_presolve_operations - old_num_presolve_op;
13470 if (num_ops == 0)
break;
13472 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13475 MergeNoOverlapConstraints();
13476 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13480 if (context_->working_model->has_objective()) {
13481 if (!context_->params().keep_symmetry_in_presolve()) {
13483 if (!context_->modified_domains.PositionsSetAtLeastOnce().empty()) {
13486 PresolveToFixPoint();
13488 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13489 ShiftObjectiveWithExactlyOnes();
13490 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13494 if (!context_->CanonicalizeObjective())
return InfeasibleStatus();
13495 context_->WriteObjectiveToProto();
13500 for (
int c = 0;
c < context_->working_model->constraints_size(); ++
c) {
13501 ConstraintProto& ct = *context_->working_model->mutable_constraints(c);
13502 bool need_canonicalize =
false;
13503 if (ct.constraint_case() == ConstraintProto::kLinear) {
13504 for (
const int v : ct.linear().vars()) {
13505 if (context_->IsFixed(v)) {
13506 need_canonicalize =
true;
13511 if (need_canonicalize) {
13512 if (CanonicalizeLinear(&ct)) {
13513 context_->UpdateConstraintVariableUsage(c);
13522 EncodeAllAffineRelations();
13523 if (context_->ModelIsUnsat())
return InfeasibleStatus();
13526 if (context_->working_model->has_symmetry()) {
13528 context_->working_model->mutable_symmetry(), context_)) {
13529 return InfeasibleStatus();
13541 absl::flat_hash_set<int> used_variables;
13542 for (DecisionStrategyProto& strategy :
13543 *context_->working_model->mutable_search_strategy()) {
13544 CHECK(strategy.variables().empty());
13545 if (strategy.exprs().empty())
continue;
13548 ConstraintProto empy_enforcement;
13549 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
13550 CanonicalizeLinearExpression(empy_enforcement, &expr);
13555 for (
const LinearExpressionProto& expr : strategy.exprs()) {
13556 if (context_->IsFixed(expr))
continue;
13558 const auto [_, inserted] = used_variables.insert(expr.vars(0));
13559 if (!inserted)
continue;
13561 *strategy.mutable_exprs(new_size++) = expr;
13567 context_->WriteVariableDomainsToProto();
13568 InitializeMappingModelVariables(context_->AllDomains(),
13569 &fixed_postsolve_mapping,
13570 context_->mapping_model);
13573 postsolve_mapping_->clear();
13574 std::vector<int> mapping(context_->working_model->variables_size(), -1);
13575 absl::flat_hash_map<int64_t, int> constant_to_index;
13576 int num_unused_variables = 0;
13577 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
13578 if (mapping[
i] != -1)
continue;
13580 if (context_->VariableWasRemoved(
i)) {
13585 const int r =
PositiveRef(context_->GetAffineRelation(
i).representative);
13586 if (mapping[r] == -1 && !context_->VariableIsNotUsedAnymore(r)) {
13587 mapping[r] = postsolve_mapping_->size();
13588 postsolve_mapping_->push_back(fixed_postsolve_mapping[r]);
13597 if (context_->VariableIsNotUsedAnymore(
i) &&
13598 (!context_->params().keep_all_feasible_solutions_in_presolve() ||
13599 context_->IsFixed(
i))) {
13605 ++num_unused_variables;
13607 context_->mapping_model->mutable_variables(
13608 fixed_postsolve_mapping[
i]));
13614 if (context_->IsFixed(
i)) {
13615 auto [it, inserted] = constant_to_index.insert(
13616 {context_->FixedValue(
i), postsolve_mapping_->size()});
13618 mapping[
i] = it->second;
13623 mapping[
i] = postsolve_mapping_->size();
13624 postsolve_mapping_->push_back(fixed_postsolve_mapping[
i]);
13626 context_->UpdateRuleStats(absl::StrCat(
"presolve: ", num_unused_variables,
13627 " unused variables removed."));
13629 if (context_->params().permute_variable_randomly()) {
13631 const int n = postsolve_mapping_->size();
13632 std::vector<int> perm(n);
13633 std::iota(perm.begin(), perm.end(), 0);
13634 std::shuffle(perm.begin(), perm.end(), *context_->random());
13635 for (
int i = 0;
i < context_->working_model->variables_size(); ++
i) {
13636 if (mapping[
i] != -1) mapping[
i] = perm[mapping[
i]];
13638 std::vector<int> new_postsolve_mapping(n);
13639 for (
int i = 0;
i < n; ++
i) {
13640 new_postsolve_mapping[perm[
i]] = (*postsolve_mapping_)[
i];
13642 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13645 DCHECK(context_->ConstraintVariableUsageIsConsistent());
13646 UpdateHintInProto(context_);
13647 const int old_size = postsolve_mapping_->size();
13649 context_->working_model);
13650 CHECK_EQ(old_size, postsolve_mapping_->size());
13656 if (context_->deductions.NumDeductions() > 0) {
13657 context_->UpdateRuleStats(absl::StrCat(
13658 "deductions: ", context_->deductions.NumDeductions(),
" stored"));
13662 if (logger_->LoggingIsEnabled()) context_->LogInfo();
13668 const std::string error =
13670 if (!error.empty()) {
13671 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
13672 return CpSolverStatus::MODEL_INVALID;
13677 if (!error.empty()) {
13679 "Error while validating mapping_model model: ", error);
13680 return CpSolverStatus::MODEL_INVALID;
13684 return CpSolverStatus::UNKNOWN;
13688 std::vector<int>* reverse_mapping,
13689 CpModelProto* proto) {
13692 auto mapping_function = [mapping, reverse_mapping](
int* ref)
mutable {
13694 int image = mapping[var];
13697 image = mapping[var] = reverse_mapping->size();
13698 reverse_mapping->push_back(var);
13702 for (ConstraintProto& ct_ref : *proto->mutable_constraints()) {
13708 if (proto->has_objective()) {
13709 for (
int& mutable_ref : *proto->mutable_objective()->mutable_vars()) {
13710 mapping_function(&mutable_ref);
13715 for (
int& mutable_ref : *proto->mutable_assumptions()) {
13716 mapping_function(&mutable_ref);
13721 if (proto->has_symmetry()) {
13722 for (SparsePermutationProto& generator :
13723 *proto->mutable_symmetry()->mutable_permutations()) {
13724 for (
int& var : *generator.mutable_support()) {
13725 mapping_function(&var);
13730 proto->mutable_symmetry()->clear_orbitopes();
13738 for (DecisionStrategyProto& strategy : *proto->mutable_search_strategy()) {
13740 for (LinearExpressionProto expr : strategy.exprs()) {
13741 DCHECK_EQ(expr.vars().size(), 1);
13742 const int image = mapping[expr.vars(0)];
13744 expr.set_vars(0, image);
13745 *strategy.mutable_exprs(new_size++) = expr;
13754 for (
const DecisionStrategyProto& strategy : proto->search_strategy()) {
13755 if (strategy.exprs().empty())
continue;
13756 *proto->mutable_search_strategy(new_size++) = strategy;
13763 if (proto->has_solution_hint()) {
13764 auto* mutable_hint = proto->mutable_solution_hint();
13769 absl::flat_hash_set<int> hinted_images;
13772 const int old_size = mutable_hint->vars().size();
13773 for (
int i = 0;
i < old_size; ++
i) {
13774 const int hinted_var = mutable_hint->vars(
i);
13775 const int64_t hinted_value = mutable_hint->values(
i);
13776 const int image = mapping[hinted_var];
13778 if (!hinted_images.insert(image).second)
continue;
13779 mutable_hint->set_vars(new_size, image);
13780 mutable_hint->set_values(new_size, hinted_value);
13784 mutable_hint->mutable_vars()->Truncate(new_size);
13785 mutable_hint->mutable_values()->Truncate(new_size);
13789 google::protobuf::RepeatedPtrField<IntegerVariableProto>
13790 new_variables_storage;
13791 google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
13792 if (proto->GetArena() ==
nullptr) {
13793 new_variables = &new_variables_storage;
13795 new_variables = google::protobuf::Arena::Create<
13796 google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
13797 proto->GetArena());
13799 for (
int i = 0;
i < mapping.size(); ++
i) {
13800 const int image = mapping[
i];
13801 if (image < 0)
continue;
13802 while (image >= new_variables->size()) {
13803 new_variables->Add();
13805 (*new_variables)[image].Swap(proto->mutable_variables(
i));
13807 proto->mutable_variables()->Swap(new_variables);
13810 for (
const IntegerVariableProto& v : proto->variables()) {
13811 CHECK_GT(v.domain_size(), 0);
13815bool CpModelPresolver::MaybeRemoveFixedVariables(
13816 std::vector<int>* postsolve_mapping) {
13817 postsolve_mapping->clear();
13818 if (!context_->
params().remove_fixed_variables_early())
return true;
13819 if (!context_->
params().cp_model_presolve())
return true;
13828 const int num_vars = context_->
working_model->variables().size();
13829 std::vector<int> mapping(num_vars, -1);
13830 for (
int i = 0;
i < num_vars; ++
i) {
13831 if (context_->
IsFixed(
i))
continue;
13832 mapping[
i] = postsolve_mapping->size();
13833 postsolve_mapping->push_back(
i);
13837 const int num_fixed = num_vars - postsolve_mapping->size();
13838 if (num_fixed < 1000 || num_fixed * 2 <= num_vars) {
13839 postsolve_mapping->clear();
13846 if (context_->working_model->has_objective()) {
13847 context_->ReadObjectiveFromProto();
13848 if (!context_->CanonicalizeObjective())
return false;
13849 if (!PropagateObjective())
return false;
13850 if (context_->ModelIsUnsat())
return false;
13851 context_->WriteObjectiveToProto();
13856 context_->WriteVariableDomainsToProto();
13857 *context_->mapping_model->mutable_variables() =
13858 context_->working_model->variables();
13861 context_->ResetAfterCopy();
13863 SOLVER_LOG(logger_,
"Large number of fixed variables ",
13865 ", doing a first remapping phase to go down to ",
13870 const int old_size = postsolve_mapping->size();
13872 context_->working_model);
13873 if (postsolve_mapping->size() > old_size) {
13874 const int new_extra = postsolve_mapping->size() - old_size;
13876 " fixed variables still required in the model!");
13884ConstraintProto CopyObjectiveForDuplicateDetection(
13885 const CpObjectiveProto& objective) {
13886 ConstraintProto copy;
13887 *copy.mutable_linear()->mutable_vars() = objective.vars();
13888 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
13892struct ConstraintHashForDuplicateDetection {
13893 const CpModelProto* working_model;
13894 bool ignore_enforcement;
13895 ConstraintProto objective_constraint;
13897 ConstraintHashForDuplicateDetection(
const CpModelProto* working_model,
13898 bool ignore_enforcement)
13899 : working_model(working_model),
13900 ignore_enforcement(ignore_enforcement),
13901 objective_constraint(
13902 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13907 std::size_t operator()(
int ct_idx)
const {
13909 ? objective_constraint
13910 : working_model->constraints(ct_idx);
13911 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
13912 type_and_enforcement = {ct.constraint_case(),
13914 ? absl::Span<const int>()
13915 : absl::MakeSpan(ct.enforcement_literal())};
13916 switch (ct.constraint_case()) {
13917 case ConstraintProto::kLinear:
13918 if (ignore_enforcement) {
13919 return absl::HashOf(type_and_enforcement,
13920 absl::MakeSpan(ct.linear().vars()),
13921 absl::MakeSpan(ct.linear().coeffs()),
13922 absl::MakeSpan(ct.linear().domain()));
13926 return absl::HashOf(type_and_enforcement,
13927 absl::MakeSpan(ct.linear().vars()),
13928 absl::MakeSpan(ct.linear().coeffs()));
13930 case ConstraintProto::kBoolAnd:
13931 return absl::HashOf(type_and_enforcement,
13932 absl::MakeSpan(ct.bool_and().literals()));
13933 case ConstraintProto::kBoolOr:
13934 return absl::HashOf(type_and_enforcement,
13935 absl::MakeSpan(ct.bool_or().literals()));
13936 case ConstraintProto::kAtMostOne:
13937 return absl::HashOf(type_and_enforcement,
13938 absl::MakeSpan(ct.at_most_one().literals()));
13939 case ConstraintProto::kExactlyOne:
13940 return absl::HashOf(type_and_enforcement,
13941 absl::MakeSpan(ct.exactly_one().literals()));
13943 ConstraintProto copy = ct;
13945 if (ignore_enforcement) {
13946 copy.mutable_enforcement_literal()->Clear();
13948 return absl::HashOf(copy.SerializeAsString());
13953struct ConstraintEqForDuplicateDetection {
13954 const CpModelProto* working_model;
13955 bool ignore_enforcement;
13956 ConstraintProto objective_constraint;
13958 ConstraintEqForDuplicateDetection(
const CpModelProto* working_model,
13959 bool ignore_enforcement)
13960 : working_model(working_model),
13961 ignore_enforcement(ignore_enforcement),
13962 objective_constraint(
13963 CopyObjectiveForDuplicateDetection(working_model->objective())) {}
13965 bool operator()(
int a,
int b)
const {
13970 ? objective_constraint
13971 : working_model->constraints(a);
13973 ? objective_constraint
13974 : working_model->constraints(
b);
13976 if (ct_a.constraint_case() != ct_b.constraint_case())
return false;
13977 if (!ignore_enforcement) {
13978 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
13979 absl::MakeSpan(ct_b.enforcement_literal())) {
13983 switch (ct_a.constraint_case()) {
13984 case ConstraintProto::kLinear:
13987 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
13988 absl::MakeSpan(ct_b.linear().domain())) {
13991 return absl::MakeSpan(ct_a.linear().vars()) ==
13992 absl::MakeSpan(ct_b.linear().vars()) &&
13993 absl::MakeSpan(ct_a.linear().coeffs()) ==
13994 absl::MakeSpan(ct_b.linear().coeffs());
13995 case ConstraintProto::kBoolAnd:
13996 return absl::MakeSpan(ct_a.bool_and().literals()) ==
13997 absl::MakeSpan(ct_b.bool_and().literals());
13998 case ConstraintProto::kBoolOr:
13999 return absl::MakeSpan(ct_a.bool_or().literals()) ==
14000 absl::MakeSpan(ct_b.bool_or().literals());
14001 case ConstraintProto::kAtMostOne:
14002 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
14003 absl::MakeSpan(ct_b.at_most_one().literals());
14004 case ConstraintProto::kExactlyOne:
14005 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
14006 absl::MakeSpan(ct_b.exactly_one().literals());
14009 ConstraintProto copy_a = ct_a;
14010 ConstraintProto copy_b = ct_b;
14011 copy_a.clear_name();
14012 copy_b.clear_name();
14013 if (ignore_enforcement) {
14014 copy_a.mutable_enforcement_literal()->Clear();
14015 copy_b.mutable_enforcement_literal()->Clear();
14017 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
14025 const CpModelProto& model_proto,
bool ignore_enforcement) {
14026 std::vector<std::pair<int, int>> result;
14030 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
14031 ConstraintEqForDuplicateDetection>
14033 model_proto.constraints_size(),
14034 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
14035 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
14038 if (model_proto.has_objective() && !ignore_enforcement) {
14042 const int num_constraints = model_proto.constraints().size();
14043 for (
int c = 0; c < num_constraints; ++c) {
14044 const auto type = model_proto.constraints(c).constraint_case();
14045 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
14049 if (type == ConstraintProto::kInterval)
continue;
14052 if (ignore_enforcement && type == ConstraintProto::kBoolAnd)
continue;
14054 const auto [it, inserted] = equiv_constraints.insert({c, c});
14055 if (it->second != c) {
14057 result.push_back({c, it->second});
14065bool SimpleLinearExprEq(
const LinearExpressionProto& a,
14066 const LinearExpressionProto&
b) {
14067 return absl::MakeSpan(a.vars()) == absl::MakeSpan(
b.vars()) &&
14068 absl::MakeSpan(a.coeffs()) == absl::MakeSpan(
b.coeffs()) &&
14069 a.offset() ==
b.offset();
14072std::size_t LinearExpressionHash(
const LinearExpressionProto& expr) {
14073 return absl::HashOf(absl::MakeSpan(expr.vars()),
14074 absl::MakeSpan(expr.coeffs()), expr.offset());
14079bool CpModelPresolver::IntervalConstraintEq::operator()(
int a,
int b)
const {
14080 const ConstraintProto& ct_a = working_model->constraints(a);
14081 const ConstraintProto& ct_b = working_model->constraints(
b);
14082 return absl::MakeSpan(ct_a.enforcement_literal()) ==
14083 absl::MakeSpan(ct_b.enforcement_literal()) &&
14084 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
14085 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
14086 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
14089std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
14090 int ct_idx)
const {
14091 const ConstraintProto& ct = working_model->constraints(ct_idx);
14092 return absl::HashOf(absl::MakeSpan(ct.enforcement_literal()),
14093 LinearExpressionHash(ct.interval().start()),
14094 LinearExpressionHash(ct.interval().size()),
14095 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 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)
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
bool IsFixed(int ref) 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 UpdateConstraintVariableUsage(int c)
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)
void ApplyVariableMapping(absl::Span< int > mapping, std::vector< int > *reverse_mapping, CpModelProto *proto)
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.
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 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)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
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)
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, CpModelProto *proto, SolverLogger *logger)
Detects symmetries and fill the symmetry field.
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)
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 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)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
absl::StatusOr< std::vector< int > > 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,...)