25#include "absl/container/btree_map.h"
26#include "absl/container/btree_set.h"
27#include "absl/container/flat_hash_map.h"
28#include "absl/container/flat_hash_set.h"
29#include "absl/log/check.h"
30#include "absl/log/log.h"
31#include "absl/log/vlog_is_on.h"
32#include "absl/meta/type_traits.h"
33#include "absl/strings/str_cat.h"
34#include "absl/types/span.h"
73template <
typename Values>
74std::vector<int64_t> ValuesFromProto(
const Values& values) {
75 return std::vector<int64_t>(values.begin(), values.end());
80 return proto.domain_size() == 2 && proto.domain(0) == proto.domain(1);
86 int64_t* single_value) {
87 const auto [sum_min, sum_max] =
88 mapping->ComputeMinMaxActivity(proto, integer_trail);
90 const Domain complement =
91 Domain(sum_min, sum_max)
93 if (complement.IsEmpty())
return false;
94 const int64_t value = complement.
Min();
96 if (complement.Size() == 1) {
97 if (single_value !=
nullptr) {
98 *single_value = value;
108 bool view_all_booleans_as_integers,
Model* m) {
116 CHECK_EQ(sat_solver->NumVariables(), 0);
118 BooleanVariable new_var(0);
119 std::vector<BooleanVariable> false_variables;
120 std::vector<BooleanVariable> true_variables;
123 mapping->reverse_boolean_map_.resize(num_proto_variables, -1);
124 for (
int i = 0;
i < num_proto_variables; ++
i) {
126 if (domain.size() != 2)
continue;
127 if (domain[0] >= 0 && domain[1] <= 1) {
128 mapping->booleans_[
i] = new_var;
129 mapping->reverse_boolean_map_[new_var] =
i;
130 if (domain[1] == 0) {
131 false_variables.push_back(new_var);
132 }
else if (domain[0] == 1) {
133 true_variables.push_back(new_var);
139 sat_solver->SetNumVariables(new_var.value());
140 for (
const BooleanVariable var : true_variables) {
143 for (
const BooleanVariable var : false_variables) {
150 std::vector<int> var_to_instantiate_as_integer;
151 if (view_all_booleans_as_integers) {
152 var_to_instantiate_as_integer.resize(num_proto_variables);
153 for (
int i = 0;
i < num_proto_variables; ++
i) {
154 var_to_instantiate_as_integer[
i] =
i;
158 absl::flat_hash_set<int> used_variables;
160 const bool some_linerization =
173 if (some_linerization) {
196 for (
int i = 0;
i < num_proto_variables; ++
i) {
198 used_variables.insert(
i);
203 var_to_instantiate_as_integer.assign(used_variables.begin(),
204 used_variables.end());
213 int reservation_size = var_to_instantiate_as_integer.size();
216 const int ct_size = ct.linear().vars().size();
218 reservation_size +=
static_cast<int>(std::round(std::sqrt(ct_size)));
222 reservation_size += 1;
223 const int ct_size = model_proto.
objective().
vars().size() + 1;
225 reservation_size +=
static_cast<int>(std::round(std::sqrt(ct_size)));
233 mapping->reverse_integer_map_.resize(2 * var_to_instantiate_as_integer.size(),
235 for (
const int i : var_to_instantiate_as_integer) {
236 const auto& var_proto = model_proto.
variables(
i);
237 mapping->integers_[
i] =
239 DCHECK_LT(mapping->integers_[
i], mapping->reverse_integer_map_.size());
240 mapping->reverse_integer_map_[mapping->integers_[
i]] =
i;
247 for (
int i = 0;
i < num_proto_variables; ++
i) {
252 encoder->AssociateToIntegerEqualValue(
271 mapping->intervals_[c] = intervals_repository->CreateInterval(
277 mapping->intervals_[c] = intervals_repository->CreateInterval(
283 mapping->already_loaded_ct_.insert(&ct);
290 if (symmetry.permutations().empty())
return;
293 const int num_vars = model_proto.
variables().size();
294 std::vector<bool> can_be_used_in_symmetry(num_vars,
true);
297 for (
int v = 0; v < num_vars; ++v) {
298 if (!mapping->IsBoolean(v)) can_be_used_in_symmetry[v] =
false;
309 const int num_constraints = model_proto.
constraints().size();
310 for (
int c = 0; c < num_constraints; ++c) {
325 sat_solver->AddPropagator(symmetry_handler);
326 const int num_literals = 2 * sat_solver->NumVariables();
329 bool can_be_used =
true;
330 for (
const int var : perm.support()) {
331 if (!can_be_used_in_symmetry[var]) {
336 if (!can_be_used)
continue;
339 auto literal_permutation =
340 std::make_unique<SparsePermutation>(num_literals);
341 int support_index = 0;
342 const int num_cycle = perm.cycle_sizes().size();
343 for (
int i = 0;
i < num_cycle; ++
i) {
344 const int size = perm.cycle_sizes(
i);
345 const int saved_support_index = support_index;
346 for (
int j = 0; j < size; ++j) {
347 const int var = perm.support(support_index++);
348 literal_permutation->AddToCurrentCycle(
349 mapping->Literal(var).Index().value());
351 literal_permutation->CloseCurrentCycle();
355 support_index = saved_support_index;
356 for (
int j = 0; j < size; ++j) {
357 const int var = perm.support(support_index++);
358 literal_permutation->AddToCurrentCycle(
359 mapping->Literal(var).NegatedIndex().value());
361 literal_permutation->CloseCurrentCycle();
363 symmetry_handler->AddSymmetry(std::move(literal_permutation));
367 symmetry_handler->num_permutations(),
368 " symmetry to the SAT solver.");
386 if (sat_solver->ModelIsUnsat())
return;
391 struct EqualityDetectionHelper {
397 bool operator<(
const EqualityDetectionHelper& o)
const {
398 if (literal.
Variable() == o.literal.Variable()) {
399 if (value == o.value)
return is_equality && !o.is_equality;
400 return value < o.value;
402 return literal.
Variable() < o.literal.Variable();
405 std::vector<std::vector<EqualityDetectionHelper>> var_to_equalities(
418 struct InequalityDetectionHelper {
423 bool operator<(
const InequalityDetectionHelper& o)
const {
424 if (literal.
Variable() == o.literal.Variable()) {
425 return i_lit.
var < o.i_lit.var;
427 return literal.
Variable() < o.literal.Variable();
430 std::vector<InequalityDetectionHelper> inequalities;
437 if (ct.enforcement_literal().size() != 1)
continue;
438 if (ct.linear().vars_size() != 1)
continue;
442 mapping->
Literal(ct.enforcement_literal(0));
443 if (sat_solver->Assignment().LiteralIsFalse(enforcement_literal))
continue;
445 const int ref = ct.linear().vars(0);
449 const Domain domain_if_enforced =
454 if (domain_if_enforced.
IsEmpty()) {
455 if (!sat_solver->AddUnitClause(enforcement_literal.
Negated()))
return;
461 if (domain_if_enforced.
Max() >= domain.
Max() &&
462 domain_if_enforced.
Min() > domain.
Min()) {
463 inequalities.push_back({&ct, enforcement_literal,
465 mapping->Integer(var),
466 IntegerValue(domain_if_enforced.
Min()))});
467 }
else if (domain_if_enforced.
Min() <= domain.
Min() &&
468 domain_if_enforced.
Max() < domain.
Max()) {
469 inequalities.push_back({&ct, enforcement_literal,
471 mapping->Integer(var),
472 IntegerValue(domain_if_enforced.
Max()))});
478 if (domain_if_enforced.
Min() > domain.
Min()) {
482 mapping->Integer(var), IntegerValue(domain_if_enforced.
Min())));
484 if (domain_if_enforced.
Max() < domain.
Max()) {
488 IntegerValue(domain_if_enforced.
Max())));
499 if (inter.
Min() == 0) {
500 detector->ProcessConditionalZero(enforcement_literal,
501 mapping->Integer(var));
503 var_to_equalities[var].push_back(
504 {&ct, enforcement_literal, inter.
Min(),
true});
511 var_to_equalities[var].push_back(
512 {&ct, enforcement_literal, inter.
Min(),
false});
518 int num_inequalities = 0;
519 std::sort(inequalities.begin(), inequalities.end());
520 for (
int i = 0;
i + 1 < inequalities.size();
i++) {
521 if (inequalities[
i].literal != inequalities[
i + 1].literal.Negated()) {
528 if (integer_trail->IntegerLiteralIsTrue(inequalities[
i].i_lit) ||
529 integer_trail->IntegerLiteralIsFalse(inequalities[
i].i_lit)) {
532 if (integer_trail->IntegerLiteralIsTrue(inequalities[
i + 1].i_lit) ||
533 integer_trail->IntegerLiteralIsFalse(inequalities[
i + 1].i_lit)) {
537 const auto pair_a = encoder->Canonicalize(inequalities[
i].i_lit);
538 const auto pair_b = encoder->Canonicalize(inequalities[
i + 1].i_lit);
539 if (pair_a.first == pair_b.second) {
541 encoder->AssociateToIntegerLiteral(inequalities[
i].literal,
542 inequalities[
i].i_lit);
543 mapping->already_loaded_ct_.insert(inequalities[
i].ct);
544 mapping->already_loaded_ct_.insert(inequalities[
i + 1].ct);
549 int num_half_inequalities = 0;
550 for (
const auto inequality : inequalities) {
551 if (mapping->ConstraintIsAlreadyLoaded(inequality.ct))
continue;
554 encoder->GetOrCreateAssociatedLiteral(inequality.i_lit)));
555 if (sat_solver->ModelIsUnsat())
return;
557 ++num_half_inequalities;
558 mapping->already_loaded_ct_.insert(inequality.ct);
559 mapping->is_half_encoding_ct_.insert(inequality.ct);
561 if (!inequalities.empty()) {
562 SOLVER_LOG(logger,
"[Encoding] ", num_inequalities,
563 " literals associated to VAR >= value, and ",
564 num_half_inequalities,
" half-associations.");
570 int num_equalities = 0;
571 int num_half_equalities = 0;
572 int num_fully_encoded = 0;
573 int num_partially_encoded = 0;
574 for (
int i = 0;
i < var_to_equalities.size(); ++
i) {
575 std::vector<EqualityDetectionHelper>& encoding = var_to_equalities[
i];
576 std::sort(encoding.begin(), encoding.end());
577 if (encoding.empty())
continue;
579 absl::flat_hash_set<int64_t> values;
580 const IntegerVariable var = mapping->integers_[
i];
581 for (
int j = 0; j + 1 < encoding.size(); j++) {
582 if ((encoding[j].value != encoding[j + 1].value) ||
583 (encoding[j].literal != encoding[j + 1].literal.Negated()) ||
584 (encoding[j].is_equality !=
true) ||
585 (encoding[j + 1].is_equality !=
false)) {
590 encoder->AssociateToIntegerEqualValue(encoding[j].literal, var,
591 IntegerValue(encoding[j].value));
592 mapping->already_loaded_ct_.insert(encoding[j].ct);
593 mapping->already_loaded_ct_.insert(encoding[j + 1].ct);
594 values.insert(encoding[j].value);
600 if (sat_solver->ModelIsUnsat())
return;
610 for (
const auto equality : encoding) {
611 if (mapping->ConstraintIsAlreadyLoaded(equality.ct))
continue;
612 if (equality.is_equality) {
625 encoder->GetOrCreateAssociatedLiteral(
629 encoder->GetOrCreateAssociatedLiteral(
632 const Literal eq = encoder->GetOrCreateLiteralAssociatedToEquality(
633 var, equality.value);
637 ++num_half_equalities;
638 mapping->already_loaded_ct_.insert(equality.ct);
639 mapping->is_half_encoding_ct_.insert(equality.ct);
643 if (encoder->VariableIsFullyEncoded(var)) {
646 ++num_partially_encoded;
650 if (num_equalities > 0 || num_half_equalities > 0) {
651 SOLVER_LOG(logger,
"[Encoding] ", num_equalities,
652 " literals associated to VAR == value, and ",
653 num_half_equalities,
" half-associations.");
655 if (num_fully_encoded > 0) {
657 "[Encoding] num_fully_encoded_variables:", num_fully_encoded);
659 if (num_partially_encoded > 0) {
660 SOLVER_LOG(logger,
"[Encoding] num_partially_encoded_variables:",
661 num_partially_encoded);
666 int num_element_encoded = 0;
676 int num_support_clauses = 0;
677 int num_dedicated_propagator = 0;
678 std::vector<Literal> clause;
679 std::vector<Literal> selectors;
680 std::vector<AffineExpression> exprs;
681 std::vector<AffineExpression> negated_exprs;
687 absl::btree_map<IntegerVariable, std::vector<ValueLiteralPair>>
688 var_to_value_literal_list;
691 for (
const auto& var_value : implied_bounds->GetImpliedValues(literal)) {
692 var_to_value_literal_list[var_value.first].push_back(
693 {var_value.second, literal});
698 std::vector<IntegerVariable> encoded_variables;
699 std::string encoded_variables_str;
702 for (
auto& [var, encoding] : var_to_value_literal_list) {
704 VLOG(2) <<
"X" << var.value() <<
" has " << encoding.size()
705 <<
" implied values, and a domain of size "
707 ->InitialVariableDomain(var)
713 ++num_element_encoded;
714 element_encodings->
Add(var, encoding, c);
716 encoded_variables.push_back(var);
717 absl::StrAppend(&encoded_variables_str,
" X", var.value());
723 bool need_extra_propagation =
false;
724 std::sort(encoding.begin(), encoding.end(),
726 for (
int i = 0, j = 0;
i < encoding.size();
i = j) {
728 const IntegerValue value = encoding[
i].value;
729 while (j < encoding.size() && encoding[j].value == value) ++j;
733 if (!encoder->IsFixedOrHasAssociatedLiteral(
735 !encoder->IsFixedOrHasAssociatedLiteral(
737 need_extra_propagation =
true;
740 encoder->AssociateToIntegerEqualValue(encoding[
i].literal, var,
744 if (encoder->GetAssociatedEqualityLiteral(var, value) ==
746 need_extra_propagation =
true;
754 ++num_support_clauses;
756 for (
int k =
i; k < j; ++k) clause.push_back(encoding[k].literal);
758 encoder->GetOrCreateLiteralAssociatedToEquality(var, value);
759 clause.push_back(eq_lit.
Negated());
764 sat_solver->AddProblemClause(clause);
767 if (need_extra_propagation) {
768 ++num_dedicated_propagator;
771 negated_exprs.clear();
772 for (
const auto [value, literal] : encoding) {
773 selectors.push_back(literal);
784 NegationOf(var), negated_exprs, selectors, {}, m);
789 if (encoded_variables.size() > 1 && VLOG_IS_ON(1)) {
790 VLOG(1) <<
"exactly_one(" << c <<
") encodes " << encoded_variables.size()
791 <<
" variables at the same time: " << encoded_variables_str;
795 if (num_element_encoded > 0) {
797 "[Encoding] num_element_encoding: ", num_element_encoded);
799 if (num_support_clauses > 0) {
800 SOLVER_LOG(logger,
"[Encoding] Added ", num_support_clauses,
801 " element support clauses, and ", num_dedicated_propagator,
802 " dedicated propagators.");
813 int64_t num_associations = 0;
814 int64_t num_set_to_false = 0;
816 if (!ct.enforcement_literal().empty())
continue;
818 if (ct.linear().vars_size() != 2)
continue;
819 if (!ConstraintIsEq(ct.linear()))
continue;
821 const IntegerValue rhs(ct.linear().domain(0));
824 IntegerVariable var1 = mapping->Integer(ct.linear().vars(0));
825 IntegerVariable var2 = mapping->Integer(ct.linear().vars(1));
826 IntegerValue coeff1(ct.linear().coeffs(0));
827 IntegerValue coeff2(ct.linear().coeffs(1));
839 if (coeff1 == 0 || coeff2 == 0)
continue;
844 for (
int i = 0;
i < 2; ++
i) {
845 for (
const auto [value1, literal1] :
846 encoder->PartialGreaterThanEncoding(var1)) {
847 const IntegerValue bound2 =
FloorRatio(rhs - value1 * coeff1, coeff2);
849 encoder->AssociateToIntegerLiteral(
852 std::swap(var1, var2);
853 std::swap(coeff1, coeff2);
861 for (
int i = 0;
i < 2; ++
i) {
862 const auto copy = encoder->PartialDomainEncoding(var1);
863 for (
const auto value_literal : copy) {
864 const IntegerValue value1 = value_literal.value;
865 const IntegerValue intermediate = rhs - value1 * coeff1;
866 if (intermediate % coeff2 != 0) {
869 if (!sat_solver->AddUnitClause(value_literal.literal.Negated())) {
875 encoder->AssociateToIntegerEqualValue(value_literal.literal, var2,
876 intermediate / coeff2);
878 std::swap(var1, var2);
879 std::swap(coeff1, coeff2);
883 if (num_associations > 0) {
884 VLOG(1) <<
"Num associations from equivalences = " << num_associations;
886 if (num_set_to_false > 0) {
887 VLOG(1) <<
"Num literals set to false from equivalences = "
899 std::vector<bool> already_seen(num_proto_variables,
false);
914 std::vector<std::vector<int>> enforcement_intersection(num_proto_variables);
915 absl::btree_set<int> literals_set;
920 already_seen[var] =
true;
921 enforcement_intersection[var].clear();
924 literals_set.clear();
928 if (!already_seen[var]) {
933 std::vector<int>& vector_ref = enforcement_intersection[var];
935 for (
const int literal : vector_ref) {
936 if (literals_set.contains(literal)) {
937 vector_ref[new_size++] = literal;
940 vector_ref.resize(new_size);
942 already_seen[var] =
true;
948 int num_optionals = 0;
949 for (
int var = 0; var < num_proto_variables; ++var) {
951 const int64_t min = var_proto.
domain(0);
952 const int64_t max = var_proto.
domain(var_proto.
domain().size() - 1);
953 if (min == max)
continue;
954 if (min == 0 && max == 1)
continue;
955 if (enforcement_intersection[var].empty())
continue;
960 if (num_optionals > 0) {
962 " optional variables. Note that for now we DO NOT do anything "
963 "with this information.");
974 if (strategy.domain_reduction_strategy() ==
977 const int var = expr.vars(0);
978 if (!mapping->IsInteger(var))
continue;
979 const IntegerVariable variable = mapping->Integer(var);
980 if (!integer_trail->IsFixed(variable)) {
993 auto* mapping = m->GetOrCreate<CpModelMapping>();
995 std::vector<Literal> literals = mapping->Literals(ct.bool_or().literals());
996 for (
const int ref : ct.enforcement_literal()) {
997 literals.push_back(mapping->Literal(ref).Negated());
1000 if (literals.size() == 3) {
1001 m->GetOrCreate<ProductDetector>()->ProcessTernaryClause(literals);
1007 std::vector<Literal> literals;
1008 for (
const int ref : ct.enforcement_literal()) {
1009 literals.push_back(mapping->Literal(ref).Negated());
1011 auto* sat_solver = m->GetOrCreate<SatSolver>();
1012 for (
const Literal literal : mapping->Literals(ct.bool_and().literals())) {
1013 literals.push_back(literal);
1014 sat_solver->AddProblemClause(literals);
1015 literals.pop_back();
1023 if (!implications->AddAtMostOne(
1024 mapping->Literals(ct.at_most_one().literals()))) {
1025 m->GetOrCreate<
SatSolver>()->NotifyThatModelIsUnsat();
1030 auto* mapping = m->GetOrCreate<CpModelMapping>();
1032 const auto& literals = mapping->Literals(ct.exactly_one().literals());
1034 if (literals.size() == 3) {
1035 m->GetOrCreate<
ProductDetector>()->ProcessTernaryExactlyOne(literals);
1040 auto* mapping = m->GetOrCreate<CpModelMapping>();
1042 m->Add(
LiteralXorIs(mapping->Literals(ct.bool_xor().literals()),
true));
1049void LoadEquivalenceAC(
const std::vector<Literal> enforcement_literal,
1050 IntegerValue coeff1, IntegerVariable var1,
1051 IntegerValue coeff2, IntegerVariable var2,
1052 const IntegerValue rhs,
Model* m) {
1053 auto* encoder = m->GetOrCreate<IntegerEncoder>();
1054 CHECK(encoder->VariableIsFullyEncoded(var1));
1055 CHECK(encoder->VariableIsFullyEncoded(var2));
1056 absl::flat_hash_map<IntegerValue, Literal> term1_value_to_literal;
1057 for (
const auto value_literal : encoder->FullDomainEncoding(var1)) {
1058 term1_value_to_literal[coeff1 * value_literal.value] =
1059 value_literal.literal;
1061 const auto copy = encoder->FullDomainEncoding(var2);
1062 for (
const auto value_literal : copy) {
1063 const IntegerValue target = rhs - value_literal.value * coeff2;
1064 if (!term1_value_to_literal.contains(target)) {
1066 {value_literal.literal.Negated()}));
1068 const Literal target_literal = term1_value_to_literal[target];
1070 {value_literal.literal.Negated(), target_literal}));
1072 {value_literal.literal, target_literal.Negated()}));
1076 term1_value_to_literal.erase(target);
1082 std::vector<Literal> implied_false;
1083 for (
const auto entry : term1_value_to_literal) {
1084 implied_false.push_back(entry.second);
1086 std::sort(implied_false.begin(), implied_false.end());
1087 for (
const Literal l : implied_false) {
1094void LoadEquivalenceNeqAC(
const std::vector<Literal> enforcement_literal,
1095 IntegerValue coeff1, IntegerVariable var1,
1096 IntegerValue coeff2, IntegerVariable var2,
1097 const IntegerValue rhs,
Model* m) {
1098 auto* encoder = m->GetOrCreate<IntegerEncoder>();
1099 CHECK(encoder->VariableIsFullyEncoded(var1));
1100 CHECK(encoder->VariableIsFullyEncoded(var2));
1101 absl::flat_hash_map<IntegerValue, Literal> term1_value_to_literal;
1102 for (
const auto value_literal : encoder->FullDomainEncoding(var1)) {
1103 term1_value_to_literal[coeff1 * value_literal.value] =
1104 value_literal.literal;
1106 const auto copy = encoder->FullDomainEncoding(var2);
1107 for (
const auto value_literal : copy) {
1108 const IntegerValue target_value = rhs - value_literal.value * coeff2;
1109 const auto& it = term1_value_to_literal.find(target_value);
1110 if (it != term1_value_to_literal.end()) {
1111 const Literal target_literal = it->second;
1113 enforcement_literal,
1114 {value_literal.literal.Negated(), target_literal.Negated()}));
1120 if (ct.enforcement_literal().size() != 1)
return false;
1121 if (ct.linear().vars().size() > 2)
return false;
1122 if (ct.linear().domain().size() != 2)
return false;
1123 if (ct.linear().domain(0) != 0)
return false;
1124 if (ct.linear().domain(1) != 0)
return false;
1125 for (
const int64_t coeff : ct.linear().coeffs()) {
1126 if (std::abs(coeff) != 1)
return false;
1136 std::vector<IntegerVariable>* vars,
1137 std::vector<int64_t>* coeffs,
1146 std::vector<IntegerVariable> bucket_sum_vars;
1147 std::vector<int64_t> bucket_sum_coeffs;
1148 std::vector<IntegerVariable> local_vars;
1149 std::vector<int64_t> local_coeffs;
1152 const int64_t num_vars = vars->size();
1153 const int64_t num_buckets =
static_cast<int>(std::round(std::sqrt(num_vars)));
1155 for (int64_t
b = 0;
b < num_buckets; ++
b) {
1157 local_coeffs.clear();
1158 int64_t bucket_lb = 0;
1159 int64_t bucket_ub = 0;
1161 const int64_t limit = num_vars * (
b + 1);
1162 for (;
i * num_buckets < limit; ++
i) {
1163 const IntegerVariable var = (*vars)[
i];
1164 const int64_t coeff = (*coeffs)[
i];
1165 gcd = std::gcd(gcd, std::abs(coeff));
1166 local_vars.push_back(var);
1167 local_coeffs.push_back(coeff);
1168 const int64_t term1 = coeff * integer_trail->LowerBound(var).value();
1169 const int64_t term2 = coeff * integer_trail->UpperBound(var).value();
1170 bucket_lb += std::min(term1, term2);
1171 bucket_ub += std::max(term1, term2);
1173 if (gcd == 0)
continue;
1176 for (int64_t& ref : local_coeffs) ref /= gcd;
1181 const IntegerVariable bucket_sum =
1182 integer_trail->AddIntegerVariable(bucket_lb, bucket_ub);
1183 bucket_sum_vars.push_back(bucket_sum);
1184 bucket_sum_coeffs.push_back(gcd);
1185 local_vars.push_back(bucket_sum);
1186 local_coeffs.push_back(-1);
1197 *vars = bucket_sum_vars;
1198 *coeffs = bucket_sum_coeffs;
1202 auto* mapping = m->GetOrCreate<CpModelMapping>();
1204 if (ct.linear().vars().empty()) {
1208 std::vector<Literal> clause;
1209 for (
const int ref : ct.enforcement_literal()) {
1210 clause.push_back(mapping->Literal(ref).Negated());
1214 VLOG(1) <<
"Trivially UNSAT constraint: " << ct;
1215 m->GetOrCreate<
SatSolver>()->NotifyThatModelIsUnsat();
1220 if (IsPartOfProductEncoding(ct)) {
1221 const Literal l = mapping->Literal(ct.enforcement_literal(0));
1222 auto* detector = m->GetOrCreate<ProductDetector>();
1223 if (ct.linear().vars().size() == 1) {
1226 detector->ProcessConditionalZero(l,
1227 mapping->Integer(ct.linear().vars(0)));
1228 }
else if (ct.linear().vars().size() == 2) {
1229 const IntegerVariable x = mapping->Integer(ct.linear().vars(0));
1230 const IntegerVariable y = mapping->Integer(ct.linear().vars(1));
1231 detector->ProcessConditionalEquality(
1233 ct.linear().coeffs(0) == ct.linear().coeffs(1) ?
NegationOf(y) : y);
1238 std::vector<IntegerVariable> vars = mapping->Integers(ct.linear().vars());
1239 std::vector<int64_t> coeffs = ValuesFromProto(ct.linear().coeffs());
1245 IntegerValue min_sum(0);
1246 IntegerValue max_sum(0);
1247 IntegerValue max_domain_size(0);
1248 bool all_booleans =
true;
1249 for (
int i = 0;
i < vars.size(); ++
i) {
1250 if (all_booleans && !mapping->IsBoolean(ct.linear().vars(
i))) {
1251 all_booleans =
false;
1253 const IntegerValue lb = integer_trail->LowerBound(vars[
i]);
1254 const IntegerValue ub = integer_trail->UpperBound(vars[
i]);
1255 max_domain_size = std::max(max_domain_size, ub - lb + 1);
1256 const IntegerValue term_a = coeffs[
i] * lb;
1257 const IntegerValue term_b = coeffs[
i] * ub;
1258 min_sum += std::min(term_a, term_b);
1259 max_sum += std::max(term_a, term_b);
1268 int64_t rhs_min = ct.linear().domain(0);
1269 int64_t rhs_max = ct.linear().domain(ct.linear().domain().size() - 1);
1270 rhs_min = std::max(rhs_min, min_sum.value());
1271 rhs_max = std::min(rhs_max, max_sum.value());
1273 if (vars.size() == 2) {
1275 precedences->AddBounds(expr, rhs_min, rhs_max);
1276 }
else if (vars.size() == 3) {
1281 for (
int i = 0;
i < 3; ++
i) {
1282 for (
int j = 0; j < 3; ++j) {
1283 if (
i == j)
continue;
1284 const int other = 3 -
i - j;
1286 const int64_t coeff = coeffs[other];
1287 const int64_t other_lb =
1289 ? coeff * integer_trail->LowerBound(vars[other]).value()
1290 : coeff * integer_trail->UpperBound(vars[other]).value();
1291 const int64_t other_ub =
1293 ? coeff * integer_trail->UpperBound(vars[other]).value()
1294 : coeff * integer_trail->LowerBound(vars[other]).value();
1296 precedences->AddBounds(expr, rhs_min - other_ub, rhs_max - other_lb);
1303 const IntegerValue domain_size_limit(
1304 params.max_domain_size_when_encoding_eq_neq_constraints());
1305 if (ct.linear().vars_size() == 2 && !integer_trail->IsFixed(vars[0]) &&
1306 !integer_trail->IsFixed(vars[1]) &&
1307 max_domain_size <= domain_size_limit) {
1309 if (params.boolean_encoding_level() > 0 && ConstraintIsEq(ct.linear()) &&
1310 ct.linear().domain(0) != min_sum && ct.linear().domain(0) != max_sum &&
1311 encoder->VariableIsFullyEncoded(vars[0]) &&
1312 encoder->VariableIsFullyEncoded(vars[1])) {
1313 VLOG(3) <<
"Load AC version of " << ct <<
", var0 domain = "
1314 << integer_trail->InitialVariableDomain(vars[0])
1315 <<
", var1 domain = "
1316 << integer_trail->InitialVariableDomain(vars[1]);
1317 return LoadEquivalenceAC(mapping->Literals(ct.enforcement_literal()),
1318 IntegerValue(coeffs[0]), vars[0],
1319 IntegerValue(coeffs[1]), vars[1],
1320 IntegerValue(ct.linear().domain(0)), m);
1323 int64_t single_value = 0;
1324 if (params.boolean_encoding_level() > 0 &&
1325 ConstraintIsNEq(ct.linear(), mapping, integer_trail, &single_value) &&
1326 single_value != min_sum && single_value != max_sum &&
1327 encoder->VariableIsFullyEncoded(vars[0]) &&
1328 encoder->VariableIsFullyEncoded(vars[1])) {
1329 VLOG(3) <<
"Load NAC version of " << ct <<
", var0 domain = "
1330 << integer_trail->InitialVariableDomain(vars[0])
1331 <<
", var1 domain = "
1332 << integer_trail->InitialVariableDomain(vars[1])
1333 <<
", value = " << single_value;
1334 return LoadEquivalenceNeqAC(mapping->Literals(ct.enforcement_literal()),
1335 IntegerValue(coeffs[0]), vars[0],
1336 IntegerValue(coeffs[1]), vars[1],
1337 IntegerValue(single_value), m);
1345 ct.linear().domain_size() == 2 && all_booleans;
1346 if (!pseudo_boolean &&
1347 ct.linear().vars().size() > params.linear_split_size()) {
1348 const auto& domain = ct.linear().domain();
1350 domain.size() > 2 || min_sum < domain[0],
1351 domain.size() > 2 || max_sum > domain[1], &vars, &coeffs, m);
1354 if (ct.linear().domain_size() == 2) {
1355 const int64_t lb = ct.linear().domain(0);
1356 const int64_t ub = ct.linear().domain(1);
1357 const std::vector<Literal> enforcement_literals =
1358 mapping->Literals(ct.enforcement_literal());
1359 if (all_booleans && enforcement_literals.empty()) {
1362 std::vector<LiteralWithCoeff> cst;
1363 for (
int i = 0;
i < vars.size(); ++
i) {
1364 const int ref = ct.linear().vars(
i);
1365 cst.push_back({mapping->Literal(ref), coeffs[
i]});
1367 m->GetOrCreate<
SatSolver>()->AddLinearConstraint(
1369 (max_sum > ub), ub, &cst);
1385 const bool is_linear1 = vars.size() == 1 && coeffs[0] == 1;
1387 bool special_case =
false;
1388 std::vector<Literal> clause;
1389 std::vector<Literal> for_enumeration;
1391 const int domain_size = ct.linear().domain_size();
1392 for (
int i = 0;
i < domain_size;
i += 2) {
1393 const int64_t lb = ct.linear().domain(
i);
1394 const int64_t ub = ct.linear().domain(
i + 1);
1397 if (min_sum > ub)
continue;
1398 if (max_sum < lb)
continue;
1402 if (min_sum >= lb && max_sum <= ub)
return;
1407 encoding->GetOrCreateLiteralAssociatedToEquality(vars[0], lb));
1409 }
else if (min_sum >= lb) {
1410 clause.push_back(encoding->GetOrCreateAssociatedLiteral(
1413 }
else if (max_sum <= ub) {
1414 clause.push_back(encoding->GetOrCreateAssociatedLiteral(
1423 if (ct.enforcement_literal().empty() && clause.size() == 1 &&
1424 i + 1 == domain_size) {
1425 special_case =
true;
1428 const Literal subdomain_literal(
1429 special_case ? clause.back().Negated()
1431 clause.push_back(subdomain_literal);
1432 for_enumeration.push_back(subdomain_literal);
1442 const std::vector<Literal> enforcement_literals =
1443 mapping->Literals(ct.enforcement_literal());
1446 if (params.enumerate_all_solutions() && !enforcement_literals.empty()) {
1448 if (enforcement_literals.size() == 1) {
1449 linear_is_enforced = enforcement_literals[0];
1452 std::vector<Literal> maintain_linear_is_enforced;
1453 for (
const Literal e_lit : enforcement_literals) {
1454 m->Add(
Implication(e_lit.Negated(), linear_is_enforced.Negated()));
1455 maintain_linear_is_enforced.push_back(e_lit.Negated());
1457 maintain_linear_is_enforced.push_back(linear_is_enforced);
1460 for (
const Literal lit : for_enumeration) {
1461 m->Add(
Implication(linear_is_enforced.Negated(), lit.Negated()));
1462 if (special_case)
break;
1466 if (!special_case) {
1467 for (
const Literal e_lit : enforcement_literals) {
1468 clause.push_back(e_lit.Negated());
1476 const std::vector<AffineExpression> expressions =
1477 mapping->
Affines(ct.all_diff().exprs());
1482 auto* mapping = m->GetOrCreate<CpModelMapping>();
1484 std::vector<AffineExpression> terms;
1486 terms.push_back(mapping->Affine(expr));
1488 switch (terms.size()) {
1490 auto* integer_trail = m->GetOrCreate<IntegerTrail>();
1491 auto* sat_solver = m->GetOrCreate<SatSolver>();
1494 sat_solver->NotifyThatModelIsUnsat();
1499 sat_solver->NotifyThatModelIsUnsat();
1506 builder.AddTerm(prod, 1);
1507 builder.AddTerm(terms[0], -1);
1516 LOG(FATAL) <<
"Loading int_prod with arity > 2, should not be here.";
1528 if (integer_trail->IsFixed(denom)) {
1531 if (VLOG_IS_ON(1)) {
1532 LinearConstraintBuilder builder(m);
1533 if (m->GetOrCreate<ProductDecomposer>()->TryToLinearize(num, denom,
1535 VLOG(1) <<
"Division " << ct <<
" can be linearized";
1549 CHECK(integer_trail->IsFixed(mod));
1550 const IntegerValue fixed_modulo = integer_trail->FixedValue(mod);
1555 if (ct.lin_max().exprs().empty()) {
1560 auto* mapping = m->GetOrCreate<CpModelMapping>();
1561 const LinearExpression max = mapping->GetExprFromProto(ct.lin_max().target());
1562 std::vector<LinearExpression> negated_exprs;
1563 negated_exprs.reserve(ct.lin_max().exprs_size());
1564 for (
int i = 0;
i < ct.lin_max().exprs_size(); ++
i) {
1565 negated_exprs.push_back(
1566 NegationOf(mapping->GetExprFromProto(ct.lin_max().exprs(
i))));
1578 if (ct.no_overlap_2d().x_intervals().empty())
return;
1580 const std::vector<IntervalVariable> x_intervals =
1581 mapping->
Intervals(ct.no_overlap_2d().x_intervals());
1582 const std::vector<IntervalVariable> y_intervals =
1583 mapping->Intervals(ct.no_overlap_2d().y_intervals());
1588 auto* mapping = m->GetOrCreate<CpModelMapping>();
1589 const std::vector<IntervalVariable> intervals =
1590 mapping->Intervals(ct.cumulative().intervals());
1591 const AffineExpression capacity = mapping->Affine(ct.cumulative().capacity());
1592 const std::vector<AffineExpression> demands =
1593 mapping->Affines(ct.cumulative().demands());
1594 m->Add(
Cumulative(intervals, demands, capacity));
1598 auto* mapping = m->GetOrCreate<CpModelMapping>();
1600 const std::vector<AffineExpression> times =
1601 mapping->Affines(ct.reservoir().time_exprs());
1602 const std::vector<AffineExpression> level_changes =
1603 mapping->Affines(ct.reservoir().level_changes());
1604 std::vector<Literal> presences;
1605 const int size = ct.reservoir().time_exprs().size();
1606 for (
int i = 0;
i < size; ++
i) {
1607 if (!ct.reservoir().active_literals().empty()) {
1608 presences.push_back(mapping->Literal(ct.reservoir().active_literals(
i)));
1610 presences.push_back(encoder->GetTrueLiteral());
1614 ct.reservoir().min_level(), ct.reservoir().max_level(),
1619 const auto& circuit = ct.circuit();
1620 if (circuit.tails().empty())
return;
1622 std::vector<int> tails(circuit.tails().begin(), circuit.tails().end());
1623 std::vector<int> heads(circuit.heads().begin(), circuit.heads().end());
1624 std::vector<Literal> literals =
1626 const int num_nodes =
ReindexArcs(&tails, &heads);
1631 const auto& routes = ct.routes();
1632 if (routes.tails().empty())
return;
1634 std::vector<int> tails(routes.tails().begin(), routes.tails().end());
1635 std::vector<int> heads(routes.heads().begin(), routes.heads().end());
1636 std::vector<Literal> literals =
1638 const int num_nodes =
ReindexArcs(&tails, &heads);
1644 switch (ct.constraint_case()) {
1662 case ConstraintProto::ConstraintProto::kLinear:
1665 case ConstraintProto::ConstraintProto::kAllDiff:
1668 case ConstraintProto::ConstraintProto::kIntProd:
1671 case ConstraintProto::ConstraintProto::kIntDiv:
1674 case ConstraintProto::ConstraintProto::kIntMod:
1677 case ConstraintProto::ConstraintProto::kLinMax:
1680 case ConstraintProto::ConstraintProto::kInterval:
1683 case ConstraintProto::ConstraintProto::kNoOverlap:
1686 case ConstraintProto::ConstraintProto::kNoOverlap2D:
1689 case ConstraintProto::ConstraintProto::kCumulative:
1692 case ConstraintProto::ConstraintProto::kReservoir:
1695 case ConstraintProto::ConstraintProto::kCircuit:
1698 case ConstraintProto::ConstraintProto::kRoutes:
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
Domain Complement() const
Domain InverseMultiplicationBy(int64_t coeff) const
::int32_t literals(int index) const
int literals_size() const
repeated int32 literals = 1;
::int32_t literals(int index) const
ConstraintCase constraint_case() const
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::IntervalConstraintProto & interval() const
const ::operations_research::sat::RoutesConstraintProto & routes() const
const ::operations_research::sat::CircuitConstraintProto & circuit() const
sat::Literal Literal(int ref) const
std::vector< IntervalVariable > Intervals(const ProtoIndices &indices) const
std::vector< AffineExpression > Affines(const List &list) const
const ::operations_research::sat::SymmetryProto & symmetry() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
bool has_objective() const
.operations_research.sat.CpObjectiveProto objective = 4;
const ::operations_research::sat::DecisionStrategyProto & search_strategy(int index) const
int constraints_size() const
repeated .operations_research.sat.ConstraintProto constraints = 3;
int variables_size() const
repeated .operations_research.sat.IntegerVariableProto variables = 2;
const ::operations_research::sat::CpObjectiveProto & objective() const
::int32_t vars(int index) const
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
void RegisterWith(GenericLiteralWatcher *watcher)
bool Add(Literal literal, IntegerLiteral integer_literal)
void ReserveSpaceForNumVariables(int num_vars)
::int64_t domain(int index) const
const ::operations_research::sat::LinearExpressionProto & size() const
const ::operations_research::sat::LinearExpressionProto & end() const
const ::operations_research::sat::LinearExpressionProto & start() const
::int32_t vars(int index) const
::int64_t domain(int index) const
LiteralIndex Index() const
BooleanVariable Variable() const
Literal(int signed_value)
T Add(std::function< T(Model *)> f)
::int32_t literals(int index) const
bool enumerate_all_solutions() const
bool use_optional_variables() const
bool AddProblemClause(absl::Span< const Literal > literals)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
std::function< std::vector< ValueLiteralPair >(Model *)> FullyEncodeVariable(IntegerVariable var)
void LoadBoolXorConstraint(const ConstraintProto &ct, Model *m)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
void LoadCumulativeConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> LiteralXorIs(const std::vector< Literal > &literals, bool value)
Enforces the XOR of a set of literals to be equal to the given value.
bool RefIsPositive(int ref)
void LoadLinMaxConstraint(const ConstraintProto &ct, Model *m)
void LoadIntProdConstraint(const ConstraintProto &ct, Model *m)
void SplitAndLoadIntermediateConstraints(bool lb_required, bool ub_required, std::vector< IntegerVariable > *vars, std::vector< int64_t > *coeffs, Model *m)
std::function< void(Model *)> WeightedSumGreaterOrEqual(absl::Span< const IntegerVariable > vars, const VectorInt &coefficients, int64_t lower_bound)
Weighted sum >= constant.
const LiteralIndex kNoLiteralIndex(-1)
std::function< void(Model *)> ProductConstraint(AffineExpression a, AffineExpression b, AffineExpression p)
Adds the constraint: a * b = p.
void DetectOptionalVariables(const CpModelProto &model_proto, Model *m)
Automatically detect optional variables.
void LoadBoolOrConstraint(const ConstraintProto &ct, Model *m)
void LoadSubcircuitConstraint(int num_nodes, absl::Span< const int > tails, absl::Span< const int > heads, absl::Span< const Literal > literals, Model *model, bool multiple_subcircuit_through_zero)
std::function< void(Model *)> ClauseConstraint(absl::Span< const Literal > literals)
std::function< void(Model *)> EnforcedClause(absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > clause)
enforcement_literals => clause.
std::function< BooleanVariable(Model *)> NewBooleanVariable()
std::function< void(Model *)> FixedDivisionConstraint(AffineExpression a, IntegerValue b, AffineExpression c)
Adds the constraint: a / b = c where b is a constant.
bool HasEnforcementLiteral(const ConstraintProto &ct)
Small utility functions to deal with half-reified constraints.
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Returns the vector of the negated variables.
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
std::function< void(Model *)> IsEqualToMinOf(const LinearExpression &min_expr, const std::vector< LinearExpression > &exprs)
void LoadIntModConstraint(const ConstraintProto &ct, Model *m)
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> Implication(absl::Span< const Literal > enforcement_literals, IntegerLiteral i)
const IntervalVariable kNoIntervalVariable(-1)
std::function< void(Model *)> AllDifferentOnBounds(absl::Span< const AffineExpression > expressions)
void LoadBooleanSymmetries(const CpModelProto &model_proto, Model *m)
std::function< void(Model *)> DivisionConstraint(AffineExpression num, AffineExpression denom, AffineExpression div)
Adds the constraint: num / denom = div. (denom > 0).
void LoadRoutesConstraint(const ConstraintProto &ct, Model *m)
void LoadAtMostOneConstraint(const ConstraintProto &ct, Model *m)
void AddFullEncodingFromSearchBranching(const CpModelProto &model_proto, Model *m)
std::vector< int > UsedVariables(const ConstraintProto &ct)
void LoadCircuitConstraint(const ConstraintProto &ct, Model *m)
void ExtractElementEncoding(const CpModelProto &model_proto, Model *m)
void LoadReservoirConstraint(const ConstraintProto &ct, Model *m)
void AddWeightedSumLowerOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const int64_t > coefficients, int64_t upper_bound, Model *model)
enforcement_literals => sum <= upper_bound
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
void LoadNoOverlapConstraint(const ConstraintProto &ct, Model *m)
void LoadAllDiffConstraint(const ConstraintProto &ct, Model *m)
void AddDisjunctive(const std::vector< IntervalVariable > &intervals, Model *model)
std::vector< Literal > Literals(absl::Span< const int > input)
void LoadNoOverlap2dConstraint(const ConstraintProto &ct, Model *m)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void LoadBoolAndConstraint(const ConstraintProto &ct, Model *m)
void AddNonOverlappingRectangles(const std::vector< IntervalVariable > &x, const std::vector< IntervalVariable > &y, Model *model)
void LoadExactlyOneConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> ExactlyOneConstraint(absl::Span< const Literal > literals)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> Cumulative(const std::vector< IntervalVariable > &vars, absl::Span< const AffineExpression > demands, AffineExpression capacity, SchedulingConstraintHelper *helper)
void PropagateEncodingFromEquivalenceRelations(const CpModelProto &model_proto, Model *m)
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< AffineExpression > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
std::function< void(Model *)> FixedModuloConstraint(AffineExpression a, IntegerValue b, AffineExpression c)
Adds the constraint: a % b = c where b is a constant.
const BooleanVariable kNoBooleanVariable(-1)
void LoadLinearConstraint(const ConstraintProto &ct, Model *m)
void AddWeightedSumGreaterOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const int64_t > coefficients, int64_t lower_bound, Model *model)
enforcement_literals => sum >= lower_bound
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
void LoadIntDivConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> WeightedSumLowerOrEqual(absl::Span< const IntegerVariable > vars, const VectorInt &coefficients, int64_t upper_bound)
Weighted sum <= constant.
In SWIG mode, we don't want anything besides these top-level includes.
int64_t Min() const
Returns the min of the domain.
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.
IntegerLiteral LowerOrEqual(IntegerValue bound) const
var * coeff + constant <= bound.
std::vector< int > variables
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
#define SOLVER_LOG(logger,...)