20 #include "absl/container/flat_hash_map.h"
36 void ExpandReservoir(ConstraintProto*
ct, PresolveContext*
context) {
37 if (
ct->reservoir().min_level() >
ct->reservoir().max_level()) {
38 VLOG(1) <<
"Empty level domain in reservoir constraint.";
39 return (
void)
context->NotifyThatModelIsUnsat();
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);
52 int num_positives = 0;
53 int num_negatives = 0;
54 for (
const int64_t
demand : reservoir.demands()) {
62 if (num_positives > 0 && num_negatives > 0) {
64 for (
int i = 0; i < num_events - 1; ++i) {
65 const int active_i = is_active_literal(i);
66 if (
context->LiteralIsFalse(active_i))
continue;
67 const int time_i = reservoir.times(i);
69 for (
int j = i + 1; j < num_events; ++j) {
70 const int active_j = is_active_literal(j);
71 if (
context->LiteralIsFalse(active_j))
continue;
72 const int time_j = reservoir.times(j);
74 const int i_lesseq_j =
context->GetOrCreateReifiedPrecedenceLiteral(
75 time_i, time_j, active_i, active_j);
76 context->working_model->mutable_variables(i_lesseq_j)
77 ->set_name(absl::StrCat(i,
" before ", j));
78 const int j_lesseq_i =
context->GetOrCreateReifiedPrecedenceLiteral(
79 time_j, time_i, active_j, active_i);
80 context->working_model->mutable_variables(j_lesseq_i)
81 ->set_name(absl::StrCat(j,
" before ", i));
89 for (
int i = 0; i < num_events; ++i) {
90 const int active_i = is_active_literal(i);
91 if (
context->LiteralIsFalse(active_i))
continue;
92 const int time_i = reservoir.times(i);
95 ConstraintProto*
const level =
context->working_model->add_constraints();
96 level->add_enforcement_literal(active_i);
99 for (
int j = 0; j < num_events; ++j) {
100 if (i == j)
continue;
101 const int active_j = is_active_literal(j);
102 if (
context->LiteralIsFalse(active_j))
continue;
104 const int time_j = reservoir.times(j);
105 level->mutable_linear()->add_vars(
106 context->GetOrCreateReifiedPrecedenceLiteral(time_j, time_i,
107 active_j, active_i));
108 level->mutable_linear()->add_coeffs(reservoir.demands(j));
112 const int64_t demand_i = reservoir.demands(i);
113 level->mutable_linear()->add_domain(
114 CapSub(reservoir.min_level(), demand_i));
115 level->mutable_linear()->add_domain(
116 CapSub(reservoir.max_level(), demand_i));
122 context->working_model->add_constraints()->mutable_linear();
123 for (
int i = 0; i < num_events; ++i) {
124 sum->add_vars(is_active_literal(i));
125 sum->add_coeffs(reservoir.demands(i));
127 sum->add_domain(reservoir.min_level());
128 sum->add_domain(reservoir.max_level());
132 context->UpdateRuleStats(
"reservoir: expanded");
137 void ExpandIntDiv(ConstraintProto*
ct, PresolveContext*
context) {
138 const int divisor =
ct->int_div().vars(1);
139 if (!
context->IntersectDomainWith(divisor, Domain(0).Complement())) {
140 return (
void)
context->NotifyThatModelIsUnsat();
144 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
145 const IntegerArgumentProto& int_mod =
ct->int_mod();
146 const int var = int_mod.vars(0);
147 const int mod_var = int_mod.vars(1);
148 const int target_var = int_mod.target();
150 const int64_t mod_lb =
context->MinOf(mod_var);
152 const int64_t mod_ub =
context->MaxOf(mod_var);
160 context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb));
162 auto add_enforcement_literal_if_needed = [&]() {
163 if (
ct->enforcement_literal_size() == 0)
return;
164 const int literal =
ct->enforcement_literal(0);
165 ConstraintProto*
const last =
context->working_model->mutable_constraints(
166 context->working_model->constraints_size() - 1);
167 last->add_enforcement_literal(
literal);
171 IntegerArgumentProto*
const div_proto =
172 context->working_model->add_constraints()->mutable_int_div();
173 div_proto->set_target(div_var);
174 div_proto->add_vars(
var);
175 div_proto->add_vars(mod_var);
176 add_enforcement_literal_if_needed();
179 if (mod_lb == mod_ub) {
181 LinearConstraintProto*
const lin =
182 context->working_model->add_constraints()->mutable_linear();
183 lin->add_vars(int_mod.vars(0));
185 lin->add_vars(div_var);
186 lin->add_coeffs(-mod_lb);
187 lin->add_vars(target_var);
191 add_enforcement_literal_if_needed();
194 const int prod_var =
context->NewIntVar(
195 context->DomainOf(div_var).ContinuousMultiplicationBy(
197 IntegerArgumentProto*
const int_prod =
198 context->working_model->add_constraints()->mutable_int_prod();
199 int_prod->set_target(prod_var);
200 int_prod->add_vars(div_var);
201 int_prod->add_vars(mod_var);
202 add_enforcement_literal_if_needed();
205 LinearConstraintProto*
const lin =
206 context->working_model->add_constraints()->mutable_linear();
209 lin->add_vars(prod_var);
211 lin->add_vars(target_var);
215 add_enforcement_literal_if_needed();
219 context->UpdateRuleStats(
"int_mod: expanded");
222 void ExpandIntProdWithBoolean(
int bool_ref,
int int_ref,
int product_ref,
224 ConstraintProto*
const one =
context->working_model->add_constraints();
225 one->add_enforcement_literal(bool_ref);
226 one->mutable_linear()->add_vars(int_ref);
227 one->mutable_linear()->add_coeffs(1);
228 one->mutable_linear()->add_vars(product_ref);
229 one->mutable_linear()->add_coeffs(-1);
230 one->mutable_linear()->add_domain(0);
231 one->mutable_linear()->add_domain(0);
233 ConstraintProto*
const zero =
context->working_model->add_constraints();
234 zero->add_enforcement_literal(
NegatedRef(bool_ref));
235 zero->mutable_linear()->add_vars(product_ref);
236 zero->mutable_linear()->add_coeffs(1);
237 zero->mutable_linear()->add_domain(0);
238 zero->mutable_linear()->add_domain(0);
241 void AddXEqualYOrXEqualZero(
int x_eq_y,
int x,
int y,
243 ConstraintProto* equality =
context->working_model->add_constraints();
244 equality->add_enforcement_literal(x_eq_y);
245 equality->mutable_linear()->add_vars(x);
246 equality->mutable_linear()->add_coeffs(1);
247 equality->mutable_linear()->add_vars(y);
248 equality->mutable_linear()->add_coeffs(-1);
249 equality->mutable_linear()->add_domain(0);
250 equality->mutable_linear()->add_domain(0);
255 void ExpandIntProdWithOneAcrossZero(
int a_ref,
int b_ref,
int product_ref,
262 const int a_is_positive =
context->NewBoolVar();
263 context->AddImplyInDomain(a_is_positive, a_ref,
267 const int pos_a_ref =
context->NewIntVar({0,
context->MaxOf(a_ref)});
268 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
270 const int neg_a_ref =
context->NewIntVar({
context->MinOf(a_ref), 0});
274 const bool b_is_positive =
context->MinOf(b_ref) >= 0;
275 const Domain pos_a_product_domain =
276 b_is_positive ? Domain({0,
context->MaxOf(product_ref)})
277 : Domain({
context->MinOf(product_ref), 0});
278 const int pos_a_product =
context->NewIntVar(pos_a_product_domain);
279 IntegerArgumentProto* pos_product =
280 context->working_model->add_constraints()->mutable_int_prod();
281 pos_product->set_target(pos_a_product);
282 pos_product->add_vars(pos_a_ref);
283 pos_product->add_vars(b_ref);
286 const Domain neg_a_product_domain =
287 b_is_positive ? Domain({
context->MinOf(product_ref), 0})
288 : Domain({0,
context->MaxOf(product_ref)});
289 const int neg_a_product =
context->NewIntVar(neg_a_product_domain);
290 IntegerArgumentProto* neg_product =
291 context->working_model->add_constraints()->mutable_int_prod();
292 neg_product->set_target(neg_a_product);
293 neg_product->add_vars(neg_a_ref);
294 neg_product->add_vars(b_ref);
297 LinearConstraintProto* lin =
298 context->working_model->add_constraints()->mutable_linear();
299 lin->add_vars(product_ref);
301 lin->add_vars(pos_a_product);
303 lin->add_vars(neg_a_product);
309 void ExpandIntProdWithTwoAcrossZero(
int a_ref,
int b_ref,
int product_ref,
312 const int a_is_positive =
context->NewBoolVar();
313 context->AddImplyInDomain(a_is_positive, a_ref,
317 const int64_t min_of_a =
context->MinOf(a_ref);
318 const int64_t max_of_a =
context->MaxOf(a_ref);
320 const int pos_a_ref =
context->NewIntVar({0, max_of_a});
321 AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref,
context);
323 const int neg_a_ref =
context->NewIntVar({min_of_a, 0});
327 const int pos_product_ref =
329 ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref,
context);
330 const int neg_product_ref =
332 ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref,
context);
335 LinearConstraintProto* lin =
336 context->working_model->add_constraints()->mutable_linear();
337 lin->add_vars(product_ref);
339 lin->add_vars(pos_product_ref);
341 lin->add_vars(neg_product_ref);
347 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
348 const IntegerArgumentProto& int_prod =
ct->int_prod();
349 if (int_prod.vars_size() != 2)
return;
350 const int a = int_prod.vars(0);
351 const int b = int_prod.vars(1);
352 const int p = int_prod.target();
353 const bool a_is_boolean =
355 const bool b_is_boolean =
360 if (a_is_boolean && !b_is_boolean) {
361 ExpandIntProdWithBoolean(
a,
b, p,
context);
363 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
366 if (b_is_boolean && !a_is_boolean) {
367 ExpandIntProdWithBoolean(
b,
a, p,
context);
369 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
373 const bool a_span_across_zero =
375 const bool b_span_across_zero =
377 if (a_span_across_zero && !b_span_across_zero) {
378 ExpandIntProdWithOneAcrossZero(
a,
b, p,
context);
381 "int_prod: expanded product with general integer variables");
384 if (!a_span_across_zero && b_span_across_zero) {
385 ExpandIntProdWithOneAcrossZero(
b,
a, p,
context);
388 "int_prod: expanded product with general integer variables");
391 if (a_span_across_zero && b_span_across_zero) {
392 ExpandIntProdWithTwoAcrossZero(
a,
b, p,
context);
395 "int_prod: expanded product with general integer variables");
400 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
401 const int size =
ct->inverse().f_direct().size();
402 CHECK_EQ(size,
ct->inverse().f_inverse().size());
408 for (
const int ref :
ct->inverse().f_direct()) {
409 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
410 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
414 for (
const int ref :
ct->inverse().f_inverse()) {
415 if (!
context->IntersectDomainWith(ref, Domain(0, size - 1))) {
416 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
423 std::vector<int64_t> possible_values;
427 const auto filter_inverse_domain = [
context, size, &possible_values](
429 const auto& inverse) {
431 for (
int i = 0; i < size; ++i) {
432 possible_values.clear();
433 const Domain domain =
context->DomainOf(direct[i]);
434 bool removed_value =
false;
435 for (
const ClosedInterval&
interval : domain) {
437 if (
context->DomainOf(inverse[j]).Contains(i)) {
438 possible_values.push_back(j);
440 removed_value =
true;
445 if (!
context->IntersectDomainWith(
447 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
455 if (!filter_inverse_domain(
ct->inverse().f_direct(),
456 ct->inverse().f_inverse())) {
460 if (!filter_inverse_domain(
ct->inverse().f_inverse(),
461 ct->inverse().f_direct())) {
467 for (
int i = 0; i < size; ++i) {
468 const int f_i =
ct->inverse().f_direct(i);
469 const Domain domain =
context->DomainOf(f_i);
470 for (
const ClosedInterval&
interval : domain) {
473 const int r_j =
ct->inverse().f_inverse(j);
475 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
476 context->InsertVarValueEncoding(r_j_i, f_i, j);
478 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
479 context->InsertVarValueEncoding(f_i_j, r_j, i);
486 context->UpdateRuleStats(
"inverse: expanded");
489 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
490 const ElementConstraintProto& element =
ct->element();
491 const int index_ref = element.index();
492 const int target_ref = element.target();
493 const int size = element.vars_size();
495 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
496 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
497 return (
void)
context->NotifyThatModelIsUnsat();
500 bool all_constants =
true;
501 absl::flat_hash_map<int64_t, int> constant_var_values_usage;
502 std::vector<int64_t> constant_var_values;
503 std::vector<int64_t> invalid_indices;
504 Domain index_domain =
context->DomainOf(index_ref);
505 Domain target_domain =
context->DomainOf(target_ref);
506 for (
const ClosedInterval&
interval : index_domain) {
508 const int var = element.vars(v);
509 const Domain var_domain =
context->DomainOf(
var);
510 if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
511 invalid_indices.push_back(v);
514 if (var_domain.Min() != var_domain.Max()) {
515 all_constants =
false;
519 const int64_t
value = var_domain.Min();
520 if (constant_var_values_usage[
value]++ == 0) {
521 constant_var_values.push_back(
value);
526 if (!invalid_indices.empty() && target_ref != index_ref) {
527 if (!
context->IntersectDomainWith(
529 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
530 return (
void)
context->NotifyThatModelIsUnsat();
534 index_domain =
context->DomainOf(index_ref);
542 absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
543 if (all_constants && target_ref != index_ref) {
544 if (!
context->IntersectDomainWith(
546 VLOG(1) <<
"Empty domain for the target variable in ExpandElement()";
550 target_domain =
context->DomainOf(target_ref);
551 if (target_domain.Size() == 1) {
552 context->UpdateRuleStats(
"element: one value array");
557 for (
const ClosedInterval&
interval : target_domain) {
561 const int lit =
context->GetOrCreateVarValueEncoding(target_ref, v);
562 BoolArgumentProto*
const support =
563 context->working_model->add_constraints()->mutable_bool_or();
564 supports[v] = support;
573 auto* bool_or =
context->working_model->add_constraints()->mutable_bool_or();
575 for (
const ClosedInterval&
interval : index_domain) {
577 const int var = element.vars(v);
578 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
579 const Domain var_domain =
context->DomainOf(
var);
581 bool_or->add_literals(index_lit);
583 if (target_ref == index_ref) {
586 context->AddImplyInDomain(index_lit,
var, Domain(v));
587 }
else if (target_domain.Size() == 1) {
590 context->AddImplyInDomain(index_lit,
var, target_domain);
591 }
else if (var_domain.Size() == 1) {
593 const int64_t
value = var_domain.Min();
594 if (constant_var_values_usage[
value] > 1) {
597 const int target_lit =
598 context->GetOrCreateVarValueEncoding(target_ref,
value);
599 context->AddImplication(index_lit, target_lit);
603 context->InsertVarValueEncoding(index_lit, target_ref,
value);
606 context->AddImplyInDomain(index_lit, target_ref, var_domain);
609 ConstraintProto*
const ct =
context->working_model->add_constraints();
610 ct->add_enforcement_literal(index_lit);
611 ct->mutable_linear()->add_vars(
var);
612 ct->mutable_linear()->add_coeffs(1);
613 ct->mutable_linear()->add_vars(target_ref);
614 ct->mutable_linear()->add_coeffs(-1);
615 ct->mutable_linear()->add_domain(0);
616 ct->mutable_linear()->add_domain(0);
622 const int64_t var_min = target_domain.Min();
627 for (
const auto it : constant_var_values_usage) {
628 if (it.second > usage ||
629 (it.second == usage && it.first < most_frequent_value)) {
631 most_frequent_value = it.first;
643 usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
644 if (base != var_min) {
645 VLOG(3) <<
"expand element: choose " << base <<
" with usage " << usage
646 <<
" over " << var_min <<
" among " << size <<
" values.";
649 LinearConstraintProto*
const linear =
650 context->working_model->add_constraints()->mutable_linear();
652 linear->add_vars(target_ref);
653 linear->add_coeffs(-1);
654 for (
const ClosedInterval&
interval : index_domain) {
656 const int ref = element.vars(v);
657 const int index_lit =
658 context->GetOrCreateVarValueEncoding(index_ref, v);
659 const int64_t
delta =
context->DomainOf(ref).Min() - base;
661 linear->add_vars(index_lit);
662 linear->add_coeffs(
delta);
665 linear->add_coeffs(-
delta);
670 linear->add_domain(rhs);
671 linear->add_domain(rhs);
673 context->UpdateRuleStats(
"element: expanded value element");
675 context->UpdateRuleStats(
"element: expanded");
682 void LinkLiteralsAndValues(
683 const std::vector<int>& value_literals,
const std::vector<int64_t>& values,
684 const absl::flat_hash_map<int64_t, int>& target_encoding,
686 CHECK_EQ(value_literals.size(), values.size());
690 std::map<int, std::vector<int>> value_literals_per_target_literal;
695 for (
int i = 0; i < values.size(); ++i) {
696 const int64_t v = values[i];
697 CHECK(target_encoding.contains(v));
699 value_literals_per_target_literal[lit].push_back(value_literals[i]);
704 for (
const auto& it : value_literals_per_target_literal) {
705 const int target_literal = it.first;
706 switch (it.second.size()) {
708 if (!
context->SetLiteralToFalse(target_literal)) {
714 context->StoreBooleanEqualityRelation(target_literal,
719 BoolArgumentProto*
const bool_or =
720 context->working_model->add_constraints()->mutable_bool_or();
721 bool_or->add_literals(
NegatedRef(target_literal));
722 for (
const int value_literal : it.second) {
723 bool_or->add_literals(value_literal);
724 context->AddImplication(value_literal, target_literal);
731 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
732 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
734 if (
proto.vars_size() == 0) {
735 const int64_t initial_state =
proto.starting_state();
736 for (
const int64_t final_state :
proto.final_states()) {
737 if (initial_state == final_state) {
738 context->UpdateRuleStats(
"automaton: empty constraint");
744 return (
void)
context->NotifyThatModelIsUnsat();
745 }
else if (
proto.transition_label_size() == 0) {
747 return (
void)
context->NotifyThatModelIsUnsat();
750 const int n =
proto.vars_size();
751 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
754 const absl::flat_hash_set<int64_t> final_states(
755 {
proto.final_states().begin(),
proto.final_states().end()});
756 std::vector<absl::flat_hash_set<int64_t>> reachable_states(n + 1);
757 reachable_states[0].insert(
proto.starting_state());
761 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
762 const int64_t
tail =
proto.transition_tail(t);
763 const int64_t label =
proto.transition_label(t);
764 const int64_t
head =
proto.transition_head(t);
765 if (!reachable_states[
time].contains(
tail))
continue;
766 if (!
context->DomainContains(vars[
time], label))
continue;
767 if (
time == n - 1 && !final_states.contains(
head))
continue;
768 reachable_states[
time + 1].insert(
head);
774 absl::flat_hash_set<int64_t> new_set;
775 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
776 const int64_t
tail =
proto.transition_tail(t);
777 const int64_t label =
proto.transition_label(t);
778 const int64_t
head =
proto.transition_head(t);
780 if (!reachable_states[
time].contains(
tail))
continue;
781 if (!
context->DomainContains(vars[
time], label))
continue;
782 if (!reachable_states[
time + 1].contains(
head))
continue;
783 new_set.insert(
tail);
785 reachable_states[
time].swap(new_set);
793 absl::flat_hash_map<int64_t, int> encoding;
794 absl::flat_hash_map<int64_t, int> in_encoding;
795 absl::flat_hash_map<int64_t, int> out_encoding;
796 bool removed_values =
false;
802 std::vector<int64_t> in_states;
803 std::vector<int64_t> transition_values;
804 std::vector<int64_t> out_states;
805 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
806 const int64_t
tail =
proto.transition_tail(i);
807 const int64_t label =
proto.transition_label(i);
808 const int64_t
head =
proto.transition_head(i);
810 if (!reachable_states[
time].contains(
tail))
continue;
811 if (!reachable_states[
time + 1].contains(
head))
continue;
812 if (!
context->DomainContains(vars[
time], label))
continue;
817 in_states.push_back(
tail);
818 transition_values.push_back(label);
822 out_states.push_back(
time + 1 == n ? 0 :
head);
825 std::vector<int> tuple_literals;
826 if (transition_values.size() == 1) {
827 bool tmp_removed_values =
false;
828 tuple_literals.push_back(
context->GetOrCreateConstantVar(1));
831 Domain(transition_values.front()),
832 &tmp_removed_values)) {
833 return (
void)
context->NotifyThatModelIsUnsat();
837 }
else if (transition_values.size() == 2) {
838 const int bool_var =
context->NewBoolVar();
839 tuple_literals.push_back(bool_var);
840 tuple_literals.push_back(
NegatedRef(bool_var));
845 LinearConstraintProto*
const exactly_one =
846 context->working_model->add_constraints()->mutable_linear();
847 exactly_one->add_domain(1);
848 exactly_one->add_domain(1);
849 for (
int i = 0; i < transition_values.size(); ++i) {
850 const int tuple_literal =
context->NewBoolVar();
851 tuple_literals.push_back(tuple_literal);
852 exactly_one->add_vars(tuple_literal);
853 exactly_one->add_coeffs(1);
859 std::vector<int64_t> s = transition_values;
865 return (
void)
context->NotifyThatModelIsUnsat();
871 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
878 std::vector<int64_t> s = out_states;
881 out_encoding.clear();
884 out_encoding[s.front()] =
var;
886 }
else if (s.size() > 2) {
887 for (
const int64_t state : s) {
888 out_encoding[state] =
context->NewBoolVar();
893 if (!in_encoding.empty()) {
894 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
896 if (!encoding.empty()) {
897 LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
900 if (!out_encoding.empty()) {
901 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
903 in_encoding.swap(out_encoding);
904 out_encoding.clear();
907 if (removed_values) {
908 context->UpdateRuleStats(
"automaton: reduced variable domains");
910 context->UpdateRuleStats(
"automaton: expanded");
914 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
915 TableConstraintProto& table = *
ct->mutable_table();
916 const int num_vars = table.vars_size();
917 const int num_original_tuples = table.values_size() / num_vars;
918 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
920 for (
int i = 0; i < num_original_tuples; ++i) {
921 for (
int j = 0; j < num_vars; ++j) {
922 tuples[i].push_back(table.values(count++));
926 if (tuples.empty()) {
927 context->UpdateRuleStats(
"table: empty negated constraint");
934 std::vector<int64_t> domain_sizes;
935 for (
int i = 0; i < num_vars; ++i) {
936 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
941 std::vector<int> clause;
942 for (
const std::vector<int64_t>& tuple : tuples) {
944 for (
int i = 0; i < num_vars; ++i) {
945 const int64_t
value = tuple[i];
946 if (
value == any_value)
continue;
949 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
952 if (!clause.empty()) {
953 BoolArgumentProto* bool_or =
954 context->working_model->add_constraints()->mutable_bool_or();
955 for (
const int lit : clause) {
956 bool_or->add_literals(lit);
960 context->UpdateRuleStats(
"table: expanded negated constraint");
964 void ExpandLinMin(ConstraintProto*
ct, PresolveContext*
context) {
965 ConstraintProto*
const lin_max =
context->working_model->add_constraints();
966 for (
int i = 0; i <
ct->enforcement_literal_size(); ++i) {
967 lin_max->add_enforcement_literal(
ct->enforcement_literal(i));
972 lin_max->mutable_lin_max()->mutable_target());
974 for (
int i = 0; i <
ct->lin_min().exprs_size(); ++i) {
975 LinearExpressionProto*
const expr = lin_max->mutable_lin_max()->add_exprs();
986 void ProcessOneVariable(
const std::vector<int>& tuple_literals,
987 const std::vector<int64_t>& values,
int variable,
988 const std::vector<int>& tuples_with_any,
990 VLOG(2) <<
"Process var(" << variable <<
") with domain "
991 <<
context->DomainOf(variable) <<
" and " << values.size()
992 <<
" active tuples, and " << tuples_with_any.size() <<
" any tuples";
993 CHECK_EQ(tuple_literals.size(), values.size());
994 std::vector<std::pair<int64_t, int>> pairs;
997 for (
int i = 0; i < values.size(); ++i) {
998 const int64_t
value = values[i];
1000 pairs.emplace_back(
value, tuple_literals[i]);
1005 std::vector<int> selected;
1006 std::sort(pairs.begin(), pairs.end());
1007 for (
int i = 0; i < pairs.size();) {
1009 const int64_t
value = pairs[i].first;
1010 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1011 selected.push_back(pairs[i].second);
1014 CHECK(!selected.empty() || !tuples_with_any.empty());
1015 if (selected.size() == 1 && tuples_with_any.empty()) {
1016 context->InsertVarValueEncoding(selected.front(), variable,
value);
1018 const int value_literal =
1020 BoolArgumentProto* no_support =
1021 context->working_model->add_constraints()->mutable_bool_or();
1022 for (
const int lit : selected) {
1023 no_support->add_literals(lit);
1024 context->AddImplication(lit, value_literal);
1026 for (
const int lit : tuples_with_any) {
1027 no_support->add_literals(lit);
1031 no_support->add_literals(
NegatedRef(value_literal));
1037 void AddSizeTwoTable(
1038 const std::vector<int>& vars,
1039 const std::vector<std::vector<int64_t>>& tuples,
1040 const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1043 const int left_var = vars[0];
1044 const int right_var = vars[1];
1045 if (
context->DomainOf(left_var).Size() == 1 ||
1046 context->DomainOf(right_var).Size() == 1) {
1052 std::map<int, std::vector<int>> left_to_right;
1053 std::map<int, std::vector<int>> right_to_left;
1055 for (
const auto& tuple : tuples) {
1056 const int64_t left_value(tuple[0]);
1057 const int64_t right_value(tuple[1]);
1059 CHECK(
context->DomainContains(right_var, right_value));
1061 const int left_literal =
1062 context->GetOrCreateVarValueEncoding(left_var, left_value);
1063 const int right_literal =
1064 context->GetOrCreateVarValueEncoding(right_var, right_value);
1065 left_to_right[left_literal].push_back(right_literal);
1066 right_to_left[right_literal].push_back(left_literal);
1069 int num_implications = 0;
1070 int num_clause_added = 0;
1071 int num_large_clause_added = 0;
1072 auto add_support_constraint =
1073 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1074 int lit,
const std::vector<int>& support_literals,
1075 int max_support_size) {
1076 if (support_literals.size() == max_support_size)
return;
1077 if (support_literals.size() == 1) {
1078 context->AddImplication(lit, support_literals.front());
1081 BoolArgumentProto* bool_or =
1082 context->working_model->add_constraints()->mutable_bool_or();
1083 for (
const int support_literal : support_literals) {
1084 bool_or->add_literals(support_literal);
1088 if (support_literals.size() > max_support_size / 2) {
1089 num_large_clause_added++;
1094 for (
const auto& it : left_to_right) {
1095 add_support_constraint(it.first, it.second, values_per_var[1].size());
1097 for (
const auto& it : right_to_left) {
1098 add_support_constraint(it.first, it.second, values_per_var[0].size());
1100 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1101 << num_clause_added <<
" clauses, including "
1102 << num_large_clause_added <<
" large clauses, " << num_implications
1106 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1107 const TableConstraintProto& table =
ct->table();
1108 const std::vector<int> vars(table.vars().begin(), table.vars().end());
1109 const int num_vars = table.vars_size();
1110 const int num_original_tuples = table.values_size() / num_vars;
1113 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1115 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1116 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1117 tuples[tuple_index].push_back(table.values(count++));
1123 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1125 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1127 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1128 const int64_t
value = tuples[tuple_index][var_index];
1129 if (!
context->DomainContains(vars[var_index],
value)) {
1135 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1136 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1138 std::swap(tuples[tuple_index], tuples[new_size]);
1142 tuples.resize(new_size);
1143 const int num_valid_tuples = tuples.size();
1145 if (tuples.empty()) {
1146 context->UpdateRuleStats(
"table: empty");
1147 return (
void)
context->NotifyThatModelIsUnsat();
1153 int num_fixed_variables = 0;
1154 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1158 values_per_var[var_index].end()})));
1159 if (
context->DomainOf(vars[var_index]).Size() == 1) {
1160 num_fixed_variables++;
1164 if (num_fixed_variables == num_vars - 1) {
1165 context->UpdateRuleStats(
"table: one variable not fixed");
1168 }
else if (num_fixed_variables == num_vars) {
1169 context->UpdateRuleStats(
"table: all variables fixed");
1175 if (num_vars == 2) {
1176 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1178 "table: expanded positive constraint with two variables");
1185 int num_prefix_tuples = 0;
1187 absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
1188 for (
const std::vector<int64_t>& tuple : tuples) {
1189 prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1191 num_prefix_tuples = prefixes.size();
1198 std::vector<int64_t> domain_sizes;
1199 for (
int i = 0; i < num_vars; ++i) {
1200 domain_sizes.push_back(values_per_var[i].size());
1203 const int num_compressed_tuples = tuples.size();
1205 if (num_compressed_tuples == 1) {
1207 context->UpdateRuleStats(
"table: one tuple");
1213 const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1214 if (prefixes_are_all_different) {
1216 "TODO table: last value implied by previous values");
1226 int64_t max_num_prefix_tuples = 1;
1227 for (
int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1228 max_num_prefix_tuples =
1229 CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1233 absl::StrCat(
"Table: ", num_vars,
1234 " variables, original tuples = ", num_original_tuples);
1235 if (num_valid_tuples != num_original_tuples) {
1236 absl::StrAppend(&
message,
", valid tuples = ", num_valid_tuples);
1238 if (prefixes_are_all_different) {
1239 if (num_prefix_tuples < max_num_prefix_tuples) {
1240 absl::StrAppend(&
message,
", partial prefix = ", num_prefix_tuples,
"/",
1241 max_num_prefix_tuples);
1243 absl::StrAppend(&
message,
", full prefix = true");
1246 absl::StrAppend(&
message,
", num prefix tuples = ", num_prefix_tuples);
1248 if (num_compressed_tuples != num_valid_tuples) {
1250 ", compressed tuples = ", num_compressed_tuples);
1256 if (num_compressed_tuples == 2) {
1257 context->UpdateRuleStats(
"TODO table: two tuples");
1269 std::vector<int> tuple_literals(num_compressed_tuples);
1270 BoolArgumentProto* at_least_one_tuple =
1271 context->working_model->add_constraints()->mutable_bool_or();
1284 BoolArgumentProto* at_most_one_tuple =
nullptr;
1285 if (
context->keep_all_feasible_solutions) {
1287 context->working_model->add_constraints()->mutable_at_most_one();
1290 for (
int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1291 tuple_literals[var_index] =
context->NewBoolVar();
1292 at_least_one_tuple->add_literals(tuple_literals[var_index]);
1293 if (at_most_one_tuple !=
nullptr) {
1294 at_most_one_tuple->add_literals(tuple_literals[var_index]);
1298 std::vector<int> active_tuple_literals;
1299 std::vector<int64_t> active_values;
1300 std::vector<int> any_tuple_literals;
1301 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1302 if (values_per_var[var_index].size() == 1)
continue;
1304 active_tuple_literals.clear();
1305 active_values.clear();
1306 any_tuple_literals.clear();
1307 for (
int tuple_index = 0; tuple_index < tuple_literals.size();
1309 const int64_t
value = tuples[tuple_index][var_index];
1310 const int tuple_literal = tuple_literals[tuple_index];
1312 if (
value == any_value) {
1313 any_tuple_literals.push_back(tuple_literal);
1315 active_tuple_literals.push_back(tuple_literal);
1316 active_values.push_back(
value);
1320 if (!active_tuple_literals.empty()) {
1321 ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1326 context->UpdateRuleStats(
"table: expanded positive constraint");
1330 void ExpandAllDiff(
bool expand_non_permutations, ConstraintProto*
ct,
1332 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1333 if (
proto.vars_size() <= 2)
return;
1335 const int num_vars =
proto.vars_size();
1337 Domain union_of_domains =
context->DomainOf(
proto.vars(0));
1338 for (
int i = 1; i < num_vars; ++i) {
1340 union_of_domains.UnionWith(
context->DomainOf(
proto.vars(i)));
1343 const bool is_permutation =
proto.vars_size() == union_of_domains.Size();
1345 if (!is_permutation && !expand_non_permutations)
return;
1350 for (
const ClosedInterval&
interval : union_of_domains) {
1353 std::vector<int> possible_refs;
1354 int fixed_variable_count = 0;
1355 for (
const int ref :
proto.vars()) {
1356 if (!
context->DomainContains(ref, v))
continue;
1357 possible_refs.push_back(ref);
1358 if (
context->DomainOf(ref).Size() == 1) {
1359 fixed_variable_count++;
1363 if (fixed_variable_count > 1) {
1365 return (
void)
context->NotifyThatModelIsUnsat();
1366 }
else if (fixed_variable_count == 1) {
1368 for (
const int ref : possible_refs) {
1369 if (
context->DomainOf(ref).Size() == 1)
continue;
1370 if (!
context->IntersectDomainWith(ref, Domain(v).Complement())) {
1371 VLOG(1) <<
"Empty domain for a variable in ExpandAllDiff()";
1377 LinearConstraintProto* at_most_or_equal_one =
1378 context->working_model->add_constraints()->mutable_linear();
1379 int lb = is_permutation ? 1 : 0;
1381 for (
const int ref : possible_refs) {
1384 const int encoding =
context->GetOrCreateVarValueEncoding(ref, v);
1386 at_most_or_equal_one->add_vars(encoding);
1387 at_most_or_equal_one->add_coeffs(1);
1389 at_most_or_equal_one->add_vars(
PositiveRef(encoding));
1390 at_most_or_equal_one->add_coeffs(-1);
1395 at_most_or_equal_one->add_domain(lb);
1396 at_most_or_equal_one->add_domain(ub);
1399 if (is_permutation) {
1400 context->UpdateRuleStats(
"alldiff: permutation expanded");
1402 context->UpdateRuleStats(
"alldiff: expanded");
1410 if (
context->params().disable_constraint_expansion())
return;
1412 if (
context->ModelIsUnsat())
return;
1415 context->InitializeNewDomains();
1418 context->ClearPrecedenceCache();
1420 const int num_constraints =
context->working_model->constraints_size();
1421 for (
int i = 0; i < num_constraints; ++i) {
1422 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
1424 switch (
ct->constraint_case()) {
1425 case ConstraintProto::ConstraintCase::kReservoir:
1426 if (
context->params().expand_reservoir_constraints()) {
1430 case ConstraintProto::ConstraintCase::kIntMod:
1433 case ConstraintProto::ConstraintCase::kIntDiv:
1436 case ConstraintProto::ConstraintCase::kIntProd:
1439 case ConstraintProto::ConstraintCase::kLinMin:
1442 case ConstraintProto::ConstraintCase::kElement:
1443 if (
context->params().expand_element_constraints()) {
1447 case ConstraintProto::ConstraintCase::kInverse:
1450 case ConstraintProto::ConstraintCase::kAutomaton:
1451 if (
context->params().expand_automaton_constraints()) {
1455 case ConstraintProto::ConstraintCase::kTable:
1456 if (
ct->table().negated()) {
1458 }
else if (
context->params().expand_table_constraints()) {
1462 case ConstraintProto::ConstraintCase::kAllDiff:
1463 ExpandAllDiff(
context->params().expand_alldiff_constraints(),
ct,
1473 context->UpdateNewConstraintsVariableUsage();
1474 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1475 context->UpdateConstraintVariableUsage(i);
1479 if (
context->ModelIsUnsat()) {
1489 context->ClearPrecedenceCache();
1492 context->InitializeNewDomains();
1495 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
1497 context->working_model->mutable_variables(i));
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define DCHECK_GT(val1, val2)
#define DCHECK_LT(val1, val2)
#define DCHECK(condition)
#define VLOG(verboselevel)
Domain Complement() const
Returns the set Int64 ∖ D.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
GurobiMPCallbackContext * context
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
bool RefIsPositive(int ref)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)
void CompressTuples(absl::Span< const int64_t > domain_sizes, int64_t any_value, std::vector< std::vector< int64_t >> *tuples)
void ExpandCpModel(PresolveContext *context)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
#define SOLVER_LOG(logger,...)
#define VLOG_IS_ON(verboselevel)