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 ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
321 const IntegerArgumentProto& int_prod =
ct->int_prod();
322 if (int_prod.vars_size() != 2)
return;
323 const int a = int_prod.vars(0);
324 const int b = int_prod.vars(1);
325 const int p = int_prod.target();
326 const bool a_is_boolean =
328 const bool b_is_boolean =
333 if (a_is_boolean && !b_is_boolean) {
334 ExpandIntProdWithBoolean(
a,
b, p,
context);
336 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
337 }
else if (b_is_boolean && !a_is_boolean) {
338 ExpandIntProdWithBoolean(
b,
a, p,
context);
340 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
344 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
345 const int size =
ct->inverse().f_direct().size();
346 CHECK_EQ(size,
ct->inverse().f_inverse().size());
352 for (
const int ref :
ct->inverse().f_direct()) {
353 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
354 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
358 for (
const int ref :
ct->inverse().f_inverse()) {
359 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
360 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
367 std::vector<int64> possible_values;
371 const auto filter_inverse_domain = [
context, size, &possible_values](
372 const google::protobuf::RepeatedField<int>& direct,
373 const google::protobuf::RepeatedField<int>& inverse) {
375 for (
int i = 0; i < size; ++i) {
376 possible_values.clear();
377 const Domain domain =
context->DomainOf(direct[i]);
378 bool removed_value =
false;
379 for (
const ClosedInterval&
interval : domain) {
381 if (
context->DomainOf(inverse[j]).Contains(i)) {
382 possible_values.push_back(j);
384 removed_value =
true;
389 if (!
context->IntersectDomainWith(
391 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
399 if (!filter_inverse_domain(
ct->inverse().f_direct(),
400 ct->inverse().f_inverse())) {
404 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
405 ct->inverse().f_direct())) {
411 for (
int i = 0; i < size; ++i) {
412 const int f_i =
ct->inverse().f_direct(i);
413 const Domain domain =
context->DomainOf(f_i);
414 for (
const ClosedInterval&
interval : domain) {
417 const int r_j =
ct->inverse().f_inverse(j);
419 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
420 context->InsertVarValueEncoding(r_j_i, f_i, j);
422 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
423 context->InsertVarValueEncoding(f_i_j, r_j, i);
430 context->UpdateRuleStats(
"inverse: expanded");
433 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
434 const ElementConstraintProto& element =
ct->element();
435 const int index_ref = element.index();
436 const int target_ref = element.target();
437 const int size = element.vars_size();
439 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
440 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
441 return (
void)
context->NotifyThatModelIsUnsat();
444 bool all_constants =
true;
445 absl::flat_hash_map<int64, int> constant_var_values_usage;
446 std::vector<int64> constant_var_values;
447 std::vector<int64> invalid_indices;
448 Domain index_domain =
context->DomainOf(index_ref);
449 Domain target_domain =
context->DomainOf(target_ref);
450 for (
const ClosedInterval&
interval : index_domain) {
452 const int var = element.vars(v);
453 const Domain var_domain =
context->DomainOf(
var);
454 if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
455 invalid_indices.push_back(v);
458 if (var_domain.Min() != var_domain.Max()) {
459 all_constants =
false;
464 if (constant_var_values_usage[
value]++ == 0) {
465 constant_var_values.push_back(
value);
470 if (!invalid_indices.empty() && target_ref != index_ref) {
471 if (!
context->IntersectDomainWith(
473 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
474 return (
void)
context->NotifyThatModelIsUnsat();
478 index_domain =
context->DomainOf(index_ref);
486 absl::flat_hash_map<int64, BoolArgumentProto*> supports;
487 if (all_constants && target_ref != index_ref) {
488 if (!
context->IntersectDomainWith(
490 VLOG(1) <<
"Empty domain for the target variable in ExpandElement()";
494 target_domain =
context->DomainOf(target_ref);
495 if (target_domain.Size() == 1) {
496 context->UpdateRuleStats(
"element: one value array");
501 for (
const ClosedInterval&
interval : target_domain) {
505 const int lit =
context->GetOrCreateVarValueEncoding(target_ref, v);
506 BoolArgumentProto*
const support =
507 context->working_model->add_constraints()->mutable_bool_or();
508 supports[v] = support;
517 auto* bool_or =
context->working_model->add_constraints()->mutable_bool_or();
519 for (
const ClosedInterval&
interval : index_domain) {
521 const int var = element.vars(v);
522 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
523 const Domain var_domain =
context->DomainOf(
var);
525 bool_or->add_literals(index_lit);
527 if (target_ref == index_ref) {
530 context->AddImplyInDomain(index_lit,
var, Domain(v));
531 }
else if (target_domain.Size() == 1) {
534 context->AddImplyInDomain(index_lit,
var, target_domain);
535 }
else if (var_domain.Size() == 1) {
538 if (constant_var_values_usage[
value] > 1) {
541 const int target_lit =
542 context->GetOrCreateVarValueEncoding(target_ref,
value);
543 context->AddImplication(index_lit, target_lit);
547 context->InsertVarValueEncoding(index_lit, target_ref,
value);
550 context->AddImplyInDomain(index_lit, target_ref, var_domain);
553 ConstraintProto*
const ct =
context->working_model->add_constraints();
554 ct->add_enforcement_literal(index_lit);
555 ct->mutable_linear()->add_vars(
var);
556 ct->mutable_linear()->add_coeffs(1);
557 ct->mutable_linear()->add_vars(target_ref);
558 ct->mutable_linear()->add_coeffs(-1);
559 ct->mutable_linear()->add_domain(0);
560 ct->mutable_linear()->add_domain(0);
566 const int64 var_min = target_domain.Min();
571 for (
const auto it : constant_var_values_usage) {
572 if (it.second > usage ||
573 (it.second == usage && it.first < most_frequent_value)) {
575 most_frequent_value = it.first;
587 usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
588 if (base != var_min) {
589 VLOG(3) <<
"expand element: choose " << base <<
" with usage " << usage
590 <<
" over " << var_min <<
" among " << size <<
" values.";
593 LinearConstraintProto*
const linear =
594 context->working_model->add_constraints()->mutable_linear();
596 linear->add_vars(target_ref);
597 linear->add_coeffs(-1);
598 for (
const ClosedInterval&
interval : index_domain) {
600 const int ref = element.vars(v);
601 const int index_lit =
602 context->GetOrCreateVarValueEncoding(index_ref, v);
605 linear->add_vars(index_lit);
606 linear->add_coeffs(
delta);
609 linear->add_coeffs(-
delta);
614 linear->add_domain(rhs);
615 linear->add_domain(rhs);
617 context->UpdateRuleStats(
"element: expanded value element");
619 context->UpdateRuleStats(
"element: expanded");
626 void LinkLiteralsAndValues(
627 const std::vector<int>& value_literals,
const std::vector<int64>& values,
628 const absl::flat_hash_map<int64, int>& target_encoding,
630 CHECK_EQ(value_literals.size(), values.size());
634 std::map<int, std::vector<int>> value_literals_per_target_literal;
639 for (
int i = 0; i < values.size(); ++i) {
640 const int64 v = values[i];
641 CHECK(target_encoding.contains(v));
643 value_literals_per_target_literal[lit].push_back(value_literals[i]);
648 for (
const auto& it : value_literals_per_target_literal) {
649 const int target_literal = it.first;
650 switch (it.second.size()) {
652 if (!
context->SetLiteralToFalse(target_literal)) {
658 context->StoreBooleanEqualityRelation(target_literal,
663 BoolArgumentProto*
const bool_or =
664 context->working_model->add_constraints()->mutable_bool_or();
665 bool_or->add_literals(
NegatedRef(target_literal));
666 for (
const int value_literal : it.second) {
667 bool_or->add_literals(value_literal);
668 context->AddImplication(value_literal, target_literal);
675 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
676 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
678 if (
proto.vars_size() == 0) {
679 const int64 initial_state =
proto.starting_state();
680 for (
const int64 final_state :
proto.final_states()) {
681 if (initial_state == final_state) {
682 context->UpdateRuleStats(
"automaton: empty constraint");
688 return (
void)
context->NotifyThatModelIsUnsat();
689 }
else if (
proto.transition_label_size() == 0) {
691 return (
void)
context->NotifyThatModelIsUnsat();
694 const int n =
proto.vars_size();
695 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
698 const absl::flat_hash_set<int64> final_states(
699 {
proto.final_states().begin(),
proto.final_states().end()});
700 std::vector<absl::flat_hash_set<int64>> reachable_states(n + 1);
701 reachable_states[0].insert(
proto.starting_state());
705 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
707 const int64 label =
proto.transition_label(t);
709 if (!reachable_states[
time].contains(
tail))
continue;
710 if (!
context->DomainContains(vars[
time], label))
continue;
711 if (
time == n - 1 && !final_states.contains(
head))
continue;
712 reachable_states[
time + 1].insert(
head);
718 absl::flat_hash_set<int64> new_set;
719 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
721 const int64 label =
proto.transition_label(t);
724 if (!reachable_states[
time].contains(
tail))
continue;
725 if (!
context->DomainContains(vars[
time], label))
continue;
726 if (!reachable_states[
time + 1].contains(
head))
continue;
727 new_set.insert(
tail);
729 reachable_states[
time].swap(new_set);
737 absl::flat_hash_map<int64, int> encoding;
738 absl::flat_hash_map<int64, int> in_encoding;
739 absl::flat_hash_map<int64, int> out_encoding;
740 bool removed_values =
false;
746 std::vector<int64> in_states;
747 std::vector<int64> transition_values;
748 std::vector<int64> out_states;
749 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
751 const int64 label =
proto.transition_label(i);
754 if (!reachable_states[
time].contains(
tail))
continue;
755 if (!reachable_states[
time + 1].contains(
head))
continue;
756 if (!
context->DomainContains(vars[
time], label))
continue;
761 in_states.push_back(
tail);
762 transition_values.push_back(label);
766 out_states.push_back(
time + 1 == n ? 0 :
head);
769 std::vector<int> tuple_literals;
770 if (transition_values.size() == 1) {
771 bool tmp_removed_values =
false;
772 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
773 CHECK_EQ(reachable_states[
time + 1].size(), 1);
775 Domain(transition_values.front()),
776 &tmp_removed_values)) {
777 return (
void)
context->NotifyThatModelIsUnsat();
781 }
else if (transition_values.size() == 2) {
782 const int bool_var =
context->NewBoolVar();
783 tuple_literals.push_back(bool_var);
784 tuple_literals.push_back(
NegatedRef(bool_var));
789 LinearConstraintProto*
const exactly_one =
790 context->working_model->add_constraints()->mutable_linear();
791 exactly_one->add_domain(1);
792 exactly_one->add_domain(1);
793 for (
int i = 0; i < transition_values.size(); ++i) {
794 const int tuple_literal =
context->NewBoolVar();
795 tuple_literals.push_back(tuple_literal);
796 exactly_one->add_vars(tuple_literal);
797 exactly_one->add_coeffs(1);
803 std::vector<int64> s = transition_values;
809 return (
void)
context->NotifyThatModelIsUnsat();
815 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
822 std::vector<int64> s = out_states;
825 out_encoding.clear();
828 out_encoding[s.front()] =
var;
830 }
else if (s.size() > 2) {
831 for (
const int64 state : s) {
832 out_encoding[state] =
context->NewBoolVar();
837 if (!in_encoding.empty()) {
838 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
840 if (!encoding.empty()) {
841 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
844 if (!out_encoding.empty()) {
845 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
847 in_encoding.swap(out_encoding);
848 out_encoding.clear();
851 if (removed_values) {
852 context->UpdateRuleStats(
"automaton: reduced variable domains");
854 context->UpdateRuleStats(
"automaton: expanded");
858 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
859 TableConstraintProto& table = *
ct->mutable_table();
860 const int num_vars = table.vars_size();
861 const int num_original_tuples = table.values_size() / num_vars;
862 std::vector<std::vector<int64>> tuples(num_original_tuples);
864 for (
int i = 0; i < num_original_tuples; ++i) {
865 for (
int j = 0; j < num_vars; ++j) {
866 tuples[i].push_back(table.values(count++));
870 if (tuples.empty()) {
871 context->UpdateRuleStats(
"table: empty negated constraint");
878 std::vector<int64> domain_sizes;
879 for (
int i = 0; i < num_vars; ++i) {
880 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
885 std::vector<int> clause;
886 for (
const std::vector<int64>& tuple : tuples) {
888 for (
int i = 0; i < num_vars; ++i) {
890 if (
value == any_value)
continue;
893 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
896 if (!clause.empty()) {
897 BoolArgumentProto* bool_or =
898 context->working_model->add_constraints()->mutable_bool_or();
899 for (
const int lit : clause) {
900 bool_or->add_literals(lit);
904 context->UpdateRuleStats(
"table: expanded negated constraint");
908 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
909 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
910 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
911 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
916 lin_max->mutable_lin_max()->mutable_target());
918 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
919 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
930 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
931 const std::vector<int64>& values,
int variable,
932 const std::vector<int>& tuples_with_any,
934 VLOG(2) <<
"Process var(" << variable <<
") with domain "
935 <<
context->DomainOf(variable) <<
" and " << values.size()
936 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
937 CHECK_EQ(tuple_literals.size(), values.size());
938 std::vector<std::pair<int64, int>> pairs;
941 for (
int i = 0; i < values.size(); ++i) {
944 pairs.emplace_back(
value, tuple_literals[i]);
949 std::vector<int> selected;
950 std::sort(pairs.begin(), pairs.end());
951 for (
int i = 0; i < pairs.size();) {
954 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
955 selected.push_back(pairs[i].second);
958 CHECK(!selected.empty() || !tuples_with_any.empty());
959 if (selected.size() == 1 && tuples_with_any.empty()) {
960 context->InsertVarValueEncoding(selected.front(), variable,
value);
962 const int value_literal =
964 BoolArgumentProto* no_support =
965 context->working_model->add_constraints()->mutable_bool_or();
966 for (
const int lit : selected) {
967 no_support->add_literals(lit);
968 context->AddImplication(lit, value_literal);
970 for (
const int lit : tuples_with_any) {
971 no_support->add_literals(lit);
975 no_support->add_literals(
NegatedRef(value_literal));
981 void AddSizeTwoTable(
982 const std::vector<int>& vars,
const std::vector<std::vector<int64>>& tuples,
983 const std::vector<absl::flat_hash_set<int64>>& values_per_var,
985 CHECK_EQ(vars.size(), 2);
986 const int left_var = vars[0];
987 const int right_var = vars[1];
988 if (
context->DomainOf(left_var).Size() == 1 ||
989 context->DomainOf(right_var).Size() == 1) {
995 std::map<int, std::vector<int>> left_to_right;
996 std::map<int, std::vector<int>> right_to_left;
998 for (
const auto& tuple : tuples) {
999 const int64 left_value(tuple[0]);
1000 const int64 right_value(tuple[1]);
1001 CHECK(
context->DomainContains(left_var, left_value));
1002 CHECK(
context->DomainContains(right_var, right_value));
1004 const int left_literal =
1005 context->GetOrCreateVarValueEncoding(left_var, left_value);
1006 const int right_literal =
1007 context->GetOrCreateVarValueEncoding(right_var, right_value);
1008 left_to_right[left_literal].push_back(right_literal);
1009 right_to_left[right_literal].push_back(left_literal);
1012 int num_implications = 0;
1013 int num_clause_added = 0;
1014 int num_large_clause_added = 0;
1015 auto add_support_constraint =
1016 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1017 int lit,
const std::vector<int>& support_literals,
1018 int max_support_size) {
1019 if (support_literals.size() == max_support_size)
return;
1020 if (support_literals.size() == 1) {
1021 context->AddImplication(lit, support_literals.front());
1024 BoolArgumentProto* bool_or =
1025 context->working_model->add_constraints()->mutable_bool_or();
1026 for (
const int support_literal : support_literals) {
1027 bool_or->add_literals(support_literal);
1031 if (support_literals.size() > max_support_size / 2) {
1032 num_large_clause_added++;
1037 for (
const auto& it : left_to_right) {
1038 add_support_constraint(it.first, it.second, values_per_var[1].size());
1040 for (
const auto& it : right_to_left) {
1041 add_support_constraint(it.first, it.second, values_per_var[0].size());
1043 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1044 << num_clause_added <<
" clauses, including "
1045 << num_large_clause_added <<
" large clauses, " << num_implications
1049 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1050 const TableConstraintProto& table =
ct->table();
1051 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1052 const int num_vars = table.vars_size();
1053 const int num_original_tuples = table.values_size() / num_vars;
1056 std::vector<std::vector<int64>> tuples(num_original_tuples);
1058 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1059 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1060 tuples[tuple_index].push_back(table.values(count++));
1066 std::vector<absl::flat_hash_set<int64>> values_per_var(num_vars);
1068 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1070 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1071 const int64 value = tuples[tuple_index][var_index];
1072 if (!
context->DomainContains(vars[var_index],
value)) {
1078 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1079 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1081 std::swap(tuples[tuple_index], tuples[new_size]);
1085 tuples.resize(new_size);
1086 const int num_valid_tuples = tuples.size();
1088 if (tuples.empty()) {
1089 context->UpdateRuleStats(
"table: empty");
1090 return (
void)
context->NotifyThatModelIsUnsat();
1096 int num_fixed_variables = 0;
1097 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1098 CHECK(
context->IntersectDomainWith(
1101 values_per_var[var_index].end()})));
1102 if (
context->DomainOf(vars[var_index]).Size() == 1) {
1103 num_fixed_variables++;
1107 if (num_fixed_variables == num_vars - 1) {
1108 context->UpdateRuleStats(
"table: one variable not fixed");
1111 }
else if (num_fixed_variables == num_vars) {
1112 context->UpdateRuleStats(
"table: all variables fixed");
1118 if (num_vars == 2) {
1119 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1121 "table: expanded positive constraint with two variables");
1128 int num_prefix_tuples = 0;
1130 absl::flat_hash_set<absl::Span<const int64>> prefixes;
1131 for (
const std::vector<int64>& tuple : tuples) {
1132 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1134 num_prefix_tuples = prefixes.size();
1141 std::vector<int64> domain_sizes;
1142 for (
int i = 0; i < num_vars; ++i) {
1143 domain_sizes.push_back(values_per_var[i].size());
1146 const int num_compressed_tuples = tuples.size();
1148 if (num_compressed_tuples == 1) {
1150 context->UpdateRuleStats(
"table: one tuple");
1156 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1157 if (prefixes_are_all_different) {
1159 "TODO table: last value implied by previous values");
1167 if (VLOG_IS_ON(2)) {
1169 int64 max_num_prefix_tuples = 1;
1170 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1171 max_num_prefix_tuples =
1172 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1176 absl::StrCat(
"Table: ", num_vars,
1177 " variables, original tuples = ", num_original_tuples);
1178 if (num_valid_tuples != num_original_tuples) {
1179 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1181 if (prefixes_are_all_different) {
1182 if (num_prefix_tuples < max_num_prefix_tuples) {
1183 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1184 max_num_prefix_tuples);
1186 absl::StrAppend(&
message,
", full prefix = true");
1189 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1191 if (num_compressed_tuples != num_valid_tuples) {
1193 ", compressed tuples = ", num_compressed_tuples);
1199 if (num_compressed_tuples == 2) {
1200 context->UpdateRuleStats(
"TODO table: two tuples");
1212 std::vector<int> tuple_literals(num_compressed_tuples);
1213 BoolArgumentProto* at_least_one_tuple =
1214 context->working_model->add_constraints()->mutable_bool_or();
1227 BoolArgumentProto* at_most_one_tuple =
nullptr;
1228 if (
context->keep_all_feasible_solutions) {
1230 context->working_model->add_constraints()->mutable_at_most_one();
1233 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1234 tuple_literals[var_index] =
context->NewBoolVar();
1235 at_least_one_tuple->add_literals(tuple_literals[var_index]);
1236 if (at_most_one_tuple !=
nullptr) {
1237 at_most_one_tuple->add_literals(tuple_literals[var_index]);
1241 std::vector<int> active_tuple_literals;
1242 std::vector<int64> active_values;
1243 std::vector<int> any_tuple_literals;
1244 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1245 if (values_per_var[var_index].size() == 1)
continue;
1247 active_tuple_literals.clear();
1248 active_values.clear();
1249 any_tuple_literals.clear();
1250 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1252 const int64 value = tuples[tuple_index][var_index];
1253 const int tuple_literal = tuple_literals[tuple_index];
1255 if (
value == any_value) {
1256 any_tuple_literals.push_back(tuple_literal);
1258 active_tuple_literals.push_back(tuple_literal);
1259 active_values.push_back(
value);
1263 if (!active_tuple_literals.empty()) {
1264 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1269 context->UpdateRuleStats(
"table: expanded positive constraint");
1275 if (
context->ModelIsUnsat())
return;
1278 context->InitializeNewDomains();
1280 const int num_constraints =
context->working_model->constraints_size();
1281 for (
int i = 0; i < num_constraints; ++i) {
1282 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
1284 switch (
ct->constraint_case()) {
1285 case ConstraintProto::ConstraintCase::kReservoir:
1288 case ConstraintProto::ConstraintCase::kIntMod:
1291 case ConstraintProto::ConstraintCase::kIntProd:
1294 case ConstraintProto::ConstraintCase::kLinMin:
1297 case ConstraintProto::ConstraintCase::kElement:
1298 if (options.
parameters.expand_element_constraints()) {
1302 case ConstraintProto::ConstraintCase::kInverse:
1305 case ConstraintProto::ConstraintCase::kAutomaton:
1306 if (options.
parameters.expand_automaton_constraints()) {
1310 case ConstraintProto::ConstraintCase::kTable:
1311 if (
ct->table().negated()) {
1313 }
else if (options.
parameters.expand_table_constraints()) {
1324 context->UpdateNewConstraintsVariableUsage();
1325 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1326 context->UpdateConstraintVariableUsage(i);
1330 if (
context->ModelIsUnsat())
return;
1334 context->InitializeNewDomains();
1337 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1339 context->working_model->mutable_variables(i));