18 #include "absl/container/flat_hash_map.h"
34 void ExpandReservoir(ConstraintProto*
ct, PresolveContext*
context) {
35 if (
ct->reservoir().min_level() >
ct->reservoir().max_level()) {
36 VLOG(1) <<
"Empty level domain in reservoir constraint.";
37 return (
void)
context->NotifyThatModelIsUnsat();
41 absl::flat_hash_map<std::tuple<int, int, int, int>,
int> precedence_cache;
42 const ReservoirConstraintProto& reservoir =
ct->reservoir();
43 const int num_events = reservoir.times_size();
45 const int true_literal =
context->GetOrCreateConstantVar(1);
47 const auto is_active_literal = [&reservoir, true_literal](
int index) {
48 if (reservoir.actives_size() == 0)
return true_literal;
49 return reservoir.actives(
index);
53 const auto add_reified_precedence = [&
context](
int x_lesseq_y,
int x,
int y,
56 ConstraintProto*
const lesseq =
context->working_model->add_constraints();
57 lesseq->add_enforcement_literal(x_lesseq_y);
58 lesseq->mutable_linear()->add_vars(x);
59 lesseq->mutable_linear()->add_vars(y);
60 lesseq->mutable_linear()->add_coeffs(-1);
61 lesseq->mutable_linear()->add_coeffs(1);
62 lesseq->mutable_linear()->add_domain(0);
63 lesseq->mutable_linear()->add_domain(
kint64max);
64 if (!
context->LiteralIsTrue(l_x)) {
65 context->AddImplication(x_lesseq_y, l_x);
67 if (!
context->LiteralIsTrue(l_y)) {
68 context->AddImplication(x_lesseq_y, l_y);
72 ConstraintProto*
const greater =
context->working_model->add_constraints();
73 greater->mutable_linear()->add_vars(x);
74 greater->mutable_linear()->add_vars(y);
75 greater->mutable_linear()->add_coeffs(-1);
76 greater->mutable_linear()->add_coeffs(1);
77 greater->mutable_linear()->add_domain(
kint64min);
78 greater->mutable_linear()->add_domain(-1);
81 greater->add_enforcement_literal(
NegatedRef(x_lesseq_y));
82 greater->add_enforcement_literal(l_x);
83 greater->add_enforcement_literal(l_y);
86 int num_positives = 0;
87 int num_negatives = 0;
96 if (num_positives > 0 && num_negatives > 0) {
98 for (
int i = 0; i < num_events - 1; ++i) {
99 const int active_i = is_active_literal(i);
100 if (
context->LiteralIsFalse(active_i))
continue;
102 const int time_i = reservoir.times(i);
103 for (
int j = i + 1; j < num_events; ++j) {
104 const int active_j = is_active_literal(j);
105 if (
context->LiteralIsFalse(active_j))
continue;
107 const int time_j = reservoir.times(j);
108 const std::tuple<int, int, int, int> p =
109 std::make_tuple(time_i, time_j, active_i, active_j);
110 const std::tuple<int, int, int, int> rev_p =
111 std::make_tuple(time_j, time_i, active_j, active_i);
114 const int i_lesseq_j =
context->NewBoolVar();
115 context->working_model->mutable_variables(i_lesseq_j)
116 ->set_name(absl::StrCat(i,
" before ", j));
117 precedence_cache[p] = i_lesseq_j;
118 const int j_lesseq_i =
context->NewBoolVar();
119 context->working_model->mutable_variables(j_lesseq_i)
120 ->set_name(absl::StrCat(j,
" before ", i));
122 precedence_cache[rev_p] = j_lesseq_i;
123 add_reified_precedence(i_lesseq_j, time_i, time_j, active_i, active_j);
124 add_reified_precedence(j_lesseq_i, time_j, time_i, active_j, active_i);
127 auto*
const bool_or =
128 context->working_model->add_constraints()->mutable_bool_or();
129 bool_or->add_literals(i_lesseq_j);
130 bool_or->add_literals(j_lesseq_i);
140 for (
int i = 0; i < num_events; ++i) {
141 const int active_i = is_active_literal(i);
142 if (
context->LiteralIsFalse(active_i))
continue;
143 const int time_i = reservoir.times(i);
146 ConstraintProto*
const level =
context->working_model->add_constraints();
147 level->add_enforcement_literal(active_i);
150 for (
int j = 0; j < num_events; ++j) {
151 if (i == j)
continue;
152 const int active_j = is_active_literal(j);
153 if (
context->LiteralIsFalse(active_j))
continue;
155 const int time_j = reservoir.times(j);
158 std::make_tuple(time_j, time_i, active_j, active_i)));
159 level->mutable_linear()->add_coeffs(reservoir.demands(j));
163 const int64 demand_i = reservoir.demands(i);
164 level->mutable_linear()->add_domain(
165 CapSub(reservoir.min_level(), demand_i));
166 level->mutable_linear()->add_domain(
167 CapSub(reservoir.max_level(), demand_i));
173 context->working_model->add_constraints()->mutable_linear();
174 for (
int i = 0; i < num_events; ++i) {
175 sum->add_vars(is_active_literal(i));
176 sum->add_coeffs(reservoir.demands(i));
178 sum->add_domain(reservoir.min_level());
179 sum->add_domain(reservoir.max_level());
185 if (reservoir.min_level() > 0 || reservoir.max_level() < 0) {
186 auto*
const sum_at_zero =
187 context->working_model->add_constraints()->mutable_linear();
188 for (
int i = 0; i < num_events; ++i) {
189 const int active_i = is_active_literal(i);
190 if (
context->LiteralIsFalse(active_i))
continue;
192 const int time_i = reservoir.times(i);
193 const int lesseq_0 =
context->NewBoolVar();
196 ConstraintProto*
const lesseq =
context->working_model->add_constraints();
197 lesseq->add_enforcement_literal(lesseq_0);
198 lesseq->mutable_linear()->add_vars(time_i);
199 lesseq->mutable_linear()->add_coeffs(1);
200 lesseq->mutable_linear()->add_domain(
kint64min);
201 lesseq->mutable_linear()->add_domain(0);
203 if (!
context->LiteralIsTrue(active_i)) {
204 context->AddImplication(lesseq_0, active_i);
208 ConstraintProto*
const greater =
209 context->working_model->add_constraints();
210 greater->add_enforcement_literal(
NegatedRef(lesseq_0));
211 greater->add_enforcement_literal(active_i);
212 greater->mutable_linear()->add_vars(time_i);
213 greater->mutable_linear()->add_coeffs(1);
214 greater->mutable_linear()->add_domain(1);
215 greater->mutable_linear()->add_domain(
kint64max);
218 sum_at_zero->add_vars(lesseq_0);
219 sum_at_zero->add_coeffs(reservoir.demands(i));
221 sum_at_zero->add_domain(reservoir.min_level());
222 sum_at_zero->add_domain(reservoir.max_level());
226 context->UpdateRuleStats(
"reservoir: expanded");
231 void ExpandIntDiv(ConstraintProto*
ct, PresolveContext*
context) {
232 const int divisor =
ct->int_div().vars(1);
233 if (!
context->IntersectDomainWith(divisor, Domain(0).Complement())) {
234 return (
void)
context->NotifyThatModelIsUnsat();
238 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
239 const IntegerArgumentProto& int_mod =
ct->int_mod();
240 const int var = int_mod.vars(0);
241 const int mod_var = int_mod.vars(1);
242 const int target_var = int_mod.target();
253 context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb));
255 auto add_enforcement_literal_if_needed = [&]() {
256 if (
ct->enforcement_literal_size() == 0)
return;
257 const int literal =
ct->enforcement_literal(0);
258 ConstraintProto*
const last =
context->working_model->mutable_constraints(
259 context->working_model->constraints_size() - 1);
260 last->add_enforcement_literal(
literal);
264 IntegerArgumentProto*
const div_proto =
265 context->working_model->add_constraints()->mutable_int_div();
266 div_proto->set_target(div_var);
267 div_proto->add_vars(
var);
268 div_proto->add_vars(mod_var);
269 add_enforcement_literal_if_needed();
272 if (mod_lb == mod_ub) {
274 LinearConstraintProto*
const lin =
275 context->working_model->add_constraints()->mutable_linear();
276 lin->add_vars(int_mod.vars(0));
278 lin->add_vars(div_var);
279 lin->add_coeffs(-mod_lb);
280 lin->add_vars(target_var);
284 add_enforcement_literal_if_needed();
287 const int prod_var =
context->NewIntVar(
288 Domain(var_lb * mod_lb / mod_ub, var_ub * mod_ub / mod_lb));
289 IntegerArgumentProto*
const int_prod =
290 context->working_model->add_constraints()->mutable_int_prod();
291 int_prod->set_target(prod_var);
292 int_prod->add_vars(div_var);
293 int_prod->add_vars(mod_var);
294 add_enforcement_literal_if_needed();
297 LinearConstraintProto*
const lin =
298 context->working_model->add_constraints()->mutable_linear();
301 lin->add_vars(prod_var);
303 lin->add_vars(target_var);
307 add_enforcement_literal_if_needed();
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);
333 void AddXEqualYOrXEqualZero(
int x_eq_y,
int x,
int y,
335 ConstraintProto* equality =
context->working_model->add_constraints();
336 equality->add_enforcement_literal(x_eq_y);
337 equality->mutable_linear()->add_vars(x);
338 equality->mutable_linear()->add_coeffs(1);
339 equality->mutable_linear()->add_vars(y);
340 equality->mutable_linear()->add_coeffs(-1);
341 equality->mutable_linear()->add_domain(0);
342 equality->mutable_linear()->add_domain(0);
347 void ExpandIntProdWithOneAcrossZero(
int a_ref,
int b_ref,
int product_ref,
354 const int a_is_positive =
context->NewBoolVar();
357 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
358 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
360 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
364 const bool b_is_positive =
context->MinOf(b_ref) >= 0;
365 const Domain pos_a_product_domain =
366 b_is_positive ? Domain({0,
context->MaxOf(product_ref)})
367 : Domain({
context->MinOf(product_ref), 0});
368 const int pos_a_product =
context->NewIntVar(pos_a_product_domain);
369 IntegerArgumentProto* pos_product =
370 context->working_model->add_constraints()->mutable_int_prod();
371 pos_product->set_target(pos_a_product);
372 pos_product->add_vars(pos_a_ref);
373 pos_product->add_vars(b_ref);
376 const Domain neg_a_product_domain =
377 b_is_positive ? Domain({
context->MinOf(product_ref), 0})
378 : Domain({0,
context->MaxOf(product_ref)});
379 const int neg_a_product =
context->NewIntVar(neg_a_product_domain);
380 IntegerArgumentProto* neg_product =
381 context->working_model->add_constraints()->mutable_int_prod();
382 neg_product->set_target(neg_a_product);
383 neg_product->add_vars(neg_a_ref);
384 neg_product->add_vars(b_ref);
387 LinearConstraintProto* lin =
388 context->working_model->add_constraints()->mutable_linear();
389 lin->add_vars(product_ref);
391 lin->add_vars(pos_a_product);
393 lin->add_vars(neg_a_product);
399 void ExpandIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
int product_ref,
402 const int a_is_positive =
context->NewBoolVar();
408 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
409 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
411 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
415 const int pos_product_ref =
417 ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref,
context);
418 const int neg_product_ref =
420 ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref,
context);
423 LinearConstraintProto* lin =
424 context->working_model->add_constraints()->mutable_linear();
425 lin->add_vars(product_ref);
427 lin->add_vars(pos_product_ref);
429 lin->add_vars(neg_product_ref);
435 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
436 const IntegerArgumentProto& int_prod =
ct->int_prod();
437 if (int_prod.vars_size() != 2)
return;
438 const int a = int_prod.vars(0);
439 const int b = int_prod.vars(1);
440 const int p = int_prod.target();
441 const bool a_is_boolean =
443 const bool b_is_boolean =
448 if (a_is_boolean && !b_is_boolean) {
449 ExpandIntProdWithBoolean(
a,
b, p,
context);
451 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
454 if (b_is_boolean && !a_is_boolean) {
455 ExpandIntProdWithBoolean(
b,
a, p,
context);
457 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
461 const bool a_span_across_zero =
463 const bool b_span_across_zero =
465 if (a_span_across_zero && !b_span_across_zero) {
466 ExpandIntProdWithOneAcrossZero(
a,
b, p,
context);
469 "int_prod: expanded product with general integer variables");
472 if (!a_span_across_zero && b_span_across_zero) {
473 ExpandIntProdWithOneAcrossZero(
b,
a, p,
context);
476 "int_prod: expanded product with general integer variables");
479 if (a_span_across_zero && b_span_across_zero) {
480 ExpandIntProdWithTwoAcrossZero(
a,
b, p,
context);
483 "int_prod: expanded product with general integer variables");
488 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
489 const int size =
ct->inverse().f_direct().size();
490 CHECK_EQ(size,
ct->inverse().f_inverse().size());
496 for (
const int ref :
ct->inverse().f_direct()) {
497 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
498 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
502 for (
const int ref :
ct->inverse().f_inverse()) {
503 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
504 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
511 std::vector<int64> possible_values;
515 const auto filter_inverse_domain = [
context, size, &possible_values](
517 const auto& inverse) {
519 for (
int i = 0; i < size; ++i) {
520 possible_values.clear();
521 const Domain domain =
context->DomainOf(direct[i]);
522 bool removed_value =
false;
523 for (
const ClosedInterval&
interval : domain) {
525 if (
context->DomainOf(inverse[j]).Contains(i)) {
526 possible_values.push_back(j);
528 removed_value =
true;
533 if (!
context->IntersectDomainWith(
535 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
543 if (!filter_inverse_domain(
ct->inverse().f_direct(),
544 ct->inverse().f_inverse())) {
548 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
549 ct->inverse().f_direct())) {
555 for (
int i = 0; i < size; ++i) {
556 const int f_i =
ct->inverse().f_direct(i);
557 const Domain domain =
context->DomainOf(f_i);
558 for (
const ClosedInterval&
interval : domain) {
561 const int r_j =
ct->inverse().f_inverse(j);
563 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
564 context->InsertVarValueEncoding(r_j_i, f_i, j);
566 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
567 context->InsertVarValueEncoding(f_i_j, r_j, i);
574 context->UpdateRuleStats(
"inverse: expanded");
577 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
578 const ElementConstraintProto& element =
ct->element();
579 const int index_ref = element.index();
580 const int target_ref = element.target();
581 const int size = element.vars_size();
583 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
584 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
585 return (
void)
context->NotifyThatModelIsUnsat();
588 bool all_constants =
true;
589 absl::flat_hash_map<int64, int> constant_var_values_usage;
590 std::vector<int64> constant_var_values;
591 std::vector<int64> invalid_indices;
592 Domain index_domain =
context->DomainOf(index_ref);
593 Domain target_domain =
context->DomainOf(target_ref);
594 for (
const ClosedInterval&
interval : index_domain) {
596 const int var = element.vars(v);
597 const Domain var_domain =
context->DomainOf(
var);
598 if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
599 invalid_indices.push_back(v);
602 if (var_domain.Min() != var_domain.Max()) {
603 all_constants =
false;
608 if (constant_var_values_usage[
value]++ == 0) {
609 constant_var_values.push_back(
value);
614 if (!invalid_indices.empty() && target_ref != index_ref) {
615 if (!
context->IntersectDomainWith(
617 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
618 return (
void)
context->NotifyThatModelIsUnsat();
622 index_domain =
context->DomainOf(index_ref);
630 absl::flat_hash_map<int64, BoolArgumentProto*> supports;
631 if (all_constants && target_ref != index_ref) {
632 if (!
context->IntersectDomainWith(
634 VLOG(1) <<
"Empty domain for the target variable in ExpandElement()";
638 target_domain =
context->DomainOf(target_ref);
639 if (target_domain.Size() == 1) {
640 context->UpdateRuleStats(
"element: one value array");
645 for (
const ClosedInterval&
interval : target_domain) {
649 const int lit =
context->GetOrCreateVarValueEncoding(target_ref, v);
650 BoolArgumentProto*
const support =
651 context->working_model->add_constraints()->mutable_bool_or();
652 supports[v] = support;
661 auto* bool_or =
context->working_model->add_constraints()->mutable_bool_or();
663 for (
const ClosedInterval&
interval : index_domain) {
665 const int var = element.vars(v);
666 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
667 const Domain var_domain =
context->DomainOf(
var);
669 bool_or->add_literals(index_lit);
671 if (target_ref == index_ref) {
674 context->AddImplyInDomain(index_lit,
var, Domain(v));
675 }
else if (target_domain.Size() == 1) {
678 context->AddImplyInDomain(index_lit,
var, target_domain);
679 }
else if (var_domain.Size() == 1) {
682 if (constant_var_values_usage[
value] > 1) {
685 const int target_lit =
686 context->GetOrCreateVarValueEncoding(target_ref,
value);
687 context->AddImplication(index_lit, target_lit);
691 context->InsertVarValueEncoding(index_lit, target_ref,
value);
694 context->AddImplyInDomain(index_lit, target_ref, var_domain);
697 ConstraintProto*
const ct =
context->working_model->add_constraints();
698 ct->add_enforcement_literal(index_lit);
699 ct->mutable_linear()->add_vars(
var);
700 ct->mutable_linear()->add_coeffs(1);
701 ct->mutable_linear()->add_vars(target_ref);
702 ct->mutable_linear()->add_coeffs(-1);
703 ct->mutable_linear()->add_domain(0);
704 ct->mutable_linear()->add_domain(0);
710 const int64 var_min = target_domain.Min();
715 for (
const auto it : constant_var_values_usage) {
716 if (it.second > usage ||
717 (it.second == usage && it.first < most_frequent_value)) {
719 most_frequent_value = it.first;
731 usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
732 if (base != var_min) {
733 VLOG(3) <<
"expand element: choose " << base <<
" with usage " << usage
734 <<
" over " << var_min <<
" among " << size <<
" values.";
737 LinearConstraintProto*
const linear =
738 context->working_model->add_constraints()->mutable_linear();
740 linear->add_vars(target_ref);
741 linear->add_coeffs(-1);
742 for (
const ClosedInterval&
interval : index_domain) {
744 const int ref = element.vars(v);
745 const int index_lit =
746 context->GetOrCreateVarValueEncoding(index_ref, v);
749 linear->add_vars(index_lit);
750 linear->add_coeffs(
delta);
753 linear->add_coeffs(-
delta);
758 linear->add_domain(rhs);
759 linear->add_domain(rhs);
761 context->UpdateRuleStats(
"element: expanded value element");
763 context->UpdateRuleStats(
"element: expanded");
770 void LinkLiteralsAndValues(
771 const std::vector<int>& value_literals,
const std::vector<int64>& values,
772 const absl::flat_hash_map<int64, int>& target_encoding,
774 CHECK_EQ(value_literals.size(), values.size());
778 std::map<int, std::vector<int>> value_literals_per_target_literal;
783 for (
int i = 0; i < values.size(); ++i) {
784 const int64 v = values[i];
785 CHECK(target_encoding.contains(v));
787 value_literals_per_target_literal[lit].push_back(value_literals[i]);
792 for (
const auto& it : value_literals_per_target_literal) {
793 const int target_literal = it.first;
794 switch (it.second.size()) {
796 if (!
context->SetLiteralToFalse(target_literal)) {
802 context->StoreBooleanEqualityRelation(target_literal,
807 BoolArgumentProto*
const bool_or =
808 context->working_model->add_constraints()->mutable_bool_or();
809 bool_or->add_literals(
NegatedRef(target_literal));
810 for (
const int value_literal : it.second) {
811 bool_or->add_literals(value_literal);
812 context->AddImplication(value_literal, target_literal);
819 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
820 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
822 if (
proto.vars_size() == 0) {
823 const int64 initial_state =
proto.starting_state();
824 for (
const int64 final_state :
proto.final_states()) {
825 if (initial_state == final_state) {
826 context->UpdateRuleStats(
"automaton: empty constraint");
832 return (
void)
context->NotifyThatModelIsUnsat();
833 }
else if (
proto.transition_label_size() == 0) {
835 return (
void)
context->NotifyThatModelIsUnsat();
838 const int n =
proto.vars_size();
839 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
842 const absl::flat_hash_set<int64> final_states(
843 {
proto.final_states().begin(),
proto.final_states().end()});
844 std::vector<absl::flat_hash_set<int64>> reachable_states(n + 1);
845 reachable_states[0].insert(
proto.starting_state());
849 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
851 const int64 label =
proto.transition_label(t);
853 if (!reachable_states[
time].contains(
tail))
continue;
854 if (!
context->DomainContains(vars[
time], label))
continue;
855 if (
time == n - 1 && !final_states.contains(
head))
continue;
856 reachable_states[
time + 1].insert(
head);
862 absl::flat_hash_set<int64> new_set;
863 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
865 const int64 label =
proto.transition_label(t);
868 if (!reachable_states[
time].contains(
tail))
continue;
869 if (!
context->DomainContains(vars[
time], label))
continue;
870 if (!reachable_states[
time + 1].contains(
head))
continue;
871 new_set.insert(
tail);
873 reachable_states[
time].swap(new_set);
881 absl::flat_hash_map<int64, int> encoding;
882 absl::flat_hash_map<int64, int> in_encoding;
883 absl::flat_hash_map<int64, int> out_encoding;
884 bool removed_values =
false;
890 std::vector<int64> in_states;
891 std::vector<int64> transition_values;
892 std::vector<int64> out_states;
893 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
895 const int64 label =
proto.transition_label(i);
898 if (!reachable_states[
time].contains(
tail))
continue;
899 if (!reachable_states[
time + 1].contains(
head))
continue;
900 if (!
context->DomainContains(vars[
time], label))
continue;
905 in_states.push_back(
tail);
906 transition_values.push_back(label);
910 out_states.push_back(
time + 1 == n ? 0 :
head);
913 std::vector<int> tuple_literals;
914 if (transition_values.size() == 1) {
915 bool tmp_removed_values =
false;
916 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
919 Domain(transition_values.front()),
920 &tmp_removed_values)) {
921 return (
void)
context->NotifyThatModelIsUnsat();
925 }
else if (transition_values.size() == 2) {
926 const int bool_var =
context->NewBoolVar();
927 tuple_literals.push_back(bool_var);
928 tuple_literals.push_back(
NegatedRef(bool_var));
933 LinearConstraintProto*
const exactly_one =
934 context->working_model->add_constraints()->mutable_linear();
935 exactly_one->add_domain(1);
936 exactly_one->add_domain(1);
937 for (
int i = 0; i < transition_values.size(); ++i) {
938 const int tuple_literal =
context->NewBoolVar();
939 tuple_literals.push_back(tuple_literal);
940 exactly_one->add_vars(tuple_literal);
941 exactly_one->add_coeffs(1);
947 std::vector<int64> s = transition_values;
953 return (
void)
context->NotifyThatModelIsUnsat();
959 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
966 std::vector<int64> s = out_states;
969 out_encoding.clear();
972 out_encoding[s.front()] =
var;
974 }
else if (s.size() > 2) {
975 for (
const int64 state : s) {
976 out_encoding[state] =
context->NewBoolVar();
981 if (!in_encoding.empty()) {
982 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
984 if (!encoding.empty()) {
985 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
988 if (!out_encoding.empty()) {
989 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
991 in_encoding.swap(out_encoding);
992 out_encoding.clear();
995 if (removed_values) {
996 context->UpdateRuleStats(
"automaton: reduced variable domains");
998 context->UpdateRuleStats(
"automaton: expanded");
1002 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
1003 TableConstraintProto& table = *
ct->mutable_table();
1004 const int num_vars = table.vars_size();
1005 const int num_original_tuples = table.values_size() / num_vars;
1006 std::vector<std::vector<int64>> tuples(num_original_tuples);
1008 for (
int i = 0; i < num_original_tuples; ++i) {
1009 for (
int j = 0; j < num_vars; ++j) {
1010 tuples[i].push_back(table.values(count++));
1014 if (tuples.empty()) {
1015 context->UpdateRuleStats(
"table: empty negated constraint");
1022 std::vector<int64> domain_sizes;
1023 for (
int i = 0; i < num_vars; ++i) {
1024 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
1029 std::vector<int> clause;
1030 for (
const std::vector<int64>& tuple : tuples) {
1032 for (
int i = 0; i < num_vars; ++i) {
1034 if (
value == any_value)
continue;
1037 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
1040 if (!clause.empty()) {
1041 BoolArgumentProto* bool_or =
1042 context->working_model->add_constraints()->mutable_bool_or();
1043 for (
const int lit : clause) {
1044 bool_or->add_literals(lit);
1048 context->UpdateRuleStats(
"table: expanded negated constraint");
1052 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
1053 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
1054 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
1055 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
1060 lin_max->mutable_lin_max()->mutable_target());
1062 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
1063 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
1074 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
1075 const std::vector<int64>& values,
int variable,
1076 const std::vector<int>& tuples_with_any,
1078 VLOG(2) <<
"Process var(" << variable <<
") with domain "
1079 <<
context->DomainOf(variable) <<
" and " << values.size()
1080 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
1081 CHECK_EQ(tuple_literals.size(), values.size());
1082 std::vector<std::pair<int64, int>> pairs;
1085 for (
int i = 0; i < values.size(); ++i) {
1088 pairs.emplace_back(
value, tuple_literals[i]);
1093 std::vector<int> selected;
1094 std::sort(pairs.begin(), pairs.end());
1095 for (
int i = 0; i < pairs.size();) {
1098 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1099 selected.push_back(pairs[i].second);
1102 CHECK(!selected.empty() || !tuples_with_any.empty());
1103 if (selected.size() == 1 && tuples_with_any.empty()) {
1104 context->InsertVarValueEncoding(selected.front(), variable,
value);
1106 const int value_literal =
1108 BoolArgumentProto* no_support =
1109 context->working_model->add_constraints()->mutable_bool_or();
1110 for (
const int lit : selected) {
1111 no_support->add_literals(lit);
1112 context->AddImplication(lit, value_literal);
1114 for (
const int lit : tuples_with_any) {
1115 no_support->add_literals(lit);
1119 no_support->add_literals(
NegatedRef(value_literal));
1125 void AddSizeTwoTable(
1126 const std::vector<int>& vars,
const std::vector<std::vector<int64>>& tuples,
1127 const std::vector<absl::flat_hash_set<int64>>& values_per_var,
1130 const int left_var = vars[0];
1131 const int right_var = vars[1];
1132 if (
context->DomainOf(left_var).Size() == 1 ||
1133 context->DomainOf(right_var).Size() == 1) {
1139 std::map<int, std::vector<int>> left_to_right;
1140 std::map<int, std::vector<int>> right_to_left;
1142 for (
const auto& tuple : tuples) {
1143 const int64 left_value(tuple[0]);
1144 const int64 right_value(tuple[1]);
1146 CHECK(
context->DomainContains(right_var, right_value));
1148 const int left_literal =
1149 context->GetOrCreateVarValueEncoding(left_var, left_value);
1150 const int right_literal =
1151 context->GetOrCreateVarValueEncoding(right_var, right_value);
1152 left_to_right[left_literal].push_back(right_literal);
1153 right_to_left[right_literal].push_back(left_literal);
1156 int num_implications = 0;
1157 int num_clause_added = 0;
1158 int num_large_clause_added = 0;
1159 auto add_support_constraint =
1160 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1161 int lit,
const std::vector<int>& support_literals,
1162 int max_support_size) {
1163 if (support_literals.size() == max_support_size)
return;
1164 if (support_literals.size() == 1) {
1165 context->AddImplication(lit, support_literals.front());
1168 BoolArgumentProto* bool_or =
1169 context->working_model->add_constraints()->mutable_bool_or();
1170 for (
const int support_literal : support_literals) {
1171 bool_or->add_literals(support_literal);
1175 if (support_literals.size() > max_support_size / 2) {
1176 num_large_clause_added++;
1181 for (
const auto& it : left_to_right) {
1182 add_support_constraint(it.first, it.second, values_per_var[1].size());
1184 for (
const auto& it : right_to_left) {
1185 add_support_constraint(it.first, it.second, values_per_var[0].size());
1187 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1188 << num_clause_added <<
" clauses, including "
1189 << num_large_clause_added <<
" large clauses, " << num_implications
1193 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1194 const TableConstraintProto& table =
ct->table();
1195 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1196 const int num_vars = table.vars_size();
1197 const int num_original_tuples = table.values_size() / num_vars;
1200 std::vector<std::vector<int64>> tuples(num_original_tuples);
1202 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1203 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1204 tuples[tuple_index].push_back(table.values(count++));
1210 std::vector<absl::flat_hash_set<int64>> values_per_var(num_vars);
1212 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1214 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1215 const int64 value = tuples[tuple_index][var_index];
1216 if (!
context->DomainContains(vars[var_index],
value)) {
1222 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1223 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1225 std::swap(tuples[tuple_index], tuples[new_size]);
1229 tuples.resize(new_size);
1230 const int num_valid_tuples = tuples.size();
1232 if (tuples.empty()) {
1233 context->UpdateRuleStats(
"table: empty");
1234 return (
void)
context->NotifyThatModelIsUnsat();
1240 int num_fixed_variables = 0;
1241 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1245 values_per_var[var_index].end()})));
1246 if (
context->DomainOf(vars[var_index]).Size() == 1) {
1247 num_fixed_variables++;
1251 if (num_fixed_variables == num_vars - 1) {
1252 context->UpdateRuleStats(
"table: one variable not fixed");
1255 }
else if (num_fixed_variables == num_vars) {
1256 context->UpdateRuleStats(
"table: all variables fixed");
1262 if (num_vars == 2) {
1263 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1265 "table: expanded positive constraint with two variables");
1272 int num_prefix_tuples = 0;
1274 absl::flat_hash_set<absl::Span<const int64>> prefixes;
1275 for (
const std::vector<int64>& tuple : tuples) {
1276 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1278 num_prefix_tuples = prefixes.size();
1285 std::vector<int64> domain_sizes;
1286 for (
int i = 0; i < num_vars; ++i) {
1287 domain_sizes.push_back(values_per_var[i].size());
1290 const int num_compressed_tuples = tuples.size();
1292 if (num_compressed_tuples == 1) {
1294 context->UpdateRuleStats(
"table: one tuple");
1300 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1301 if (prefixes_are_all_different) {
1303 "TODO table: last value implied by previous values");
1313 int64 max_num_prefix_tuples = 1;
1314 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1315 max_num_prefix_tuples =
1316 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1320 absl::StrCat(
"Table: ", num_vars,
1321 " variables, original tuples = ", num_original_tuples);
1322 if (num_valid_tuples != num_original_tuples) {
1323 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1325 if (prefixes_are_all_different) {
1326 if (num_prefix_tuples < max_num_prefix_tuples) {
1327 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1328 max_num_prefix_tuples);
1330 absl::StrAppend(&
message,
", full prefix = true");
1333 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1335 if (num_compressed_tuples != num_valid_tuples) {
1337 ", compressed tuples = ", num_compressed_tuples);
1343 if (num_compressed_tuples == 2) {
1344 context->UpdateRuleStats(
"TODO table: two tuples");
1356 std::vector<int> tuple_literals(num_compressed_tuples);
1357 BoolArgumentProto* at_least_one_tuple =
1358 context->working_model->add_constraints()->mutable_bool_or();
1371 BoolArgumentProto* at_most_one_tuple =
nullptr;
1372 if (
context->keep_all_feasible_solutions) {
1374 context->working_model->add_constraints()->mutable_at_most_one();
1377 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1378 tuple_literals[var_index] =
context->NewBoolVar();
1379 at_least_one_tuple->add_literals(tuple_literals[var_index]);
1380 if (at_most_one_tuple !=
nullptr) {
1381 at_most_one_tuple->add_literals(tuple_literals[var_index]);
1385 std::vector<int> active_tuple_literals;
1386 std::vector<int64> active_values;
1387 std::vector<int> any_tuple_literals;
1388 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1389 if (values_per_var[var_index].size() == 1)
continue;
1391 active_tuple_literals.clear();
1392 active_values.clear();
1393 any_tuple_literals.clear();
1394 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1396 const int64 value = tuples[tuple_index][var_index];
1397 const int tuple_literal = tuple_literals[tuple_index];
1399 if (
value == any_value) {
1400 any_tuple_literals.push_back(tuple_literal);
1402 active_tuple_literals.push_back(tuple_literal);
1403 active_values.push_back(
value);
1407 if (!active_tuple_literals.empty()) {
1408 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1413 context->UpdateRuleStats(
"table: expanded positive constraint");
1417 void ExpandAllDiff(
bool expand_non_permutations, ConstraintProto*
ct,
1419 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1420 if (
proto.vars_size() <= 2)
return;
1422 const int num_vars =
proto.vars_size();
1424 Domain union_of_domains =
context->DomainOf(
proto.vars(0));
1425 for (
int i = 1; i < num_vars; ++i) {
1427 union_of_domains.UnionWith(
context->DomainOf(
proto.vars(i)));
1430 const bool is_permutation =
proto.vars_size() == union_of_domains.Size();
1432 if (!is_permutation && !expand_non_permutations)
return;
1437 for (
const ClosedInterval&
interval : union_of_domains) {
1440 std::vector<int> possible_refs;
1441 int fixed_variable_count = 0;
1442 for (
const int ref :
proto.vars()) {
1443 if (!
context->DomainContains(ref, v))
continue;
1444 possible_refs.push_back(ref);
1445 if (
context->DomainOf(ref).Size() == 1) {
1446 fixed_variable_count++;
1450 if (fixed_variable_count > 1) {
1452 return (
void)
context->NotifyThatModelIsUnsat();
1453 }
else if (fixed_variable_count == 1) {
1455 for (
const int ref : possible_refs) {
1456 if (
context->DomainOf(ref).Size() == 1)
continue;
1457 if (!
context->IntersectDomainWith(ref, Domain(v).Complement())) {
1458 VLOG(1) <<
"Empty domain for a variable in ExpandAllDiff()";
1464 LinearConstraintProto* at_most_or_equal_one =
1465 context->working_model->add_constraints()->mutable_linear();
1466 int lb = is_permutation ? 1 : 0;
1468 for (
const int ref : possible_refs) {
1471 const int encoding =
context->GetOrCreateVarValueEncoding(ref, v);
1473 at_most_or_equal_one->add_vars(encoding);
1474 at_most_or_equal_one->add_coeffs(1);
1476 at_most_or_equal_one->add_vars(
PositiveRef(encoding));
1477 at_most_or_equal_one->add_coeffs(-1);
1482 at_most_or_equal_one->add_domain(lb);
1483 at_most_or_equal_one->add_domain(ub);
1486 if (is_permutation) {
1487 context->UpdateRuleStats(
"alldiff: permutation expanded");
1489 context->UpdateRuleStats(
"alldiff: expanded");
1497 if (
context->ModelIsUnsat())
return;
1500 context->InitializeNewDomains();
1502 const int num_constraints =
context->working_model->constraints_size();
1503 for (
int i = 0; i < num_constraints; ++i) {
1504 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
1506 switch (
ct->constraint_case()) {
1507 case ConstraintProto::ConstraintCase::kReservoir:
1510 case ConstraintProto::ConstraintCase::kIntMod:
1513 case ConstraintProto::ConstraintCase::kIntDiv:
1516 case ConstraintProto::ConstraintCase::kIntProd:
1519 case ConstraintProto::ConstraintCase::kLinMin:
1522 case ConstraintProto::ConstraintCase::kElement:
1523 if (options.
parameters.expand_element_constraints()) {
1527 case ConstraintProto::ConstraintCase::kInverse:
1530 case ConstraintProto::ConstraintCase::kAutomaton:
1531 if (options.
parameters.expand_automaton_constraints()) {
1535 case ConstraintProto::ConstraintCase::kTable:
1536 if (
ct->table().negated()) {
1538 }
else if (options.
parameters.expand_table_constraints()) {
1542 case ConstraintProto::ConstraintCase::kAllDiff:
1543 ExpandAllDiff(options.
parameters.expand_alldiff_constraints(),
ct,
1553 context->UpdateNewConstraintsVariableUsage();
1554 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1555 context->UpdateConstraintVariableUsage(i);
1559 if (
context->ModelIsUnsat()) {
1561 LOG(
INFO) <<
"UNSAT after expansion of "
1569 context->InitializeNewDomains();
1572 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1574 context->working_model->mutable_variables(i));