25#include "absl/container/btree_map.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/container/inlined_vector.h"
29#include "absl/log/check.h"
30#include "absl/meta/type_traits.h"
31#include "absl/strings/str_cat.h"
32#include "google/protobuf/message.h"
36#include "ortools/sat/cp_model.pb.h"
40#include "ortools/sat/sat_parameters.pb.h"
53 std::vector<absl::flat_hash_set<int64_t>>* states,
54 std::vector<absl::flat_hash_set<int64_t>>* labels) {
55 const int n =
proto.vars_size();
56 const absl::flat_hash_set<int64_t> final_states(
57 {
proto.final_states().begin(),
proto.final_states().end()});
62 states->resize(n + 1);
63 (*states)[0].insert(
proto.starting_state());
67 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
68 const int64_t
tail =
proto.transition_tail(t);
69 const int64_t label =
proto.transition_label(t);
70 const int64_t
head =
proto.transition_head(t);
71 if (!(*states)[
time].contains(
tail))
continue;
73 if (
time == n - 1 && !final_states.contains(
head))
continue;
74 (*labels)[
time].insert(label);
81 absl::flat_hash_set<int64_t> new_states;
82 absl::flat_hash_set<int64_t> new_labels;
83 for (
int t = 0; t <
proto.transition_tail_size(); ++t) {
84 const int64_t
tail =
proto.transition_tail(t);
85 const int64_t label =
proto.transition_label(t);
86 const int64_t
head =
proto.transition_head(t);
88 if (!(*states)[
time].contains(
tail))
continue;
89 if (!(*labels)[
time].contains(label))
continue;
90 if (!(*states)[
time + 1].contains(
head))
continue;
91 new_labels.insert(label);
92 new_states.insert(
tail);
94 (*labels)[
time].swap(new_labels);
95 (*states)[
time].swap(new_states);
103void ExpandReservoirUsingCircuit(int64_t sum_of_positive_demand,
104 int64_t sum_of_negative_demand,
105 ConstraintProto* reservoir_ct,
107 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
108 const int num_events = reservoir.time_exprs_size();
112 CircuitConstraintProto* circuit =
113 context->working_model->add_constraints()->mutable_circuit();
115 const int64_t var_min =
116 std::max(reservoir.min_level(), sum_of_negative_demand);
117 const int64_t var_max =
118 std::min(reservoir.max_level(), sum_of_positive_demand);
119 std::vector<int> level_vars(num_events);
120 for (
int i = 0;
i < num_events; ++
i) {
127 circuit->add_tails(num_events);
128 circuit->add_heads(num_events);
129 circuit->add_literals(
context->NewBoolVar());
132 for (
int i = 0;
i < num_events; ++
i) {
133 if (!reservoir.active_literals().empty()) {
135 circuit->add_tails(
i);
136 circuit->add_heads(
i);
137 circuit->add_literals(
NegatedRef(reservoir.active_literals(
i)));
144 const int start_var =
context->NewBoolVar();
145 circuit->add_tails(num_events);
146 circuit->add_heads(
i);
147 circuit->add_literals(start_var);
151 ConstraintProto* new_ct =
context->working_model->add_constraints();
152 new_ct->add_enforcement_literal(start_var);
153 LinearConstraintProto* lin = new_ct->mutable_linear();
156 lin->add_vars(level_vars[
i]);
160 context->CanonicalizeLinearConstraint(new_ct);
164 circuit->add_tails(
i);
165 circuit->add_heads(num_events);
166 circuit->add_literals(
context->NewBoolVar());
169 for (
int j = 0; j < num_events; ++j) {
170 if (
i == j)
continue;
182 const int arc_i_j =
context->NewBoolVar();
183 circuit->add_tails(
i);
184 circuit->add_heads(j);
185 circuit->add_literals(arc_i_j);
189 ConstraintProto* new_ct =
context->working_model->add_constraints();
190 new_ct->add_enforcement_literal(arc_i_j);
191 LinearConstraintProto* lin = new_ct->mutable_linear();
193 lin->add_domain(std::numeric_limits<int64_t>::max());
196 context->CanonicalizeLinearConstraint(new_ct);
201 ConstraintProto* new_ct =
context->working_model->add_constraints();
202 new_ct->add_enforcement_literal(arc_i_j);
203 LinearConstraintProto* lin = new_ct->mutable_linear();
206 lin->add_vars(level_vars[j]);
208 lin->add_vars(level_vars[
i]);
212 context->CanonicalizeLinearConstraint(new_ct);
217 reservoir_ct->Clear();
218 context->UpdateRuleStats(
"reservoir: expanded using circuit.");
221void ExpandReservoirUsingPrecedences(int64_t sum_of_positive_demand,
222 int64_t sum_of_negative_demand,
223 ConstraintProto* reservoir_ct,
225 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
226 const int num_events = reservoir.time_exprs_size();
227 const int true_literal =
context->GetTrueLiteral();
228 const auto is_active_literal = [&reservoir, true_literal](
int index) {
229 if (reservoir.active_literals_size() == 0)
return true_literal;
230 return reservoir.active_literals(
index);
235 for (
int i = 0;
i < num_events; ++
i) {
236 const int active_i = is_active_literal(
i);
237 if (
context->LiteralIsFalse(active_i))
continue;
239 const int64_t demand_i =
context->FixedValue(reservoir.level_changes(
i));
240 if (demand_i == 0)
continue;
244 if (demand_i > 0 && sum_of_positive_demand <= reservoir.max_level()) {
247 if (demand_i < 0 && sum_of_negative_demand >= reservoir.min_level()) {
251 ConstraintProto* new_ct =
context->working_model->add_constraints();
252 LinearConstraintProto* new_linear = new_ct->mutable_linear();
256 const LinearExpressionProto& time_i = reservoir.time_exprs(
i);
257 for (
int j = 0; j < num_events; ++j) {
258 if (
i == j)
continue;
259 const int active_j = is_active_literal(j);
260 if (
context->LiteralIsFalse(active_j))
continue;
267 const LinearExpressionProto& time_j = reservoir.time_exprs(j);
268 const int j_lesseq_i =
context->GetOrCreateReifiedPrecedenceLiteral(
269 time_j, time_i, active_j, active_i);
270 context->working_model->mutable_variables(j_lesseq_i)
271 ->set_name(absl::StrCat(j,
" before ",
i));
273 const int64_t
demand =
context->FixedValue(reservoir.level_changes(j));
275 new_linear->add_vars(j_lesseq_i);
276 new_linear->add_coeffs(
demand);
279 new_linear->add_coeffs(-
demand);
290 new_linear->add_vars(active_i);
291 new_linear->add_coeffs(demand_i);
294 new_linear->add_coeffs(-demand_i);
300 new_linear->add_domain(std::numeric_limits<int64_t>::min());
301 new_linear->add_domain(reservoir.max_level());
303 new_linear->add_domain(reservoir.min_level());
304 new_linear->add_domain(std::numeric_limits<int64_t>::max());
307 context->CanonicalizeLinearConstraint(new_ct);
310 reservoir_ct->Clear();
311 context->UpdateRuleStats(
"reservoir: expanded using precedences");
314void ExpandReservoir(ConstraintProto* reservoir_ct, PresolveContext*
context) {
315 if (reservoir_ct->reservoir().min_level() >
316 reservoir_ct->reservoir().max_level()) {
317 VLOG(1) <<
"Empty level domain in reservoir constraint.";
318 return (
void)
context->NotifyThatModelIsUnsat();
321 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
322 const int num_events = reservoir.time_exprs_size();
324 int num_positives = 0;
325 int num_negatives = 0;
326 bool all_demands_are_fixed =
true;
327 int64_t sum_of_positive_demand = 0;
328 int64_t sum_of_negative_demand = 0;
329 for (
const LinearExpressionProto& demand_expr : reservoir.level_changes()) {
330 if (!
context->IsFixed(demand_expr)) {
331 all_demands_are_fixed =
false;
333 const int64_t max_demand =
context->MaxOf(demand_expr);
334 if (max_demand > 0) {
336 sum_of_positive_demand += max_demand;
338 const int64_t min_demand =
context->MinOf(demand_expr);
339 if (min_demand < 0) {
341 sum_of_negative_demand += min_demand;
345 if (sum_of_negative_demand >= reservoir.min_level() &&
346 sum_of_positive_demand <= reservoir.max_level()) {
347 context->UpdateRuleStats(
"reservoir: always true");
348 reservoir_ct->Clear();
355 if (num_negatives == 0 || num_positives == 0) {
356 const int true_literal =
context->GetTrueLiteral();
357 ConstraintProto* new_ct =
context->working_model->add_constraints();
358 LinearConstraintProto* sum = new_ct->mutable_linear();
359 for (
int i = 0;
i < num_events; ++
i) {
360 const int active = reservoir.active_literals().empty()
362 : reservoir.active_literals(
i);
363 const LinearExpressionProto&
demand = reservoir.level_changes(
i);
365 const int64_t change =
context->FixedValue(reservoir.level_changes(
i));
367 sum->add_vars(active);
368 sum->add_coeffs(change);
371 sum->add_vars(true_literal);
372 sum->add_coeffs(change);
374 sum->add_coeffs(-change);
376 }
else if (
context->LiteralIsTrue(active)) {
379 const int new_var =
context->NewIntVar(
381 .UnionWith(Domain(0)));
382 sum->add_vars(new_var);
387 ConstraintProto* demand_ct =
388 context->working_model->add_constraints();
389 demand_ct->add_enforcement_literal(active);
390 LinearConstraintProto* lin = demand_ct->mutable_linear();
393 lin->add_vars(new_var);
396 context->CanonicalizeLinearConstraint(demand_ct);
403 sum->add_domain(reservoir.min_level());
404 sum->add_domain(reservoir.max_level());
405 context->CanonicalizeLinearConstraint(new_ct);
407 context->UpdateRuleStats(
"reservoir: simple expansion with sum");
408 reservoir_ct->Clear();
413 if (
context->params().expand_reservoir_using_circuit()) {
414 ExpandReservoirUsingCircuit(sum_of_positive_demand, sum_of_negative_demand,
418 if (all_demands_are_fixed) {
419 ExpandReservoirUsingPrecedences(sum_of_positive_demand,
420 sum_of_negative_demand, reservoir_ct,
424 "reservoir: skipped expansion due to variable demands");
430void EncodeCumulativeAsReservoir(ConstraintProto*
ct,
432 if (!
context->IsFixed(
ct->cumulative().capacity())) {
434 "cumulative -> reservoir: expansion is not supported with variable "
441 ConstraintProto reservoir_ct;
442 auto* reservoir = reservoir_ct.mutable_reservoir();
443 reservoir->set_min_level(std::numeric_limits<int64_t>::min());
444 reservoir->set_max_level(
context->FixedValue(
ct->cumulative().capacity()));
446 const int true_literal =
context->GetTrueLiteral();
447 const int num_intervals =
ct->cumulative().intervals().size();
448 for (
int i = 0;
i < num_intervals; ++
i) {
449 const auto& interval_ct =
450 context->working_model->constraints(
ct->cumulative().intervals(
i));
451 const auto&
interval = interval_ct.interval();
452 *reservoir->add_time_exprs() =
interval.start();
453 *reservoir->add_time_exprs() =
interval.end();
455 const LinearExpressionProto&
demand =
ct->cumulative().demands(
i);
456 *reservoir->add_level_changes() =
demand;
457 LinearExpressionProto& negated = *reservoir->add_level_changes();
458 negated.set_offset(-
demand.offset());
459 for (
int j = 0; j <
demand.vars().
size(); ++j) {
460 negated.add_vars(
demand.vars(j));
461 negated.add_coeffs(-
demand.coeffs(j));
464 if (interval_ct.enforcement_literal().empty()) {
465 reservoir->add_active_literals(true_literal);
466 reservoir->add_active_literals(true_literal);
468 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
469 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
470 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
476 context->UpdateRuleStats(
"cumulative: expanded into reservoir");
477 ExpandReservoir(&reservoir_ct,
context);
480void ExpandIntMod(ConstraintProto*
ct, PresolveContext*
context) {
481 const LinearArgumentProto& int_mod =
ct->int_mod();
482 const LinearExpressionProto& mod_expr = int_mod.exprs(1);
483 if (
context->IsFixed(mod_expr))
return;
485 const LinearExpressionProto& expr = int_mod.exprs(0);
486 const LinearExpressionProto& target_expr = int_mod.target();
489 if (!
context->IntersectDomainWith(
490 target_expr,
context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
491 context->DomainSuperSetOf(mod_expr)))) {
496 auto new_enforced_constraint = [&]() {
497 ConstraintProto* new_ct =
context->working_model->add_constraints();
498 *new_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
503 const int div_var =
context->NewIntVar(
504 context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
505 context->DomainSuperSetOf(mod_expr)));
506 LinearExpressionProto div_expr;
507 div_expr.add_vars(div_var);
508 div_expr.add_coeffs(1);
510 LinearArgumentProto*
const div_proto =
511 new_enforced_constraint()->mutable_int_div();
512 *div_proto->mutable_target() = div_expr;
513 *div_proto->add_exprs() = expr;
514 *div_proto->add_exprs() = mod_expr;
517 const Domain prod_domain =
519 .ContinuousMultiplicationBy(
context->DomainSuperSetOf(mod_expr))
520 .IntersectionWith(
context->DomainSuperSetOf(expr).AdditionWith(
521 context->DomainSuperSetOf(target_expr).Negation()));
522 const int prod_var =
context->NewIntVar(prod_domain);
523 LinearExpressionProto prod_expr;
524 prod_expr.add_vars(prod_var);
525 prod_expr.add_coeffs(1);
527 LinearArgumentProto*
const int_prod =
528 new_enforced_constraint()->mutable_int_prod();
529 *int_prod->mutable_target() = prod_expr;
530 *int_prod->add_exprs() = div_expr;
531 *int_prod->add_exprs() = mod_expr;
534 LinearConstraintProto*
const lin =
535 new_enforced_constraint()->mutable_linear();
543 context->UpdateRuleStats(
"int_mod: expanded");
546void ExpandNonBinaryIntProd(ConstraintProto*
ct, PresolveContext*
context) {
547 CHECK_GT(
ct->int_prod().exprs_size(), 2);
548 std::deque<LinearExpressionProto> terms(
549 {
ct->int_prod().exprs().begin(),
ct->int_prod().exprs().end()});
550 while (terms.size() > 2) {
551 const LinearExpressionProto& left = terms[0];
552 const LinearExpressionProto& right = terms[1];
553 const Domain new_domain =
554 context->DomainSuperSetOf(left).ContinuousMultiplicationBy(
555 context->DomainSuperSetOf(right));
556 const int new_var =
context->NewIntVar(new_domain);
557 LinearArgumentProto*
const int_prod =
558 context->working_model->add_constraints()->mutable_int_prod();
559 *int_prod->add_exprs() = left;
560 *int_prod->add_exprs() = right;
561 int_prod->mutable_target()->add_vars(new_var);
562 int_prod->mutable_target()->add_coeffs(1);
565 terms.push_back(int_prod->target());
568 LinearArgumentProto*
const final_int_prod =
569 context->working_model->add_constraints()->mutable_int_prod();
570 *final_int_prod->add_exprs() = terms[0];
571 *final_int_prod->add_exprs() = terms[1];
572 *final_int_prod->mutable_target() =
ct->int_prod().target();
574 context->UpdateRuleStats(absl::StrCat(
575 "int_prod: expanded int_prod with arity ",
ct->int_prod().exprs_size()));
580void ExpandIntProdWithBoolean(
int bool_ref,
581 const LinearExpressionProto& int_expr,
582 const LinearExpressionProto& product_expr,
584 ConstraintProto*
const one =
context->working_model->add_constraints();
585 one->add_enforcement_literal(bool_ref);
586 one->mutable_linear()->add_domain(0);
587 one->mutable_linear()->add_domain(0);
590 one->mutable_linear());
592 ConstraintProto*
const zero =
context->working_model->add_constraints();
593 zero->add_enforcement_literal(
NegatedRef(bool_ref));
594 zero->mutable_linear()->add_domain(0);
595 zero->mutable_linear()->add_domain(0);
597 zero->mutable_linear());
600void ExpandIntProd(ConstraintProto*
ct, PresolveContext*
context) {
601 const LinearArgumentProto& int_prod =
ct->int_prod();
602 if (int_prod.exprs_size() > 2) {
606 if (int_prod.exprs_size() != 2)
return;
607 const LinearExpressionProto&
a = int_prod.exprs(0);
608 const LinearExpressionProto&
b = int_prod.exprs(1);
609 const LinearExpressionProto& p = int_prod.target();
616 if (a_is_literal && !b_is_literal) {
619 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
620 }
else if (b_is_literal) {
623 context->UpdateRuleStats(
"int_prod: expanded product with Boolean var");
627void ExpandInverse(ConstraintProto*
ct, PresolveContext*
context) {
628 const auto& f_direct =
ct->inverse().f_direct();
629 const auto& f_inverse =
ct->inverse().f_inverse();
630 const int n = f_direct.size();
631 CHECK_EQ(n, f_inverse.size());
639 absl::flat_hash_set<int> used_variables;
640 for (
const int ref : f_direct) {
642 if (!
context->IntersectDomainWith(ref, Domain(0, n - 1))) {
643 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
647 for (
const int ref : f_inverse) {
649 if (!
context->IntersectDomainWith(ref, Domain(0, n - 1))) {
650 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
657 if (used_variables.size() != 2 * n) {
658 for (
int i = 0;
i < n; ++
i) {
659 for (
int j = 0; j < n; ++j) {
664 if (
i == j)
continue;
665 if (!
context->IntersectDomainWith(
675 std::vector<int64_t> possible_values;
678 const auto filter_inverse_domain =
679 [
context, n, &possible_values](
const auto& direct,
const auto& inverse) {
681 for (
int i = 0;
i < n; ++
i) {
682 possible_values.clear();
683 const Domain domain =
context->DomainOf(direct[
i]);
684 bool removed_value =
false;
685 for (
const int64_t j : domain.Values()) {
686 if (
context->DomainOf(inverse[j]).Contains(
i)) {
687 possible_values.push_back(j);
689 removed_value =
true;
693 if (!
context->IntersectDomainWith(
695 VLOG(1) <<
"Empty domain for a variable in ExpandInverse()";
705 if (!filter_inverse_domain(f_direct, f_inverse))
return;
706 if (!filter_inverse_domain(f_inverse, f_direct))
return;
712 for (
int i = 0;
i < n; ++
i) {
713 const int f_i = f_direct[
i];
714 for (
const int64_t j :
context->DomainOf(f_i).Values()) {
716 const int r_j = f_inverse[j];
718 if (
context->HasVarValueEncoding(r_j,
i, &r_j_i)) {
719 context->InsertVarValueEncoding(r_j_i, f_i, j);
721 const int f_i_j =
context->GetOrCreateVarValueEncoding(f_i, j);
722 context->InsertVarValueEncoding(f_i_j, r_j,
i);
728 context->UpdateRuleStats(
"inverse: expanded");
731void ExpandLinMax(ConstraintProto*
ct, PresolveContext*
context) {
732 const int num_exprs =
ct->lin_max().exprs().size();
733 if (num_exprs < 2)
return;
743 for (
const LinearExpressionProto& expr :
ct->lin_max().exprs()) {
744 ConstraintProto* new_ct =
context->working_model->add_constraints();
745 LinearConstraintProto* lin = new_ct->mutable_linear();
747 lin->add_domain(std::numeric_limits<int64_t>::max());
750 context->CanonicalizeLinearConstraint(new_ct);
755 std::vector<int> enforcement_literals;
756 enforcement_literals.reserve(num_exprs);
757 if (num_exprs == 2) {
758 const int new_bool =
context->NewBoolVar();
759 enforcement_literals.push_back(new_bool);
760 enforcement_literals.push_back(
NegatedRef(new_bool));
762 ConstraintProto* exactly_one =
context->working_model->add_constraints();
763 for (
int i = 0;
i < num_exprs; ++
i) {
764 const int new_bool =
context->NewBoolVar();
765 exactly_one->mutable_exactly_one()->add_literals(new_bool);
766 enforcement_literals.push_back(new_bool);
770 for (
int i = 0;
i < num_exprs; ++
i) {
771 ConstraintProto* new_ct =
context->working_model->add_constraints();
772 new_ct->add_enforcement_literal(enforcement_literals[
i]);
773 LinearConstraintProto* lin = new_ct->mutable_linear();
774 lin->add_domain(std::numeric_limits<int64_t>::min());
778 context->CanonicalizeLinearConstraint(new_ct);
781 context->UpdateRuleStats(
"lin_max: expanded lin_max");
786void ExpandElementWithTargetEqualIndex(ConstraintProto*
ct,
788 const ElementConstraintProto& element =
ct->element();
789 DCHECK_EQ(element.index(), element.target());
791 const int index_ref = element.index();
792 std::vector<int64_t> valid_indices;
793 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
794 if (!
context->DomainContains(element.vars(v), v))
continue;
795 valid_indices.push_back(v);
797 if (valid_indices.size() <
context->DomainOf(index_ref).Size()) {
798 if (!
context->IntersectDomainWith(index_ref,
800 VLOG(1) <<
"No compatible variable domains in "
801 "ExpandElementWithTargetEqualIndex()";
804 context->UpdateRuleStats(
"element: reduced index domain");
807 for (
const int64_t v :
context->DomainOf(index_ref).Values()) {
808 const int var = element.vars(v);
811 context->GetOrCreateVarValueEncoding(index_ref, v),
var, Domain(v));
814 "element: expanded with special case target = index");
819void ExpandConstantArrayElement(ConstraintProto*
ct, PresolveContext*
context) {
820 const ElementConstraintProto& element =
ct->element();
821 const int index_ref = element.index();
822 const int target_ref = element.target();
825 const Domain index_domain =
context->DomainOf(index_ref);
826 const Domain target_domain =
context->DomainOf(target_ref);
833 absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
835 absl::flat_hash_map<int64_t, int> constant_var_values_usage;
836 for (
const int64_t v : index_domain.Values()) {
837 DCHECK(
context->IsFixed(element.vars(v)));
839 if (++constant_var_values_usage[
value] == 2) {
841 BoolArgumentProto*
const support =
842 context->working_model->add_constraints()->mutable_bool_or();
843 const int target_literal =
844 context->GetOrCreateVarValueEncoding(target_ref,
value);
845 support->add_literals(
NegatedRef(target_literal));
846 supports[
value] = support;
855 context->working_model->add_constraints()->mutable_exactly_one();
856 for (
const int64_t v : index_domain.Values()) {
857 const int index_literal =
858 context->GetOrCreateVarValueEncoding(index_ref, v);
859 exactly_one->add_literals(index_literal);
862 const auto& it = supports.find(
value);
863 if (it != supports.end()) {
866 const int target_literal =
867 context->GetOrCreateVarValueEncoding(target_ref,
value);
868 context->AddImplication(index_literal, target_literal);
869 it->second->add_literals(index_literal);
872 context->InsertVarValueEncoding(index_literal, target_ref,
value);
877 context->UpdateRuleStats(
"element: expanded value element");
882void ExpandVariableElement(ConstraintProto*
ct, PresolveContext*
context) {
883 const ElementConstraintProto& element =
ct->element();
884 const int index_ref = element.index();
885 const int target_ref = element.target();
886 const Domain index_domain =
context->DomainOf(index_ref);
888 BoolArgumentProto* exactly_one =
889 context->working_model->add_constraints()->mutable_exactly_one();
891 for (
const int64_t v : index_domain.Values()) {
892 const int var = element.vars(v);
893 const Domain var_domain =
context->DomainOf(
var);
894 const int index_lit =
context->GetOrCreateVarValueEncoding(index_ref, v);
895 exactly_one->add_literals(index_lit);
897 if (var_domain.IsFixed()) {
898 context->AddImplyInDomain(index_lit, target_ref, var_domain);
904 ConstraintProto*
const ct =
context->working_model->add_constraints();
905 ct->add_enforcement_literal(index_lit);
907 ct->mutable_linear()->add_vars(
var);
908 ct->mutable_linear()->add_coeffs(1);
911 ct->mutable_linear()->add_coeffs(-1);
914 ct->mutable_linear()->add_vars(target_ref);
915 ct->mutable_linear()->add_coeffs(-1);
918 ct->mutable_linear()->add_coeffs(1);
920 ct->mutable_linear()->add_domain(0);
921 ct->mutable_linear()->add_domain(0);
925 ct->mutable_linear()->vars(),
926 ct->mutable_linear()->coeffs()))
927 << google::protobuf::ShortFormat(*
ct);
931 context->UpdateRuleStats(
"element: expanded");
935void ExpandElement(ConstraintProto*
ct, PresolveContext*
context) {
936 const ElementConstraintProto& element =
ct->element();
938 const int index_ref = element.index();
939 const int target_ref = element.target();
940 const int size = element.vars_size();
944 if (!
context->IntersectDomainWith(index_ref, Domain(0,
size - 1))) {
945 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
950 if (index_ref == target_ref) {
951 ExpandElementWithTargetEqualIndex(
ct,
context);
956 bool all_constants =
true;
957 std::vector<int64_t> valid_indices;
958 const Domain index_domain =
context->DomainOf(index_ref);
959 const Domain target_domain =
context->DomainOf(target_ref);
960 Domain reached_domain;
961 for (
const int64_t v : index_domain.Values()) {
962 const Domain var_domain =
context->DomainOf(element.vars(v));
963 if (var_domain.IntersectionWith(target_domain).IsEmpty())
continue;
965 valid_indices.push_back(v);
966 reached_domain = reached_domain.UnionWith(var_domain);
967 if (var_domain.Min() != var_domain.Max()) {
968 all_constants =
false;
972 if (valid_indices.size() < index_domain.Size()) {
973 if (!
context->IntersectDomainWith(index_ref,
975 VLOG(1) <<
"No compatible variable domains in ExpandElement()";
979 context->UpdateRuleStats(
"element: reduced index domain");
984 bool target_domain_changed =
false;
985 if (!
context->IntersectDomainWith(target_ref, reached_domain,
986 &target_domain_changed)) {
990 if (target_domain_changed) {
991 context->UpdateRuleStats(
"element: reduced target domain");
1004void LinkLiteralsAndValues(
const std::vector<int>& literals,
1005 const std::vector<int64_t>& values,
1006 const absl::flat_hash_map<int64_t, int>& encoding,
1008 CHECK_EQ(literals.size(), values.size());
1013 absl::btree_map<int, std::vector<int>> encoding_lit_to_support;
1018 for (
int i = 0;
i < values.size(); ++
i) {
1019 encoding_lit_to_support[encoding.at(values[
i])].push_back(literals[
i]);
1024 for (
const auto& [encoding_lit, support] : encoding_lit_to_support) {
1025 CHECK(!support.empty());
1026 if (support.size() == 1) {
1027 context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
1029 BoolArgumentProto* bool_or =
1030 context->working_model->add_constraints()->mutable_bool_or();
1031 bool_or->add_literals(
NegatedRef(encoding_lit));
1032 for (
const int lit : support) {
1033 bool_or->add_literals(
lit);
1042void AddImplyInReachableValues(
int literal,
1043 std::vector<int64_t>& reachable_values,
1044 const absl::flat_hash_map<int64_t, int> encoding,
1047 if (reachable_values.size() == encoding.size())
return;
1048 if (reachable_values.size() <= encoding.size() / 2) {
1050 ConstraintProto*
ct =
context->working_model->add_constraints();
1052 BoolArgumentProto* bool_or =
ct->mutable_bool_or();
1053 for (
const int64_t v : reachable_values) {
1054 bool_or->add_literals(encoding.at(v));
1058 absl::flat_hash_set<int64_t> set(reachable_values.begin(),
1059 reachable_values.end());
1060 ConstraintProto*
ct =
context->working_model->add_constraints();
1062 BoolArgumentProto* bool_and =
ct->mutable_bool_and();
1064 if (!set.contains(
value)) {
1071void ExpandAutomaton(ConstraintProto*
ct, PresolveContext*
context) {
1072 AutomatonConstraintProto&
proto = *
ct->mutable_automaton();
1074 if (
proto.vars_size() == 0) {
1075 const int64_t initial_state =
proto.starting_state();
1076 for (
const int64_t final_state :
proto.final_states()) {
1077 if (initial_state == final_state) {
1078 context->UpdateRuleStats(
"automaton: empty and trivially feasible");
1083 return (
void)
context->NotifyThatModelIsUnsat(
1084 "automaton: empty with an initial state not in the final states.");
1085 }
else if (
proto.transition_label_size() == 0) {
1086 return (
void)
context->NotifyThatModelIsUnsat(
1087 "automaton: non-empty with no transition.");
1090 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
1091 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
1099 absl::flat_hash_map<int64_t, int> encoding;
1100 absl::flat_hash_map<int64_t, int> in_encoding;
1101 absl::flat_hash_map<int64_t, int> out_encoding;
1102 bool removed_values =
false;
1104 const int n =
proto.vars_size();
1105 const std::vector<int> vars = {
proto.vars().begin(),
proto.vars().end()};
1110 std::vector<int64_t> in_states;
1111 std::vector<int64_t> labels;
1112 std::vector<int64_t> out_states;
1113 for (
int i = 0;
i <
proto.transition_label_size(); ++
i) {
1114 const int64_t
tail =
proto.transition_tail(
i);
1115 const int64_t label =
proto.transition_label(
i);
1116 const int64_t
head =
proto.transition_head(
i);
1118 if (!reachable_states[
time].contains(
tail))
continue;
1119 if (!reachable_states[
time + 1].contains(
head))
continue;
1120 if (!
context->DomainContains(vars[
time], label))
continue;
1125 in_states.push_back(
tail);
1126 labels.push_back(label);
1130 out_states.push_back(
time + 1 == n ? 0 :
head);
1134 const int num_tuples = in_states.size();
1135 if (num_tuples == 1) {
1136 if (!
context->IntersectDomainWith(vars[
time], Domain(labels.front()))) {
1137 VLOG(1) <<
"Infeasible automaton.";
1144 std::vector<int> at_false;
1146 if (
value != in_states[0]) at_false.push_back(
literal);
1148 for (
const int literal : at_false) {
1152 in_encoding.clear();
1158 std::vector<int64_t> transitions = labels;
1162 if (!
context->IntersectDomainWith(
1164 VLOG(1) <<
"Infeasible automaton.";
1171 for (
const int64_t v :
context->DomainOf(vars[
time]).Values()) {
1172 encoding[v] =
context->GetOrCreateVarValueEncoding(vars[
time], v);
1179 absl::flat_hash_map<int64_t, int> in_count;
1180 absl::flat_hash_map<int64_t, int> transition_count;
1181 absl::flat_hash_map<int64_t, int> out_count;
1182 for (
int i = 0;
i < num_tuples; ++
i) {
1183 in_count[in_states[
i]]++;
1184 transition_count[labels[
i]]++;
1185 out_count[out_states[
i]]++;
1192 std::vector<int64_t> states = out_states;
1195 out_encoding.clear();
1196 if (states.size() == 2) {
1198 out_encoding[states[0]] =
var;
1200 }
else if (states.size() > 2) {
1201 struct UniqueDetector {
1202 void Set(int64_t v) {
1203 if (!is_unique)
return;
1205 if (v !=
value) is_unique =
false;
1211 bool is_set =
false;
1212 bool is_unique =
true;
1218 absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
1219 absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
1220 for (
int i = 0;
i < num_tuples; ++
i) {
1221 out_to_in[out_states[
i]].Set(in_states[
i]);
1222 out_to_transition[out_states[
i]].Set(labels[
i]);
1225 for (
const int64_t state : states) {
1228 if (!in_encoding.empty() && out_to_in[state].is_unique) {
1229 const int64_t unique_in = out_to_in[state].value;
1230 if (in_count[unique_in] == out_count[state]) {
1231 out_encoding[state] = in_encoding[unique_in];
1238 if (!encoding.empty() && out_to_transition[state].is_unique) {
1239 const int64_t unique_transition = out_to_transition[state].value;
1240 if (transition_count[unique_transition] == out_count[state]) {
1241 out_encoding[state] = encoding[unique_transition];
1246 out_encoding[state] =
context->NewBoolVar();
1264 const int num_involved_variables =
1265 in_encoding.size() + encoding.size() + out_encoding.size();
1266 const bool use_light_encoding = (num_tuples > num_involved_variables);
1267 if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
1268 !out_encoding.empty()) {
1272 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
1273 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
1274 for (
int i = 0;
i < num_tuples; ++
i) {
1275 in_to_label[in_states[
i]].push_back(labels[
i]);
1276 in_to_out[in_states[
i]].push_back(out_states[
i]);
1278 for (
const auto [in_value, in_literal] : in_encoding) {
1279 AddImplyInReachableValues(in_literal, in_to_label[in_value], encoding,
1281 AddImplyInReachableValues(in_literal, in_to_out[in_value], out_encoding,
1286 for (
int i = 0;
i < num_tuples; ++
i) {
1288 context->working_model->add_constraints()->mutable_bool_or();
1289 bool_or->add_literals(
NegatedRef(in_encoding.at(in_states[
i])));
1290 bool_or->add_literals(
NegatedRef(encoding.at(labels[
i])));
1291 bool_or->add_literals(out_encoding.at(out_states[
i]));
1294 in_encoding.swap(out_encoding);
1295 out_encoding.clear();
1303 std::vector<int> tuple_literals;
1304 if (num_tuples == 2) {
1305 const int bool_var =
context->NewBoolVar();
1306 tuple_literals.push_back(bool_var);
1307 tuple_literals.push_back(
NegatedRef(bool_var));
1312 BoolArgumentProto* exactly_one =
1313 context->working_model->add_constraints()->mutable_exactly_one();
1314 for (
int i = 0;
i < num_tuples; ++
i) {
1316 if (in_count[in_states[
i]] == 1 && !in_encoding.empty()) {
1317 tuple_literal = in_encoding[in_states[
i]];
1318 }
else if (transition_count[labels[
i]] == 1 && !encoding.empty()) {
1319 tuple_literal = encoding[labels[
i]];
1320 }
else if (out_count[out_states[
i]] == 1 && !out_encoding.empty()) {
1321 tuple_literal = out_encoding[out_states[
i]];
1323 tuple_literal =
context->NewBoolVar();
1326 tuple_literals.push_back(tuple_literal);
1327 exactly_one->add_literals(tuple_literal);
1331 if (!in_encoding.empty()) {
1332 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding,
context);
1334 if (!encoding.empty()) {
1335 LinkLiteralsAndValues(tuple_literals, labels, encoding,
context);
1337 if (!out_encoding.empty()) {
1338 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding,
context);
1341 in_encoding.swap(out_encoding);
1342 out_encoding.clear();
1345 if (removed_values) {
1346 context->UpdateRuleStats(
"automaton: reduced variable domains");
1348 context->UpdateRuleStats(
"automaton: expanded");
1352void ExpandNegativeTable(ConstraintProto*
ct, PresolveContext*
context) {
1353 TableConstraintProto& table = *
ct->mutable_table();
1354 const int num_vars = table.vars_size();
1355 const int num_original_tuples = table.values_size() / num_vars;
1356 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1358 for (
int i = 0;
i < num_original_tuples; ++
i) {
1359 for (
int j = 0; j < num_vars; ++j) {
1360 tuples[
i].push_back(table.values(count++));
1364 if (tuples.empty()) {
1365 context->UpdateRuleStats(
"table: empty negated constraint");
1371 std::vector<int64_t> domain_sizes;
1372 for (
int i = 0;
i < num_vars; ++
i) {
1373 domain_sizes.push_back(
context->DomainOf(table.vars(
i)).Size());
1378 std::vector<int> clause;
1379 for (
const std::vector<int64_t>& tuple : tuples) {
1381 for (
int i = 0;
i < num_vars; ++
i) {
1382 const int64_t
value = tuple[
i];
1391 ConstraintProto* tuple_ct =
context->working_model->add_constraints();
1392 *tuple_ct->mutable_enforcement_literal() =
ct->enforcement_literal();
1393 BoolArgumentProto* bool_or = tuple_ct->mutable_bool_or();
1394 for (
const int lit : clause) {
1395 bool_or->add_literals(
lit);
1398 context->UpdateRuleStats(
"table: expanded negated constraint");
1407void ProcessOneCompressedColumn(
1408 int variable,
const std::vector<int>& tuple_literals,
1409 const std::vector<absl::InlinedVector<int64_t, 2>>& values,
1410 std::optional<int> table_is_active_literal, PresolveContext*
context) {
1411 DCHECK_EQ(tuple_literals.size(), values.size());
1418 std::vector<std::pair<int64_t, int>> pairs;
1419 std::vector<int> any_values_literals;
1420 for (
int i = 0;
i < values.size(); ++
i) {
1421 if (values[
i].empty()) {
1422 any_values_literals.push_back(tuple_literals[
i]);
1426 ConstraintProto*
ct =
context->working_model->add_constraints();
1427 ct->add_enforcement_literal(tuple_literals[
i]);
1432 values[
i].size() == 1 ?
ct->mutable_bool_and() :
ct->mutable_bool_or();
1433 for (
const int64_t v : values[
i]) {
1434 DCHECK(
context->DomainContains(variable, v));
1435 literals->add_literals(
context->GetOrCreateVarValueEncoding(variable, v));
1436 pairs.emplace_back(v, tuple_literals[
i]);
1442 std::vector<int> selected;
1443 std::sort(pairs.begin(), pairs.end());
1444 for (
int i = 0;
i < pairs.size();) {
1446 const int64_t
value = pairs[
i].first;
1447 for (;
i < pairs.size() && pairs[
i].first ==
value; ++
i) {
1448 selected.push_back(pairs[
i].second);
1453 BoolArgumentProto* no_support =
1454 context->working_model->add_constraints()->mutable_bool_or();
1455 for (
const int lit : selected) {
1456 no_support->add_literals(
lit);
1458 for (
const int lit : any_values_literals) {
1459 no_support->add_literals(
lit);
1461 if (table_is_active_literal.has_value()) {
1462 no_support->add_literals(
NegatedRef(table_is_active_literal.value()));
1466 const int value_literal =
1468 no_support->add_literals(
NegatedRef(value_literal));
1473void AddSizeTwoTable(
1474 const std::vector<int>& vars,
1475 const std::vector<std::vector<int64_t>>& tuples,
1476 const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1478 CHECK_EQ(vars.size(), 2);
1479 const int left_var = vars[0];
1480 const int right_var = vars[1];
1481 if (
context->DomainOf(left_var).IsFixed() ||
1482 context->DomainOf(right_var).IsFixed()) {
1488 absl::btree_map<int, std::vector<int>> left_to_right;
1489 absl::btree_map<int, std::vector<int>> right_to_left;
1491 for (
const auto& tuple : tuples) {
1492 const int64_t left_value(tuple[0]);
1493 const int64_t right_value(tuple[1]);
1494 DCHECK(
context->DomainContains(left_var, left_value));
1495 DCHECK(
context->DomainContains(right_var, right_value));
1497 const int left_literal =
1498 context->GetOrCreateVarValueEncoding(left_var, left_value);
1499 const int right_literal =
1500 context->GetOrCreateVarValueEncoding(right_var, right_value);
1501 left_to_right[left_literal].push_back(right_literal);
1502 right_to_left[right_literal].push_back(left_literal);
1505 int num_implications = 0;
1506 int num_clause_added = 0;
1507 int num_large_clause_added = 0;
1508 auto add_support_constraint =
1509 [
context, &num_clause_added, &num_large_clause_added, &num_implications](
1510 int lit,
const std::vector<int>& support_literals,
1511 int max_support_size) {
1512 if (support_literals.size() == max_support_size)
return;
1513 if (support_literals.size() == 1) {
1514 context->AddImplication(
lit, support_literals.front());
1517 BoolArgumentProto* bool_or =
1518 context->working_model->add_constraints()->mutable_bool_or();
1519 for (
const int support_literal : support_literals) {
1520 bool_or->add_literals(support_literal);
1524 if (support_literals.size() > max_support_size / 2) {
1525 num_large_clause_added++;
1530 for (
const auto& it : left_to_right) {
1531 add_support_constraint(it.first, it.second, values_per_var[1].size());
1533 for (
const auto& it : right_to_left) {
1534 add_support_constraint(it.first, it.second, values_per_var[0].size());
1536 VLOG(2) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1537 << num_clause_added <<
" clauses, including "
1538 << num_large_clause_added <<
" large clauses, " << num_implications
1546bool ReduceTableInPresenceOfUniqueVariableWithCosts(
1547 std::vector<int>* vars, std::vector<std::vector<int64_t>>* tuples,
1549 const int num_vars = vars->size();
1551 std::vector<bool> only_here_and_in_objective(num_vars,
false);
1552 std::vector<int64_t> objective_coeffs(num_vars, 0.0);
1553 std::vector<int> new_vars;
1554 std::vector<int> deleted_vars;
1561 if (
context->VariableWithCostIsUnique(
var)) {
1562 context->UpdateRuleStats(
"table: removed unused column with cost");
1563 only_here_and_in_objective[
var_index] =
true;
1569 deleted_vars.push_back(
var);
1570 }
else if (
context->VarToConstraints(
var).size() == 1) {
1573 context->UpdateRuleStats(
"table: removed unused column");
1574 only_here_and_in_objective[
var_index] =
true;
1577 deleted_vars.push_back(
var);
1579 new_vars.push_back(
var);
1582 if (new_vars.size() == num_vars)
return false;
1586 int64_t min_cost = std::numeric_limits<int64_t>::max();
1587 std::vector<int64_t> temp;
1588 for (
int i = 0;
i < tuples->size(); ++
i) {
1594 if (only_here_and_in_objective[
var_index]) {
1595 temp.push_back(
value);
1596 const int64_t objective_coeff = objective_coeffs[
var_index];
1597 cost +=
value * objective_coeff;
1599 (*tuples)[
i][new_size++] =
value;
1602 (*tuples)[
i].resize(new_size);
1603 (*tuples)[
i].push_back(cost);
1604 min_cost = std::min(min_cost, cost);
1608 (*tuples)[
i].insert((*tuples)[
i].
end(), temp.begin(), temp.end());
1616 const int old_size = tuples->size();
1617 std::sort(tuples->begin(), tuples->end());
1618 for (
int i = 0;
i < tuples->size(); ++
i) {
1633 for (
int j = 0; j < deleted_vars.size(); ++j) {
1634 ConstraintProto* mapping_ct =
1635 context->NewMappingConstraint(__FILE__, __LINE__);
1637 mapping_ct->add_enforcement_literal(
1641 LinearConstraintProto* new_lin = mapping_ct->mutable_linear();
1642 new_lin->add_vars(deleted_vars[j]);
1643 new_lin->add_coeffs(1);
1644 new_lin->add_domain((*tuples)[
i][new_vars.size() + 1 + j]);
1645 new_lin->add_domain((*tuples)[
i][new_vars.size() + 1 + j]);
1647 (*tuples)[
i].resize(new_vars.size() + 1);
1648 (*tuples)[new_size++] = (*tuples)[
i];
1650 tuples->resize(new_size);
1651 if (new_size < old_size) {
1653 "table: removed duplicate tuples with different costs");
1658 context->AddToObjectiveOffset(min_cost);
1659 context->UpdateRuleStats(
"table: transferred min_cost to objective offset");
1660 for (
int i = 0;
i < tuples->size(); ++
i) {
1661 (*tuples)[
i].back() -= min_cost;
1677 absl::flat_hash_map<int64_t, int64_t> value_to_min_cost;
1678 const int num_tuples = tuples->size();
1679 for (
int i = 0;
i < num_tuples; ++
i) {
1681 const int64_t cost = (*tuples)[
i].back();
1682 auto insert = value_to_min_cost.insert({v, cost});
1683 if (!insert.second) {
1684 insert.first->second = std::min(insert.first->second, cost);
1687 for (
int i = 0;
i < num_tuples; ++
i) {
1689 (*tuples)[
i].back() -= value_to_min_cost.at(v);
1691 for (
const auto entry : value_to_min_cost) {
1692 if (entry.second == 0)
continue;
1693 context->UpdateRuleStats(
"table: transferred cost to encoding");
1694 const int value_literal =
context->GetOrCreateVarValueEncoding(
1696 context->AddLiteralToObjective(value_literal, entry.second);
1701 context->UpdateRuleStats(absl::StrCat(
1702 "table: expansion with column(s) only in objective. Arity = ",
1711void CompressAndExpandPositiveTable(ConstraintProto*
ct,
1712 bool last_column_is_cost,
1713 const std::vector<int>& vars,
1714 std::vector<std::vector<int64_t>>* tuples,
1716 const int num_tuples_before_compression = tuples->size();
1720 std::vector<int64_t> domain_sizes;
1721 for (
const int var : vars) {
1722 domain_sizes.push_back(
context->DomainOf(
var).Size());
1724 if (last_column_is_cost) {
1725 domain_sizes.push_back(std::numeric_limits<int64_t>::max());
1729 const int compression_level =
context->params().table_compression_level();
1730 if (compression_level > 0) {
1733 const int num_tuples_after_first_compression = tuples->size();
1746 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> compressed_table;
1747 if (compression_level > 2 ||
1748 (compression_level == 2 && num_tuples_after_first_compression > 1000)) {
1750 if (compressed_table.size() < num_tuples_before_compression) {
1751 context->UpdateRuleStats(
"table: fully compress tuples");
1755 for (
int i = 0;
i < tuples->size(); ++
i) {
1756 compressed_table.push_back({});
1757 for (
const int64_t v : (*tuples)[
i]) {
1759 compressed_table.back().push_back({});
1761 compressed_table.back().push_back({v});
1765 if (compressed_table.size() < num_tuples_before_compression) {
1766 context->UpdateRuleStats(
"table: compress tuples");
1770 VLOG(2) <<
"Table compression" <<
" var=" << vars.size()
1771 <<
" cost=" << domain_sizes.size() - vars.size()
1772 <<
" tuples= " << num_tuples_before_compression <<
" -> "
1773 << num_tuples_after_first_compression <<
" -> "
1774 << compressed_table.size();
1777 std::sort(compressed_table.begin(), compressed_table.end());
1779 const int num_vars = vars.size();
1780 if (compressed_table.size() == 1 &&
ct->enforcement_literal().empty()) {
1782 context->UpdateRuleStats(
"table: one tuple");
1783 if (last_column_is_cost) {
1786 context->AddToObjectiveOffset(compressed_table[0].back()[0]);
1794 std::vector<bool> has_any(num_vars,
false);
1795 std::vector<absl::flat_hash_map<int64_t, int>> var_index_to_value_count(
1797 for (
int i = 0;
i < compressed_table.size(); ++
i) {
1799 if (compressed_table[
i][
var_index].empty()) {
1803 for (
const int64_t v : compressed_table[
i][
var_index]) {
1806 var_index_to_value_count[
var_index][v]++;
1813 BoolArgumentProto* exactly_one =
1814 context->working_model->add_constraints()->mutable_exactly_one();
1816 std::optional<int> table_is_active_literal = std::nullopt;
1818 if (
ct->enforcement_literal().size() == 1) {
1819 table_is_active_literal =
ct->enforcement_literal(0);
1820 }
else if (
ct->enforcement_literal().size() > 1) {
1821 table_is_active_literal =
context->NewBoolVar();
1824 BoolArgumentProto* bool_or =
1825 context->working_model->add_constraints()->mutable_bool_or();
1826 bool_or->add_literals(table_is_active_literal.value());
1827 for (
const int lit :
ct->enforcement_literal()) {
1828 context->AddImplication(table_is_active_literal.value(),
lit);
1833 int64_t num_reused_variables = 0;
1834 std::vector<int> tuple_literals(compressed_table.size());
1835 for (
int i = 0;
i < compressed_table.size(); ++
i) {
1836 bool create_new_var =
true;
1840 !
ct->enforcement_literal().empty()) {
1843 const int64_t v = compressed_table[
i][
var_index][0];
1844 if (var_index_to_value_count[
var_index][v] != 1)
continue;
1846 ++num_reused_variables;
1847 create_new_var =
false;
1852 if (create_new_var) {
1853 tuple_literals[
i] =
context->NewBoolVar();
1855 exactly_one->add_literals(tuple_literals[
i]);
1857 if (num_reused_variables > 0) {
1858 context->UpdateRuleStats(
"table: reused literals");
1863 if (last_column_is_cost) {
1864 for (
int i = 0;
i < tuple_literals.size(); ++
i) {
1865 context->AddLiteralToObjective(tuple_literals[
i],
1866 compressed_table[
i].back()[0]);
1870 std::vector<absl::InlinedVector<int64_t, 2>>
column;
1875 for (
int i = 0;
i < tuple_literals.size(); ++
i) {
1879 table_is_active_literal,
context);
1882 if (table_is_active_literal.has_value()) {
1883 exactly_one->add_literals(
NegatedRef(table_is_active_literal.value()));
1886 context->UpdateRuleStats(
"table: expanded positive constraint");
1896void ExpandPositiveTable(ConstraintProto*
ct, PresolveContext*
context) {
1897 const TableConstraintProto& table =
ct->table();
1898 const int num_vars = table.vars_size();
1899 const int num_original_tuples = table.values_size() / num_vars;
1902 std::vector<int> vars(table.vars().begin(), table.vars().end());
1903 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1905 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1907 tuples[tuple_index].push_back(table.values(count++));
1913 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1915 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1928 std::swap(tuples[tuple_index], tuples[new_size]);
1932 tuples.resize(new_size);
1934 if (tuples.empty()) {
1935 if (
ct->enforcement_literal().empty()) {
1936 context->UpdateRuleStats(
"table: empty");
1937 return (
void)
context->NotifyThatModelIsUnsat();
1939 context->UpdateRuleStats(
"table: enforced and empty");
1940 BoolArgumentProto* bool_or =
1941 context->working_model->add_constraints()->mutable_bool_or();
1942 for (
const int lit :
ct->enforcement_literal()) {
1953 if (
ct->enforcement_literal().empty()) {
1954 int num_fixed_variables = 0;
1956 CHECK(
context->IntersectDomainWith(
1959 values_per_var[var_index].end()})));
1961 num_fixed_variables++;
1965 if (num_fixed_variables == num_vars - 1) {
1966 context->UpdateRuleStats(
"table: one variable not fixed");
1969 }
else if (num_fixed_variables == num_vars) {
1970 context->UpdateRuleStats(
"table: all variables fixed");
1981 if (num_vars == 2 && !
context->params().detect_table_with_cost() &&
1982 ct->enforcement_literal().empty()) {
1983 AddSizeTwoTable(vars, tuples, values_per_var,
context);
1985 "table: expanded positive constraint with two variables");
1990 bool last_column_is_cost =
false;
1991 if (
context->params().detect_table_with_cost() &&
1992 ct->enforcement_literal().empty()) {
1993 last_column_is_cost =
1994 ReduceTableInPresenceOfUniqueVariableWithCosts(&vars, &tuples,
context);
1997 CompressAndExpandPositiveTable(
ct, last_column_is_cost, vars, &tuples,
2002bool AllDiffShouldBeExpanded(
const Domain& union_of_domains,
2003 ConstraintProto*
ct, PresolveContext*
context) {
2004 const AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
2005 const int num_exprs =
proto.exprs_size();
2006 int num_fully_encoded = 0;
2007 for (
int i = 0;
i < num_exprs; ++
i) {
2009 num_fully_encoded++;
2013 if ((union_of_domains.Size() <= 2 *
proto.exprs_size()) ||
2014 (union_of_domains.Size() <= 32)) {
2019 if (num_fully_encoded == num_exprs && union_of_domains.Size() < 256) {
2032void ExpandSomeLinearOfSizeTwo(ConstraintProto*
ct, PresolveContext*
context) {
2033 const LinearConstraintProto& arg =
ct->linear();
2034 if (arg.vars_size() != 2)
return;
2036 const int var1 = arg.vars(0);
2037 const int var2 = arg.vars(1);
2040 const int64_t coeff1 = arg.coeffs(0);
2041 const int64_t coeff2 = arg.coeffs(1);
2042 const Domain reachable_rhs_superset =
2044 .MultiplicationBy(coeff1)
2045 .RelaxIfTooComplex()
2046 .AdditionWith(
context->DomainOf(var2)
2047 .MultiplicationBy(coeff2)
2048 .RelaxIfTooComplex());
2049 const Domain infeasible_reachable_values =
2050 reachable_rhs_superset.IntersectionWith(
2054 if (infeasible_reachable_values.Size() != 1)
return;
2059 int64_t cte = infeasible_reachable_values.FixedValue();
2064 context->UpdateRuleStats(
"linear: expand always feasible ax + by != cte");
2068 const Domain reduced_domain =
2070 .AdditionWith(Domain(-x0))
2071 .InverseMultiplicationBy(
b)
2072 .IntersectionWith(
context->DomainOf(var2)
2073 .AdditionWith(Domain(-y0))
2074 .InverseMultiplicationBy(-
a));
2076 if (reduced_domain.Size() > 16)
return;
2081 const int64_t size1 =
context->DomainOf(var1).Size();
2082 const int64_t size2 =
context->DomainOf(var2).Size();
2083 for (
const int64_t z : reduced_domain.Values()) {
2084 const int64_t value1 = x0 +
b * z;
2085 const int64_t value2 = y0 -
a * z;
2086 DCHECK(
context->DomainContains(var1, value1)) <<
"value1 = " << value1;
2087 DCHECK(
context->DomainContains(var2, value2)) <<
"value2 = " << value2;
2088 DCHECK_EQ(coeff1 * value1 + coeff2 * value2,
2089 infeasible_reachable_values.FixedValue());
2091 if (!
context->HasVarValueEncoding(var1, value1,
nullptr) || size1 == 2) {
2094 if (!
context->HasVarValueEncoding(var2, value2,
nullptr) || size2 == 2) {
2101 for (
const int64_t z : reduced_domain.Values()) {
2102 const int64_t value1 = x0 +
b * z;
2103 const int64_t value2 = y0 -
a * z;
2105 const int lit1 =
context->GetOrCreateVarValueEncoding(var1, value1);
2106 const int lit2 =
context->GetOrCreateVarValueEncoding(var2, value2);
2108 context->working_model->add_constraints()->mutable_bool_or();
2111 for (
const int lit :
ct->enforcement_literal()) {
2116 context->UpdateRuleStats(
"linear: expand small ax + by != cte");
2128void ExpandComplexLinearConstraint(
int c, ConstraintProto*
ct,
2134 if (
ct->linear().domain().size() <= 2)
return;
2135 if (
ct->linear().vars().size() == 1)
return;
2137 const SatParameters& params =
context->params();
2138 if (params.encode_complex_linear_constraint_with_integer()) {
2144 const int slack =
context->NewIntVar(rhs);
2145 ct->mutable_linear()->add_vars(slack);
2146 ct->mutable_linear()->add_coeffs(-1);
2147 ct->mutable_linear()->clear_domain();
2148 ct->mutable_linear()->add_domain(0);
2149 ct->mutable_linear()->add_domain(0);
2153 BoolArgumentProto* clause =
nullptr;
2154 std::vector<int> domain_literals;
2155 if (
ct->enforcement_literal().empty() &&
ct->linear().domain_size() == 4) {
2158 single_bool =
context->NewBoolVar();
2160 clause =
context->working_model->add_constraints()->mutable_bool_or();
2161 for (
const int ref :
ct->enforcement_literal()) {
2167 const std::vector<int> enforcement_literals(
2168 ct->enforcement_literal().begin(),
ct->enforcement_literal().end());
2169 ct->mutable_enforcement_literal()->Clear();
2170 for (
int i = 0;
i <
ct->linear().domain_size();
i += 2) {
2171 const int64_t lb =
ct->linear().domain(
i);
2172 const int64_t ub =
ct->linear().domain(
i + 1);
2174 int subdomain_literal;
2175 if (clause !=
nullptr) {
2176 subdomain_literal =
context->NewBoolVar();
2177 clause->add_literals(subdomain_literal);
2178 domain_literals.push_back(subdomain_literal);
2180 if (
i == 0) domain_literals.push_back(single_bool);
2181 subdomain_literal =
i == 0 ? single_bool :
NegatedRef(single_bool);
2186 ConstraintProto* new_ct =
context->working_model->add_constraints();
2188 new_ct->add_enforcement_literal(subdomain_literal);
2193 if (
context->params().enumerate_all_solutions() &&
2194 !enforcement_literals.empty()) {
2195 int linear_is_enforced;
2196 if (enforcement_literals.size() == 1) {
2197 linear_is_enforced = enforcement_literals[0];
2199 linear_is_enforced =
context->NewBoolVar();
2200 BoolArgumentProto* maintain_linear_is_enforced =
2201 context->working_model->add_constraints()->mutable_bool_or();
2202 for (
const int e_lit : enforcement_literals) {
2205 maintain_linear_is_enforced->add_literals(
NegatedRef(e_lit));
2207 maintain_linear_is_enforced->add_literals(linear_is_enforced);
2210 for (
const int lit : domain_literals) {
2218 context->UpdateRuleStats(
"linear: expanded complex rhs");
2219 context->InitializeNewDomains();
2220 context->UpdateNewConstraintsVariableUsage();
2221 context->UpdateConstraintVariableUsage(c);
2224bool IsVarEqOrNeqValue(PresolveContext*
context,
2225 const LinearConstraintProto& lin) {
2226 if (lin.vars_size() != 1)
return false;
2230 if (rhs.IsFixed())
return true;
2233 const Domain not_implied =
2234 rhs.InverseMultiplicationBy(lin.coeffs(0))
2236 .IntersectionWith(
context->DomainOf(lin.vars(0)));
2237 if (not_implied.IsEmpty())
return false;
2238 return not_implied.IsFixed();
2254void ScanModelAndDecideAllDiffExpansion(
2255 ConstraintProto* all_diff_ct, PresolveContext*
context,
2256 absl::flat_hash_set<int>& domain_of_var_is_used,
2257 absl::flat_hash_set<int>& bounds_of_var_are_used,
2258 absl::flat_hash_set<int>& processed_variables,
bool& expand,
bool& keep) {
2259 CHECK_EQ(all_diff_ct->constraint_case(), ConstraintProto::kAllDiff);
2261 bool at_least_one_var_domain_is_used =
false;
2262 bool at_least_one_var_bound_is_used =
false;
2265 for (
const LinearExpressionProto& expr : all_diff_ct->all_diff().exprs()) {
2267 if (expr.vars().empty())
continue;
2268 DCHECK_EQ(1, expr.vars_size());
2269 const int var = expr.vars(0);
2273 bool at_least_one_var_domain_is_used =
false;
2274 bool at_least_one_var_bound_is_used =
false;
2277 if (!processed_variables.insert(
var).second) {
2278 at_least_one_var_domain_is_used = bounds_of_var_are_used.contains(
var);
2279 at_least_one_var_bound_is_used = domain_of_var_is_used.contains(
var);
2281 bool domain_is_used =
false;
2282 bool bounds_are_used =
false;
2289 const ConstraintProto& other_ct =
2291 switch (other_ct.constraint_case()) {
2292 case ConstraintProto::ConstraintCase::kBoolOr:
2294 case ConstraintProto::ConstraintCase::kBoolAnd:
2296 case ConstraintProto::ConstraintCase::kAtMostOne:
2298 case ConstraintProto::ConstraintCase::kExactlyOne:
2300 case ConstraintProto::ConstraintCase::kBoolXor:
2302 case ConstraintProto::ConstraintCase::kIntDiv:
2304 case ConstraintProto::ConstraintCase::kIntMod:
2306 case ConstraintProto::ConstraintCase::kLinMax:
2307 bounds_are_used =
true;
2309 case ConstraintProto::ConstraintCase::kIntProd:
2311 case ConstraintProto::ConstraintCase::kLinear:
2312 if (IsVarEqOrNeqValue(
context, other_ct.linear()) &&
2313 var == other_ct.linear().vars(0)) {
2315 domain_is_used =
true;
2316 }
else if (other_ct.linear().vars_size() > 2 &&
2317 other_ct.linear().domain_size() == 2 &&
2318 other_ct.linear().domain(0) ==
2319 other_ct.linear().domain(1)) {
2322 bounds_are_used =
true;
2325 case ConstraintProto::ConstraintCase::kAllDiff:
2329 case ConstraintProto::ConstraintCase::kDummyConstraint:
2331 case ConstraintProto::ConstraintCase::kElement:
2333 if (other_ct.element().index() ==
var) {
2334 domain_is_used =
true;
2337 case ConstraintProto::ConstraintCase::kCircuit:
2339 case ConstraintProto::ConstraintCase::kRoutes:
2341 case ConstraintProto::ConstraintCase::kInverse:
2342 domain_is_used =
true;
2344 case ConstraintProto::ConstraintCase::kReservoir:
2346 case ConstraintProto::ConstraintCase::kTable:
2347 domain_is_used =
true;
2349 case ConstraintProto::ConstraintCase::kAutomaton:
2350 domain_is_used =
true;
2352 case ConstraintProto::ConstraintCase::kInterval:
2353 bounds_are_used =
true;
2355 case ConstraintProto::ConstraintCase::kNoOverlap:
2358 case ConstraintProto::ConstraintCase::kNoOverlap2D:
2361 case ConstraintProto::ConstraintCase::kCumulative:
2364 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
2369 if (domain_is_used && bounds_are_used)
break;
2373 if (domain_is_used) domain_of_var_is_used.insert(
var);
2374 if (bounds_are_used) bounds_of_var_are_used.insert(
var);
2377 at_least_one_var_domain_is_used |= domain_is_used;
2378 at_least_one_var_bound_is_used |= bounds_are_used;
2381 if (at_least_one_var_domain_is_used && at_least_one_var_bound_is_used) {
2386 expand = at_least_one_var_domain_is_used;
2387 keep = at_least_one_var_bound_is_used;
2390void MaybeExpandAllDiff(ConstraintProto*
ct, PresolveContext*
context,
2391 absl::flat_hash_set<int>& domain_of_var_is_used,
2392 absl::flat_hash_set<int>& bounds_of_var_are_used,
2393 absl::flat_hash_set<int>& processed_variable) {
2394 const bool expand_all_diff_from_parameters =
2395 context->params().expand_alldiff_constraints();
2396 AllDifferentConstraintProto&
proto = *
ct->mutable_all_diff();
2397 if (
proto.exprs_size() <= 1)
return;
2398 if (
context->ModelIsUnsat())
return;
2400 bool keep_after_expansion =
false;
2401 bool expand_all_diff_from_usage =
false;
2402 ScanModelAndDecideAllDiffExpansion(
2403 ct,
context, domain_of_var_is_used, bounds_of_var_are_used,
2404 processed_variable, expand_all_diff_from_usage, keep_after_expansion);
2406 const int num_exprs =
proto.exprs_size();
2407 Domain union_of_domains =
context->DomainSuperSetOf(
proto.exprs(0));
2408 for (
int i = 1;
i < num_exprs; ++
i) {
2410 union_of_domains.UnionWith(
context->DomainSuperSetOf(
proto.exprs(
i)));
2413 const bool expand_all_diff_from_size =
2414 AllDiffShouldBeExpanded(union_of_domains,
ct,
context);
2421 const bool should_expand =
2422 expand_all_diff_from_parameters ||
2423 (expand_all_diff_from_size &&
2424 (expand_all_diff_from_usage || !keep_after_expansion));
2425 if (!should_expand)
return;
2427 const bool is_a_permutation = num_exprs == union_of_domains.Size();
2432 for (
const int64_t v : union_of_domains.Values()) {
2434 std::vector<LinearExpressionProto> possible_exprs;
2435 int fixed_expression_count = 0;
2436 for (
const LinearExpressionProto& expr :
proto.exprs()) {
2437 if (!
context->DomainContains(expr, v))
continue;
2438 possible_exprs.push_back(expr);
2440 fixed_expression_count++;
2444 if (fixed_expression_count > 1) {
2446 return (
void)
context->NotifyThatModelIsUnsat();
2447 }
else if (fixed_expression_count == 1) {
2449 for (
const LinearExpressionProto& expr : possible_exprs) {
2450 if (
context->IsFixed(expr))
continue;
2451 if (!
context->IntersectDomainWith(expr, Domain(v).Complement())) {
2452 VLOG(1) <<
"Empty domain for a variable in MaybeExpandAllDiff()";
2458 BoolArgumentProto* at_most_or_equal_one =
2460 ?
context->working_model->add_constraints()->mutable_exactly_one()
2461 :
context->working_model->add_constraints()->mutable_at_most_one();
2462 for (
const LinearExpressionProto& expr : possible_exprs) {
2465 if (!
context->DomainContains(expr, v))
continue;
2470 const int encoding =
context->GetOrCreateAffineValueEncoding(expr, v);
2471 at_most_or_equal_one->add_literals(encoding);
2476 absl::StrCat(
"all_diff:", is_a_permutation ?
" permutation" :
"",
2477 " expanded", keep_after_expansion ?
" and kept" :
""));
2478 if (!keep_after_expansion)
ct->Clear();
2484 if (
context->params().disable_constraint_expansion())
return;
2485 if (
context->ModelIsUnsat())
return;
2489 if (
context->ModelIsExpanded())
return;
2492 context->InitializeNewDomains();
2493 if (
context->ModelIsUnsat())
return;
2496 context->ClearPrecedenceCache();
2498 bool has_all_diffs =
false;
2501 for (
int c = 0; c <
context->working_model->constraints_size(); ++c) {
2502 ConstraintProto*
const ct =
context->working_model->mutable_constraints(c);
2504 switch (
ct->constraint_case()) {
2505 case ConstraintProto::kLinear:
2508 if (
ct->linear().domain().size() > 2 &&
2509 !
context->params().cp_model_presolve()) {
2510 ExpandComplexLinearConstraint(c,
ct,
context);
2513 case ConstraintProto::kReservoir:
2514 if (
context->params().expand_reservoir_constraints()) {
2518 case ConstraintProto::kCumulative:
2519 if (
context->params().encode_cumulative_as_reservoir()) {
2520 EncodeCumulativeAsReservoir(
ct,
context);
2523 case ConstraintProto::kIntMod:
2526 case ConstraintProto::kIntProd:
2529 case ConstraintProto::kElement:
2532 case ConstraintProto::kInverse:
2535 case ConstraintProto::kAutomaton:
2538 case ConstraintProto::kTable:
2539 if (
ct->table().negated()) {
2545 case ConstraintProto::kLinMax:
2546 if (
ct->lin_max().exprs().size() <=
2547 context->params().max_lin_max_size_for_expansion()) {
2551 case ConstraintProto::kAllDiff:
2552 has_all_diffs =
true;
2562 context->UpdateNewConstraintsVariableUsage();
2563 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2564 context->UpdateConstraintVariableUsage(c);
2568 if (
context->ModelIsUnsat()) {
2579 absl::flat_hash_set<int> domain_of_var_is_used;
2580 absl::flat_hash_set<int> bounds_of_var_are_used;
2581 absl::flat_hash_set<int> processed_variables;
2582 for (
int i = 0;
i <
context->working_model->constraints_size(); ++
i) {
2583 ConstraintProto*
const ct =
context->working_model->mutable_constraints(
i);
2585 switch (
ct->constraint_case()) {
2586 case ConstraintProto::kAllDiff:
2587 MaybeExpandAllDiff(
ct,
context, domain_of_var_is_used,
2588 bounds_of_var_are_used, processed_variables);
2590 case ConstraintProto::kLinear:
2601 context->UpdateNewConstraintsVariableUsage();
2602 if (
ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2603 context->UpdateConstraintVariableUsage(
i);
2607 if (
context->ModelIsUnsat()) {
2617 context->ClearPrecedenceCache();
2620 context->InitializeNewDomains();
2623 for (
int i = 0;
i <
context->working_model->variables_size(); ++
i) {
2625 context->working_model->mutable_variables(
i));
2628 context->NotifyThatModelIsExpanded();
2632 if (
context->params().disable_constraint_expansion())
return;
2633 if (
context->ModelIsUnsat())
return;
2634 for (
int c = 0; c <
context->working_model->constraints_size(); ++c) {
2635 ConstraintProto*
const ct =
context->working_model->mutable_constraints(c);
2636 switch (
ct->constraint_case()) {
2637 case ConstraintProto::kLinear:
2638 if (
ct->linear().domain().size() > 2) {
2639 ExpandComplexLinearConstraint(c,
ct,
context);
static Domain FromValues(std::vector< int64_t > values)
Domain Complement() const
CpModelProto proto
The output proto.
GurobiMPCallbackContext * context
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
constexpr int64_t kTableAnyValue
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
bool RefIsPositive(int ref)
void ExpandCpModel(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
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 FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
void FinalExpansionForLinearConstraint(PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
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)
Fills and propagates the set of reachable states/labels.
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtobufShortDebugString(const P &message)
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)