22 #include "absl/container/flat_hash_map.h" 38 void AddXEqualYOrXEqualZero(
int x_eq_y,
int x,
int y,
40 ConstraintProto* equality =
context->working_model->add_constraints();
41 equality->add_enforcement_literal(x_eq_y);
42 equality->mutable_linear()->add_vars(x);
43 equality->mutable_linear()->add_coeffs(1);
44 equality->mutable_linear()->add_vars(y);
45 equality->mutable_linear()->add_coeffs(-1);
46 equality->mutable_linear()->add_domain(0);
47 equality->mutable_linear()->add_domain(0);
51 void ExpandReservoir(ConstraintProto*
ct, PresolveContext*
context) {
52 if (
ct->reservoir().min_level() >
ct->reservoir().max_level()) {
53 VLOG(1) <<
"Empty level domain in reservoir constraint.";
54 return (
void)
context->NotifyThatModelIsUnsat();
57 const ReservoirConstraintProto& reservoir =
ct->reservoir();
58 const int num_events = reservoir.times_size();
60 const int true_literal =
context->GetOrCreateConstantVar(1);
62 const auto is_active_literal = [&reservoir, true_literal](
int index) {
63 if (reservoir.actives_size() == 0)
return true_literal;
64 return reservoir.actives(
index);
67 int num_positives = 0;
68 int num_negatives = 0;
69 for (
const int64_t
demand : reservoir.demands()) {
77 if (num_positives > 0 && num_negatives > 0) {
79 for (
int i = 0; i < num_events - 1; ++i) {
80 const int active_i = is_active_literal(i);
81 if (
context->LiteralIsFalse(active_i))
continue;
82 const int time_i = reservoir.times(i);
84 for (
int j = i + 1; j < num_events; ++j) {
85 const int active_j = is_active_literal(j);
86 if (
context->LiteralIsFalse(active_j))
continue;
87 const int time_j = reservoir.times(j);
89 const int i_lesseq_j =
context->GetOrCreateReifiedPrecedenceLiteral(
90 time_i, time_j, active_i, active_j);
91 context->working_model->mutable_variables(i_lesseq_j)
92 ->set_name(absl::StrCat(i,
" before ", j));
93 const int j_lesseq_i =
context->GetOrCreateReifiedPrecedenceLiteral(
94 time_j, time_i, active_j, active_i);
95 context->working_model->mutable_variables(j_lesseq_i)
96 ->set_name(absl::StrCat(j,
" before ", i));
104 for (
int i = 0; i < num_events; ++i) {
105 const int active_i = is_active_literal(i);
106 if (
context->LiteralIsFalse(active_i))
continue;
107 const int time_i = reservoir.times(i);
110 ConstraintProto*
const level =
context->working_model->add_constraints();
111 level->add_enforcement_literal(active_i);
114 for (
int j = 0; j < num_events; ++j) {
115 if (i == j)
continue;
116 const int active_j = is_active_literal(j);
117 if (
context->LiteralIsFalse(active_j))
continue;
119 const int time_j = reservoir.times(j);
120 level->mutable_linear()->add_vars(
121 context->GetOrCreateReifiedPrecedenceLiteral(time_j, time_i,
122 active_j, active_i));
123 level->mutable_linear()->add_coeffs(reservoir.demands(j));
127 const int64_t demand_i = reservoir.demands(i);
128 level->mutable_linear()->add_domain(
129 CapSub(reservoir.min_level(), demand_i));
130 level->mutable_linear()->add_domain(
131 CapSub(reservoir.max_level(), demand_i));
137 context->working_model->add_constraints()->mutable_linear();
138 for (
int i = 0; i < num_events; ++i) {
139 sum->add_vars(is_active_literal(i));
140 sum->add_coeffs(reservoir.demands(i));
142 sum->add_domain(reservoir.min_level());
143 sum->add_domain(reservoir.max_level());
147 context->UpdateRuleStats(
"reservoir: expanded");
151 void ExpandIntDivWithOneAcrossZero(
int a_ref,
int b_ref,
int div_ref,
158 const int a_is_positive =
context->NewBoolVar();
159 context->AddImplyInDomain(a_is_positive, a_ref,
163 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
164 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
166 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
170 const int pos_a_div =
context->NewIntVar({0,
context->MaxOf(div_ref)});
171 IntegerArgumentProto* pos_div =
172 context->working_model->add_constraints()->mutable_int_div();
173 pos_div->set_target(pos_a_div);
174 pos_div->add_vars(pos_a_ref);
175 pos_div->add_vars(b_ref);
178 const int neg_a_div =
context->NewIntVar({
context->MinOf(div_ref), 0});
179 IntegerArgumentProto* neg_div =
180 context->working_model->add_constraints()->mutable_int_div();
183 neg_div->add_vars(b_ref);
186 LinearConstraintProto* lin =
187 context->working_model->add_constraints()->mutable_linear();
188 lin->add_vars(div_ref);
190 lin->add_vars(pos_a_div);
192 lin->add_vars(neg_a_div);
200 void ExpandIntDiv(ConstraintProto*
ct, PresolveContext*
context) {
201 const int numerator =
ct->int_div().vars(0);
202 const int divisor =
ct->int_div().vars(1);
203 const int target =
ct->int_div().target();
204 if (!
context->IntersectDomainWith(divisor, Domain(0).Complement())) {
205 return (
void)
context->NotifyThatModelIsUnsat();
211 if (
context->MinOf(numerator) < 0 &&
context->MaxOf(numerator) > 0) {
212 if (
context->MinOf(divisor) > 0) {
213 ExpandIntDivWithOneAcrossZero(numerator, divisor, target,
context);
220 "int_div: expanded division with numerator spanning across zero");
225 if (
context->MinOf(divisor) < 0) {
227 ct->mutable_int_div()->set_vars(1,
NegatedRef(divisor));
228 context->UpdateRuleStats(
"int_div: inverse the sign of the divisor");
234 if (
context->MinOf(numerator) < 0) {
236 ct->mutable_int_div()->set_vars(0,
NegatedRef(numerator));
237 context->UpdateRuleStats(
"int_div: inverse the sign of the numerator");
241 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
242 const IntegerArgumentProto& int_mod =
ct->int_mod();
243 const int var = int_mod.vars(0);
244 const int mod_var = int_mod.vars(1);
245 const int target_var = int_mod.target();
248 if (!
context->IntersectDomainWith(
249 target_var,
context->DomainOf(
var).PositiveModuloBySuperset(
250 context->DomainOf(mod_var)))) {
255 auto new_enforced_constraint = [&]() {
256 ConstraintProto* new_ct =
context->working_model->add_constraints();
257 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
265 IntegerArgumentProto*
const div_proto =
266 new_enforced_constraint()->mutable_int_div();
267 div_proto->set_target(div_var);
268 div_proto->add_vars(
var);
269 div_proto->add_vars(mod_var);
271 if (
context->IsFixed(mod_var)) {
273 LinearConstraintProto*
const lin =
274 new_enforced_constraint()->mutable_linear();
275 lin->add_vars(int_mod.vars(0));
277 lin->add_vars(div_var);
278 lin->add_coeffs(-
context->MinOf(mod_var));
279 lin->add_vars(target_var);
285 const Domain prod_domain =
287 .ContinuousMultiplicationBy(
context->DomainOf(mod_var))
288 .IntersectionWith(
context->DomainOf(
var).AdditionWith(
289 context->DomainOf(target_var).Negation()));
290 const int prod_var =
context->NewIntVar(prod_domain);
291 IntegerArgumentProto*
const int_prod =
292 new_enforced_constraint()->mutable_int_prod();
293 int_prod->set_target(prod_var);
294 int_prod->add_vars(div_var);
295 int_prod->add_vars(mod_var);
298 LinearConstraintProto*
const lin =
299 new_enforced_constraint()->mutable_linear();
302 lin->add_vars(prod_var);
304 lin->add_vars(target_var);
311 context->UpdateRuleStats(
"int_mod: expanded");
314 void ExpandIntProdWithBoolean(
int bool_ref,
int int_ref,
int product_ref,
316 ConstraintProto*
const one =
context->working_model->add_constraints();
317 one->add_enforcement_literal(bool_ref);
318 one->mutable_linear()->add_vars(int_ref);
319 one->mutable_linear()->add_coeffs(1);
320 one->mutable_linear()->add_vars(product_ref);
321 one->mutable_linear()->add_coeffs(-1);
322 one->mutable_linear()->add_domain(0);
323 one->mutable_linear()->add_domain(0);
325 ConstraintProto*
const zero =
context->working_model->add_constraints();
326 zero->add_enforcement_literal(
NegatedRef(bool_ref));
327 zero->mutable_linear()->add_vars(product_ref);
328 zero->mutable_linear()->add_coeffs(1);
329 zero->mutable_linear()->add_domain(0);
330 zero->mutable_linear()->add_domain(0);
334 void ExpandIntProdWithOneAcrossZero(
int a_ref,
int b_ref,
int product_ref,
341 const int a_is_positive =
context->NewBoolVar();
342 context->AddImplyInDomain(a_is_positive, a_ref,
346 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
347 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
349 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
353 const bool b_is_positive =
context->MinOf(b_ref) >= 0;
354 const Domain pos_a_product_domain =
355 b_is_positive ? Domain({0,
context->MaxOf(product_ref)})
356 : Domain({
context->MinOf(product_ref), 0});
357 const int pos_a_product =
context->NewIntVar(pos_a_product_domain);
358 IntegerArgumentProto* pos_product =
359 context->working_model->add_constraints()->mutable_int_prod();
360 pos_product->set_target(pos_a_product);
361 pos_product->add_vars(pos_a_ref);
362 pos_product->add_vars(b_ref);
365 const Domain neg_a_product_domain =
366 b_is_positive ? Domain({
context->MinOf(product_ref), 0})
367 : Domain({0,
context->MaxOf(product_ref)});
368 const int neg_a_product =
context->NewIntVar(neg_a_product_domain);
369 IntegerArgumentProto* neg_product =
370 context->working_model->add_constraints()->mutable_int_prod();
371 neg_product->set_target(neg_a_product);
372 neg_product->add_vars(neg_a_ref);
373 neg_product->add_vars(b_ref);
376 LinearConstraintProto* lin =
377 context->working_model->add_constraints()->mutable_linear();
378 lin->add_vars(product_ref);
380 lin->add_vars(pos_a_product);
382 lin->add_vars(neg_a_product);
388 void ExpandPositiveIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
391 const int terms_are_positive =
context->NewBoolVar();
393 const Domain product_domain =
context->DomainOf(product_ref);
395 const int64_t max_of_a =
context->MaxOf(a_ref);
396 const int64_t max_of_b =
context->MaxOf(b_ref);
397 const Domain positive_vars_domain =
398 Domain(0,
CapProd(max_of_a, max_of_b)).IntersectionWith(product_domain);
400 if (!positive_vars_domain.IsEmpty()) {
401 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
402 AddXEqualYOrXEqualZero(terms_are_positive, pos_a_ref, a_ref,
context);
403 const int pos_b_ref =
context->NewIntVar({0, max_of_b});
404 AddXEqualYOrXEqualZero(terms_are_positive, pos_b_ref, b_ref,
context);
407 both_positive_product_ref =
408 context->NewIntVar(positive_vars_domain.UnionWith(Domain(0)));
409 IntegerArgumentProto* pos_product =
410 context->working_model->add_constraints()->mutable_int_prod();
411 pos_product->set_target(both_positive_product_ref);
412 pos_product->add_vars(pos_a_ref);
413 pos_product->add_vars(pos_b_ref);
416 const int64_t min_of_a =
context->MinOf(a_ref);
417 const int64_t min_of_b =
context->MinOf(b_ref);
418 const Domain negative_vars_domain =
419 Domain(0,
CapProd(min_of_a, min_of_b)).IntersectionWith(product_domain);
421 if (!negative_vars_domain.IsEmpty()) {
422 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
423 AddXEqualYOrXEqualZero(
NegatedRef(terms_are_positive), neg_a_ref, a_ref,
425 const int neg_b_ref =
context->NewIntVar({min_of_b, 0});
426 AddXEqualYOrXEqualZero(
NegatedRef(terms_are_positive), neg_b_ref, b_ref,
429 both_negative_product_ref =
430 context->NewIntVar(negative_vars_domain.UnionWith(Domain(0)));
431 IntegerArgumentProto* neg_product =
432 context->working_model->add_constraints()->mutable_int_prod();
433 neg_product->set_target(both_negative_product_ref);
434 neg_product->add_vars(neg_a_ref);
435 neg_product->add_vars(neg_b_ref);
439 LinearConstraintProto* lin =
440 context->working_model->add_constraints()->mutable_linear();
441 lin->add_vars(product_ref);
444 lin->add_vars(both_positive_product_ref);
448 lin->add_vars(both_negative_product_ref);
455 void ExpandIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
int product_ref,
457 if (
context->MinOf(product_ref) >= 0) {
458 ExpandPositiveIntProdWithTwoAcrossZero(a_ref, b_ref, product_ref,
context);
460 }
else if (
context->MaxOf(product_ref) <= 0) {
461 ExpandPositiveIntProdWithTwoAcrossZero(a_ref,
NegatedRef(b_ref),
466 const int a_is_positive =
context->NewBoolVar();
467 context->AddImplyInDomain(a_is_positive, a_ref,
471 const int64_t min_of_a =
context->MinOf(a_ref);
472 const int64_t max_of_a =
context->MaxOf(a_ref);
474 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
475 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
477 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
481 const int pos_product_ref =
483 ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref,
context);
484 const int neg_product_ref =
486 ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref,
context);
489 LinearConstraintProto* lin =
490 context->working_model->add_constraints()->mutable_linear();
491 lin->add_vars(product_ref);
493 lin->add_vars(pos_product_ref);
495 lin->add_vars(neg_product_ref);
501 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
502 const IntegerArgumentProto& int_prod =
ct->int_prod();
503 if (int_prod.vars_size() != 2)
return;
504 const int a = int_prod.vars(0);
505 const int b = int_prod.vars(1);
506 const int p = int_prod.target();
507 const bool a_is_boolean =
509 const bool b_is_boolean =
514 if (a_is_boolean && !b_is_boolean) {
515 ExpandIntProdWithBoolean(
a,
b, p,
context);
517 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
520 if (b_is_boolean && !a_is_boolean) {
521 ExpandIntProdWithBoolean(
b,
a, p,
context);
523 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
527 const bool a_span_across_zero =
529 const bool b_span_across_zero =
531 if (a_span_across_zero && !b_span_across_zero) {
532 ExpandIntProdWithOneAcrossZero(
a,
b, p,
context);
535 "int_prod: expanded product with general integer variables");
538 if (!a_span_across_zero && b_span_across_zero) {
539 ExpandIntProdWithOneAcrossZero(
b,
a, p,
context);
542 "int_prod: expanded product with general integer variables");
545 if (a_span_across_zero && b_span_across_zero) {
546 ExpandIntProdWithTwoAcrossZero(
a,
b, p,
context);
549 "int_prod: expanded product with general integer variables");
554 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
555 const int size =
ct->inverse().f_direct().size();
556 CHECK_EQ(size,
ct->inverse().f_inverse().size());
562 for (
const int ref :
ct->inverse().f_direct()) {
563 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
564 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
568 for (
const int ref :
ct->inverse().f_inverse()) {
569 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
570 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
577 std::vector<int64_t> possible_values;
581 const auto filter_inverse_domain = [
context, size, &possible_values](
583 const auto& inverse) {
585 for (
int i = 0; i < size; ++i) {
586 possible_values.clear();
587 const Domain domain =
context->DomainOf(direct[i]);
588 bool removed_value =
false;
589 for (
const int64_t j : domain.Values()) {
590 if (
context->DomainOf(inverse[j]).Contains(i)) {
591 possible_values.push_back(j);
593 removed_value =
true;
597 if (!
context->IntersectDomainWith(
599 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
607 if (!filter_inverse_domain(
ct->inverse().f_direct(),
608 ct->inverse().f_inverse())) {
612 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
613 ct->inverse().f_direct())) {
619 for (
int i = 0; i < size; ++i) {
620 const int f_i =
ct->inverse().f_direct(i);
621 for (
const int64_t j :
context->DomainOf(f_i).Values()) {
623 const int r_j =
ct->inverse().f_inverse(j);
625 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
626 context->InsertVarValueEncoding(r_j_i, f_i, j);
628 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
629 context->InsertVarValueEncoding(f_i_j, r_j, i);
635 context->UpdateRuleStats(
"inverse: expanded");
639 void ExpandElementWithTargetEqualIndex(ConstraintProto*
ct,
641 const ElementConstraintProto& element =
ct->element();
642 DCHECK_EQ(element.index(), element.target());
644 const int index_ref = element.index();
645 std::vector<int64_t> valid_indices;
646 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
647 if (!
context->DomainContains(element.vars(v), v))
continue;
648 valid_indices.push_back(v);
650 if (valid_indices.size() <
context->DomainOf(index_ref).Size()) {
651 if (!
context->IntersectDomainWith(index_ref,
653 VLOG(1) <<
"No compatible variable domains in " 654 "ExpandElementWithTargetEqualIndex()";
657 context->UpdateRuleStats(
"element: reduced index domain");
660 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
661 const int var = element.vars(v);
664 context->GetOrCreateVarValueEncoding(index_ref, v),
var, Domain(v));
667 "element: expanded with special case target = index");
672 void ExpandConstantArrayElement(ConstraintProto*
ct, PresolveContext*
context) {
673 const ElementConstraintProto& element =
ct->element();
674 const int index_ref = element.index();
675 const int target_ref = element.target();
678 const Domain index_domain =
context->DomainOf(index_ref);
679 const Domain target_domain =
context->DomainOf(target_ref);
686 absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
688 absl::flat_hash_map<int64_t, int> constant_var_values_usage;
689 for (
const int64_t v : index_domain.Values()) {
692 if (++constant_var_values_usage[
value] == 2) {
694 BoolArgumentProto*
const support =
695 context->working_model->add_constraints()->mutable_bool_or();
696 const int target_literal =
697 context->GetOrCreateVarValueEncoding(target_ref,
value);
698 support->add_literals(
NegatedRef(target_literal));
699 supports[
value] = support;
708 context->working_model->add_constraints()->mutable_exactly_one();
709 for (
const int64_t v : index_domain.Values()) {
710 const int index_literal =
711 context->GetOrCreateVarValueEncoding(index_ref, v);
712 exactly_one->add_literals(index_literal);
715 const auto& it = supports.find(
value);
716 if (it != supports.end()) {
719 const int target_literal =
720 context->GetOrCreateVarValueEncoding(target_ref,
value);
721 context->AddImplication(index_literal, target_literal);
722 it->second->add_literals(index_literal);
725 context->InsertVarValueEncoding(index_literal, target_ref,
value);
730 context->UpdateRuleStats(
"element: expanded value element");
735 void ExpandVariableElement(ConstraintProto*
ct, PresolveContext*
context) {
736 const ElementConstraintProto& element =
ct->element();
737 const int index_ref = element.index();
738 const int target_ref = element.target();
739 const Domain index_domain =
context->DomainOf(index_ref);
741 BoolArgumentProto* bool_or =
742 context->working_model->add_constraints()->mutable_bool_or();
744 for (
const int64_t v : index_domain.Values()) {
745 const int var = element.vars(v);
746 const Domain var_domain =
context->DomainOf(
var);
747 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
748 bool_or->add_literals(index_lit);
750 if (var_domain.IsFixed()) {
751 context->AddImplyInDomain(index_lit, target_ref, var_domain);
753 ConstraintProto*
const ct =
context->working_model->add_constraints();
754 ct->add_enforcement_literal(index_lit);
755 ct->mutable_linear()->add_vars(
var);
756 ct->mutable_linear()->add_coeffs(1);
757 ct->mutable_linear()->add_vars(target_ref);
758 ct->mutable_linear()->add_coeffs(-1);
759 ct->mutable_linear()->add_domain(0);
760 ct->mutable_linear()->add_domain(0);
764 context->UpdateRuleStats(
"element: expanded");
768 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
769 const ElementConstraintProto& element =
ct->element();
771 const int index_ref = element.index();
772 const int target_ref = element.target();
773 const int size = element.vars_size();
777 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
778 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
783 if (index_ref == target_ref) {
784 ExpandElementWithTargetEqualIndex(
ct,
context);
789 bool all_constants =
true;
790 std::vector<int64_t> valid_indices;
791 const Domain index_domain =
context->DomainOf(index_ref);
792 const Domain target_domain =
context->DomainOf(target_ref);
793 Domain reached_domain;
794 for (
const int64_t v : index_domain.Values()) {
795 const Domain var_domain =
context->DomainOf(element.vars(v));
796 if (var_domain.IntersectionWith(target_domain).IsEmpty())
continue;
798 valid_indices.push_back(v);
799 reached_domain = reached_domain.UnionWith(var_domain);
800 if (var_domain.Min() != var_domain.Max()) {
801 all_constants =
false;
805 if (valid_indices.size() < index_domain.Size()) {
806 if (!
context->IntersectDomainWith(index_ref,
808 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
812 context->UpdateRuleStats(
"element: reduced index domain");
817 bool target_domain_changed =
false;
818 if (!
context->IntersectDomainWith(target_ref, reached_domain,
819 &target_domain_changed)) {
823 if (target_domain_changed) {
824 context->UpdateRuleStats(
"element: reduced target domain");
837 void LinkLiteralsAndValues(
838 const std::vector<int>& value_literals,
const std::vector<int64_t>& values,
839 const absl::flat_hash_map<int64_t, int>& target_encoding,
841 CHECK_EQ(value_literals.size(), values.size());
845 std::map<int, std::vector<int>> value_literals_per_target_literal;
850 for (
int i = 0; i < values.size(); ++i) {
851 const int64_t v = values[i];
852 CHECK(target_encoding.contains(v));
854 value_literals_per_target_literal[lit].push_back(value_literals[i]);
859 for (
const auto& it : value_literals_per_target_literal) {
860 const int target_literal = it.first;
861 switch (it.second.size()) {
863 if (!
context->SetLiteralToFalse(target_literal)) {
869 context->StoreBooleanEqualityRelation(target_literal,
874 BoolArgumentProto*
const bool_or =
875 context->working_model->add_constraints()->mutable_bool_or();
876 bool_or->add_literals(
NegatedRef(target_literal));
877 for (
const int value_literal : it.second) {
878 bool_or->add_literals(value_literal);
879 context->AddImplication(value_literal, target_literal);
886 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
887 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
889 if (
proto.vars_size() == 0) {
890 const int64_t initial_state =
proto.starting_state();
891 for (
const int64_t final_state :
proto.final_states()) {
892 if (initial_state == final_state) {
893 context->UpdateRuleStats(
"automaton: empty and trivially feasible");
898 return (
void)
context->NotifyThatModelIsUnsat(
899 "automaton: empty with an initial state not in the final states.");
900 }
else if (
proto.transition_label_size() == 0) {
901 return (
void)
context->NotifyThatModelIsUnsat(
902 "automaton: non-empty with no transition.");
905 const int n =
proto.vars_size();
906 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
909 const absl::flat_hash_set<int64_t> final_states(
910 {
proto.final_states().begin(),
proto.final_states().end()});
911 std::vector<absl::flat_hash_set<int64_t>> reachable_states(n + 1);
912 reachable_states[0].insert(
proto.starting_state());
916 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
917 const int64_t
tail =
proto.transition_tail(t);
918 const int64_t label =
proto.transition_label(t);
919 const int64_t
head =
proto.transition_head(t);
920 if (!reachable_states[
time].contains(
tail))
continue;
921 if (!
context->DomainContains(vars[
time], label))
continue;
922 if (
time == n - 1 && !final_states.contains(
head))
continue;
923 reachable_states[
time + 1].insert(
head);
929 absl::flat_hash_set<int64_t> new_set;
930 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
931 const int64_t
tail =
proto.transition_tail(t);
932 const int64_t label =
proto.transition_label(t);
933 const int64_t
head =
proto.transition_head(t);
935 if (!reachable_states[
time].contains(
tail))
continue;
936 if (!
context->DomainContains(vars[
time], label))
continue;
937 if (!reachable_states[
time + 1].contains(
head))
continue;
938 new_set.insert(
tail);
940 reachable_states[
time].
swap(new_set);
948 absl::flat_hash_map<int64_t, int> encoding;
949 absl::flat_hash_map<int64_t, int> in_encoding;
950 absl::flat_hash_map<int64_t, int> out_encoding;
951 bool removed_values =
false;
957 std::vector<int64_t> in_states;
958 std::vector<int64_t> transition_values;
959 std::vector<int64_t> out_states;
960 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
961 const int64_t
tail =
proto.transition_tail(i);
962 const int64_t label =
proto.transition_label(i);
963 const int64_t
head =
proto.transition_head(i);
965 if (!reachable_states[
time].contains(
tail))
continue;
966 if (!reachable_states[
time + 1].contains(
head))
continue;
967 if (!
context->DomainContains(vars[
time], label))
continue;
972 in_states.push_back(
tail);
973 transition_values.push_back(label);
977 out_states.push_back(
time + 1 == n ? 0 :
head);
980 std::vector<int> tuple_literals;
981 if (transition_values.size() == 1) {
982 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
984 Domain(transition_values.front()))) {
985 VLOG(1) <<
"Infeasible automaton.";
990 }
else if (transition_values.size() == 2) {
991 const int bool_var =
context->NewBoolVar();
992 tuple_literals.push_back(bool_var);
993 tuple_literals.push_back(
NegatedRef(bool_var));
998 LinearConstraintProto*
const exactly_one =
999 context->working_model->add_constraints()->mutable_linear();
1000 exactly_one->add_domain(1);
1001 exactly_one->add_domain(1);
1002 for (
int i = 0; i < transition_values.size(); ++i) {
1003 const int tuple_literal =
context->NewBoolVar();
1004 tuple_literals.push_back(tuple_literal);
1005 exactly_one->add_vars(tuple_literal);
1006 exactly_one->add_coeffs(1);
1012 std::vector<int64_t> s = transition_values;
1018 VLOG(1) <<
"Infeasible automaton.";
1023 for (
const int64_t v :
context->DomainOf(vars[
time]).Values()) {
1024 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
1030 std::vector<int64_t> s = out_states;
1033 out_encoding.clear();
1034 if (s.size() == 2) {
1036 out_encoding[s.front()] =
var;
1038 }
else if (s.size() > 2) {
1039 for (
const int64_t state : s) {
1040 out_encoding[state] =
context->NewBoolVar();
1045 if (!in_encoding.empty()) {
1046 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
1048 if (!encoding.empty()) {
1049 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
1052 if (!out_encoding.empty()) {
1053 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
1055 in_encoding.swap(out_encoding);
1056 out_encoding.clear();
1059 if (removed_values) {
1060 context->UpdateRuleStats(
"automaton: reduced variable domains");
1062 context->UpdateRuleStats(
"automaton: expanded");
1066 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
1067 TableConstraintProto& table = *
ct->mutable_table();
1068 const int num_vars = table.vars_size();
1069 const int num_original_tuples = table.values_size() / num_vars;
1070 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1072 for (
int i = 0; i < num_original_tuples; ++i) {
1073 for (
int j = 0; j < num_vars; ++j) {
1074 tuples[i].push_back(table.values(count++));
1078 if (tuples.empty()) {
1079 context->UpdateRuleStats(
"table: empty negated constraint");
1086 std::vector<int64_t> domain_sizes;
1087 for (
int i = 0; i < num_vars; ++i) {
1088 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
1093 std::vector<int> clause;
1094 for (
const std::vector<int64_t>& tuple : tuples) {
1096 for (
int i = 0; i < num_vars; ++i) {
1097 const int64_t
value = tuple[i];
1098 if (
value == any_value)
continue;
1101 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
1106 BoolArgumentProto* bool_or =
1107 context->working_model->add_constraints()->mutable_bool_or();
1108 for (
const int lit : clause) {
1109 bool_or->add_literals(lit);
1112 context->UpdateRuleStats(
"table: expanded negated constraint");
1116 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
1117 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
1118 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
1119 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
1124 lin_max->mutable_lin_max()->mutable_target());
1126 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
1127 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
1138 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
1139 const std::vector<int64_t>& values,
int variable,
1140 const std::vector<int>& tuples_with_any,
1142 VLOG(2) <<
"Process var(" << variable <<
") with domain " 1143 <<
context->DomainOf(variable) <<
" and " << values.size()
1144 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
1145 CHECK_EQ(tuple_literals.size(), values.size());
1146 std::vector<std::pair<int64_t, int>> pairs;
1149 for (
int i = 0; i < values.size(); ++i) {
1150 const int64_t
value = values[i];
1152 pairs.emplace_back(
value, tuple_literals[i]);
1157 std::vector<int> selected;
1158 std::sort(pairs.begin(), pairs.end());
1159 for (
int i = 0; i < pairs.size();) {
1161 const int64_t
value = pairs[i].first;
1162 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1163 selected.push_back(pairs[i].second);
1166 CHECK(!selected.empty() || !tuples_with_any.empty());
1167 if (selected.size() == 1 && tuples_with_any.empty()) {
1168 context->InsertVarValueEncoding(selected.front(), variable,
value);
1170 const int value_literal =
1172 BoolArgumentProto* no_support =
1173 context->working_model->add_constraints()->mutable_bool_or();
1174 for (
const int lit : selected) {
1175 no_support->add_literals(lit);
1176 context->AddImplication(lit, value_literal);
1178 for (
const int lit : tuples_with_any) {
1179 no_support->add_literals(lit);
1183 no_support->add_literals(
NegatedRef(value_literal));
1189 void AddSizeTwoTable(
1190 const std::vector<int>& vars,
1191 const std::vector<std::vector<int64_t>>& tuples,
1192 const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1195 const int left_var = vars[0];
1196 const int right_var = vars[1];
1197 if (
context->DomainOf(left_var).IsFixed() ||
1198 context->DomainOf(right_var).IsFixed()) {
1204 std::map<int, std::vector<int>> left_to_right;
1205 std::map<int, std::vector<int>> right_to_left;
1207 for (
const auto& tuple : tuples) {
1208 const int64_t left_value(tuple[0]);
1209 const int64_t right_value(tuple[1]);
1211 CHECK(
context->DomainContains(right_var, right_value));
1213 const int left_literal =
1214 context->GetOrCreateVarValueEncoding(left_var, left_value);
1215 const int right_literal =
1216 context->GetOrCreateVarValueEncoding(right_var, right_value);
1217 left_to_right[left_literal].push_back(right_literal);
1218 right_to_left[right_literal].push_back(left_literal);
1221 int num_implications = 0;
1222 int num_clause_added = 0;
1223 int num_large_clause_added = 0;
1224 auto add_support_constraint =
1225 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1226 int lit,
const std::vector<int>& support_literals,
1227 int max_support_size) {
1228 if (support_literals.size() == max_support_size)
return;
1229 if (support_literals.size() == 1) {
1230 context->AddImplication(lit, support_literals.front());
1233 BoolArgumentProto* bool_or =
1234 context->working_model->add_constraints()->mutable_bool_or();
1235 for (
const int support_literal : support_literals) {
1236 bool_or->add_literals(support_literal);
1240 if (support_literals.size() > max_support_size / 2) {
1241 num_large_clause_added++;
1246 for (
const auto& it : left_to_right) {
1247 add_support_constraint(it.first, it.second, values_per_var[1].size());
1249 for (
const auto& it : right_to_left) {
1250 add_support_constraint(it.first, it.second, values_per_var[0].size());
1252 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using " 1253 << num_clause_added <<
" clauses, including " 1254 << num_large_clause_added <<
" large clauses, " << num_implications
1258 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1259 const TableConstraintProto& table =
ct->table();
1260 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1261 const int num_vars = table.vars_size();
1262 const int num_original_tuples = table.values_size() / num_vars;
1265 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1267 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1268 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1269 tuples[tuple_index].push_back(table.values(count++));
1275 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1277 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1279 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1280 const int64_t
value = tuples[tuple_index][var_index];
1281 if (!
context->DomainContains(vars[var_index],
value)) {
1287 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1288 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1290 std::swap(tuples[tuple_index], tuples[new_size]);
1294 tuples.resize(new_size);
1295 const int num_valid_tuples = tuples.size();
1297 if (tuples.empty()) {
1298 context->UpdateRuleStats(
"table: empty");
1299 return (
void)
context->NotifyThatModelIsUnsat();
1305 int num_fixed_variables = 0;
1306 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1310 values_per_var[var_index].end()})));
1311 if (
context->DomainOf(vars[var_index]).IsFixed()) {
1312 num_fixed_variables++;
1316 if (num_fixed_variables == num_vars - 1) {
1317 context->UpdateRuleStats(
"table: one variable not fixed");
1320 }
else if (num_fixed_variables == num_vars) {
1321 context->UpdateRuleStats(
"table: all variables fixed");
1327 if (num_vars == 2) {
1328 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1330 "table: expanded positive constraint with two variables");
1337 int num_prefix_tuples = 0;
1339 absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
1340 for (
const std::vector<int64_t>& tuple : tuples) {
1341 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1343 num_prefix_tuples = prefixes.size();
1350 std::vector<int64_t> domain_sizes;
1351 for (
int i = 0; i < num_vars; ++i) {
1352 domain_sizes.push_back(values_per_var[i].size());
1354 const int num_tuples_before_compression = tuples.size();
1356 const int num_compressed_tuples = tuples.size();
1357 if (num_compressed_tuples < num_tuples_before_compression) {
1358 context->UpdateRuleStats(
"table: compress tuples");
1361 if (num_compressed_tuples == 1) {
1363 context->UpdateRuleStats(
"table: one tuple");
1369 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1370 if (prefixes_are_all_different) {
1372 "TODO table: last value implied by previous values");
1382 int64_t max_num_prefix_tuples = 1;
1383 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1384 max_num_prefix_tuples =
1385 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1389 absl::StrCat(
"Table: ", num_vars,
1390 " variables, original tuples = ", num_original_tuples);
1391 if (num_valid_tuples != num_original_tuples) {
1392 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1394 if (prefixes_are_all_different) {
1395 if (num_prefix_tuples < max_num_prefix_tuples) {
1396 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1397 max_num_prefix_tuples);
1399 absl::StrAppend(&
message,
", full prefix = true");
1402 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1404 if (num_compressed_tuples != num_valid_tuples) {
1406 ", compressed tuples = ", num_compressed_tuples);
1412 if (num_compressed_tuples == 2) {
1413 context->UpdateRuleStats(
"TODO table: two tuples");
1419 std::vector<int> tuple_literals(num_compressed_tuples);
1420 BoolArgumentProto* exactly_one =
1421 context->working_model->add_constraints()->mutable_exactly_one();
1423 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1424 tuple_literals[var_index] =
context->NewBoolVar();
1425 exactly_one->add_literals(tuple_literals[var_index]);
1428 std::vector<int> active_tuple_literals;
1429 std::vector<int64_t> active_values;
1430 std::vector<int> any_tuple_literals;
1431 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1432 if (values_per_var[var_index].size() == 1)
continue;
1434 active_tuple_literals.clear();
1435 active_values.clear();
1436 any_tuple_literals.clear();
1437 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1439 const int64_t
value = tuples[tuple_index][var_index];
1440 const int tuple_literal = tuple_literals[tuple_index];
1442 if (
value == any_value) {
1443 any_tuple_literals.push_back(tuple_literal);
1445 active_tuple_literals.push_back(tuple_literal);
1446 active_values.push_back(
value);
1450 if (!active_tuple_literals.empty()) {
1451 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1456 context->UpdateRuleStats(
"table: expanded positive constraint");
1460 void ExpandAllDiff(
bool expand_non_permutations, ConstraintProto*
ct,
1462 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1463 if (
proto.vars_size() <= 2)
return;
1465 const int num_vars =
proto.vars_size();
1467 Domain union_of_domains =
context->DomainOf(
proto.vars(0));
1468 for (
int i = 1; i < num_vars; ++i) {
1470 union_of_domains.UnionWith(
context->DomainOf(
proto.vars(i)));
1473 const bool is_a_permutation =
proto.vars_size() == union_of_domains.Size();
1474 const bool has_small_domains =
1475 (union_of_domains.Size() <= 2 *
proto.vars_size()) ||
1476 (union_of_domains.Size() <= 32);
1478 if (!is_a_permutation && !has_small_domains && !expand_non_permutations) {
1485 for (
const int64_t v : union_of_domains.Values()) {
1487 std::vector<int> possible_refs;
1488 int fixed_variable_count = 0;
1489 for (
const int ref :
proto.vars()) {
1490 if (!
context->DomainContains(ref, v))
continue;
1491 possible_refs.push_back(ref);
1492 if (
context->DomainOf(ref).IsFixed()) {
1493 fixed_variable_count++;
1497 if (fixed_variable_count > 1) {
1499 return (
void)
context->NotifyThatModelIsUnsat();
1500 }
else if (fixed_variable_count == 1) {
1502 for (
const int ref : possible_refs) {
1503 if (
context->DomainOf(ref).IsFixed())
continue;
1504 if (!
context->IntersectDomainWith(ref, Domain(v).Complement())) {
1505 VLOG(1) <<
"Empty domain for a variable in ExpandAllDiff()";
1511 LinearConstraintProto* at_most_or_equal_one =
1512 context->working_model->add_constraints()->mutable_linear();
1513 int lb = is_a_permutation ? 1 : 0;
1515 for (
const int ref : possible_refs) {
1518 const int encoding =
context->GetOrCreateVarValueEncoding(ref, v);
1520 at_most_or_equal_one->add_vars(encoding);
1521 at_most_or_equal_one->add_coeffs(1);
1523 at_most_or_equal_one->add_vars(
PositiveRef(encoding));
1524 at_most_or_equal_one->add_coeffs(-1);
1529 at_most_or_equal_one->add_domain(lb);
1530 at_most_or_equal_one->add_domain(ub);
1532 if (is_a_permutation) {
1533 context->UpdateRuleStats(
"alldiff: permutation expanded");
1535 context->UpdateRuleStats(
"alldiff: expanded");
1543 if (
context->params().disable_constraint_expansion())
return;
1544 if (
context->ModelIsUnsat())
return;
1548 if (
context->ModelIsExpanded())
return;
1551 context->InitializeNewDomains();
1554 context->ClearPrecedenceCache();
1556 for (
int i = 0; i <
context->working_model->constraints_size(); ++i) {
1559 switch (
ct->constraint_case()) {
1560 case ConstraintProto::ConstraintCase::kReservoir:
1561 if (
context->params().expand_reservoir_constraints()) {
1565 case ConstraintProto::ConstraintCase::kIntMod:
1568 case ConstraintProto::ConstraintCase::kIntDiv:
1571 case ConstraintProto::ConstraintCase::kIntProd:
1574 case ConstraintProto::ConstraintCase::kLinMin:
1577 case ConstraintProto::ConstraintCase::kElement:
1578 if (
context->params().expand_element_constraints()) {
1582 case ConstraintProto::ConstraintCase::kInverse:
1585 case ConstraintProto::ConstraintCase::kAutomaton:
1586 if (
context->params().expand_automaton_constraints()) {
1590 case ConstraintProto::ConstraintCase::kTable:
1591 if (
ct->table().negated()) {
1593 }
else if (
context->params().expand_table_constraints()) {
1597 case ConstraintProto::ConstraintCase::kAllDiff:
1598 ExpandAllDiff(
context->params().expand_alldiff_constraints(),
ct,
1608 context->UpdateNewConstraintsVariableUsage();
1610 context->UpdateConstraintVariableUsage(i);
1614 if (
context->ModelIsUnsat()) {
1624 context->ClearPrecedenceCache();
1627 context->InitializeNewDomains();
1630 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1632 context->working_model->mutable_variables(i));
1635 context->NotifyThatModelIsExpanded();
int64_t CapSub(int64_t x, int64_t y)
#define SOLVER_LOG(logger,...)
#define VLOG(verboselevel)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
int64_t CapProd(int64_t x, int64_t y)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
#define DCHECK_GT(val1, val2)
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
void ExpandCpModel(PresolveContext *context)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
#define CHECK_EQ(val1, val2)
friend void swap(CpModelProto &a, CpModelProto &b)
#define DCHECK(condition)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
#define DCHECK_EQ(val1, val2)
std::string ProtobufShortDebugString(const P &message)
Collection of objects used to extend the Constraint Solver library.
bool RefIsPositive(int ref)
void CompressTuples(absl::Span< const int64_t > domain_sizes, int64_t any_value, std::vector< std::vector< int64_t >> *tuples)
#define VLOG_IS_ON(verboselevel)
GurobiMPCallbackContext * context
#define DCHECK_LT(val1, val2)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)