30#include "absl/algorithm/container.h"
31#include "absl/base/attributes.h"
32#include "absl/container/btree_map.h"
33#include "absl/container/btree_set.h"
34#include "absl/container/flat_hash_map.h"
35#include "absl/container/flat_hash_set.h"
36#include "absl/hash/hash.h"
37#include "absl/log/check.h"
38#include "absl/meta/type_traits.h"
39#include "absl/numeric/int128.h"
40#include "absl/random/distributions.h"
41#include "absl/status/statusor.h"
42#include "absl/strings/str_cat.h"
43#include "absl/types/span.h"
44#include "google/protobuf/repeated_field.h"
45#include "google/protobuf/text_format.h"
58#include "ortools/sat/cp_model.pb.h"
74#include "ortools/sat/sat_parameters.pb.h"
94bool LinearConstraintIsClean(
const LinearConstraintProto& linear) {
95 const int num_vars = linear.vars().size();
96 for (
int i = 0;
i < num_vars; ++
i) {
98 if (linear.coeffs(
i) == 0)
return false;
105bool CpModelPresolver::RemoveConstraint(ConstraintProto*
ct) {
116 interval_representative_.clear();
117 std::vector<int> interval_mapping(context_->
working_model->constraints_size(),
119 int new_num_constraints = 0;
120 const int old_num_non_empty_constraints =
122 for (
int c = 0; c < old_num_non_empty_constraints; ++c) {
123 const auto type = context_->
working_model->constraints(c).constraint_case();
124 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
125 if (type == ConstraintProto::kDummyConstraint)
continue;
126 context_->
working_model->mutable_constraints(new_num_constraints)
128 if (type == ConstraintProto::kInterval) {
132 const auto [it, inserted] = interval_representative_.insert(
133 {new_num_constraints, new_num_constraints});
134 interval_mapping[c] = it->second;
135 if (it->second != new_num_constraints) {
137 "intervals: change duplicate index across constraints");
141 new_num_constraints++;
144 context_->
working_model->mutable_constraints(), new_num_constraints);
145 for (ConstraintProto& ct_ref :
148 [&interval_mapping](
int* ref) {
149 *ref = interval_mapping[*ref];
156bool CpModelPresolver::PresolveEnforcementLiteral(ConstraintProto*
ct) {
161 const int old_size =
ct->enforcement_literal().size();
163 for (
const int literal :
ct->enforcement_literal()) {
172 return RemoveConstraint(
ct);
179 return RemoveConstraint(
ct);
186 const int64_t obj_coeff =
190 context_->
UpdateRuleStats(
"enforcement: literal with unique direction");
192 return RemoveConstraint(
ct);
208 return RemoveConstraint(
ct);
212 ct->set_enforcement_literal(new_size++,
literal);
214 ct->mutable_enforcement_literal()->Truncate(new_size);
215 return new_size != old_size;
218bool CpModelPresolver::PresolveBoolXor(ConstraintProto*
ct) {
223 bool changed =
false;
224 int num_true_literals = 0;
225 int true_literal = std::numeric_limits<int32_t>::min();
226 for (
const int literal :
ct->bool_xor().literals()) {
247 ct->mutable_bool_xor()->set_literals(new_size++,
literal);
251 if (num_true_literals % 2 == 0) {
255 return RemoveConstraint(
ct);
257 }
else if (new_size == 1) {
258 if (num_true_literals % 2 == 0) {
261 "bool_xor: cannot fix last literal");
266 "bool_xor: cannot fix last literal");
270 return RemoveConstraint(
ct);
271 }
else if (new_size == 2) {
272 const int a =
ct->bool_xor().literals(0);
273 const int b =
ct->bool_xor().literals(1);
275 if (num_true_literals % 2 == 0) {
279 return RemoveConstraint(
ct);
283 if (num_true_literals % 2 == 1) {
287 return RemoveConstraint(
ct);
290 if (num_true_literals % 2 == 0) {
297 return RemoveConstraint(
ct);
300 if (num_true_literals % 2 == 1) {
301 CHECK_NE(true_literal, std::numeric_limits<int32_t>::min());
302 ct->mutable_bool_xor()->set_literals(new_size++, true_literal);
304 if (num_true_literals > 1) {
305 context_->
UpdateRuleStats(
"bool_xor: remove even number of true literals");
308 ct->mutable_bool_xor()->mutable_literals()->Truncate(new_size);
312bool CpModelPresolver::PresolveBoolOr(ConstraintProto*
ct) {
319 for (
const int literal :
ct->enforcement_literal()) {
322 ct->clear_enforcement_literal();
330 bool changed =
false;
333 for (
const int literal :
ct->bool_or().literals()) {
340 return RemoveConstraint(
ct);
348 return RemoveConstraint(
ct);
352 return RemoveConstraint(
ct);
371 return RemoveConstraint(
ct);
384 ct->mutable_bool_or()->mutable_literals()->Clear();
386 ct->mutable_bool_or()->add_literals(
lit);
394ABSL_MUST_USE_RESULT
bool CpModelPresolver::MarkConstraintAsFalse(
395 ConstraintProto*
ct) {
398 ct->mutable_bool_or()->clear_literals();
399 for (
const int lit :
ct->enforcement_literal()) {
402 ct->clear_enforcement_literal();
410bool CpModelPresolver::PresolveBoolAnd(ConstraintProto*
ct) {
415 for (
const int literal :
ct->bool_and().literals()) {
418 return RemoveConstraint(
ct);
421 bool changed =
false;
424 const absl::flat_hash_set<int> enforcement_literals_set(
425 ct->enforcement_literal().begin(),
ct->enforcement_literal().end());
426 for (
const int literal :
ct->bool_and().literals()) {
429 return MarkConstraintAsFalse(
ct);
435 if (enforcement_literals_set.contains(
literal)) {
442 return MarkConstraintAsFalse(
ct);
452 return MarkConstraintAsFalse(
ct);
470 ct->mutable_bool_and()->mutable_literals()->Clear();
472 ct->mutable_bool_and()->add_literals(
lit);
481 if (
ct->enforcement_literal().size() == 1 &&
482 ct->bool_and().literals().size() == 1) {
483 const int enforcement =
ct->enforcement_literal(0);
493 ct->bool_and().literals(0));
501bool CpModelPresolver::PresolveAtMostOrExactlyOne(ConstraintProto*
ct) {
502 bool is_at_most_one =
ct->constraint_case() == ConstraintProto::kAtMostOne;
503 const std::string
name = is_at_most_one ?
"at_most_one: " :
"exactly_one: ";
504 auto* literals = is_at_most_one
505 ?
ct->mutable_at_most_one()->mutable_literals()
506 :
ct->mutable_exactly_one()->mutable_literals();
510 std::sort(literals->begin(), literals->end());
514 for (
const int literal : *literals) {
521 int num_positive = 0;
522 int num_negative = 0;
523 for (
const int other : *literals) {
544 return RemoveConstraint(
ct);
550 std::vector<std::pair<int, int64_t>> singleton_literal_with_cost;
553 bool changed =
false;
555 for (
const int literal : *literals) {
558 for (
const int other : *literals) {
563 return RemoveConstraint(
ct);
573 singleton_literal_with_cost.push_back({
literal, 0});
580 singleton_literal_with_cost.push_back({
literal, it->second});
584 singleton_literal_with_cost.push_back({
literal, -it->second});
592 bool transform_to_at_most_one =
false;
593 if (!singleton_literal_with_cost.empty()) {
597 if (singleton_literal_with_cost.size() > 1) {
599 singleton_literal_with_cost.begin(),
600 singleton_literal_with_cost.end(),
601 [](
const std::pair<int, int64_t>&
a,
602 const std::pair<int, int64_t>&
b) { return a.second < b.second; });
603 for (
int i = 1;
i < singleton_literal_with_cost.size(); ++
i) {
606 singleton_literal_with_cost[
i].first)) {
610 singleton_literal_with_cost.resize(1);
613 const int literal = singleton_literal_with_cost[0].first;
614 const int64_t literal_cost = singleton_literal_with_cost[0].second;
615 if (is_at_most_one && literal_cost >= 0) {
628 if (!is_at_most_one) transform_to_at_most_one =
true;
629 is_at_most_one =
true;
636 ->mutable_exactly_one();
638 mapping_exo->add_literals(
lit);
640 mapping_exo->add_literals(
literal);
644 if (!is_at_most_one && !transform_to_at_most_one &&
649 if (transform_to_at_most_one) {
652 literals =
ct->mutable_at_most_one()->mutable_literals();
664bool CpModelPresolver::PresolveAtMostOne(ConstraintProto*
ct) {
668 const bool changed = PresolveAtMostOrExactlyOne(
ct);
669 if (
ct->constraint_case() != ConstraintProto::kAtMostOne)
return changed;
672 const auto& literals =
ct->at_most_one().literals();
673 if (literals.empty()) {
675 return RemoveConstraint(
ct);
679 if (literals.size() == 1) {
681 return RemoveConstraint(
ct);
687bool CpModelPresolver::PresolveExactlyOne(ConstraintProto*
ct) {
690 const bool changed = PresolveAtMostOrExactlyOne(
ct);
691 if (
ct->constraint_case() != ConstraintProto::kExactlyOne)
return changed;
694 const auto& literals =
ct->exactly_one().literals();
695 if (literals.empty()) {
700 if (literals.size() == 1) {
703 return RemoveConstraint(
ct);
707 if (literals.size() == 2) {
711 return RemoveConstraint(
ct);
717bool CpModelPresolver::CanonicalizeLinearArgument(
const ConstraintProto&
ct,
718 LinearArgumentProto*
proto) {
722 bool changed = CanonicalizeLinearExpression(
ct,
proto->mutable_target());
723 for (LinearExpressionProto& exp : *(
proto->mutable_exprs())) {
724 changed |= CanonicalizeLinearExpression(
ct, &exp);
731bool CpModelPresolver::DivideLinMaxByGcd(
int c, ConstraintProto*
ct) {
732 LinearArgumentProto* lin_max =
ct->mutable_lin_max();
736 for (
const LinearExpressionProto& expr : lin_max->exprs()) {
740 if (gcd <= 1)
return true;
744 const LinearExpressionProto& target = lin_max->target();
745 const int64_t old_gcd = gcd;
747 if (gcd != old_gcd) {
748 if (target.vars().empty()) {
754 if (target.vars().size() == 1) {
758 target.vars(0), target.coeffs(0), gcd, -target.offset())) {
761 CanonicalizeLinearExpression(*
ct, lin_max->mutable_target());
766 "TODO lin_max: lhs not trivially divisible by rhs gcd");
769 if (gcd <= 1)
return true;
773 for (LinearExpressionProto& expr : *lin_max->mutable_exprs()) {
781int64_t EvaluateSingleVariableExpression(
const LinearExpressionProto& expr,
783 int64_t result = expr.offset();
784 for (
int i = 0;
i < expr.vars().
size(); ++
i) {
785 CHECK_EQ(expr.vars(
i),
var);
786 result += expr.coeffs(
i) *
value;
791template <
class ExpressionList>
792int GetFirstVar(ExpressionList exprs) {
793 for (
const LinearExpressionProto& expr : exprs) {
794 for (
const int var : expr.vars()) {
802bool IsAffineIntAbs(
const ConstraintProto&
ct) {
803 if (
ct.constraint_case() != ConstraintProto::kLinMax ||
804 ct.lin_max().exprs_size() != 2 ||
ct.lin_max().target().vars_size() > 1 ||
805 ct.lin_max().exprs(0).vars_size() != 1 ||
806 ct.lin_max().exprs(1).vars_size() != 1) {
810 const LinearArgumentProto& lin_max =
ct.lin_max();
811 if (lin_max.exprs(0).offset() != -lin_max.exprs(1).offset())
return false;
817 const int64_t left_coeff =
RefIsPositive(lin_max.exprs(0).vars(0))
818 ? lin_max.exprs(0).coeffs(0)
819 : -lin_max.exprs(0).coeffs(0);
820 const int64_t right_coeff =
RefIsPositive(lin_max.exprs(1).vars(0))
821 ? lin_max.exprs(1).coeffs(0)
822 : -lin_max.exprs(1).coeffs(0);
823 return left_coeff == -right_coeff;
828bool CpModelPresolver::PropagateAndReduceAffineMax(ConstraintProto*
ct) {
830 const int unique_var = GetFirstVar(
ct->lin_max().exprs());
832 const auto& lin_max =
ct->lin_max();
833 const int num_exprs = lin_max.exprs_size();
834 const auto& target = lin_max.target();
835 std::vector<int> num_wins(num_exprs, 0);
836 std::vector<int64_t> reachable_target_values;
837 std::vector<int64_t> valid_variable_values;
838 std::vector<int64_t> tmp_values(num_exprs);
840 const bool target_has_same_unique_var =
841 target.vars_size() == 1 && target.vars(0) == unique_var;
846 int64_t current_max = std::numeric_limits<int64_t>::min();
849 for (
int i = 0;
i < num_exprs; ++
i) {
851 EvaluateSingleVariableExpression(lin_max.exprs(
i), unique_var,
value);
852 current_max = std::max(current_max, v);
861 if (target_has_same_unique_var &&
862 EvaluateSingleVariableExpression(target, unique_var,
value) !=
867 valid_variable_values.push_back(
value);
868 reachable_target_values.push_back(current_max);
869 for (
int i = 0;
i < num_exprs; ++
i) {
870 DCHECK_LE(tmp_values[
i], current_max);
871 if (tmp_values[
i] == current_max) {
877 if (reachable_target_values.empty() || valid_variable_values.empty()) {
878 context_->
UpdateRuleStats(
"lin_max: infeasible affine_max constraint");
879 return MarkConstraintAsFalse(
ct);
883 bool reduced =
false;
889 context_->
UpdateRuleStats(
"lin_max: affine_max target domain reduced");
894 bool reduced =
false;
901 "lin_max: unique affine_max var domain reduced");
906 for (
int i = 0;
i < num_exprs; ++
i) {
907 if (num_wins[
i] == valid_variable_values.size()) {
908 const LinearExpressionProto winner_expr = lin_max.exprs(
i);
909 ct->mutable_lin_max()->clear_exprs();
910 *
ct->mutable_lin_max()->add_exprs() = winner_expr;
915 bool changed =
false;
916 if (
ct->lin_max().exprs_size() > 1) {
918 for (
int i = 0;
i < num_exprs; ++
i) {
919 if (num_wins[
i] == 0)
continue;
920 *
ct->mutable_lin_max()->mutable_exprs(new_size) =
ct->lin_max().exprs(
i);
923 if (new_size < ct->lin_max().exprs_size()) {
931 if (context_->
IsFixed(target)) {
933 return RemoveConstraint(
ct);
936 if (target_has_same_unique_var) {
937 context_->
UpdateRuleStats(
"lin_max: target_affine(x) = max(affine_i(x))");
938 return RemoveConstraint(
ct);
950 return RemoveConstraint(
ct);
956bool CpModelPresolver::PropagateAndReduceLinMax(ConstraintProto*
ct) {
957 const LinearExpressionProto& target =
ct->lin_max().target();
962 int64_t infered_min = context_->
MinOf(target);
963 int64_t infered_max = std::numeric_limits<int64_t>::min();
964 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
965 infered_min = std::max(infered_min, context_->
MinOf(expr));
966 infered_max = std::max(infered_max, context_->
MaxOf(expr));
969 if (target.vars().empty()) {
970 if (!Domain(infered_min, infered_max).Contains(target.offset())) {
972 return MarkConstraintAsFalse(
ct);
975 if (target.vars().size() <= 1) {
977 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
978 rhs_domain = rhs_domain.UnionWith(
980 {infered_min, infered_max}));
982 bool reduced =
false;
993 const int64_t target_min = context_->
MinOf(target);
994 bool changed =
false;
1001 bool has_greater_or_equal_to_target_min =
false;
1002 int64_t max_at_index_to_keep = std::numeric_limits<int64_t>::min();
1003 int index_to_keep = -1;
1004 for (
int i = 0;
i <
ct->lin_max().exprs_size(); ++
i) {
1005 const LinearExpressionProto& expr =
ct->lin_max().exprs(
i);
1006 if (context_->
MinOf(expr) >= target_min) {
1007 const int64_t expr_max = context_->
MaxOf(expr);
1008 if (expr_max > max_at_index_to_keep) {
1009 max_at_index_to_keep = expr_max;
1012 has_greater_or_equal_to_target_min =
true;
1017 for (
int i = 0;
i <
ct->lin_max().exprs_size(); ++
i) {
1018 const LinearExpressionProto& expr =
ct->lin_max().exprs(
i);
1019 const int64_t expr_max = context_->
MaxOf(expr);
1022 if (expr_max < target_min)
continue;
1023 if (expr_max == target_min && has_greater_or_equal_to_target_min &&
1024 i != index_to_keep) {
1027 *
ct->mutable_lin_max()->mutable_exprs(new_size) = expr;
1030 if (new_size < ct->lin_max().exprs_size()) {
1041bool CpModelPresolver::PresolveLinMax(ConstraintProto*
ct) {
1044 const LinearExpressionProto& target =
ct->lin_max().target();
1047 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1049 for (
const LinearExpressionProto& e :
ct->lin_max().exprs()) {
1051 LinearConstraintProto* prec =
1052 context_->
working_model->add_constraints()->mutable_linear();
1053 prec->add_domain(0);
1054 prec->add_domain(std::numeric_limits<int64_t>::max());
1059 return RemoveConstraint(
ct);
1063 const bool is_one_var_affine_max =
1065 ct->lin_max().target().vars_size() <= 1;
1066 bool unique_var_is_small_enough =
false;
1067 const bool is_int_abs = IsAffineIntAbs(*
ct);
1069 if (is_one_var_affine_max) {
1070 const int unique_var = GetFirstVar(
ct->lin_max().exprs());
1071 unique_var_is_small_enough = context_->
DomainOf(unique_var).
Size() <= 1000;
1077 if (is_one_var_affine_max && unique_var_is_small_enough) {
1078 changed = PropagateAndReduceAffineMax(
ct);
1079 }
else if (is_int_abs) {
1080 changed = PropagateAndReduceIntAbs(
ct);
1082 changed = PropagateAndReduceLinMax(
ct);
1086 if (
ct->constraint_case() != ConstraintProto::kLinMax) {
1091 if (
ct->lin_max().exprs().empty()) {
1093 return MarkConstraintAsFalse(
ct);
1098 if (
ct->lin_max().exprs().size() == 1) {
1100 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1102 auto* arg = new_ct->mutable_linear();
1103 const LinearExpressionProto&
a =
ct->lin_max().target();
1104 const LinearExpressionProto&
b =
ct->lin_max().exprs(0);
1105 for (
int i = 0;
i <
a.vars().
size(); ++
i) {
1106 arg->add_vars(
a.vars(
i));
1107 arg->add_coeffs(
a.coeffs(
i));
1109 for (
int i = 0;
i <
b.vars().
size(); ++
i) {
1110 arg->add_vars(
b.vars(
i));
1111 arg->add_coeffs(-
b.coeffs(
i));
1113 arg->add_domain(
b.offset() -
a.offset());
1114 arg->add_domain(
b.offset() -
a.offset());
1116 return RemoveConstraint(
ct);
1122 const int64_t target_min = context_->
MinOf(target);
1123 const int64_t target_max = context_->
MaxOf(target);
1126 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1127 const int64_t value_min = context_->
MinOf(expr);
1128 bool modified =
false;
1136 const int64_t value_max = context_->
MaxOf(expr);
1137 if (value_max > target_max) {
1138 context_->
UpdateRuleStats(
"TODO lin_max: linear expression above max.");
1142 if (abort)
return changed;
1146 bool affine_target_domain_contains_max_domain =
false;
1148 int64_t infered_min = std::numeric_limits<int64_t>::min();
1149 int64_t infered_max = std::numeric_limits<int64_t>::min();
1150 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1151 infered_min = std::max(infered_min, context_->
MinOf(expr));
1152 infered_max = std::max(infered_max, context_->
MaxOf(expr));
1155 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1156 rhs_domain = rhs_domain.UnionWith(
1158 {infered_min, infered_max}));
1164 DCHECK_EQ(std::abs(target.coeffs(0)), 1);
1165 const Domain target_domain =
1166 target.coeffs(0) == 1 ? context_->
DomainOf(target.vars(0))
1168 affine_target_domain_contains_max_domain =
1184 if (affine_target_domain_contains_max_domain) {
1185 const int target_var = target.vars(0);
1187 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1188 for (
const int var : expr.vars()) {
1189 if (
var == target_var &&
1201 "TODO lin_max: affine(x) = max(affine'(x), ...) !!");
1202 affine_target_domain_contains_max_domain =
false;
1207 if (affine_target_domain_contains_max_domain &&
1212 return RemoveConstraint(
ct);
1217 if (affine_target_domain_contains_max_domain &&
1219 (target.coeffs(0) > 0) ==
1222 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1223 LinearConstraintProto* prec =
1224 context_->
working_model->add_constraints()->mutable_linear();
1225 prec->add_domain(0);
1226 prec->add_domain(std::numeric_limits<int64_t>::max());
1231 return RemoveConstraint(
ct);
1235 if (target_min == target_max) {
1236 bool all_booleans =
true;
1237 std::vector<int> literals;
1238 const int64_t fixed_target = target_min;
1239 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1240 const int64_t value_min = context_->
MinOf(expr);
1241 const int64_t value_max = context_->
MaxOf(expr);
1242 CHECK_LE(value_max, fixed_target) <<
"Presolved above";
1243 if (value_max < fixed_target)
continue;
1245 if (value_min == value_max && value_max == fixed_target) {
1247 return RemoveConstraint(
ct);
1250 CHECK_EQ(value_max, fixed_target);
1253 all_booleans =
false;
1257 if (literals.empty()) {
1258 return MarkConstraintAsFalse(
ct);
1263 for (
const int lit : literals) {
1264 ct->mutable_bool_or()->add_literals(
lit);
1271 changed |= PresolveLinMaxWhenAllBoolean(
ct);
1276bool CpModelPresolver::PresolveLinMaxWhenAllBoolean(ConstraintProto*
ct) {
1280 const LinearExpressionProto& target =
ct->lin_max().target();
1283 const int64_t target_min = context_->
MinOf(target);
1284 const int64_t target_max = context_->
MaxOf(target);
1287 bool min_is_reachable =
false;
1288 std::vector<int> min_literals;
1289 std::vector<int> literals_above_min;
1290 std::vector<int> max_literals;
1292 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
1294 const int64_t value_min = context_->
MinOf(expr);
1295 const int64_t value_max = context_->
MaxOf(expr);
1300 if (value_min > target_min) {
1305 if (value_max > target_max) {
1312 if (value_min == value_max) {
1313 if (value_min == target_min) min_is_reachable =
true;
1317 CHECK_LE(value_min, target_min);
1318 if (value_min == target_min) {
1322 CHECK_LE(value_max, target_max);
1323 if (value_max == target_max) {
1324 max_literals.push_back(ref);
1325 literals_above_min.push_back(ref);
1326 }
else if (value_max > target_min) {
1327 literals_above_min.push_back(ref);
1328 }
else if (value_max == target_min) {
1329 min_literals.push_back(ref);
1336 ConstraintProto* clause = context_->
working_model->add_constraints();
1337 clause->add_enforcement_literal(target_ref);
1338 clause->mutable_bool_or();
1339 for (
const int lit : max_literals) {
1340 clause->mutable_bool_or()->add_literals(
lit);
1344 for (
const int lit : literals_above_min) {
1348 if (!min_is_reachable) {
1350 ConstraintProto* clause = context_->
working_model->add_constraints();
1351 clause->add_enforcement_literal(
NegatedRef(target_ref));
1352 clause->mutable_bool_or();
1353 for (
const int lit : min_literals) {
1354 clause->mutable_bool_or()->add_literals(
lit);
1359 return RemoveConstraint(
ct);
1364bool CpModelPresolver::PropagateAndReduceIntAbs(ConstraintProto*
ct) {
1365 CHECK_EQ(
ct->enforcement_literal_size(), 0);
1367 const LinearExpressionProto& target_expr =
ct->lin_max().target();
1368 const LinearExpressionProto& expr =
ct->lin_max().exprs(0);
1369 DCHECK_EQ(expr.vars_size(), 1);
1374 const Domain new_target_domain =
1375 expr_domain.
UnionWith(expr_domain.Negation())
1377 bool target_domain_modified =
false;
1379 &target_domain_modified)) {
1382 if (expr_domain.IsFixed()) {
1384 return RemoveConstraint(
ct);
1386 if (target_domain_modified) {
1387 context_->
UpdateRuleStats(
"lin_max: propagate domain from x to abs(x)");
1393 const Domain target_domain =
1396 const Domain new_expr_domain =
1397 target_domain.
UnionWith(target_domain.Negation());
1398 bool expr_domain_modified =
false;
1400 &expr_domain_modified)) {
1405 if (context_->
IsFixed(target_expr)) {
1407 return RemoveConstraint(
ct);
1409 if (expr_domain_modified) {
1410 context_->
UpdateRuleStats(
"lin_max: propagate domain from abs(x) to x");
1415 if (context_->
MinOf(expr) >= 0) {
1417 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1418 new_ct->set_name(
ct->name());
1419 auto* arg = new_ct->mutable_linear();
1424 CanonicalizeLinear(new_ct);
1426 return RemoveConstraint(
ct);
1429 if (context_->
MaxOf(expr) <= 0) {
1431 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1432 new_ct->set_name(
ct->name());
1433 auto* arg = new_ct->mutable_linear();
1438 CanonicalizeLinear(new_ct);
1440 return RemoveConstraint(
ct);
1452 return RemoveConstraint(
ct);
1458bool CpModelPresolver::PresolveIntProd(ConstraintProto*
ct) {
1463 bool domain_modified =
false;
1466 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1471 &domain_modified)) {
1478 int64_t constant_factor = 1;
1480 bool changed =
false;
1481 LinearArgumentProto*
proto =
ct->mutable_int_prod();
1482 for (
int i = 0;
i <
ct->int_prod().exprs().
size(); ++
i) {
1483 LinearExpressionProto expr =
ct->int_prod().exprs(
i);
1484 if (context_->
IsFixed(expr)) {
1485 const int64_t expr_value = context_->
FixedValue(expr);
1486 constant_factor =
CapProd(constant_factor, expr_value);
1492 constant_factor =
CapProd(constant_factor, expr_divisor);
1493 *
proto->mutable_exprs(new_size++) = expr;
1496 proto->mutable_exprs()->erase(
proto->mutable_exprs()->begin() + new_size,
1497 proto->mutable_exprs()->end());
1499 if (
ct->int_prod().exprs().empty() || constant_factor == 0) {
1501 Domain(constant_factor))) {
1505 return RemoveConstraint(
ct);
1509 if (context_->
IsFixed(
ct->int_prod().target()) &&
1511 constant_factor != 1) {
1513 constant_factor = 1;
1523 constant_factor = 1;
1527 if (
ct->int_prod().exprs().size() == 1) {
1528 LinearExpressionProto*
const target =
1529 ct->mutable_int_prod()->mutable_target();
1530 LinearConstraintProto*
const lin =
1531 context_->
working_model->add_constraints()->mutable_linear();
1533 if (context_->
IsFixed(*target)) {
1534 int64_t target_value = context_->
FixedValue(*target);
1535 if (target_value % constant_factor != 0) {
1537 "int_prod: product incompatible with fixed target");
1540 lin->add_domain(target_value / constant_factor);
1541 lin->add_domain(target_value / constant_factor);
1545 return RemoveConstraint(
ct);
1553 static_cast<uint64_t
>(std::abs(target_divisor)));
1555 constant_factor /= gcd;
1563 ct->int_prod().target(), 1, lin) ||
1565 ct->int_prod().exprs(0), -constant_factor, lin);
1570 lin->coeffs(), lin->domain(0))) {
1571 context_->
working_model->mutable_constraints()->RemoveLast();
1573 ct->mutable_int_prod()->add_exprs()->set_offset(constant_factor);
1576 context_->
UpdateRuleStats(
"int_prod: linearize product by constant.");
1577 return RemoveConstraint(
ct);
1581 if (constant_factor != 1) {
1589 const LinearExpressionProto old_target =
ct->int_prod().target();
1590 if (!context_->
IsFixed(old_target)) {
1591 const int ref = old_target.vars(0);
1592 const int64_t coeff = old_target.coeffs(0);
1593 const int64_t offset = old_target.offset();
1604 if (context_->
IsFixed(old_target)) {
1605 const int64_t target_value = context_->
FixedValue(old_target);
1606 if (target_value % constant_factor != 0) {
1608 "int_prod: constant factor does not divide constant target");
1611 proto->clear_target();
1612 proto->mutable_target()->set_offset(target_value / constant_factor);
1614 "int_prod: divide product and fixed target by constant factor");
1617 const AffineRelation::Relation r =
1619 const absl::int128 temp_coeff =
1620 absl::int128(old_target.coeffs(0)) * absl::int128(r.coeff);
1621 CHECK_EQ(temp_coeff % absl::int128(constant_factor), 0);
1622 const absl::int128 temp_offset =
1623 absl::int128(old_target.coeffs(0)) * absl::int128(r.offset) +
1624 absl::int128(old_target.offset());
1625 CHECK_EQ(temp_offset % absl::int128(constant_factor), 0);
1626 const absl::int128 new_coeff = temp_coeff / absl::int128(constant_factor);
1627 const absl::int128 new_offset =
1628 temp_offset / absl::int128(constant_factor);
1636 if (new_coeff > absl::int128(std::numeric_limits<int64_t>::max()) ||
1637 new_coeff < absl::int128(std::numeric_limits<int64_t>::min()) ||
1638 new_offset > absl::int128(std::numeric_limits<int64_t>::max()) ||
1639 new_offset < absl::int128(std::numeric_limits<int64_t>::min())) {
1641 "int_prod: overflow during simplification.");
1645 proto->mutable_target()->set_coeffs(0,
static_cast<int64_t
>(new_coeff));
1646 proto->mutable_target()->set_vars(0, r.representative);
1647 proto->mutable_target()->set_offset(
static_cast<int64_t
>(new_offset));
1648 context_->
UpdateRuleStats(
"int_prod: divide product by constant factor");
1655 bool is_square =
false;
1656 if (
ct->int_prod().exprs_size() == 2 &&
1658 ct->int_prod().exprs(1))) {
1663 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1669 &domain_modified)) {
1672 if (domain_modified) {
1674 is_square ?
"int_square" :
"int_prod",
": reduced target domain."));
1679 const int64_t target_max = context_->
MaxOf(
ct->int_prod().target());
1680 DCHECK_GE(target_max, 0);
1682 bool expr_reduced =
false;
1684 {-sqrt_max, sqrt_max}, &expr_reduced)) {
1692 if (
ct->int_prod().exprs_size() == 2) {
1693 LinearExpressionProto
a =
ct->int_prod().exprs(0);
1694 LinearExpressionProto
b =
ct->int_prod().exprs(1);
1695 const LinearExpressionProto product =
ct->int_prod().target();
1702 context_->
UpdateRuleStats(
"int_square: fix variable to zero or one.");
1703 return RemoveConstraint(
ct);
1708 const LinearExpressionProto target_expr =
ct->int_prod().target();
1713 std::vector<int> literals;
1714 for (
const LinearExpressionProto& expr :
ct->int_prod().exprs()) {
1719 literals.push_back(
lit);
1725 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1726 new_ct->add_enforcement_literal(target);
1727 auto* arg = new_ct->mutable_bool_and();
1728 for (
const int lit : literals) {
1729 arg->add_literals(
lit);
1733 ConstraintProto* new_ct = context_->
working_model->add_constraints();
1734 auto* arg = new_ct->mutable_bool_or();
1735 arg->add_literals(target);
1736 for (
const int lit : literals) {
1741 return RemoveConstraint(
ct);
1744bool CpModelPresolver::PresolveIntDiv(
int c, ConstraintProto*
ct) {
1747 const LinearExpressionProto target =
ct->int_div().target();
1748 const LinearExpressionProto expr =
ct->int_div().exprs(0);
1749 const LinearExpressionProto div =
ct->int_div().exprs(1);
1756 return RemoveConstraint(
ct);
1762 return RemoveConstraint(
ct);
1768 if (
ct->enforcement_literal().empty() &&
1773 "TODO int_div: single variable with large domain");
1775 std::vector<int64_t> possible_values;
1777 const int64_t target_v =
1778 EvaluateSingleVariableExpression(target,
var, v);
1779 const int64_t expr_v = EvaluateSingleVariableExpression(expr,
var, v);
1780 const int64_t div_v = EvaluateSingleVariableExpression(div,
var, v);
1781 if (div_v == 0)
continue;
1782 if (target_v == expr_v / div_v) {
1783 possible_values.push_back(v);
1789 return RemoveConstraint(
ct);
1794 if (!context_->
IsFixed(div))
return false;
1796 const int64_t divisor = context_->
FixedValue(div);
1799 if (divisor == 1 || divisor == -1) {
1800 LinearConstraintProto*
const lin =
1801 context_->
working_model->add_constraints()->mutable_linear();
1808 return RemoveConstraint(
ct);
1813 bool domain_modified =
false;
1814 const Domain target_implied_domain =
1818 &domain_modified)) {
1821 if (domain_modified) {
1823 if (target_implied_domain.IsFixed()) {
1825 "int_div: target has been fixed by propagating X / cte");
1828 "int_div: updated domain of target in target = X / cte");
1834 if (context_->
IsFixed(target) &&
1836 1 + std::abs(context_->
FixedValue(target)))) !=
1837 std::numeric_limits<int64_t>::max()) {
1839 int64_t d = divisor;
1845 const Domain expr_implied_domain =
1847 ? Domain(t * d, (t + 1) * d - 1)
1848 : (t == 0 ? Domain(1 - d, d - 1) : Domain((t - 1) * d + 1, t * d));
1849 bool domain_modified =
false;
1851 &domain_modified)) {
1854 if (domain_modified) {
1859 return RemoveConstraint(
ct);
1865 if (context_->
MinOf(target) >= 0 && context_->
MinOf(expr) >= 0 &&
1868 std::numeric_limits<int64_t>::max()) {
1869 LinearConstraintProto*
const lin =
1870 context_->
working_model->add_constraints()->mutable_linear();
1872 lin->add_domain(divisor - 1);
1877 "int_div: linearize positive division with a constant divisor");
1879 return RemoveConstraint(
ct);
1887bool CpModelPresolver::PresolveIntMod(
int c, ConstraintProto*
ct) {
1891 const LinearExpressionProto target =
ct->int_mod().target();
1892 const LinearExpressionProto expr =
ct->int_mod().exprs(0);
1893 const LinearExpressionProto mod =
ct->int_mod().exprs(1);
1895 if (context_->
MinOf(target) > 0) {
1896 bool domain_changed =
false;
1898 expr, Domain(0, std::numeric_limits<int64_t>::max()),
1902 if (domain_changed) {
1904 "int_mod: non negative target implies positive expression");
1908 if (context_->
MinOf(target) >= context_->
MaxOf(mod) ||
1909 context_->
MaxOf(target) <= -context_->
MaxOf(mod)) {
1911 "int_mod: incompatible target and mod");
1914 if (context_->
MaxOf(target) < 0) {
1915 bool domain_changed =
false;
1917 expr, Domain(std::numeric_limits<int64_t>::min(), 0),
1921 if (domain_changed) {
1923 "int_mod: non positive target implies negative expression");
1928 context_->
FixedValue(mod) > 1 &&
ct->enforcement_literal().empty() &&
1929 expr.vars().size() == 1) {
1931 const int64_t fixed_mod = context_->
FixedValue(mod);
1932 const int64_t fixed_target = context_->
FixedValue(target);
1936 fixed_target - expr.offset())) {
1941 return RemoveConstraint(
ct);
1944 bool domain_changed =
false;
1953 if (domain_changed) {
1962 if (target.vars().size() == 1 && expr.vars().size() == 1 &&
1965 target.vars(0) != expr.vars(0)) {
1966 const int64_t fixed_mod = context_->
FixedValue(mod);
1967 std::vector<int64_t> values;
1968 const Domain dom = context_->
DomainOf(target.vars(0));
1969 for (
const int64_t v : context_->
DomainOf(expr.vars(0)).
Values()) {
1970 const int64_t rhs = (v * expr.coeffs(0) + expr.offset()) % fixed_mod;
1971 const int64_t target_term = rhs - target.offset();
1972 if (target_term % target.coeffs(0) != 0)
continue;
1973 if (dom.Contains(target_term / target.coeffs(0))) {
1974 values.push_back(v);
1995bool CpModelPresolver::ExploitEquivalenceRelations(
int c, ConstraintProto*
ct) {
1996 bool changed =
false;
2001 if (
ct->constraint_case() == ConstraintProto::kLinear) {
2002 for (
int& ref : *
ct->mutable_enforcement_literal()) {
2014 bool work_to_do =
false;
2017 if (r.representative !=
var) {
2022 if (!work_to_do)
return false;
2026 [&changed,
this](
int* ref) {
2037bool CpModelPresolver::DivideLinearByGcd(ConstraintProto*
ct) {
2042 const int num_vars =
ct->linear().vars().size();
2043 for (
int i = 0;
i < num_vars; ++
i) {
2044 const int64_t magnitude = std::abs(
ct->linear().coeffs(
i));
2046 if (gcd == 1)
break;
2050 for (
int i = 0;
i < num_vars; ++
i) {
2051 ct->mutable_linear()->set_coeffs(
i,
ct->linear().coeffs(
i) / gcd);
2055 if (
ct->linear().domain_size() == 0) {
2056 return MarkConstraintAsFalse(
ct);
2062bool CpModelPresolver::CanonicalizeLinearExpression(
2063 const ConstraintProto&
ct, LinearExpressionProto* exp) {
2067bool CpModelPresolver::CanonicalizeLinear(ConstraintProto*
ct) {
2068 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2071 if (
ct->linear().domain().empty()) {
2073 return MarkConstraintAsFalse(
ct);
2077 changed |= DivideLinearByGcd(
ct);
2083 if (!
ct->linear().coeffs().empty() &&
ct->linear().coeffs(0) < 0) {
2084 for (int64_t& ref_coeff : *
ct->mutable_linear()->mutable_coeffs()) {
2085 ref_coeff = -ref_coeff;
2088 ct->mutable_linear());
2094bool CpModelPresolver::RemoveSingletonInLinear(ConstraintProto*
ct) {
2095 if (
ct->constraint_case() != ConstraintProto::kLinear ||
2100 absl::btree_set<int> index_to_erase;
2101 const int num_vars =
ct->linear().vars().size();
2107 for (
int i = 0;
i < num_vars; ++
i) {
2108 const int var =
ct->linear().vars(
i);
2109 const int64_t coeff =
ct->linear().coeffs(
i);
2116 if (std::abs(coeff) != 1)
continue;
2119 const auto term_domain =
2121 if (!exact)
continue;
2125 if (new_rhs.NumIntervals() > 100)
continue;
2132 index_to_erase.insert(
i);
2145 if (index_to_erase.empty()) {
2146 int num_singletons = 0;
2147 for (
const int var :
ct->linear().vars()) {
2155 if (num_singletons == num_vars) {
2157 std::vector<Domain> domains;
2158 std::vector<int64_t> coeffs;
2159 std::vector<int64_t> costs;
2160 for (
int i = 0;
i < num_vars; ++
i) {
2161 const int var =
ct->linear().vars(
i);
2164 coeffs.push_back(
ct->linear().coeffs(
i));
2167 BasicKnapsackSolver solver;
2168 const auto& result = solver.Solve(domains, coeffs, costs,
2170 if (!result.solved) {
2172 "TODO independent linear: minimize single linear constraint");
2173 }
else if (result.infeasible) {
2175 "independent linear: no DP solution to simple constraint");
2176 return MarkConstraintAsFalse(
ct);
2178 if (
ct->enforcement_literal().empty()) {
2181 for (
int i = 0;
i < num_vars; ++
i) {
2183 Domain(result.solution[
i]))) {
2187 return RemoveConstraint(
ct);
2192 if (
ct->enforcement_literal().size() == 1) {
2193 indicator =
ct->enforcement_literal(0);
2197 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2198 new_ct->mutable_bool_or()->add_literals(indicator);
2201 for (
int i = 0;
i < num_vars; ++
i) {
2202 const int64_t best_value =
2203 costs[
i] > 0 ? domains[
i].Min() : domains[
i].Max();
2204 const int64_t other_value = result.solution[
i];
2205 if (best_value == other_value) {
2207 Domain(best_value))) {
2214 other_value - best_value,
2221 best_value - other_value, other_value)) {
2227 "independent linear: with enforcement, but solved by DP");
2228 return RemoveConstraint(
ct);
2234 if (index_to_erase.empty()) {
2236 if (context_->
params().presolve_substitution_level() <= 0)
return false;
2237 if (!
ct->enforcement_literal().empty())
return false;
2241 if (rhs.Min() != rhs.Max())
return false;
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);
2266 if (objective_coeff % coeff != 0)
continue;
2272 if (std::abs(objective_coeff) != 1)
continue;
2276 const auto term_domain =
2278 if (!exact)
continue;
2280 if (new_rhs.NumIntervals() > 100)
continue;
2303 context_->
UpdateRuleStats(
"linear: singleton column define objective.");
2306 return RemoveConstraint(
ct);
2322 "linear: singleton column in equality and in objective.");
2324 index_to_erase.insert(
i);
2328 if (index_to_erase.empty())
return false;
2339 if (!
ct->enforcement_literal().empty()) {
2340 for (
const int i : index_to_erase) {
2341 const int var =
ct->linear().vars(
i);
2344 new_lin->add_vars(
var);
2345 new_lin->add_coeffs(1);
2357 for (
int i = 0;
i < num_vars; ++
i) {
2358 if (index_to_erase.count(
i)) {
2362 ct->mutable_linear()->set_coeffs(new_size,
ct->linear().coeffs(
i));
2363 ct->mutable_linear()->set_vars(new_size,
ct->linear().vars(
i));
2366 ct->mutable_linear()->mutable_vars()->Truncate(new_size);
2367 ct->mutable_linear()->mutable_coeffs()->Truncate(new_size);
2369 DivideLinearByGcd(
ct);
2375bool CpModelPresolver::AddVarAffineRepresentativeFromLinearEquality(
2376 int target_index, ConstraintProto*
ct) {
2378 const int num_variables =
ct->linear().vars().size();
2379 for (
int i = 0;
i < num_variables; ++
i) {
2380 if (
i == target_index)
continue;
2381 const int64_t magnitude = std::abs(
ct->linear().coeffs(
i));
2383 if (gcd == 1)
return false;
2389 const int ref =
ct->linear().vars(target_index);
2390 const int64_t coeff =
ct->linear().coeffs(target_index);
2391 const int64_t rhs =
ct->linear().domain(0);
2395 if (coeff % gcd == 0)
return false;
2403 return CanonicalizeLinear(
ct);
2408bool IsLinearEqualityConstraint(
const ConstraintProto&
ct) {
2409 return ct.constraint_case() == ConstraintProto::kLinear &&
2410 ct.linear().domain().size() == 2 &&
2411 ct.linear().domain(0) ==
ct.linear().domain(1) &&
2412 ct.enforcement_literal().empty();
2428bool CpModelPresolver::PresolveLinearEqualityWithModulo(ConstraintProto*
ct) {
2430 if (!IsLinearEqualityConstraint(*
ct))
return false;
2432 const int num_variables =
ct->linear().vars().size();
2433 if (num_variables < 2)
return false;
2435 std::vector<int> mod2_indices;
2436 std::vector<int> mod3_indices;
2437 std::vector<int> mod5_indices;
2439 int64_t min_magnitude;
2440 int num_smallest = 0;
2442 for (
int i = 0;
i < num_variables; ++
i) {
2443 const int64_t magnitude = std::abs(
ct->linear().coeffs(
i));
2444 if (num_smallest == 0 || magnitude < min_magnitude) {
2445 min_magnitude = magnitude;
2448 }
else if (magnitude == min_magnitude) {
2452 if (magnitude % 2 != 0) mod2_indices.push_back(
i);
2453 if (magnitude % 3 != 0) mod3_indices.push_back(
i);
2454 if (magnitude % 5 != 0) mod5_indices.push_back(
i);
2457 if (mod2_indices.size() == 2) {
2459 std::vector<int> literals;
2460 for (
const int i : mod2_indices) {
2461 const int ref =
ct->linear().vars(
i);
2466 literals.push_back(ref);
2469 const int64_t rhs = std::abs(
ct->linear().domain(0));
2470 context_->
UpdateRuleStats(
"linear: only two odd Booleans in equality");
2482 if (mod2_indices.size() == 1) {
2483 return AddVarAffineRepresentativeFromLinearEquality(mod2_indices[0],
ct);
2485 if (mod3_indices.size() == 1) {
2486 return AddVarAffineRepresentativeFromLinearEquality(mod3_indices[0],
ct);
2488 if (mod5_indices.size() == 1) {
2489 return AddVarAffineRepresentativeFromLinearEquality(mod5_indices[0],
ct);
2491 if (num_smallest == 1) {
2492 return AddVarAffineRepresentativeFromLinearEquality(smallest_index,
ct);
2498bool CpModelPresolver::PresolveLinearOfSizeOne(ConstraintProto*
ct) {
2499 CHECK_EQ(
ct->linear().vars().size(), 1);
2502 const int var =
ct->linear().vars(0);
2503 const Domain var_domain = context_->
DomainOf(
var);
2507 if (rhs.IsEmpty()) {
2509 return MarkConstraintAsFalse(
ct);
2513 return RemoveConstraint(
ct);
2518 if (
ct->linear().coeffs(0) != 1) {
2520 ct->mutable_linear()->set_coeffs(0, 1);
2528 return RemoveConstraint(
ct);
2533 DCHECK(rhs.IsFixed());
2534 if (rhs.FixedValue() == 1) {
2535 ct->mutable_bool_and()->add_literals(
var);
2537 CHECK_EQ(rhs.FixedValue(), 0);
2547 if (
ct->enforcement_literal().size() == 1) {
2550 const int lit =
ct->enforcement_literal(0);
2552 if (rhs.IsFixed()) {
2553 const int64_t
value = rhs.FixedValue();
2556 if (
lit == encoding_lit)
return false;
2573 const Domain complement = rhs.Complement().IntersectionWith(var_domain);
2574 if (complement.IsFixed()) {
2575 const int64_t
value = complement.FixedValue();
2599bool CpModelPresolver::PresolveLinearOfSizeTwo(ConstraintProto*
ct) {
2600 DCHECK_EQ(
ct->linear().vars().size(), 2);
2602 const LinearConstraintProto& arg =
ct->linear();
2603 const int var1 = arg.vars(0);
2604 const int var2 = arg.vars(1);
2605 const int64_t coeff1 = arg.coeffs(0);
2606 const int64_t coeff2 = arg.coeffs(1);
2618 arg.domain_size() == 2 && arg.domain(0) == arg.domain(1);
2621 int64_t value_on_true, coeff;
2624 value_on_true = coeff1;
2629 value_on_true = coeff2;
2638 const Domain rhs_if_true =
2639 rhs.AdditionWith(Domain(-value_on_true)).InverseMultiplicationBy(coeff);
2640 const Domain rhs_if_false = rhs.InverseMultiplicationBy(coeff);
2641 const bool implied_false =
2643 const bool implied_true =
2645 if (implied_true && implied_false) {
2647 return MarkConstraintAsFalse(
ct);
2648 }
else if (implied_true) {
2649 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2652 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2653 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2654 new_ct->mutable_bool_and()->add_literals(
lit);
2658 ct->mutable_linear()->Clear();
2659 ct->mutable_linear()->add_vars(
var);
2660 ct->mutable_linear()->add_coeffs(1);
2662 return PresolveLinearOfSizeOne(
ct) ||
true;
2663 }
else if (implied_false) {
2664 context_->
UpdateRuleStats(
"linear2: Boolean with one feasible value.");
2667 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2668 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
2673 ct->mutable_linear()->Clear();
2674 ct->mutable_linear()->add_vars(
var);
2675 ct->mutable_linear()->add_coeffs(1);
2677 return PresolveLinearOfSizeOne(
ct) ||
true;
2678 }
else if (
ct->enforcement_literal().empty() &&
2687 const Domain var_domain = context_->
DomainOf(
var);
2688 if (!var_domain.IsIncludedIn(rhs_if_true)) {
2689 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2690 new_ct->add_enforcement_literal(
lit);
2691 new_ct->mutable_linear()->add_vars(
var);
2692 new_ct->mutable_linear()->add_coeffs(1);
2694 new_ct->mutable_linear());
2698 if (!var_domain.IsIncludedIn(rhs_if_false)) {
2699 ConstraintProto* new_ct = context_->
working_model->add_constraints();
2701 new_ct->mutable_linear()->add_vars(
var);
2702 new_ct->mutable_linear()->add_coeffs(1);
2704 new_ct->mutable_linear());
2718 const int64_t rhs = arg.domain(0);
2719 if (
ct->enforcement_literal().empty()) {
2727 }
else if (coeff2 == 1) {
2729 }
else if (coeff1 == -1) {
2731 }
else if (coeff2 == -1) {
2743 if (added)
return RemoveConstraint(
ct);
2753 "linear2: implied ax + by = cte has no solutions");
2754 return MarkConstraintAsFalse(
ct);
2756 const Domain reduced_domain =
2762 .InverseMultiplicationBy(-
a));
2764 if (reduced_domain.IsEmpty()) {
2766 "linear2: implied ax + by = cte has no solutions");
2767 return MarkConstraintAsFalse(
ct);
2770 if (reduced_domain.Size() == 1) {
2771 const int64_t z = reduced_domain.FixedValue();
2772 const int64_t value1 = x0 +
b * z;
2773 const int64_t value2 = y0 -
a * z;
2777 DCHECK_EQ(coeff1 * value1 + coeff2 * value2, rhs);
2779 ConstraintProto* imply1 = context_->
working_model->add_constraints();
2780 *imply1->mutable_enforcement_literal() =
ct->enforcement_literal();
2781 imply1->mutable_linear()->add_vars(var1);
2782 imply1->mutable_linear()->add_coeffs(1);
2783 imply1->mutable_linear()->add_domain(value1);
2784 imply1->mutable_linear()->add_domain(value1);
2786 ConstraintProto* imply2 = context_->
working_model->add_constraints();
2787 *imply2->mutable_enforcement_literal() =
ct->enforcement_literal();
2788 imply2->mutable_linear()->add_vars(var2);
2789 imply2->mutable_linear()->add_coeffs(1);
2790 imply2->mutable_linear()->add_domain(value2);
2791 imply2->mutable_linear()->add_domain(value2);
2793 "linear2: implied ax + by = cte has only one solution");
2795 return RemoveConstraint(
ct);
2802bool CpModelPresolver::PresolveSmallLinear(ConstraintProto*
ct) {
2803 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2806 if (
ct->linear().vars().empty()) {
2809 if (rhs.Contains(0)) {
2810 return RemoveConstraint(
ct);
2812 return MarkConstraintAsFalse(
ct);
2814 }
else if (
ct->linear().vars().size() == 1) {
2815 return PresolveLinearOfSizeOne(
ct);
2816 }
else if (
ct->linear().vars().size() == 2) {
2817 return PresolveLinearOfSizeTwo(
ct);
2823bool CpModelPresolver::PresolveDiophantine(ConstraintProto*
ct) {
2824 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
2825 if (
ct->linear().vars().size() <= 1)
return false;
2829 const LinearConstraintProto& linear_constraint =
ct->linear();
2830 if (linear_constraint.domain_size() != 2)
return false;
2831 if (linear_constraint.domain(0) != linear_constraint.domain(1))
return false;
2833 std::vector<int64_t> lbs(linear_constraint.vars_size());
2834 std::vector<int64_t> ubs(linear_constraint.vars_size());
2835 for (
int i = 0;
i < linear_constraint.vars_size(); ++
i) {
2836 lbs[
i] = context_->
MinOf(linear_constraint.vars(
i));
2837 ubs[
i] = context_->
MaxOf(linear_constraint.vars(
i));
2840 linear_constraint.coeffs(), linear_constraint.domain(0), lbs, ubs);
2842 if (!diophantine_solution.has_solutions) {
2844 return MarkConstraintAsFalse(
ct);
2846 if (diophantine_solution.no_reformulation_needed)
return false;
2849 for (
const std::vector<absl::int128>&
b : diophantine_solution.kernel_basis) {
2852 "diophantine: couldn't apply due to int64_t overflow");
2858 "diophantine: couldn't apply due to int64_t overflow");
2862 const int num_replaced_variables =
2863 static_cast<int>(diophantine_solution.special_solution.size());
2864 const int num_new_variables =
2865 static_cast<int>(diophantine_solution.kernel_vars_lbs.size());
2866 DCHECK_EQ(num_new_variables + 1, num_replaced_variables);
2867 for (
int i = 0;
i < num_new_variables; ++
i) {
2871 "diophantine: couldn't apply due to int64_t overflow");
2881 std::vector<int> new_variables(num_new_variables);
2882 for (
int i = 0;
i < num_new_variables; ++
i) {
2886 static_cast<int64_t
>(diophantine_solution.kernel_vars_lbs[
i]));
2888 static_cast<int64_t
>(diophantine_solution.kernel_vars_ubs[
i]));
2889 if (!
ct->name().empty()) {
2890 var->set_name(absl::StrCat(
"u_diophantine_",
ct->name(),
"_",
i));
2900 for (
int i = 0;
i < num_replaced_variables; ++
i) {
2901 ConstraintProto* identity = context_->
working_model->add_constraints();
2902 LinearConstraintProto* lin = identity->mutable_linear();
2903 if (!
ct->name().empty()) {
2904 identity->set_name(absl::StrCat(
"c_diophantine_",
ct->name(),
"_",
i));
2906 *identity->mutable_enforcement_literal() =
ct->enforcement_literal();
2908 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
2911 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
2913 static_cast<int64_t
>(diophantine_solution.special_solution[
i]));
2914 for (
int j = std::max(1,
i); j < num_replaced_variables; ++j) {
2915 lin->add_vars(new_variables[j - 1]);
2917 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
2919 for (
int j = num_replaced_variables; j < linear_constraint.vars_size();
2922 linear_constraint.vars(diophantine_solution.index_permutation[j]));
2924 -
static_cast<int64_t
>(diophantine_solution.kernel_basis[j - 1][
i]));
2933 "diophantine: couldn't apply due to overflowing activity of new "
2936 context_->
working_model->mutable_constraints()->DeleteSubrange(
2938 context_->
working_model->mutable_variables()->DeleteSubrange(
2939 context_->
working_model->variables_size() - num_new_variables,
2946 if (VLOG_IS_ON(2)) {
2947 std::string log_eq = absl::StrCat(linear_constraint.domain(0),
" = ");
2948 const int terms_to_show = std::min<int>(15, linear_constraint.vars_size());
2949 for (
int i = 0;
i < terms_to_show; ++
i) {
2950 if (
i > 0) absl::StrAppend(&log_eq,
" + ");
2953 linear_constraint.coeffs(diophantine_solution.index_permutation[
i]),
2955 linear_constraint.vars(diophantine_solution.index_permutation[
i]));
2957 if (terms_to_show < linear_constraint.vars_size()) {
2958 absl::StrAppend(&log_eq,
"+ ... (", linear_constraint.vars_size(),
2961 VLOG(2) <<
"[Diophantine] " << log_eq;
2966 return RemoveConstraint(
ct);
2981void CpModelPresolver::TryToReduceCoefficientsOfLinearConstraint(
2982 int c, ConstraintProto*
ct) {
2983 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
2987 const LinearConstraintProto& lin =
ct->linear();
2988 if (lin.domain().size() != 2)
return;
2994 int64_t max_variation = 0;
2996 rd_entries_.clear();
2997 rd_magnitudes_.clear();
3001 int64_t max_magnitude = 0;
3002 const int num_terms = lin.vars().size();
3003 for (
int i = 0;
i < num_terms; ++
i) {
3004 const int64_t coeff = lin.coeffs(
i);
3005 const int64_t magnitude = std::abs(lin.coeffs(
i));
3006 if (magnitude == 0)
continue;
3007 max_magnitude = std::max(max_magnitude, magnitude);
3012 lb = context_->
MinOf(lin.vars(
i));
3013 ub = context_->
MaxOf(lin.vars(
i));
3015 lb = -context_->
MaxOf(lin.vars(
i));
3016 ub = -context_->
MinOf(lin.vars(
i));
3018 lb_sum += lb * magnitude;
3019 ub_sum += ub * magnitude;
3022 if (lb == ub)
return;
3024 rd_lbs_.push_back(lb);
3025 rd_ubs_.push_back(ub);
3026 rd_magnitudes_.push_back(magnitude);
3027 rd_entries_.push_back({magnitude, magnitude * (ub - lb),
i});
3028 max_variation += rd_entries_.back().max_variation;
3033 if (lb_sum > rhs.Max() || rhs.Min() > ub_sum) {
3034 (void)MarkConstraintAsFalse(
ct);
3038 const IntegerValue rhs_ub(
CapSub(rhs.Max(), lb_sum));
3039 const IntegerValue rhs_lb(
CapSub(ub_sum, rhs.Min()));
3040 const bool use_ub = max_variation > rhs_ub;
3041 const bool use_lb = max_variation > rhs_lb;
3042 if (!use_ub && !use_lb) {
3043 (void)RemoveConstraint(
ct);
3049 if (max_magnitude <= 1)
return;
3054 lb_feasible_.
Reset(rhs_lb.value());
3055 lb_infeasible_.
Reset(rhs.Min() - lb_sum - 1);
3058 ub_feasible_.
Reset(rhs_ub.value());
3059 ub_infeasible_.
Reset(ub_sum - rhs.Max() - 1);
3065 int64_t max_error = max_variation;
3066 std::stable_sort(rd_entries_.begin(), rd_entries_.end(),
3067 [](
const RdEntry&
a,
const RdEntry&
b) {
3068 return a.magnitude > b.magnitude;
3071 rd_divisors_.clear();
3072 for (
int i = 0;
i < rd_entries_.size(); ++
i) {
3073 const RdEntry& e = rd_entries_[
i];
3075 max_error -= e.max_variation;
3081 range += e.max_variation / e.magnitude;
3082 if (
i + 1 < rd_entries_.size() &&
3083 e.magnitude == rd_entries_[
i + 1].magnitude) {
3086 const int64_t saved_range =
range;
3089 if (e.magnitude > 1) {
3094 rd_divisors_.push_back(e.magnitude);
3098 bool simplify_lb =
false;
3105 lb_feasible_.
Bound()) {
3111 lb_infeasible_.
Bound()) {
3117 bool simplify_ub =
false;
3122 ub_feasible_.
Bound()) {
3126 ub_infeasible_.
Bound()) {
3133 if (max_error == 0)
break;
3134 if (simplify_lb && simplify_ub) {
3137 int64_t shift_lb = 0;
3138 int64_t shift_ub = 0;
3141 for (
int j = 0; j <=
i; ++j) {
3142 const int index = rd_entries_[j].index;
3143 const int64_t m = rd_magnitudes_[
index];
3144 shift_lb += rd_lbs_[
index] * m;
3145 shift_ub += rd_ubs_[
index] * m;
3146 rd_vars_.push_back(lin.vars(
index));
3147 rd_coeffs_.push_back(lin.coeffs(
index));
3149 LinearConstraintProto* mut_lin =
ct->mutable_linear();
3150 mut_lin->mutable_vars()->Assign(rd_vars_.begin(), rd_vars_.end());
3151 mut_lin->mutable_coeffs()->Assign(rd_coeffs_.begin(), rd_coeffs_.end());
3157 const int64_t new_rhs_lb =
3158 use_lb ? shift_ub - lb_feasible_.
CurrentMax() : shift_lb;
3159 const int64_t new_rhs_ub =
3160 use_ub ? shift_lb + ub_feasible_.
CurrentMax() : shift_ub;
3161 if (new_rhs_lb > new_rhs_ub) {
3162 (void)MarkConstraintAsFalse(
ct);
3167 DivideLinearByGcd(
ct);
3176 if (DivideLinearByGcd(
ct)) {
3187 const int64_t new_rhs_lb =
3188 use_lb ? ub_sum - lb_feasible_.
CurrentMax() : lb_sum;
3189 const int64_t new_rhs_ub =
3190 use_ub ? lb_sum + ub_feasible_.
CurrentMax() : ub_sum;
3191 if (new_rhs_lb > new_rhs_ub) {
3192 (void)MarkConstraintAsFalse(
ct);
3200 if (rd_divisors_.size() > 3) rd_divisors_.resize(3);
3201 for (
const int64_t divisor : rd_divisors_) {
3205 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, rhs.Max(), &new_ub)) {
3210 int64_t minus_new_lb;
3211 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3212 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3213 rd_lbs_[
i] = -rd_lbs_[
i];
3214 rd_ubs_[
i] = -rd_ubs_[
i];
3217 divisor, rd_magnitudes_, rd_lbs_, rd_ubs_, -rhs.Min(),
3219 for (
int i = 0;
i < rd_lbs_.size(); ++
i) {
3220 std::swap(rd_lbs_[
i], rd_ubs_[
i]);
3221 rd_lbs_[
i] = -rd_lbs_[
i];
3222 rd_ubs_[
i] = -rd_ubs_[
i];
3230 LinearConstraintProto* mutable_linear =
ct->mutable_linear();
3231 for (
int i = 0;
i < lin.coeffs().
size(); ++
i) {
3232 const int64_t new_coeff =
3234 if (new_coeff == 0)
continue;
3235 mutable_linear->set_vars(new_size, lin.vars(
i));
3236 mutable_linear->set_coeffs(new_size, new_coeff);
3239 mutable_linear->mutable_vars()->Truncate(new_size);
3240 mutable_linear->mutable_coeffs()->Truncate(new_size);
3241 const Domain new_rhs = Domain(-minus_new_lb, new_ub);
3242 if (new_rhs.IsEmpty()) {
3243 (void)MarkConstraintAsFalse(
ct);
3257bool RhsCanBeFixedToMin(int64_t coeff,
const Domain& var_domain,
3258 const Domain& terms,
const Domain& rhs) {
3259 if (var_domain.NumIntervals() != 1)
return false;
3260 if (std::abs(coeff) != 1)
return false;
3268 if (coeff == 1 && terms.Max() + var_domain.Min() <= rhs.Min()) {
3271 if (coeff == -1 && terms.Max() - var_domain.Max() <= rhs.Min()) {
3277bool RhsCanBeFixedToMax(int64_t coeff,
const Domain& var_domain,
3278 const Domain& terms,
const Domain& rhs) {
3279 if (var_domain.NumIntervals() != 1)
return false;
3280 if (std::abs(coeff) != 1)
return false;
3282 if (coeff == 1 && terms.Min() + var_domain.Max() >= rhs.Max()) {
3285 if (coeff == -1 && terms.Min() - var_domain.Min() >= rhs.Max()) {
3291int FixLiteralFromSet(
const absl::flat_hash_set<int>& literals_at_true,
3292 LinearConstraintProto* linear) {
3295 const int num_terms = linear->vars().size();
3297 for (
int i = 0;
i < num_terms; ++
i) {
3298 const int var = linear->vars(
i);
3299 const int64_t coeff = linear->coeffs(
i);
3300 if (literals_at_true.contains(
var)) {
3305 linear->set_vars(new_size,
var);
3306 linear->set_coeffs(new_size, coeff);
3313 linear->mutable_vars()->Truncate(new_size);
3314 linear->mutable_coeffs()->Truncate(new_size);
3324void CpModelPresolver::ProcessAtMostOneAndLinear() {
3327 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
3328 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
3330 ActivityBoundHelper amo_in_linear;
3333 int num_changes = 0;
3334 const int num_constraints = context_->
working_model->constraints_size();
3335 for (
int c = 0;
c < num_constraints; ++
c) {
3336 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
3337 if (
ct->constraint_case() != ConstraintProto::kLinear)
continue;
3340 for (
int i = 0;
i < 5; ++
i) {
3341 const int old_size =
ct->linear().vars().size();
3342 const int old_enf_size =
ct->enforcement_literal().size();
3343 ProcessOneLinearWithAmo(c,
ct, &amo_in_linear);
3345 if (
ct->constraint_case() != ConstraintProto::kLinear)
break;
3346 if (
ct->linear().vars().size() == old_size &&
3347 ct->enforcement_literal().size() == old_enf_size) {
3354 timer.AddCounter(
"num_changes", num_changes);
3362void CpModelPresolver::ProcessOneLinearWithAmo(
int ct_index,
3363 ConstraintProto*
ct,
3364 ActivityBoundHelper* helper) {
3365 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
3366 if (
ct->linear().vars().size() <= 1)
return;
3370 Domain non_boolean_domain(0);
3371 const int initial_size =
ct->linear().vars().size();
3372 int64_t min_magnitude = std::numeric_limits<int64_t>::max();
3373 int64_t max_magnitude = 0;
3374 for (
int i = 0;
i < initial_size; ++
i) {
3376 int ref =
ct->linear().vars(
i);
3377 int64_t coeff =
ct->linear().coeffs(
i);
3383 tmp_terms_.push_back({ref, coeff});
3384 min_magnitude = std::min(min_magnitude, std::abs(coeff));
3385 max_magnitude = std::max(max_magnitude, std::abs(coeff));
3387 non_boolean_domain =
3391 .RelaxIfTooComplex();
3392 temp_ct_.mutable_linear()->add_vars(ref);
3393 temp_ct_.mutable_linear()->add_coeffs(coeff);
3398 if (tmp_terms_.empty())
return;
3409 if (non_boolean_domain == Domain(0) && rhs.NumIntervals() == 1 &&
3410 min_magnitude < max_magnitude) {
3411 int64_t min_activity = 0;
3412 int64_t max_activity = 0;
3413 for (
const auto [ref, coeff] : tmp_terms_) {
3415 max_activity += coeff;
3417 min_activity += coeff;
3420 const int64_t transformed_rhs = rhs.Max() - min_activity;
3421 if (min_activity >= rhs.Min() && max_magnitude <= transformed_rhs) {
3422 std::vector<int> literals;
3423 for (
const auto [ref, coeff] : tmp_terms_) {
3424 if (coeff + min_magnitude > transformed_rhs)
continue;
3425 literals.push_back(coeff > 0 ? ref :
NegatedRef(ref));
3427 if (helper->IsAmo(literals)) {
3431 for (
int i = 0;
i < initial_size; ++
i) {
3433 if (
ct->linear().coeffs(
i) > 0) {
3434 ct->mutable_linear()->set_coeffs(
i, 1);
3436 ct->mutable_linear()->set_coeffs(
i, -1);
3447 const int64_t min_bool_activity =
3448 helper->ComputeMinActivity(tmp_terms_, &conditional_mins_);
3449 const int64_t max_bool_activity =
3450 helper->ComputeMaxActivity(tmp_terms_, &conditional_maxs_);
3454 const Domain activity = non_boolean_domain.AdditionWith(
3455 Domain(min_bool_activity, max_bool_activity));
3456 if (activity.IntersectionWith(rhs).IsEmpty()) {
3458 context_->
UpdateRuleStats(
"linear + amo: infeasible linear constraint");
3459 (void)MarkConstraintAsFalse(
ct);
3462 }
else if (activity.IsIncludedIn(rhs)) {
3470 if (
ct->enforcement_literal().empty() && !temp_ct_.linear().vars().empty()) {
3473 Domain(min_bool_activity, max_bool_activity).Negation()),
3474 temp_ct_.mutable_linear());
3475 if (!PropagateDomainsInLinear(-1, &temp_ct_)) {
3492 std::vector<int> new_enforcement;
3493 std::vector<int> must_be_true;
3494 for (
int i = 0;
i < tmp_terms_.size(); ++
i) {
3495 const int ref = tmp_terms_[
i].first;
3497 const Domain bool0(conditional_mins_[
i][0], conditional_maxs_[
i][0]);
3498 const Domain activity0 = bool0.AdditionWith(non_boolean_domain);
3499 if (activity0.IntersectionWith(rhs).IsEmpty()) {
3501 must_be_true.push_back(ref);
3502 }
else if (activity0.IsIncludedIn(rhs)) {
3504 new_enforcement.push_back(ref);
3507 const Domain bool1(conditional_mins_[
i][1], conditional_maxs_[
i][1]);
3508 const Domain activity1 = bool1.AdditionWith(non_boolean_domain);
3509 if (activity1.IntersectionWith(rhs).IsEmpty()) {
3512 }
else if (activity1.IsIncludedIn(rhs)) {
3523 if (
ct->enforcement_literal().empty() && !must_be_true.empty()) {
3527 must_be_true.size());
3528 for (
const int lit : must_be_true) {
3531 CanonicalizeLinear(
ct);
3536 if (!new_enforcement.empty()) {
3537 context_->
UpdateRuleStats(
"linear + amo: extracted enforcement literal",
3538 new_enforcement.size());
3539 for (
const int ref : new_enforcement) {
3540 ct->add_enforcement_literal(ref);
3544 if (!
ct->enforcement_literal().empty()) {
3545 const int old_enf_size =
ct->enforcement_literal().size();
3546 if (!helper->PresolveEnforcement(
ct->linear().vars(),
ct, &temp_set_)) {
3552 if (
ct->enforcement_literal().size() < old_enf_size) {
3553 context_->
UpdateRuleStats(
"linear + amo: simplified enforcement list");
3557 for (
const int lit : must_be_true) {
3562 "linear + amo: advanced infeasible linear constraint");
3563 (void)MarkConstraintAsFalse(
ct);
3570 if (
ct->enforcement_literal().size() == 1 && !must_be_true.empty()) {
3575 ConstraintProto* new_ct = context_->
working_model->add_constraints();
3576 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
3577 for (
const int lit : must_be_true) {
3578 new_ct->mutable_bool_and()->add_literals(
lit);
3579 temp_set_.insert(
lit);
3584 const int num_fixed = FixLiteralFromSet(temp_set_,
ct->mutable_linear());
3585 if (num_fixed > new_enforcement.size()) {
3587 "linear + amo: fixed literal implied by enforcement");
3589 if (num_fixed > 0) {
3594 if (
ct->linear().vars().empty()) {
3596 PresolveSmallLinear(
ct);
3602 if (initial_size !=
ct->linear().vars().size() && PresolveSmallLinear(
ct)) {
3604 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
3612 if (!
ct->enforcement_literal().empty()) {
3615 Domain non_boolean_domain(0);
3616 const int num_ct_terms =
ct->linear().vars().size();
3617 for (
int i = 0;
i < num_ct_terms; ++
i) {
3618 const int ref =
ct->linear().vars(
i);
3619 const int64_t coeff =
ct->linear().coeffs(
i);
3622 tmp_terms_.push_back({ref, coeff});
3624 non_boolean_domain =
3628 .RelaxIfTooComplex();
3631 const int num_removed = helper->RemoveEnforcementThatMakesConstraintTrivial(
3633 if (num_removed > 0) {
3634 context_->
UpdateRuleStats(
"linear + amo: removed enforcement literal",
3641bool CpModelPresolver::PropagateDomainsInLinear(
int ct_index,
3642 ConstraintProto*
ct) {
3643 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
3647 int64_t min_activity;
3648 int64_t max_activity;
3651 const int num_vars =
ct->linear().vars_size();
3654 const bool slow_mode = num_vars < 10;
3658 term_domains.resize(num_vars + 1);
3659 left_domains.resize(num_vars + 1);
3660 left_domains[0] = Domain(0);
3661 term_domains[num_vars] = Domain(0);
3662 for (
int i = 0;
i < num_vars; ++
i) {
3663 const int var =
ct->linear().vars(
i);
3664 const int64_t coeff =
ct->linear().coeffs(
i);
3667 left_domains[
i + 1] =
3671 std::tie(min_activity, max_activity) =
3674 const Domain& implied_rhs =
3675 slow_mode ? left_domains[num_vars] : Domain(min_activity, max_activity);
3679 if (implied_rhs.IsIncludedIn(old_rhs)) {
3681 return RemoveConstraint(
ct);
3685 Domain rhs = old_rhs.SimplifyUsingImpliedDomain(implied_rhs);
3686 if (rhs.IsEmpty()) {
3688 return MarkConstraintAsFalse(
ct);
3690 if (rhs != old_rhs) {
3696 if (
ct->enforcement_literal().size() > 1)
return false;
3698 bool new_bounds =
false;
3699 bool recanonicalize =
false;
3700 Domain negated_rhs = rhs.Negation();
3701 Domain right_domain(0);
3703 Domain activity_minus_term;
3704 for (
int i = num_vars - 1;
i >= 0; --
i) {
3705 const int var =
ct->linear().vars(
i);
3706 const int64_t var_coeff =
ct->linear().coeffs(
i);
3710 right_domain.AdditionWith(term_domains[
i + 1]).RelaxIfTooComplex();
3711 activity_minus_term = left_domains[
i].AdditionWith(right_domain);
3713 int64_t min_term = var_coeff * context_->
MinOf(
var);
3714 int64_t max_term = var_coeff * context_->
MaxOf(
var);
3715 if (var_coeff < 0) std::swap(min_term, max_term);
3716 activity_minus_term =
3717 Domain(min_activity - min_term, max_activity - max_term);
3719 new_domain = activity_minus_term.AdditionWith(negated_rhs)
3720 .InverseMultiplicationBy(-var_coeff);
3722 if (
ct->enforcement_literal().empty()) {
3727 }
else if (
ct->enforcement_literal().size() == 1) {
3738 recanonicalize =
true;
3744 if (!
ct->enforcement_literal().empty())
continue;
3756 if (rhs.Min() != rhs.Max() &&
3759 const bool same_sign = (var_coeff > 0) == (obj_coeff > 0);
3761 if (same_sign && RhsCanBeFixedToMin(var_coeff, context_->
DomainOf(
var),
3762 activity_minus_term, rhs)) {
3763 rhs = Domain(rhs.Min());
3766 if (!same_sign && RhsCanBeFixedToMax(var_coeff, context_->
DomainOf(
var),
3767 activity_minus_term, rhs)) {
3768 rhs = Domain(rhs.Max());
3774 negated_rhs = rhs.Negation();
3778 right_domain = Domain(0);
3792 if (
ct->linear().vars().size() <= 2)
continue;
3797 if (rhs.Min() != rhs.Max())
continue;
3807 if (std::abs(var_coeff) != 1)
continue;
3808 if (context_->
params().presolve_substitution_level() <= 0)
continue;
3814 if (is_in_objective) col_size--;
3815 const int row_size =
ct->linear().vars_size();
3819 const int num_entries_added = (row_size - 1) * (col_size - 1);
3820 const int num_entries_removed = col_size + row_size - 1;
3821 if (num_entries_added > num_entries_removed)
continue;
3826 std::vector<int> others;
3835 if (context_->
working_model->constraints(c).constraint_case() !=
3836 ConstraintProto::kLinear) {
3840 for (
const int ref :
3841 context_->
working_model->constraints(c).enforcement_literal()) {
3848 others.push_back(c);
3850 if (abort)
continue;
3863 if (others.size() != 1)
continue;
3864 const ConstraintProto& other_ct =
3866 if (!other_ct.enforcement_literal().empty())
continue;
3870 const LinearConstraintProto& other_lin = other_ct.linear();
3871 if (other_lin.vars().size() > 100)
continue;
3873 int64_t other_coeff = 0;
3874 for (
int i = 0;
i < other_lin.vars().
size(); ++
i) {
3875 const int v = other_lin.vars(
i);
3876 const int64_t coeff = other_lin.coeffs(
i);
3880 other_coeff += coeff;
3885 .RelaxIfTooComplex();
3888 if (other_coeff == 0)
continue;
3889 implied = implied.InverseMultiplicationBy(other_coeff);
3895 if (new_domain.IntersectionWith(implied) != context_->
DomainOf(
var)) {
3905 if (is_in_objective &&
3911 ConstraintProto copy_if_we_abort;
3912 absl::c_sort(others);
3913 for (
const int c : others) {
3925 var, var_coeff, *
ct,
3933 if (CanonicalizeLinear(
3944 context_->
working_model->constraints(c).linear().coeffs())) {
3946 *context_->
working_model->mutable_constraints(c) = copy_if_we_abort;
3954 if (abort)
continue;
3957 absl::StrCat(
"linear: variable substitution ", others.size()));
3968 ConstraintProto* mapping_ct =
3971 LinearConstraintProto* mapping_linear_ct = mapping_ct->mutable_linear();
3972 std::swap(mapping_linear_ct->mutable_vars()->at(0),
3973 mapping_linear_ct->mutable_vars()->at(
i));
3974 std::swap(mapping_linear_ct->mutable_coeffs()->at(0),
3975 mapping_linear_ct->mutable_coeffs()->at(
i));
3976 return RemoveConstraint(
ct);
3983 "linear: reduced variable domains in derived constraint");
3991 if (recanonicalize)
return CanonicalizeLinear(
ct);
3997void CpModelPresolver::LowerThanCoeffStrengthening(
bool from_lower_bound,
3998 int64_t min_magnitude,
4000 ConstraintProto*
ct) {
4001 const LinearConstraintProto& arg =
ct->linear();
4002 const int64_t second_threshold = rhs - min_magnitude;
4003 const int num_vars = arg.vars_size();
4016 if (min_magnitude <= second_threshold) {
4018 int64_t max_magnitude_left = 0;
4019 int64_t max_activity_left = 0;
4020 int64_t activity_when_coeff_are_one = 0;
4022 for (
int i = 0;
i < num_vars; ++
i) {
4023 const int64_t magnitude = std::abs(arg.coeffs(
i));
4024 if (magnitude <= second_threshold) {
4026 max_magnitude_left = std::max(max_magnitude_left, magnitude);
4027 const int64_t bound_diff =
4028 context_->
MaxOf(arg.vars(
i)) - context_->
MinOf(arg.vars(
i));
4029 activity_when_coeff_are_one += bound_diff;
4030 max_activity_left += magnitude * bound_diff;
4033 CHECK_GT(min_magnitude, 0);
4034 CHECK_LE(min_magnitude, max_magnitude_left);
4037 int64_t new_rhs = 0;
4038 bool set_all_to_one =
false;
4039 if (max_activity_left <= rhs) {
4042 new_rhs = activity_when_coeff_are_one;
4043 set_all_to_one =
true;
4044 }
else if (rhs / min_magnitude == rhs / max_magnitude_left) {
4047 new_rhs = rhs / min_magnitude;
4048 set_all_to_one =
true;
4049 }
else if (gcd > 1) {
4052 new_rhs = rhs / gcd;
4056 int64_t rhs_offset = 0;
4057 for (
int i = 0;
i < num_vars; ++
i) {
4058 const int ref = arg.vars(
i);
4059 const int64_t coeff = from_lower_bound ? arg.coeffs(
i) : -arg.coeffs(
i);
4062 const int64_t magnitude = std::abs(coeff);
4063 if (magnitude > rhs) {
4064 new_coeff = new_rhs + 1;
4065 }
else if (magnitude > second_threshold) {
4066 new_coeff = new_rhs;
4068 new_coeff = set_all_to_one ? 1 : magnitude / gcd;
4074 ct->mutable_linear()->set_coeffs(
i, new_coeff);
4075 rhs_offset += new_coeff * context_->
MinOf(ref);
4077 ct->mutable_linear()->set_coeffs(
i, -new_coeff);
4078 rhs_offset -= new_coeff * context_->
MaxOf(ref);
4082 ct->mutable_linear());
4087 int64_t rhs_offset = 0;
4088 for (
int i = 0;
i < num_vars; ++
i) {
4089 int ref = arg.vars(
i);
4090 int64_t coeff = arg.coeffs(
i);
4097 if (
ct->enforcement_literal().empty()) {
4107 ref, Domain(from_lower_bound ? context_->
MinOf(ref)
4108 : context_->
MaxOf(ref)))) {
4116 if (coeff > second_threshold && coeff < rhs) {
4118 "linear: coefficient strengthening by increasing it.");
4119 if (from_lower_bound) {
4121 rhs_offset -= (coeff - rhs) * context_->
MinOf(ref);
4124 rhs_offset -= (coeff - rhs) * context_->
MaxOf(ref);
4126 ct->mutable_linear()->set_coeffs(
i, arg.coeffs(
i) > 0 ? rhs : -rhs);
4129 if (rhs_offset != 0) {
4131 ct->mutable_linear());
4142void CpModelPresolver::ExtractEnforcementLiteralFromLinearConstraint(
4144 if (
ct->constraint_case() != ConstraintProto::kLinear)
return;
4147 const LinearConstraintProto& arg =
ct->linear();
4148 const int num_vars = arg.vars_size();
4152 if (num_vars <= 1)
return;
4154 int64_t min_sum = 0;
4155 int64_t max_sum = 0;
4156 int64_t max_coeff_magnitude = 0;
4157 int64_t min_coeff_magnitude = std::numeric_limits<int64_t>::max();
4158 for (
int i = 0;
i < num_vars; ++
i) {
4159 const int ref = arg.vars(
i);
4160 const int64_t coeff = arg.coeffs(
i);
4162 max_coeff_magnitude = std::max(max_coeff_magnitude, coeff);
4163 min_coeff_magnitude = std::min(min_coeff_magnitude, coeff);
4164 min_sum += coeff * context_->
MinOf(ref);
4165 max_sum += coeff * context_->
MaxOf(ref);
4167 max_coeff_magnitude = std::max(max_coeff_magnitude, -coeff);
4168 min_coeff_magnitude = std::min(min_coeff_magnitude, -coeff);
4169 min_sum += coeff * context_->
MaxOf(ref);
4170 max_sum += coeff * context_->
MinOf(ref);
4173 if (max_coeff_magnitude == 1)
return;
4181 const auto& domain =
ct->linear().domain();
4182 const int64_t ub_threshold = domain[domain.size() - 2] - min_sum;
4183 const int64_t lb_threshold = max_sum - domain[1];
4184 if (max_coeff_magnitude + min_coeff_magnitude <
4185 std::max(ub_threshold, lb_threshold)) {
4190 if (domain.size() == 2 && min_coeff_magnitude > 1 &&
4191 min_coeff_magnitude < max_coeff_magnitude) {
4192 const int64_t rhs_min = domain[0];
4193 const int64_t rhs_max = domain[1];
4194 if (min_sum >= rhs_min &&
4195 max_coeff_magnitude + min_coeff_magnitude > rhs_max - min_sum) {
4196 LowerThanCoeffStrengthening(
true,
4197 min_coeff_magnitude, rhs_max - min_sum,
ct);
4200 if (max_sum <= rhs_max &&
4201 max_coeff_magnitude + min_coeff_magnitude > max_sum - rhs_min) {
4202 LowerThanCoeffStrengthening(
false,
4203 min_coeff_magnitude, max_sum - rhs_min,
ct);
4223 const bool lower_bounded = min_sum < rhs_domain.Min();
4224 const bool upper_bounded = max_sum > rhs_domain.Max();
4225 if (!lower_bounded && !upper_bounded)
return;
4226 if (lower_bounded && upper_bounded) {
4231 if (max_coeff_magnitude < std::max(ub_threshold, lb_threshold))
return;
4234 ConstraintProto* new_ct1 = context_->
working_model->add_constraints();
4236 if (!
ct->name().empty()) {
4237 new_ct1->set_name(absl::StrCat(
ct->name(),
" (part 1)"));
4240 new_ct1->mutable_linear());
4242 ConstraintProto* new_ct2 = context_->
working_model->add_constraints();
4244 if (!
ct->name().empty()) {
4245 new_ct2->set_name(absl::StrCat(
ct->name(),
" (part 2)"));
4248 new_ct2->mutable_linear());
4259 const int64_t threshold = lower_bounded ? ub_threshold : lb_threshold;
4267 int64_t second_threshold = std::max(
CeilOfRatio(threshold, int64_t{2}),
4268 threshold - min_coeff_magnitude);
4276 if (rhs_domain.NumIntervals() > 1) {
4277 second_threshold = threshold;
4285 const bool only_extract_booleans =
4286 !context_->
params().presolve_extract_integer_enforcement() ||
4292 int64_t rhs_offset = 0;
4293 bool some_integer_encoding_were_extracted =
false;
4294 LinearConstraintProto* mutable_arg =
ct->mutable_linear();
4295 for (
int i = 0;
i < arg.vars_size(); ++
i) {
4296 int ref = arg.vars(
i);
4297 int64_t coeff = arg.coeffs(
i);
4306 if (context_->
IsFixed(ref) || coeff < threshold ||
4307 (only_extract_booleans && !is_boolean)) {
4308 mutable_arg->set_vars(new_size, mutable_arg->vars(
i));
4310 int64_t new_magnitude = std::abs(arg.coeffs(
i));
4311 if (coeff > threshold) {
4314 new_magnitude = threshold;
4316 }
else if (coeff > second_threshold && coeff < threshold) {
4319 new_magnitude = second_threshold;
4321 "linear: advanced coefficient strenghtening.");
4323 if (coeff != new_magnitude) {
4324 if (lower_bounded) {
4326 rhs_offset -= (coeff - new_magnitude) * context_->
MinOf(ref);
4329 rhs_offset -= (coeff - new_magnitude) * context_->
MaxOf(ref);
4333 mutable_arg->set_coeffs(
4334 new_size, arg.coeffs(
i) > 0 ? new_magnitude : -new_magnitude);
4342 some_integer_encoding_were_extracted =
true;
4344 "linear: extracted integer enforcement literal");
4346 if (lower_bounded) {
4347 ct->add_enforcement_literal(is_boolean
4350 ref, context_->
MinOf(ref)));
4351 rhs_offset -= coeff * context_->
MinOf(ref);
4353 ct->add_enforcement_literal(is_boolean
4356 ref, context_->
MaxOf(ref)));
4357 rhs_offset -= coeff * context_->
MaxOf(ref);
4360 mutable_arg->mutable_vars()->Truncate(new_size);
4361 mutable_arg->mutable_coeffs()->Truncate(new_size);
4363 if (some_integer_encoding_were_extracted || new_size == 1) {
4369void CpModelPresolver::ExtractAtMostOneFromLinear(ConstraintProto*
ct) {
4374 const LinearConstraintProto& arg =
ct->linear();
4375 const int num_vars = arg.vars_size();
4376 int64_t min_sum = 0;
4377 int64_t max_sum = 0;
4378 for (
int i = 0;
i < num_vars; ++
i) {
4379 const int ref = arg.vars(
i);
4380 const int64_t coeff = arg.coeffs(
i);
4381 const int64_t term_a = coeff * context_->
MinOf(ref);
4382 const int64_t term_b = coeff * context_->
MaxOf(ref);
4383 min_sum += std::min(term_a, term_b);
4384 max_sum += std::max(term_a, term_b);
4386 for (
const int type : {0, 1}) {
4387 std::vector<int> at_most_one;
4388 for (
int i = 0;
i < num_vars; ++
i) {
4389 const int ref = arg.vars(
i);
4390 const int64_t coeff = arg.coeffs(
i);
4391 if (context_->
MinOf(ref) != 0)
continue;
4392 if (context_->
MaxOf(ref) != 1)
continue;
4397 if (min_sum + 2 * std::abs(coeff) > rhs.Max()) {
4398 at_most_one.push_back(coeff > 0 ? ref :
NegatedRef(ref));
4401 if (max_sum - 2 * std::abs(coeff) < rhs.Min()) {
4402 at_most_one.push_back(coeff > 0 ?
NegatedRef(ref) : ref);
4406 if (at_most_one.size() > 1) {
4412 ConstraintProto* new_ct = context_->
working_model->add_constraints();
4413 new_ct->set_name(
ct->name());
4414 for (
const int ref : at_most_one) {
4415 new_ct->mutable_at_most_one()->add_literals(ref);
4424bool CpModelPresolver::PresolveLinearOnBooleans(ConstraintProto*
ct) {
4425 if (
ct->constraint_case() != ConstraintProto::kLinear)
return false;
4428 const LinearConstraintProto& arg =
ct->linear();
4429 const int num_vars = arg.vars_size();
4430 int64_t min_coeff = std::numeric_limits<int64_t>::max();
4431 int64_t max_coeff = 0;
4432 int64_t min_sum = 0;
4433 int64_t max_sum = 0;
4434 for (
int i = 0;
i < num_vars; ++
i) {
4436 const int var = arg.vars(
i);
4437 const int64_t coeff = arg.coeffs(
i);
4440 if (context_->
MinOf(
var) != 0)
return false;
4441 if (context_->
MaxOf(
var) != 1)
return false;
4445 min_coeff = std::min(min_coeff, coeff);
4446 max_coeff = std::max(max_coeff, coeff);
4450 min_coeff = std::min(min_coeff, -coeff);
4451 max_coeff = std::max(max_coeff, -coeff);
4454 CHECK_LE(min_coeff, max_coeff);
4463 if ((!rhs_domain.Contains(min_sum) &&
4464 min_sum + min_coeff > rhs_domain.Max()) ||
4465 (!rhs_domain.Contains(max_sum) &&
4466 max_sum - min_coeff < rhs_domain.Min())) {
4467 context_->
UpdateRuleStats(
"linear: all booleans and trivially false");
4468 return MarkConstraintAsFalse(
ct);
4470 if (Domain(min_sum, max_sum).IsIncludedIn(rhs_domain)) {
4472 return RemoveConstraint(
ct);
4479 DCHECK(!rhs_domain.IsEmpty());
4480 if (min_sum + min_coeff > rhs_domain.Max()) {
4483 const auto copy = arg;
4484 ct->mutable_bool_and()->clear_literals();
4485 for (
int i = 0;
i < num_vars; ++
i) {
4486 ct->mutable_bool_and()->add_literals(
4487 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4489 PresolveBoolAnd(
ct);
4491 }
else if (max_sum - min_coeff < rhs_domain.Min()) {
4494 const auto copy = arg;
4495 ct->mutable_bool_and()->clear_literals();
4496 for (
int i = 0;
i < num_vars; ++
i) {
4497 ct->mutable_bool_and()->add_literals(
4498 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4500 PresolveBoolAnd(
ct);
4502 }
else if (min_sum + min_coeff >= rhs_domain.Min() &&
4503 rhs_domain.front().end >= max_sum) {
4506 const auto copy = arg;
4507 ct->mutable_bool_or()->clear_literals();
4508 for (
int i = 0;
i < num_vars; ++
i) {
4509 ct->mutable_bool_or()->add_literals(
4510 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4514 }
else if (max_sum - min_coeff <= rhs_domain.Max() &&
4515 rhs_domain.back().start <= min_sum) {
4518 const auto copy = arg;
4519 ct->mutable_bool_or()->clear_literals();
4520 for (
int i = 0;
i < num_vars; ++
i) {
4521 ct->mutable_bool_or()->add_literals(
4522 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4527 min_sum + max_coeff <= rhs_domain.Max() &&
4528 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4529 rhs_domain.back().start <= min_sum) {
4533 const auto copy = arg;
4534 ct->mutable_at_most_one()->clear_literals();
4535 for (
int i = 0;
i < num_vars; ++
i) {
4536 ct->mutable_at_most_one()->add_literals(
4537 copy.coeffs(
i) > 0 ? copy.vars(
i) :
NegatedRef(copy.vars(
i)));
4541 max_sum - max_coeff >= rhs_domain.Min() &&
4542 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4543 rhs_domain.front().end >= max_sum) {
4547 const auto copy = arg;
4548 ct->mutable_at_most_one()->clear_literals();
4549 for (
int i = 0;
i < num_vars; ++
i) {
4550 ct->mutable_at_most_one()->add_literals(
4551 copy.coeffs(
i) > 0 ?
NegatedRef(copy.vars(
i)) : copy.vars(
i));
4555 min_sum < rhs_domain.Min() &&
4556 min_sum + min_coeff >= rhs_domain.Min() &&
4557 min_sum + 2 * min_coeff > rhs_domain.Max() &&
4558 min_sum + max_coeff <= rhs_domain.Max()) {
4561 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
4562 exactly_one->set_name(
ct->name());
4563 for (
int i = 0;
i < num_vars; ++
i) {
4564 exactly_one->mutable_exactly_one()->add_literals(
4568 return RemoveConstraint(
ct);
4570 max_sum > rhs_domain.Max() &&
4571 max_sum - min_coeff <= rhs_domain.Max() &&
4572 max_sum - 2 * min_coeff < rhs_domain.Min() &&
4573 max_sum - max_coeff >= rhs_domain.Min()) {
4576 ConstraintProto* exactly_one = context_->
working_model->add_constraints();
4577 exactly_one->set_name(
ct->name());
4578 for (
int i = 0;
i < num_vars; ++
i) {
4579 exactly_one->mutable_exactly_one()->add_literals(
4583 return RemoveConstraint(
ct);
4590 if (num_vars > 3)
return false;
4595 const int max_mask = (1 << arg.vars_size());
4596 for (
int mask = 0; mask < max_mask; ++mask) {
4598 for (
int i = 0;
i < num_vars; ++
i) {
4599 if ((mask >>
i) & 1)
value += arg.coeffs(
i);
4601 if (rhs_domain.Contains(
value))
continue;
4604 ConstraintProto* new_ct = context_->
working_model->add_constraints();
4605 auto* new_arg = new_ct->mutable_bool_or();
4607 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
4609 for (
int i = 0;
i < num_vars; ++
i) {
4610 new_arg->add_literals(((mask >>
i) & 1) ?
NegatedRef(arg.vars(
i))
4616 return RemoveConstraint(
ct);
4619bool CpModelPresolver::PresolveInterval(
int c, ConstraintProto*
ct) {
4621 IntervalConstraintProto*
interval =
ct->mutable_interval();
4624 if (!
ct->enforcement_literal().empty() && context_->
SizeMax(c) < 0) {
4625 context_->
UpdateRuleStats(
"interval: negative size implies unperformed");
4626 return MarkConstraintAsFalse(
ct);
4629 if (
ct->enforcement_literal().empty()) {
4630 bool domain_changed =
false;
4633 interval->size(), Domain(0, std::numeric_limits<int64_t>::max()),
4637 if (domain_changed) {
4639 "interval: performed intervals must have a positive size");
4648 return RemoveConstraint(
ct);
4651 bool changed =
false;
4652 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_start());
4653 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_size());
4654 changed |= CanonicalizeLinearExpression(*
ct,
interval->mutable_end());
4659bool CpModelPresolver::PresolveInverse(ConstraintProto*
ct) {
4660 const int size =
ct->inverse().f_direct().size();
4661 bool changed =
false;
4664 for (
const int ref :
ct->inverse().f_direct()) {
4666 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4670 for (
const int ref :
ct->inverse().f_inverse()) {
4672 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4682 absl::flat_hash_set<int> direct_vars;
4683 for (
const int ref :
ct->inverse().f_direct()) {
4684 const auto [it, inserted] = direct_vars.insert(
PositiveRef(ref));
4690 absl::flat_hash_set<int> inverse_vars;
4691 for (
const int ref :
ct->inverse().f_inverse()) {
4692 const auto [it, inserted] = inverse_vars.insert(
PositiveRef(ref));
4702 const auto filter_inverse_domain =
4703 [
this,
size, &changed](
const auto& direct,
const auto& inverse) {
4705 std::vector<absl::flat_hash_set<int64_t>> inverse_values(
size);
4706 for (
int i = 0;
i <
size; ++
i) {
4707 const Domain domain = context_->
DomainOf(inverse[
i]);
4708 for (
const int64_t j : domain.Values()) {
4709 inverse_values[
i].insert(j);
4716 std::vector<int64_t> possible_values;
4717 for (
int i = 0;
i <
size; ++
i) {
4718 possible_values.clear();
4719 const Domain domain = context_->
DomainOf(direct[
i]);
4720 bool removed_value =
false;
4721 for (
const int64_t j : domain.Values()) {
4722 if (inverse_values[j].contains(
i)) {
4723 possible_values.push_back(j);
4725 removed_value =
true;
4728 if (removed_value) {
4732 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
4740 if (!filter_inverse_domain(
ct->inverse().f_direct(),
4741 ct->inverse().f_inverse())) {
4745 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
4746 ct->inverse().f_direct())) {
4757bool CpModelPresolver::PresolveElement(ConstraintProto*
ct) {
4760 if (
ct->element().vars().empty()) {
4765 const int index_ref =
ct->element().index();
4766 const int target_ref =
ct->element().target();
4771 bool all_constants =
true;
4772 std::vector<int64_t> constants;
4773 bool all_included_in_target_domain =
true;
4777 index_ref, Domain(0,
ct->element().vars_size() - 1))) {
4785 std::vector<int64_t> possible_indices;
4786 const Domain& index_domain = context_->
DomainOf(index_ref);
4787 for (
const int64_t index_value : index_domain.Values()) {
4788 const int ref =
ct->element().vars(index_value);
4789 const int64_t target_value =
4790 target_ref == index_ref ? index_value : -index_value;
4792 possible_indices.push_back(target_value);
4795 if (possible_indices.size() < index_domain.Size()) {
4801 "element: reduced index domain when target equals index");
4807 Domain infered_domain;
4808 const Domain& initial_index_domain = context_->
DomainOf(index_ref);
4809 const Domain& target_domain = context_->
DomainOf(target_ref);
4810 std::vector<int64_t> possible_indices;
4811 for (
const int64_t
value : initial_index_domain.Values()) {
4813 CHECK_LT(
value,
ct->element().vars_size());
4814 const int ref =
ct->element().vars(
value);
4817 Domain domain = context_->
DomainOf(ref);
4818 if (ref == index_ref) {
4819 domain = Domain(
value);
4821 domain = Domain(-
value);
4826 if (domain.IntersectionWith(target_domain).IsEmpty())
continue;
4827 possible_indices.push_back(
value);
4828 if (domain.IsFixed()) {
4829 constants.push_back(domain.Min());
4831 all_constants =
false;
4833 if (!domain.IsIncludedIn(target_domain)) {
4834 all_included_in_target_domain =
false;
4836 infered_domain = infered_domain.UnionWith(domain);
4838 if (possible_indices.size() < initial_index_domain.Size()) {
4845 bool domain_modified =
false;
4847 &domain_modified)) {
4850 if (domain_modified) {
4856 if (context_->
IsFixed(index_ref)) {
4857 const int var =
ct->element().vars(context_->
MinOf(index_ref));
4858 if (
var != target_ref) {
4859 LinearConstraintProto*
const lin =
4860 context_->
working_model->add_constraints()->mutable_linear();
4862 lin->add_coeffs(-1);
4863 lin->add_vars(target_ref);
4870 return RemoveConstraint(
ct);
4876 if (all_constants && context_->
IsFixed(target_ref)) {
4878 return RemoveConstraint(
ct);
4883 if (context_->
MinOf(index_ref) == 0 && context_->
MaxOf(index_ref) == 1 &&
4885 const int64_t v0 = constants[0];
4886 const int64_t v1 = constants[1];
4888 LinearConstraintProto*
const lin =
4889 context_->
working_model->add_constraints()->mutable_linear();
4890 lin->add_vars(target_ref);
4892 lin->add_vars(index_ref);
4893 lin->add_coeffs(v0 - v1);
4894 lin->add_domain(v0);
4895 lin->add_domain(v0);
4897 context_->
UpdateRuleStats(
"element: linearize constant element of size 2");
4898 return RemoveConstraint(
ct);
4902 const AffineRelation::Relation r_index =
4904 if (r_index.representative != index_ref) {
4906 if (context_->
DomainOf(r_index.representative).
Size() >
4911 const int r_ref = r_index.representative;
4912 const int64_t r_min = context_->
MinOf(r_ref);
4913 const int64_t r_max = context_->
MaxOf(r_ref);
4914 const int array_size =
ct->element().vars_size();
4916 context_->
UpdateRuleStats(
"TODO element: representative has bad domain");
4917 }
else if (r_index.offset >= 0 && r_index.offset < array_size &&
4918 r_index.offset + r_max * r_index.coeff >= 0 &&
4919 r_index.offset + r_max * r_index.coeff < array_size) {
4921 ElementConstraintProto*
const element =
4922 context_->
working_model->add_constraints()->mutable_element();
4923 for (int64_t v = 0; v <= r_max; ++v) {
4924 const int64_t scaled_index = v * r_index.coeff + r_index.offset;
4925 CHECK_GE(scaled_index, 0);
4926 CHECK_LT(scaled_index, array_size);
4927 element->add_vars(
ct->element().vars(scaled_index));
4929 element->set_index(r_ref);
4930 element->set_target(target_ref);
4932 if (r_index.coeff == 1) {
4938 return RemoveConstraint(
ct);
4943 DCHECK(!context_->
IsFixed(index_ref));
4952 absl::flat_hash_map<int, int> local_var_occurrence_counter;
4953 local_var_occurrence_counter[
PositiveRef(index_ref)]++;
4954 local_var_occurrence_counter[
PositiveRef(target_ref)]++;
4958 DCHECK_GE(
value, 0);
4959 DCHECK_LT(
value,
ct->element().vars_size());
4960 const int ref =
ct->element().vars(
value);
4966 local_var_occurrence_counter.at(
PositiveRef(index_ref)) == 1) {
4967 if (all_constants) {
4971 context_->
UpdateRuleStats(
"element: trivial target domain reduction");
4974 return RemoveConstraint(
ct);
4980 if (!context_->
IsFixed(target_ref) &&
4982 local_var_occurrence_counter.at(
PositiveRef(target_ref)) == 1) {
4983 if (all_included_in_target_domain) {
4987 return RemoveConstraint(
ct);
4996bool CpModelPresolver::PresolveTable(ConstraintProto*
ct) {
4998 if (
ct->table().vars().empty()) {
5000 return MarkConstraintAsFalse(
ct);
5003 const int initial_num_vars =
ct->table().vars_size();
5004 bool changed =
true;
5007 std::vector<AffineRelation::Relation> affine_relations;
5008 std::vector<int64_t> old_var_lb;
5009 std::vector<int64_t> old_var_ub;
5011 for (
int v = 0; v < initial_num_vars; ++v) {
5012 const int ref =
ct->table().vars(v);
5014 affine_relations.push_back(r);
5015 old_var_lb.push_back(context_->
MinOf(ref));
5016 old_var_ub.push_back(context_->
MaxOf(ref));
5017 if (r.representative != ref) {
5019 ct->mutable_table()->set_vars(v, r.representative);
5021 "table: replace variable by canonical affine one");
5030 std::vector<int> old_index_of_duplicate_to_new_index_of_first_occurrence(
5031 initial_num_vars, -1);
5033 std::vector<int> old_index_to_new_index(initial_num_vars, -1);
5036 absl::flat_hash_map<int, int> first_visit;
5037 for (
int p = 0; p < initial_num_vars; ++p) {
5038 const int ref =
ct->table().vars(p);
5040 const auto& it = first_visit.find(
var);
5041 if (it != first_visit.end()) {
5042 const int previous = it->second;
5043 old_index_of_duplicate_to_new_index_of_first_occurrence[p] = previous;
5047 ct->mutable_table()->set_vars(num_vars, ref);
5048 first_visit[
var] = num_vars;
5049 old_index_to_new_index[p] = num_vars;
5054 if (num_vars < initial_num_vars) {
5055 ct->mutable_table()->mutable_vars()->Truncate(num_vars);
5062 std::vector<std::vector<int64_t>> new_tuples;
5063 const int initial_num_tuples =
ct->table().values_size() / initial_num_vars;
5064 std::vector<absl::flat_hash_set<int64_t>> new_domains(num_vars);
5067 std::vector<int64_t> tuple(num_vars);
5068 new_tuples.reserve(initial_num_tuples);
5069 for (
int i = 0;
i < initial_num_tuples; ++
i) {
5070 bool delete_row =
false;
5072 for (
int j = 0; j < initial_num_vars; ++j) {
5073 const int64_t old_value =
ct->table().values(
i * initial_num_vars + j);
5077 if (old_value < old_var_lb[j] || old_value > old_var_ub[j]) {
5083 const AffineRelation::Relation& r = affine_relations[j];
5084 const int64_t
value = (old_value - r.offset) / r.coeff;
5085 if (
value * r.coeff + r.offset != old_value) {
5090 const int mapped_position = old_index_to_new_index[j];
5091 if (mapped_position == -1) {
5092 const int new_index_of_first_occurrence =
5093 old_index_of_duplicate_to_new_index_of_first_occurrence[j];
5094 if (
value != tuple[new_index_of_first_occurrence]) {
5099 const int ref =
ct->table().vars(mapped_position);
5104 tuple[mapped_position] =
value;
5111 new_tuples.push_back(tuple);
5112 for (
int j = 0; j < num_vars; ++j) {
5113 new_domains[j].insert(tuple[j]);
5117 if (new_tuples.size() < initial_num_tuples) {
5124 ct->mutable_table()->clear_values();
5125 for (
const std::vector<int64_t>& t : new_tuples) {
5126 for (
const int64_t v : t) {
5127 ct->mutable_table()->add_values(v);
5133 if (
ct->table().negated())
return changed;
5139 for (
int j = 0; j < num_vars; ++j) {
5140 const int ref =
ct->table().vars(j);
5144 new_domains[j].
end())),
5152 if (num_vars == 1) {
5155 return RemoveConstraint(
ct);
5160 for (
int j = 0; j < num_vars; ++j) prod *= new_domains[j].
size();
5161 if (prod == new_tuples.size()) {
5163 return RemoveConstraint(
ct);
5169 if (new_tuples.size() > 0.7 * prod) {
5171 std::vector<std::vector<int64_t>> var_to_values(num_vars);
5172 for (
int j = 0; j < num_vars; ++j) {
5173 var_to_values[j].assign(new_domains[j].begin(), new_domains[j].
end());
5175 std::vector<std::vector<int64_t>> all_tuples(prod);
5176 for (
int i = 0;
i < prod; ++
i) {
5177 all_tuples[
i].resize(num_vars);
5179 for (
int j = 0; j < num_vars; ++j) {
5180 all_tuples[
i][j] = var_to_values[j][
index % var_to_values[j].size()];
5181 index /= var_to_values[j].size();
5187 std::vector<std::vector<int64_t>> diff(prod - new_tuples.size());
5188 std::set_difference(all_tuples.begin(), all_tuples.end(),
5189 new_tuples.begin(), new_tuples.end(), diff.begin());
5192 ct->mutable_table()->set_negated(!
ct->table().negated());
5193 ct->mutable_table()->clear_values();
5194 for (
const std::vector<int64_t>& t : diff) {
5195 for (
const int64_t v : t)
ct->mutable_table()->add_values(v);
5202bool CpModelPresolver::PresolveAllDiff(ConstraintProto*
ct) {
5206 AllDifferentConstraintProto& all_diff = *
ct->mutable_all_diff();
5208 bool constraint_has_changed =
false;
5209 for (LinearExpressionProto& exp :
5210 *(
ct->mutable_all_diff()->mutable_exprs())) {
5211 constraint_has_changed |= CanonicalizeLinearExpression(*
ct, &exp);
5215 const int size = all_diff.exprs_size();
5218 return RemoveConstraint(
ct);
5222 return RemoveConstraint(
ct);
5225 bool something_was_propagated =
false;
5226 std::vector<LinearExpressionProto> kept_expressions;
5227 for (
int i = 0;
i <
size; ++
i) {
5228 if (!context_->
IsFixed(all_diff.exprs(
i))) {
5229 kept_expressions.push_back(all_diff.exprs(
i));
5233 const int64_t
value = context_->
MinOf(all_diff.exprs(
i));
5234 bool propagated =
false;
5235 for (
int j = 0; j <
size; ++j) {
5236 if (
i == j)
continue;
5239 Domain(
value).Complement())) {
5247 something_was_propagated =
true;
5254 kept_expressions.begin(), kept_expressions.end(),
5255 [](
const LinearExpressionProto& expr_a,
5256 const LinearExpressionProto& expr_b) {
5257 DCHECK_EQ(expr_a.vars_size(), 1);
5258 DCHECK_EQ(expr_b.vars_size(), 1);
5259 const int ref_a = expr_a.vars(0);
5260 const int ref_b = expr_b.vars(0);
5261 const int64_t coeff_a = expr_a.coeffs(0);
5262 const int64_t coeff_b = expr_b.coeffs(0);
5263 const int64_t abs_coeff_a = std::abs(coeff_a);
5264 const int64_t abs_coeff_b = std::abs(coeff_b);
5265 const int64_t offset_a = expr_a.offset();
5266 const int64_t offset_b = expr_b.offset();
5267 const int64_t abs_offset_a = std::abs(offset_a);
5268 const int64_t abs_offset_b = std::abs(offset_b);
5269 return std::tie(ref_a, abs_coeff_a, coeff_a, abs_offset_a, offset_a) <
5270 std::tie(ref_b, abs_coeff_b, coeff_b, abs_offset_b, offset_b);
5276 for (
int i = 1;
i < kept_expressions.size(); ++
i) {
5278 kept_expressions[
i - 1], 1)) {
5280 "Duplicate variable in all_diff");
5283 kept_expressions[
i - 1], -1)) {
5284 bool domain_modified =
false;
5286 Domain(0).Complement(),
5287 &domain_modified)) {
5290 if (domain_modified) {
5292 "all_diff: remove 0 from expression appearing with its "
5298 if (kept_expressions.size() < all_diff.exprs_size()) {
5299 all_diff.clear_exprs();
5300 for (
const LinearExpressionProto& expr : kept_expressions) {
5301 *all_diff.add_exprs() = expr;
5304 something_was_propagated =
true;
5305 constraint_has_changed =
true;
5306 if (kept_expressions.size() <= 1)
continue;
5310 CHECK_GE(all_diff.exprs_size(), 2);
5312 for (
int i = 1;
i < all_diff.exprs_size(); ++
i) {
5315 if (all_diff.exprs_size() == domain.Size()) {
5316 absl::flat_hash_map<int64_t, std::vector<LinearExpressionProto>>
5318 for (
const LinearExpressionProto& expr : all_diff.exprs()) {
5319 for (
const int64_t v : context_->
DomainOf(expr.vars(0)).
Values()) {
5320 value_to_exprs[expr.coeffs(0) * v + expr.offset()].push_back(expr);
5323 bool propagated =
false;
5324 for (
const auto& it : value_to_exprs) {
5325 if (it.second.size() == 1 && !context_->
IsFixed(it.second.front())) {
5326 const LinearExpressionProto& expr = it.second.front();
5335 "all_diff: propagated mandatory values in permutation");
5336 something_was_propagated =
true;
5339 if (!something_was_propagated)
break;
5342 return constraint_has_changed;
5349void AddImplication(
int lhs,
int rhs, CpModelProto*
proto,
5350 absl::flat_hash_map<int, int>* ref_to_bool_and) {
5351 if (ref_to_bool_and->contains(lhs)) {
5352 const int ct_index = (*ref_to_bool_and)[lhs];
5353 proto->mutable_constraints(
ct_index)->mutable_bool_and()->add_literals(rhs);
5354 }
else if (ref_to_bool_and->contains(
NegatedRef(rhs))) {
5356 proto->mutable_constraints(
ct_index)->mutable_bool_and()->add_literals(
5359 (*ref_to_bool_and)[lhs] =
proto->constraints_size();
5360 ConstraintProto*
ct =
proto->add_constraints();
5361 ct->add_enforcement_literal(lhs);
5362 ct->mutable_bool_and()->add_literals(rhs);
5366template <
typename ClauseContainer>
5367void ExtractClauses(
bool merge_into_bool_and,
5368 const std::vector<int>& index_mapping,
5369 const ClauseContainer& container, CpModelProto*
proto) {
5376 absl::flat_hash_map<int, int> ref_to_bool_and;
5377 for (
int i = 0;
i < container.NumClauses(); ++
i) {
5378 const std::vector<Literal>& clause = container.Clause(
i);
5379 if (clause.empty())
continue;
5384 if (merge_into_bool_and && clause.size() == 2) {
5385 const int var_a = index_mapping[clause[0].Variable().value()];
5386 const int var_b = index_mapping[clause[1].Variable().value()];
5387 const int ref_a = clause[0].IsPositive() ? var_a :
NegatedRef(var_a);
5388 const int ref_b = clause[1].IsPositive() ? var_b :
NegatedRef(var_b);
5394 ConstraintProto*
ct =
proto->add_constraints();
5395 ct->mutable_bool_or()->mutable_literals()->Reserve(clause.size());
5396 for (
const Literal l : clause) {
5397 const int var = index_mapping[l.Variable().value()];
5398 if (l.IsPositive()) {
5399 ct->mutable_bool_or()->add_literals(
var);
5409bool CpModelPresolver::PresolveNoOverlap(ConstraintProto*
ct) {
5411 NoOverlapConstraintProto*
proto =
ct->mutable_no_overlap();
5412 bool changed =
false;
5417 absl::flat_hash_set<int> visited_intervals;
5418 absl::flat_hash_set<int> duplicate_intervals;
5426 const int initial_num_intervals =
proto->intervals_size();
5428 visited_intervals.clear();
5430 for (
int i = 0;
i < initial_num_intervals; ++
i) {
5438 ConstraintProto* interval_ct =
5443 if (!MarkConstraintAsFalse(interval_ct)) {
5448 "no_overlap: unperform duplicate non zero-sized intervals");
5462 "no_overlap: zero the size of performed duplicate intervals");
5466 const int performed_literal = interval_ct->enforcement_literal(0);
5467 ConstraintProto* size_eq_zero =
5469 size_eq_zero->add_enforcement_literal(performed_literal);
5470 size_eq_zero->mutable_linear()->add_domain(0);
5471 size_eq_zero->mutable_linear()->add_domain(0);
5473 interval_ct->interval().size(), 1,
5474 size_eq_zero->mutable_linear());
5476 "no_overlap: make duplicate intervals as unperformed or zero "
5485 if (new_size < initial_num_intervals) {
5486 proto->mutable_intervals()->Truncate(new_size);
5493 if (
proto->intervals_size() > 1) {
5494 std::vector<IndexedInterval> indexed_intervals;
5497 indexed_intervals.push_back({
index,
5501 std::vector<std::vector<int>> components;
5504 if (components.size() > 1) {
5505 for (
const std::vector<int>& intervals : components) {
5506 if (intervals.size() <= 1)
continue;
5508 NoOverlapConstraintProto* new_no_overlap =
5509 context_->
working_model->add_constraints()->mutable_no_overlap();
5512 for (
const int i : intervals) {
5513 new_no_overlap->add_intervals(
i);
5517 context_->
UpdateRuleStats(
"no_overlap: split into disjoint components");
5518 return RemoveConstraint(
ct);
5522 std::vector<int> constant_intervals;
5523 int64_t size_min_of_non_constant_intervals =
5524 std::numeric_limits<int64_t>::max();
5525 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
5530 size_min_of_non_constant_intervals =
5531 std::min(size_min_of_non_constant_intervals,
5536 bool move_constraint_last =
false;
5537 if (!constant_intervals.empty()) {
5539 std::sort(constant_intervals.begin(), constant_intervals.end(),
5540 [
this](
int i1,
int i2) {
5541 const int64_t s1 = context_->StartMin(i1);
5542 const int64_t e1 = context_->EndMax(i1);
5543 const int64_t s2 = context_->StartMin(i2);
5544 const int64_t e2 = context_->EndMax(i2);
5545 return std::tie(s1, e1) < std::tie(s2, e2);
5551 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5552 if (context_->
EndMax(constant_intervals[
i]) >
5553 context_->
StartMin(constant_intervals[
i + 1])) {
5559 if (constant_intervals.size() ==
proto->intervals_size()) {
5561 return RemoveConstraint(
ct);
5564 absl::flat_hash_set<int> intervals_to_remove;
5568 for (
int i = 0;
i + 1 < constant_intervals.size(); ++
i) {
5570 while (
i + 1 < constant_intervals.size() &&
5571 context_->
StartMin(constant_intervals[
i + 1]) -
5572 context_->
EndMax(constant_intervals[
i]) <
5573 size_min_of_non_constant_intervals) {
5576 if (
i ==
start)
continue;
5577 for (
int j =
start; j <=
i; ++j) {
5578 intervals_to_remove.insert(constant_intervals[j]);
5580 const int64_t new_start = context_->
StartMin(constant_intervals[
start]);
5581 const int64_t new_end = context_->
EndMax(constant_intervals[
i]);
5583 IntervalConstraintProto* new_interval =
5584 context_->
working_model->add_constraints()->mutable_interval();
5585 new_interval->mutable_start()->set_offset(new_start);
5586 new_interval->mutable_size()->set_offset(new_end - new_start);
5587 new_interval->mutable_end()->set_offset(new_end);
5588 move_constraint_last =
true;
5592 if (!intervals_to_remove.empty()) {
5594 const int old_size =
proto->intervals_size();
5595 for (
int i = 0;
i < old_size; ++
i) {
5602 CHECK_LT(new_size, old_size);
5603 proto->mutable_intervals()->Truncate(new_size);
5605 "no_overlap: merge constant contiguous intervals");
5606 intervals_to_remove.clear();
5607 constant_intervals.clear();
5613 if (
proto->intervals_size() == 1) {
5615 return RemoveConstraint(
ct);
5617 if (
proto->intervals().empty()) {
5619 return RemoveConstraint(
ct);
5625 if (move_constraint_last) {
5629 return RemoveConstraint(
ct);
5635bool CpModelPresolver::PresolveNoOverlap2D(
int , ConstraintProto*
ct) {
5640 const NoOverlap2DConstraintProto&
proto =
ct->no_overlap_2d();
5641 const int initial_num_boxes =
proto.x_intervals_size();
5643 bool x_constant =
true;
5644 bool y_constant =
true;
5645 bool has_zero_sized_interval =
false;
5646 bool has_potential_zero_sized_interval =
false;
5650 std::vector<Rectangle> bounding_boxes, fixed_boxes;
5651 std::vector<RectangleInRange> non_fixed_boxes;
5652 std::vector<int> active_boxes;
5653 absl::flat_hash_set<int> fixed_item_indexes;
5654 for (
int i = 0;
i <
proto.x_intervals_size(); ++
i) {
5655 const int x_interval_index =
proto.x_intervals(
i);
5656 const int y_interval_index =
proto.y_intervals(
i);
5663 ct->mutable_no_overlap_2d()->set_x_intervals(new_size, x_interval_index);
5664 ct->mutable_no_overlap_2d()->set_y_intervals(new_size, y_interval_index);
5665 bounding_boxes.push_back(
5666 {IntegerValue(context_->
StartMin(x_interval_index)),
5667 IntegerValue(context_->
EndMax(x_interval_index)),
5668 IntegerValue(context_->
StartMin(y_interval_index)),
5669 IntegerValue(context_->
EndMax(y_interval_index))});
5670 active_boxes.push_back(new_size);
5673 context_->
SizeMax(x_interval_index) > 0 &&
5674 context_->
SizeMax(y_interval_index) > 0) {
5675 fixed_boxes.push_back(bounding_boxes.back());
5676 fixed_item_indexes.insert(new_size);
5678 non_fixed_boxes.push_back(
5679 {.box_index = new_size,
5680 .bounding_area = bounding_boxes.back(),
5681 .x_size = context_->
SizeMin(x_interval_index),
5682 .y_size = context_->
SizeMin(y_interval_index)});
5692 if (context_->
SizeMax(x_interval_index) == 0 ||
5693 context_->
SizeMax(y_interval_index) == 0) {
5694 has_zero_sized_interval =
true;
5696 if (context_->
SizeMin(x_interval_index) == 0 ||
5697 context_->
SizeMin(y_interval_index) == 0) {
5698 has_potential_zero_sized_interval =
true;
5703 bounding_boxes, absl::MakeSpan(active_boxes));
5707 const bool is_fully_connected =
5708 components.size() == 1 && components[0].size() == active_boxes.size();
5709 if (!is_fully_connected) {
5710 for (
const absl::Span<int> boxes : components) {
5711 if (boxes.size() <= 1)
continue;
5713 NoOverlap2DConstraintProto* new_no_overlap_2d =
5714 context_->
working_model->add_constraints()->mutable_no_overlap_2d();
5715 for (
const int b : boxes) {
5716 new_no_overlap_2d->add_x_intervals(
proto.x_intervals(
b));
5717 new_no_overlap_2d->add_y_intervals(
proto.y_intervals(
b));
5721 context_->
UpdateRuleStats(
"no_overlap_2d: split into disjoint components");
5722 return RemoveConstraint(
ct);
5726 if (!has_zero_sized_interval && (x_constant || y_constant)) {
5728 "no_overlap_2d: a dimension is constant, splitting into many "
5730 std::vector<IndexedInterval> indexed_intervals;
5731 for (
int i = 0;
i < new_size; ++
i) {
5734 if (x_constant) std::swap(
x,
y);
5735 indexed_intervals.push_back({
x, IntegerValue(context_->
StartMin(
y)),
5736 IntegerValue(context_->
EndMax(
y))});
5738 std::vector<std::vector<int>> no_overlaps;
5741 for (
const std::vector<int>& no_overlap : no_overlaps) {
5742 ConstraintProto* new_ct = context_->
working_model->add_constraints();
5745 for (
const int i : no_overlap) {
5746 new_ct->mutable_no_overlap()->add_intervals(
i);
5750 return RemoveConstraint(
ct);
5753 if (new_size < initial_num_boxes) {
5755 ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5756 ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5759 if (new_size == 0) {
5761 return RemoveConstraint(
ct);
5764 if (new_size == 1) {
5766 return RemoveConstraint(
ct);
5771 for (
int i = 0;
i < fixed_boxes.size(); ++
i) {
5772 const Rectangle& fixed_box = fixed_boxes[
i];
5773 for (
int j =
i + 1; j < fixed_boxes.size(); ++j) {
5774 const Rectangle& other_fixed_box = fixed_boxes[j];
5775 if (!fixed_box.IsDisjoint(other_fixed_box)) {
5777 "Two fixed boxes in no_overlap_2d overlap");
5782 if (fixed_boxes.size() == active_boxes.size()) {
5784 return RemoveConstraint(
ct);
5790 if (!has_potential_zero_sized_interval && !fixed_boxes.empty()) {
5791 const bool presolved =
5794 NoOverlap2DConstraintProto new_no_overlap_2d;
5797 const int old_size =
proto.x_intervals_size();
5798 for (
int i = 0;
i < old_size; ++
i) {
5799 if (fixed_item_indexes.contains(
i)) {
5802 new_no_overlap_2d.add_x_intervals(
proto.x_intervals(
i));
5803 new_no_overlap_2d.add_y_intervals(
proto.y_intervals(
i));
5805 for (
const Rectangle& fixed_box : fixed_boxes) {
5806 const int item_x_interval =
5808 IntervalConstraintProto* new_interval =
5809 context_->
working_model->add_constraints()->mutable_interval();
5810 new_interval->mutable_start()->set_offset(fixed_box.x_min.value());
5811 new_interval->mutable_size()->set_offset(fixed_box.SizeX().value());
5812 new_interval->mutable_end()->set_offset(fixed_box.x_max.value());
5814 const int item_y_interval =
5817 context_->
working_model->add_constraints()->mutable_interval();
5818 new_interval->mutable_start()->set_offset(fixed_box.y_min.value());
5819 new_interval->mutable_size()->set_offset(fixed_box.SizeY().value());
5820 new_interval->mutable_end()->set_offset(fixed_box.y_max.value());
5822 new_no_overlap_2d.add_x_intervals(item_x_interval);
5823 new_no_overlap_2d.add_y_intervals(item_y_interval);
5825 context_->
working_model->add_constraints()->mutable_no_overlap_2d()->Swap(
5826 &new_no_overlap_2d);
5828 context_->
UpdateRuleStats(
"no_overlap_2d: presolved fixed rectangles");
5829 return RemoveConstraint(
ct);
5832 return new_size < initial_num_boxes;
5836LinearExpressionProto ConstantExpressionProto(int64_t
value) {
5837 LinearExpressionProto expr;
5838 expr.set_offset(
value);
5843void CpModelPresolver::DetectDuplicateIntervals(
5844 int c, google::protobuf::RepeatedField<int32_t>* intervals) {
5845 interval_representative_.clear();
5846 bool changed =
false;
5847 const int size = intervals->size();
5848 for (
int i = 0;
i <
size; ++
i) {
5849 const int index = (*intervals)[
i];
5850 const auto [it, inserted] = interval_representative_.insert({
index,
index});
5851 if (it->second !=
index) {
5853 intervals->Set(
i, it->second);
5855 "intervals: change duplicate index inside constraint");
5861bool CpModelPresolver::PresolveCumulative(ConstraintProto*
ct) {
5864 CumulativeConstraintProto*
proto =
ct->mutable_cumulative();
5866 bool changed = CanonicalizeLinearExpression(*
ct,
proto->mutable_capacity());
5867 for (LinearExpressionProto& exp :
5868 *(
ct->mutable_cumulative()->mutable_demands())) {
5869 changed |= CanonicalizeLinearExpression(*
ct, &exp);
5872 const int64_t capacity_max = context_->
MaxOf(
proto->capacity());
5876 bool domain_changed =
false;
5878 proto->capacity(), Domain(0, capacity_max), &domain_changed)) {
5881 if (domain_changed) {
5891 absl::flat_hash_map<int, int> interval_to_i;
5893 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
5894 const auto [it, inserted] =
5895 interval_to_i.insert({
proto->intervals(
i), new_size});
5898 const int old_index = it->second;
5899 proto->mutable_demands(old_index)->set_offset(
5900 proto->demands(old_index).offset() +
5903 "cumulative: merged demand of identical interval");
5907 "TODO cumulative: merged demand of identical interval");
5910 proto->set_intervals(new_size,
proto->intervals(
i));
5911 *
proto->mutable_demands(new_size) =
proto->demands(
i);
5914 if (new_size < proto->intervals_size()) {
5916 proto->mutable_intervals()->Truncate(new_size);
5917 proto->mutable_demands()->erase(
5918 proto->mutable_demands()->begin() + new_size,
5919 proto->mutable_demands()->end());
5927 int num_zero_demand_removed = 0;
5928 int num_zero_size_removed = 0;
5929 int num_incompatible_intervals = 0;
5930 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
5933 const LinearExpressionProto& demand_expr =
proto->demands(
i);
5934 const int64_t demand_max = context_->
MaxOf(demand_expr);
5935 if (demand_max == 0) {
5936 num_zero_demand_removed++;
5943 num_zero_size_removed++;
5952 ConstraintProto* interval_ct =
5954 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
5955 const int literal = interval_ct->enforcement_literal(0);
5959 num_incompatible_intervals++;
5963 "cumulative: inconsistent intervals cannot be performed.");
5967 if (context_->
MinOf(demand_expr) > capacity_max) {
5970 ConstraintProto* interval_ct =
5972 DCHECK_EQ(interval_ct->enforcement_literal_size(), 1);
5973 const int literal = interval_ct->enforcement_literal(0);
5977 num_incompatible_intervals++;
5982 const ConstraintProto& interval_ct =
5989 "cumulative: zero size of performed demand that exceeds "
5991 ++num_zero_demand_removed;
5997 *
proto->mutable_demands(new_size) =
proto->demands(
i);
6001 if (new_size < proto->intervals_size()) {
6003 proto->mutable_intervals()->Truncate(new_size);
6004 proto->mutable_demands()->erase(
6005 proto->mutable_demands()->begin() + new_size,
6006 proto->mutable_demands()->end());
6009 if (num_zero_demand_removed > 0) {
6011 "cumulative: removed intervals with no demands");
6013 if (num_zero_size_removed > 0) {
6015 "cumulative: removed intervals with a size of zero");
6017 if (num_incompatible_intervals > 0) {
6019 "cumulative: removed intervals that can't be performed");
6025 for (
int i = 0;
i <
proto->demands_size(); ++
i) {
6027 const LinearExpressionProto& demand_expr =
proto->demands(
i);
6030 bool domain_changed =
false;
6035 if (domain_changed) {
6037 "cumulative: fit demand in [0..capacity_max]");
6049 if (
proto->intervals_size() > 1) {
6050 std::vector<IndexedInterval> indexed_intervals;
6053 indexed_intervals.push_back({
i, IntegerValue(context_->
StartMin(
index)),
6056 std::vector<std::vector<int>> components;
6059 if (components.size() > 1) {
6060 for (
const std::vector<int>& component : components) {
6061 CumulativeConstraintProto* new_cumulative =
6062 context_->
working_model->add_constraints()->mutable_cumulative();
6063 for (
const int i : component) {
6064 new_cumulative->add_intervals(
proto->intervals(
i));
6065 *new_cumulative->add_demands() =
proto->demands(
i);
6067 *new_cumulative->mutable_capacity() =
proto->capacity();
6070 context_->
UpdateRuleStats(
"cumulative: split into disjoint components");
6071 return RemoveConstraint(
ct);
6079 absl::btree_map<int64_t, int64_t> time_to_demand_deltas;
6080 const int64_t capacity_min = context_->
MinOf(
proto->capacity());
6081 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
6083 const int64_t demand_max = context_->
MaxOf(
proto->demands(
i));
6094 int num_possible_overloads = 0;
6095 int64_t current_load = 0;
6096 absl::flat_hash_map<int64_t, int64_t> num_possible_overloads_before;
6097 for (
const auto& it : time_to_demand_deltas) {
6098 num_possible_overloads_before[it.first] = num_possible_overloads;
6099 current_load += it.second;
6100 if (current_load > capacity_min) {
6101 ++num_possible_overloads;
6104 CHECK_EQ(current_load, 0);
6107 if (num_possible_overloads == 0) {
6109 "cumulative: max profile is always under the min capacity");
6110 return RemoveConstraint(
ct);
6120 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
6138 const int num_diff = num_possible_overloads_before.at(
end_max) -
6139 num_possible_overloads_before.at(
start_min);
6140 if (num_diff == 0)
continue;
6142 proto->set_intervals(new_size,
proto->intervals(
i));
6143 *
proto->mutable_demands(new_size) =
proto->demands(
i);
6147 if (new_size < proto->intervals_size()) {
6149 proto->mutable_intervals()->Truncate(new_size);
6150 proto->mutable_demands()->erase(
6151 proto->mutable_demands()->begin() + new_size,
6152 proto->mutable_demands()->end());
6154 "cumulative: remove never conflicting intervals.");
6158 if (
proto->intervals().empty()) {
6160 return RemoveConstraint(
ct);
6164 int64_t max_of_performed_demand_mins = 0;
6165 int64_t sum_of_max_demands = 0;
6166 for (
int i = 0;
i <
proto->intervals_size(); ++
i) {
6168 const ConstraintProto& interval_ct =
6171 const LinearExpressionProto& demand_expr =
proto->demands(
i);
6172 sum_of_max_demands += context_->
MaxOf(demand_expr);
6174 if (interval_ct.enforcement_literal().empty() &&
6176 max_of_performed_demand_mins = std::max(max_of_performed_demand_mins,
6177 context_->
MinOf(demand_expr));
6181 const LinearExpressionProto& capacity_expr =
proto->capacity();
6182 if (max_of_performed_demand_mins > context_->
MinOf(capacity_expr)) {
6185 capacity_expr, Domain(max_of_performed_demand_mins,
6186 std::numeric_limits<int64_t>::max()))) {
6191 if (max_of_performed_demand_mins > context_->
MaxOf(capacity_expr)) {
6192 context_->
UpdateRuleStats(
"cumulative: cannot fit performed demands");
6196 if (sum_of_max_demands <= context_->MinOf(capacity_expr)) {
6197 context_->
UpdateRuleStats(
"cumulative: capacity exceeds sum of demands");
6198 return RemoveConstraint(
ct);
6204 for (
int i = 0;
i <
ct->cumulative().demands_size(); ++
i) {
6205 const LinearExpressionProto& demand_expr =
ct->cumulative().demands(
i);
6206 if (!context_->
IsFixed(demand_expr)) {
6212 if (gcd == 1)
break;
6216 for (
int i = 0;
i <
ct->cumulative().demands_size(); ++
i) {
6217 const int64_t
demand = context_->
MinOf(
ct->cumulative().demands(
i));
6218 *
proto->mutable_demands(
i) = ConstantExpressionProto(
demand / gcd);
6221 const int64_t old_capacity = context_->
MinOf(
proto->capacity());
6222 *
proto->mutable_capacity() = ConstantExpressionProto(old_capacity / gcd);
6224 "cumulative: divide demands and capacity by gcd");
6228 const int num_intervals =
proto->intervals_size();
6229 const LinearExpressionProto& capacity_expr =
proto->capacity();
6231 std::vector<LinearExpressionProto> start_exprs(num_intervals);
6233 int num_duration_one = 0;
6234 int num_greater_half_capacity = 0;
6236 bool has_optional_interval =
false;
6237 for (
int i = 0;
i < num_intervals; ++
i) {
6241 const ConstraintProto&
ct =
6243 const IntervalConstraintProto&
interval =
ct.interval();
6246 const LinearExpressionProto& demand_expr =
proto->demands(
i);
6256 const int64_t demand_min = context_->
MinOf(demand_expr);
6257 const int64_t demand_max = context_->
MaxOf(demand_expr);
6258 if (demand_min > capacity_max / 2) {
6259 num_greater_half_capacity++;
6261 if (demand_min > capacity_max) {
6262 context_->
UpdateRuleStats(
"cumulative: demand_min exceeds capacity max");
6266 CHECK_EQ(
ct.enforcement_literal().size(), 1);
6272 }
else if (demand_max > capacity_max) {
6273 if (
ct.enforcement_literal().empty()) {
6275 "cumulative: demand_max exceeds capacity max.");
6278 Domain(std::numeric_limits<int64_t>::min(), capacity_max))) {
6285 "cumulative: demand_max of optional interval exceeds capacity.");
6290 if (num_greater_half_capacity == num_intervals) {
6291 if (num_duration_one == num_intervals && !has_optional_interval) {
6293 ConstraintProto* new_ct = context_->
working_model->add_constraints();
6294 auto* arg = new_ct->mutable_all_diff();
6295 for (
const LinearExpressionProto& expr : start_exprs) {
6296 *arg->add_exprs() = expr;
6298 if (!context_->
IsFixed(capacity_expr)) {
6299 const int64_t capacity_min = context_->
MinOf(capacity_expr);
6300 for (
const LinearExpressionProto& expr :
proto->demands()) {
6301 if (capacity_min >= context_->
MaxOf(expr))
continue;
6302 LinearConstraintProto* fit =
6303 context_->
working_model->add_constraints()->mutable_linear();
6305 fit->add_domain(std::numeric_limits<int64_t>::max());
6311 return RemoveConstraint(
ct);
6316 for (
int i = 0;
i <
proto->demands_size(); ++
i) {
6317 const LinearExpressionProto& demand_expr =
proto->demands(
i);
6318 const int64_t demand_max = context_->
MaxOf(demand_expr);
6319 if (demand_max > context_->
MinOf(capacity_expr)) {
6320 ConstraintProto* capacity_gt =
6322 *capacity_gt->mutable_enforcement_literal() =
6324 .enforcement_literal();
6325 capacity_gt->mutable_linear()->add_domain(0);
6326 capacity_gt->mutable_linear()->add_domain(
6327 std::numeric_limits<int64_t>::max());
6329 capacity_gt->mutable_linear());
6331 capacity_gt->mutable_linear());
6335 ConstraintProto* new_ct = context_->
working_model->add_constraints();
6336 auto* arg = new_ct->mutable_no_overlap();
6341 return RemoveConstraint(
ct);
6348bool CpModelPresolver::PresolveRoutes(ConstraintProto*
ct) {
6351 RoutesConstraintProto&
proto = *
ct->mutable_routes();
6353 const int old_size =
proto.literals_size();
6355 std::vector<bool> has_incoming_or_outgoing_arcs;
6356 const int num_arcs =
proto.literals_size();
6357 for (
int i = 0;
i < num_arcs; ++
i) {
6358 const int ref =
proto.literals(
i);
6362 if (
tail >= has_incoming_or_outgoing_arcs.size()) {
6363 has_incoming_or_outgoing_arcs.resize(
tail + 1,
false);
6365 if (
head >= has_incoming_or_outgoing_arcs.size()) {
6366 has_incoming_or_outgoing_arcs.resize(
head + 1,
false);
6373 proto.set_literals(new_size, ref);
6377 has_incoming_or_outgoing_arcs[
tail] =
true;
6378 has_incoming_or_outgoing_arcs[
head] =
true;
6381 if (old_size > 0 && new_size == 0) {
6386 "routes: graph with nodes and no arcs");
6391 for (
int n = 0; n < has_incoming_or_outgoing_arcs.size(); ++n) {
6392 if (!has_incoming_or_outgoing_arcs[n]) {
6394 "routes: node ", n,
" misses incoming or outgoing arcs"));
6398 if (new_size < num_arcs) {
6399 proto.mutable_literals()->Truncate(new_size);
6400 proto.mutable_tails()->Truncate(new_size);
6401 proto.mutable_heads()->Truncate(new_size);
6408bool CpModelPresolver::PresolveCircuit(ConstraintProto*
ct) {
6411 CircuitConstraintProto&
proto = *
ct->mutable_circuit();
6415 ct->mutable_circuit()->mutable_heads());
6419 std::vector<std::vector<int>> incoming_arcs;
6420 std::vector<std::vector<int>> outgoing_arcs;
6422 const int num_arcs =
proto.literals_size();
6423 for (
int i = 0;
i < num_arcs; ++
i) {
6424 const int ref =
proto.literals(
i);
6427 num_nodes = std::max(num_nodes, std::max(
tail,
head) + 1);
6428 if (std::max(
tail,
head) >= incoming_arcs.size()) {
6429 incoming_arcs.resize(std::max(
tail,
head) + 1);
6430 outgoing_arcs.resize(std::max(
tail,
head) + 1);
6432 incoming_arcs[
head].push_back(ref);
6433 outgoing_arcs[
tail].push_back(ref);
6437 for (
int i = 0;
i < num_nodes; ++
i) {
6438 if (incoming_arcs[
i].empty() || outgoing_arcs[
i].empty()) {
6439 return MarkConstraintAsFalse(
ct);
6448 bool loop_again =
true;
6449 int num_fixed_at_true = 0;
6450 while (loop_again) {
6452 for (
const auto* node_to_refs : {&incoming_arcs, &outgoing_arcs}) {
6453 for (
const std::vector<int>& refs : *node_to_refs) {
6454 if (refs.size() == 1) {
6456 ++num_fixed_at_true;
6465 for (
const int ref : refs) {
6475 if (num_true == 1) {
6476 for (
const int ref : refs) {
6477 if (ref != true_ref) {
6478 if (!context_->
IsFixed(ref)) {
6489 if (num_fixed_at_true > 0) {
6496 int circuit_start = -1;
6497 std::vector<int>
next(num_nodes, -1);
6498 std::vector<int> new_in_degree(num_nodes, 0);
6499 std::vector<int> new_out_degree(num_nodes, 0);
6500 for (
int i = 0;
i < num_arcs; ++
i) {
6501 const int ref =
proto.literals(
i);
6509 circuit_start =
proto.tails(
i);
6513 ++new_out_degree[
proto.tails(
i)];
6514 ++new_in_degree[
proto.heads(
i)];
6517 proto.set_literals(new_size, ref);
6527 for (
int i = 0;
i < num_nodes; ++
i) {
6528 if (new_in_degree[
i] == 0 || new_out_degree[
i] == 0) {
6534 if (circuit_start != -1) {
6535 std::vector<bool> visited(num_nodes,
false);
6536 int current = circuit_start;
6537 while (current != -1 && !visited[current]) {
6538 visited[current] =
true;
6539 current =
next[current];
6541 if (current == circuit_start) {
6544 std::vector<bool> has_self_arc(num_nodes,
false);
6545 for (
int i = 0;
i < num_arcs; ++
i) {
6546 if (visited[
proto.tails(
i)])
continue;
6548 has_self_arc[
proto.tails(
i)] =
true;
6554 for (
int n = 0; n < num_nodes; ++n) {
6555 if (!visited[n] && !has_self_arc[n]) {
6557 return MarkConstraintAsFalse(
ct);
6561 return RemoveConstraint(
ct);
6565 if (num_true == new_size) {
6567 return RemoveConstraint(
ct);
6573 for (
int i = 0;
i < num_nodes; ++
i) {
6574 for (
const std::vector<int>* arc_literals :
6575 {&incoming_arcs[
i], &outgoing_arcs[
i]}) {
6576 std::vector<int> literals;
6577 for (
const int ref : *arc_literals) {
6583 literals.push_back(ref);
6585 if (literals.size() == 2 && literals[0] !=
NegatedRef(literals[1])) {
6594 if (new_size < num_arcs) {
6595 proto.mutable_tails()->Truncate(new_size);
6596 proto.mutable_heads()->Truncate(new_size);
6597 proto.mutable_literals()->Truncate(new_size);
6604bool CpModelPresolver::PresolveAutomaton(ConstraintProto*
ct) {
6607 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
6608 if (
proto.vars_size() == 0 ||
proto.transition_label_size() == 0) {
6612 bool all_have_same_affine_relation =
true;
6613 std::vector<AffineRelation::Relation> affine_relations;
6614 for (
int v = 0; v <
proto.vars_size(); ++v) {
6615 const int var =
ct->automaton().vars(v);
6617 affine_relations.push_back(r);
6618 if (r.representative ==
var) {
6619 all_have_same_affine_relation =
false;
6622 if (v > 0 && (r.coeff != affine_relations[v - 1].coeff ||
6623 r.offset != affine_relations[v - 1].offset)) {
6624 all_have_same_affine_relation =
false;
6629 if (all_have_same_affine_relation) {
6630 for (
int v = 0; v <
proto.vars_size(); ++v) {
6633 const AffineRelation::Relation rep = affine_relations.front();
6635 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
6636 const int64_t label =
proto.transition_label(t);
6637 int64_t inverse_label = (label - rep.offset) / rep.coeff;
6638 if (inverse_label * rep.coeff + rep.offset == label) {
6639 if (new_size != t) {
6640 proto.set_transition_tail(new_size,
proto.transition_tail(t));
6641 proto.set_transition_head(new_size,
proto.transition_head(t));
6643 proto.set_transition_label(new_size, inverse_label);
6647 if (new_size <
proto.transition_tail_size()) {
6648 proto.mutable_transition_tail()->Truncate(new_size);
6649 proto.mutable_transition_label()->Truncate(new_size);
6650 proto.mutable_transition_head()->Truncate(new_size);
6657 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
6658 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
6662 bool removed_values =
false;
6664 for (
int time = 0;
time < reachable_labels.size(); ++
time) {
6668 {reachable_labels[time].begin(), reachable_labels[time].end()}),
6674 if (removed_values) {
6680 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
6681 const int64_t label =
proto.transition_label(t);
6682 if (hull.Contains(label)) {
6683 if (new_size != t) {
6684 proto.set_transition_tail(new_size,
proto.transition_tail(t));
6685 proto.set_transition_label(new_size, label);
6686 proto.set_transition_head(new_size,
proto.transition_head(t));
6691 if (new_size <
proto.transition_tail_size()) {
6692 proto.mutable_transition_tail()->Truncate(new_size);
6693 proto.mutable_transition_label()->Truncate(new_size);
6694 proto.mutable_transition_head()->Truncate(new_size);
6702bool CpModelPresolver::PresolveReservoir(ConstraintProto*
ct) {
6706 ReservoirConstraintProto&
proto = *
ct->mutable_reservoir();
6707 bool changed =
false;
6708 for (LinearExpressionProto& exp : *(
proto.mutable_time_exprs())) {
6709 changed |= CanonicalizeLinearExpression(*
ct, &exp);
6711 for (LinearExpressionProto& exp : *(
proto.mutable_level_changes())) {
6712 changed |= CanonicalizeLinearExpression(*
ct, &exp);
6715 if (
proto.active_literals().empty()) {
6717 for (
int i = 0;
i <
proto.time_exprs_size(); ++
i) {
6718 proto.add_active_literals(true_literal);
6723 const auto& demand_is_null = [&](
int i) {
6731 for (
int i = 0;
i <
proto.level_changes_size(); ++
i) {
6732 if (demand_is_null(
i)) num_zeros++;
6735 if (num_zeros > 0) {
6738 for (
int i = 0;
i <
proto.level_changes_size(); ++
i) {
6739 if (demand_is_null(
i))
continue;
6740 *
proto.mutable_level_changes(new_size) =
proto.level_changes(
i);
6741 *
proto.mutable_time_exprs(new_size) =
proto.time_exprs(
i);
6742 proto.set_active_literals(new_size,
proto.active_literals(
i));
6746 proto.mutable_level_changes()->erase(
6747 proto.mutable_level_changes()->begin() + new_size,
6748 proto.mutable_level_changes()->end());
6749 proto.mutable_time_exprs()->erase(
6750 proto.mutable_time_exprs()->begin() + new_size,
6751 proto.mutable_time_exprs()->end());
6752 proto.mutable_active_literals()->Truncate(new_size);
6755 "reservoir: remove zero level_changes or inactive events.");
6759 for (
const LinearExpressionProto& level_change :
proto.level_changes()) {
6760 if (!context_->
IsFixed(level_change))
return changed;
6763 const int num_events =
proto.level_changes_size();
6764 int64_t gcd =
proto.level_changes().empty()
6767 int num_positives = 0;
6768 int num_negatives = 0;
6769 int64_t max_sum_of_positive_level_changes = 0;
6770 int64_t min_sum_of_negative_level_changes = 0;
6771 for (
int i = 0;
i < num_events; ++
i) {
6776 max_sum_of_positive_level_changes +=
demand;
6780 min_sum_of_negative_level_changes +=
demand;
6784 if (min_sum_of_negative_level_changes >=
proto.min_level() &&
6785 max_sum_of_positive_level_changes <=
proto.max_level()) {
6787 return RemoveConstraint(
ct);
6790 if (min_sum_of_negative_level_changes >
proto.max_level() ||
6791 max_sum_of_positive_level_changes <
proto.min_level()) {
6796 if (min_sum_of_negative_level_changes >
proto.min_level()) {
6797 proto.set_min_level(min_sum_of_negative_level_changes);
6799 "reservoir: increase min_level to reachable value");
6802 if (max_sum_of_positive_level_changes <
proto.max_level()) {
6803 proto.set_max_level(max_sum_of_positive_level_changes);
6804 context_->
UpdateRuleStats(
"reservoir: reduce max_level to reachable value");
6807 if (
proto.min_level() <= 0 &&
proto.max_level() >= 0 &&
6808 (num_positives == 0 || num_negatives == 0)) {
6812 context_->
working_model->add_constraints()->mutable_linear();
6813 int64_t fixed_contrib = 0;
6814 for (
int i = 0;
i <
proto.level_changes_size(); ++
i) {
6818 const int active =
proto.active_literals(
i);
6820 sum->add_vars(active);
6824 sum->add_coeffs(-
demand);
6828 sum->add_domain(
proto.min_level() - fixed_contrib);
6829 sum->add_domain(
proto.max_level() - fixed_contrib);
6831 return RemoveConstraint(
ct);
6835 for (
int i = 0;
i <
proto.level_changes_size(); ++
i) {
6836 proto.mutable_level_changes(
i)->set_offset(
6838 proto.mutable_level_changes(
i)->clear_vars();
6839 proto.mutable_level_changes(
i)->clear_coeffs();
6845 const Domain reduced_domain = Domain({
proto.min_level(),
proto.max_level()})
6846 .InverseMultiplicationBy(gcd);
6847 proto.set_min_level(reduced_domain.Min());
6848 proto.set_max_level(reduced_domain.Max());
6850 "reservoir: simplify level_changes and levels by gcd.");
6853 if (num_positives == 1 && num_negatives > 0) {
6855 "TODO reservoir: one producer, multiple consumers.");
6858 absl::flat_hash_set<std::tuple<int, int64_t, int64_t, int>> time_active_set;
6859 for (
int i = 0;
i <
proto.level_changes_size(); ++
i) {
6860 const LinearExpressionProto&
time =
proto.time_exprs(
i);
6861 const int var = context_->
IsFixed(
time) ? std::numeric_limits<int>::min()
6864 const std::tuple<int, int64_t, int64_t, int> key = std::make_tuple(
6867 proto.active_literals(
i));
6868 if (time_active_set.contains(key)) {
6869 context_->UpdateRuleStats(
"TODO reservoir: merge synchronized events.");
6872 time_active_set.insert(key);
6882void CpModelPresolver::ConvertToBoolAnd() {
6883 absl::flat_hash_map<int, int> ref_to_bool_and;
6884 const int num_constraints = context_->
working_model->constraints_size();
6885 std::vector<int> to_remove;
6886 for (
int c = 0;
c < num_constraints; ++
c) {
6890 if (
ct.constraint_case() == ConstraintProto::kBoolOr &&
6891 ct.bool_or().literals().size() == 2) {
6895 to_remove.push_back(c);
6899 if (
ct.constraint_case() == ConstraintProto::kAtMostOne &&
6900 ct.at_most_one().literals().size() == 2) {
6901 AddImplication(
ct.at_most_one().literals(0),
6904 to_remove.push_back(c);
6910 for (
const int c : to_remove) {
6911 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
6912 CHECK(RemoveConstraint(
ct));
6919void CpModelPresolver::Probe() {
6920 auto probing_timer =
6921 std::make_unique<PresolveTimer>(__FUNCTION__, logger_, time_limit_);
6930 auto* implication_graph =
model.GetOrCreate<BinaryImplicationGraph>();
6931 auto* sat_solver =
model.GetOrCreate<SatSolver>();
6932 auto* mapping =
model.GetOrCreate<CpModelMapping>();
6933 auto* prober =
model.GetOrCreate<Prober>();
6946 const auto& assignment = sat_solver->Assignment();
6947 prober->SetPropagationCallback([&](Literal decision) {
6948 if (probing_timer->WorkLimitIsReached())
return;
6949 const int decision_var =
6950 mapping->GetProtoVariableFromBooleanVariable(decision.Variable());
6951 if (decision_var < 0)
return;
6952 probing_timer->TrackSimpleLoop(
6954 std::vector<int> to_update;
6956 if (c < 0)
continue;
6958 if (
ct.enforcement_literal().size() > 2) {
6972 bool decision_is_positive =
false;
6973 bool has_false_literal =
false;
6974 bool simplification_possible =
false;
6975 probing_timer->TrackSimpleLoop(
ct.enforcement_literal().size());
6976 for (
const int ref :
ct.enforcement_literal()) {
6977 const Literal
lit = mapping->Literal(ref);
6980 decision_is_positive = assignment.LiteralIsTrue(
lit);
6981 if (!decision_is_positive)
break;
6984 if (assignment.LiteralIsFalse(
lit)) {
6986 has_false_literal =
true;
6987 }
else if (assignment.LiteralIsTrue(
lit)) {
6989 simplification_possible =
true;
6992 if (!decision_is_positive)
continue;
6994 if (has_false_literal) {
6996 auto* mutable_ct = context_->
working_model->mutable_constraints(c);
6997 mutable_ct->Clear();
6998 mutable_ct->add_enforcement_literal(decision_ref);
6999 mutable_ct->mutable_bool_and()->add_literals(
NegatedRef(false_ref));
7001 "probing: reduced enforced constraint to implication.");
7002 to_update.push_back(c);
7006 if (simplification_possible) {
7008 auto* mutable_enforcements =
7010 ->mutable_enforcement_literal();
7011 for (
const int ref :
ct.enforcement_literal()) {
7013 assignment.LiteralIsTrue(mapping->Literal(ref))) {
7016 mutable_enforcements->Set(new_size++, ref);
7018 mutable_enforcements->Truncate(new_size);
7020 to_update.push_back(c);
7025 if (
ct.constraint_case() != ConstraintProto::kBoolOr)
continue;
7026 if (
ct.bool_or().literals().size() <= 2)
continue;
7030 bool decision_is_negative =
false;
7031 bool has_true_literal =
false;
7032 bool simplification_possible =
false;
7033 probing_timer->TrackSimpleLoop(
ct.bool_or().literals().size());
7034 for (
const int ref :
ct.bool_or().literals()) {
7035 const Literal
lit = mapping->Literal(ref);
7038 decision_is_negative = assignment.LiteralIsFalse(
lit);
7039 if (!decision_is_negative)
break;
7042 if (assignment.LiteralIsTrue(
lit)) {
7044 has_true_literal =
true;
7045 }
else if (assignment.LiteralIsFalse(
lit)) {
7047 simplification_possible =
true;
7050 if (!decision_is_negative)
continue;
7052 if (has_true_literal) {
7055 auto* mutable_bool_or =
7056 context_->
working_model->mutable_constraints(c)->mutable_bool_or();
7057 mutable_bool_or->mutable_literals()->Clear();
7058 mutable_bool_or->add_literals(decision_ref);
7059 mutable_bool_or->add_literals(true_ref);
7061 to_update.push_back(c);
7065 if (simplification_possible) {
7067 auto* mutable_bool_or =
7068 context_->
working_model->mutable_constraints(c)->mutable_bool_or();
7069 for (
const int ref :
ct.bool_or().literals()) {
7071 assignment.LiteralIsFalse(mapping->Literal(ref))) {
7074 mutable_bool_or->set_literals(new_size++, ref);
7076 mutable_bool_or->mutable_literals()->Truncate(new_size);
7078 to_update.push_back(c);
7082 absl::c_sort(to_update);
7083 for (
const int c : to_update) {
7088 prober->ProbeBooleanVariables(
7089 context_->
params().probing_deterministic_time_limit());
7091 probing_timer->AddCounter(
"probed", prober->num_decisions());
7092 probing_timer->AddToWork(
7093 model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7094 if (sat_solver->ModelIsUnsat() || !implication_graph->DetectEquivalences()) {
7100 CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
7101 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
7102 const Literal l = sat_solver->LiteralTrail()[
i];
7103 const int var = mapping->GetProtoVariableFromBooleanVariable(l.Variable());
7106 if (context_->
IsFixed(ref))
continue;
7111 probing_timer->AddCounter(
"fixed_bools", num_fixed);
7113 DetectDuplicateConstraintsWithDifferentEnforcements(
7114 mapping, implication_graph,
model.GetOrCreate<Trail>());
7117 int num_changed_bounds = 0;
7118 const int num_variables = context_->
working_model->variables().size();
7119 auto* integer_trail =
model.GetOrCreate<IntegerTrail>();
7120 for (
int var = 0;
var < num_variables; ++
var) {
7123 if (!mapping->IsBoolean(
var)) {
7124 bool changed =
false;
7126 var, integer_trail->InitialVariableDomain(mapping->Integer(
var)),
7130 if (changed) ++num_changed_bounds;
7135 const Literal l = mapping->Literal(
var);
7136 const Literal r = implication_graph->RepresentativeOf(l);
7140 mapping->GetProtoVariableFromBooleanVariable(r.Variable());
7146 probing_timer->AddCounter(
"new_bounds", num_changed_bounds);
7147 probing_timer->AddCounter(
"equiv", num_equiv);
7148 probing_timer->AddCounter(
"new_binary_clauses",
7149 prober->num_new_binary_clauses());
7152 probing_timer.reset();
7155 if (context_->
params().merge_at_most_one_work_limit() > 0.0) {
7156 PresolveTimer timer(
"MaxClique", logger_, time_limit_);
7157 std::vector<std::vector<Literal>> cliques;
7159 int64_t num_literals_before = 0;
7160 const int num_constraints = context_->
working_model->constraints_size();
7161 for (
int c = 0;
c < num_constraints; ++
c) {
7162 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
7163 if (
ct->constraint_case() == ConstraintProto::kAtMostOne) {
7164 std::vector<Literal> clique;
7165 for (
const int ref :
ct->at_most_one().literals()) {
7166 clique.push_back(mapping->Literal(ref));
7168 num_literals_before += clique.size();
7169 cliques.push_back(clique);
7172 }
else if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
7173 if (
ct->enforcement_literal().size() != 1)
continue;
7174 const Literal enforcement =
7175 mapping->Literal(
ct->enforcement_literal(0));
7176 for (
const int ref :
ct->bool_and().literals()) {
7177 if (ref ==
ct->enforcement_literal(0))
continue;
7178 num_literals_before += 2;
7179 cliques.push_back({enforcement, mapping->Literal(ref).Negated()});
7185 const int64_t num_old_cliques = cliques.size();
7187 implication_graph->TransformIntoMaxCliques(
7194 int num_new_cliques = 0;
7195 int64_t num_literals_after = 0;
7196 for (
const std::vector<Literal>& clique : cliques) {
7197 if (clique.empty())
continue;
7199 num_literals_after += clique.size();
7201 for (
const Literal
literal : clique) {
7203 mapping->GetProtoVariableFromBooleanVariable(
literal.Variable());
7204 if (
var < 0)
continue;
7206 ct->mutable_at_most_one()->add_literals(
var);
7213 PresolveAtMostOne(
ct);
7216 if (num_new_cliques != num_old_cliques) {
7217 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
7220 if (num_old_cliques != num_new_cliques ||
7221 num_literals_before != num_literals_after) {
7222 timer.AddMessage(absl::StrCat(
"Merged ", num_old_cliques,
"(",
7223 num_literals_before,
" literals) into ",
7224 num_new_cliques,
"(", num_literals_after,
7225 " literals) at_most_ones. "));
7232bool FixFromAssignment(
const VariablesAssignment& assignment,
7233 const std::vector<int>& var_mapping,
7235 const int num_vars = assignment.NumberOfVariables();
7236 for (
int i = 0;
i < num_vars; ++
i) {
7237 const Literal
lit(BooleanVariable(
i),
true);
7238 const int ref = var_mapping[
i];
7239 if (assignment.LiteralIsTrue(
lit)) {
7240 if (!
context->SetLiteralToTrue(ref))
return false;
7241 }
else if (assignment.LiteralIsFalse(
lit)) {
7242 if (!
context->SetLiteralToFalse(ref))
return false;
7252bool CpModelPresolver::PresolvePureSatPart() {
7260 int num_variables = 0;
7261 int num_ignored_variables = 0;
7262 const int total_num_vars = context_->
working_model->variables().size();
7263 std::vector<int> new_index(total_num_vars, -1);
7264 std::vector<int> new_to_old_index;
7265 for (
int i = 0;
i < total_num_vars; ++
i) {
7267 ++num_ignored_variables;
7274 new_to_old_index.push_back(
i);
7275 new_index[
i] = num_variables++;
7276 DCHECK_EQ(num_variables, new_to_old_index.size());
7280 auto convert = [&new_index](
int ref) {
7282 DCHECK_NE(
index, -1);
7298 local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
7299 auto* sat_solver = local_model.GetOrCreate<SatSolver>();
7300 sat_solver->SetNumVariables(num_variables);
7305 for (
const int var : new_to_old_index) {
7308 if (!sat_solver->AddUnitClause({convert(var)}))
return false;
7310 if (!sat_solver->AddUnitClause({convert(NegatedRef(var))})) {
7317 std::vector<Literal> clause;
7318 int num_removed_constraints = 0;
7319 int num_ignored_constraints = 0;
7323 if (
ct.constraint_case() == ConstraintProto::kBoolOr) {
7324 ++num_removed_constraints;
7326 for (
const int ref :
ct.bool_or().literals()) {
7327 clause.push_back(convert(ref));
7329 for (
const int ref :
ct.enforcement_literal()) {
7330 clause.push_back(convert(ref).Negated());
7332 sat_solver->AddProblemClause(clause,
false);
7339 if (
ct.constraint_case() == ConstraintProto::kBoolAnd) {
7342 const int left_size =
ct.enforcement_literal().size();
7343 const int right_size =
ct.bool_and().literals().size();
7344 if (left_size > 1 && right_size > 1 &&
7345 (left_size + 1) * right_size > 10'000) {
7346 ++num_ignored_constraints;
7350 ++num_removed_constraints;
7351 std::vector<Literal> clause;
7352 for (
const int ref :
ct.enforcement_literal()) {
7353 clause.push_back(convert(ref).Negated());
7356 for (
const int ref :
ct.bool_and().literals()) {
7357 clause.back() = convert(ref);
7358 sat_solver->AddProblemClause(clause,
false);
7366 if (
ct.constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
7370 ++num_ignored_constraints;
7372 if (sat_solver->ModelIsUnsat())
return false;
7375 if (num_removed_constraints == 0)
return true;
7385 int num_in_extra_constraints = 0;
7386 std::vector<bool> can_be_removed(num_variables,
false);
7387 for (
int i = 0;
i < num_variables; ++
i) {
7388 const int var = new_to_old_index[
i];
7390 can_be_removed[
i] =
true;
7394 ++num_in_extra_constraints;
7407 SatParameters params = context_->
params();
7408 if (params.debug_postsolve_with_full_solver() ||
7409 params.fill_tightened_domains_in_response()) {
7410 params.set_presolve_blocked_clause(
false);
7413 SatPostsolver sat_postsolver(num_variables);
7424 if (!context_->
params().debug_postsolve_with_full_solver() &&
7425 num_ignored_variables == 0 && num_ignored_constraints == 0 &&
7426 num_in_extra_constraints == 0) {
7432 &local_model, logger_)) {
7435 if (sat_solver->LiteralTrail().Index() == num_variables) {
7437 CHECK(FixFromAssignment(sat_solver->Assignment(), new_to_old_index,
7442 SatPresolveOptions options;
7443 options.log_info =
true;
7444 options.extract_binary_clauses_in_probing =
false;
7445 options.use_transitive_reduction =
false;
7446 options.deterministic_time_limit =
7447 context_->
params().presolve_probing_deterministic_time_limit();
7449 auto* inprocessing = local_model.GetOrCreate<Inprocessing>();
7450 inprocessing->ProvideLogger(logger_);
7451 if (!inprocessing->PresolveLoop(options))
return false;
7452 for (
const auto& c : local_model.GetOrCreate<PostsolveClauses>()->clauses) {
7453 sat_postsolver.Add(c[0], c);
7459 nullptr, &equiv_map,
7461 if (sat_solver->ModelIsUnsat())
return false;
7466 params.set_presolve_use_bva(
false);
7470 if (!sat_solver->ResetToLevelZero())
return false;
7472 local_model.GetOrCreate<TimeLimit>()->GetElapsedDeterministicTime());
7475 SatPresolver sat_presolver(&sat_postsolver, logger_);
7476 sat_presolver.SetNumVariables(num_variables);
7477 if (!equiv_map.empty()) {
7478 sat_presolver.SetEquivalentLiteralMapping(equiv_map);
7480 sat_presolver.SetTimeLimit(time_limit_);
7481 sat_presolver.SetParameters(params);
7485 for (
int i = 0;
i < sat_solver->LiteralTrail().
Index(); ++
i) {
7486 sat_postsolver.FixVariable(sat_solver->LiteralTrail()[
i]);
7488 sat_solver->ExtractClauses(&sat_presolver);
7492 for (
int i = 0;
i < 1; ++
i) {
7493 const int old_num_clause = sat_postsolver.NumClauses();
7494 if (!sat_presolver.Presolve(can_be_removed))
return false;
7495 if (old_num_clause == sat_postsolver.NumClauses())
break;
7499 const int new_num_variables = sat_presolver.NumVariables();
7500 if (new_num_variables > num_variables) {
7501 VLOG(1) <<
"New variables added by the SAT presolver.";
7502 for (
int i = num_variables;
i < new_num_variables; ++
i) {
7503 new_to_old_index.push_back(context_->
working_model->variables().size());
7504 IntegerVariableProto* var_proto =
7506 var_proto->add_domain(0);
7507 var_proto->add_domain(1);
7513 if (!FixFromAssignment(sat_postsolver.assignment(), new_to_old_index,
7519 ExtractClauses(
true, new_to_old_index, sat_presolver,
7528 ExtractClauses(
false, new_to_old_index,
7533void CpModelPresolver::ShiftObjectiveWithExactlyOnes() {
7542 std::vector<int> exos;
7543 const int num_constraints = context_->
working_model->constraints_size();
7544 for (
int c = 0;
c < num_constraints; ++
c) {
7546 if (!
ct.enforcement_literal().empty())
continue;
7547 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
7564 for (
int i = 0;
i < 3; ++
i) {
7565 for (
const int c : exos) {
7567 const int num_terms =
ct.exactly_one().literals().size();
7568 if (num_terms <= 1)
continue;
7569 int64_t min_obj = std::numeric_limits<int64_t>::max();
7570 int64_t second_min = std::numeric_limits<int64_t>::max();
7571 for (
int i = 0;
i < num_terms; ++
i) {
7572 const int literal =
ct.exactly_one().literals(
i);
7575 if (obj < min_obj) {
7576 second_min = min_obj;
7578 }
else if (obj < second_min) {
7582 if (second_min == 0)
continue;
7591 if (num_shifts > 0) {
7592 context_->
UpdateRuleStats(
"objective: shifted cost with exactly ones",
7611void CpModelPresolver::ExpandObjective() {
7614 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
7622 const int num_variables = context_->
working_model->variables_size();
7623 const int num_constraints = context_->
working_model->constraints_size();
7626 const auto get_index = [](
int var,
bool to_lb) {
7627 return 2 *
var + (to_lb ? 0 : 1);
7629 const auto get_lit_index = [](
int lit) {
7632 const int num_nodes = 2 * num_variables;
7633 std::vector<std::vector<int>> index_graph(num_nodes);
7637 std::vector<int> index_to_best_c(num_nodes, -1);
7638 std::vector<int> index_to_best_size(num_nodes, 0);
7642 int num_entries = 0;
7643 int num_propagations = 0;
7644 int num_tight_variables = 0;
7645 int num_tight_constraints = 0;
7646 const int kNumEntriesThreshold = 1e8;
7647 for (
int c = 0;
c < num_constraints; ++
c) {
7648 if (num_entries > kNumEntriesThreshold)
break;
7651 if (!
ct.enforcement_literal().empty())
continue;
7659 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
7660 if (PresolveExactlyOne(context_->
working_model->mutable_constraints(c))) {
7664 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
7665 const int num_terms =
ct.exactly_one().literals().size();
7666 ++num_tight_constraints;
7667 num_tight_variables += num_terms;
7668 for (
int i = 0;
i < num_terms; ++
i) {
7669 if (num_entries > kNumEntriesThreshold)
break;
7670 const int neg_index = get_lit_index(
ct.exactly_one().literals(
i)) ^ 1;
7672 const int old_c = index_to_best_c[neg_index];
7673 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
7674 index_to_best_c[neg_index] =
c;
7675 index_to_best_size[neg_index] = num_terms;
7678 for (
int j = 0; j < num_terms; ++j) {
7679 if (j ==
i)
continue;
7680 const int other_index = get_lit_index(
ct.exactly_one().literals(j));
7682 index_graph[neg_index].push_back(other_index);
7689 if (!IsLinearEqualityConstraint(
ct))
continue;
7693 const auto [min_activity, max_activity] =
7696 bool is_tight =
false;
7697 const int64_t rhs =
ct.linear().domain(0);
7698 const int num_terms =
ct.linear().vars_size();
7699 for (
int i = 0;
i < num_terms; ++
i) {
7700 const int var =
ct.linear().vars(
i);
7701 const int64_t coeff =
ct.linear().coeffs(
i);
7702 if (std::abs(coeff) != 1)
continue;
7703 if (num_entries > kNumEntriesThreshold)
break;
7705 const int index = get_index(
var, coeff > 0);
7708 const int64_t implied_shifted_ub = rhs - min_activity;
7709 if (implied_shifted_ub <= var_range) {
7710 if (implied_shifted_ub < var_range) ++num_propagations;
7712 ++num_tight_variables;
7714 const int neg_index =
index ^ 1;
7715 const int old_c = index_to_best_c[neg_index];
7716 if (old_c == -1 || num_terms > index_to_best_size[neg_index]) {
7717 index_to_best_c[neg_index] =
c;
7718 index_to_best_size[neg_index] = num_terms;
7721 for (
int j = 0; j < num_terms; ++j) {
7722 if (j ==
i)
continue;
7723 const int other_index =
7724 get_index(
ct.linear().vars(j),
ct.linear().coeffs(j) > 0);
7726 index_graph[neg_index].push_back(other_index);
7729 const int64_t implied_shifted_lb = max_activity - rhs;
7730 if (implied_shifted_lb <= var_range) {
7731 if (implied_shifted_lb < var_range) ++num_propagations;
7733 ++num_tight_variables;
7735 const int old_c = index_to_best_c[
index];
7736 if (old_c == -1 || num_terms > index_to_best_size[
index]) {
7737 index_to_best_c[
index] =
c;
7738 index_to_best_size[
index] = num_terms;
7741 for (
int j = 0; j < num_terms; ++j) {
7742 if (j ==
i)
continue;
7743 const int other_index =
7744 get_index(
ct.linear().vars(j),
ct.linear().coeffs(j) < 0);
7746 index_graph[
index].push_back(other_index);
7750 if (is_tight) ++num_tight_constraints;
7756 if (num_propagations > 0) {
7776 if (!topo_order.ok()) {
7779 std::vector<int64_t> var_min(num_variables);
7780 std::vector<int64_t> var_max(num_variables);
7781 for (
int var = 0;
var < num_variables; ++
var) {
7786 std::vector<std::vector<int>> components;
7788 index_graph, &components);
7789 for (
const std::vector<int>& compo : components) {
7790 if (compo.size() == 1)
continue;
7792 const int rep_var = compo[0] / 2;
7793 const bool rep_to_lp = (compo[0] % 2) == 0;
7794 for (
int i = 1;
i < compo.size(); ++
i) {
7795 const int var = compo[
i] / 2;
7796 const bool to_lb = (compo[
i] % 2) == 0;
7800 const int64_t rep_coeff = rep_to_lp ? 1 : -1;
7801 const int64_t var_coeff = to_lb ? 1 : -1;
7802 const int64_t offset =
7803 (to_lb ? -var_min[
var] : var_max[
var]) -
7804 (rep_to_lp ? -var_min[rep_var] : var_max[rep_var]);
7806 rep_coeff * offset)) {
7819 int num_expands = 0;
7821 for (
const int index : *topo_order) {
7822 if (index_graph[
index].empty())
continue;
7826 if (obj_coeff == 0)
continue;
7828 const bool to_lb = (
index % 2) == 0;
7829 if (obj_coeff > 0 == to_lb) {
7830 const ConstraintProto&
ct =
7832 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
7834 for (
const int lit :
ct.exactly_one().literals()) {
7855 int64_t objective_coeff_in_expanded_constraint = 0;
7856 const int num_terms =
ct.linear().vars().size();
7857 for (
int i = 0;
i < num_terms; ++
i) {
7858 if (
ct.linear().vars(
i) ==
var) {
7859 objective_coeff_in_expanded_constraint =
ct.linear().coeffs(
i);
7863 if (objective_coeff_in_expanded_constraint == 0) {
7869 var, objective_coeff_in_expanded_constraint,
ct)) {
7879 if (num_expands > 0) {
7884 timer.AddCounter(
"propagations", num_propagations);
7885 timer.AddCounter(
"entries", num_entries);
7886 timer.AddCounter(
"tight_variables", num_tight_variables);
7887 timer.AddCounter(
"tight_constraints", num_tight_constraints);
7888 timer.AddCounter(
"expands", num_expands);
7889 timer.AddCounter(
"issues", num_issues);
7892void CpModelPresolver::MergeNoOverlapConstraints() {
7895 const int num_constraints = context_->
working_model->constraints_size();
7896 int old_num_no_overlaps = 0;
7897 int old_num_intervals = 0;
7900 std::vector<int> disjunctive_index;
7901 std::vector<std::vector<Literal>> cliques;
7902 for (
int c = 0;
c < num_constraints; ++
c) {
7904 if (
ct.constraint_case() != ConstraintProto::kNoOverlap)
continue;
7905 std::vector<Literal> clique;
7906 for (
const int i :
ct.no_overlap().intervals()) {
7907 clique.push_back(Literal(BooleanVariable(
i),
true));
7909 cliques.push_back(clique);
7910 disjunctive_index.push_back(c);
7912 old_num_no_overlaps++;
7913 old_num_intervals += clique.size();
7915 if (old_num_no_overlaps == 0)
return;
7919 local_model.GetOrCreate<Trail>()->Resize(num_constraints);
7920 auto*
graph = local_model.GetOrCreate<BinaryImplicationGraph>();
7921 graph->Resize(num_constraints);
7922 for (
const std::vector<Literal>& clique : cliques) {
7925 CHECK(
graph->AddAtMostOne(clique));
7927 CHECK(
graph->DetectEquivalences());
7928 graph->TransformIntoMaxCliques(
7933 int new_num_no_overlaps = 0;
7934 int new_num_intervals = 0;
7935 for (
int i = 0;
i < cliques.size(); ++
i) {
7936 const int ct_index = disjunctive_index[
i];
7937 ConstraintProto*
ct =
7940 if (cliques[
i].empty())
continue;
7941 for (
const Literal l : cliques[
i]) {
7942 CHECK(l.IsPositive());
7943 ct->mutable_no_overlap()->add_intervals(l.Variable().value());
7945 new_num_no_overlaps++;
7946 new_num_intervals += cliques[
i].size();
7948 if (old_num_intervals != new_num_intervals ||
7949 old_num_no_overlaps != new_num_no_overlaps) {
7950 VLOG(1) << absl::StrCat(
"Merged ", old_num_no_overlaps,
" no-overlaps (",
7951 old_num_intervals,
" intervals) into ",
7952 new_num_no_overlaps,
" no-overlaps (",
7953 new_num_intervals,
" intervals).");
7962void CpModelPresolver::TransformIntoMaxCliques() {
7964 if (context_->
params().merge_at_most_one_work_limit() <= 0.0)
return;
7966 auto convert = [](
int ref) {
7967 if (
RefIsPositive(ref))
return Literal(BooleanVariable(ref),
true);
7968 return Literal(BooleanVariable(
NegatedRef(ref)),
false);
7970 const int num_constraints = context_->
working_model->constraints_size();
7974 std::vector<std::vector<Literal>> cliques;
7976 for (
int c = 0;
c < num_constraints; ++
c) {
7977 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
7978 if (
ct->constraint_case() == ConstraintProto::kAtMostOne) {
7979 std::vector<Literal> clique;
7980 for (
const int ref :
ct->at_most_one().literals()) {
7981 clique.push_back(convert(ref));
7983 cliques.push_back(clique);
7984 if (RemoveConstraint(
ct)) {
7987 }
else if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
7988 if (
ct->enforcement_literal().size() != 1)
continue;
7989 const Literal enforcement = convert(
ct->enforcement_literal(0));
7990 for (
const int ref :
ct->bool_and().literals()) {
7991 if (ref ==
ct->enforcement_literal(0))
continue;
7992 cliques.push_back({enforcement, convert(ref).Negated()});
7994 if (RemoveConstraint(
ct)) {
8000 int64_t num_literals_before = 0;
8001 const int num_old_cliques = cliques.size();
8005 const int num_variables = context_->
working_model->variables().size();
8006 local_model.GetOrCreate<Trail>()->Resize(num_variables);
8007 auto*
graph = local_model.GetOrCreate<BinaryImplicationGraph>();
8008 graph->Resize(num_variables);
8009 for (
const std::vector<Literal>& clique : cliques) {
8010 num_literals_before += clique.size();
8011 if (!
graph->AddAtMostOne(clique)) {
8015 if (!
graph->DetectEquivalences()) {
8018 graph->TransformIntoMaxCliques(
8025 for (
int var = 0;
var < num_variables; ++
var) {
8026 const Literal l = Literal(BooleanVariable(
var),
true);
8027 if (
graph->RepresentativeOf(l) != l) {
8028 const Literal r =
graph->RepresentativeOf(l);
8030 var, r.IsPositive() ? r.Variable().value()
8035 int num_new_cliques = 0;
8036 int64_t num_literals_after = 0;
8037 for (
const std::vector<Literal>& clique : cliques) {
8038 if (clique.empty())
continue;
8040 num_literals_after += clique.size();
8042 for (
const Literal
literal : clique) {
8044 ct->mutable_at_most_one()->add_literals(
literal.Variable().value());
8046 ct->mutable_at_most_one()->add_literals(
8052 PresolveAtMostOne(
ct);
8055 if (num_new_cliques != num_old_cliques) {
8056 context_->
UpdateRuleStats(
"at_most_one: transformed into max clique.");
8059 if (num_old_cliques != num_new_cliques ||
8060 num_literals_before != num_literals_after) {
8061 SOLVER_LOG(logger_,
"[MaxClique] Merged ", num_old_cliques,
"(",
8062 num_literals_before,
" literals) into ", num_new_cliques,
"(",
8063 num_literals_after,
" literals) at_most_ones.");
8069 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
8072 if (ExploitEquivalenceRelations(c,
ct)) {
8077 if (PresolveEnforcementLiteral(
ct)) {
8082 switch (
ct->constraint_case()) {
8083 case ConstraintProto::kBoolOr:
8084 return PresolveBoolOr(
ct);
8085 case ConstraintProto::kBoolAnd:
8086 return PresolveBoolAnd(
ct);
8087 case ConstraintProto::kAtMostOne:
8088 return PresolveAtMostOne(
ct);
8089 case ConstraintProto::kExactlyOne:
8090 return PresolveExactlyOne(
ct);
8091 case ConstraintProto::kBoolXor:
8092 return PresolveBoolXor(
ct);
8093 case ConstraintProto::kLinMax:
8094 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_lin_max())) {
8097 if (!DivideLinMaxByGcd(c,
ct))
return false;
8098 return PresolveLinMax(
ct);
8099 case ConstraintProto::kIntProd:
8100 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_prod())) {
8103 return PresolveIntProd(
ct);
8104 case ConstraintProto::kIntDiv:
8105 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_div())) {
8108 return PresolveIntDiv(c,
ct);
8109 case ConstraintProto::kIntMod:
8110 if (CanonicalizeLinearArgument(*
ct,
ct->mutable_int_mod())) {
8113 return PresolveIntMod(c,
ct);
8114 case ConstraintProto::kLinear: {
8115 if (CanonicalizeLinear(
ct)) {
8118 if (PropagateDomainsInLinear(c,
ct)) {
8121 if (PresolveSmallLinear(
ct)) {
8124 if (PresolveLinearEqualityWithModulo(
ct)) {
8128 if (RemoveSingletonInLinear(
ct)) {
8133 if (PresolveSmallLinear(
ct)) {
8137 if (PresolveSmallLinear(
ct)) {
8140 if (PresolveLinearOnBooleans(
ct)) {
8145 const int old_num_enforcement_literals =
ct->enforcement_literal_size();
8146 ExtractEnforcementLiteralFromLinearConstraint(c,
ct);
8148 if (
ct->enforcement_literal_size() > old_num_enforcement_literals) {
8149 if (DivideLinearByGcd(
ct)) {
8152 if (PresolveSmallLinear(
ct)) {
8157 if (PresolveDiophantine(
ct)) {
8161 TryToReduceCoefficientsOfLinearConstraint(c,
ct);
8164 case ConstraintProto::kInterval:
8165 return PresolveInterval(c,
ct);
8166 case ConstraintProto::kInverse:
8167 return PresolveInverse(
ct);
8168 case ConstraintProto::kElement:
8169 return PresolveElement(
ct);
8170 case ConstraintProto::kTable:
8171 return PresolveTable(
ct);
8172 case ConstraintProto::kAllDiff:
8173 return PresolveAllDiff(
ct);
8174 case ConstraintProto::kNoOverlap:
8175 DetectDuplicateIntervals(c,
8176 ct->mutable_no_overlap()->mutable_intervals());
8177 return PresolveNoOverlap(
ct);
8178 case ConstraintProto::kNoOverlap2D:
8179 DetectDuplicateIntervals(
8180 c,
ct->mutable_no_overlap_2d()->mutable_x_intervals());
8181 DetectDuplicateIntervals(
8182 c,
ct->mutable_no_overlap_2d()->mutable_y_intervals());
8183 return PresolveNoOverlap2D(c,
ct);
8184 case ConstraintProto::kCumulative:
8185 DetectDuplicateIntervals(c,
8186 ct->mutable_cumulative()->mutable_intervals());
8187 return PresolveCumulative(
ct);
8188 case ConstraintProto::kCircuit:
8189 return PresolveCircuit(
ct);
8190 case ConstraintProto::kRoutes:
8191 return PresolveRoutes(
ct);
8192 case ConstraintProto::kAutomaton:
8193 return PresolveAutomaton(
ct);
8194 case ConstraintProto::kReservoir:
8195 return PresolveReservoir(
ct);
8202bool CpModelPresolver::ProcessSetPPCSubset(
int subset_c,
int superset_c,
8203 absl::flat_hash_set<int>* tmp_set,
8204 bool* remove_subset,
8205 bool* remove_superset,
8206 bool* stop_processing_superset) {
8207 ConstraintProto* subset_ct =
8209 ConstraintProto* superset_ct =
8212 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8213 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8214 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8215 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8219 if (subset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8220 tmp_set->insert(subset_ct->bool_or().literals().begin(),
8221 subset_ct->bool_or().literals().end());
8223 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8224 subset_ct->exactly_one().literals().end());
8230 superset_ct->constraint_case() == ConstraintProto::kAtMostOne
8231 ? superset_ct->at_most_one().literals()
8232 : superset_ct->exactly_one().literals()) {
8233 if (tmp_set->contains(
literal))
continue;
8239 if (superset_ct->constraint_case() != ConstraintProto::kExactlyOne) {
8240 ConstraintProto copy = *superset_ct;
8241 (*superset_ct->mutable_exactly_one()->mutable_literals()) =
8242 copy.at_most_one().literals();
8245 *remove_subset =
true;
8249 if ((subset_ct->constraint_case() == ConstraintProto::kBoolOr ||
8250 subset_ct->constraint_case() == ConstraintProto::kExactlyOne) &&
8251 superset_ct->constraint_case() == ConstraintProto::kBoolOr) {
8253 *remove_superset =
true;
8257 if (subset_ct->constraint_case() == ConstraintProto::kAtMostOne &&
8258 (superset_ct->constraint_case() == ConstraintProto::kAtMostOne ||
8259 superset_ct->constraint_case() == ConstraintProto::kExactlyOne)) {
8261 *remove_subset =
true;
8267 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne &&
8268 superset_ct->constraint_case() == ConstraintProto::kLinear) {
8270 int64_t min_sum = std::numeric_limits<int64_t>::max();
8271 int64_t max_sum = std::numeric_limits<int64_t>::min();
8272 tmp_set->insert(subset_ct->exactly_one().literals().begin(),
8273 subset_ct->exactly_one().literals().end());
8276 int num_matches = 0;
8278 Domain reachable(0);
8279 std::vector<std::pair<int64_t, int>> coeff_counts;
8280 for (
int i = 0;
i < superset_ct->linear().vars().
size(); ++
i) {
8281 const int var = superset_ct->linear().vars(
i);
8282 const int64_t coeff = superset_ct->linear().coeffs(
i);
8283 if (tmp_set->contains(
var)) {
8285 min_sum = std::min(min_sum, coeff);
8286 max_sum = std::max(max_sum, coeff);
8287 coeff_counts.push_back({superset_ct->linear().coeffs(
i), 1});
8293 .RelaxIfTooComplex();
8294 temp_ct_.mutable_linear()->add_vars(
var);
8295 temp_ct_.mutable_linear()->add_coeffs(coeff);
8305 if (num_matches != tmp_set->size())
return true;
8306 if (subset_ct->constraint_case() == ConstraintProto::kExactlyOne) {
8312 reachable = reachable.AdditionWith(Domain(min_sum, max_sum));
8314 if (reachable.IsIncludedIn(superset_rhs)) {
8316 context_->
UpdateRuleStats(
"setppc: removed trivial linear constraint");
8317 *remove_superset =
true;
8320 if (reachable.IntersectionWith(superset_rhs).IsEmpty()) {
8322 context_->
UpdateRuleStats(
"setppc: removed infeasible linear constraint");
8323 *stop_processing_superset =
true;
8324 return MarkConstraintAsFalse(superset_ct);
8329 if (superset_ct->enforcement_literal().empty()) {
8330 CHECK_GT(num_matches, 0);
8333 temp_ct_.mutable_linear());
8334 PropagateDomainsInLinear(-1, &temp_ct_);
8340 std::sort(coeff_counts.begin(), coeff_counts.end());
8342 for (
int i = 0;
i < coeff_counts.size(); ++
i) {
8344 coeff_counts[
i].first == coeff_counts[new_size - 1].first) {
8345 coeff_counts[new_size - 1].second++;
8348 coeff_counts[new_size++] = coeff_counts[
i];
8350 coeff_counts.resize(new_size);
8352 int64_t best_count = 0;
8353 for (
const auto [coeff, count] : coeff_counts) {
8354 if (count > best_count) {
8360 LinearConstraintProto new_ct = superset_ct->linear();
8362 for (
int i = 0;
i < new_ct.vars().
size(); ++
i) {
8363 const int var = new_ct.vars(
i);
8364 int64_t coeff = new_ct.coeffs(
i);
8365 if (tmp_set->contains(
var)) {
8366 if (coeff == best)
continue;
8369 new_ct.set_vars(new_size,
var);
8370 new_ct.set_coeffs(new_size, coeff);
8374 new_ct.mutable_vars()->Truncate(new_size);
8375 new_ct.mutable_coeffs()->Truncate(new_size);
8380 *superset_ct->mutable_linear() = std::move(new_ct);
8397void CpModelPresolver::ProcessSetPPC() {
8400 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
8401 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8404 CompactVectorVector<int> storage;
8406 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
8409 std::vector<int> temp_literals;
8410 const int num_constraints = context_->
working_model->constraints_size();
8411 std::vector<int> relevant_constraints;
8412 for (
int c = 0;
c < num_constraints; ++
c) {
8413 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
8414 const auto type =
ct->constraint_case();
8415 if (type == ConstraintProto::kBoolOr ||
8416 type == ConstraintProto::kAtMostOne ||
8417 type == ConstraintProto::kExactlyOne) {
8426 temp_literals.clear();
8427 for (
const int ref :
8428 type == ConstraintProto::kAtMostOne ?
ct->at_most_one().literals()
8429 : type == ConstraintProto::kBoolOr ?
ct->bool_or().literals()
8430 :
ct->exactly_one().literals()) {
8431 temp_literals.push_back(
8436 relevant_constraints.push_back(c);
8437 detector.AddPotentialSet(storage.Add(temp_literals));
8438 }
else if (type == ConstraintProto::kLinear) {
8448 const int size =
ct->linear().vars().size();
8449 if (
size <= 2)
continue;
8454 temp_literals.clear();
8455 for (
int i = 0;
i <
size; ++
i) {
8456 const int var =
ct->linear().vars(
i);
8459 temp_literals.push_back(
8462 if (temp_literals.size() > 2) {
8464 relevant_constraints.push_back(c);
8465 detector.AddPotentialSuperset(storage.Add(temp_literals));
8470 absl::flat_hash_set<int> tmp_set;
8471 int64_t num_inclusions = 0;
8472 detector.DetectInclusions([&](
int subset,
int superset) {
8474 bool remove_subset =
false;
8475 bool remove_superset =
false;
8476 bool stop_processing_superset =
false;
8477 const int subset_c = relevant_constraints[subset];
8478 const int superset_c = relevant_constraints[superset];
8479 detector.IncreaseWorkDone(storage[subset].
size());
8480 detector.IncreaseWorkDone(storage[superset].
size());
8481 if (!ProcessSetPPCSubset(subset_c, superset_c, &tmp_set, &remove_subset,
8482 &remove_superset, &stop_processing_superset)) {
8486 if (remove_subset) {
8487 context_->
working_model->mutable_constraints(subset_c)->Clear();
8489 detector.StopProcessingCurrentSubset();
8491 if (remove_superset) {
8492 context_->
working_model->mutable_constraints(superset_c)->Clear();
8494 detector.StopProcessingCurrentSuperset();
8496 if (stop_processing_superset) {
8498 detector.StopProcessingCurrentSuperset();
8502 timer.AddToWork(detector.work_done() * 1e-9);
8503 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
8504 timer.AddCounter(
"num_inclusions", num_inclusions);
8507void CpModelPresolver::DetectIncludedEnforcement() {
8510 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
8511 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8514 std::vector<int> relevant_constraints;
8515 CompactVectorVector<int> storage;
8517 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
8519 std::vector<int> temp_literals;
8520 const int num_constraints = context_->
working_model->constraints_size();
8521 for (
int c = 0;
c < num_constraints; ++
c) {
8522 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
8523 if (
ct->enforcement_literal().size() <= 1)
continue;
8526 if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
8534 temp_literals.clear();
8535 for (
const int ref :
ct->enforcement_literal()) {
8536 temp_literals.push_back(
8541 relevant_constraints.push_back(c);
8545 if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
8546 detector.AddPotentialSet(storage.Add(temp_literals));
8548 detector.AddPotentialSuperset(storage.Add(temp_literals));
8552 int64_t num_inclusions = 0;
8553 detector.DetectInclusions([&](
int subset,
int superset) {
8555 const int subset_c = relevant_constraints[subset];
8556 const int superset_c = relevant_constraints[superset];
8557 ConstraintProto* subset_ct =
8559 ConstraintProto* superset_ct =
8561 if (subset_ct->constraint_case() != ConstraintProto::kBoolAnd)
return;
8564 for (
const int ref : subset_ct->bool_and().literals()) {
8571 for (
const int ref : superset_ct->enforcement_literal()) {
8576 superset_ct->Clear();
8578 detector.StopProcessingCurrentSuperset();
8581 superset_ct->set_enforcement_literal(new_size++, ref);
8584 if (new_size < superset_ct->bool_and().literals().
size()) {
8586 superset_ct->mutable_enforcement_literal()->Truncate(new_size);
8590 if (superset_ct->constraint_case() == ConstraintProto::kBoolAnd) {
8592 for (
const int ref : superset_ct->bool_and().literals()) {
8597 if (!MarkConstraintAsFalse(superset_ct))
return;
8599 detector.StopProcessingCurrentSuperset();
8602 superset_ct->mutable_bool_and()->set_literals(new_size++, ref);
8605 if (new_size < superset_ct->bool_and().literals().
size()) {
8607 superset_ct->mutable_bool_and()->mutable_literals()->Truncate(new_size);
8611 if (superset_ct->constraint_case() == ConstraintProto::kLinear) {
8616 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
8617 timer.AddCounter(
"relevant_constraints", relevant_constraints.size());
8618 timer.AddCounter(
"num_inclusions", num_inclusions);
8629bool CpModelPresolver::ProcessEncodingFromLinear(
8630 const int linear_encoding_ct_index,
8631 const ConstraintProto& at_most_or_exactly_one, int64_t* num_unique_terms,
8632 int64_t* num_multiple_terms) {
8634 bool in_exactly_one =
false;
8635 absl::flat_hash_map<int, int> var_to_ref;
8636 if (at_most_or_exactly_one.constraint_case() == ConstraintProto::kAtMostOne) {
8637 for (
const int ref : at_most_or_exactly_one.at_most_one().literals()) {
8642 CHECK_EQ(at_most_or_exactly_one.constraint_case(),
8643 ConstraintProto::kExactlyOne);
8644 in_exactly_one =
true;
8645 for (
const int ref : at_most_or_exactly_one.exactly_one().literals()) {
8652 const ConstraintProto& linear_encoding =
8653 context_->
working_model->constraints(linear_encoding_ct_index);
8654 int64_t rhs = linear_encoding.linear().domain(0);
8655 int target_ref = std::numeric_limits<int>::min();
8656 std::vector<std::pair<int, int64_t>> ref_to_coeffs;
8657 const int num_terms = linear_encoding.linear().vars().size();
8658 for (
int i = 0;
i < num_terms; ++
i) {
8659 const int ref = linear_encoding.linear().vars(
i);
8660 const int64_t coeff = linear_encoding.linear().coeffs(
i);
8661 const auto it = var_to_ref.find(
PositiveRef(ref));
8663 if (it == var_to_ref.end()) {
8664 CHECK_EQ(target_ref, std::numeric_limits<int>::min()) <<
"Uniqueness";
8665 CHECK_EQ(std::abs(coeff), 1);
8666 target_ref = coeff == 1 ? ref :
NegatedRef(ref);
8672 if (it->second == ref) {
8674 ref_to_coeffs.push_back({ref, coeff});
8678 ref_to_coeffs.push_back({
NegatedRef(ref), -coeff});
8681 if (target_ref == std::numeric_limits<int>::min() ||
8686 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
8691 std::vector<int64_t> all_values;
8692 absl::btree_map<int64_t, std::vector<int>> value_to_refs;
8693 for (
const auto& [ref, coeff] : ref_to_coeffs) {
8694 const int64_t
value = rhs - coeff;
8695 all_values.push_back(
value);
8696 value_to_refs[
value].push_back(ref);
8700 for (
const auto& [
var, ref] : var_to_ref) {
8701 all_values.push_back(rhs);
8702 value_to_refs[rhs].push_back(ref);
8704 if (!in_exactly_one) {
8707 all_values.push_back(rhs);
8712 bool domain_reduced =
false;
8716 if (domain_reduced) {
8722 context_->
UpdateRuleStats(
"encoding: candidate linear is all Boolean now.");
8727 absl::flat_hash_set<int64_t> value_set;
8728 for (
const int64_t v : context_->
DomainOf(target_ref).
Values()) {
8729 value_set.insert(v);
8731 for (
auto& [
value, literals] : value_to_refs) {
8733 absl::c_sort(literals);
8736 if (!value_set.contains(
value)) {
8737 for (
const int lit : literals) {
8743 if (literals.size() == 1 && (in_exactly_one ||
value != rhs)) {
8746 ++*num_unique_terms;
8751 ++*num_multiple_terms;
8752 const int associated_lit =
8754 for (
const int lit : literals) {
8760 if (in_exactly_one ||
value != rhs) {
8766 context_->
working_model->add_constraints()->mutable_bool_or();
8767 for (
const int lit : literals) bool_or->add_literals(
lit);
8768 bool_or->add_literals(
NegatedRef(associated_lit));
8774 context_->
working_model->mutable_constraints(linear_encoding_ct_index)
8781void CpModelPresolver::DetectDuplicateConstraints() {
8784 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8797 const std::vector<std::pair<int, int>> duplicates =
8799 timer.AddCounter(
"duplicates", duplicates.size());
8800 for (
const auto& [dup, rep] : duplicates) {
8807 : context_->
working_model->constraints(rep).constraint_case();
8811 if (type == ConstraintProto::kLinear) {
8816 if (rep_domain != d) {
8817 context_->
UpdateRuleStats(
"duplicate: merged rhs of linear constraint");
8818 const Domain rhs = rep_domain.IntersectionWith(d);
8819 if (rhs.IsEmpty()) {
8820 if (!MarkConstraintAsFalse(
8822 SOLVER_LOG(logger_,
"Unsat after merging two linear constraints");
8833 ->mutable_linear());
8839 "duplicate: linear constraint parallel to objective");
8840 const Domain objective_domain =
8844 if (objective_domain != d) {
8846 const Domain new_domain = objective_domain.IntersectionWith(d);
8847 if (new_domain.IsEmpty()) {
8849 "Constraint parallel to the objective makes the objective domain "
8865void CpModelPresolver::DetectDuplicateConstraintsWithDifferentEnforcements(
8866 const CpModelMapping* mapping, BinaryImplicationGraph* implication_graph,
8870 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
8878 absl::flat_hash_set<Literal> enforcement_vars;
8879 std::vector<std::pair<Literal, Literal>> implications_used;
8884 const std::vector<std::pair<int, int>> duplicates_without_enforcement =
8886 timer.AddCounter(
"without_enforcements",
8887 duplicates_without_enforcement.size());
8888 for (
const auto& [dup, rep] : duplicates_without_enforcement) {
8889 auto* dup_ct = context_->
working_model->mutable_constraints(dup);
8890 auto* rep_ct = context_->
working_model->mutable_constraints(rep);
8891 if (rep_ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
8898 if (trail !=
nullptr) {
8899 bool found_false_enforcement =
false;
8900 for (
const int c : {dup, rep}) {
8902 context_->
working_model->constraints(c).enforcement_literal()) {
8903 if (trail->Assignment().LiteralIsFalse(mapping->Literal(l))) {
8904 found_false_enforcement =
true;
8908 if (found_false_enforcement) {
8911 rep_ct->Swap(dup_ct);
8919 if (found_false_enforcement) {
8926 if (dup_ct->enforcement_literal().empty() ||
8927 rep_ct->enforcement_literal().empty()) {
8929 rep_ct->mutable_enforcement_literal()->Clear();
8939 const int a = rep_ct->enforcement_literal(0);
8940 const int b = dup_ct->enforcement_literal(0);
8978 if (rep_ct->enforcement_literal().size() > 1 ||
8979 dup_ct->enforcement_literal().size() > 1) {
8981 "TODO duplicate: identical constraint with unique enforcement "
8988 context_->
UpdateRuleStats(
"duplicate: dual equivalence of enforcement");
8993 if (dup_ct->enforcement_literal().size() == 1 &&
8994 rep_ct->enforcement_literal().size() == 1) {
9002 if (implication_graph !=
nullptr && mapping !=
nullptr &&
9004 for (
int i = 0;
i < 2;
i++) {
9008 const int c_a =
i == 0 ? dup : rep;
9009 const int c_b =
i == 0 ? rep : dup;
9011 enforcement_vars.clear();
9012 implications_used.clear();
9013 for (
const int proto_lit :
9014 context_->
working_model->constraints(c_b).enforcement_literal()) {
9015 const Literal
lit = mapping->Literal(proto_lit);
9016 if (trail->Assignment().LiteralIsTrue(
lit))
continue;
9017 enforcement_vars.insert(
lit);
9019 for (
const int proto_lit :
9020 context_->
working_model->constraints(c_a).enforcement_literal()) {
9021 const Literal
lit = mapping->Literal(proto_lit);
9022 if (trail->Assignment().LiteralIsTrue(
lit))
continue;
9023 for (
const Literal implication_lit :
9024 implication_graph->DirectImplications(
lit)) {
9025 auto extracted = enforcement_vars.extract(implication_lit);
9026 if (!extracted.empty() &&
lit != implication_lit) {
9027 implications_used.push_back({
lit, implication_lit});
9031 if (enforcement_vars.empty()) {
9033 "duplicate: identical constraint with implied enforcements");
9037 rep_ct->Swap(dup_ct);
9045 for (
const auto& [
a,
b] : implications_used) {
9047 mapping->GetProtoVariableFromBooleanVariable(
a.Variable());
9048 const int proto_lit_a =
a.IsPositive() ? var_a :
NegatedRef(var_a);
9050 mapping->GetProtoVariableFromBooleanVariable(
b.Variable());
9051 const int proto_lit_b =
b.IsPositive() ? var_b :
NegatedRef(var_b);
9062void CpModelPresolver::DetectDifferentVariables() {
9065 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9069 std::vector<std::pair<int, int>> different_vars;
9070 absl::flat_hash_map<std::pair<int, int>, std::pair<int64_t, int64_t>> offsets;
9073 const auto process_difference = [&different_vars, &offsets](
int v1,
int v2,
9075 Domain exclusion = d.Complement().PartAroundZero();
9076 if (exclusion.IsEmpty())
return;
9077 if (v1 == v2)
return;
9078 std::pair<int, int> key = {v1, v2};
9080 std::swap(key.first, key.second);
9081 exclusion = exclusion.Negation();
9087 different_vars.push_back(key);
9088 offsets[key] = {exclusion.Min() == std::numeric_limits<int64_t>::min()
9089 ? std::numeric_limits<int64_t>::max()
9090 :
CapAdd(-exclusion.Min(), 1),
9091 CapAdd(exclusion.Max(), 1)};
9112 bool has_all_diff =
false;
9113 bool has_no_overlap =
false;
9114 std::vector<std::pair<uint64_t, int>> hashes;
9115 const int num_constraints = context_->
working_model->constraints_size();
9116 for (
int c = 0;
c < num_constraints; ++
c) {
9118 if (
ct.constraint_case() == ConstraintProto::kAllDiff) {
9119 has_all_diff =
true;
9122 if (
ct.constraint_case() == ConstraintProto::kNoOverlap) {
9123 has_no_overlap =
true;
9126 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
9127 if (
ct.linear().vars().size() == 1)
continue;
9131 if (
ct.linear().vars().size() == 2 &&
ct.enforcement_literal().empty() &&
9132 ct.linear().coeffs(0) == -
ct.linear().coeffs(1)) {
9134 if (
ct.linear().coeffs(0) == 1) {
9135 process_difference(
ct.linear().vars(0),
ct.linear().vars(1),
9137 }
else if (
ct.linear().coeffs(0) == -1) {
9138 process_difference(
ct.linear().vars(1),
ct.linear().vars(0),
9144 if (
ct.enforcement_literal().size() > 1)
continue;
9149 hashes.push_back({
hash,
c});
9151 std::sort(hashes.begin(), hashes.end());
9154 while (
next < hashes.size() && hashes[
next].first == hashes[
start].first) {
9157 absl::Span<const std::pair<uint64_t, int>>
range(&hashes[
start],
9159 if (
range.size() <= 1)
continue;
9160 if (
range.size() > 10)
continue;
9162 for (
int i = 0;
i <
range.size(); ++
i) {
9163 const ConstraintProto& ct1 =
9165 const int num_terms = ct1.linear().vars().size();
9166 for (
int j =
i + 1; j <
range.size(); ++j) {
9167 const ConstraintProto& ct2 =
9169 if (ct2.linear().vars().size() != num_terms)
continue;
9175 if (absl::MakeSpan(ct1.linear().vars().data(), num_terms) !=
9176 absl::MakeSpan(ct2.linear().vars().data(), num_terms)) {
9179 if (absl::MakeSpan(ct1.linear().coeffs().data(), num_terms) !=
9180 absl::MakeSpan(ct2.linear().coeffs().data(), num_terms)) {
9184 if (ct1.enforcement_literal().empty() &&
9185 ct2.enforcement_literal().empty()) {
9187 "two incompatible linear constraint");
9190 if (ct1.enforcement_literal().empty()) {
9192 "incompatible linear: set enforcement to false");
9198 if (ct2.enforcement_literal().empty()) {
9200 "incompatible linear: set enforcement to false");
9207 const int lit1 = ct1.enforcement_literal(0);
9208 const int lit2 = ct2.enforcement_literal(0);
9211 if (ct1.linear().vars().size() == 2 &&
9212 ct1.linear().coeffs(0) == -ct1.linear().coeffs(1) &&
9215 Domain union_of_domain =
9220 if (ct1.linear().coeffs(0) == 1) {
9221 process_difference(ct1.linear().vars(0), ct1.linear().vars(1),
9222 std::move(union_of_domain));
9223 }
else if (ct1.linear().coeffs(0) == -1) {
9224 process_difference(ct1.linear().vars(1), ct1.linear().vars(0),
9225 union_of_domain.Negation());
9252 if (context_->
params().infer_all_diffs() && !has_all_diff &&
9253 !has_no_overlap && different_vars.size() > 2) {
9257 std::vector<std::vector<Literal>> cliques;
9258 absl::flat_hash_set<int> used_var;
9261 const int num_variables = context_->
working_model->variables().size();
9262 local_model.GetOrCreate<Trail>()->Resize(num_variables);
9263 auto*
graph = local_model.GetOrCreate<BinaryImplicationGraph>();
9264 graph->Resize(num_variables);
9265 for (
const auto [var1, var2] : different_vars) {
9274 CHECK(
graph->AddAtMostOne({Literal(BooleanVariable(var1), true),
9275 Literal(BooleanVariable(var2), true)}));
9276 if (!used_var.contains(var1)) {
9277 used_var.insert(var1);
9278 cliques.push_back({Literal(BooleanVariable(var1),
true),
9279 Literal(BooleanVariable(var2),
true)});
9281 if (!used_var.contains(var2)) {
9282 used_var.insert(var2);
9283 cliques.push_back({Literal(BooleanVariable(var1),
true),
9284 Literal(BooleanVariable(var2),
true)});
9287 CHECK(
graph->DetectEquivalences());
9288 graph->TransformIntoMaxCliques(&cliques, 1e8);
9290 int num_cliques = 0;
9291 int64_t cumulative_size = 0;
9292 for (std::vector<Literal>& clique : cliques) {
9293 if (clique.size() <= 2)
continue;
9296 cumulative_size += clique.size();
9297 std::sort(clique.begin(), clique.end());
9303 const int num_terms = clique.size();
9304 std::vector<int64_t> sizes(num_terms,
9305 std::numeric_limits<int64_t>::max());
9306 for (
int i = 0;
i < num_terms; ++
i) {
9307 const int v1 = clique[
i].Variable().value();
9308 for (
int j =
i + 1; j < num_terms; ++j) {
9309 const int v2 = clique[j].Variable().value();
9310 const auto [o1, o2] = offsets.at({v1, v2});
9311 sizes[
i] = std::min(sizes[
i], o1);
9312 sizes[j] = std::min(sizes[j], o2);
9316 int num_greater_than_one = 0;
9318 for (
int i = 0;
i < num_terms; ++
i) {
9319 CHECK_GE(sizes[
i], 1);
9320 if (sizes[
i] > 1) ++num_greater_than_one;
9325 issue =
CapAdd(issue, sizes[
i]);
9326 if (issue == std::numeric_limits<int64_t>::max()) {
9328 num_greater_than_one = 0;
9333 if (num_greater_than_one > 0) {
9340 "no_overlap: inferred from x != y constraints");
9342 std::vector<int> intervals;
9343 for (
int i = 0;
i < num_terms; ++
i) {
9344 intervals.push_back(context_->
working_model->constraints().size());
9345 auto* new_interval =
9346 context_->
working_model->add_constraints()->mutable_interval();
9347 new_interval->mutable_start()->set_offset(0);
9348 new_interval->mutable_start()->add_coeffs(1);
9349 new_interval->mutable_start()->add_vars(clique[
i].Variable().
value());
9351 new_interval->mutable_size()->set_offset(sizes[
i]);
9353 new_interval->mutable_end()->set_offset(sizes[
i]);
9354 new_interval->mutable_end()->add_coeffs(1);
9355 new_interval->mutable_end()->add_vars(clique[
i].Variable().
value());
9358 context_->
working_model->add_constraints()->mutable_no_overlap();
9359 for (
const int interval : intervals) {
9363 context_->
UpdateRuleStats(
"all_diff: inferred from x != y constraints");
9365 context_->
working_model->add_constraints()->mutable_all_diff();
9366 for (
const Literal l : clique) {
9367 auto* expr = new_ct->add_exprs();
9368 expr->add_vars(l.Variable().value());
9369 expr->add_coeffs(1);
9374 timer.AddCounter(
"different", different_vars.size());
9375 timer.AddCounter(
"cliques", num_cliques);
9376 timer.AddCounter(
"size", cumulative_size);
9385void Substitute(int64_t factor,
9386 const absl::flat_hash_map<int, int64_t>& subset_coeff_map,
9387 const Domain& subset_rhs,
const Domain& superset_rhs,
9388 LinearConstraintProto* mutable_linear) {
9390 const int old_size = mutable_linear->vars().size();
9391 for (
int i = 0;
i < old_size; ++
i) {
9392 const int var = mutable_linear->vars(
i);
9393 int64_t coeff = mutable_linear->coeffs(
i);
9394 const auto it = subset_coeff_map.find(
var);
9395 if (it != subset_coeff_map.end()) {
9396 coeff += factor * it->second;
9397 if (coeff == 0)
continue;
9400 mutable_linear->set_vars(new_size,
var);
9401 mutable_linear->set_coeffs(new_size, coeff);
9404 mutable_linear->mutable_vars()->Truncate(new_size);
9405 mutable_linear->mutable_coeffs()->Truncate(new_size);
9407 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(factor)),
9413void CpModelPresolver::DetectDominatedLinearConstraints() {
9416 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
9417 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9425 explicit Storage(CpModelProto*
proto) : proto_(*
proto) {}
9426 int size()
const {
return static_cast<int>(proto_.constraints().
size()); }
9427 absl::Span<const int> operator[](
int c)
const {
9428 return absl::MakeSpan(proto_.constraints(c).linear().vars());
9432 const CpModelProto& proto_;
9436 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
9440 std::vector<int> constraint_indices_to_clean;
9444 absl::flat_hash_map<int, Domain> cached_expr_domain;
9446 const int num_constraints = context_->
working_model->constraints().size();
9447 for (
int c = 0;
c < num_constraints; ++
c) {
9449 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
9453 if (!
ct.enforcement_literal().empty()) {
9454 if (
ct.linear().vars().size() < 3)
continue;
9457 if (!LinearConstraintIsClean(
ct.linear())) {
9464 detector.AddPotentialSet(c);
9466 const auto [min_activity, max_activity] =
9468 cached_expr_domain[
c] = Domain(min_activity, max_activity);
9471 int64_t num_inclusions = 0;
9472 absl::flat_hash_map<int, int64_t> coeff_map;
9473 detector.DetectInclusions([&](
int subset_c,
int superset_c) {
9477 const ConstraintProto& subset_ct =
9479 const LinearConstraintProto& subset_lin = subset_ct.linear();
9481 detector.IncreaseWorkDone(subset_lin.vars().size());
9482 for (
int i = 0;
i < subset_lin.vars().
size(); ++
i) {
9483 coeff_map[subset_lin.vars(
i)] = subset_lin.coeffs(
i);
9489 bool perfect_match =
true;
9493 int64_t min_pos_factor = std::numeric_limits<int64_t>::max();
9494 int64_t max_neg_factor = std::numeric_limits<int64_t>::min();
9500 const ConstraintProto& superset_ct =
9502 const LinearConstraintProto& superset_lin = superset_ct.linear();
9503 int64_t diff_min_activity = 0;
9504 int64_t diff_max_activity = 0;
9505 detector.IncreaseWorkDone(superset_lin.vars().size());
9506 for (
int i = 0;
i < superset_lin.vars().
size(); ++
i) {
9507 const int var = superset_lin.vars(
i);
9508 int64_t coeff = superset_lin.coeffs(
i);
9509 const auto it = coeff_map.find(
var);
9511 if (it != coeff_map.end()) {
9512 const int64_t subset_coeff = it->second;
9514 const int64_t div = coeff / subset_coeff;
9516 min_pos_factor = std::min(div, min_pos_factor);
9518 max_neg_factor = std::max(div, max_neg_factor);
9521 if (perfect_match) {
9522 if (coeff % subset_coeff == 0) {
9526 }
else if (factor != div) {
9527 perfect_match =
false;
9530 perfect_match =
false;
9535 coeff -= subset_coeff;
9537 if (coeff == 0)
continue;
9539 &diff_max_activity);
9542 const Domain diff_domain(diff_min_activity, diff_max_activity);
9553 if (subset_ct.enforcement_literal().empty()) {
9554 const Domain implied_superset_domain =
9555 subset_rhs.AdditionWith(diff_domain)
9556 .IntersectionWith(cached_expr_domain[superset_c]);
9557 if (implied_superset_domain.IsIncludedIn(superset_rhs)) {
9559 "linear inclusion: redundant containing constraint");
9560 context_->
working_model->mutable_constraints(superset_c)->Clear();
9561 constraint_indices_to_clean.push_back(superset_c);
9562 detector.StopProcessingCurrentSuperset();
9568 if (superset_ct.enforcement_literal().empty()) {
9569 const Domain implied_subset_domain =
9570 superset_rhs.AdditionWith(diff_domain.Negation())
9571 .IntersectionWith(cached_expr_domain[subset_c]);
9572 if (implied_subset_domain.IsIncludedIn(subset_rhs)) {
9574 "linear inclusion: redundant included constraint");
9575 context_->
working_model->mutable_constraints(subset_c)->Clear();
9576 constraint_indices_to_clean.push_back(subset_c);
9577 detector.StopProcessingCurrentSubset();
9586 if (subset_rhs.IsFixed() && subset_ct.enforcement_literal().empty()) {
9587 const int64_t best_factor =
9588 max_neg_factor > -min_pos_factor ? max_neg_factor : min_pos_factor;
9596 bool is_tigher =
true;
9597 if (min_pos_factor != std::numeric_limits<int64_t>::max() &&
9598 max_neg_factor != std::numeric_limits<int64_t>::min()) {
9599 int64_t min_before = 0;
9600 int64_t max_before = 0;
9601 int64_t min_after =
CapProd(best_factor, subset_rhs.FixedValue());
9602 int64_t max_after = min_after;
9603 for (
int i = 0;
i < superset_lin.vars().
size(); ++
i) {
9604 const int var = superset_lin.vars(
i);
9605 const auto it = coeff_map.find(
var);
9606 if (it == coeff_map.end())
continue;
9608 const int64_t coeff_before = superset_lin.coeffs(
i);
9609 const int64_t coeff_after = coeff_before - best_factor * it->second;
9615 is_tigher = min_after >= min_before && max_after <= max_before;
9619 Substitute(-best_factor, coeff_map, subset_rhs, superset_rhs,
9621 ->mutable_linear());
9622 constraint_indices_to_clean.push_back(superset_c);
9623 detector.StopProcessingCurrentSuperset();
9630 if (perfect_match && subset_ct.enforcement_literal().empty() &&
9631 superset_ct.enforcement_literal().empty()) {
9632 CHECK_NE(factor, 0);
9638 auto* mutable_linear = temp_ct_.mutable_linear();
9639 for (
int i = 0;
i < superset_lin.vars().
size(); ++
i) {
9640 const int var = superset_lin.vars(
i);
9641 const int64_t coeff = superset_lin.coeffs(
i);
9642 const auto it = coeff_map.find(
var);
9643 if (it != coeff_map.end())
continue;
9644 mutable_linear->add_vars(
var);
9645 mutable_linear->add_coeffs(coeff);
9648 superset_rhs.AdditionWith(subset_rhs.MultiplicationBy(-factor)),
9650 PropagateDomainsInLinear(-1, &temp_ct_);
9653 if (superset_rhs.IsFixed()) {
9654 if (subset_lin.vars().size() + 1 == superset_lin.vars().size()) {
9658 "linear inclusion: subset + singleton is equality");
9659 context_->
working_model->mutable_constraints(subset_c)->Clear();
9660 constraint_indices_to_clean.push_back(subset_c);
9661 detector.StopProcessingCurrentSubset();
9667 "TODO linear inclusion: superset is equality");
9672 for (
const int c : constraint_indices_to_clean) {
9676 timer.AddToWork(1e-9 *
static_cast<double>(detector.work_done()));
9677 timer.AddCounter(
"relevant_constraints", detector.num_potential_supersets());
9678 timer.AddCounter(
"num_inclusions", num_inclusions);
9679 timer.AddCounter(
"num_redundant", constraint_indices_to_clean.size());
9684bool CpModelPresolver::RemoveCommonPart(
9685 const absl::flat_hash_map<int, int64_t>& common_var_coeff_map,
9686 const std::vector<std::pair<int, int64_t>>& block,
9687 ActivityBoundHelper* helper) {
9695 int definiting_equation = -1;
9696 for (
const auto [c, multiple] : block) {
9698 if (std::abs(multiple) != 1)
continue;
9699 if (!IsLinearEqualityConstraint(
ct))
continue;
9700 if (
ct.linear().vars().size() != common_var_coeff_map.size() + 1)
continue;
9703 "linear matrix: defining equation for common rectangle");
9704 definiting_equation =
c;
9708 const int num_terms =
ct.linear().vars().size();
9709 for (
int k = 0; k < num_terms; ++k) {
9710 if (common_var_coeff_map.contains(
ct.linear().vars(k)))
continue;
9711 new_var =
ct.linear().vars(k);
9712 coeff =
ct.linear().coeffs(k);
9719 gcd = -multiple * coeff;
9720 offset = multiple *
ct.linear().domain(0);
9725 if (definiting_equation == -1) {
9727 int64_t min_activity = 0;
9728 int64_t max_activity = 0;
9730 std::vector<std::pair<int, int64_t>> common_part;
9731 for (
const auto [
var, coeff] : common_var_coeff_map) {
9732 common_part.push_back({
var, coeff});
9733 gcd = std::gcd(gcd, std::abs(coeff));
9735 tmp_terms_.push_back({
var, coeff});
9739 min_activity += coeff * context_->
MinOf(
var);
9740 max_activity += coeff * context_->
MaxOf(
var);
9742 min_activity += coeff * context_->
MaxOf(
var);
9743 max_activity += coeff * context_->
MinOf(
var);
9751 if (!tmp_terms_.empty()) {
9752 min_activity += helper->ComputeMinActivity(tmp_terms_);
9753 max_activity += helper->ComputeMaxActivity(tmp_terms_);
9757 min_activity /= gcd;
9758 max_activity /= gcd;
9759 for (
int i = 0;
i < common_part.size(); ++
i) {
9760 common_part[
i].second /= gcd;
9765 if (max_activity == min_activity + 1) {
9770 std::sort(common_part.begin(), common_part.end());
9772 Domain(min_activity, max_activity), common_part);
9776 context_->
working_model->add_constraints()->mutable_linear();
9777 for (
const auto [
var, coeff] : common_part) {
9778 new_linear->add_vars(
var);
9779 new_linear->add_coeffs(coeff);
9781 new_linear->add_vars(new_var);
9782 new_linear->add_coeffs(-1);
9783 new_linear->add_domain(0);
9784 new_linear->add_domain(0);
9786 new_linear->coeffs())) {
9788 "TODO linear matrix: possible overflow in common part!");
9789 context_->
working_model->mutable_constraints()->RemoveLast();
9797 for (
const auto [c, multiple] : block) {
9798 if (c == definiting_equation)
continue;
9800 auto* mutable_linear =
9801 context_->
working_model->mutable_constraints(c)->mutable_linear();
9802 const int num_terms = mutable_linear->vars().size();
9804 bool new_var_already_seen =
false;
9805 for (
int k = 0; k < num_terms; ++k) {
9806 if (common_var_coeff_map.contains(mutable_linear->vars(k))) {
9807 CHECK_EQ(common_var_coeff_map.at(mutable_linear->vars(k)) * multiple,
9808 mutable_linear->coeffs(k));
9813 int64_t new_coeff = mutable_linear->coeffs(k);
9814 if (mutable_linear->vars(k) == new_var) {
9815 new_var_already_seen =
true;
9816 new_coeff += gcd * multiple;
9817 if (new_coeff == 0)
continue;
9820 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
9821 mutable_linear->set_coeffs(new_size, new_coeff);
9824 mutable_linear->mutable_vars()->Truncate(new_size);
9825 mutable_linear->mutable_coeffs()->Truncate(new_size);
9826 if (!new_var_already_seen) {
9827 mutable_linear->add_vars(new_var);
9828 mutable_linear->add_coeffs(gcd * multiple);
9832 .AdditionWith(Domain(-offset * multiple)),
9842int64_t FindVarCoeff(
int var,
const ConstraintProto&
ct) {
9843 const int num_terms =
ct.linear().vars().size();
9844 for (
int k = 0; k < num_terms; ++k) {
9845 if (
ct.linear().vars(k) ==
var)
return ct.linear().coeffs(k);
9850int64_t ComputeNonZeroReduction(
size_t block_size,
size_t common_part_size) {
9853 return static_cast<int64_t
>(block_size * (common_part_size - 1) -
9854 common_part_size - 1);
9862void CpModelPresolver::FindBigAtMostOneAndLinearOverlap(
9863 ActivityBoundHelper* helper) {
9866 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
9867 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
9869 int64_t num_blocks = 0;
9870 int64_t nz_reduction = 0;
9871 std::vector<int> amo_cts;
9872 std::vector<int> amo_literals;
9874 std::vector<int> common_part;
9875 std::vector<int> best_common_part;
9877 std::vector<bool> common_part_sign;
9878 std::vector<bool> best_common_part_sign;
9881 absl::flat_hash_map<int, bool> var_in_amo;
9886 if (timer.WorkLimitIsReached())
break;
9887 if (helper->NumAmoForVariable(
x) == 0)
continue;
9892 if (c < 0)
continue;
9894 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
9895 amo_cts.push_back(c);
9896 }
else if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
9897 amo_cts.push_back(c);
9900 if (amo_cts.empty())
continue;
9910 amo_literals.clear();
9911 common_part.clear();
9912 common_part_sign.clear();
9916 std::sort(amo_cts.begin(), amo_cts.end());
9917 const int random_c =
9918 absl::Uniform<int>(*context_->
random(), 0, amo_cts.size());
9919 base_ct_index = amo_cts[random_c];
9920 const ConstraintProto&
ct =
9922 const auto& literals =
ct.constraint_case() == ConstraintProto::kAtMostOne
9923 ?
ct.at_most_one().literals()
9924 :
ct.exactly_one().literals();
9925 timer.TrackSimpleLoop(5 * literals.size());
9926 for (
const int literal : literals) {
9927 amo_literals.push_back(
literal);
9930 const auto [_, inserted] =
9936 const int64_t x_multiplier = var_in_amo.at(
x) ? 1 : -1;
9940 std::vector<int> block_cts;
9941 std::vector<int> linear_cts;
9942 int max_common_part = 0;
9945 if (c < 0)
continue;
9947 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
9948 const int num_terms =
ct.linear().vars().size();
9949 if (num_terms < 2)
continue;
9951 timer.TrackSimpleLoop(2 * num_terms);
9952 const int64_t x_coeff = x_multiplier * FindVarCoeff(
x,
ct);
9953 if (x_coeff == 0)
continue;
9956 for (
int k = 0; k < num_terms; ++k) {
9957 const int var =
ct.linear().vars(k);
9962 const auto it = var_in_amo.find(
var);
9963 if (it == var_in_amo.end())
continue;
9964 int64_t coeff =
ct.linear().coeffs(k);
9965 if (!it->second) coeff = -coeff;
9966 if (coeff != x_coeff)
continue;
9969 if (num_in_amo < 2)
continue;
9971 max_common_part += num_in_amo;
9972 if (num_in_amo == common_part.size()) {
9974 block_cts.push_back(c);
9976 linear_cts.push_back(c);
9979 if (linear_cts.empty() && block_cts.empty())
continue;
9980 if (max_common_part < 100)
continue;
9984 best_common_part = common_part;
9985 best_common_part_sign = common_part_sign;
9986 int best_block_size = block_cts.size();
9988 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
9991 std::sort(block_cts.begin(), block_cts.end());
9992 std::sort(linear_cts.begin(), linear_cts.end());
9996 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->
random());
9997 for (
const int c : linear_cts) {
9999 const int num_terms =
ct.linear().vars().size();
10000 timer.TrackSimpleLoop(2 * num_terms);
10001 const int64_t x_coeff = x_multiplier * FindVarCoeff(
x,
ct);
10002 CHECK_NE(x_coeff, 0);
10004 common_part.clear();
10005 common_part_sign.clear();
10006 for (
int k = 0; k < num_terms; ++k) {
10007 const int var =
ct.linear().vars(k);
10008 const auto it = var_in_amo.find(
var);
10009 if (it == var_in_amo.end())
continue;
10010 int64_t coeff =
ct.linear().coeffs(k);
10011 if (!it->second) coeff = -coeff;
10012 if (coeff != x_coeff)
continue;
10013 common_part.push_back(
var);
10014 common_part_sign.push_back(it->second);
10016 if (common_part.size() < 2)
continue;
10019 block_cts.push_back(c);
10020 if (common_part.size() < var_in_amo.size()) {
10021 var_in_amo.clear();
10022 for (
int i = 0;
i < common_part.size(); ++
i) {
10023 var_in_amo[common_part[
i]] = common_part_sign[
i];
10030 const int64_t saved_nz =
10031 ComputeNonZeroReduction(block_cts.size() + 1, common_part.size());
10032 if (saved_nz > best_saved_nz) {
10033 best_block_size = block_cts.size();
10034 best_saved_nz = saved_nz;
10035 best_common_part = common_part;
10036 best_common_part_sign = common_part_sign;
10039 if (best_saved_nz < 100)
continue;
10044 block_cts.resize(best_block_size);
10045 var_in_amo.clear();
10046 for (
int i = 0;
i < best_common_part.size(); ++
i) {
10047 var_in_amo[best_common_part[
i]] = best_common_part_sign[
i];
10051 nz_reduction += best_saved_nz;
10056 for (
const int lit : amo_literals) {
10058 amo_literals[new_size++] =
lit;
10060 if (new_size == amo_literals.size()) {
10061 const ConstraintProto&
ct =
10063 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
10064 context_->
UpdateRuleStats(
"TODO linear matrix: constant rectangle!");
10067 "TODO linear matrix: reuse defining constraint");
10069 }
else if (new_size + 1 == amo_literals.size()) {
10070 const ConstraintProto&
ct =
10072 if (
ct.constraint_case() == ConstraintProto::kExactlyOne) {
10073 context_->
UpdateRuleStats(
"TODO linear matrix: reuse exo constraint");
10076 amo_literals.resize(new_size);
10082 context_->
working_model->add_constraints()->mutable_exactly_one();
10083 new_exo->mutable_literals()->Reserve(amo_literals.size() + 1);
10084 for (
const int lit : amo_literals) {
10085 new_exo->add_literals(
lit);
10093 ConstraintProto*
ct =
10094 context_->
working_model->mutable_constraints(base_ct_index);
10095 auto* mutable_literals =
10096 ct->constraint_case() == ConstraintProto::kAtMostOne
10097 ?
ct->mutable_at_most_one()->mutable_literals()
10098 :
ct->mutable_exactly_one()->mutable_literals();
10100 for (
const int lit : *mutable_literals) {
10102 (*mutable_literals)[new_size++] =
lit;
10104 (*mutable_literals)[new_size++] = new_var;
10105 mutable_literals->Truncate(new_size);
10110 for (
const int c : block_cts) {
10111 auto* mutable_linear =
10112 context_->
working_model->mutable_constraints(c)->mutable_linear();
10115 int64_t offset = 0;
10116 int64_t coeff_x = 0;
10119 const int num_terms = mutable_linear->vars().size();
10120 for (
int k = 0; k < num_terms; ++k) {
10121 const int var = mutable_linear->vars(k);
10123 int64_t coeff = mutable_linear->coeffs(k);
10124 const auto it = var_in_amo.find(
var);
10125 if (it != var_in_amo.end()) {
10134 if (coeff_x == 0) coeff_x = coeff;
10135 CHECK_EQ(coeff, coeff_x);
10138 mutable_linear->set_vars(new_size, mutable_linear->vars(k));
10139 mutable_linear->set_coeffs(new_size, coeff);
10144 mutable_linear->set_vars(new_size, new_var);
10145 mutable_linear->set_coeffs(new_size, coeff_x);
10148 mutable_linear->mutable_vars()->Truncate(new_size);
10149 mutable_linear->mutable_coeffs()->Truncate(new_size);
10159 timer.AddCounter(
"blocks", num_blocks);
10160 timer.AddCounter(
"saved_nz", nz_reduction);
10165void CpModelPresolver::FindBigVerticalLinearOverlap(
10166 ActivityBoundHelper* helper) {
10169 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
10170 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10172 int64_t num_blocks = 0;
10173 int64_t nz_reduction = 0;
10174 absl::flat_hash_map<int, int64_t> coeff_map;
10176 if (timer.WorkLimitIsReached())
break;
10178 bool in_enforcement =
false;
10179 std::vector<int> linear_cts;
10182 if (c < 0)
continue;
10184 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
10186 const int num_terms =
ct.linear().vars().size();
10187 if (num_terms < 2)
continue;
10188 bool is_canonical =
true;
10189 timer.TrackSimpleLoop(num_terms);
10190 for (
int k = 0; k < num_terms; ++k) {
10192 is_canonical =
false;
10196 if (!is_canonical)
continue;
10199 timer.TrackSimpleLoop(
ct.enforcement_literal().size());
10200 for (
const int lit :
ct.enforcement_literal()) {
10202 in_enforcement =
true;
10209 if (in_enforcement)
continue;
10210 linear_cts.push_back(c);
10216 if (in_enforcement)
continue;
10217 if (linear_cts.size() < 10)
continue;
10220 std::sort(linear_cts.begin(), linear_cts.end());
10221 std::shuffle(linear_cts.begin(), linear_cts.end(), *context_->
random());
10230 std::vector<std::pair<int, int64_t>> block;
10231 std::vector<std::pair<int, int64_t>> common_part;
10232 for (
const int c : linear_cts) {
10234 const int num_terms =
ct.linear().vars().size();
10235 timer.TrackSimpleLoop(num_terms);
10238 const int64_t x_coeff = FindVarCoeff(
x,
ct);
10239 if (x_coeff == 0)
continue;
10241 if (block.empty()) {
10244 for (
int k = 0; k < num_terms; ++k) {
10245 coeff_map[
ct.linear().vars(k)] =
ct.linear().coeffs(k);
10247 if (coeff_map.size() < 2)
continue;
10248 block.push_back({
c, x_coeff});
10253 const int64_t gcd =
10254 std::gcd(std::abs(coeff_map.at(
x)), std::abs(x_coeff));
10255 const int64_t multiple_base = coeff_map.at(
x) / gcd;
10256 const int64_t multiple_ct = x_coeff / gcd;
10257 common_part.clear();
10258 for (
int k = 0; k < num_terms; ++k) {
10259 const int64_t coeff =
ct.linear().coeffs(k);
10260 if (coeff % multiple_ct != 0)
continue;
10262 const auto it = coeff_map.find(
ct.linear().vars(k));
10263 if (it == coeff_map.end())
continue;
10264 if (it->second % multiple_base != 0)
continue;
10265 if (it->second / multiple_base != coeff / multiple_ct)
continue;
10267 common_part.push_back({
ct.linear().vars(k), coeff / multiple_ct});
10271 if (common_part.size() < 2)
continue;
10274 block.push_back({
c, x_coeff});
10276 for (
const auto [
var, coeff] : common_part) {
10277 coeff_map[
var] = coeff;
10282 const int64_t saved_nz =
10283 ComputeNonZeroReduction(block.size(), coeff_map.size());
10284 if (saved_nz < 30)
continue;
10287 const int64_t base_x = coeff_map.at(
x);
10288 for (
auto& [c, multipier] : block) {
10289 CHECK_EQ(multipier % base_x, 0);
10290 multipier /= base_x;
10294 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
10296 nz_reduction += saved_nz;
10297 context_->
UpdateRuleStats(
"linear matrix: common vertical rectangle");
10300 timer.AddCounter(
"blocks", num_blocks);
10301 timer.AddCounter(
"saved_nz", nz_reduction);
10312void CpModelPresolver::FindBigHorizontalLinearOverlap(
10313 ActivityBoundHelper* helper) {
10316 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
10317 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10319 const int num_constraints = context_->
working_model->constraints_size();
10320 std::vector<std::pair<int, int>> to_sort;
10321 for (
int c = 0;
c < num_constraints; ++
c) {
10323 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
10324 const int size =
ct.linear().vars().size();
10325 if (
size < 5)
continue;
10326 to_sort.push_back({-
size,
c});
10328 std::sort(to_sort.begin(), to_sort.end());
10330 std::vector<int> sorted_linear;
10331 for (
int i = 0;
i < to_sort.size(); ++
i) {
10332 sorted_linear.push_back(to_sort[
i].second);
10337 std::vector<int> var_to_coeff_non_zeros;
10338 std::vector<int64_t> var_to_coeff(context_->
working_model->variables_size(),
10341 int64_t num_blocks = 0;
10342 int64_t nz_reduction = 0;
10343 for (
int i = 0;
i < sorted_linear.size(); ++
i) {
10344 const int c = sorted_linear[
i];
10345 if (c < 0)
continue;
10346 if (timer.WorkLimitIsReached())
break;
10348 for (
const int var : var_to_coeff_non_zeros) {
10349 var_to_coeff[
var] = 0;
10351 var_to_coeff_non_zeros.clear();
10354 const int num_terms =
ct.linear().vars().size();
10355 timer.TrackSimpleLoop(num_terms);
10356 for (
int k = 0; k < num_terms; ++k) {
10357 const int var =
ct.linear().vars(k);
10358 var_to_coeff[
var] =
ct.linear().coeffs(k);
10359 var_to_coeff_non_zeros.push_back(
var);
10367 int saved_nz = 100;
10368 std::vector<int> used_sorted_linear = {
i};
10369 std::vector<std::pair<int, int64_t>> block = {{
c, 1}};
10370 std::vector<std::pair<int, int64_t>> common_part;
10371 std::vector<std::pair<int, int>> old_matches;
10373 for (
int j = 0; j < sorted_linear.size(); ++j) {
10374 if (
i == j)
continue;
10375 const int other_c = sorted_linear[j];
10376 if (other_c < 0)
continue;
10377 const ConstraintProto&
ct = context_->
working_model->constraints(other_c);
10380 const int num_terms =
ct.linear().vars().size();
10381 const int best_saved_nz =
10382 ComputeNonZeroReduction(block.size() + 1, num_terms);
10383 if (best_saved_nz <= saved_nz)
break;
10386 timer.TrackSimpleLoop(num_terms);
10387 common_part.clear();
10388 for (
int k = 0; k < num_terms; ++k) {
10389 const int var =
ct.linear().vars(k);
10390 if (var_to_coeff[
var] ==
ct.linear().coeffs(k)) {
10391 common_part.push_back({
var,
ct.linear().coeffs(k)});
10400 const int64_t new_saved_nz =
10401 ComputeNonZeroReduction(block.size() + 1, common_part.size());
10402 if (new_saved_nz > saved_nz) {
10403 saved_nz = new_saved_nz;
10404 used_sorted_linear.push_back(j);
10405 block.push_back({other_c, 1});
10409 for (
const int var : var_to_coeff_non_zeros) {
10410 var_to_coeff[
var] = 0;
10412 var_to_coeff_non_zeros.clear();
10413 for (
const auto [
var, coeff] : common_part) {
10414 var_to_coeff[
var] = coeff;
10415 var_to_coeff_non_zeros.push_back(
var);
10418 if (common_part.size() > 1) {
10419 old_matches.push_back({j, common_part.size()});
10426 if (block.size() > 1) {
10428 const int match_size = var_to_coeff_non_zeros.size();
10429 for (
const auto [
index, old_match_size] : old_matches) {
10430 if (old_match_size < match_size)
continue;
10432 int new_match_size = 0;
10433 const int other_c = sorted_linear[
index];
10434 const ConstraintProto&
ct =
10436 const int num_terms =
ct.linear().vars().size();
10437 for (
int k = 0; k < num_terms; ++k) {
10438 if (var_to_coeff[
ct.linear().vars(k)] ==
ct.linear().coeffs(k)) {
10442 if (new_match_size == match_size) {
10444 "linear matrix: common horizontal rectangle extension");
10445 used_sorted_linear.push_back(
index);
10446 block.push_back({other_c, 1});
10452 absl::flat_hash_map<int, int64_t> coeff_map;
10453 for (
const int var : var_to_coeff_non_zeros) {
10454 coeff_map[
var] = var_to_coeff[
var];
10456 if (!RemoveCommonPart(coeff_map, block, helper))
continue;
10459 nz_reduction += ComputeNonZeroReduction(block.size(), coeff_map.size());
10460 context_->
UpdateRuleStats(
"linear matrix: common horizontal rectangle");
10461 for (
const int i : used_sorted_linear) sorted_linear[
i] = -1;
10465 timer.AddCounter(
"blocks", num_blocks);
10466 timer.AddCounter(
"saved_nz", nz_reduction);
10467 timer.AddCounter(
"linears", sorted_linear.size());
10476void CpModelPresolver::FindAlmostIdenticalLinearConstraints() {
10483 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10486 std::vector<std::pair<int, int>> to_sort;
10487 const int num_constraints = context_->
working_model->constraints_size();
10488 for (
int c = 0;
c < num_constraints; ++
c) {
10490 if (!IsLinearEqualityConstraint(
ct))
continue;
10491 if (
ct.linear().vars().size() <= 2)
continue;
10494 if (!std::is_sorted(
ct.linear().vars().begin(),
ct.linear().vars().end())) {
10498 to_sort.push_back({
ct.linear().vars().size(),
c});
10500 std::sort(to_sort.begin(), to_sort.end());
10504 std::vector<int> var_to_clear;
10505 std::vector<std::vector<std::pair<int, int64_t>>> var_to_ct_coeffs_;
10506 const int num_variables = context_->
working_model->variables_size();
10507 var_to_ct_coeffs_.resize(num_variables);
10510 int num_tested_pairs = 0;
10511 int num_affine_relations = 0;
10515 const int length = to_sort[
start].first;
10516 for (;
end < to_sort.size(); ++
end) {
10517 if (to_sort[
end].first != length)
break;
10519 const int span_size =
end -
start;
10520 if (span_size == 1)
continue;
10523 for (
const int var : var_to_clear) var_to_ct_coeffs_[
var].clear();
10524 var_to_clear.clear();
10526 const int c = to_sort[
i].second;
10527 const LinearConstraintProto& lin =
10530 absl::Uniform<int>(*context_->
random(), 0, lin.vars().size());
10532 if (var_to_ct_coeffs_[
var].empty()) var_to_clear.push_back(
var);
10533 var_to_ct_coeffs_[
var].push_back({
c, lin.coeffs(
index)});
10542 for (
int i1 =
start; i1 <
end; ++i1) {
10543 if (timer.WorkLimitIsReached())
break;
10544 const int c1 = to_sort[i1].second;
10545 const LinearConstraintProto& lin1 =
10548 for (
int i = 0; !skip &&
i < lin1.vars().
size(); ++
i) {
10549 for (
const auto [c2, coeff2] : var_to_ct_coeffs_[lin1.vars(
i)]) {
10550 if (c2 == c1)
continue;
10553 if (coeff2 != lin1.coeffs(
i))
continue;
10554 if (timer.WorkLimitIsReached())
break;
10557 const ConstraintProto& ct2 = context_->
working_model->constraints(c2);
10558 if (ct2.constraint_case() != ConstraintProto::kLinear)
continue;
10559 const LinearConstraintProto& lin2 =
10561 if (lin2.vars().size() != length)
continue;
10566 timer.TrackSimpleLoop(length);
10568 ++num_tested_pairs;
10573 auto* to_modify = context_->
working_model->mutable_constraints(c1);
10575 -1, context_->
working_model->constraints(c2), to_modify)) {
10582 DCHECK_LE(to_modify->linear().vars().size(), 2);
10584 ++num_affine_relations;
10586 "linear: advanced affine relation from 2 constraints.");
10589 DivideLinearByGcd(to_modify);
10590 PresolveSmallLinear(to_modify);
10600 timer.AddCounter(
"num_tested_pairs", num_tested_pairs);
10601 timer.AddCounter(
"found", num_affine_relations);
10605void CpModelPresolver::ExtractEncodingFromLinear() {
10608 if (context_->
params().presolve_inclusion_work_limit() == 0)
return;
10609 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
10612 std::vector<int> relevant_constraints;
10613 CompactVectorVector<int> storage;
10615 detector.SetWorkLimit(context_->
params().presolve_inclusion_work_limit());
10621 std::vector<int> vars;
10622 const int num_constraints = context_->
working_model->constraints().size();
10623 for (
int c = 0;
c < num_constraints; ++
c) {
10625 switch (
ct.constraint_case()) {
10626 case ConstraintProto::kAtMostOne: {
10628 for (
const int ref :
ct.at_most_one().literals()) {
10631 relevant_constraints.push_back(c);
10632 detector.AddPotentialSuperset(storage.Add(vars));
10635 case ConstraintProto::kExactlyOne: {
10637 for (
const int ref :
ct.exactly_one().literals()) {
10640 relevant_constraints.push_back(c);
10641 detector.AddPotentialSuperset(storage.Add(vars));
10644 case ConstraintProto::kLinear: {
10646 if (!IsLinearEqualityConstraint(
ct))
continue;
10650 bool is_candidate =
true;
10651 int num_integers = 0;
10653 const int num_terms =
ct.linear().vars().size();
10654 for (
int i = 0;
i < num_terms; ++
i) {
10655 const int ref =
ct.linear().vars(
i);
10660 if (std::abs(
ct.linear().coeffs(
i)) != 1) {
10661 is_candidate =
false;
10664 if (num_integers == 2) {
10665 is_candidate =
false;
10673 if (is_candidate && num_integers == 1 && vars.size() > 1) {
10674 relevant_constraints.push_back(c);
10675 detector.AddPotentialSubset(storage.Add(vars));
10685 int64_t num_exactly_one_encodings = 0;
10686 int64_t num_at_most_one_encodings = 0;
10687 int64_t num_literals = 0;
10688 int64_t num_unique_terms = 0;
10689 int64_t num_multiple_terms = 0;
10691 detector.DetectInclusions([&](
int subset,
int superset) {
10692 const int subset_c = relevant_constraints[subset];
10693 const int superset_c = relevant_constraints[superset];
10694 const ConstraintProto& superset_ct =
10696 if (superset_ct.constraint_case() == ConstraintProto::kAtMostOne) {
10697 ++num_at_most_one_encodings;
10699 ++num_exactly_one_encodings;
10701 num_literals += storage[subset].size();
10704 if (!ProcessEncodingFromLinear(subset_c, superset_ct, &num_unique_terms,
10705 &num_multiple_terms)) {
10709 detector.StopProcessingCurrentSubset();
10712 timer.AddCounter(
"potential_supersets", detector.num_potential_supersets());
10713 timer.AddCounter(
"potential_subsets", detector.num_potential_subsets());
10714 timer.AddCounter(
"amo_encodings", num_at_most_one_encodings);
10715 timer.AddCounter(
"exo_encodings", num_exactly_one_encodings);
10716 timer.AddCounter(
"unique_terms", num_unique_terms);
10717 timer.AddCounter(
"multiple_terms", num_multiple_terms);
10718 timer.AddCounter(
"literals", num_literals);
10732void CpModelPresolver::LookAtVariableWithDegreeTwo(
int var) {
10748 bool abort =
false;
10750 Domain union_of_domain;
10751 int num_positive = 0;
10752 std::vector<int> constraint_indices_to_remove;
10758 constraint_indices_to_remove.push_back(c);
10760 if (
ct.enforcement_literal().size() != 1 ||
10762 ct.constraint_case() != ConstraintProto::kLinear ||
10763 ct.linear().vars().size() != 1) {
10767 if (
ct.enforcement_literal(0) ==
var) ++num_positive;
10768 if (ct_var != -1 &&
PositiveRef(
ct.linear().vars(0)) != ct_var) {
10773 union_of_domain = union_of_domain.UnionWith(
10776 ?
ct.linear().coeffs(0)
10777 : -
ct.linear().coeffs(0)));
10780 if (num_positive != 1)
return;
10783 context_->
UpdateRuleStats(
"variables: removable enforcement literal");
10784 absl::c_sort(constraint_indices_to_remove);
10785 for (
const int c : constraint_indices_to_remove) {
10787 __FILE__, __LINE__);
10796absl::Span<const int> AtMostOneOrExactlyOneLiterals(
const ConstraintProto&
ct) {
10797 if (
ct.constraint_case() == ConstraintProto::kAtMostOne) {
10798 return {
ct.at_most_one().literals()};
10800 return {
ct.exactly_one().literals()};
10806void CpModelPresolver::ProcessVariableInTwoAtMostOrExactlyOne(
int var) {
10829 if (c < 0)
continue;
10831 if (
ct.constraint_case() != ConstraintProto::kAtMostOne &&
10832 ct.constraint_case() != ConstraintProto::kExactlyOne) {
10843 if (c1 == -1 || c2 == -1)
return;
10847 if (c1 > c2) std::swap(c1, c2);
10857 int c1_ref = std::numeric_limits<int>::min();
10858 const ConstraintProto& ct1 = context_->
working_model->constraints(c1);
10859 if (AtMostOneOrExactlyOneLiterals(ct1).
size() <= 1)
return;
10860 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct1)) {
10867 int c2_ref = std::numeric_limits<int>::min();
10868 const ConstraintProto& ct2 = context_->
working_model->constraints(c2);
10869 if (AtMostOneOrExactlyOneLiterals(ct2).
size() <= 1)
return;
10870 for (
const int lit : AtMostOneOrExactlyOneLiterals(ct2)) {
10877 DCHECK_NE(c1_ref, std::numeric_limits<int>::min());
10878 DCHECK_NE(c2_ref, std::numeric_limits<int>::min());
10883 int64_t cost_shift = 0;
10884 absl::Span<const int> literals;
10885 if (ct1.constraint_case() == ConstraintProto::kExactlyOne) {
10887 literals = ct1.exactly_one().literals();
10888 }
else if (ct2.constraint_case() == ConstraintProto::kExactlyOne) {
10890 literals = ct2.exactly_one().literals();
10901 literals = ct1.at_most_one().literals();
10904 literals = ct2.at_most_one().literals();
10911 ->mutable_exactly_one()
10912 ->mutable_literals()
10913 ->Assign(literals.begin(), literals.end());
10916 const int new_ct_index = context_->
working_model->constraints().size();
10917 ConstraintProto* new_ct = context_->
working_model->add_constraints();
10918 if (ct1.constraint_case() == ConstraintProto::kExactlyOne &&
10919 ct2.constraint_case() == ConstraintProto::kExactlyOne) {
10921 new_ct->mutable_exactly_one()->add_literals(
lit);
10927 new_ct->mutable_at_most_one()->add_literals(
lit);
10938 "at_most_one: resolved two constraints with opposite literal");
10944 DCHECK_NE(new_ct->constraint_case(), ConstraintProto::CONSTRAINT_NOT_SET);
10945 if (PresolveAtMostOrExactlyOne(new_ct)) {
10961void CpModelPresolver::MaybeTransferLinear1ToAnotherVariable(
int var) {
10964 int num_others = 0;
10965 std::vector<int> to_rewrite;
10969 if (
ct.constraint_case() == ConstraintProto::kLinear &&
10970 ct.linear().vars().size() == 1) {
10971 to_rewrite.push_back(c);
10978 if (num_others != 1)
return;
10979 if (other_c < 0)
return;
10984 const auto& other_ct = context_->
working_model->constraints(other_c);
10986 !other_ct.enforcement_literal().empty() ||
10987 other_ct.constraint_case() == ConstraintProto::kLinear) {
10993 std::function<std::pair<int, Domain>(
const Domain& implied)> transfer_f =
11001 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
11002 other_ct.lin_max().target().vars().size() == 1 &&
11003 other_ct.lin_max().target().vars(0) ==
var &&
11004 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
11005 IsAffineIntAbs(other_ct)) {
11007 const LinearExpressionProto& target = other_ct.lin_max().target();
11008 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
11009 transfer_f = [target, expr](
const Domain& implied) {
11010 Domain target_domain =
11011 implied.ContinuousMultiplicationBy(target.coeffs(0))
11012 .AdditionWith(Domain(target.offset()));
11014 target_domain.IntersectionWith(Domain(0, target_domain.Max()));
11017 const Domain expr_domain =
11018 target_domain.UnionWith(target_domain.Negation());
11019 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
11020 .InverseMultiplicationBy(expr.coeffs(0));
11021 return std::make_pair(expr.vars(0), new_domain);
11025 if (transfer_f ==
nullptr) {
11027 "TODO linear1: appear in only one extra 2-var constraint");
11032 std::sort(to_rewrite.begin(), to_rewrite.end());
11033 const Domain var_domain = context_->
DomainOf(
var);
11034 for (
const int c : to_rewrite) {
11035 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
11036 if (
ct->linear().vars(0) !=
var ||
ct->linear().coeffs(0) != 1) {
11038 LOG(INFO) <<
"Aborted in MaybeTransferLinear1ToAnotherVariable()";
11042 const Domain implied =
11044 auto [new_var, new_domain] = transfer_f(implied);
11045 const Domain current = context_->
DomainOf(new_var);
11047 if (new_domain.IsEmpty()) {
11048 if (!MarkConstraintAsFalse(
ct))
return;
11049 }
else if (new_domain == current) {
11052 ct->mutable_linear()->set_vars(0, new_var);
11060 context_->
working_model->mutable_constraints(other_c)->Clear();
11073void CpModelPresolver::ProcessVariableOnlyUsedInEncoding(
int var) {
11079 if (context_->
params().search_branching() == SatParameters::FIXED_SEARCH) {
11085 MaybeTransferLinear1ToAnotherVariable(
var);
11104 if (c < 0)
continue;
11105 CHECK_EQ(unique_c, -1);
11108 CHECK_NE(unique_c, -1);
11109 const ConstraintProto&
ct = context_->
working_model->constraints(unique_c);
11111 if (
ct.linear().vars(0) ==
var) {
11115 if (implied.IsEmpty()) {
11116 if (!MarkConstraintAsFalse(
11124 int64_t value1, value2;
11126 context_->
UpdateRuleStats(
"variables: fix singleton var in linear1");
11128 }
else if (cost > 0) {
11130 value2 = implied.Min();
11133 value2 = implied.Max();
11138 context_->
UpdateRuleStats(
"variables: reduced domain to two values");
11150 absl::flat_hash_set<int64_t> values_set;
11151 absl::flat_hash_map<int64_t, std::vector<int>> value_to_equal_literals;
11152 absl::flat_hash_map<int64_t, std::vector<int>> value_to_not_equal_literals;
11153 bool abort =
false;
11155 if (c < 0)
continue;
11157 CHECK_EQ(
ct.constraint_case(), ConstraintProto::kLinear);
11158 CHECK_EQ(
ct.linear().vars().size(), 1);
11159 int64_t coeff =
ct.linear().coeffs(0);
11160 if (std::abs(coeff) != 1 ||
ct.enforcement_literal().size() != 1) {
11166 const Domain var_domain = context_->
DomainOf(
var);
11170 if (rhs.IsEmpty()) {
11175 }
else if (rhs.IsFixed()) {
11176 if (!var_domain.Contains(rhs.FixedValue())) {
11181 values_set.insert(rhs.FixedValue());
11182 value_to_equal_literals[rhs.FixedValue()].push_back(
11183 ct.enforcement_literal(0));
11186 const Domain complement = var_domain.IntersectionWith(rhs.Complement());
11187 if (complement.IsEmpty()) {
11192 if (complement.IsFixed()) {
11193 if (var_domain.Contains(complement.FixedValue())) {
11194 values_set.insert(complement.FixedValue());
11195 value_to_not_equal_literals[complement.FixedValue()].push_back(
11196 ct.enforcement_literal(0));
11207 }
else if (value_to_not_equal_literals.empty() &&
11208 value_to_equal_literals.empty()) {
11215 std::vector<int64_t> encoded_values(values_set.begin(), values_set.end());
11216 std::sort(encoded_values.begin(), encoded_values.end());
11217 CHECK(!encoded_values.empty());
11218 const bool is_fully_encoded =
11224 for (
const int64_t v : encoded_values) {
11226 const auto eq_it = value_to_equal_literals.find(v);
11227 if (eq_it != value_to_equal_literals.end()) {
11228 for (
const int lit : eq_it->second) {
11232 const auto neq_it = value_to_not_equal_literals.find(v);
11233 if (neq_it != value_to_not_equal_literals.end()) {
11234 for (
const int lit : neq_it->second) {
11242 Domain other_values;
11243 if (!is_fully_encoded) {
11253 if (is_fully_encoded) {
11257 obj_coeff > 0 ? encoded_values.front() : encoded_values.back();
11262 !other_values.IsFixed()) {
11264 "TODO variables: only used in objective and in encoding");
11275 Domain(obj_coeff > 0 ? other_values.Min() : other_values.Max());
11276 min_value = other_values.FixedValue();
11281 int64_t accumulated = std::abs(min_value);
11282 for (
const int64_t
value : encoded_values) {
11284 if (accumulated == std::numeric_limits<int64_t>::max()) {
11286 "TODO variables: only used in objective and in encoding");
11291 ConstraintProto encoding_ct;
11292 LinearConstraintProto* linear = encoding_ct.mutable_linear();
11293 const int64_t coeff_in_equality = -1;
11294 linear->add_vars(
var);
11295 linear->add_coeffs(coeff_in_equality);
11297 linear->add_domain(-min_value);
11298 linear->add_domain(-min_value);
11299 for (
const int64_t
value : encoded_values) {
11300 if (
value == min_value)
continue;
11302 const int64_t coeff =
value - min_value;
11304 linear->add_vars(enf);
11305 linear->add_coeffs(coeff);
11308 linear->set_domain(0, encoding_ct.linear().domain(0) - coeff);
11309 linear->set_domain(1, encoding_ct.linear().domain(1) - coeff);
11311 linear->add_coeffs(-coeff);
11317 "TODO variables: only used in objective and in encoding");
11321 "variables: only used in objective and in encoding");
11328 std::vector<int> to_clear;
11330 if (c >= 0) to_clear.push_back(c);
11332 absl::c_sort(to_clear);
11333 for (
const int c : to_clear) {
11334 if (c < 0)
continue;
11341 ConstraintProto* new_ct = context_->
working_model->add_constraints();
11342 if (is_fully_encoded) {
11344 for (
const int64_t
value : encoded_values) {
11345 new_ct->mutable_exactly_one()->add_literals(
11348 PresolveExactlyOne(new_ct);
11353 ConstraintProto* mapping_ct =
11355 mapping_ct->mutable_linear()->add_vars(
var);
11356 mapping_ct->mutable_linear()->add_coeffs(1);
11359 for (
const int64_t
value : encoded_values) {
11362 new_ct->mutable_at_most_one()->add_literals(
literal);
11364 PresolveAtMostOne(new_ct);
11369 for (
const int64_t
value : encoded_values) {
11372 ct->add_enforcement_literal(enf);
11373 ct->mutable_linear()->add_vars(
var);
11374 ct->mutable_linear()->add_coeffs(1);
11375 ct->mutable_linear()->add_domain(
value);
11376 ct->mutable_linear()->add_domain(
value);
11383void CpModelPresolver::TryToSimplifyDomain(
int var) {
11392 if (r.representative !=
var)
return;
11398 if (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)) {
11403 if (domain.NumIntervals() != domain.Size())
return;
11405 const int64_t var_min = domain.
Min();
11406 int64_t gcd = domain[1].start - var_min;
11408 const ClosedInterval&
i = domain[
index];
11409 DCHECK_EQ(
i.start,
i.end);
11410 const int64_t shifted_value =
i.start - var_min;
11411 DCHECK_GT(shifted_value, 0);
11414 if (gcd == 1)
break;
11416 if (gcd == 1)
return;
11423void CpModelPresolver::EncodeAllAffineRelations() {
11424 int64_t num_added = 0;
11429 if (r.representative ==
var)
continue;
11435 if (!PresolveAffineRelationIfAny(
var))
break;
11441 auto* arg =
ct->mutable_linear();
11442 arg->add_vars(
var);
11443 arg->add_coeffs(1);
11444 arg->add_vars(r.representative);
11445 arg->add_coeffs(-r.coeff);
11446 arg->add_domain(r.offset);
11447 arg->add_domain(r.offset);
11455 if (num_added > 0) {
11456 SOLVER_LOG(logger_, num_added,
" affine relations still in the model.");
11461bool CpModelPresolver::PresolveAffineRelationIfAny(
int var) {
11463 if (r.representative ==
var)
return true;
11482 auto* arg =
ct->mutable_linear();
11483 arg->add_vars(
var);
11484 arg->add_coeffs(1);
11485 arg->add_vars(r.representative);
11486 arg->add_coeffs(-r.coeff);
11487 arg->add_domain(r.offset);
11488 arg->add_domain(r.offset);
11495bool CpModelPresolver::ProcessChangedVariables(std::vector<bool>* in_queue,
11496 std::deque<int>* queue) {
11500 in_queue->resize(context_->
working_model->constraints_size(),
false);
11501 const auto& vector_that_can_grow_during_iter =
11503 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
11504 const int v = vector_that_can_grow_during_iter[
i];
11507 if (!PresolveAffineRelationIfAny(v))
return false;
11510 TryToSimplifyDomain(v);
11518 in_queue->resize(context_->
working_model->constraints_size(),
false);
11520 if (c >= 0 && !(*in_queue)[c]) {
11521 (*in_queue)[
c] =
true;
11522 queue->push_back(c);
11530 std::sort(queue->begin(), queue->end());
11531 return !queue->empty();
11534void CpModelPresolver::PresolveToFixPoint() {
11537 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
11540 int num_dominance_tests = 0;
11541 int num_dual_strengthening = 0;
11544 const int64_t max_num_operations =
11545 context_->
params().debug_max_num_presolve_operations() > 0
11546 ? context_->
params().debug_max_num_presolve_operations()
11547 : std::numeric_limits<int64_t>::max();
11552 absl::flat_hash_set<std::pair<int, int>> var_constraint_pair_already_called;
11555 std::vector<bool> in_queue(context_->
working_model->constraints_size(),
11557 std::deque<int> queue;
11558 for (
int c = 0;
c < in_queue.size(); ++
c) {
11559 if (context_->
working_model->constraints(c).constraint_case() !=
11560 ConstraintProto::CONSTRAINT_NOT_SET) {
11561 in_queue[
c] =
true;
11562 queue.push_back(c);
11570 if (context_->
params().permute_presolve_constraint_order()) {
11571 std::shuffle(queue.begin(), queue.end(), *context_->
random());
11573 std::sort(queue.begin(), queue.end(), [
this](
int a,
int b) {
11574 const int score_a = context_->ConstraintToVars(a).size();
11575 const int score_b = context_->ConstraintToVars(b).size();
11576 return score_a < score_b || (score_a == score_b && a < b);
11584 constexpr int kMaxNumLoops = 1000;
11585 for (; num_loops < kMaxNumLoops && !queue.empty(); ++num_loops) {
11594 const int c = queue.front();
11595 in_queue[
c] =
false;
11598 const int old_num_constraint =
11603 logger_,
"Unsat after presolving constraint #", c,
11604 " (warning, dump might be inconsistent): ",
11609 const int new_num_constraints =
11611 if (new_num_constraints > old_num_constraint) {
11613 in_queue.resize(new_num_constraints,
true);
11614 for (
int c = old_num_constraint;
c < new_num_constraints; ++
c) {
11615 queue.push_back(c);
11628 in_queue.resize(context_->
working_model->constraints_size(),
false);
11629 const auto& vector_that_can_grow_during_iter =
11631 for (
int i = 0;
i < vector_that_can_grow_during_iter.size(); ++
i) {
11632 const int v = vector_that_can_grow_during_iter[
i];
11638 if (!PresolveAffineRelationIfAny(v))
return;
11641 if (degree == 0)
continue;
11642 if (degree == 2) LookAtVariableWithDegreeTwo(v);
11643 if (degree == 2 || degree == 3) {
11645 ProcessVariableInTwoAtMostOrExactlyOne(v);
11646 in_queue.resize(context_->
working_model->constraints_size(),
false);
11653 if (degree != 1)
continue;
11655 if (c < 0)
continue;
11660 if (var_constraint_pair_already_called.contains(
11661 std::pair<int, int>(v, c))) {
11664 var_constraint_pair_already_called.insert({v,
c});
11666 if (!in_queue[c]) {
11667 in_queue[
c] =
true;
11668 queue.push_back(c);
11673 if (ProcessChangedVariables(&in_queue, &queue))
continue;
11677 ProcessVariableOnlyUsedInEncoding(v);
11679 if (ProcessChangedVariables(&in_queue, &queue))
continue;
11686 if (!context_->
working_model->assumptions().empty())
break;
11691 for (
int i = 0;
i < 10; ++
i) {
11693 ++num_dual_strengthening;
11694 DualBoundStrengthening dual_bound_strengthening;
11696 if (!dual_bound_strengthening.Strengthen(context_))
return;
11697 if (ProcessChangedVariables(&in_queue, &queue))
break;
11704 if (dual_bound_strengthening.NumDeletedConstraints() == 0)
break;
11706 if (!queue.empty())
continue;
11710 if (num_dominance_tests++ < 2) {
11712 PresolveTimer timer(
"DetectDominanceRelations", logger_, time_limit_);
11713 VarDomination var_dom;
11716 if (ProcessChangedVariables(&in_queue, &queue))
continue;
11730 const int num_constraints = context_->
working_model->constraints_size();
11731 for (
int c = 0;
c < num_constraints; ++
c) {
11732 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
11733 switch (
ct->constraint_case()) {
11734 case ConstraintProto::kNoOverlap:
11736 if (PresolveNoOverlap(
ct)) {
11740 case ConstraintProto::kNoOverlap2D:
11742 if (PresolveNoOverlap2D(c,
ct)) {
11746 case ConstraintProto::kCumulative:
11748 if (PresolveCumulative(
ct)) {
11752 case ConstraintProto::kBoolOr: {
11755 for (
const auto& pair :
11757 bool modified =
false;
11773 timer.AddCounter(
"num_loops", num_loops);
11774 timer.AddCounter(
"num_dual_strengthening", num_dual_strengthening);
11781 const CpModelProto& in_model) {
11782 if (context_->
params().ignore_names()) {
11785 in_model.variables_size());
11786 for (
const IntegerVariableProto& var_proto : in_model.variables()) {
11787 *context_->
working_model->add_variables()->mutable_domain() =
11788 var_proto.domain();
11791 *context_->
working_model->mutable_variables() = in_model.variables();
11796 for (
const Domain& domain : domains) {
11807 const CpModelProto& in_model,
bool first_copy,
11808 std::function<
bool(
int)> active_constraints) {
11810 const bool ignore_names = context_->
params().ignore_names();
11814 std::vector<int> constraints_using_intervals;
11816 starting_constraint_index_ = context_->
working_model->constraints_size();
11817 for (
int c = 0; c < in_model.constraints_size(); ++c) {
11818 if (active_constraints !=
nullptr && !active_constraints(c))
continue;
11819 const ConstraintProto&
ct = in_model.constraints(c);
11821 if (!PrepareEnforcementCopyWithDup(
ct))
continue;
11823 if (!PrepareEnforcementCopy(
ct))
continue;
11829 switch (
ct.constraint_case()) {
11830 case ConstraintProto::CONSTRAINT_NOT_SET:
11832 case ConstraintProto::kBoolOr:
11834 if (!CopyBoolOrWithDupSupport(
ct))
return CreateUnsatModel(c,
ct);
11836 if (!CopyBoolOr(
ct))
return CreateUnsatModel(c,
ct);
11839 case ConstraintProto::kBoolAnd:
11840 if (temp_enforcement_literals_.empty()) {
11841 for (
const int lit :
ct.bool_and().literals()) {
11844 return CreateUnsatModel(c,
ct);
11847 }
else if (first_copy) {
11848 if (!CopyBoolAndWithDupSupport(
ct))
return CreateUnsatModel(c,
ct);
11850 if (!CopyBoolAnd(
ct))
return CreateUnsatModel(c,
ct);
11853 case ConstraintProto::kLinear:
11854 if (!CopyLinear(
ct))
return CreateUnsatModel(c,
ct);
11856 case ConstraintProto::kAtMostOne:
11857 if (!CopyAtMostOne(
ct))
return CreateUnsatModel(c,
ct);
11859 case ConstraintProto::kExactlyOne:
11860 if (!CopyExactlyOne(
ct))
return CreateUnsatModel(c,
ct);
11862 case ConstraintProto::kInterval:
11863 if (!CopyInterval(
ct, c, ignore_names))
return CreateUnsatModel(c,
ct);
11865 AddLinearConstraintForInterval(
ct);
11868 case ConstraintProto::kNoOverlap:
11870 constraints_using_intervals.push_back(c);
11872 CopyAndMapNoOverlap(
ct);
11875 case ConstraintProto::kNoOverlap2D:
11877 constraints_using_intervals.push_back(c);
11879 CopyAndMapNoOverlap2D(
ct);
11882 case ConstraintProto::kCumulative:
11884 constraints_using_intervals.push_back(c);
11886 CopyAndMapCumulative(
ct);
11890 ConstraintProto* new_ct = context_->
working_model->add_constraints();
11892 if (ignore_names) {
11894 new_ct->clear_name();
11901 DCHECK(first_copy || constraints_using_intervals.empty());
11902 for (
const int c : constraints_using_intervals) {
11903 const ConstraintProto&
ct = in_model.constraints(c);
11904 switch (
ct.constraint_case()) {
11905 case ConstraintProto::kNoOverlap:
11906 CopyAndMapNoOverlap(
ct);
11908 case ConstraintProto::kNoOverlap2D:
11909 CopyAndMapNoOverlap2D(
ct);
11911 case ConstraintProto::kCumulative:
11912 CopyAndMapCumulative(
ct);
11915 LOG(DFATAL) <<
"Shouldn't be here.";
11922bool ModelCopy::PrepareEnforcementCopy(
const ConstraintProto&
ct) {
11923 temp_enforcement_literals_.clear();
11924 for (
const int lit :
ct.enforcement_literal()) {
11930 temp_enforcement_literals_.push_back(
lit);
11935bool ModelCopy::PrepareEnforcementCopyWithDup(
const ConstraintProto&
ct) {
11936 temp_enforcement_literals_.clear();
11937 temp_enforcement_literals_set_.clear();
11938 for (
const int lit :
ct.enforcement_literal()) {
11940 if (temp_enforcement_literals_set_.contains(
lit)) {
11950 if (temp_enforcement_literals_set_.contains(
NegatedRef(
lit))) {
11955 temp_enforcement_literals_.push_back(
lit);
11956 temp_enforcement_literals_set_.insert(
lit);
11961void ModelCopy::FinishEnforcementCopy(ConstraintProto*
ct) {
11962 ct->mutable_enforcement_literal()->Add(temp_enforcement_literals_.begin(),
11963 temp_enforcement_literals_.end());
11966bool ModelCopy::FinishBoolOrCopy() {
11967 if (temp_literals_.empty())
return false;
11969 if (temp_literals_.size() == 1) {
11975 ->mutable_bool_or()
11976 ->mutable_literals()
11977 ->Add(temp_literals_.begin(), temp_literals_.end());
11981bool ModelCopy::CopyBoolOr(
const ConstraintProto&
ct) {
11982 temp_literals_.clear();
11983 for (
const int lit : temp_enforcement_literals_) {
11986 for (
const int lit :
ct.bool_or().literals()) {
11991 temp_literals_.push_back(
lit);
11994 return FinishBoolOrCopy();
11997bool ModelCopy::CopyBoolOrWithDupSupport(
const ConstraintProto&
ct) {
11998 temp_literals_.clear();
11999 temp_literals_set_.clear();
12000 for (
const int enforcement_lit : temp_enforcement_literals_) {
12007 temp_literals_set_.insert(
lit);
12008 temp_literals_.push_back(
lit);
12010 for (
const int lit :
ct.bool_or().literals()) {
12020 const auto [it, inserted] = temp_literals_set_.insert(
lit);
12021 if (inserted) temp_literals_.push_back(
lit);
12023 return FinishBoolOrCopy();
12026bool ModelCopy::CopyBoolAnd(
const ConstraintProto&
ct) {
12027 bool at_least_one_false =
false;
12028 int num_non_fixed_literals = 0;
12029 for (
const int lit :
ct.bool_and().literals()) {
12031 at_least_one_false =
true;
12035 num_non_fixed_literals++;
12039 if (at_least_one_false) {
12041 BoolArgumentProto* bool_or =
12042 context_->
working_model->add_constraints()->mutable_bool_or();
12043 for (
const int lit : temp_enforcement_literals_) {
12046 return !bool_or->literals().empty();
12047 }
else if (num_non_fixed_literals > 0) {
12048 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12049 FinishEnforcementCopy(new_ct);
12050 BoolArgumentProto* bool_and = new_ct->mutable_bool_and();
12051 bool_and->mutable_literals()->Reserve(num_non_fixed_literals);
12052 for (
const int lit :
ct.bool_and().literals()) {
12054 bool_and->add_literals(
lit);
12060bool ModelCopy::CopyBoolAndWithDupSupport(
const ConstraintProto&
ct) {
12061 DCHECK(!temp_enforcement_literals_.empty());
12063 bool at_least_one_false =
false;
12064 temp_literals_.clear();
12065 temp_literals_set_.clear();
12066 for (
const int lit :
ct.bool_and().literals()) {
12069 at_least_one_false =
true;
12074 at_least_one_false =
true;
12077 if (temp_enforcement_literals_set_.contains(
NegatedRef(
lit))) {
12079 at_least_one_false =
true;
12084 if (temp_enforcement_literals_set_.contains(
lit)) {
12088 const auto [it, inserted] = temp_literals_set_.insert(
lit);
12089 if (inserted) temp_literals_.push_back(
lit);
12092 if (at_least_one_false) {
12094 BoolArgumentProto* bool_or =
12095 context_->
working_model->add_constraints()->mutable_bool_or();
12096 for (
const int lit : temp_enforcement_literals_) {
12099 return !bool_or->literals().empty();
12102 if (temp_literals_.empty()) {
12108 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12109 FinishEnforcementCopy(new_ct);
12110 new_ct->mutable_bool_and()->mutable_literals()->Add(temp_literals_.begin(),
12111 temp_literals_.end());
12115bool ModelCopy::CopyLinear(
const ConstraintProto&
ct) {
12116 non_fixed_variables_.clear();
12117 non_fixed_coefficients_.clear();
12118 int64_t offset = 0;
12119 int64_t min_activity = 0;
12120 int64_t max_activity = 0;
12121 for (
int i = 0;
i <
ct.linear().vars_size(); ++
i) {
12122 const int ref =
ct.linear().vars(
i);
12123 const int64_t coeff =
ct.linear().coeffs(
i);
12124 if (coeff == 0)
continue;
12125 if (context_->
IsFixed(ref)) {
12126 offset += coeff * context_->
MinOf(ref);
12131 min_activity += coeff * context_->
MinOf(ref);
12132 max_activity += coeff * context_->
MaxOf(ref);
12134 min_activity += coeff * context_->
MaxOf(ref);
12135 max_activity += coeff * context_->
MinOf(ref);
12140 non_fixed_variables_.push_back(ref);
12141 non_fixed_coefficients_.push_back(coeff);
12143 non_fixed_variables_.push_back(
NegatedRef(ref));
12144 non_fixed_coefficients_.push_back(-coeff);
12148 const Domain implied(min_activity, max_activity);
12149 const Domain new_rhs =
12153 if (implied.IsIncludedIn(new_rhs)) {
12159 if (implied.IntersectionWith(new_rhs).IsEmpty()) {
12160 if (
ct.enforcement_literal().empty())
return false;
12161 temp_literals_.clear();
12162 for (
const int literal :
ct.enforcement_literal()) {
12168 ->mutable_bool_or()
12169 ->mutable_literals()
12170 ->Add(temp_literals_.begin(), temp_literals_.end());
12171 return !temp_literals_.empty();
12174 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12175 FinishEnforcementCopy(new_ct);
12176 LinearConstraintProto* linear = new_ct->mutable_linear();
12177 linear->mutable_vars()->Add(non_fixed_variables_.begin(),
12178 non_fixed_variables_.end());
12179 linear->mutable_coeffs()->Add(non_fixed_coefficients_.begin(),
12180 non_fixed_coefficients_.end());
12185bool ModelCopy::CopyAtMostOne(
const ConstraintProto&
ct) {
12187 temp_literals_.clear();
12188 for (
const int lit :
ct.at_most_one().literals()) {
12190 temp_literals_.push_back(
lit);
12194 if (temp_literals_.size() <= 1)
return true;
12195 if (num_true > 1)
return false;
12198 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12199 FinishEnforcementCopy(new_ct);
12200 new_ct->mutable_at_most_one()->mutable_literals()->Add(temp_literals_.begin(),
12201 temp_literals_.end());
12205bool ModelCopy::CopyExactlyOne(
const ConstraintProto&
ct) {
12207 temp_literals_.clear();
12208 for (
const int lit :
ct.exactly_one().literals()) {
12210 temp_literals_.push_back(
lit);
12214 if (temp_literals_.empty() || num_true > 1)
return false;
12215 if (temp_literals_.size() == 1 && num_true == 1)
return true;
12218 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12219 FinishEnforcementCopy(new_ct);
12220 new_ct->mutable_exactly_one()->mutable_literals()->Add(temp_literals_.begin(),
12221 temp_literals_.end());
12225bool ModelCopy::CopyInterval(
const ConstraintProto&
ct,
int c,
12226 bool ignore_names) {
12227 CHECK_EQ(starting_constraint_index_, 0)
12228 <<
"Adding new interval constraints to partially filled model is not "
12230 interval_mapping_[
c] = context_->
working_model->constraints_size();
12231 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12232 if (ignore_names) {
12233 *new_ct->mutable_enforcement_literal() =
ct.enforcement_literal();
12234 *new_ct->mutable_interval() =
ct.interval();
12242void ModelCopy::AddLinearConstraintForInterval(
const ConstraintProto&
ct) {
12247 const IntervalConstraintProto& itv =
ct.interval();
12248 if (itv.size().vars().empty() &&
12249 itv.start().offset() + itv.size().offset() == itv.end().offset() &&
12250 absl::Span<const int>(itv.start().vars()) ==
12251 absl::Span<const int>(itv.end().vars()) &&
12252 absl::Span<const int64_t>(itv.start().coeffs()) ==
12253 absl::Span<const int64_t>(itv.end().coeffs())) {
12256 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12257 *new_ct->mutable_enforcement_literal() =
ct.enforcement_literal();
12259 LinearConstraintProto* mutable_linear = new_ct->mutable_linear();
12260 mutable_linear->add_domain(0);
12261 mutable_linear->add_domain(0);
12268 const LinearExpressionProto& size_expr = itv.size();
12269 if (context_->
MinOf(size_expr) < 0) {
12270 ConstraintProto* new_ct = context_->
working_model->add_constraints();
12271 *new_ct->mutable_enforcement_literal() =
ct.enforcement_literal();
12272 *new_ct->mutable_linear()->mutable_vars() = size_expr.vars();
12273 *new_ct->mutable_linear()->mutable_coeffs() = size_expr.coeffs();
12274 new_ct->mutable_linear()->add_domain(-size_expr.offset());
12275 new_ct->mutable_linear()->add_domain(std::numeric_limits<int64_t>::max());
12279void ModelCopy::CopyAndMapNoOverlap(
const ConstraintProto&
ct) {
12282 context_->
working_model->add_constraints()->mutable_no_overlap();
12283 new_ct->mutable_intervals()->Reserve(
ct.no_overlap().intervals().size());
12284 for (
const int index :
ct.no_overlap().intervals()) {
12285 const auto it = interval_mapping_.find(
index);
12286 if (it == interval_mapping_.end())
continue;
12287 new_ct->add_intervals(it->second);
12291void ModelCopy::CopyAndMapNoOverlap2D(
const ConstraintProto&
ct) {
12294 context_->
working_model->add_constraints()->mutable_no_overlap_2d();
12296 const int num_intervals =
ct.no_overlap_2d().x_intervals().size();
12297 new_ct->mutable_x_intervals()->Reserve(num_intervals);
12298 new_ct->mutable_y_intervals()->Reserve(num_intervals);
12299 for (
int i = 0;
i < num_intervals; ++
i) {
12300 const auto x_it = interval_mapping_.find(
ct.no_overlap_2d().x_intervals(
i));
12301 if (x_it == interval_mapping_.end())
continue;
12302 const auto y_it = interval_mapping_.find(
ct.no_overlap_2d().y_intervals(
i));
12303 if (y_it == interval_mapping_.end())
continue;
12304 new_ct->add_x_intervals(x_it->second);
12305 new_ct->add_y_intervals(y_it->second);
12309void ModelCopy::CopyAndMapCumulative(
const ConstraintProto&
ct) {
12312 context_->
working_model->add_constraints()->mutable_cumulative();
12313 *new_ct->mutable_capacity() =
ct.cumulative().capacity();
12315 const int num_intervals =
ct.cumulative().intervals().size();
12316 new_ct->mutable_intervals()->Reserve(num_intervals);
12317 new_ct->mutable_demands()->Reserve(num_intervals);
12318 for (
int i = 0;
i < num_intervals; ++
i) {
12319 const auto it = interval_mapping_.find(
ct.cumulative().intervals(
i));
12320 if (it == interval_mapping_.end())
continue;
12321 new_ct->add_intervals(it->second);
12322 *new_ct->add_demands() =
ct.cumulative().demands(
i);
12326bool ModelCopy::CreateUnsatModel(
int c,
const ConstraintProto&
ct) {
12328 context_->
working_model->add_constraints()->mutable_bool_or();
12334 std::string proto_string;
12335#if !defined(__PORTABLE_PLATFORM__)
12336 google::protobuf::TextFormat::Printer printer;
12338 printer.PrintToString(
ct, &proto_string);
12340 std::string
message = absl::StrCat(
12341 "proven during initial copy of constraint #", c,
":\n", proto_string);
12343 if (vars.size() < 10) {
12344 absl::StrAppend(&
message,
"With current variable domains:\n");
12345 for (
const int var : vars) {
12362 return !
context->ModelIsUnsat();
12366 const CpModelProto& in_model,
const std::vector<Domain>& domains,
12368 CHECK_EQ(domains.size(), in_model.variables_size());
12372 active_constraints)) {
12377 return !
context->ModelIsUnsat();
12382 if (!in_model.name().empty()) {
12383 context->working_model->set_name(in_model.name());
12385 if (in_model.has_objective()) {
12386 *
context->working_model->mutable_objective() = in_model.objective();
12388 if (in_model.has_floating_point_objective()) {
12389 *
context->working_model->mutable_floating_point_objective() =
12390 in_model.floating_point_objective();
12392 if (!in_model.search_strategy().empty()) {
12394 *
context->working_model->mutable_search_strategy() =
12395 in_model.search_strategy();
12396 for (DecisionStrategyProto& strategy :
12397 *
context->working_model->mutable_search_strategy()) {
12398 if (!strategy.variables().empty()) {
12399 CHECK(strategy.exprs().empty());
12400 for (
const int ref : strategy.variables()) {
12401 LinearExpressionProto* expr = strategy.add_exprs();
12405 strategy.clear_variables();
12409 if (!in_model.assumptions().empty()) {
12410 *
context->working_model->mutable_assumptions() = in_model.assumptions();
12412 if (in_model.has_symmetry()) {
12413 *
context->working_model->mutable_symmetry() = in_model.symmetry();
12415 if (in_model.has_solution_hint()) {
12416 *
context->working_model->mutable_solution_hint() = in_model.solution_hint();
12427void CpModelPresolver::MergeClauses() {
12429 PresolveTimer timer(__FUNCTION__, logger_, time_limit_);
12432 std::vector<int> to_clean;
12435 absl::flat_hash_map<uint64_t, int> bool_and_map;
12441 const int num_variables = context_->
working_model->variables_size();
12442 std::vector<int> bool_or_indices;
12443 std::vector<int64_t> literal_score(2 * num_variables, 0);
12444 const auto get_index = [](
int ref) {
12448 int64_t num_collisions = 0;
12449 int64_t num_merges = 0;
12450 int64_t num_saved_literals = 0;
12451 ClauseWithOneMissingHasher hasher(*context_->
random());
12452 const int num_constraints = context_->
working_model->constraints_size();
12453 for (
int c = 0; c < num_constraints; ++c) {
12454 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
12455 if (
ct->constraint_case() == ConstraintProto::kBoolAnd) {
12456 if (
ct->enforcement_literal().size() > 1) {
12458 std::sort(
ct->mutable_enforcement_literal()->begin(),
12459 ct->mutable_enforcement_literal()->end(),
12460 std::greater<int>());
12461 const auto [it, inserted] = bool_and_map.insert(
12462 {hasher.HashOfNegatedLiterals(
ct->enforcement_literal()), c});
12464 to_clean.push_back(c);
12467 ConstraintProto* other_ct =
12469 const absl::Span<const int> s1(
ct->enforcement_literal());
12470 const absl::Span<const int> s2(other_ct->enforcement_literal());
12473 "bool_and: merged constraints with same enforcement");
12474 other_ct->mutable_bool_and()->mutable_literals()->Add(
12475 ct->bool_and().literals().begin(),
12476 ct->bool_and().literals().end());
12484 if (
ct->constraint_case() == ConstraintProto::kAtMostOne) {
12485 const int size =
ct->at_most_one().literals().size();
12486 for (
const int ref :
ct->at_most_one().literals()) {
12487 literal_score[get_index(ref)] +=
size;
12491 if (
ct->constraint_case() == ConstraintProto::kExactlyOne) {
12492 const int size =
ct->exactly_one().literals().size();
12493 for (
const int ref :
ct->exactly_one().literals()) {
12494 literal_score[get_index(ref)] +=
size;
12499 if (
ct->constraint_case() != ConstraintProto::kBoolOr)
continue;
12502 if (!
ct->enforcement_literal().empty())
continue;
12503 if (
ct->bool_or().literals().size() <= 2)
continue;
12505 std::sort(
ct->mutable_bool_or()->mutable_literals()->begin(),
12506 ct->mutable_bool_or()->mutable_literals()->end());
12507 hasher.RegisterClause(c,
ct->bool_or().literals());
12508 bool_or_indices.push_back(c);
12511 for (
const int c : bool_or_indices) {
12512 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
12514 bool merged =
false;
12515 timer.TrackSimpleLoop(
ct->bool_or().literals().size());
12516 if (timer.WorkLimitIsReached())
break;
12517 for (
const int ref :
ct->bool_or().literals()) {
12518 const uint64_t
hash = hasher.HashWithout(c, ref);
12519 const auto it = bool_and_map.find(
hash);
12520 if (it != bool_and_map.end()) {
12522 const int base_c = it->second;
12523 auto* and_ct = context_->
working_model->mutable_constraints(base_c);
12525 ct->bool_or().literals(), and_ct->enforcement_literal(), ref)) {
12527 num_saved_literals +=
ct->bool_or().literals().size() - 1;
12529 and_ct->mutable_bool_and()->add_literals(ref);
12539 int best_ref =
ct->bool_or().literals(0);
12540 int64_t best_score = literal_score[get_index(
NegatedRef(best_ref))];
12541 for (
const int ref :
ct->bool_or().literals()) {
12542 const int64_t score = literal_score[get_index(
NegatedRef(ref))];
12543 if (score > best_score) {
12545 best_score = score;
12549 const uint64_t
hash = hasher.HashWithout(c, best_ref);
12550 const auto [_, inserted] = bool_and_map.insert({
hash,
c});
12552 to_clean.push_back(c);
12554 for (
const int lit :
ct->bool_or().literals()) {
12555 if (
lit == best_ref)
continue;
12559 ct->mutable_enforcement_literal()->Assign(
12561 ct->mutable_bool_and()->add_literals(best_ref);
12567 for (
const int c : to_clean) {
12568 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
12569 if (
ct->bool_and().literals().size() > 1) {
12577 for (
const int ref :
ct->enforcement_literal()) {
12581 ct->mutable_bool_or()->mutable_literals()->Assign(
12585 timer.AddCounter(
"num_collisions", num_collisions);
12586 timer.AddCounter(
"num_merges", num_merges);
12587 timer.AddCounter(
"num_saved_literals", num_saved_literals);
12595 std::vector<int>* postsolve_mapping) {
12597 return presolver.Presolve();
12601 std::vector<int>* postsolve_mapping)
12602 : postsolve_mapping_(postsolve_mapping),
12610CpSolverStatus CpModelPresolver::InfeasibleStatus() {
12612 return CpSolverStatus::INFEASIBLE;
12615void CpModelPresolver::InitializeMappingModelVariables() {
12620 DCHECK_GT(context_->
working_model->variables(
i).domain_size(), 0);
12646 context_->
params().keep_all_feasible_solutions_in_presolve() ||
12647 context_->
params().enumerate_all_solutions() ||
12649 !context_->
params().cp_model_presolve();
12652 for (
const auto& decision_strategy :
12654 *(context_->
mapping_model->add_search_strategy()) = decision_strategy;
12666 if (context_->
working_model->has_floating_point_objective()) {
12669 "The floating point objective cannot be scaled with enough "
12671 return CpSolverStatus::MODEL_INVALID;
12694 if (!context_->
params().cp_model_presolve()) {
12696 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12705 EncodeAllAffineRelations();
12711 InitializeMappingModelVariables();
12714 return CpSolverStatus::UNKNOWN;
12719 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12722 if (!PresolveAffineRelationIfAny(
var))
return InfeasibleStatus();
12727 TryToSimplifyDomain(
var);
12728 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12734 for (
int iter = 0; iter < context_->
params().max_presolve_iterations();
12744 PresolveToFixPoint();
12748 ExtractEncodingFromLinear();
12750 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12756 const int num_constraints = context_->
working_model->constraints().size();
12757 for (
int c = 0;
c < num_constraints; ++
c) {
12758 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
12759 const auto type =
ct->constraint_case();
12760 if (type == ConstraintProto::kAtMostOne ||
12761 type == ConstraintProto::kExactlyOne) {
12765 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12771 const int num_vars = context_->
working_model->variables().size();
12772 for (
int var = 0;
var < num_vars; ++
var) {
12798 if (context_->
params().cp_model_use_sat_presolve()) {
12800 if (!PresolvePureSatPart()) {
12802 "Proven Infeasible during SAT presolve");
12803 return InfeasibleStatus();
12817 const int old_size = context_->
working_model->constraints_size();
12818 for (
int c = 0;
c < old_size; ++
c) {
12819 ConstraintProto*
ct = context_->
working_model->mutable_constraints(c);
12820 if (
ct->constraint_case() != ConstraintProto::kLinear)
continue;
12821 ExtractAtMostOneFromLinear(
ct);
12826 if (context_->
params().cp_model_probing_level() > 0) {
12829 PresolveToFixPoint();
12832 TransformIntoMaxCliques();
12839 ProcessAtMostOneAndLinear();
12840 DetectDuplicateConstraints();
12841 DetectDuplicateConstraintsWithDifferentEnforcements();
12842 DetectDominatedLinearConstraints();
12843 DetectDifferentVariables();
12846 if (context_->
params().find_big_linear_overlap()) {
12847 FindAlmostIdenticalLinearConstraints();
12849 ActivityBoundHelper activity_amo_helper;
12850 activity_amo_helper.AddAllAtMostOnes(*context_->
working_model);
12851 FindBigAtMostOneAndLinearOverlap(&activity_amo_helper);
12856 FindBigVerticalLinearOverlap(&activity_amo_helper);
12857 FindBigHorizontalLinearOverlap(&activity_amo_helper);
12859 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12866 if ( (
false)) DetectIncludedEnforcement();
12871 ConvertToBoolAnd();
12875 PresolveToFixPoint();
12880 const int64_t num_ops =
12882 if (num_ops == 0)
break;
12884 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12887 MergeNoOverlapConstraints();
12888 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12894 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12895 ShiftObjectiveWithExactlyOnes();
12896 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12907 EncodeAllAffineRelations();
12908 if (context_->
ModelIsUnsat())
return InfeasibleStatus();
12918 absl::flat_hash_set<int> used_variables;
12919 for (DecisionStrategyProto& strategy :
12921 CHECK(strategy.variables().empty());
12922 if (strategy.exprs().empty())
continue;
12925 ConstraintProto empy_enforcement;
12926 for (LinearExpressionProto& expr : *strategy.mutable_exprs()) {
12927 CanonicalizeLinearExpression(empy_enforcement, &expr);
12932 for (
const LinearExpressionProto& expr : strategy.exprs()) {
12933 if (context_->
IsFixed(expr))
continue;
12935 const auto [_, inserted] = used_variables.insert(expr.vars(0));
12936 if (!inserted)
continue;
12938 *strategy.mutable_exprs(new_size++) = expr;
12944 InitializeMappingModelVariables();
12947 postsolve_mapping_->clear();
12948 std::vector<int> mapping(context_->
working_model->variables_size(), -1);
12949 absl::flat_hash_map<int64_t, int> constant_to_index;
12950 int num_unused_variables = 0;
12952 if (mapping[
i] != -1)
continue;
12961 mapping[r] = postsolve_mapping_->size();
12962 postsolve_mapping_->push_back(r);
12978 ++num_unused_variables;
12987 auto [it, inserted] = constant_to_index.insert(
12988 {context_->
FixedValue(
i), postsolve_mapping_->size()});
12990 mapping[
i] = it->second;
12995 mapping[
i] = postsolve_mapping_->size();
12996 postsolve_mapping_->push_back(
i);
12998 context_->
UpdateRuleStats(absl::StrCat(
"presolve: ", num_unused_variables,
12999 " unused variables removed."));
13001 if (context_->
params().permute_variable_randomly()) {
13003 const int n = postsolve_mapping_->size();
13004 std::vector<int> perm(n);
13005 std::iota(perm.begin(), perm.end(), 0);
13006 std::shuffle(perm.begin(), perm.end(), *context_->
random());
13008 if (mapping[
i] != -1) mapping[
i] = perm[mapping[
i]];
13010 std::vector<int> new_postsolve_mapping(n);
13011 for (
int i = 0;
i < n; ++
i) {
13012 new_postsolve_mapping[perm[
i]] = (*postsolve_mapping_)[
i];
13014 *postsolve_mapping_ = std::move(new_postsolve_mapping);
13036 const std::string error =
13038 if (!error.empty()) {
13039 SOLVER_LOG(logger_,
"Error while validating postsolved model: ", error);
13040 return CpSolverStatus::MODEL_INVALID;
13045 if (!error.empty()) {
13047 "Error while validating mapping_model model: ", error);
13048 return CpSolverStatus::MODEL_INVALID;
13052 return CpSolverStatus::UNKNOWN;
13056 const PresolveContext&
context) {
13061 auto mapping_function = [&mapping](
int* ref) {
13063 CHECK_GE(image, 0);
13066 for (ConstraintProto& ct_ref : *
proto->mutable_constraints()) {
13072 if (
proto->has_objective()) {
13073 for (
int& mutable_ref : *
proto->mutable_objective()->mutable_vars()) {
13074 mapping_function(&mutable_ref);
13079 for (
int& mutable_ref : *
proto->mutable_assumptions()) {
13080 mapping_function(&mutable_ref);
13085 for (DecisionStrategyProto& strategy : *
proto->mutable_search_strategy()) {
13087 for (LinearExpressionProto expr : strategy.exprs()) {
13088 DCHECK_EQ(expr.vars().size(), 1);
13089 const int image = mapping[expr.vars(0)];
13091 expr.set_vars(0, image);
13092 *strategy.mutable_exprs(new_size++) = expr;
13101 for (
const DecisionStrategyProto& strategy :
proto->search_strategy()) {
13102 if (strategy.exprs().empty())
continue;
13103 *
proto->mutable_search_strategy(new_size++) = strategy;
13111 if (
proto->has_solution_hint()) {
13112 absl::flat_hash_set<int> used_vars;
13113 auto* mutable_hint =
proto->mutable_solution_hint();
13114 mutable_hint->clear_vars();
13115 mutable_hint->clear_values();
13116 const int num_vars =
context.working_model->variables().size();
13117 for (
int hinted_var = 0; hinted_var < num_vars; ++hinted_var) {
13118 if (!
context.VarHasSolutionHint(hinted_var))
continue;
13119 int64_t hinted_value =
context.SolutionHint(hinted_var);
13123 if (hinted_value <
context.MinOf(hinted_var)) {
13124 hinted_value =
context.MinOf(hinted_var);
13126 if (hinted_value >
context.MaxOf(hinted_var)) {
13127 hinted_value =
context.MaxOf(hinted_var);
13133 const AffineRelation::Relation r =
context.GetAffineRelation(hinted_var);
13134 const int var = r.representative;
13135 const int64_t
value = (hinted_value - r.offset) / r.coeff;
13137 const int image = mapping[
var];
13139 if (!used_vars.insert(image).second)
continue;
13140 mutable_hint->add_vars(image);
13141 mutable_hint->add_values(
value);
13147 std::vector<IntegerVariableProto> new_variables;
13148 for (
int i = 0;
i < mapping.size(); ++
i) {
13149 const int image = mapping[
i];
13150 if (image < 0)
continue;
13151 if (image >= new_variables.size()) {
13152 new_variables.resize(image + 1, IntegerVariableProto());
13154 new_variables[image].Swap(
proto->mutable_variables(
i));
13156 proto->clear_variables();
13157 for (IntegerVariableProto& proto_ref : new_variables) {
13158 proto->add_variables()->Swap(&proto_ref);
13162 for (
const IntegerVariableProto& v :
proto->variables()) {
13163 CHECK_GT(v.domain_size(), 0);
13170ConstraintProto CopyObjectiveForDuplicateDetection(
13171 const CpObjectiveProto& objective) {
13172 ConstraintProto copy;
13173 *copy.mutable_linear()->mutable_vars() = objective.vars();
13174 *copy.mutable_linear()->mutable_coeffs() = objective.coeffs();
13178struct ConstraintHashForDuplicateDetection {
13183 ConstraintHashForDuplicateDetection(
const CpModelProto* working_model,
13184 bool ignore_enforcement)
13188 CopyObjectiveForDuplicateDetection(
working_model->objective())) {}
13193 std::size_t operator()(
int ct_idx)
const {
13197 const std::pair<ConstraintProto::ConstraintCase, absl::Span<const int>>
13198 type_and_enforcement = {
ct.constraint_case(),
13200 ? absl::Span<const int>()
13201 :
absl::MakeSpan(
ct.enforcement_literal())};
13202 switch (
ct.constraint_case()) {
13203 case ConstraintProto::kLinear:
13204 if (ignore_enforcement) {
13205 return absl::HashOf(type_and_enforcement,
13206 absl::MakeSpan(ct.linear().vars()),
13207 absl::MakeSpan(ct.linear().coeffs()),
13208 absl::MakeSpan(ct.linear().domain()));
13212 return absl::HashOf(type_and_enforcement,
13213 absl::MakeSpan(ct.linear().vars()),
13214 absl::MakeSpan(ct.linear().coeffs()));
13216 case ConstraintProto::kBoolAnd:
13217 return absl::HashOf(type_and_enforcement,
13218 absl::MakeSpan(
ct.bool_and().literals()));
13219 case ConstraintProto::kBoolOr:
13220 return absl::HashOf(type_and_enforcement,
13221 absl::MakeSpan(
ct.bool_or().literals()));
13222 case ConstraintProto::kAtMostOne:
13223 return absl::HashOf(type_and_enforcement,
13224 absl::MakeSpan(
ct.at_most_one().literals()));
13225 case ConstraintProto::kExactlyOne:
13226 return absl::HashOf(type_and_enforcement,
13227 absl::MakeSpan(
ct.exactly_one().literals()));
13229 ConstraintProto copy =
ct;
13231 if (ignore_enforcement) {
13232 copy.mutable_enforcement_literal()->Clear();
13234 return absl::HashOf(copy.SerializeAsString());
13239struct ConstraintEqForDuplicateDetection {
13244 ConstraintEqForDuplicateDetection(
const CpModelProto* working_model,
13245 bool ignore_enforcement)
13249 CopyObjectiveForDuplicateDetection(
working_model->objective())) {}
13251 bool operator()(
int a,
int b)
const {
13262 if (ct_a.constraint_case() != ct_b.constraint_case())
return false;
13263 if (!ignore_enforcement) {
13264 if (absl::MakeSpan(ct_a.enforcement_literal()) !=
13265 absl::MakeSpan(ct_b.enforcement_literal())) {
13269 switch (ct_a.constraint_case()) {
13270 case ConstraintProto::kLinear:
13273 if (ignore_enforcement && absl::MakeSpan(ct_a.linear().domain()) !=
13274 absl::MakeSpan(ct_b.linear().domain())) {
13277 return absl::MakeSpan(ct_a.linear().vars()) ==
13278 absl::MakeSpan(ct_b.linear().vars()) &&
13279 absl::MakeSpan(ct_a.linear().coeffs()) ==
13280 absl::MakeSpan(ct_b.linear().coeffs());
13281 case ConstraintProto::kBoolAnd:
13282 return absl::MakeSpan(ct_a.bool_and().literals()) ==
13283 absl::MakeSpan(ct_b.bool_and().literals());
13284 case ConstraintProto::kBoolOr:
13285 return absl::MakeSpan(ct_a.bool_or().literals()) ==
13286 absl::MakeSpan(ct_b.bool_or().literals());
13287 case ConstraintProto::kAtMostOne:
13288 return absl::MakeSpan(ct_a.at_most_one().literals()) ==
13289 absl::MakeSpan(ct_b.at_most_one().literals());
13290 case ConstraintProto::kExactlyOne:
13291 return absl::MakeSpan(ct_a.exactly_one().literals()) ==
13292 absl::MakeSpan(ct_b.exactly_one().literals());
13295 ConstraintProto copy_a = ct_a;
13296 ConstraintProto copy_b = ct_b;
13297 copy_a.clear_name();
13298 copy_b.clear_name();
13299 if (ignore_enforcement) {
13300 copy_a.mutable_enforcement_literal()->Clear();
13301 copy_b.mutable_enforcement_literal()->Clear();
13303 return copy_a.SerializeAsString() == copy_b.SerializeAsString();
13312 std::vector<std::pair<int, int>> result;
13316 absl::flat_hash_map<int, int, ConstraintHashForDuplicateDetection,
13317 ConstraintEqForDuplicateDetection>
13319 model_proto.constraints_size(),
13320 ConstraintHashForDuplicateDetection{&model_proto, ignore_enforcement},
13321 ConstraintEqForDuplicateDetection{&model_proto, ignore_enforcement});
13328 const int num_constraints = model_proto.constraints().size();
13329 for (
int c = 0; c < num_constraints; ++c) {
13330 const auto type = model_proto.constraints(c).constraint_case();
13331 if (type == ConstraintProto::CONSTRAINT_NOT_SET)
continue;
13335 if (type == ConstraintProto::kInterval)
continue;
13340 const auto [it, inserted] = equiv_constraints.insert({c, c});
13341 if (it->second != c) {
13343 result.push_back({c, it->second});
13351bool SimpleLinearExprEq(
const LinearExpressionProto&
a,
13352 const LinearExpressionProto&
b) {
13353 return absl::MakeSpan(
a.vars()) == absl::MakeSpan(
b.vars()) &&
13354 absl::MakeSpan(
a.coeffs()) == absl::MakeSpan(
b.coeffs()) &&
13355 a.offset() ==
b.offset();
13358std::size_t LinearExpressionHash(
const LinearExpressionProto& expr) {
13359 return absl::HashOf(absl::MakeSpan(expr.vars()),
13360 absl::MakeSpan(expr.coeffs()), expr.offset());
13365bool CpModelPresolver::IntervalConstraintEq::operator()(
int a,
int b)
const {
13368 return absl::MakeSpan(ct_a.enforcement_literal()) ==
13369 absl::MakeSpan(ct_b.enforcement_literal()) &&
13370 SimpleLinearExprEq(ct_a.interval().start(), ct_b.interval().start()) &&
13371 SimpleLinearExprEq(ct_a.interval().size(), ct_b.interval().size()) &&
13372 SimpleLinearExprEq(ct_a.interval().end(), ct_b.interval().end());
13375std::size_t CpModelPresolver::IntervalConstraintHash::operator()(
13376 int ct_idx)
const {
13378 return absl::HashOf(absl::MakeSpan(
ct.enforcement_literal()),
13379 LinearExpressionHash(
ct.interval().start()),
13380 LinearExpressionHash(
ct.interval().size()),
13381 LinearExpressionHash(
ct.interval().end()));
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 ContinuousMultiplicationBy(int64_t coeff) const
DomainIteratorBeginEnd Values() const &
Domain PositiveModuloBySuperset(const Domain &modulo) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
int64_t SmallestValue() const
Domain DivisionBy(int64_t coeff) const
std::string ToString() const
Domain RelaxIfTooComplex() const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
static int64_t GCD64(int64_t x, int64_t y)
bool LoggingIsEnabled() const
Returns true iff logging is enabled.
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
void Set(IntegerType index)
void AdvanceDeterministicTime(double deterministic_duration)
CpModelPresolver(PresolveContext *context, std::vector< int > *postsolve_mapping)
CpSolverStatus Presolve()
bool PresolveOneConstraint(int c)
Executes presolve method for the given constraint. Public for testing only.
void RemoveEmptyConstraints()
Public for testing only.
int NumDeductions() const
Returns the total number of "deductions" stored by this class.
void MarkProcessingAsDoneForNow()
std::vector< std::pair< int, Domain > > ProcessClause(absl::Span< const int > clause)
void AddDeduction(int literal_ref, int var, Domain domain)
int64_t CurrentMax() const
void Reset(int64_t bound)
void AddMultiples(int64_t coeff, int64_t max_value)
Adds [0, coeff, 2 * coeff, ... max_value * coeff].
void ImportVariablesAndMaybeIgnoreNames(const CpModelProto &in_model)
void CreateVariablesFromDomains(const std::vector< Domain > &domains)
ModelCopy(PresolveContext *context)
bool ImportAndSimplifyConstraints(const CpModelProto &in_model, bool first_copy=false, std::function< bool(int)> active_constraints=nullptr)
DomainDeductions deductions
Advanced presolve. See this class comment.
Domain DomainOf(int ref) const
const SatParameters & params() const
int IntervalUsage(int c) const
ABSL_MUST_USE_RESULT bool CanonicalizeObjective(bool simplify_domain=true)
bool CanonicalizeAffineVariable(int ref, int64_t coeff, int64_t mod, int64_t rhs)
void RegisterVariablesUsedInAssumptions()
bool StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool ObjectiveDomainIsConstraining() const
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
bool ExpressionIsALiteral(const LinearExpressionProto &expr, int *literal=nullptr) const
Returns true iff the expr is a literal (x or not(x)).
CpModelProto * working_model
std::vector< Domain > tmp_term_domains
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
bool VariableIsUniqueAndRemovable(int ref) const
SparseBitset< int > modified_domains
Each time a domain is modified this is set to true.
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool LiteralIsFalse(int lit) const
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality)
int64_t FixedValue(int ref) const
void AddImplication(int a, int b)
a => b.
bool IntervalIsConstant(int ct_ref) const
Helper to query the state of an interval.
bool ShiftCostInExactlyOne(absl::Span< const int > exactly_one, int64_t shift)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
Returns false if the 'lit' doesn't have the desired value in the domain.
absl::Span< const int > ConstraintToVars(int c) const
int NewIntVarWithDefinition(const Domain &domain, absl::Span< const std::pair< int, int64_t > > definition)
int64_t ObjectiveCoeff(int var) const
void ReadObjectiveFromProto()
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(absl::string_view message="")
void CanonicalizeDomainOfSizeTwo(int var)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
int64_t num_presolve_operations
int64_t SizeMax(int ct_ref) const
ABSL_MUST_USE_RESULT bool CanonicalizeOneObjectiveVariable(int var)
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
void WriteObjectiveToProto() const
bool IsFixed(int ref) const
bool VariableIsOnlyUsedInLinear1AndOneExtraConstraint(int var) const
int64_t EndMax(int ct_ref) const
int64_t SizeMin(int ct_ref) const
bool RecomputeSingletonObjectiveDomain()
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int var) const
void CappedUpdateMinMaxActivity(int var, int64_t coeff, int64_t *min_activity, int64_t *max_activity)
Utility function.
std::pair< int64_t, int64_t > ComputeMinMaxActivity(const ProtoWithVarsAndCoeffs &proto) const
void LogInfo()
Logs stats to the logger.
bool ConstraintIsInactive(int ct_index) const
std::vector< Domain > tmp_left_domains
int NewBoolVarWithClause(absl::Span< const int > clause)
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool ConstraintIsOptional(int ct_ref) const
void RemoveAllVariablesFromAffineRelationConstraint()
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
Return a super-set of the domain of the linear expression.
bool ModelIsExpanded() const
void InitializeNewDomains()
Creates the internal structure for any new variables in working_model.
bool VariableIsNotUsedAnymore(int ref) const
Returns true if this ref no longer appears in the model.
absl::flat_hash_set< int > tmp_literal_set
bool VariableIsUnique(int ref) const
Returns true if this ref only appear in one constraint.
bool VariableWasRemoved(int ref) const
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
bool ConstraintVariableGraphIsUpToDate() const
ABSL_MUST_USE_RESULT bool ScaleFloatingPointObjective()
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset, bool debug_no_recursion=false)
int GetLiteralRepresentative(int ref) const
Returns the representative of a literal.
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
void UpdateConstraintVariableUsage(int c)
SparseBitset< int > var_with_reduced_small_degree
bool PropagateAffineRelation(int ref)
std::vector< int > tmp_literals
Temporary storage.
bool CanonicalizeLinearExpression(absl::Span< const int > enforcements, LinearExpressionProto *expr)
bool DomainOfVarIsIncludedIn(int var, const Domain &domain)
This function takes a positive variable reference.
void UpdateRuleStats(const std::string &name, int num_times=1)
AffineRelation::Relation GetAffineRelation(int ref) const
Returns the representative of ref under the affine relations.
const absl::flat_hash_set< int > & VarToConstraints(int var) const
void MarkVariableAsRemoved(int ref)
bool InsertVarValueEncoding(int literal, int var, int64_t value)
void UpdateNewConstraintsVariableUsage()
Calls UpdateConstraintVariableUsage() on all newly created constraints.
bool CanBeUsedAsLiteral(int ref) const
bool keep_all_feasible_solutions
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
bool ConstraintVariableUsageIsConsistent()
int64_t StartMin(int ct_ref) const
int64_t MinOf(int ref) const
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
int64_t MaxOf(int ref) const
void RemoveVariableFromAffineRelation(int var)
ModelRandomGenerator * random()
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
bool VariableWithCostIsUnique(int ref) const
CpModelProto * mapping_model
bool DomainContains(int ref, int64_t value) const
bool ModelIsUnsat() const
bool CanonicalizeLinearConstraint(ConstraintProto *ct)
bool LiteralIsTrue(int lit) const
CpModelProto proto
The output proto.
const CpModelProto * working_model
ConstraintProto objective_constraint
const std::string name
A name for logging purposes.
GurobiMPCallbackContext * context
void Truncate(RepeatedPtrField< T > *array, int new_size)
RepeatedPtrField version.
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
void ApplyVariableMapping(const std::vector< int > &mapping, const PresolveContext &context)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
constexpr uint64_t kDefaultFingerprintSeed
Default seed for fingerprints.
uint64_t FingerprintRepeatedField(const google::protobuf::RepeatedField< T > &sequence, uint64_t seed)
bool RefIsPositive(int ref)
bool ExploitDominanceRelations(const VarDomination &var_domination, PresolveContext *context)
const LiteralIndex kNoLiteralIndex(-1)
std::vector< std::pair< int, int > > FindDuplicateConstraints(const CpModelProto &model_proto, bool ignore_enforcement)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void ExpandCpModel(PresolveContext *context)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
int64_t FloorSquareRoot(int64_t a)
The argument must be non-negative.
IntType CeilOfRatio(IntType numerator, IntType denominator)
bool ImportModelAndDomainsWithBasicPresolveIntoContext(const CpModelProto &in_model, const std::vector< Domain > &domains, std::function< bool(int)> active_constraints, PresolveContext *context)
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 ConstructOverlappingSets(bool already_sorted, std::vector< IndexedInterval > *intervals, std::vector< std::vector< int > > *result)
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 ImportModelWithBasicPresolveIntoContext(const CpModelProto &in_model, PresolveContext *context)
bool IsNegatableInt64(absl::int128 x)
Tells whether a int128 can be casted to a int64_t that can be negated.
CpSolverStatus PresolveCpModel(PresolveContext *context, std::vector< int > *postsolve_mapping)
Convenient wrapper to call the full presolve.
bool SubstituteVariable(int var, int64_t var_coeff_in_definition, const ConstraintProto &definition, ConstraintProto *ct)
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.
bool AddLinearConstraintMultiple(int64_t factor, const ConstraintProto &to_add, ConstraintProto *to_modify)
constexpr int kAffineRelationConstraint
std::vector< int > UsedVariables(const ConstraintProto &ct)
IntegerValue PositiveRemainder(IntegerValue dividend, IntegerValue positive_divisor)
int64_t SafeDoubleToInt64(double value)
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)
std::vector< absl::Span< int > > GetOverlappingRectangleComponents(absl::Span< const Rectangle > rectangles, absl::Span< int > active_rectangles)
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.
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 SetupTextFormatPrinter(google::protobuf::TextFormat::Printer *printer)
void ScanModelForDominanceDetection(PresolveContext &context, VarDomination *var_domination)
void CopyEverythingExceptVariablesAndConstraintsFieldsIntoContext(const CpModelProto &in_model, PresolveContext *context)
Copies the non constraint, non variables part of the model.
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.
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)
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)
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)
std::optional< int64_t > end
const std::optional< Range > & range
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
#define SOLVER_LOG(logger,...)