18 #include "absl/container/flat_hash_map.h"
33 void ExpandReservoir(ConstraintProto*
ct, PresolveContext*
context) {
34 if (
ct->reservoir().min_level() >
ct->reservoir().max_level()) {
35 VLOG(1) <<
"Empty level domain in reservoir constraint.";
36 return (
void)
context->NotifyThatModelIsUnsat();
40 absl::flat_hash_map<std::tuple<int, int, int, int>,
int> precedence_cache;
41 const ReservoirConstraintProto& reservoir =
ct->reservoir();
42 const int num_events = reservoir.times_size();
44 const int true_literal =
context->GetOrCreateConstantVar(1);
46 const auto is_active_literal = [&reservoir, true_literal](
int index) {
47 if (reservoir.actives_size() == 0)
return true_literal;
48 return reservoir.actives(
index);
52 const auto add_reified_precedence = [&
context](
int x_lesseq_y,
int x,
int y,
55 ConstraintProto*
const lesseq =
context->working_model->add_constraints();
56 lesseq->add_enforcement_literal(x_lesseq_y);
57 lesseq->mutable_linear()->add_vars(x);
58 lesseq->mutable_linear()->add_vars(y);
59 lesseq->mutable_linear()->add_coeffs(-1);
60 lesseq->mutable_linear()->add_coeffs(1);
61 lesseq->mutable_linear()->add_domain(0);
62 lesseq->mutable_linear()->add_domain(
kint64max);
63 if (!
context->LiteralIsTrue(l_x)) {
64 context->AddImplication(x_lesseq_y, l_x);
66 if (!
context->LiteralIsTrue(l_y)) {
67 context->AddImplication(x_lesseq_y, l_y);
71 ConstraintProto*
const greater =
context->working_model->add_constraints();
72 greater->mutable_linear()->add_vars(x);
73 greater->mutable_linear()->add_vars(y);
74 greater->mutable_linear()->add_coeffs(-1);
75 greater->mutable_linear()->add_coeffs(1);
76 greater->mutable_linear()->add_domain(
kint64min);
77 greater->mutable_linear()->add_domain(-1);
80 greater->add_enforcement_literal(
NegatedRef(x_lesseq_y));
81 greater->add_enforcement_literal(l_x);
82 greater->add_enforcement_literal(l_y);
85 int num_positives = 0;
86 int num_negatives = 0;
95 if (num_positives > 0 && num_negatives > 0) {
97 for (
int i = 0; i < num_events - 1; ++i) {
98 const int active_i = is_active_literal(i);
99 if (
context->LiteralIsFalse(active_i))
continue;
101 const int time_i = reservoir.times(i);
102 for (
int j = i + 1; j < num_events; ++j) {
103 const int active_j = is_active_literal(j);
104 if (
context->LiteralIsFalse(active_j))
continue;
106 const int time_j = reservoir.times(j);
107 const std::tuple<int, int, int, int> p =
108 std::make_tuple(time_i, time_j, active_i, active_j);
109 const std::tuple<int, int, int, int> rev_p =
110 std::make_tuple(time_j, time_i, active_j, active_i);
113 const int i_lesseq_j =
context->NewBoolVar();
114 context->working_model->mutable_variables(i_lesseq_j)
115 ->set_name(absl::StrCat(i,
" before ", j));
116 precedence_cache[p] = i_lesseq_j;
117 const int j_lesseq_i =
context->NewBoolVar();
118 context->working_model->mutable_variables(j_lesseq_i)
119 ->set_name(absl::StrCat(j,
" before ", i));
121 precedence_cache[rev_p] = j_lesseq_i;
122 add_reified_precedence(i_lesseq_j, time_i, time_j, active_i, active_j);
123 add_reified_precedence(j_lesseq_i, time_j, time_i, active_j, active_i);
126 auto*
const bool_or =
127 context->working_model->add_constraints()->mutable_bool_or();
128 bool_or->add_literals(i_lesseq_j);
129 bool_or->add_literals(j_lesseq_i);
139 for (
int i = 0; i < num_events; ++i) {
140 const int active_i = is_active_literal(i);
141 if (
context->LiteralIsFalse(active_i))
continue;
142 const int time_i = reservoir.times(i);
145 ConstraintProto*
const level =
context->working_model->add_constraints();
146 level->add_enforcement_literal(active_i);
149 for (
int j = 0; j < num_events; ++j) {
150 if (i == j)
continue;
151 const int active_j = is_active_literal(j);
152 if (
context->LiteralIsFalse(active_j))
continue;
154 const int time_j = reservoir.times(j);
157 std::make_tuple(time_j, time_i, active_j, active_i)));
158 level->mutable_linear()->add_coeffs(reservoir.demands(j));
162 const int64 demand_i = reservoir.demands(i);
163 level->mutable_linear()->add_domain(
164 CapSub(reservoir.min_level(), demand_i));
165 level->mutable_linear()->add_domain(
166 CapSub(reservoir.max_level(), demand_i));
172 context->working_model->add_constraints()->mutable_linear();
173 for (
int i = 0; i < num_events; ++i) {
174 sum->add_vars(is_active_literal(i));
175 sum->add_coeffs(reservoir.demands(i));
177 sum->add_domain(reservoir.min_level());
178 sum->add_domain(reservoir.max_level());
184 if (reservoir.min_level() > 0 || reservoir.max_level() < 0) {
185 auto*
const sum_at_zero =
186 context->working_model->add_constraints()->mutable_linear();
187 for (
int i = 0; i < num_events; ++i) {
188 const int active_i = is_active_literal(i);
189 if (
context->LiteralIsFalse(active_i))
continue;
191 const int time_i = reservoir.times(i);
192 const int lesseq_0 =
context->NewBoolVar();
195 ConstraintProto*
const lesseq =
context->working_model->add_constraints();
196 lesseq->add_enforcement_literal(lesseq_0);
197 lesseq->mutable_linear()->add_vars(time_i);
198 lesseq->mutable_linear()->add_coeffs(1);
199 lesseq->mutable_linear()->add_domain(
kint64min);
200 lesseq->mutable_linear()->add_domain(0);
202 if (!
context->LiteralIsTrue(active_i)) {
203 context->AddImplication(lesseq_0, active_i);
207 ConstraintProto*
const greater =
208 context->working_model->add_constraints();
209 greater->add_enforcement_literal(
NegatedRef(lesseq_0));
210 greater->add_enforcement_literal(active_i);
211 greater->mutable_linear()->add_vars(time_i);
212 greater->mutable_linear()->add_coeffs(1);
213 greater->mutable_linear()->add_domain(1);
214 greater->mutable_linear()->add_domain(
kint64max);
217 sum_at_zero->add_vars(lesseq_0);
218 sum_at_zero->add_coeffs(reservoir.demands(i));
220 sum_at_zero->add_domain(reservoir.min_level());
221 sum_at_zero->add_domain(reservoir.max_level());
225 context->UpdateRuleStats(
"reservoir: expanded");
228 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
229 const IntegerArgumentProto& int_mod =
ct->int_mod();
230 const int var = int_mod.vars(0);
231 const int mod_var = int_mod.vars(1);
232 const int target_var = int_mod.target();
243 context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb));
245 auto add_enforcement_literal_if_needed = [&]() {
246 if (
ct->enforcement_literal_size() == 0)
return;
247 const int literal =
ct->enforcement_literal(0);
248 ConstraintProto*
const last =
context->working_model->mutable_constraints(
249 context->working_model->constraints_size() - 1);
250 last->add_enforcement_literal(
literal);
254 IntegerArgumentProto*
const div_proto =
255 context->working_model->add_constraints()->mutable_int_div();
256 div_proto->set_target(div_var);
257 div_proto->add_vars(
var);
258 div_proto->add_vars(mod_var);
259 add_enforcement_literal_if_needed();
262 if (mod_lb == mod_ub) {
264 LinearConstraintProto*
const lin =
265 context->working_model->add_constraints()->mutable_linear();
266 lin->add_vars(int_mod.vars(0));
268 lin->add_vars(div_var);
269 lin->add_coeffs(-mod_lb);
270 lin->add_vars(target_var);
274 add_enforcement_literal_if_needed();
277 const int prod_var =
context->NewIntVar(
278 Domain(var_lb * mod_lb / mod_ub, var_ub * mod_ub / mod_lb));
279 IntegerArgumentProto*
const int_prod =
280 context->working_model->add_constraints()->mutable_int_prod();
281 int_prod->set_target(prod_var);
282 int_prod->add_vars(div_var);
283 int_prod->add_vars(mod_var);
284 add_enforcement_literal_if_needed();
287 LinearConstraintProto*
const lin =
288 context->working_model->add_constraints()->mutable_linear();
291 lin->add_vars(prod_var);
293 lin->add_vars(target_var);
297 add_enforcement_literal_if_needed();
301 context->UpdateRuleStats(
"int_mod: expanded");
304 void ExpandIntProdWithBoolean(
int bool_ref,
int int_ref,
int product_ref,
306 ConstraintProto*
const one =
context->working_model->add_constraints();
307 one->add_enforcement_literal(bool_ref);
308 one->mutable_linear()->add_vars(int_ref);
309 one->mutable_linear()->add_coeffs(1);
310 one->mutable_linear()->add_vars(product_ref);
311 one->mutable_linear()->add_coeffs(-1);
312 one->mutable_linear()->add_domain(0);
313 one->mutable_linear()->add_domain(0);
315 ConstraintProto*
const zero =
context->working_model->add_constraints();
316 zero->add_enforcement_literal(
NegatedRef(bool_ref));
317 zero->mutable_linear()->add_vars(product_ref);
318 zero->mutable_linear()->add_coeffs(1);
319 zero->mutable_linear()->add_domain(0);
320 zero->mutable_linear()->add_domain(0);
323 void AddXEqualYOrXEqualZero(
int x_eq_y,
int x,
int y,
325 ConstraintProto* equality =
context->working_model->add_constraints();
326 equality->add_enforcement_literal(x_eq_y);
327 equality->mutable_linear()->add_vars(x);
328 equality->mutable_linear()->add_coeffs(1);
329 equality->mutable_linear()->add_vars(y);
330 equality->mutable_linear()->add_coeffs(-1);
331 equality->mutable_linear()->add_domain(0);
332 equality->mutable_linear()->add_domain(0);
337 void ExpandIntProdWithOneAcrossZero(
int a_ref,
int b_ref,
int product_ref,
344 const int a_is_positive =
context->NewBoolVar();
347 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
348 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
350 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
354 const bool b_is_positive =
context->MinOf(b_ref) >= 0;
355 const Domain pos_a_product_domain =
356 b_is_positive ? Domain({0,
context->MaxOf(product_ref)})
357 : Domain({
context->MinOf(product_ref), 0});
358 const int pos_a_product =
context->NewIntVar(pos_a_product_domain);
359 IntegerArgumentProto* pos_product =
360 context->working_model->add_constraints()->mutable_int_prod();
361 pos_product->set_target(pos_a_product);
362 pos_product->add_vars(pos_a_ref);
363 pos_product->add_vars(b_ref);
366 const Domain neg_a_product_domain =
367 b_is_positive ? Domain({
context->MinOf(product_ref), 0})
368 : Domain({0,
context->MaxOf(product_ref)});
369 const int neg_a_product =
context->NewIntVar(neg_a_product_domain);
370 IntegerArgumentProto* neg_product =
371 context->working_model->add_constraints()->mutable_int_prod();
372 neg_product->set_target(neg_a_product);
373 neg_product->add_vars(neg_a_ref);
374 neg_product->add_vars(b_ref);
377 LinearConstraintProto* lin =
378 context->working_model->add_constraints()->mutable_linear();
379 lin->add_vars(product_ref);
381 lin->add_vars(pos_a_product);
383 lin->add_vars(neg_a_product);
389 void ExpandIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
int product_ref,
392 const int a_is_positive =
context->NewBoolVar();
398 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
399 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
401 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
405 const int pos_product_ref =
407 ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref,
context);
408 const int neg_product_ref =
410 ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref,
context);
413 LinearConstraintProto* lin =
414 context->working_model->add_constraints()->mutable_linear();
415 lin->add_vars(product_ref);
417 lin->add_vars(pos_product_ref);
419 lin->add_vars(neg_product_ref);
425 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
426 const IntegerArgumentProto& int_prod =
ct->int_prod();
427 if (int_prod.vars_size() != 2)
return;
428 const int a = int_prod.vars(0);
429 const int b = int_prod.vars(1);
430 const int p = int_prod.target();
431 const bool a_is_boolean =
433 const bool b_is_boolean =
438 if (a_is_boolean && !b_is_boolean) {
439 ExpandIntProdWithBoolean(
a,
b, p,
context);
441 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
444 if (b_is_boolean && !a_is_boolean) {
445 ExpandIntProdWithBoolean(
b,
a, p,
context);
447 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
451 const bool a_span_across_zero =
453 const bool b_span_across_zero =
455 if (a_span_across_zero && !b_span_across_zero) {
456 ExpandIntProdWithOneAcrossZero(
a,
b, p,
context);
459 "int_prod: expanded product with general integer variables");
462 if (!a_span_across_zero && b_span_across_zero) {
463 ExpandIntProdWithOneAcrossZero(
b,
a, p,
context);
466 "int_prod: expanded product with general integer variables");
469 if (a_span_across_zero && b_span_across_zero) {
470 ExpandIntProdWithTwoAcrossZero(
a,
b, p,
context);
473 "int_prod: expanded product with general integer variables");
478 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
479 const int size =
ct->inverse().f_direct().size();
480 CHECK_EQ(size,
ct->inverse().f_inverse().size());
486 for (
const int ref :
ct->inverse().f_direct()) {
487 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
488 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
492 for (
const int ref :
ct->inverse().f_inverse()) {
493 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
494 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
501 std::vector<int64> possible_values;
505 const auto filter_inverse_domain = [
context, size, &possible_values](
507 const auto& inverse) {
509 for (
int i = 0; i < size; ++i) {
510 possible_values.clear();
511 const Domain domain =
context->DomainOf(direct[i]);
512 bool removed_value =
false;
513 for (
const ClosedInterval&
interval : domain) {
515 if (
context->DomainOf(inverse[j]).Contains(i)) {
516 possible_values.push_back(j);
518 removed_value =
true;
523 if (!
context->IntersectDomainWith(
525 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
533 if (!filter_inverse_domain(
ct->inverse().f_direct(),
534 ct->inverse().f_inverse())) {
538 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
539 ct->inverse().f_direct())) {
545 for (
int i = 0; i < size; ++i) {
546 const int f_i =
ct->inverse().f_direct(i);
547 const Domain domain =
context->DomainOf(f_i);
548 for (
const ClosedInterval&
interval : domain) {
551 const int r_j =
ct->inverse().f_inverse(j);
553 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
554 context->InsertVarValueEncoding(r_j_i, f_i, j);
556 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
557 context->InsertVarValueEncoding(f_i_j, r_j, i);
564 context->UpdateRuleStats(
"inverse: expanded");
567 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
568 const ElementConstraintProto& element =
ct->element();
569 const int index_ref = element.index();
570 const int target_ref = element.target();
571 const int size = element.vars_size();
573 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
574 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
575 return (
void)
context->NotifyThatModelIsUnsat();
578 bool all_constants =
true;
579 absl::flat_hash_map<int64, int> constant_var_values_usage;
580 std::vector<int64> constant_var_values;
581 std::vector<int64> invalid_indices;
582 Domain index_domain =
context->DomainOf(index_ref);
583 Domain target_domain =
context->DomainOf(target_ref);
584 for (
const ClosedInterval&
interval : index_domain) {
586 const int var = element.vars(v);
587 const Domain var_domain =
context->DomainOf(
var);
588 if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
589 invalid_indices.push_back(v);
592 if (var_domain.Min() != var_domain.Max()) {
593 all_constants =
false;
598 if (constant_var_values_usage[
value]++ == 0) {
599 constant_var_values.push_back(
value);
604 if (!invalid_indices.empty() && target_ref != index_ref) {
605 if (!
context->IntersectDomainWith(
607 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
608 return (
void)
context->NotifyThatModelIsUnsat();
612 index_domain =
context->DomainOf(index_ref);
620 absl::flat_hash_map<int64, BoolArgumentProto*> supports;
621 if (all_constants && target_ref != index_ref) {
622 if (!
context->IntersectDomainWith(
624 VLOG(1) <<
"Empty domain for the target variable in ExpandElement()";
628 target_domain =
context->DomainOf(target_ref);
629 if (target_domain.Size() == 1) {
630 context->UpdateRuleStats(
"element: one value array");
635 for (
const ClosedInterval&
interval : target_domain) {
639 const int lit =
context->GetOrCreateVarValueEncoding(target_ref, v);
640 BoolArgumentProto*
const support =
641 context->working_model->add_constraints()->mutable_bool_or();
642 supports[v] = support;
651 auto* bool_or =
context->working_model->add_constraints()->mutable_bool_or();
653 for (
const ClosedInterval&
interval : index_domain) {
655 const int var = element.vars(v);
656 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
657 const Domain var_domain =
context->DomainOf(
var);
659 bool_or->add_literals(index_lit);
661 if (target_ref == index_ref) {
664 context->AddImplyInDomain(index_lit,
var, Domain(v));
665 }
else if (target_domain.Size() == 1) {
668 context->AddImplyInDomain(index_lit,
var, target_domain);
669 }
else if (var_domain.Size() == 1) {
672 if (constant_var_values_usage[
value] > 1) {
675 const int target_lit =
676 context->GetOrCreateVarValueEncoding(target_ref,
value);
677 context->AddImplication(index_lit, target_lit);
681 context->InsertVarValueEncoding(index_lit, target_ref,
value);
684 context->AddImplyInDomain(index_lit, target_ref, var_domain);
687 ConstraintProto*
const ct =
context->working_model->add_constraints();
688 ct->add_enforcement_literal(index_lit);
689 ct->mutable_linear()->add_vars(
var);
690 ct->mutable_linear()->add_coeffs(1);
691 ct->mutable_linear()->add_vars(target_ref);
692 ct->mutable_linear()->add_coeffs(-1);
693 ct->mutable_linear()->add_domain(0);
694 ct->mutable_linear()->add_domain(0);
700 const int64 var_min = target_domain.Min();
705 for (
const auto it : constant_var_values_usage) {
706 if (it.second > usage ||
707 (it.second == usage && it.first < most_frequent_value)) {
709 most_frequent_value = it.first;
721 usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
722 if (base != var_min) {
723 VLOG(3) <<
"expand element: choose " << base <<
" with usage " << usage
724 <<
" over " << var_min <<
" among " << size <<
" values.";
727 LinearConstraintProto*
const linear =
728 context->working_model->add_constraints()->mutable_linear();
730 linear->add_vars(target_ref);
731 linear->add_coeffs(-1);
732 for (
const ClosedInterval&
interval : index_domain) {
734 const int ref = element.vars(v);
735 const int index_lit =
736 context->GetOrCreateVarValueEncoding(index_ref, v);
739 linear->add_vars(index_lit);
740 linear->add_coeffs(
delta);
743 linear->add_coeffs(-
delta);
748 linear->add_domain(rhs);
749 linear->add_domain(rhs);
751 context->UpdateRuleStats(
"element: expanded value element");
753 context->UpdateRuleStats(
"element: expanded");
760 void LinkLiteralsAndValues(
761 const std::vector<int>& value_literals,
const std::vector<int64>& values,
762 const absl::flat_hash_map<int64, int>& target_encoding,
764 CHECK_EQ(value_literals.size(), values.size());
768 std::map<int, std::vector<int>> value_literals_per_target_literal;
773 for (
int i = 0; i < values.size(); ++i) {
774 const int64 v = values[i];
775 CHECK(target_encoding.contains(v));
777 value_literals_per_target_literal[lit].push_back(value_literals[i]);
782 for (
const auto& it : value_literals_per_target_literal) {
783 const int target_literal = it.first;
784 switch (it.second.size()) {
786 if (!
context->SetLiteralToFalse(target_literal)) {
792 context->StoreBooleanEqualityRelation(target_literal,
797 BoolArgumentProto*
const bool_or =
798 context->working_model->add_constraints()->mutable_bool_or();
799 bool_or->add_literals(
NegatedRef(target_literal));
800 for (
const int value_literal : it.second) {
801 bool_or->add_literals(value_literal);
802 context->AddImplication(value_literal, target_literal);
809 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
810 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
812 if (
proto.vars_size() == 0) {
813 const int64 initial_state =
proto.starting_state();
814 for (
const int64 final_state :
proto.final_states()) {
815 if (initial_state == final_state) {
816 context->UpdateRuleStats(
"automaton: empty constraint");
822 return (
void)
context->NotifyThatModelIsUnsat();
823 }
else if (
proto.transition_label_size() == 0) {
825 return (
void)
context->NotifyThatModelIsUnsat();
828 const int n =
proto.vars_size();
829 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
832 const absl::flat_hash_set<int64> final_states(
833 {
proto.final_states().begin(),
proto.final_states().end()});
834 std::vector<absl::flat_hash_set<int64>> reachable_states(n + 1);
835 reachable_states[0].insert(
proto.starting_state());
839 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
841 const int64 label =
proto.transition_label(t);
843 if (!reachable_states[
time].contains(
tail))
continue;
844 if (!
context->DomainContains(vars[
time], label))
continue;
845 if (
time == n - 1 && !final_states.contains(
head))
continue;
846 reachable_states[
time + 1].insert(
head);
852 absl::flat_hash_set<int64> new_set;
853 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
855 const int64 label =
proto.transition_label(t);
858 if (!reachable_states[
time].contains(
tail))
continue;
859 if (!
context->DomainContains(vars[
time], label))
continue;
860 if (!reachable_states[
time + 1].contains(
head))
continue;
861 new_set.insert(
tail);
863 reachable_states[
time].swap(new_set);
871 absl::flat_hash_map<int64, int> encoding;
872 absl::flat_hash_map<int64, int> in_encoding;
873 absl::flat_hash_map<int64, int> out_encoding;
874 bool removed_values =
false;
880 std::vector<int64> in_states;
881 std::vector<int64> transition_values;
882 std::vector<int64> out_states;
883 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
885 const int64 label =
proto.transition_label(i);
888 if (!reachable_states[
time].contains(
tail))
continue;
889 if (!reachable_states[
time + 1].contains(
head))
continue;
890 if (!
context->DomainContains(vars[
time], label))
continue;
895 in_states.push_back(
tail);
896 transition_values.push_back(label);
900 out_states.push_back(
time + 1 == n ? 0 :
head);
903 std::vector<int> tuple_literals;
904 if (transition_values.size() == 1) {
905 bool tmp_removed_values =
false;
906 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
909 Domain(transition_values.front()),
910 &tmp_removed_values)) {
911 return (
void)
context->NotifyThatModelIsUnsat();
915 }
else if (transition_values.size() == 2) {
916 const int bool_var =
context->NewBoolVar();
917 tuple_literals.push_back(bool_var);
918 tuple_literals.push_back(
NegatedRef(bool_var));
923 LinearConstraintProto*
const exactly_one =
924 context->working_model->add_constraints()->mutable_linear();
925 exactly_one->add_domain(1);
926 exactly_one->add_domain(1);
927 for (
int i = 0; i < transition_values.size(); ++i) {
928 const int tuple_literal =
context->NewBoolVar();
929 tuple_literals.push_back(tuple_literal);
930 exactly_one->add_vars(tuple_literal);
931 exactly_one->add_coeffs(1);
937 std::vector<int64> s = transition_values;
943 return (
void)
context->NotifyThatModelIsUnsat();
949 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
956 std::vector<int64> s = out_states;
959 out_encoding.clear();
962 out_encoding[s.front()] =
var;
964 }
else if (s.size() > 2) {
965 for (
const int64 state : s) {
966 out_encoding[state] =
context->NewBoolVar();
971 if (!in_encoding.empty()) {
972 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
974 if (!encoding.empty()) {
975 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
978 if (!out_encoding.empty()) {
979 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
981 in_encoding.swap(out_encoding);
982 out_encoding.clear();
985 if (removed_values) {
986 context->UpdateRuleStats(
"automaton: reduced variable domains");
988 context->UpdateRuleStats(
"automaton: expanded");
992 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
993 TableConstraintProto& table = *
ct->mutable_table();
994 const int num_vars = table.vars_size();
995 const int num_original_tuples = table.values_size() / num_vars;
996 std::vector<std::vector<int64>> tuples(num_original_tuples);
998 for (
int i = 0; i < num_original_tuples; ++i) {
999 for (
int j = 0; j < num_vars; ++j) {
1000 tuples[i].push_back(table.values(count++));
1004 if (tuples.empty()) {
1005 context->UpdateRuleStats(
"table: empty negated constraint");
1012 std::vector<int64> domain_sizes;
1013 for (
int i = 0; i < num_vars; ++i) {
1014 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
1019 std::vector<int> clause;
1020 for (
const std::vector<int64>& tuple : tuples) {
1022 for (
int i = 0; i < num_vars; ++i) {
1024 if (
value == any_value)
continue;
1027 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
1030 if (!clause.empty()) {
1031 BoolArgumentProto* bool_or =
1032 context->working_model->add_constraints()->mutable_bool_or();
1033 for (
const int lit : clause) {
1034 bool_or->add_literals(lit);
1038 context->UpdateRuleStats(
"table: expanded negated constraint");
1042 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
1043 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
1044 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
1045 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
1050 lin_max->mutable_lin_max()->mutable_target());
1052 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
1053 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
1064 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
1065 const std::vector<int64>& values,
int variable,
1066 const std::vector<int>& tuples_with_any,
1068 VLOG(2) <<
"Process var(" << variable <<
") with domain "
1069 <<
context->DomainOf(variable) <<
" and " << values.size()
1070 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
1071 CHECK_EQ(tuple_literals.size(), values.size());
1072 std::vector<std::pair<int64, int>> pairs;
1075 for (
int i = 0; i < values.size(); ++i) {
1078 pairs.emplace_back(
value, tuple_literals[i]);
1083 std::vector<int> selected;
1084 std::sort(pairs.begin(), pairs.end());
1085 for (
int i = 0; i < pairs.size();) {
1088 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1089 selected.push_back(pairs[i].second);
1092 CHECK(!selected.empty() || !tuples_with_any.empty());
1093 if (selected.size() == 1 && tuples_with_any.empty()) {
1094 context->InsertVarValueEncoding(selected.front(), variable,
value);
1096 const int value_literal =
1098 BoolArgumentProto* no_support =
1099 context->working_model->add_constraints()->mutable_bool_or();
1100 for (
const int lit : selected) {
1101 no_support->add_literals(lit);
1102 context->AddImplication(lit, value_literal);
1104 for (
const int lit : tuples_with_any) {
1105 no_support->add_literals(lit);
1109 no_support->add_literals(
NegatedRef(value_literal));
1115 void AddSizeTwoTable(
1116 const std::vector<int>& vars,
const std::vector<std::vector<int64>>& tuples,
1117 const std::vector<absl::flat_hash_set<int64>>& values_per_var,
1120 const int left_var = vars[0];
1121 const int right_var = vars[1];
1122 if (
context->DomainOf(left_var).Size() == 1 ||
1123 context->DomainOf(right_var).Size() == 1) {
1129 std::map<int, std::vector<int>> left_to_right;
1130 std::map<int, std::vector<int>> right_to_left;
1132 for (
const auto& tuple : tuples) {
1133 const int64 left_value(tuple[0]);
1134 const int64 right_value(tuple[1]);
1136 CHECK(
context->DomainContains(right_var, right_value));
1138 const int left_literal =
1139 context->GetOrCreateVarValueEncoding(left_var, left_value);
1140 const int right_literal =
1141 context->GetOrCreateVarValueEncoding(right_var, right_value);
1142 left_to_right[left_literal].push_back(right_literal);
1143 right_to_left[right_literal].push_back(left_literal);
1146 int num_implications = 0;
1147 int num_clause_added = 0;
1148 int num_large_clause_added = 0;
1149 auto add_support_constraint =
1150 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1151 int lit,
const std::vector<int>& support_literals,
1152 int max_support_size) {
1153 if (support_literals.size() == max_support_size)
return;
1154 if (support_literals.size() == 1) {
1155 context->AddImplication(lit, support_literals.front());
1158 BoolArgumentProto* bool_or =
1159 context->working_model->add_constraints()->mutable_bool_or();
1160 for (
const int support_literal : support_literals) {
1161 bool_or->add_literals(support_literal);
1165 if (support_literals.size() > max_support_size / 2) {
1166 num_large_clause_added++;
1171 for (
const auto& it : left_to_right) {
1172 add_support_constraint(it.first, it.second, values_per_var[1].size());
1174 for (
const auto& it : right_to_left) {
1175 add_support_constraint(it.first, it.second, values_per_var[0].size());
1177 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1178 << num_clause_added <<
" clauses, including "
1179 << num_large_clause_added <<
" large clauses, " << num_implications
1183 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1184 const TableConstraintProto& table =
ct->table();
1185 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1186 const int num_vars = table.vars_size();
1187 const int num_original_tuples = table.values_size() / num_vars;
1190 std::vector<std::vector<int64>> tuples(num_original_tuples);
1192 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1193 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1194 tuples[tuple_index].push_back(table.values(count++));
1200 std::vector<absl::flat_hash_set<int64>> values_per_var(num_vars);
1202 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1204 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1205 const int64 value = tuples[tuple_index][var_index];
1206 if (!
context->DomainContains(vars[var_index],
value)) {
1212 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1213 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1215 std::swap(tuples[tuple_index], tuples[new_size]);
1219 tuples.resize(new_size);
1220 const int num_valid_tuples = tuples.size();
1222 if (tuples.empty()) {
1223 context->UpdateRuleStats(
"table: empty");
1224 return (
void)
context->NotifyThatModelIsUnsat();
1230 int num_fixed_variables = 0;
1231 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1235 values_per_var[var_index].end()})));
1236 if (
context->DomainOf(vars[var_index]).Size() == 1) {
1237 num_fixed_variables++;
1241 if (num_fixed_variables == num_vars - 1) {
1242 context->UpdateRuleStats(
"table: one variable not fixed");
1245 }
else if (num_fixed_variables == num_vars) {
1246 context->UpdateRuleStats(
"table: all variables fixed");
1252 if (num_vars == 2) {
1253 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1255 "table: expanded positive constraint with two variables");
1262 int num_prefix_tuples = 0;
1264 absl::flat_hash_set<absl::Span<const int64>> prefixes;
1265 for (
const std::vector<int64>& tuple : tuples) {
1266 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1268 num_prefix_tuples = prefixes.size();
1275 std::vector<int64> domain_sizes;
1276 for (
int i = 0; i < num_vars; ++i) {
1277 domain_sizes.push_back(values_per_var[i].size());
1280 const int num_compressed_tuples = tuples.size();
1282 if (num_compressed_tuples == 1) {
1284 context->UpdateRuleStats(
"table: one tuple");
1290 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1291 if (prefixes_are_all_different) {
1293 "TODO table: last value implied by previous values");
1303 int64 max_num_prefix_tuples = 1;
1304 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1305 max_num_prefix_tuples =
1306 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1310 absl::StrCat(
"Table: ", num_vars,
1311 " variables, original tuples = ", num_original_tuples);
1312 if (num_valid_tuples != num_original_tuples) {
1313 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1315 if (prefixes_are_all_different) {
1316 if (num_prefix_tuples < max_num_prefix_tuples) {
1317 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1318 max_num_prefix_tuples);
1320 absl::StrAppend(&
message,
", full prefix = true");
1323 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1325 if (num_compressed_tuples != num_valid_tuples) {
1327 ", compressed tuples = ", num_compressed_tuples);
1333 if (num_compressed_tuples == 2) {
1334 context->UpdateRuleStats(
"TODO table: two tuples");
1346 std::vector<int> tuple_literals(num_compressed_tuples);
1347 BoolArgumentProto* at_least_one_tuple =
1348 context->working_model->add_constraints()->mutable_bool_or();
1361 BoolArgumentProto* at_most_one_tuple =
nullptr;
1362 if (
context->keep_all_feasible_solutions) {
1364 context->working_model->add_constraints()->mutable_at_most_one();
1367 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1368 tuple_literals[var_index] =
context->NewBoolVar();
1369 at_least_one_tuple->add_literals(tuple_literals[var_index]);
1370 if (at_most_one_tuple !=
nullptr) {
1371 at_most_one_tuple->add_literals(tuple_literals[var_index]);
1375 std::vector<int> active_tuple_literals;
1376 std::vector<int64> active_values;
1377 std::vector<int> any_tuple_literals;
1378 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1379 if (values_per_var[var_index].size() == 1)
continue;
1381 active_tuple_literals.clear();
1382 active_values.clear();
1383 any_tuple_literals.clear();
1384 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1386 const int64 value = tuples[tuple_index][var_index];
1387 const int tuple_literal = tuple_literals[tuple_index];
1389 if (
value == any_value) {
1390 any_tuple_literals.push_back(tuple_literal);
1392 active_tuple_literals.push_back(tuple_literal);
1393 active_values.push_back(
value);
1397 if (!active_tuple_literals.empty()) {
1398 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1403 context->UpdateRuleStats(
"table: expanded positive constraint");
1407 void ExpandAllDiff(
bool expand_non_permutations, ConstraintProto*
ct,
1409 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1410 if (
proto.vars_size() <= 2)
return;
1412 const int num_vars =
proto.vars_size();
1414 Domain union_of_domains =
context->DomainOf(
proto.vars(0));
1415 for (
int i = 1; i < num_vars; ++i) {
1417 union_of_domains.UnionWith(
context->DomainOf(
proto.vars(i)));
1420 const bool is_permutation =
proto.vars_size() == union_of_domains.Size();
1422 if (!is_permutation && !expand_non_permutations)
return;
1427 for (
const ClosedInterval&
interval : union_of_domains) {
1430 std::vector<int> possible_refs;
1431 int fixed_variable_count = 0;
1432 for (
const int ref :
proto.vars()) {
1433 if (!
context->DomainContains(ref, v))
continue;
1434 possible_refs.push_back(ref);
1435 if (
context->DomainOf(ref).Size() == 1) {
1436 fixed_variable_count++;
1440 if (fixed_variable_count > 1) {
1442 return (
void)
context->NotifyThatModelIsUnsat();
1443 }
else if (fixed_variable_count == 1) {
1445 for (
const int ref : possible_refs) {
1446 if (
context->DomainOf(ref).Size() == 1)
continue;
1447 if (!
context->IntersectDomainWith(ref, Domain(v).Complement())) {
1448 VLOG(1) <<
"Empty domain for a variable in ExpandAllDiff()";
1454 LinearConstraintProto* at_most_or_equal_one =
1455 context->working_model->add_constraints()->mutable_linear();
1456 int lb = is_permutation ? 1 : 0;
1458 for (
const int ref : possible_refs) {
1461 const int encoding =
context->GetOrCreateVarValueEncoding(ref, v);
1463 at_most_or_equal_one->add_vars(encoding);
1464 at_most_or_equal_one->add_coeffs(1);
1466 at_most_or_equal_one->add_vars(
PositiveRef(encoding));
1467 at_most_or_equal_one->add_coeffs(-1);
1472 at_most_or_equal_one->add_domain(lb);
1473 at_most_or_equal_one->add_domain(ub);
1476 if (is_permutation) {
1477 context->UpdateRuleStats(
"alldiff: permutation expanded");
1479 context->UpdateRuleStats(
"alldiff: expanded");
1487 if (
context->ModelIsUnsat())
return;
1490 context->InitializeNewDomains();
1492 const int num_constraints =
context->working_model->constraints_size();
1493 for (
int i = 0; i < num_constraints; ++i) {
1494 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
1496 switch (
ct->constraint_case()) {
1497 case ConstraintProto::ConstraintCase::kReservoir:
1500 case ConstraintProto::ConstraintCase::kIntMod:
1503 case ConstraintProto::ConstraintCase::kIntProd:
1506 case ConstraintProto::ConstraintCase::kLinMin:
1509 case ConstraintProto::ConstraintCase::kElement:
1510 if (options.
parameters.expand_element_constraints()) {
1514 case ConstraintProto::ConstraintCase::kInverse:
1517 case ConstraintProto::ConstraintCase::kAutomaton:
1518 if (options.
parameters.expand_automaton_constraints()) {
1522 case ConstraintProto::ConstraintCase::kTable:
1523 if (
ct->table().negated()) {
1525 }
else if (options.
parameters.expand_table_constraints()) {
1529 case ConstraintProto::ConstraintCase::kAllDiff:
1530 ExpandAllDiff(options.
parameters.expand_alldiff_constraints(),
ct,
1540 context->UpdateNewConstraintsVariableUsage();
1541 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1542 context->UpdateConstraintVariableUsage(i);
1546 if (
context->ModelIsUnsat())
return;
1550 context->InitializeNewDomains();
1553 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1555 context->working_model->mutable_variables(i));