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/strings/str_cat.h"
30 #include "absl/types/span.h"
38 #include "ortools/sat/cp_model.pb.h"
52 #include "ortools/sat/sat_parameters.pb.h"
65 template <
typename Values>
66 std::vector<int64_t> ValuesFromProto(
const Values& values) {
67 return std::vector<int64_t>(values.begin(), values.end());
70 void ComputeLinearBounds(
const LinearConstraintProto&
proto,
71 CpModelMapping* mapping, IntegerTrail* integer_trail,
72 int64_t* sum_min, int64_t* sum_max) {
76 for (
int i = 0; i <
proto.vars_size(); ++i) {
77 const int64_t coeff =
proto.coeffs(i);
78 const IntegerVariable
var = mapping->Integer(
proto.vars(i));
79 const int64_t lb = integer_trail->LowerBound(
var).value();
80 const int64_t ub = integer_trail->UpperBound(
var).value();
82 (*sum_min) += coeff * lb;
83 (*sum_max) += coeff * ub;
85 (*sum_min) += coeff * ub;
86 (*sum_max) += coeff * lb;
92 bool ConstraintIsEq(
const LinearConstraintProto&
proto) {
97 bool ConstraintIsNEq(
const LinearConstraintProto&
proto,
98 CpModelMapping* mapping, IntegerTrail* integer_trail,
99 int64_t* single_value) {
102 ComputeLinearBounds(
proto, mapping, integer_trail, &sum_min, &sum_max);
104 const Domain complement =
105 Domain(sum_min, sum_max)
107 if (complement.IsEmpty())
return false;
108 const int64_t
value = complement.Min();
110 if (complement.Size() == 1) {
111 if (single_value !=
nullptr) {
112 *single_value =
value;
122 bool view_all_booleans_as_integers,
Model* m) {
124 const int num_proto_variables =
model_proto.variables_size();
130 CHECK_EQ(sat_solver->NumVariables(), 0);
132 BooleanVariable new_var(0);
133 std::vector<BooleanVariable> false_variables;
134 std::vector<BooleanVariable> true_variables;
137 mapping->reverse_boolean_map_.resize(num_proto_variables, -1);
138 for (
int i = 0; i < num_proto_variables; ++i) {
139 const auto& domain =
model_proto.variables(i).domain();
140 if (domain.size() != 2)
continue;
141 if (domain[0] >= 0 && domain[1] <= 1) {
142 mapping->booleans_[i] = new_var;
143 mapping->reverse_boolean_map_[new_var] = i;
144 if (domain[1] == 0) {
145 false_variables.push_back(new_var);
146 }
else if (domain[0] == 1) {
147 true_variables.push_back(new_var);
153 sat_solver->SetNumVariables(new_var.value());
154 for (
const BooleanVariable
var : true_variables) {
157 for (
const BooleanVariable
var : false_variables) {
164 std::vector<int> var_to_instantiate_as_integer;
165 if (view_all_booleans_as_integers) {
166 var_to_instantiate_as_integer.resize(num_proto_variables);
167 for (
int i = 0; i < num_proto_variables; ++i) {
168 var_to_instantiate_as_integer[i] = i;
172 absl::flat_hash_set<int> used_variables;
175 for (
int c = 0; c <
model_proto.constraints_size(); ++c) {
186 for (
const int obj_var :
model_proto.objective().vars()) {
193 for (
int i = 0; i < num_proto_variables; ++i) {
195 used_variables.insert(i);
200 var_to_instantiate_as_integer.assign(used_variables.begin(),
201 used_variables.end());
210 int reservation_size = var_to_instantiate_as_integer.size();
211 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
212 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
213 const int ct_size =
ct.linear().vars().size();
215 reservation_size +=
static_cast<int>(std::round(std::sqrt(ct_size)));
219 reservation_size += 1;
220 const int ct_size =
model_proto.objective().vars().size() + 1;
222 reservation_size +=
static_cast<int>(std::round(std::sqrt(ct_size)));
230 mapping->reverse_integer_map_.resize(2 * var_to_instantiate_as_integer.size(),
232 for (
const int i : var_to_instantiate_as_integer) {
234 mapping->integers_[i] =
236 DCHECK_LT(mapping->integers_[i], mapping->reverse_integer_map_.size());
237 mapping->reverse_integer_map_[mapping->integers_[i]] = i;
244 for (
int i = 0; i < num_proto_variables; ++i) {
249 encoder->AssociateToIntegerEqualValue(
250 sat::Literal(mapping->booleans_[i],
true), mapping->integers_[i],
255 mapping->intervals_.resize(
model_proto.constraints_size(),
257 for (
int c = 0; c <
model_proto.constraints_size(); ++c) {
259 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kInterval) {
264 mapping->
Literal(
ct.enforcement_literal(0));
268 mapping->intervals_[c] = intervals_repository->CreateInterval(
269 mapping->Affine(
ct.interval().start()),
270 mapping->Affine(
ct.interval().end()),
271 mapping->Affine(
ct.interval().size()), enforcement_literal.
Index(),
274 mapping->intervals_[c] = intervals_repository->CreateInterval(
275 mapping->Affine(
ct.interval().start()),
276 mapping->Affine(
ct.interval().end()),
280 mapping->already_loaded_ct_.insert(&
ct);
286 const SymmetryProto& symmetry =
model_proto.symmetry();
287 if (symmetry.permutations().empty())
return;
290 const int num_vars =
model_proto.variables().size();
291 std::vector<bool> can_be_used_in_symmetry(num_vars,
true);
294 for (
int v = 0; v < num_vars; ++v) {
295 if (!mapping->IsBoolean(v)) can_be_used_in_symmetry[v] =
false;
306 const int num_constraints =
model_proto.constraints().size();
307 for (
int c = 0; c < num_constraints; ++c) {
309 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
310 if (
ct.linear().domain().size() <= 2)
continue;
315 for (
const int ref :
ct.linear().vars()) {
322 sat_solver->AddPropagator(symmetry_handler);
323 const int num_literals = 2 * sat_solver->NumVariables();
325 for (
const SparsePermutationProto& perm : symmetry.permutations()) {
326 bool can_be_used =
true;
327 for (
const int var : perm.support()) {
328 if (!can_be_used_in_symmetry[
var]) {
333 if (!can_be_used)
continue;
336 auto literal_permutation =
337 std::make_unique<SparsePermutation>(num_literals);
338 int support_index = 0;
339 const int num_cycle = perm.cycle_sizes().size();
340 for (
int i = 0; i < num_cycle; ++i) {
341 const int size = perm.cycle_sizes(i);
342 const int saved_support_index = support_index;
343 for (
int j = 0; j < size; ++j) {
344 const int var = perm.support(support_index++);
345 literal_permutation->AddToCurrentCycle(
346 mapping->Literal(
var).Index().value());
348 literal_permutation->CloseCurrentCycle();
352 support_index = saved_support_index;
353 for (
int j = 0; j < size; ++j) {
354 const int var = perm.support(support_index++);
355 literal_permutation->AddToCurrentCycle(
356 mapping->Literal(
var).NegatedIndex().value());
358 literal_permutation->CloseCurrentCycle();
360 symmetry_handler->AddSymmetry(std::move(literal_permutation));
364 symmetry_handler->num_permutations(),
365 " symmetry to the SAT solver.");
381 if (sat_solver->ModelIsUnsat())
return;
386 struct EqualityDetectionHelper {
387 const ConstraintProto*
ct;
392 bool operator<(
const EqualityDetectionHelper& o)
const {
393 if (
literal.Variable() == o.literal.Variable()) {
394 if (
value == o.value)
return is_equality && !o.is_equality;
395 return value < o.value;
397 return literal.Variable() < o.literal.Variable();
400 std::vector<std::vector<EqualityDetectionHelper>> var_to_equalities(
413 struct InequalityDetectionHelper {
414 const ConstraintProto*
ct;
418 bool operator<(
const InequalityDetectionHelper& o)
const {
419 if (
literal.Variable() == o.literal.Variable()) {
420 return i_lit.
var < o.i_lit.var;
422 return literal.Variable() < o.literal.Variable();
425 std::vector<InequalityDetectionHelper> inequalities;
428 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
429 if (
ct.constraint_case() != ConstraintProto::ConstraintCase::kLinear) {
432 if (
ct.enforcement_literal().size() != 1)
continue;
433 if (
ct.linear().vars_size() != 1)
continue;
437 mapping->
Literal(
ct.enforcement_literal(0));
438 if (sat_solver->Assignment().LiteralIsFalse(enforcement_literal))
continue;
440 const int ref =
ct.linear().vars(0);
444 const Domain domain_if_enforced =
449 if (domain_if_enforced.
IsEmpty()) {
450 if (!sat_solver->AddUnitClause(enforcement_literal.
Negated()))
return;
456 if (domain_if_enforced.
Max() >= domain.
Max() &&
457 domain_if_enforced.
Min() > domain.
Min()) {
458 inequalities.push_back({&
ct, enforcement_literal,
460 mapping->Integer(
var),
461 IntegerValue(domain_if_enforced.
Min()))});
462 }
else if (domain_if_enforced.
Min() <= domain.
Min() &&
463 domain_if_enforced.
Max() < domain.
Max()) {
464 inequalities.push_back({&
ct, enforcement_literal,
466 mapping->Integer(
var),
467 IntegerValue(domain_if_enforced.
Max()))});
473 if (domain_if_enforced.
Min() > domain.
Min()) {
477 mapping->Integer(
var), IntegerValue(domain_if_enforced.
Min())));
479 if (domain_if_enforced.
Max() < domain.
Max()) {
483 IntegerValue(domain_if_enforced.
Max())));
494 if (inter.
Min() == 0) {
495 detector->ProcessConditionalZero(enforcement_literal,
496 mapping->Integer(
var));
498 var_to_equalities[
var].push_back(
499 {&
ct, enforcement_literal, inter.
Min(),
true});
506 var_to_equalities[
var].push_back(
507 {&
ct, enforcement_literal, inter.
Min(),
false});
513 int num_inequalities = 0;
514 std::sort(inequalities.begin(), inequalities.end());
515 for (
int i = 0; i + 1 < inequalities.size(); i++) {
523 if (integer_trail->IntegerLiteralIsTrue(inequalities[i].i_lit) ||
524 integer_trail->IntegerLiteralIsFalse(inequalities[i].i_lit)) {
527 if (integer_trail->IntegerLiteralIsTrue(inequalities[i + 1].i_lit) ||
528 integer_trail->IntegerLiteralIsFalse(inequalities[i + 1].i_lit)) {
532 const auto pair_a = encoder->Canonicalize(inequalities[i].i_lit);
533 const auto pair_b = encoder->Canonicalize(inequalities[i + 1].i_lit);
534 if (pair_a.first == pair_b.second) {
536 encoder->AssociateToIntegerLiteral(inequalities[i].
literal,
537 inequalities[i].i_lit);
538 mapping->already_loaded_ct_.insert(inequalities[i].
ct);
539 mapping->already_loaded_ct_.insert(inequalities[i + 1].
ct);
544 int num_half_inequalities = 0;
545 for (
const auto inequality : inequalities) {
546 if (mapping->ConstraintIsAlreadyLoaded(inequality.ct))
continue;
549 encoder->GetOrCreateAssociatedLiteral(inequality.i_lit)));
550 if (sat_solver->ModelIsUnsat())
return;
552 ++num_half_inequalities;
553 mapping->already_loaded_ct_.insert(inequality.ct);
554 mapping->is_half_encoding_ct_.insert(inequality.ct);
557 if (!inequalities.empty()) {
558 VLOG(1) << num_inequalities <<
" literals associated to VAR >= value, and "
559 << num_half_inequalities <<
" half-associations.";
565 int num_constraints = 0;
566 int num_equalities = 0;
567 int num_half_equalities = 0;
568 int num_fully_encoded = 0;
569 int num_partially_encoded = 0;
570 for (
int i = 0; i < var_to_equalities.size(); ++i) {
571 std::vector<EqualityDetectionHelper>& encoding = var_to_equalities[i];
572 std::sort(encoding.begin(), encoding.end());
573 if (encoding.empty())
continue;
574 num_constraints += encoding.size();
576 absl::flat_hash_set<int64_t> values;
577 for (
int j = 0; j + 1 < encoding.size(); j++) {
578 if ((encoding[j].
value != encoding[j + 1].
value) ||
580 (encoding[j].is_equality !=
true) ||
581 (encoding[j + 1].is_equality !=
false)) {
586 encoder->AssociateToIntegerEqualValue(encoding[j].
literal,
587 mapping->integers_[i],
588 IntegerValue(encoding[j].
value));
589 mapping->already_loaded_ct_.insert(encoding[j].
ct);
590 mapping->already_loaded_ct_.insert(encoding[j + 1].
ct);
591 values.insert(encoding[j].
value);
597 if (sat_solver->ModelIsUnsat())
return;
605 for (
const auto equality : encoding) {
606 if (mapping->ConstraintIsAlreadyLoaded(equality.ct))
continue;
607 const class Literal eq = encoder->GetOrCreateLiteralAssociatedToEquality(
608 mapping->integers_[i], IntegerValue(equality.value));
609 if (equality.is_equality) {
615 ++num_half_equalities;
616 mapping->already_loaded_ct_.insert(equality.ct);
617 mapping->is_half_encoding_ct_.insert(equality.ct);
622 if (encoder->VariableIsFullyEncoded(mapping->integers_[i])) {
625 ++num_partially_encoded;
630 if (num_constraints > 0) {
631 VLOG(1) << num_equalities <<
" literals associated to VAR == value, and "
632 << num_half_equalities <<
" half-associations.";
634 if (num_fully_encoded > 0) {
635 VLOG(1) <<
"num_fully_encoded_variables: " << num_fully_encoded;
637 if (num_partially_encoded > 0) {
638 VLOG(1) <<
"num_partially_encoded_variables: " << num_partially_encoded;
643 int num_element_encoded = 0;
649 for (
int c = 0; c <
model_proto.constraints_size(); ++c) {
652 if (
ct.constraint_case() != ConstraintProto::kExactlyOne)
continue;
655 absl::flat_hash_map<IntegerVariable, std::vector<ValueLiteralPair>>
656 var_to_value_literal_list;
657 for (
const int l :
ct.exactly_one().literals()) {
659 for (
const auto& var_value : implied_bounds->GetImpliedValues(
literal)) {
660 var_to_value_literal_list[var_value.first].push_back(
666 std::vector<IntegerVariable> encoded_variables;
667 std::string encoded_variables_str;
670 for (
const auto& [
var, literal_value_list] : var_to_value_literal_list) {
671 if (literal_value_list.size() <
ct.exactly_one().literals_size()) {
672 VLOG(2) <<
"X" <<
var.value() <<
" has " << literal_value_list.size()
673 <<
" implied values, and a domain of size "
675 ->InitialVariableDomain(
var)
681 implied_bounds->AddElementEncoding(
var, literal_value_list, c);
683 encoded_variables.push_back(
var);
684 absl::StrAppend(&encoded_variables_str,
" X",
var.value());
685 num_element_encoded++;
688 if (encoded_variables.size() > 1 &&
VLOG_IS_ON(1)) {
689 VLOG(1) <<
"exactly_one(" << c <<
") encodes " << encoded_variables.size()
690 <<
" variables at the same time: " << encoded_variables_str;
694 if (num_element_encoded > 0) {
695 VLOG(1) <<
"num_element_encoded: " << num_element_encoded;
706 int64_t num_associations = 0;
707 int64_t num_set_to_false = 0;
708 for (
const ConstraintProto&
ct :
model_proto.constraints()) {
709 if (!
ct.enforcement_literal().empty())
continue;
710 if (
ct.constraint_case() != ConstraintProto::kLinear)
continue;
711 if (
ct.linear().vars_size() != 2)
continue;
712 if (!ConstraintIsEq(
ct.linear()))
continue;
714 const IntegerValue rhs(
ct.linear().domain(0));
717 IntegerVariable var1 = mapping->Integer(
ct.linear().vars(0));
718 IntegerVariable var2 = mapping->Integer(
ct.linear().vars(1));
719 IntegerValue coeff1(
ct.linear().coeffs(0));
720 IntegerValue coeff2(
ct.linear().coeffs(1));
732 if (coeff1 == 0 || coeff2 == 0)
continue;
737 for (
int i = 0; i < 2; ++i) {
738 for (
const auto [value1, literal1] :
739 encoder->PartialGreaterThanEncoding(var1)) {
740 const IntegerValue bound2 =
FloorRatio(rhs - value1 * coeff1, coeff2);
742 encoder->AssociateToIntegerLiteral(
754 for (
int i = 0; i < 2; ++i) {
755 for (
const auto value_literal : encoder->PartialDomainEncoding(var1)) {
756 const IntegerValue value1 = value_literal.value;
757 const IntegerValue intermediate = rhs - value1 * coeff1;
758 if (intermediate % coeff2 != 0) {
761 sat_solver->AddUnitClause(value_literal.literal.Negated());
765 encoder->AssociateToIntegerEqualValue(value_literal.literal, var2,
766 intermediate / coeff2);
773 if (num_associations > 0) {
774 VLOG(1) <<
"Num associations from equivalences = " << num_associations;
776 if (num_set_to_false > 0) {
777 VLOG(1) <<
"Num literals set to false from equivalences = "
785 if (!
parameters.use_optional_variables())
return;
786 if (
parameters.enumerate_all_solutions())
return;
789 const int num_proto_variables =
model_proto.variables_size();
790 std::vector<bool> already_seen(num_proto_variables,
false);
792 for (
const int ref :
model_proto.objective().vars()) {
805 std::vector<std::vector<int>> enforcement_intersection(num_proto_variables);
806 absl::btree_set<int> literals_set;
807 for (
int c = 0; c <
model_proto.constraints_size(); ++c) {
809 if (
ct.enforcement_literal().empty()) {
811 already_seen[
var] =
true;
812 enforcement_intersection[
var].clear();
815 literals_set.clear();
816 literals_set.insert(
ct.enforcement_literal().begin(),
817 ct.enforcement_literal().end());
819 if (!already_seen[
var]) {
820 enforcement_intersection[
var].assign(
ct.enforcement_literal().begin(),
821 ct.enforcement_literal().end());
824 std::vector<int>& vector_ref = enforcement_intersection[
var];
826 for (
const int literal : vector_ref) {
827 if (literals_set.contains(
literal)) {
828 vector_ref[new_size++] =
literal;
831 vector_ref.resize(new_size);
833 already_seen[
var] =
true;
839 int num_optionals = 0;
841 for (
int var = 0;
var < num_proto_variables; ++
var) {
842 const IntegerVariableProto& var_proto =
model_proto.variables(
var);
843 const int64_t
min = var_proto.domain(0);
844 const int64_t
max = var_proto.domain(var_proto.domain().size() - 1);
846 if (
min == 0 &&
max == 1)
continue;
847 if (enforcement_intersection[
var].empty())
continue;
850 integer_trail->MarkIntegerVariableAsOptional(
851 mapping->Integer(
var),
852 mapping->Literal(enforcement_intersection[
var].front()));
855 if (num_optionals > 0) {
857 " optional variables.");
867 for (
const DecisionStrategyProto& strategy :
model_proto.search_strategy()) {
868 if (strategy.domain_reduction_strategy() ==
869 DecisionStrategyProto::SELECT_MEDIAN_VALUE) {
870 for (
const int ref : strategy.variables()) {
871 if (!mapping->IsInteger(ref))
continue;
872 const IntegerVariable variable = mapping->Integer(
PositiveRef(ref));
873 if (!integer_trail->IsFixed(variable)) {
887 std::vector<Literal> literals = mapping->
Literals(
ct.bool_or().literals());
888 for (
const int ref :
ct.enforcement_literal()) {
889 literals.push_back(mapping->Literal(ref).Negated());
892 if (literals.size() == 3) {
899 std::vector<Literal> literals;
900 for (
const int ref :
ct.enforcement_literal()) {
901 literals.push_back(mapping->Literal(ref).Negated());
920 const auto& literals = mapping->
Literals(
ct.exactly_one().literals());
922 if (literals.size() == 3) {
937 void LoadEquivalenceAC(
const std::vector<Literal> enforcement_literal,
938 IntegerValue coeff1, IntegerVariable var1,
939 IntegerValue coeff2, IntegerVariable var2,
940 const IntegerValue rhs, Model* m) {
941 auto* encoder = m->GetOrCreate<IntegerEncoder>();
942 CHECK(encoder->VariableIsFullyEncoded(var1));
943 CHECK(encoder->VariableIsFullyEncoded(var2));
944 absl::flat_hash_map<IntegerValue, Literal> term1_value_to_literal;
945 for (
const auto value_literal : encoder->FullDomainEncoding(var1)) {
946 term1_value_to_literal[coeff1 * value_literal.value] =
947 value_literal.literal;
949 for (
const auto value_literal : encoder->FullDomainEncoding(var2)) {
950 const IntegerValue target = rhs - value_literal.value * coeff2;
951 if (!term1_value_to_literal.contains(target)) {
953 {value_literal.literal.Negated()}));
955 const Literal target_literal = term1_value_to_literal[target];
957 {value_literal.literal.Negated(), target_literal}));
959 {value_literal.literal, target_literal.Negated()}));
963 term1_value_to_literal.erase(target);
969 std::vector<Literal> implied_false;
970 for (
const auto entry : term1_value_to_literal) {
971 implied_false.push_back(entry.second);
973 std::sort(implied_false.begin(), implied_false.end());
974 for (
const Literal l : implied_false) {
981 void LoadEquivalenceNeqAC(
const std::vector<Literal> enforcement_literal,
982 IntegerValue coeff1, IntegerVariable var1,
983 IntegerValue coeff2, IntegerVariable var2,
984 const IntegerValue rhs, Model* m) {
985 auto* encoder = m->GetOrCreate<IntegerEncoder>();
986 CHECK(encoder->VariableIsFullyEncoded(var1));
987 CHECK(encoder->VariableIsFullyEncoded(var2));
988 absl::flat_hash_map<IntegerValue, Literal> term1_value_to_literal;
989 for (
const auto value_literal : encoder->FullDomainEncoding(var1)) {
990 term1_value_to_literal[coeff1 * value_literal.value] =
991 value_literal.literal;
993 for (
const auto value_literal : encoder->FullDomainEncoding(var2)) {
994 const IntegerValue target_value = rhs - value_literal.value * coeff2;
995 const auto& it = term1_value_to_literal.find(target_value);
996 if (it != term1_value_to_literal.end()) {
997 const Literal target_literal = it->second;
1000 {value_literal.literal.Negated(), target_literal.Negated()}));
1005 bool IsPartOfProductEncoding(
const ConstraintProto&
ct) {
1006 if (
ct.enforcement_literal().size() != 1)
return false;
1007 if (
ct.linear().vars().size() > 2)
return false;
1008 if (
ct.linear().domain().size() != 2)
return false;
1009 if (
ct.linear().domain(0) != 0)
return false;
1010 if (
ct.linear().domain(1) != 0)
return false;
1011 for (
const int64_t coeff :
ct.linear().coeffs()) {
1012 if (std::abs(coeff) != 1)
return false;
1022 std::vector<IntegerVariable>* vars,
1023 std::vector<int64_t>* coeffs,
1027 if (m->
GetOrCreate<SatParameters>()->enumerate_all_solutions()) {
1032 std::vector<IntegerVariable> bucket_sum_vars;
1033 std::vector<int64_t> bucket_sum_coeffs;
1034 std::vector<IntegerVariable> local_vars;
1035 std::vector<int64_t> local_coeffs;
1038 const int64_t num_vars = vars->size();
1039 const int64_t num_buckets =
static_cast<int>(std::round(std::sqrt(num_vars)));
1041 for (int64_t
b = 0;
b < num_buckets; ++
b) {
1043 local_coeffs.clear();
1044 int64_t bucket_lb = 0;
1045 int64_t bucket_ub = 0;
1047 const int64_t limit = num_vars * (
b + 1);
1048 for (; i * num_buckets < limit; ++i) {
1049 const IntegerVariable
var = (*vars)[i];
1050 const int64_t coeff = (*coeffs)[i];
1051 gcd = std::gcd(gcd, std::abs(coeff));
1052 local_vars.push_back(
var);
1053 local_coeffs.push_back(coeff);
1054 const int64_t term1 = coeff * integer_trail->LowerBound(
var).value();
1055 const int64_t term2 = coeff * integer_trail->UpperBound(
var).value();
1056 bucket_lb +=
std::min(term1, term2);
1057 bucket_ub +=
std::max(term1, term2);
1059 if (gcd == 0)
continue;
1062 for (int64_t& ref : local_coeffs) ref /= gcd;
1067 const IntegerVariable bucket_sum =
1068 integer_trail->AddIntegerVariable(bucket_lb, bucket_ub);
1069 bucket_sum_vars.push_back(bucket_sum);
1070 bucket_sum_coeffs.push_back(gcd);
1071 local_vars.push_back(bucket_sum);
1072 local_coeffs.push_back(-1);
1083 *vars = bucket_sum_vars;
1084 *coeffs = bucket_sum_coeffs;
1089 if (
ct.linear().vars().empty()) {
1093 std::vector<Literal> clause;
1094 for (
const int ref :
ct.enforcement_literal()) {
1095 clause.push_back(mapping->Literal(ref).Negated());
1099 VLOG(1) <<
"Trivially UNSAT constraint: " <<
ct.DebugString();
1105 if (IsPartOfProductEncoding(
ct)) {
1108 if (
ct.linear().vars().size() == 1) {
1112 mapping->Integer(
ct.linear().vars(0)));
1113 }
else if (
ct.linear().vars().size() == 2) {
1114 const IntegerVariable x = mapping->Integer(
ct.linear().vars(0));
1115 const IntegerVariable y = mapping->Integer(
ct.linear().vars(1));
1116 detector->ProcessConditionalEquality(
1118 ct.linear().coeffs(0) ==
ct.linear().coeffs(1) ?
NegationOf(y) : y);
1123 std::vector<IntegerVariable> vars = mapping->Integers(
ct.linear().vars());
1124 std::vector<int64_t> coeffs = ValuesFromProto(
ct.linear().coeffs());
1130 IntegerValue min_sum(0);
1131 IntegerValue max_sum(0);
1132 IntegerValue max_domain_size(0);
1133 bool all_booleans =
true;
1134 for (
int i = 0; i < vars.size(); ++i) {
1135 if (all_booleans && !mapping->IsBoolean(
ct.linear().vars(i))) {
1136 all_booleans =
false;
1138 const IntegerValue lb = integer_trail->LowerBound(vars[i]);
1139 const IntegerValue ub = integer_trail->UpperBound(vars[i]);
1140 max_domain_size =
std::max(max_domain_size, ub - lb + 1);
1141 const IntegerValue term_a = coeffs[i] * lb;
1142 const IntegerValue term_b = coeffs[i] * ub;
1143 min_sum +=
std::min(term_a, term_b);
1144 max_sum +=
std::max(term_a, term_b);
1147 const SatParameters& params = *m->
GetOrCreate<SatParameters>();
1148 const IntegerValue domain_size_limit(
1149 params.max_domain_size_when_encoding_eq_neq_constraints());
1150 if (
ct.linear().vars_size() == 2 && !integer_trail->IsFixed(vars[0]) &&
1151 !integer_trail->IsFixed(vars[1]) &&
1152 max_domain_size <= domain_size_limit) {
1154 if (params.boolean_encoding_level() > 0 && ConstraintIsEq(
ct.linear()) &&
1155 ct.linear().domain(0) != min_sum &&
ct.linear().domain(0) != max_sum &&
1156 encoder->VariableIsFullyEncoded(vars[0]) &&
1157 encoder->VariableIsFullyEncoded(vars[1])) {
1158 VLOG(3) <<
"Load AC version of " <<
ct.DebugString() <<
", var0 domain = "
1159 << integer_trail->InitialVariableDomain(vars[0])
1160 <<
", var1 domain = "
1161 << integer_trail->InitialVariableDomain(vars[1]);
1162 return LoadEquivalenceAC(mapping->Literals(
ct.enforcement_literal()),
1163 IntegerValue(coeffs[0]), vars[0],
1164 IntegerValue(coeffs[1]), vars[1],
1165 IntegerValue(
ct.linear().domain(0)), m);
1168 int64_t single_value = 0;
1169 if (params.boolean_encoding_level() > 0 &&
1170 ConstraintIsNEq(
ct.linear(), mapping, integer_trail, &single_value) &&
1171 single_value != min_sum && single_value != max_sum &&
1172 encoder->VariableIsFullyEncoded(vars[0]) &&
1173 encoder->VariableIsFullyEncoded(vars[1])) {
1174 VLOG(3) <<
"Load NAC version of " <<
ct.DebugString()
1175 <<
", var0 domain = "
1176 << integer_trail->InitialVariableDomain(vars[0])
1177 <<
", var1 domain = "
1178 << integer_trail->InitialVariableDomain(vars[1])
1179 <<
", value = " << single_value;
1180 return LoadEquivalenceNeqAC(mapping->Literals(
ct.enforcement_literal()),
1181 IntegerValue(coeffs[0]), vars[0],
1182 IntegerValue(coeffs[1]), vars[1],
1183 IntegerValue(single_value), m);
1191 ct.linear().domain_size() == 2 && all_booleans;
1192 if (
ct.linear().vars().size() > 100 && !pseudo_boolean) {
1193 const auto& domain =
ct.linear().domain();
1195 domain.size() > 2 || min_sum < domain[0],
1196 domain.size() > 2 || max_sum > domain[1], &vars, &coeffs, m);
1199 if (
ct.linear().domain_size() == 2) {
1200 int64_t lb =
ct.linear().domain(0);
1201 int64_t ub =
ct.linear().domain(1);
1209 std::vector<LiteralWithCoeff> cst;
1210 for (
int i = 0; i < vars.size(); ++i) {
1211 const int ref =
ct.linear().vars(i);
1212 cst.push_back({mapping->Literal(ref), coeffs[i]});
1224 const std::vector<Literal> enforcement_literals =
1225 mapping->Literals(
ct.enforcement_literal());
1238 const bool special_case =
1239 ct.enforcement_literal().empty() &&
ct.linear().domain_size() == 4;
1241 std::vector<Literal> clause;
1242 for (
int i = 0; i <
ct.linear().domain_size(); i += 2) {
1243 int64_t lb =
ct.linear().domain(i);
1244 int64_t ub =
ct.linear().domain(i + 1);
1248 const Literal subdomain_literal(
1249 special_case && i > 0 ? clause.back().Negated()
1251 clause.push_back(subdomain_literal);
1263 const std::vector<Literal> enforcement_literals =
1264 mapping->Literals(
ct.enforcement_literal());
1267 if (params.enumerate_all_solutions() && !enforcement_literals.empty()) {
1269 if (enforcement_literals.size() == 1) {
1270 linear_is_enforced = enforcement_literals[0];
1273 std::vector<Literal> maintain_linear_is_enforced;
1274 for (
const Literal e_lit : enforcement_literals) {
1275 m->
Add(
Implication(e_lit.Negated(), linear_is_enforced.Negated()));
1276 maintain_linear_is_enforced.push_back(e_lit.Negated());
1278 maintain_linear_is_enforced.push_back(linear_is_enforced);
1281 for (
const Literal lit : clause) {
1282 m->
Add(
Implication(linear_is_enforced.Negated(), lit.Negated()));
1283 if (special_case)
break;
1287 if (!special_case) {
1288 for (
const Literal e_lit : enforcement_literals) {
1289 clause.push_back(e_lit.Negated());
1298 const std::vector<AffineExpression> expressions =
1299 mapping->
Affines(
ct.all_diff().exprs());
1306 CHECK_EQ(
ct.int_prod().exprs_size(), 2)
1307 <<
"General int_prod not supported yet.";
1314 VLOG(1) <<
"Product " <<
ct.DebugString() <<
" can be linearized";
1326 if (integer_trail->IsFixed(denom)) {
1332 VLOG(1) <<
"Division " <<
ct.DebugString() <<
" can be linearized";
1346 CHECK(integer_trail->IsFixed(mod));
1347 const IntegerValue fixed_modulo = integer_trail->FixedValue(mod);
1352 if (
ct.lin_max().exprs().empty()) {
1359 std::vector<LinearExpression> negated_exprs;
1360 negated_exprs.reserve(
ct.lin_max().exprs_size());
1361 for (
int i = 0; i <
ct.lin_max().exprs_size(); ++i) {
1362 negated_exprs.push_back(
1363 NegationOf(mapping->GetExprFromProto(
ct.lin_max().exprs(i))));
1372 const int num_intervals =
ct.no_overlap().intervals_size();
1373 if (num_intervals <=
1374 params->max_size_to_create_precedence_literals_in_disjunctive() &&
1375 params->use_strong_propagation_in_disjunctive()) {
1377 mapping->Intervals(
ct.no_overlap().intervals()), m);
1384 if (
ct.no_overlap_2d().x_intervals().empty())
return;
1386 const std::vector<IntervalVariable> x_intervals =
1387 mapping->
Intervals(
ct.no_overlap_2d().x_intervals());
1388 const std::vector<IntervalVariable> y_intervals =
1389 mapping->Intervals(
ct.no_overlap_2d().y_intervals());
1391 x_intervals, y_intervals,
1392 !
ct.no_overlap_2d().boxes_with_null_area_can_overlap()));
1397 const std::vector<IntervalVariable> intervals =
1400 const std::vector<AffineExpression> demands =
1401 mapping->Affines(
ct.cumulative().demands());
1408 const std::vector<AffineExpression> times =
1409 mapping->Affines(
ct.reservoir().time_exprs());
1410 const std::vector<AffineExpression> level_changes =
1411 mapping->Affines(
ct.reservoir().level_changes());
1412 std::vector<Literal> presences;
1413 const int size =
ct.reservoir().time_exprs().size();
1414 for (
int i = 0; i < size; ++i) {
1415 if (!
ct.reservoir().active_literals().empty()) {
1416 presences.push_back(mapping->Literal(
ct.reservoir().active_literals(i)));
1418 presences.push_back(encoder->GetTrueLiteral());
1422 ct.reservoir().min_level(),
ct.reservoir().max_level(),
1427 const auto& circuit =
ct.circuit();
1428 if (circuit.tails().empty())
return;
1430 std::vector<int> tails(circuit.tails().begin(), circuit.tails().end());
1431 std::vector<int> heads(circuit.heads().begin(), circuit.heads().end());
1432 std::vector<Literal> literals =
1434 const int num_nodes =
ReindexArcs(&tails, &heads);
1439 const auto& routes =
ct.routes();
1440 if (routes.tails().empty())
return;
1442 std::vector<int> tails(routes.tails().begin(), routes.tails().end());
1443 std::vector<int> heads(routes.heads().begin(), routes.heads().end());
1444 std::vector<Literal> literals =
1446 const int num_nodes =
ReindexArcs(&tails, &heads);
1452 switch (
ct.constraint_case()) {
1453 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1455 case ConstraintProto::ConstraintCase::kBoolOr:
1458 case ConstraintProto::ConstraintCase::kBoolAnd:
1461 case ConstraintProto::ConstraintCase::kAtMostOne:
1464 case ConstraintProto::ConstraintCase::kExactlyOne:
1467 case ConstraintProto::ConstraintCase::kBoolXor:
1470 case ConstraintProto::ConstraintProto::kLinear:
1473 case ConstraintProto::ConstraintProto::kAllDiff:
1476 case ConstraintProto::ConstraintProto::kIntProd:
1479 case ConstraintProto::ConstraintProto::kIntDiv:
1482 case ConstraintProto::ConstraintProto::kIntMod:
1485 case ConstraintProto::ConstraintProto::kLinMax:
1488 case ConstraintProto::ConstraintProto::kInterval:
1491 case ConstraintProto::ConstraintProto::kNoOverlap:
1494 case ConstraintProto::ConstraintProto::kNoOverlap2D:
1497 case ConstraintProto::ConstraintProto::kCumulative:
1500 case ConstraintProto::ConstraintProto::kReservoir:
1503 case ConstraintProto::ConstraintProto::kCircuit:
1506 case ConstraintProto::ConstraintProto::kRoutes:
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Complement() const
Returns the set Int64 ∖ D.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
int NumIntervals() const
Basic read-only std::vector<> wrapping to view a Domain as a sorted list of non-adjacent intervals.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
std::vector< sat::Literal > Literals(const ProtoIndices &indices) const
std::vector< AffineExpression > Affines(const List &list) const
std::vector< IntervalVariable > Intervals(const ProtoIndices &indices) const
void ReserveSpaceForNumVariables(int num_vars)
Literal(int signed_value)
LiteralIndex Index() const
Class that owns everything related to a particular optimization model.
T Add(std::function< T(Model *)> f)
This makes it possible to have a nicer API on the client side, and it allows both of these forms:
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
void ProcessConditionalZero(Literal l, IntegerVariable p)
bool AddProblemClause(absl::Span< const Literal > literals, bool is_safe=true)
CpModelProto const * model_proto
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
IntegerValue FloorRatio(IntegerValue dividend, IntegerValue positive_divisor)
std::function< void(Model *)> NonOverlappingRectangles(const std::vector< IntervalVariable > &x, const std::vector< IntervalVariable > &y, bool is_strict)
void LoadExactlyOneConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> ClauseConstraint(absl::Span< const Literal > literals)
void LoadVariables(const CpModelProto &model_proto, bool view_all_booleans_as_integers, Model *m)
std::function< void(Model *)> ExactlyOneConstraint(const std::vector< Literal > &literals)
void LoadIntProdConstraint(const ConstraintProto &ct, Model *m)
bool LoadConstraint(const ConstraintProto &ct, Model *m)
std::vector< int > UsedVariables(const ConstraintProto &ct)
void LoadBoolOrConstraint(const ConstraintProto &ct, Model *m)
bool RefIsPositive(int ref)
std::function< void(Model *)> ConditionalWeightedSumLowerOrEqual(const std::vector< Literal > &enforcement_literals, const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
void ExtractElementEncoding(const CpModelProto &model_proto, Model *m)
void AddDisjunctiveWithBooleanPrecedences(const std::vector< IntervalVariable > &intervals, Model *model)
const LiteralIndex kNoLiteralIndex(-1)
std::function< void(Model *)> Disjunctive(const std::vector< IntervalVariable > &intervals)
std::function< void(Model *)> LiteralXorIs(const std::vector< Literal > &literals, bool value)
std::function< BooleanVariable(Model *)> NewBooleanVariable()
std::function< void(Model *)> SubcircuitConstraint(int num_nodes, const std::vector< int > &tails, const std::vector< int > &heads, const std::vector< Literal > &literals, bool multiple_subcircuit_through_zero)
bool HasEnforcementLiteral(const ConstraintProto &ct)
void LoadBooleanSymmetries(const CpModelProto &model_proto, Model *m)
void LoadCumulativeConstraint(const ConstraintProto &ct, Model *m)
void LoadRoutesConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> ConditionalWeightedSumGreaterOrEqual(const std::vector< Literal > &enforcement_literals, const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t lower_bound)
void LoadReservoirConstraint(const ConstraintProto &ct, Model *m)
void LoadBoolAndConstraint(const ConstraintProto &ct, Model *m)
void LoadLinMaxConstraint(const ConstraintProto &ct, Model *m)
void LoadBoolXorConstraint(const ConstraintProto &ct, Model *m)
void LoadIntModConstraint(const ConstraintProto &ct, Model *m)
const IntegerVariable kNoIntegerVariable(-1)
const IntervalVariable kNoIntervalVariable(-1)
std::function< void(Model *)> Cumulative(const std::vector< IntervalVariable > &vars, const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper)
std::function< void(Model *)> EnforcedClause(absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > clause)
void AddReservoirConstraint(std::vector< AffineExpression > times, std::vector< AffineExpression > deltas, std::vector< Literal > presences, int64_t min_level, int64_t max_level, Model *model)
void LoadIntDivConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> ProductConstraint(AffineExpression a, AffineExpression b, AffineExpression p)
void LoadLinearConstraint(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 *)> Implication(const std::vector< Literal > &enforcement_literals, IntegerLiteral i)
bool DetectLinearEncodingOfProducts(const AffineExpression &left, const AffineExpression &right, Model *model, LinearConstraintBuilder *builder)
std::function< void(Model *)> BooleanLinearConstraint(int64_t lower_bound, int64_t upper_bound, std::vector< LiteralWithCoeff > *cst)
int ReindexArcs(IntContainer *tails, IntContainer *heads, absl::flat_hash_map< int, int > *mapping_output=nullptr)
std::function< void(Model *)> WeightedSumLowerOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
void LoadAtMostOneConstraint(const ConstraintProto &ct, Model *m)
void LoadCircuitConstraint(const ConstraintProto &ct, Model *m)
void LoadNoOverlapConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> DivisionConstraint(AffineExpression num, AffineExpression denom, AffineExpression div)
void DetectOptionalVariables(const CpModelProto &model_proto, Model *m)
void LoadAllDiffConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> FixedDivisionConstraint(AffineExpression a, IntegerValue b, AffineExpression c)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
std::function< void(Model *)> IsEqualToMinOf(IntegerVariable min_var, const std::vector< IntegerVariable > &vars)
void LoadNoOverlap2dConstraint(const ConstraintProto &ct, Model *m)
std::function< void(Model *)> AtMostOneConstraint(const std::vector< Literal > &literals)
IndexReferences GetReferencesUsedByConstraint(const ConstraintProto &ct)
std::function< void(Model *)> FixedModuloConstraint(AffineExpression a, IntegerValue b, AffineExpression c)
void AddFullEncodingFromSearchBranching(const CpModelProto &model_proto, Model *m)
void ExtractEncoding(const CpModelProto &model_proto, Model *m)
const BooleanVariable kNoBooleanVariable(-1)
void PropagateEncodingFromEquivalenceRelations(const CpModelProto &model_proto, Model *m)
std::function< std::vector< ValueLiteralPair >Model *)> FullyEncodeVariable(IntegerVariable var)
std::function< void(Model *)> AllDifferentOnBounds(const std::vector< AffineExpression > &expressions)
std::function< void(Model *)> WeightedSumGreaterOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t lower_bound)
Collection of objects used to extend the Constraint Solver library.
std::vector< int > variables
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
#define SOLVER_LOG(logger,...)
#define VLOG(verboselevel)
#define VLOG_IS_ON(verboselevel)