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::pair<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::pair<int, int> p = std::make_pair(time_i, time_j);
108 const std::pair<int, int> rev_p = std::make_pair(time_j, time_i);
111 const int i_lesseq_j =
context->NewBoolVar();
112 context->working_model->mutable_variables(i_lesseq_j)
113 ->set_name(absl::StrCat(i,
" before ", j));
114 precedence_cache[p] = i_lesseq_j;
115 const int j_lesseq_i =
context->NewBoolVar();
116 context->working_model->mutable_variables(j_lesseq_i)
117 ->set_name(absl::StrCat(j,
" before ", i));
119 precedence_cache[rev_p] = j_lesseq_i;
120 add_reified_precedence(i_lesseq_j, time_i, time_j, active_i, active_j);
121 add_reified_precedence(j_lesseq_i, time_j, time_i, active_j, active_i);
124 auto*
const bool_or =
125 context->working_model->add_constraints()->mutable_bool_or();
126 bool_or->add_literals(i_lesseq_j);
127 bool_or->add_literals(j_lesseq_i);
137 for (
int i = 0; i < num_events; ++i) {
138 const int active_i = is_active_literal(i);
139 if (
context->LiteralIsFalse(active_i))
continue;
140 const int time_i = reservoir.times(i);
143 ConstraintProto*
const level =
context->working_model->add_constraints();
144 level->add_enforcement_literal(active_i);
147 for (
int j = 0; j < num_events; ++j) {
148 if (i == j)
continue;
149 const int active_j = is_active_literal(j);
150 if (
context->LiteralIsFalse(active_j))
continue;
152 const int time_j = reservoir.times(j);
154 precedence_cache, std::make_pair(time_j, time_i)));
155 level->mutable_linear()->add_coeffs(reservoir.demands(j));
159 const int64 demand_i = reservoir.demands(i);
160 level->mutable_linear()->add_domain(
161 CapSub(reservoir.min_level(), demand_i));
162 level->mutable_linear()->add_domain(
163 CapSub(reservoir.max_level(), demand_i));
169 context->working_model->add_constraints()->mutable_linear();
170 for (
int i = 0; i < num_events; ++i) {
171 sum->add_vars(is_active_literal(i));
172 sum->add_coeffs(reservoir.demands(i));
174 sum->add_domain(reservoir.min_level());
175 sum->add_domain(reservoir.max_level());
181 if (reservoir.min_level() > 0 || reservoir.max_level() < 0) {
182 auto*
const sum_at_zero =
183 context->working_model->add_constraints()->mutable_linear();
184 for (
int i = 0; i < num_events; ++i) {
185 const int active_i = is_active_literal(i);
186 if (
context->LiteralIsFalse(active_i))
continue;
188 const int time_i = reservoir.times(i);
189 const int lesseq_0 =
context->NewBoolVar();
192 ConstraintProto*
const lesseq =
context->working_model->add_constraints();
193 lesseq->add_enforcement_literal(lesseq_0);
194 lesseq->mutable_linear()->add_vars(time_i);
195 lesseq->mutable_linear()->add_coeffs(1);
196 lesseq->mutable_linear()->add_domain(
kint64min);
197 lesseq->mutable_linear()->add_domain(0);
199 if (!
context->LiteralIsTrue(active_i)) {
200 context->AddImplication(lesseq_0, active_i);
204 ConstraintProto*
const greater =
205 context->working_model->add_constraints();
206 greater->add_enforcement_literal(
NegatedRef(lesseq_0));
207 greater->add_enforcement_literal(active_i);
208 greater->mutable_linear()->add_vars(time_i);
209 greater->mutable_linear()->add_coeffs(1);
210 greater->mutable_linear()->add_domain(1);
211 greater->mutable_linear()->add_domain(
kint64max);
214 sum_at_zero->add_vars(lesseq_0);
215 sum_at_zero->add_coeffs(reservoir.demands(i));
217 sum_at_zero->add_domain(reservoir.min_level());
218 sum_at_zero->add_domain(reservoir.max_level());
222 context->UpdateRuleStats(
"reservoir: expanded");
225 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
226 const IntegerArgumentProto& int_mod =
ct->int_mod();
227 const int var = int_mod.vars(0);
228 const int mod_var = int_mod.vars(1);
229 const int target_var = int_mod.target();
240 context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb));
242 auto add_enforcement_literal_if_needed = [&]() {
243 if (
ct->enforcement_literal_size() == 0)
return;
244 const int literal =
ct->enforcement_literal(0);
245 ConstraintProto*
const last =
context->working_model->mutable_constraints(
246 context->working_model->constraints_size() - 1);
247 last->add_enforcement_literal(
literal);
251 IntegerArgumentProto*
const div_proto =
252 context->working_model->add_constraints()->mutable_int_div();
253 div_proto->set_target(div_var);
254 div_proto->add_vars(
var);
255 div_proto->add_vars(mod_var);
256 add_enforcement_literal_if_needed();
259 if (mod_lb == mod_ub) {
261 LinearConstraintProto*
const lin =
262 context->working_model->add_constraints()->mutable_linear();
263 lin->add_vars(int_mod.vars(0));
265 lin->add_vars(div_var);
266 lin->add_coeffs(-mod_lb);
267 lin->add_vars(target_var);
271 add_enforcement_literal_if_needed();
274 const int prod_var =
context->NewIntVar(
275 Domain(var_lb * mod_lb / mod_ub, var_ub * mod_ub / mod_lb));
276 IntegerArgumentProto*
const int_prod =
277 context->working_model->add_constraints()->mutable_int_prod();
278 int_prod->set_target(prod_var);
279 int_prod->add_vars(div_var);
280 int_prod->add_vars(mod_var);
281 add_enforcement_literal_if_needed();
284 LinearConstraintProto*
const lin =
285 context->working_model->add_constraints()->mutable_linear();
288 lin->add_vars(prod_var);
290 lin->add_vars(target_var);
294 add_enforcement_literal_if_needed();
298 context->UpdateRuleStats(
"int_mod: expanded");
301 void ExpandIntProdWithBoolean(
int bool_ref,
int int_ref,
int product_ref,
303 ConstraintProto*
const one =
context->working_model->add_constraints();
304 one->add_enforcement_literal(bool_ref);
305 one->mutable_linear()->add_vars(int_ref);
306 one->mutable_linear()->add_coeffs(1);
307 one->mutable_linear()->add_vars(product_ref);
308 one->mutable_linear()->add_coeffs(-1);
309 one->mutable_linear()->add_domain(0);
310 one->mutable_linear()->add_domain(0);
312 ConstraintProto*
const zero =
context->working_model->add_constraints();
313 zero->add_enforcement_literal(
NegatedRef(bool_ref));
314 zero->mutable_linear()->add_vars(product_ref);
315 zero->mutable_linear()->add_coeffs(1);
316 zero->mutable_linear()->add_domain(0);
317 zero->mutable_linear()->add_domain(0);
320 void AddXEqualYOrXEqualZero(
int x_eq_y,
int x,
int y,
322 ConstraintProto* equality =
context->working_model->add_constraints();
323 equality->add_enforcement_literal(x_eq_y);
324 equality->mutable_linear()->add_vars(x);
325 equality->mutable_linear()->add_coeffs(1);
326 equality->mutable_linear()->add_vars(y);
327 equality->mutable_linear()->add_coeffs(-1);
328 equality->mutable_linear()->add_domain(0);
329 equality->mutable_linear()->add_domain(0);
334 void ExpandIntProdWithOneAcrossZero(
int a_ref,
int b_ref,
int product_ref,
336 DCHECK_LT(
context->MinOf(a_ref), 0);
337 DCHECK_GT(
context->MaxOf(a_ref), 0);
341 const int a_is_positive =
context->NewBoolVar();
344 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
345 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
347 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
351 const bool b_is_positive =
context->MinOf(b_ref) >= 0;
352 const Domain pos_a_product_domain =
353 b_is_positive ? Domain({0,
context->MaxOf(product_ref)})
354 : Domain({
context->MinOf(product_ref), 0});
355 const int pos_a_product =
context->NewIntVar(pos_a_product_domain);
356 IntegerArgumentProto* pos_product =
357 context->working_model->add_constraints()->mutable_int_prod();
358 pos_product->set_target(pos_a_product);
359 pos_product->add_vars(pos_a_ref);
360 pos_product->add_vars(b_ref);
363 const Domain neg_a_product_domain =
364 b_is_positive ? Domain({
context->MinOf(product_ref), 0})
365 : Domain({0,
context->MaxOf(product_ref)});
366 const int neg_a_product =
context->NewIntVar(neg_a_product_domain);
367 IntegerArgumentProto* neg_product =
368 context->working_model->add_constraints()->mutable_int_prod();
369 neg_product->set_target(neg_a_product);
370 neg_product->add_vars(neg_a_ref);
371 neg_product->add_vars(b_ref);
374 LinearConstraintProto* lin =
375 context->working_model->add_constraints()->mutable_linear();
376 lin->add_vars(product_ref);
378 lin->add_vars(pos_a_product);
380 lin->add_vars(neg_a_product);
386 void ExpandIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
int product_ref,
389 const int a_is_positive =
context->NewBoolVar();
395 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
396 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
398 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
402 const int pos_product_ref =
404 ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref,
context);
405 const int neg_product_ref =
407 ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref,
context);
410 LinearConstraintProto* lin =
411 context->working_model->add_constraints()->mutable_linear();
412 lin->add_vars(product_ref);
414 lin->add_vars(pos_product_ref);
416 lin->add_vars(neg_product_ref);
422 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
423 const IntegerArgumentProto& int_prod =
ct->int_prod();
424 if (int_prod.vars_size() != 2)
return;
425 const int a = int_prod.vars(0);
426 const int b = int_prod.vars(1);
427 const int p = int_prod.target();
428 const bool a_is_boolean =
430 const bool b_is_boolean =
435 if (a_is_boolean && !b_is_boolean) {
436 ExpandIntProdWithBoolean(
a,
b, p,
context);
438 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
441 if (b_is_boolean && !a_is_boolean) {
442 ExpandIntProdWithBoolean(
b,
a, p,
context);
444 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
448 const bool a_span_across_zero =
450 const bool b_span_across_zero =
452 if (a_span_across_zero && !b_span_across_zero) {
453 ExpandIntProdWithOneAcrossZero(
a,
b, p,
context);
456 "int_prod: expanded product with general integer variables");
459 if (!a_span_across_zero && b_span_across_zero) {
460 ExpandIntProdWithOneAcrossZero(
b,
a, p,
context);
463 "int_prod: expanded product with general integer variables");
466 if (a_span_across_zero && b_span_across_zero) {
467 ExpandIntProdWithTwoAcrossZero(
a,
b, p,
context);
470 "int_prod: expanded product with general integer variables");
475 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
476 const int size =
ct->inverse().f_direct().size();
477 CHECK_EQ(size,
ct->inverse().f_inverse().size());
483 for (
const int ref :
ct->inverse().f_direct()) {
484 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
485 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
489 for (
const int ref :
ct->inverse().f_inverse()) {
490 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
491 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
498 std::vector<int64> possible_values;
502 const auto filter_inverse_domain = [
context, size, &possible_values](
504 const auto& inverse) {
506 for (
int i = 0; i < size; ++i) {
507 possible_values.clear();
508 const Domain domain =
context->DomainOf(direct[i]);
509 bool removed_value =
false;
510 for (
const ClosedInterval&
interval : domain) {
512 if (
context->DomainOf(inverse[j]).Contains(i)) {
513 possible_values.push_back(j);
515 removed_value =
true;
520 if (!
context->IntersectDomainWith(
522 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
530 if (!filter_inverse_domain(
ct->inverse().f_direct(),
531 ct->inverse().f_inverse())) {
535 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
536 ct->inverse().f_direct())) {
542 for (
int i = 0; i < size; ++i) {
543 const int f_i =
ct->inverse().f_direct(i);
544 const Domain domain =
context->DomainOf(f_i);
545 for (
const ClosedInterval&
interval : domain) {
548 const int r_j =
ct->inverse().f_inverse(j);
550 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
551 context->InsertVarValueEncoding(r_j_i, f_i, j);
553 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
554 context->InsertVarValueEncoding(f_i_j, r_j, i);
561 context->UpdateRuleStats(
"inverse: expanded");
564 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
565 const ElementConstraintProto& element =
ct->element();
566 const int index_ref = element.index();
567 const int target_ref = element.target();
568 const int size = element.vars_size();
570 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
571 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
572 return (
void)
context->NotifyThatModelIsUnsat();
575 bool all_constants =
true;
576 absl::flat_hash_map<int64, int> constant_var_values_usage;
577 std::vector<int64> constant_var_values;
578 std::vector<int64> invalid_indices;
579 Domain index_domain =
context->DomainOf(index_ref);
580 Domain target_domain =
context->DomainOf(target_ref);
581 for (
const ClosedInterval&
interval : index_domain) {
583 const int var = element.vars(v);
584 const Domain var_domain =
context->DomainOf(
var);
585 if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
586 invalid_indices.push_back(v);
589 if (var_domain.Min() != var_domain.Max()) {
590 all_constants =
false;
595 if (constant_var_values_usage[
value]++ == 0) {
596 constant_var_values.push_back(
value);
601 if (!invalid_indices.empty() && target_ref != index_ref) {
602 if (!
context->IntersectDomainWith(
604 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
605 return (
void)
context->NotifyThatModelIsUnsat();
609 index_domain =
context->DomainOf(index_ref);
617 absl::flat_hash_map<int64, BoolArgumentProto*> supports;
618 if (all_constants && target_ref != index_ref) {
619 if (!
context->IntersectDomainWith(
621 VLOG(1) <<
"Empty domain for the target variable in ExpandElement()";
625 target_domain =
context->DomainOf(target_ref);
626 if (target_domain.Size() == 1) {
627 context->UpdateRuleStats(
"element: one value array");
632 for (
const ClosedInterval&
interval : target_domain) {
636 const int lit =
context->GetOrCreateVarValueEncoding(target_ref, v);
637 BoolArgumentProto*
const support =
638 context->working_model->add_constraints()->mutable_bool_or();
639 supports[v] = support;
648 auto* bool_or =
context->working_model->add_constraints()->mutable_bool_or();
650 for (
const ClosedInterval&
interval : index_domain) {
652 const int var = element.vars(v);
653 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
654 const Domain var_domain =
context->DomainOf(
var);
656 bool_or->add_literals(index_lit);
658 if (target_ref == index_ref) {
661 context->AddImplyInDomain(index_lit,
var, Domain(v));
662 }
else if (target_domain.Size() == 1) {
665 context->AddImplyInDomain(index_lit,
var, target_domain);
666 }
else if (var_domain.Size() == 1) {
669 if (constant_var_values_usage[
value] > 1) {
672 const int target_lit =
673 context->GetOrCreateVarValueEncoding(target_ref,
value);
674 context->AddImplication(index_lit, target_lit);
678 context->InsertVarValueEncoding(index_lit, target_ref,
value);
681 context->AddImplyInDomain(index_lit, target_ref, var_domain);
684 ConstraintProto*
const ct =
context->working_model->add_constraints();
685 ct->add_enforcement_literal(index_lit);
686 ct->mutable_linear()->add_vars(
var);
687 ct->mutable_linear()->add_coeffs(1);
688 ct->mutable_linear()->add_vars(target_ref);
689 ct->mutable_linear()->add_coeffs(-1);
690 ct->mutable_linear()->add_domain(0);
691 ct->mutable_linear()->add_domain(0);
697 const int64 var_min = target_domain.Min();
702 for (
const auto it : constant_var_values_usage) {
703 if (it.second > usage ||
704 (it.second == usage && it.first < most_frequent_value)) {
706 most_frequent_value = it.first;
718 usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
719 if (base != var_min) {
720 VLOG(3) <<
"expand element: choose " << base <<
" with usage " << usage
721 <<
" over " << var_min <<
" among " << size <<
" values.";
724 LinearConstraintProto*
const linear =
725 context->working_model->add_constraints()->mutable_linear();
727 linear->add_vars(target_ref);
728 linear->add_coeffs(-1);
729 for (
const ClosedInterval&
interval : index_domain) {
731 const int ref = element.vars(v);
732 const int index_lit =
733 context->GetOrCreateVarValueEncoding(index_ref, v);
736 linear->add_vars(index_lit);
737 linear->add_coeffs(
delta);
740 linear->add_coeffs(-
delta);
745 linear->add_domain(rhs);
746 linear->add_domain(rhs);
748 context->UpdateRuleStats(
"element: expanded value element");
750 context->UpdateRuleStats(
"element: expanded");
757 void LinkLiteralsAndValues(
758 const std::vector<int>& value_literals,
const std::vector<int64>& values,
759 const absl::flat_hash_map<int64, int>& target_encoding,
761 CHECK_EQ(value_literals.size(), values.size());
765 std::map<int, std::vector<int>> value_literals_per_target_literal;
770 for (
int i = 0; i < values.size(); ++i) {
771 const int64 v = values[i];
772 CHECK(target_encoding.contains(v));
774 value_literals_per_target_literal[lit].push_back(value_literals[i]);
779 for (
const auto& it : value_literals_per_target_literal) {
780 const int target_literal = it.first;
781 switch (it.second.size()) {
783 if (!
context->SetLiteralToFalse(target_literal)) {
789 context->StoreBooleanEqualityRelation(target_literal,
794 BoolArgumentProto*
const bool_or =
795 context->working_model->add_constraints()->mutable_bool_or();
796 bool_or->add_literals(
NegatedRef(target_literal));
797 for (
const int value_literal : it.second) {
798 bool_or->add_literals(value_literal);
799 context->AddImplication(value_literal, target_literal);
806 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
807 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
809 if (
proto.vars_size() == 0) {
810 const int64 initial_state =
proto.starting_state();
811 for (
const int64 final_state :
proto.final_states()) {
812 if (initial_state == final_state) {
813 context->UpdateRuleStats(
"automaton: empty constraint");
819 return (
void)
context->NotifyThatModelIsUnsat();
820 }
else if (
proto.transition_label_size() == 0) {
822 return (
void)
context->NotifyThatModelIsUnsat();
825 const int n =
proto.vars_size();
826 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
829 const absl::flat_hash_set<int64> final_states(
830 {
proto.final_states().begin(),
proto.final_states().end()});
831 std::vector<absl::flat_hash_set<int64>> reachable_states(n + 1);
832 reachable_states[0].insert(
proto.starting_state());
836 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
838 const int64 label =
proto.transition_label(t);
840 if (!reachable_states[
time].contains(
tail))
continue;
841 if (!
context->DomainContains(vars[
time], label))
continue;
842 if (
time == n - 1 && !final_states.contains(
head))
continue;
843 reachable_states[
time + 1].insert(
head);
849 absl::flat_hash_set<int64> new_set;
850 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
852 const int64 label =
proto.transition_label(t);
855 if (!reachable_states[
time].contains(
tail))
continue;
856 if (!
context->DomainContains(vars[
time], label))
continue;
857 if (!reachable_states[
time + 1].contains(
head))
continue;
858 new_set.insert(
tail);
860 reachable_states[
time].swap(new_set);
868 absl::flat_hash_map<int64, int> encoding;
869 absl::flat_hash_map<int64, int> in_encoding;
870 absl::flat_hash_map<int64, int> out_encoding;
871 bool removed_values =
false;
877 std::vector<int64> in_states;
878 std::vector<int64> transition_values;
879 std::vector<int64> out_states;
880 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
882 const int64 label =
proto.transition_label(i);
885 if (!reachable_states[
time].contains(
tail))
continue;
886 if (!reachable_states[
time + 1].contains(
head))
continue;
887 if (!
context->DomainContains(vars[
time], label))
continue;
892 in_states.push_back(
tail);
893 transition_values.push_back(label);
897 out_states.push_back(
time + 1 == n ? 0 :
head);
900 std::vector<int> tuple_literals;
901 if (transition_values.size() == 1) {
902 bool tmp_removed_values =
false;
903 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
904 CHECK_EQ(reachable_states[
time + 1].size(), 1);
906 Domain(transition_values.front()),
907 &tmp_removed_values)) {
908 return (
void)
context->NotifyThatModelIsUnsat();
912 }
else if (transition_values.size() == 2) {
913 const int bool_var =
context->NewBoolVar();
914 tuple_literals.push_back(bool_var);
915 tuple_literals.push_back(
NegatedRef(bool_var));
920 LinearConstraintProto*
const exactly_one =
921 context->working_model->add_constraints()->mutable_linear();
922 exactly_one->add_domain(1);
923 exactly_one->add_domain(1);
924 for (
int i = 0; i < transition_values.size(); ++i) {
925 const int tuple_literal =
context->NewBoolVar();
926 tuple_literals.push_back(tuple_literal);
927 exactly_one->add_vars(tuple_literal);
928 exactly_one->add_coeffs(1);
934 std::vector<int64> s = transition_values;
940 return (
void)
context->NotifyThatModelIsUnsat();
946 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
953 std::vector<int64> s = out_states;
956 out_encoding.clear();
959 out_encoding[s.front()] =
var;
961 }
else if (s.size() > 2) {
962 for (
const int64 state : s) {
963 out_encoding[state] =
context->NewBoolVar();
968 if (!in_encoding.empty()) {
969 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
971 if (!encoding.empty()) {
972 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
975 if (!out_encoding.empty()) {
976 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
978 in_encoding.swap(out_encoding);
979 out_encoding.clear();
982 if (removed_values) {
983 context->UpdateRuleStats(
"automaton: reduced variable domains");
985 context->UpdateRuleStats(
"automaton: expanded");
989 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
990 TableConstraintProto& table = *
ct->mutable_table();
991 const int num_vars = table.vars_size();
992 const int num_original_tuples = table.values_size() / num_vars;
993 std::vector<std::vector<int64>> tuples(num_original_tuples);
995 for (
int i = 0; i < num_original_tuples; ++i) {
996 for (
int j = 0; j < num_vars; ++j) {
997 tuples[i].push_back(table.values(count++));
1001 if (tuples.empty()) {
1002 context->UpdateRuleStats(
"table: empty negated constraint");
1009 std::vector<int64> domain_sizes;
1010 for (
int i = 0; i < num_vars; ++i) {
1011 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
1016 std::vector<int> clause;
1017 for (
const std::vector<int64>& tuple : tuples) {
1019 for (
int i = 0; i < num_vars; ++i) {
1021 if (
value == any_value)
continue;
1024 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
1027 if (!clause.empty()) {
1028 BoolArgumentProto* bool_or =
1029 context->working_model->add_constraints()->mutable_bool_or();
1030 for (
const int lit : clause) {
1031 bool_or->add_literals(lit);
1035 context->UpdateRuleStats(
"table: expanded negated constraint");
1039 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
1040 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
1041 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
1042 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
1047 lin_max->mutable_lin_max()->mutable_target());
1049 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
1050 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
1061 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
1062 const std::vector<int64>& values,
int variable,
1063 const std::vector<int>& tuples_with_any,
1065 VLOG(2) <<
"Process var(" << variable <<
") with domain "
1066 <<
context->DomainOf(variable) <<
" and " << values.size()
1067 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
1068 CHECK_EQ(tuple_literals.size(), values.size());
1069 std::vector<std::pair<int64, int>> pairs;
1072 for (
int i = 0; i < values.size(); ++i) {
1075 pairs.emplace_back(
value, tuple_literals[i]);
1080 std::vector<int> selected;
1081 std::sort(pairs.begin(), pairs.end());
1082 for (
int i = 0; i < pairs.size();) {
1085 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1086 selected.push_back(pairs[i].second);
1089 CHECK(!selected.empty() || !tuples_with_any.empty());
1090 if (selected.size() == 1 && tuples_with_any.empty()) {
1091 context->InsertVarValueEncoding(selected.front(), variable,
value);
1093 const int value_literal =
1095 BoolArgumentProto* no_support =
1096 context->working_model->add_constraints()->mutable_bool_or();
1097 for (
const int lit : selected) {
1098 no_support->add_literals(lit);
1099 context->AddImplication(lit, value_literal);
1101 for (
const int lit : tuples_with_any) {
1102 no_support->add_literals(lit);
1106 no_support->add_literals(
NegatedRef(value_literal));
1112 void AddSizeTwoTable(
1113 const std::vector<int>& vars,
const std::vector<std::vector<int64>>& tuples,
1114 const std::vector<absl::flat_hash_set<int64>>& values_per_var,
1116 CHECK_EQ(vars.size(), 2);
1117 const int left_var = vars[0];
1118 const int right_var = vars[1];
1119 if (
context->DomainOf(left_var).Size() == 1 ||
1120 context->DomainOf(right_var).Size() == 1) {
1126 std::map<int, std::vector<int>> left_to_right;
1127 std::map<int, std::vector<int>> right_to_left;
1129 for (
const auto& tuple : tuples) {
1130 const int64 left_value(tuple[0]);
1131 const int64 right_value(tuple[1]);
1132 CHECK(
context->DomainContains(left_var, left_value));
1133 CHECK(
context->DomainContains(right_var, right_value));
1135 const int left_literal =
1136 context->GetOrCreateVarValueEncoding(left_var, left_value);
1137 const int right_literal =
1138 context->GetOrCreateVarValueEncoding(right_var, right_value);
1139 left_to_right[left_literal].push_back(right_literal);
1140 right_to_left[right_literal].push_back(left_literal);
1143 int num_implications = 0;
1144 int num_clause_added = 0;
1145 int num_large_clause_added = 0;
1146 auto add_support_constraint =
1147 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1148 int lit,
const std::vector<int>& support_literals,
1149 int max_support_size) {
1150 if (support_literals.size() == max_support_size)
return;
1151 if (support_literals.size() == 1) {
1152 context->AddImplication(lit, support_literals.front());
1155 BoolArgumentProto* bool_or =
1156 context->working_model->add_constraints()->mutable_bool_or();
1157 for (
const int support_literal : support_literals) {
1158 bool_or->add_literals(support_literal);
1162 if (support_literals.size() > max_support_size / 2) {
1163 num_large_clause_added++;
1168 for (
const auto& it : left_to_right) {
1169 add_support_constraint(it.first, it.second, values_per_var[1].size());
1171 for (
const auto& it : right_to_left) {
1172 add_support_constraint(it.first, it.second, values_per_var[0].size());
1174 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1175 << num_clause_added <<
" clauses, including "
1176 << num_large_clause_added <<
" large clauses, " << num_implications
1180 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1181 const TableConstraintProto& table =
ct->table();
1182 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1183 const int num_vars = table.vars_size();
1184 const int num_original_tuples = table.values_size() / num_vars;
1187 std::vector<std::vector<int64>> tuples(num_original_tuples);
1189 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1190 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1191 tuples[tuple_index].push_back(table.values(count++));
1197 std::vector<absl::flat_hash_set<int64>> values_per_var(num_vars);
1199 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1201 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1202 const int64 value = tuples[tuple_index][var_index];
1203 if (!
context->DomainContains(vars[var_index],
value)) {
1209 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1210 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1212 std::swap(tuples[tuple_index], tuples[new_size]);
1216 tuples.resize(new_size);
1217 const int num_valid_tuples = tuples.size();
1219 if (tuples.empty()) {
1220 context->UpdateRuleStats(
"table: empty");
1221 return (
void)
context->NotifyThatModelIsUnsat();
1227 int num_fixed_variables = 0;
1228 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1229 CHECK(
context->IntersectDomainWith(
1232 values_per_var[var_index].end()})));
1233 if (
context->DomainOf(vars[var_index]).Size() == 1) {
1234 num_fixed_variables++;
1238 if (num_fixed_variables == num_vars - 1) {
1239 context->UpdateRuleStats(
"table: one variable not fixed");
1242 }
else if (num_fixed_variables == num_vars) {
1243 context->UpdateRuleStats(
"table: all variables fixed");
1249 if (num_vars == 2) {
1250 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1252 "table: expanded positive constraint with two variables");
1259 int num_prefix_tuples = 0;
1261 absl::flat_hash_set<absl::Span<const int64>> prefixes;
1262 for (
const std::vector<int64>& tuple : tuples) {
1263 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1265 num_prefix_tuples = prefixes.size();
1272 std::vector<int64> domain_sizes;
1273 for (
int i = 0; i < num_vars; ++i) {
1274 domain_sizes.push_back(values_per_var[i].size());
1277 const int num_compressed_tuples = tuples.size();
1279 if (num_compressed_tuples == 1) {
1281 context->UpdateRuleStats(
"table: one tuple");
1287 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1288 if (prefixes_are_all_different) {
1290 "TODO table: last value implied by previous values");
1298 if (VLOG_IS_ON(2)) {
1300 int64 max_num_prefix_tuples = 1;
1301 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1302 max_num_prefix_tuples =
1303 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1307 absl::StrCat(
"Table: ", num_vars,
1308 " variables, original tuples = ", num_original_tuples);
1309 if (num_valid_tuples != num_original_tuples) {
1310 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1312 if (prefixes_are_all_different) {
1313 if (num_prefix_tuples < max_num_prefix_tuples) {
1314 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1315 max_num_prefix_tuples);
1317 absl::StrAppend(&
message,
", full prefix = true");
1320 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1322 if (num_compressed_tuples != num_valid_tuples) {
1324 ", compressed tuples = ", num_compressed_tuples);
1330 if (num_compressed_tuples == 2) {
1331 context->UpdateRuleStats(
"TODO table: two tuples");
1343 std::vector<int> tuple_literals(num_compressed_tuples);
1344 BoolArgumentProto* at_least_one_tuple =
1345 context->working_model->add_constraints()->mutable_bool_or();
1358 BoolArgumentProto* at_most_one_tuple =
nullptr;
1359 if (
context->keep_all_feasible_solutions) {
1361 context->working_model->add_constraints()->mutable_at_most_one();
1364 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1365 tuple_literals[var_index] =
context->NewBoolVar();
1366 at_least_one_tuple->add_literals(tuple_literals[var_index]);
1367 if (at_most_one_tuple !=
nullptr) {
1368 at_most_one_tuple->add_literals(tuple_literals[var_index]);
1372 std::vector<int> active_tuple_literals;
1373 std::vector<int64> active_values;
1374 std::vector<int> any_tuple_literals;
1375 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1376 if (values_per_var[var_index].size() == 1)
continue;
1378 active_tuple_literals.clear();
1379 active_values.clear();
1380 any_tuple_literals.clear();
1381 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1383 const int64 value = tuples[tuple_index][var_index];
1384 const int tuple_literal = tuple_literals[tuple_index];
1386 if (
value == any_value) {
1387 any_tuple_literals.push_back(tuple_literal);
1389 active_tuple_literals.push_back(tuple_literal);
1390 active_values.push_back(
value);
1394 if (!active_tuple_literals.empty()) {
1395 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1400 context->UpdateRuleStats(
"table: expanded positive constraint");
1404 void ExpandAllDiff(
bool expand_non_permutations, ConstraintProto*
ct,
1406 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1407 if (
proto.vars_size() <= 2)
return;
1409 const int num_vars =
proto.vars_size();
1411 Domain union_of_domains =
context->DomainOf(
proto.vars(0));
1412 for (
int i = 1; i < num_vars; ++i) {
1414 union_of_domains.UnionWith(
context->DomainOf(
proto.vars(i)));
1417 const bool is_permutation =
proto.vars_size() == union_of_domains.Size();
1419 if (!is_permutation && !expand_non_permutations)
return;
1424 for (
const ClosedInterval&
interval : union_of_domains) {
1427 std::vector<int> possible_refs;
1428 int fixed_variable_count = 0;
1429 for (
const int ref :
proto.vars()) {
1430 if (!
context->DomainContains(ref, v))
continue;
1431 possible_refs.push_back(ref);
1432 if (
context->DomainOf(ref).Size() == 1) {
1433 fixed_variable_count++;
1437 if (fixed_variable_count > 1) {
1439 return (
void)
context->NotifyThatModelIsUnsat();
1440 }
else if (fixed_variable_count == 1) {
1442 for (
const int ref : possible_refs) {
1443 if (
context->DomainOf(ref).Size() == 1)
continue;
1444 if (!
context->IntersectDomainWith(ref, Domain(v).Complement())) {
1445 VLOG(1) <<
"Empty domain for a variable in ExpandAllDiff()";
1451 LinearConstraintProto* at_most_or_equal_one =
1452 context->working_model->add_constraints()->mutable_linear();
1453 int lb = is_permutation ? 1 : 0;
1455 for (
const int ref : possible_refs) {
1456 DCHECK(
context->DomainContains(ref, v));
1457 DCHECK_GT(
context->DomainOf(ref).Size(), 1);
1458 const int encoding =
context->GetOrCreateVarValueEncoding(ref, v);
1460 at_most_or_equal_one->add_vars(encoding);
1461 at_most_or_equal_one->add_coeffs(1);
1463 at_most_or_equal_one->add_vars(
PositiveRef(encoding));
1464 at_most_or_equal_one->add_coeffs(-1);
1469 at_most_or_equal_one->add_domain(lb);
1470 at_most_or_equal_one->add_domain(ub);
1473 if (is_permutation) {
1474 context->UpdateRuleStats(
"alldiff: permutation expanded");
1476 context->UpdateRuleStats(
"alldiff: expanded");
1484 if (
context->ModelIsUnsat())
return;
1487 context->InitializeNewDomains();
1489 const int num_constraints =
context->working_model->constraints_size();
1490 for (
int i = 0; i < num_constraints; ++i) {
1491 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
1493 switch (
ct->constraint_case()) {
1494 case ConstraintProto::ConstraintCase::kReservoir:
1497 case ConstraintProto::ConstraintCase::kIntMod:
1500 case ConstraintProto::ConstraintCase::kIntProd:
1503 case ConstraintProto::ConstraintCase::kLinMin:
1506 case ConstraintProto::ConstraintCase::kElement:
1507 if (options.
parameters.expand_element_constraints()) {
1511 case ConstraintProto::ConstraintCase::kInverse:
1514 case ConstraintProto::ConstraintCase::kAutomaton:
1515 if (options.
parameters.expand_automaton_constraints()) {
1519 case ConstraintProto::ConstraintCase::kTable:
1520 if (
ct->table().negated()) {
1522 }
else if (options.
parameters.expand_table_constraints()) {
1526 case ConstraintProto::ConstraintCase::kAllDiff:
1527 ExpandAllDiff(options.
parameters.expand_alldiff_constraints(),
ct,
1537 context->UpdateNewConstraintsVariableUsage();
1538 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1539 context->UpdateConstraintVariableUsage(i);
1543 if (
context->ModelIsUnsat())
return;
1547 context->InitializeNewDomains();
1550 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1552 context->working_model->mutable_variables(i));