23 #include "absl/container/btree_map.h"
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/types/span.h"
32 #include "ortools/sat/cp_model.pb.h"
35 #include "ortools/sat/sat_parameters.pb.h"
49 std::vector<absl::flat_hash_set<int64_t>>* states,
50 std::vector<absl::flat_hash_set<int64_t>>* labels) {
51 const int n =
proto.vars_size();
52 const absl::flat_hash_set<int64_t> final_states(
53 {
proto.final_states().begin(),
proto.final_states().end()});
58 states->resize(n + 1);
59 (*states)[0].insert(
proto.starting_state());
63 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
64 const int64_t
tail =
proto.transition_tail(t);
65 const int64_t label =
proto.transition_label(t);
66 const int64_t
head =
proto.transition_head(t);
67 if (!(*states)[
time].contains(
tail))
continue;
69 if (
time == n - 1 && !final_states.contains(
head))
continue;
70 (*labels)[
time].insert(label);
77 absl::flat_hash_set<int64_t> new_states;
78 absl::flat_hash_set<int64_t> new_labels;
79 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
80 const int64_t
tail =
proto.transition_tail(t);
81 const int64_t label =
proto.transition_label(t);
82 const int64_t
head =
proto.transition_head(t);
84 if (!(*states)[
time].contains(
tail))
continue;
85 if (!(*labels)[
time].contains(label))
continue;
86 if (!(*states)[
time + 1].contains(
head))
continue;
87 new_labels.insert(label);
88 new_states.insert(
tail);
90 (*labels)[
time].swap(new_labels);
91 (*states)[
time].swap(new_states);
97 void ExpandReservoir(ConstraintProto*
ct, PresolveContext*
context) {
98 if (
ct->reservoir().min_level() >
ct->reservoir().max_level()) {
99 VLOG(1) <<
"Empty level domain in reservoir constraint.";
100 return (
void)
context->NotifyThatModelIsUnsat();
103 const ReservoirConstraintProto& reservoir =
ct->reservoir();
104 const int num_events = reservoir.time_exprs_size();
105 const int true_literal =
context->GetTrueLiteral();
106 const auto is_active_literal = [&reservoir, true_literal](
int index) {
107 if (reservoir.active_literals_size() == 0)
return true_literal;
108 return reservoir.active_literals(
index);
111 int num_positives = 0;
112 int num_negatives = 0;
113 for (
const LinearExpressionProto& demand_expr : reservoir.level_changes()) {
122 absl::flat_hash_map<std::pair<int, int>,
int> precedence_cache;
124 if (num_positives > 0 && num_negatives > 0) {
126 for (
int i = 0; i < num_events - 1; ++i) {
127 const int active_i = is_active_literal(i);
128 if (
context->LiteralIsFalse(active_i))
continue;
129 const LinearExpressionProto& time_i = reservoir.time_exprs(i);
131 for (
int j = i + 1; j < num_events; ++j) {
132 const int active_j = is_active_literal(j);
133 if (
context->LiteralIsFalse(active_j))
continue;
134 const LinearExpressionProto& time_j = reservoir.time_exprs(j);
136 const int i_lesseq_j =
context->GetOrCreateReifiedPrecedenceLiteral(
137 time_i, time_j, active_i, active_j);
138 context->working_model->mutable_variables(i_lesseq_j)
139 ->set_name(absl::StrCat(i,
" before ", j));
140 precedence_cache[{i, j}] = i_lesseq_j;
141 const int j_lesseq_i =
context->GetOrCreateReifiedPrecedenceLiteral(
142 time_j, time_i, active_j, active_i);
143 context->working_model->mutable_variables(j_lesseq_i)
144 ->set_name(absl::StrCat(j,
" before ", i));
145 precedence_cache[{j, i}] = j_lesseq_i;
153 for (
int i = 0; i < num_events; ++i) {
154 const int active_i = is_active_literal(i);
155 if (
context->LiteralIsFalse(active_i))
continue;
158 ConstraintProto*
const level =
context->working_model->add_constraints();
159 level->add_enforcement_literal(active_i);
163 for (
int j = 0; j < num_events; ++j) {
164 if (i == j)
continue;
165 const int active_j = is_active_literal(j);
166 if (
context->LiteralIsFalse(active_j))
continue;
168 const auto prec_it = precedence_cache.find({j, i});
169 CHECK(prec_it != precedence_cache.end());
170 const int prec_lit = prec_it->second;
171 const int64_t
demand =
context->FixedValue(reservoir.level_changes(j));
173 level->mutable_linear()->add_vars(prec_lit);
174 level->mutable_linear()->add_coeffs(
demand);
176 level->mutable_linear()->add_vars(prec_lit);
177 level->mutable_linear()->add_coeffs(-
demand);
183 const int64_t demand_i =
context->FixedValue(reservoir.level_changes(i));
184 level->mutable_linear()->add_domain(
185 CapAdd(
CapSub(reservoir.min_level(), demand_i), offset));
186 level->mutable_linear()->add_domain(
187 CapAdd(
CapSub(reservoir.max_level(), demand_i), offset));
193 context->working_model->add_constraints()->mutable_linear();
194 for (
int i = 0; i < num_events; ++i) {
195 sum->add_vars(is_active_literal(i));
196 sum->add_coeffs(
context->FixedValue(reservoir.level_changes(i)));
198 sum->add_domain(reservoir.min_level());
199 sum->add_domain(reservoir.max_level());
203 context->UpdateRuleStats(
"reservoir: expanded");
206 void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
207 const LinearArgumentProto& int_mod =
ct->int_mod();
208 const LinearExpressionProto& mod_expr = int_mod.exprs(1);
209 if (
context->IsFixed(mod_expr))
return;
211 const LinearExpressionProto& expr = int_mod.exprs(0);
212 const LinearExpressionProto& target_expr = int_mod.target();
215 if (!
context->IntersectDomainWith(
216 target_expr,
context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
217 context->DomainSuperSetOf(mod_expr)))) {
222 auto new_enforced_constraint = [&]() {
223 ConstraintProto* new_ct =
context->working_model->add_constraints();
224 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
229 const int div_var =
context->NewIntVar(
230 context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
231 context->DomainSuperSetOf(mod_expr)));
232 LinearExpressionProto div_expr;
233 div_expr.add_vars(div_var);
234 div_expr.add_coeffs(1);
236 LinearArgumentProto*
const div_proto =
237 new_enforced_constraint()->mutable_int_div();
238 *div_proto->mutable_target() = div_expr;
239 *div_proto->add_exprs() = expr;
240 *div_proto->add_exprs() = mod_expr;
243 const Domain prod_domain =
245 .ContinuousMultiplicationBy(
context->DomainSuperSetOf(mod_expr))
246 .IntersectionWith(
context->DomainSuperSetOf(expr).AdditionWith(
247 context->DomainSuperSetOf(target_expr).Negation()));
248 const int prod_var =
context->NewIntVar(prod_domain);
249 LinearExpressionProto prod_expr;
250 prod_expr.add_vars(prod_var);
251 prod_expr.add_coeffs(1);
253 LinearArgumentProto*
const int_prod =
254 new_enforced_constraint()->mutable_int_prod();
255 *int_prod->mutable_target() = prod_expr;
256 *int_prod->add_exprs() = div_expr;
257 *int_prod->add_exprs() = mod_expr;
260 LinearConstraintProto*
const lin =
261 new_enforced_constraint()->mutable_linear();
269 context->UpdateRuleStats(
"int_mod: expanded");
273 void ExpandIntProdWithBoolean(
int bool_ref,
274 const LinearExpressionProto& int_expr,
275 const LinearExpressionProto& product_expr,
277 ConstraintProto*
const one =
context->working_model->add_constraints();
278 one->add_enforcement_literal(bool_ref);
279 one->mutable_linear()->add_domain(0);
280 one->mutable_linear()->add_domain(0);
283 one->mutable_linear());
285 ConstraintProto*
const zero =
context->working_model->add_constraints();
286 zero->add_enforcement_literal(
NegatedRef(bool_ref));
287 zero->mutable_linear()->add_domain(0);
288 zero->mutable_linear()->add_domain(0);
290 zero->mutable_linear());
293 void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
294 const LinearArgumentProto& int_prod =
ct->int_prod();
295 if (int_prod.exprs_size() != 2)
return;
296 const LinearExpressionProto&
a = int_prod.exprs(0);
297 const LinearExpressionProto&
b = int_prod.exprs(1);
298 const LinearExpressionProto& p = int_prod.target();
305 if (a_is_literal && !b_is_literal) {
308 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
309 }
else if (b_is_literal) {
312 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
316 void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
317 const auto& f_direct =
ct->inverse().f_direct();
318 const auto& f_inverse =
ct->inverse().f_inverse();
319 const int n = f_direct.size();
320 CHECK_EQ(n, f_inverse.size());
328 absl::flat_hash_set<int> used_variables;
329 for (
const int ref : f_direct) {
331 if (!
context->IntersectDomainWith(ref, Domain(0, n - 1))) {
332 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
336 for (
const int ref : f_inverse) {
338 if (!
context->IntersectDomainWith(ref, Domain(0, n - 1))) {
339 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
346 if (used_variables.size() != 2 * n) {
347 for (
int i = 0; i < n; ++i) {
348 for (
int j = 0; j < n; ++j) {
353 if (i == j)
continue;
354 if (!
context->IntersectDomainWith(
364 std::vector<int64_t> possible_values;
367 const auto filter_inverse_domain =
368 [
context, n, &possible_values](
const auto& direct,
const auto& inverse) {
370 for (
int i = 0; i < n; ++i) {
371 possible_values.clear();
372 const Domain domain =
context->DomainOf(direct[i]);
373 bool removed_value =
false;
374 for (
const int64_t j : domain.Values()) {
375 if (
context->DomainOf(inverse[j]).Contains(i)) {
376 possible_values.push_back(j);
378 removed_value =
true;
382 if (!
context->IntersectDomainWith(
384 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
394 if (!filter_inverse_domain(f_direct, f_inverse))
return;
395 if (!filter_inverse_domain(f_inverse, f_direct))
return;
401 for (
int i = 0; i < n; ++i) {
402 const int f_i = f_direct[i];
403 for (
const int64_t j :
context->DomainOf(f_i).Values()) {
405 const int r_j = f_inverse[j];
407 if (
context->HasVarValueEncoding(r_j, i, &r_j_i)) {
408 context->InsertVarValueEncoding(r_j_i, f_i, j);
410 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
411 context->InsertVarValueEncoding(f_i_j, r_j, i);
417 context->UpdateRuleStats(
"inverse: expanded");
421 void ExpandElementWithTargetEqualIndex(ConstraintProto*
ct,
423 const ElementConstraintProto& element =
ct->element();
424 DCHECK_EQ(element.index(), element.target());
426 const int index_ref = element.index();
427 std::vector<int64_t> valid_indices;
428 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
429 if (!
context->DomainContains(element.vars(v), v))
continue;
430 valid_indices.push_back(v);
432 if (valid_indices.size() <
context->DomainOf(index_ref).Size()) {
433 if (!
context->IntersectDomainWith(index_ref,
435 VLOG(1) <<
"No compatible variable domains in "
436 "ExpandElementWithTargetEqualIndex()";
439 context->UpdateRuleStats(
"element: reduced index domain");
442 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
443 const int var = element.vars(v);
446 context->GetOrCreateVarValueEncoding(index_ref, v),
var, Domain(v));
449 "element: expanded with special case target = index");
454 void ExpandConstantArrayElement(ConstraintProto*
ct, PresolveContext*
context) {
455 const ElementConstraintProto& element =
ct->element();
456 const int index_ref = element.index();
457 const int target_ref = element.target();
460 const Domain index_domain =
context->DomainOf(index_ref);
461 const Domain target_domain =
context->DomainOf(target_ref);
468 absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
470 absl::flat_hash_map<int64_t, int> constant_var_values_usage;
471 for (
const int64_t v : index_domain.Values()) {
472 DCHECK(
context->IsFixed(element.vars(v)));
474 if (++constant_var_values_usage[
value] == 2) {
476 BoolArgumentProto*
const support =
477 context->working_model->add_constraints()->mutable_bool_or();
478 const int target_literal =
479 context->GetOrCreateVarValueEncoding(target_ref,
value);
480 support->add_literals(
NegatedRef(target_literal));
481 supports[
value] = support;
490 context->working_model->add_constraints()->mutable_exactly_one();
491 for (
const int64_t v : index_domain.Values()) {
492 const int index_literal =
493 context->GetOrCreateVarValueEncoding(index_ref, v);
494 exactly_one->add_literals(index_literal);
497 const auto& it = supports.find(
value);
498 if (it != supports.end()) {
501 const int target_literal =
502 context->GetOrCreateVarValueEncoding(target_ref,
value);
503 context->AddImplication(index_literal, target_literal);
504 it->second->add_literals(index_literal);
507 context->InsertVarValueEncoding(index_literal, target_ref,
value);
512 context->UpdateRuleStats(
"element: expanded value element");
517 void ExpandVariableElement(ConstraintProto*
ct, PresolveContext*
context) {
518 const ElementConstraintProto& element =
ct->element();
519 const int index_ref = element.index();
520 const int target_ref = element.target();
521 const Domain index_domain =
context->DomainOf(index_ref);
523 BoolArgumentProto* exactly_one =
524 context->working_model->add_constraints()->mutable_exactly_one();
526 for (
const int64_t v : index_domain.Values()) {
527 const int var = element.vars(v);
528 const Domain var_domain =
context->DomainOf(
var);
529 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
530 exactly_one->add_literals(index_lit);
532 if (var_domain.IsFixed()) {
533 context->AddImplyInDomain(index_lit, target_ref, var_domain);
535 ConstraintProto*
const ct =
context->working_model->add_constraints();
536 ct->add_enforcement_literal(index_lit);
537 ct->mutable_linear()->add_vars(
var);
538 ct->mutable_linear()->add_coeffs(1);
539 ct->mutable_linear()->add_vars(target_ref);
540 ct->mutable_linear()->add_coeffs(-1);
541 ct->mutable_linear()->add_domain(0);
542 ct->mutable_linear()->add_domain(0);
546 context->UpdateRuleStats(
"element: expanded");
550 void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
551 const ElementConstraintProto& element =
ct->element();
553 const int index_ref = element.index();
554 const int target_ref = element.target();
555 const int size = element.vars_size();
559 if (!
context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
560 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
565 if (index_ref == target_ref) {
566 ExpandElementWithTargetEqualIndex(
ct,
context);
571 bool all_constants =
true;
572 std::vector<int64_t> valid_indices;
573 const Domain index_domain =
context->DomainOf(index_ref);
574 const Domain target_domain =
context->DomainOf(target_ref);
575 Domain reached_domain;
576 for (
const int64_t v : index_domain.Values()) {
577 const Domain var_domain =
context->DomainOf(element.vars(v));
578 if (var_domain.IntersectionWith(target_domain).IsEmpty())
continue;
580 valid_indices.push_back(v);
581 reached_domain = reached_domain.UnionWith(var_domain);
582 if (var_domain.Min() != var_domain.Max()) {
583 all_constants =
false;
587 if (valid_indices.size() < index_domain.Size()) {
588 if (!
context->IntersectDomainWith(index_ref,
590 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
594 context->UpdateRuleStats(
"element: reduced index domain");
599 bool target_domain_changed =
false;
600 if (!
context->IntersectDomainWith(target_ref, reached_domain,
601 &target_domain_changed)) {
605 if (target_domain_changed) {
606 context->UpdateRuleStats(
"element: reduced target domain");
619 void LinkLiteralsAndValues(
const std::vector<int>& literals,
620 const std::vector<int64_t>& values,
621 const absl::flat_hash_map<int64_t, int>& encoding,
623 CHECK_EQ(literals.size(), values.size());
628 absl::btree_map<int, std::vector<int>> encoding_lit_to_support;
633 for (
int i = 0; i < values.size(); ++i) {
634 encoding_lit_to_support[encoding.at(values[i])].push_back(literals[i]);
639 for (
const auto& [encoding_lit, support] : encoding_lit_to_support) {
640 CHECK(!support.empty());
641 if (support.size() == 1) {
642 context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
644 BoolArgumentProto* bool_or =
645 context->working_model->add_constraints()->mutable_bool_or();
646 bool_or->add_literals(
NegatedRef(encoding_lit));
647 for (
const int lit : support) {
648 bool_or->add_literals(lit);
649 context->AddImplication(lit, encoding_lit);
657 void AddImplyInReachableValues(
int literal,
658 std::vector<int64_t>& reachable_values,
659 const absl::flat_hash_map<int64_t, int> encoding,
662 if (reachable_values.size() == encoding.size())
return;
663 if (reachable_values.size() <= encoding.size() / 2) {
665 ConstraintProto*
ct =
context->working_model->add_constraints();
667 BoolArgumentProto* bool_or =
ct->mutable_bool_or();
668 for (
const int64_t v : reachable_values) {
669 bool_or->add_literals(encoding.at(v));
673 absl::flat_hash_set<int64_t> set(reachable_values.begin(),
674 reachable_values.end());
675 ConstraintProto*
ct =
context->working_model->add_constraints();
677 BoolArgumentProto* bool_and =
ct->mutable_bool_and();
679 if (!set.contains(
value)) {
686 void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
687 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
689 if (
proto.vars_size() == 0) {
690 const int64_t initial_state =
proto.starting_state();
691 for (
const int64_t final_state :
proto.final_states()) {
692 if (initial_state == final_state) {
693 context->UpdateRuleStats(
"automaton: empty and trivially feasible");
698 return (
void)
context->NotifyThatModelIsUnsat(
699 "automaton: empty with an initial state not in the final states.");
700 }
else if (
proto.transition_label_size() == 0) {
701 return (
void)
context->NotifyThatModelIsUnsat(
702 "automaton: non-empty with no transition.");
705 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
706 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
714 absl::flat_hash_map<int64_t, int> encoding;
715 absl::flat_hash_map<int64_t, int> in_encoding;
716 absl::flat_hash_map<int64_t, int> out_encoding;
717 bool removed_values =
false;
719 const int n =
proto.vars_size();
720 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
725 std::vector<int64_t> in_states;
726 std::vector<int64_t> labels;
727 std::vector<int64_t> out_states;
728 for (
int i = 0; i <
proto.transition_label_size(); ++i) {
729 const int64_t
tail =
proto.transition_tail(i);
730 const int64_t label =
proto.transition_label(i);
731 const int64_t
head =
proto.transition_head(i);
733 if (!reachable_states[
time].contains(
tail))
continue;
734 if (!reachable_states[
time + 1].contains(
head))
continue;
735 if (!
context->DomainContains(vars[
time], label))
continue;
740 in_states.push_back(
tail);
741 labels.push_back(label);
745 out_states.push_back(
time + 1 == n ? 0 :
head);
749 const int num_tuples = in_states.size();
750 if (num_tuples == 1) {
751 if (!
context->IntersectDomainWith(vars[
time], Domain(labels.front()))) {
752 VLOG(1) <<
"Infeasible automaton.";
759 std::vector<int> at_false;
763 for (
const int literal : at_false) {
773 std::vector<int64_t> transitions = labels;
777 if (!
context->IntersectDomainWith(
779 VLOG(1) <<
"Infeasible automaton.";
786 for (
const int64_t v :
context->DomainOf(vars[
time]).Values()) {
787 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
794 absl::flat_hash_map<int64_t, int> in_count;
795 absl::flat_hash_map<int64_t, int> transition_count;
796 absl::flat_hash_map<int64_t, int> out_count;
797 for (
int i = 0; i < num_tuples; ++i) {
798 in_count[in_states[i]]++;
799 transition_count[labels[i]]++;
800 out_count[out_states[i]]++;
807 std::vector<int64_t> states = out_states;
810 out_encoding.clear();
811 if (states.size() == 2) {
813 out_encoding[states[0]] =
var;
815 }
else if (states.size() > 2) {
816 struct UniqueDetector {
817 void Set(int64_t v) {
818 if (!is_unique)
return;
820 if (v !=
value) is_unique =
false;
827 bool is_unique =
true;
833 absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
834 absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
835 for (
int i = 0; i < num_tuples; ++i) {
836 out_to_in[out_states[i]].Set(in_states[i]);
837 out_to_transition[out_states[i]].Set(labels[i]);
840 for (
const int64_t state : states) {
843 if (!in_encoding.empty() && out_to_in[state].is_unique) {
844 const int64_t unique_in = out_to_in[state].value;
845 if (in_count[unique_in] == out_count[state]) {
846 out_encoding[state] = in_encoding[unique_in];
853 if (!encoding.empty() && out_to_transition[state].is_unique) {
854 const int64_t unique_transition = out_to_transition[state].value;
855 if (transition_count[unique_transition] == out_count[state]) {
856 out_encoding[state] = encoding[unique_transition];
861 out_encoding[state] =
context->NewBoolVar();
879 const int num_involved_variables =
880 in_encoding.size() + encoding.size() + out_encoding.size();
881 const bool use_light_encoding = (num_tuples > num_involved_variables);
882 if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
883 !out_encoding.empty()) {
887 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
888 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
889 for (
int i = 0; i < num_tuples; ++i) {
890 in_to_label[in_states[i]].push_back(labels[i]);
891 in_to_out[in_states[i]].push_back(out_states[i]);
893 for (
const auto [in_value, in_literal] : in_encoding) {
894 AddImplyInReachableValues(in_literal, in_to_label[in_value], encoding,
896 AddImplyInReachableValues(in_literal, in_to_out[in_value], out_encoding,
901 for (
int i = 0; i < num_tuples; ++i) {
903 context->working_model->add_constraints()->mutable_bool_or();
904 bool_or->add_literals(
NegatedRef(in_encoding.at(in_states[i])));
905 bool_or->add_literals(
NegatedRef(encoding.at(labels[i])));
906 bool_or->add_literals(out_encoding.at(out_states[i]));
909 in_encoding.swap(out_encoding);
910 out_encoding.clear();
918 std::vector<int> tuple_literals;
919 if (num_tuples == 2) {
920 const int bool_var =
context->NewBoolVar();
921 tuple_literals.push_back(bool_var);
922 tuple_literals.push_back(
NegatedRef(bool_var));
927 BoolArgumentProto* exactly_one =
928 context->working_model->add_constraints()->mutable_exactly_one();
929 for (
int i = 0; i < num_tuples; ++i) {
931 if (in_count[in_states[i]] == 1 && !in_encoding.empty()) {
932 tuple_literal = in_encoding[in_states[i]];
933 }
else if (transition_count[labels[i]] == 1 && !encoding.empty()) {
934 tuple_literal = encoding[labels[i]];
935 }
else if (out_count[out_states[i]] == 1 && !out_encoding.empty()) {
936 tuple_literal = out_encoding[out_states[i]];
938 tuple_literal =
context->NewBoolVar();
941 tuple_literals.push_back(tuple_literal);
942 exactly_one->add_literals(tuple_literal);
946 if (!in_encoding.empty()) {
947 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
949 if (!encoding.empty()) {
950 LinkLiteralsAndValues(tuple_literals, labels, encoding,
context);
952 if (!out_encoding.empty()) {
953 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
956 in_encoding.swap(out_encoding);
957 out_encoding.clear();
960 if (removed_values) {
961 context->UpdateRuleStats(
"automaton: reduced variable domains");
963 context->UpdateRuleStats(
"automaton: expanded");
967 void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
968 TableConstraintProto& table = *
ct->mutable_table();
969 const int num_vars = table.vars_size();
970 const int num_original_tuples = table.values_size() / num_vars;
971 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
973 for (
int i = 0; i < num_original_tuples; ++i) {
974 for (
int j = 0; j < num_vars; ++j) {
975 tuples[i].push_back(table.values(count++));
979 if (tuples.empty()) {
980 context->UpdateRuleStats(
"table: empty negated constraint");
986 std::vector<int64_t> domain_sizes;
987 for (
int i = 0; i < num_vars; ++i) {
988 domain_sizes.push_back(
context->DomainOf(table.vars(i)).Size());
993 std::vector<int> clause;
994 for (
const std::vector<int64_t>& tuple : tuples) {
996 for (
int i = 0; i < num_vars; ++i) {
997 const int64_t
value = tuple[i];
1001 context->GetOrCreateVarValueEncoding(table.vars(i),
value);
1006 BoolArgumentProto* bool_or =
1007 context->working_model->add_constraints()->mutable_bool_or();
1008 for (
const int lit : clause) {
1009 bool_or->add_literals(lit);
1012 context->UpdateRuleStats(
"table: expanded negated constraint");
1021 void ProcessOneCompressedColumn(
1022 int variable,
const std::vector<int>& tuple_literals,
1023 const std::vector<absl::InlinedVector<int64_t, 2>>& values,
1025 DCHECK_EQ(tuple_literals.size(), values.size());
1032 std::vector<std::pair<int64_t, int>> pairs;
1033 std::vector<int> any_values_literals;
1034 for (
int i = 0; i < values.size(); ++i) {
1035 if (values[i].empty()) {
1036 any_values_literals.push_back(tuple_literals[i]);
1039 ConstraintProto* clause =
context->working_model->add_constraints();
1040 clause->add_enforcement_literal(tuple_literals[i]);
1041 for (
const int64_t v : values[i]) {
1042 DCHECK(
context->DomainContains(variable, v));
1043 clause->mutable_bool_or()->add_literals(
1044 context->GetOrCreateVarValueEncoding(variable, v));
1045 pairs.emplace_back(v, tuple_literals[i]);
1051 std::vector<int> selected;
1052 std::sort(pairs.begin(), pairs.end());
1053 for (
int i = 0; i < pairs.size();) {
1055 const int64_t
value = pairs[i].first;
1056 for (; i < pairs.size() && pairs[i].first ==
value; ++i) {
1057 selected.push_back(pairs[i].second);
1060 BoolArgumentProto* no_support =
1061 context->working_model->add_constraints()->mutable_bool_or();
1062 for (
const int lit : selected) {
1063 no_support->add_literals(lit);
1065 for (
const int lit : any_values_literals) {
1066 no_support->add_literals(lit);
1070 const int value_literal =
1072 no_support->add_literals(
NegatedRef(value_literal));
1077 void AddSizeTwoTable(
1078 const std::vector<int>& vars,
1079 const std::vector<std::vector<int64_t>>& tuples,
1080 const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1082 CHECK_EQ(vars.size(), 2);
1083 const int left_var = vars[0];
1084 const int right_var = vars[1];
1085 if (
context->DomainOf(left_var).IsFixed() ||
1086 context->DomainOf(right_var).IsFixed()) {
1092 absl::btree_map<int, std::vector<int>> left_to_right;
1093 absl::btree_map<int, std::vector<int>> right_to_left;
1095 for (
const auto& tuple : tuples) {
1096 const int64_t left_value(tuple[0]);
1097 const int64_t right_value(tuple[1]);
1098 DCHECK(
context->DomainContains(left_var, left_value));
1099 DCHECK(
context->DomainContains(right_var, right_value));
1101 const int left_literal =
1102 context->GetOrCreateVarValueEncoding(left_var, left_value);
1103 const int right_literal =
1104 context->GetOrCreateVarValueEncoding(right_var, right_value);
1105 left_to_right[left_literal].push_back(right_literal);
1106 right_to_left[right_literal].push_back(left_literal);
1109 int num_implications = 0;
1110 int num_clause_added = 0;
1111 int num_large_clause_added = 0;
1112 auto add_support_constraint =
1113 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1114 int lit,
const std::vector<int>& support_literals,
1115 int max_support_size) {
1116 if (support_literals.size() == max_support_size)
return;
1117 if (support_literals.size() == 1) {
1118 context->AddImplication(lit, support_literals.front());
1121 BoolArgumentProto* bool_or =
1122 context->working_model->add_constraints()->mutable_bool_or();
1123 for (
const int support_literal : support_literals) {
1124 bool_or->add_literals(support_literal);
1128 if (support_literals.size() > max_support_size / 2) {
1129 num_large_clause_added++;
1134 for (
const auto& it : left_to_right) {
1135 add_support_constraint(it.first, it.second, values_per_var[1].size());
1137 for (
const auto& it : right_to_left) {
1138 add_support_constraint(it.first, it.second, values_per_var[0].size());
1140 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1141 << num_clause_added <<
" clauses, including "
1142 << num_large_clause_added <<
" large clauses, " << num_implications
1150 bool ReduceTableInPresenceOfUniqueVariableWithCosts(
1151 std::vector<int>* vars, std::vector<std::vector<int64_t>>* tuples,
1153 const int num_vars = vars->size();
1155 std::vector<bool> only_here_and_in_objective(num_vars,
false);
1156 std::vector<int64_t> objective_coeffs(num_vars, 0.0);
1157 std::vector<int> new_vars;
1158 std::vector<int> deleted_vars;
1159 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1160 const int var = (*vars)[var_index];
1163 if (
context->VariableWithCostIsUniqueAndRemovable(
var)) {
1164 context->UpdateRuleStats(
"table: removed unused column with cost");
1165 only_here_and_in_objective[var_index] =
true;
1166 objective_coeffs[var_index] =
1171 deleted_vars.push_back(
var);
1172 }
else if (
context->VariableIsUniqueAndRemovable(
var)) {
1175 context->UpdateRuleStats(
"table: removed unused column");
1176 only_here_and_in_objective[var_index] =
true;
1177 objective_coeffs[var_index] = 0;
1179 deleted_vars.push_back(
var);
1181 new_vars.push_back(
var);
1184 if (new_vars.size() == num_vars)
return false;
1189 std::vector<int64_t> temp;
1190 for (
int i = 0; i < tuples->size(); ++i) {
1194 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1195 const int64_t
value = (*tuples)[i][var_index];
1196 if (only_here_and_in_objective[var_index]) {
1197 temp.push_back(
value);
1198 const int64_t objective_coeff = objective_coeffs[var_index];
1201 (*tuples)[i][new_size++] =
value;
1204 (*tuples)[i].resize(new_size);
1205 (*tuples)[i].push_back(
cost);
1210 (*tuples)[i].insert((*tuples)[i].
end(), temp.begin(), temp.end());
1218 const int old_size = tuples->size();
1219 std::sort(tuples->begin(), tuples->end());
1220 for (
int i = 0; i < tuples->size(); ++i) {
1224 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
1225 if ((*tuples)[i][var_index] != (*tuples)[new_size - 1][var_index]) {
1235 for (
int j = 0; j < deleted_vars.size(); ++j) {
1236 ConstraintProto* new_ct =
context->mapping_model->add_constraints();
1237 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
1238 new_ct->add_enforcement_literal(
context->GetOrCreateVarValueEncoding(
1239 new_vars[var_index], (*tuples)[i][var_index]));
1241 new_ct->mutable_linear()->add_vars(deleted_vars[j]);
1242 new_ct->mutable_linear()->add_coeffs(1);
1243 new_ct->mutable_linear()->add_domain(
1244 (*tuples)[i][new_vars.size() + 1 + j]);
1245 new_ct->mutable_linear()->add_domain(
1246 (*tuples)[i][new_vars.size() + 1 + j]);
1248 (*tuples)[i].resize(new_vars.size() + 1);
1249 (*tuples)[new_size++] = (*tuples)[i];
1251 tuples->resize(new_size);
1252 if (new_size < old_size) {
1254 "table: removed duplicate tuples with different costs");
1259 context->AddToObjectiveOffset(min_cost);
1260 context->UpdateRuleStats(
"table: transferred min_cost to objective offset");
1261 for (
int i = 0; i < tuples->size(); ++i) {
1262 (*tuples)[i].back() -= min_cost;
1269 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
1270 absl::flat_hash_map<int64_t, int64_t> value_to_min_cost;
1271 const int num_tuples = tuples->size();
1272 for (
int i = 0; i < num_tuples; ++i) {
1273 const int64_t v = (*tuples)[i][var_index];
1274 const int64_t
cost = (*tuples)[i].back();
1275 auto insert = value_to_min_cost.insert({v,
cost});
1276 if (!insert.second) {
1277 insert.first->second =
std::min(insert.first->second,
cost);
1280 for (
int i = 0; i < num_tuples; ++i) {
1281 const int64_t v = (*tuples)[i][var_index];
1282 (*tuples)[i].back() -= value_to_min_cost.at(v);
1284 for (
const auto entry : value_to_min_cost) {
1285 if (entry.second == 0)
continue;
1286 context->UpdateRuleStats(
"table: transferred cost to encoding");
1287 const int value_literal =
context->GetOrCreateVarValueEncoding(
1288 new_vars[var_index], entry.first);
1289 context->AddLiteralToObjective(value_literal, entry.second);
1293 context->UpdateRuleStats(absl::StrCat(
1294 "table: expansion with column(s) only in objective. Arity = ",
1303 void CompressAndExpandPositiveTable(
bool last_column_is_cost,
1304 const std::vector<int>& vars,
1305 std::vector<std::vector<int64_t>>* tuples,
1307 const int num_tuples_before_compression = tuples->size();
1311 std::vector<int64_t> domain_sizes;
1312 for (
const int var : vars) {
1313 domain_sizes.push_back(
context->DomainOf(
var).Size());
1315 if (last_column_is_cost) {
1320 const int compression_level =
context->params().table_compression_level();
1321 if (compression_level > 0) {
1324 const int num_tuples_after_first_compression = tuples->size();
1337 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> compressed_table;
1338 if (compression_level > 2 ||
1339 (compression_level == 2 && num_tuples_after_first_compression > 1000)) {
1341 if (compressed_table.size() < num_tuples_before_compression) {
1342 context->UpdateRuleStats(
"table: fully compress tuples");
1346 for (
int i = 0; i < tuples->size(); ++i) {
1347 compressed_table.push_back({});
1348 for (
const int64_t v : (*tuples)[i]) {
1350 compressed_table.back().push_back({});
1352 compressed_table.back().push_back({v});
1356 if (compressed_table.size() < num_tuples_before_compression) {
1357 context->UpdateRuleStats(
"table: compress tuples");
1361 VLOG(2) <<
"Table compression"
1362 <<
" var=" << vars.size()
1363 <<
" cost=" << domain_sizes.size() - vars.size()
1364 <<
" tuples= " << num_tuples_before_compression <<
" -> "
1365 << num_tuples_after_first_compression <<
" -> "
1366 << compressed_table.size();
1369 std::sort(compressed_table.begin(), compressed_table.end());
1371 const int num_vars = vars.size();
1372 if (compressed_table.size() == 1) {
1374 context->UpdateRuleStats(
"table: one tuple");
1375 if (last_column_is_cost) {
1378 context->AddToObjectiveOffset(compressed_table[0].back()[0]);
1386 std::vector<bool> has_any(num_vars,
false);
1387 std::vector<absl::flat_hash_map<int64_t, int>> var_index_to_value_count(
1389 for (
int i = 0; i < compressed_table.size(); ++i) {
1390 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1391 if (compressed_table[i][var_index].empty()) {
1392 has_any[var_index] =
true;
1395 for (
const int64_t v : compressed_table[i][var_index]) {
1397 DCHECK(
context->DomainContains(vars[var_index], v));
1398 var_index_to_value_count[var_index][v]++;
1405 BoolArgumentProto* exactly_one =
1406 context->working_model->add_constraints()->mutable_exactly_one();
1408 int64_t num_reused_variables = 0;
1409 std::vector<int> tuple_literals(compressed_table.size());
1410 for (
int i = 0; i < compressed_table.size(); ++i) {
1411 bool create_new_var =
true;
1412 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1413 if (has_any[var_index])
continue;
1414 if (compressed_table[i][var_index].size() != 1)
continue;
1415 const int64_t v = compressed_table[i][var_index][0];
1416 if (var_index_to_value_count[var_index][v] != 1)
continue;
1418 ++num_reused_variables;
1419 create_new_var =
false;
1421 context->GetOrCreateVarValueEncoding(vars[var_index], v);
1424 if (create_new_var) {
1425 tuple_literals[i] =
context->NewBoolVar();
1427 exactly_one->add_literals(tuple_literals[i]);
1429 if (num_reused_variables > 0) {
1430 context->UpdateRuleStats(
"table: reused literals");
1435 if (last_column_is_cost) {
1436 for (
int i = 0; i < tuple_literals.size(); ++i) {
1437 context->AddLiteralToObjective(tuple_literals[i],
1438 compressed_table[i].back()[0]);
1442 std::vector<absl::InlinedVector<int64_t, 2>>
column;
1443 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1444 if (
context->IsFixed(vars[var_index]))
continue;
1447 for (
int i = 0; i < tuple_literals.size(); ++i) {
1448 column.push_back(compressed_table[i][var_index]);
1450 ProcessOneCompressedColumn(vars[var_index], tuple_literals,
column,
1454 context->UpdateRuleStats(
"table: expanded positive constraint");
1464 void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1465 const TableConstraintProto& table =
ct->table();
1466 const int num_vars = table.vars_size();
1467 const int num_original_tuples = table.values_size() / num_vars;
1470 std::vector<int> vars(table.vars().begin(), table.vars().end());
1471 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1473 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1474 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1475 tuples[tuple_index].push_back(table.values(count++));
1481 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1483 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1485 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1486 const int64_t
value = tuples[tuple_index][var_index];
1487 if (!
context->DomainContains(vars[var_index],
value)) {
1493 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1494 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1496 std::swap(tuples[tuple_index], tuples[new_size]);
1500 tuples.resize(new_size);
1502 if (tuples.empty()) {
1503 context->UpdateRuleStats(
"table: empty");
1504 return (
void)
context->NotifyThatModelIsUnsat();
1510 int num_fixed_variables = 0;
1511 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1512 CHECK(
context->IntersectDomainWith(
1515 values_per_var[var_index].end()})));
1516 if (
context->DomainOf(vars[var_index]).IsFixed()) {
1517 num_fixed_variables++;
1521 if (num_fixed_variables == num_vars - 1) {
1522 context->UpdateRuleStats(
"table: one variable not fixed");
1525 }
else if (num_fixed_variables == num_vars) {
1526 context->UpdateRuleStats(
"table: all variables fixed");
1536 if (num_vars == 2 && !
context->params().detect_table_with_cost()) {
1537 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1539 "table: expanded positive constraint with two variables");
1544 bool last_column_is_cost =
false;
1545 if (
context->params().detect_table_with_cost()) {
1546 last_column_is_cost =
1547 ReduceTableInPresenceOfUniqueVariableWithCosts(&vars, &tuples,
context);
1550 CompressAndExpandPositiveTable(last_column_is_cost, vars, &tuples,
context);
1554 bool AllDiffShouldBeExpanded(
const Domain& union_of_domains,
1555 ConstraintProto*
ct, PresolveContext*
context) {
1556 const AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1557 const int num_exprs =
proto.exprs_size();
1558 int num_fully_encoded = 0;
1559 for (
int i = 0; i < num_exprs; ++i) {
1561 num_fully_encoded++;
1565 if ((union_of_domains.Size() <= 2 *
proto.exprs_size()) ||
1566 (union_of_domains.Size() <= 32)) {
1571 if (num_fully_encoded == num_exprs && union_of_domains.Size() < 256) {
1584 void ExpandSomeLinearOfSizeTwo(ConstraintProto*
ct, PresolveContext*
context) {
1585 const LinearConstraintProto& arg =
ct->linear();
1586 if (arg.vars_size() != 2)
return;
1588 const int var1 = arg.vars(0);
1589 const int var2 = arg.vars(1);
1592 const int64_t coeff1 = arg.coeffs(0);
1593 const int64_t coeff2 = arg.coeffs(1);
1594 const Domain reachable_rhs_superset =
1596 .MultiplicationBy(coeff1)
1597 .RelaxIfTooComplex()
1598 .AdditionWith(
context->DomainOf(var2)
1599 .MultiplicationBy(coeff2)
1600 .RelaxIfTooComplex());
1601 const Domain infeasible_reachable_values =
1602 reachable_rhs_superset.IntersectionWith(
1606 if (infeasible_reachable_values.Size() != 1)
return;
1611 int64_t cte = infeasible_reachable_values.FixedValue();
1616 context->UpdateRuleStats(
"linear: expand always feasible ax + by != cte");
1620 const Domain reduced_domain =
1622 .AdditionWith(Domain(-x0))
1623 .InverseMultiplicationBy(
b)
1624 .IntersectionWith(
context->DomainOf(var2)
1625 .AdditionWith(Domain(-y0))
1626 .InverseMultiplicationBy(-
a));
1628 if (reduced_domain.Size() > 16)
return;
1633 const int64_t size1 =
context->DomainOf(var1).Size();
1634 const int64_t size2 =
context->DomainOf(var2).Size();
1635 for (
const int64_t z : reduced_domain.Values()) {
1636 const int64_t value1 = x0 +
b * z;
1637 const int64_t value2 = y0 -
a * z;
1638 DCHECK(
context->DomainContains(var1, value1)) <<
"value1 = " << value1;
1639 DCHECK(
context->DomainContains(var2, value2)) <<
"value2 = " << value2;
1640 DCHECK_EQ(coeff1 * value1 + coeff2 * value2,
1641 infeasible_reachable_values.FixedValue());
1643 if (!
context->HasVarValueEncoding(var1, value1,
nullptr) || size1 == 2) {
1646 if (!
context->HasVarValueEncoding(var2, value2,
nullptr) || size2 == 2) {
1653 for (
const int64_t z : reduced_domain.Values()) {
1654 const int64_t value1 = x0 +
b * z;
1655 const int64_t value2 = y0 -
a * z;
1657 const int lit1 =
context->GetOrCreateVarValueEncoding(var1, value1);
1658 const int lit2 =
context->GetOrCreateVarValueEncoding(var2, value2);
1660 context->working_model->add_constraints()->mutable_bool_or();
1663 for (
const int lit :
ct->enforcement_literal()) {
1668 context->UpdateRuleStats(
"linear: expand small ax + by != cte");
1680 void ExpandComplexLinearConstraint(
int c, ConstraintProto*
ct,
1686 if (
ct->linear().domain().size() <= 2)
return;
1687 if (
ct->linear().vars().size() == 1)
return;
1689 const SatParameters& params =
context->params();
1690 if (params.encode_complex_linear_constraint_with_integer()) {
1696 const int slack =
context->NewIntVar(rhs);
1697 ct->mutable_linear()->add_vars(slack);
1698 ct->mutable_linear()->add_coeffs(-1);
1699 ct->mutable_linear()->clear_domain();
1700 ct->mutable_linear()->add_domain(0);
1701 ct->mutable_linear()->add_domain(0);
1705 BoolArgumentProto* clause =
nullptr;
1706 std::vector<int> domain_literals;
1707 if (
ct->enforcement_literal().empty() &&
ct->linear().domain_size() == 4) {
1710 single_bool =
context->NewBoolVar();
1712 clause =
context->working_model->add_constraints()->mutable_bool_or();
1713 for (
const int ref :
ct->enforcement_literal()) {
1719 const std::vector<int> enforcement_literals(
1720 ct->enforcement_literal().begin(),
ct->enforcement_literal().end());
1721 ct->mutable_enforcement_literal()->Clear();
1722 for (
int i = 0; i <
ct->linear().domain_size(); i += 2) {
1723 const int64_t lb =
ct->linear().domain(i);
1724 const int64_t ub =
ct->linear().domain(i + 1);
1726 int subdomain_literal;
1727 if (clause !=
nullptr) {
1728 subdomain_literal =
context->NewBoolVar();
1729 clause->add_literals(subdomain_literal);
1730 domain_literals.push_back(subdomain_literal);
1732 if (i == 0) domain_literals.push_back(single_bool);
1733 subdomain_literal = i == 0 ? single_bool :
NegatedRef(single_bool);
1738 ConstraintProto* new_ct =
context->working_model->add_constraints();
1740 new_ct->add_enforcement_literal(subdomain_literal);
1745 if (
context->params().enumerate_all_solutions() &&
1746 !enforcement_literals.empty()) {
1747 int linear_is_enforced;
1748 if (enforcement_literals.size() == 1) {
1749 linear_is_enforced = enforcement_literals[0];
1751 linear_is_enforced =
context->NewBoolVar();
1752 BoolArgumentProto* maintain_linear_is_enforced =
1753 context->working_model->add_constraints()->mutable_bool_or();
1754 for (
const int e_lit : enforcement_literals) {
1757 maintain_linear_is_enforced->add_literals(
NegatedRef(e_lit));
1759 maintain_linear_is_enforced->add_literals(linear_is_enforced);
1762 for (
const int lit : domain_literals) {
1770 context->UpdateRuleStats(
"linear: expanded complex rhs");
1771 context->InitializeNewDomains();
1772 context->UpdateNewConstraintsVariableUsage();
1773 context->UpdateConstraintVariableUsage(c);
1776 bool IsVarEqOrNeqValue(PresolveContext*
context,
1777 const LinearConstraintProto& lin) {
1778 if (lin.vars_size() != 1)
return false;
1780 if (rhs.IsFixed())
return true;
1781 return rhs.InverseMultiplicationBy(lin.coeffs(0))
1783 .IntersectionWith(
context->DomainOf(lin.vars(0)))
1800 void ScanModelAndDecideAllDiffExpansion(
1801 ConstraintProto* all_diff_ct, PresolveContext*
context,
1802 absl::flat_hash_set<int>& domain_of_var_is_used,
1803 absl::flat_hash_set<int>& bounds_of_var_are_used,
1804 absl::flat_hash_set<int>& processed_variables,
bool& expand,
bool& keep) {
1805 CHECK_EQ(all_diff_ct->constraint_case(), ConstraintProto::kAllDiff);
1807 bool at_least_one_var_domain_is_used =
false;
1808 bool at_least_one_var_bound_is_used =
false;
1811 for (
const LinearExpressionProto& expr : all_diff_ct->all_diff().exprs()) {
1813 if (expr.vars().empty())
continue;
1814 DCHECK_EQ(1, expr.vars_size());
1815 const int var = expr.vars(0);
1819 bool at_least_one_var_domain_is_used =
false;
1820 bool at_least_one_var_bound_is_used =
false;
1823 if (!processed_variables.insert(
var).second) {
1824 at_least_one_var_domain_is_used = bounds_of_var_are_used.contains(
var);
1825 at_least_one_var_bound_is_used = domain_of_var_is_used.contains(
var);
1827 bool domain_is_used =
false;
1828 bool bounds_are_used =
false;
1831 for (
const int ct_index :
context->VarToConstraints(
var)) {
1833 if (ct_index < 0)
continue;
1835 const ConstraintProto& other_ct =
1836 context->working_model->constraints(ct_index);
1837 switch (other_ct.constraint_case()) {
1838 case ConstraintProto::ConstraintCase::kBoolOr:
1840 case ConstraintProto::ConstraintCase::kBoolAnd:
1842 case ConstraintProto::ConstraintCase::kAtMostOne:
1844 case ConstraintProto::ConstraintCase::kExactlyOne:
1846 case ConstraintProto::ConstraintCase::kBoolXor:
1848 case ConstraintProto::ConstraintCase::kIntDiv:
1850 case ConstraintProto::ConstraintCase::kIntMod:
1852 case ConstraintProto::ConstraintCase::kLinMax:
1853 bounds_are_used =
true;
1855 case ConstraintProto::ConstraintCase::kIntProd:
1857 case ConstraintProto::ConstraintCase::kLinear:
1858 if (IsVarEqOrNeqValue(
context, other_ct.linear()) &&
1859 var == other_ct.linear().vars(0)) {
1861 domain_is_used =
true;
1862 }
else if (other_ct.linear().vars_size() > 2 &&
1863 other_ct.linear().domain_size() == 2 &&
1864 other_ct.linear().domain(0) ==
1865 other_ct.linear().domain(1)) {
1868 bounds_are_used =
true;
1871 case ConstraintProto::ConstraintCase::kAllDiff:
1875 case ConstraintProto::ConstraintCase::kDummyConstraint:
1877 case ConstraintProto::ConstraintCase::kElement:
1879 if (other_ct.element().index() ==
var) {
1880 domain_is_used =
true;
1883 case ConstraintProto::ConstraintCase::kCircuit:
1885 case ConstraintProto::ConstraintCase::kRoutes:
1887 case ConstraintProto::ConstraintCase::kInverse:
1888 domain_is_used =
true;
1890 case ConstraintProto::ConstraintCase::kReservoir:
1892 case ConstraintProto::ConstraintCase::kTable:
1893 domain_is_used =
true;
1895 case ConstraintProto::ConstraintCase::kAutomaton:
1896 domain_is_used =
true;
1898 case ConstraintProto::ConstraintCase::kInterval:
1899 bounds_are_used =
true;
1901 case ConstraintProto::ConstraintCase::kNoOverlap:
1904 case ConstraintProto::ConstraintCase::kNoOverlap2D:
1907 case ConstraintProto::ConstraintCase::kCumulative:
1910 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1915 if (domain_is_used && bounds_are_used)
break;
1919 if (domain_is_used) domain_of_var_is_used.insert(
var);
1920 if (bounds_are_used) bounds_of_var_are_used.insert(
var);
1923 at_least_one_var_domain_is_used |= domain_is_used;
1924 at_least_one_var_bound_is_used |= bounds_are_used;
1927 if (at_least_one_var_domain_is_used && at_least_one_var_bound_is_used) {
1932 expand = at_least_one_var_domain_is_used;
1933 keep = at_least_one_var_bound_is_used;
1936 void MaybeExpandAllDiff(ConstraintProto*
ct, PresolveContext*
context,
1937 absl::flat_hash_set<int>& domain_of_var_is_used,
1938 absl::flat_hash_set<int>& bounds_of_var_are_used,
1939 absl::flat_hash_set<int>& processed_variable) {
1940 const bool expand_all_diff_from_parameters =
1941 context->params().expand_alldiff_constraints();
1942 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
1943 if (
proto.exprs_size() <= 1)
return;
1945 bool keep_after_expansion =
false;
1946 bool expand_all_diff_from_usage =
false;
1947 ScanModelAndDecideAllDiffExpansion(
1948 ct,
context, domain_of_var_is_used, bounds_of_var_are_used,
1949 processed_variable, expand_all_diff_from_usage, keep_after_expansion);
1951 const int num_exprs =
proto.exprs_size();
1952 Domain union_of_domains =
context->DomainSuperSetOf(
proto.exprs(0));
1953 for (
int i = 1; i < num_exprs; ++i) {
1955 union_of_domains.UnionWith(
context->DomainSuperSetOf(
proto.exprs(i)));
1958 const bool expand_all_diff_from_size =
1959 AllDiffShouldBeExpanded(union_of_domains,
ct,
context);
1966 const bool should_expand =
1967 expand_all_diff_from_parameters ||
1968 (expand_all_diff_from_size &&
1969 (expand_all_diff_from_usage || !keep_after_expansion));
1970 if (!should_expand)
return;
1972 const bool is_a_permutation = num_exprs == union_of_domains.Size();
1977 for (
const int64_t v : union_of_domains.Values()) {
1979 std::vector<LinearExpressionProto> possible_exprs;
1980 int fixed_expression_count = 0;
1981 for (
const LinearExpressionProto& expr :
proto.exprs()) {
1982 if (!
context->DomainContains(expr, v))
continue;
1983 possible_exprs.push_back(expr);
1985 fixed_expression_count++;
1989 if (fixed_expression_count > 1) {
1991 return (
void)
context->NotifyThatModelIsUnsat();
1992 }
else if (fixed_expression_count == 1) {
1994 for (
const LinearExpressionProto& expr : possible_exprs) {
1995 if (
context->IsFixed(expr))
continue;
1996 if (!
context->IntersectDomainWith(expr, Domain(v).Complement())) {
1997 VLOG(1) <<
"Empty domain for a variable in MaybeExpandAllDiff()";
2003 BoolArgumentProto* at_most_or_equal_one =
2005 ?
context->working_model->add_constraints()->mutable_exactly_one()
2006 :
context->working_model->add_constraints()->mutable_at_most_one();
2007 for (
const LinearExpressionProto& expr : possible_exprs) {
2010 if (!
context->DomainContains(expr, v))
continue;
2015 const int encoding =
context->GetOrCreateAffineValueEncoding(expr, v);
2016 at_most_or_equal_one->add_literals(encoding);
2021 absl::StrCat(
"all_diff:", is_a_permutation ?
" permutation" :
"",
2022 " expanded", keep_after_expansion ?
" and kept" :
""));
2023 if (!keep_after_expansion)
ct->Clear();
2029 if (
context->params().disable_constraint_expansion())
return;
2030 if (
context->ModelIsUnsat())
return;
2034 if (
context->ModelIsExpanded())
return;
2037 context->InitializeNewDomains();
2040 context->ClearPrecedenceCache();
2042 bool has_all_diffs =
false;
2045 for (
int c = 0; c <
context->working_model->constraints_size(); ++c) {
2046 ConstraintProto*
const ct =
context->working_model->mutable_constraints(c);
2048 switch (
ct->constraint_case()) {
2049 case ConstraintProto::kLinear:
2052 if (
ct->linear().domain().size() > 2 &&
2053 !
context->params().cp_model_presolve()) {
2054 ExpandComplexLinearConstraint(c,
ct,
context);
2057 case ConstraintProto::kReservoir:
2058 if (
context->params().expand_reservoir_constraints()) {
2059 for (
const LinearExpressionProto& demand_expr :
2060 ct->reservoir().level_changes()) {
2061 if (!
context->IsFixed(demand_expr)) {
2068 "reservoir: expansion is not supported with variable level "
2075 case ConstraintProto::kIntMod:
2078 case ConstraintProto::kIntProd:
2081 case ConstraintProto::kElement:
2084 case ConstraintProto::kInverse:
2087 case ConstraintProto::kAutomaton:
2090 case ConstraintProto::kTable:
2091 if (
ct->table().negated()) {
2097 case ConstraintProto::kAllDiff:
2098 has_all_diffs =
true;
2108 context->UpdateNewConstraintsVariableUsage();
2109 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2110 context->UpdateConstraintVariableUsage(c);
2114 if (
context->ModelIsUnsat()) {
2125 absl::flat_hash_set<int> domain_of_var_is_used;
2126 absl::flat_hash_set<int> bounds_of_var_are_used;
2127 absl::flat_hash_set<int> processed_variables;
2128 for (
int i = 0; i <
context->working_model->constraints_size(); ++i) {
2129 ConstraintProto*
const ct =
context->working_model->mutable_constraints(i);
2131 switch (
ct->constraint_case()) {
2132 case ConstraintProto::kAllDiff:
2133 MaybeExpandAllDiff(
ct,
context, domain_of_var_is_used,
2134 bounds_of_var_are_used, processed_variables);
2136 case ConstraintProto::kLinear:
2147 context->UpdateNewConstraintsVariableUsage();
2148 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2149 context->UpdateConstraintVariableUsage(i);
2153 if (
context->ModelIsUnsat()) {
2163 context->ClearPrecedenceCache();
2166 context->InitializeNewDomains();
2169 for (
int i = 0; i <
context->working_model->variables_size(); ++i) {
2171 context->working_model->mutable_variables(i));
2174 context->NotifyThatModelIsExpanded();
2178 if (
context->params().disable_constraint_expansion())
return;
2179 if (
context->ModelIsUnsat())
return;
2180 for (
int c = 0; c <
context->working_model->constraints_size(); ++c) {
2181 ConstraintProto*
const ct =
context->working_model->mutable_constraints(c);
2182 switch (
ct->constraint_case()) {
2183 case ConstraintProto::kLinear:
2184 if (
ct->linear().domain().size() > 2) {
2185 ExpandComplexLinearConstraint(c,
ct,
context);
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)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
bool RefIsPositive(int ref)
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t >> *tuples)
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t >> *tuples)
void ExpandCpModel(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int64_t kTableAnyValue
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t >> *states, std::vector< absl::flat_hash_set< int64_t >> *labels)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)
#define VLOG(verboselevel)