24#include "absl/algorithm/container.h"
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/strings/str_cat.h"
31#include "absl/strings/string_view.h"
32#include "absl/types/span.h"
33#include "google/protobuf/message.h"
52 absl::string_view message =
"") {
53 if (ct->enforcement_literal().empty()) {
54 return (
void)context->NotifyThatModelIsUnsat(message);
57 *context->working_model->add_constraints()->mutable_bool_or();
58 for (
const int literal : ct->enforcement_literal()) {
67class EnforcedDomains {
69 EnforcedDomains(ConstraintProto* ct, PresolveContext* context)
70 : constraint_(ct), context_(context) {}
72 int size() {
return domains_.size(); }
74 bool IsFixed(
const LinearExpressionProto& expr)
const {
75 CHECK_LE(expr.vars_size(), 1);
76 return expr.vars().empty() || DomainOf(expr.vars(0)).IsFixed();
79 Domain DomainOf(
int ref)
const {
81 if (it != domains_.end()) {
82 return RefIsPositive(ref) ? it->second : it->second.Negation();
84 return context_->DomainOf(ref);
87 bool DomainContains(
int ref, int64_t value)
const {
89 if (it != domains_.end()) {
91 : it->second.Contains(-value);
93 return context_->DomainOf(ref).Contains(value);
96 bool DomainContains(
const LinearExpressionProto& expr, int64_t value)
const {
97 CHECK_LE(expr.vars_size(), 1);
98 const int64_t offset = expr.offset();
99 if (expr.vars().empty()) {
100 return offset == value;
102 const Domain domain = DomainOf(expr.vars(0));
103 const int64_t coeff = expr.coeffs(0);
104 if (value > coeff * (coeff > 0 ? domain.
Max() : domain.
Min()) + offset) {
107 if (value < coeff * (coeff > 0 ? domain.
Min() : domain.
Max()) + offset) {
112 if ((value - expr.offset()) % expr.coeffs(0) != 0)
return false;
113 return DomainOf(expr.vars(0))
114 .Contains((value - expr.offset()) / expr.coeffs(0));
120 bool IntersectDomainWith(
int ref,
const Domain& domain,
121 absl::string_view message =
"",
122 bool* domain_modified =
nullptr) {
123 if (constraint_->enforcement_literal().empty() &&
124 !context_->IntersectDomainWith(ref, domain, domain_modified)) {
125 return context_->NotifyThatModelIsUnsat(message);
127 const Domain current_domain = DomainOf(ref);
128 if (current_domain.IsIncludedIn(domain))
return true;
129 if (domain_modified !=
nullptr) {
130 *domain_modified =
true;
132 const Domain new_domain = current_domain.IntersectionWith(domain);
133 if (new_domain.IsEmpty()) {
134 ExpandAlwaysFalseConstraint(constraint_, context_, message);
141 bool SetLiteralToFalse(
int lit) {
144 return IntersectDomainWith(var, Domain(value));
147 bool IntersectDomainWith(
const LinearExpressionProto& expr,
148 const Domain& domain, absl::string_view message =
"",
149 bool* domain_modified =
nullptr) {
150 CHECK_LE(expr.vars_size(), 1);
151 if (constraint_->enforcement_literal().empty() &&
152 !context_->IntersectDomainWith(expr, domain, domain_modified)) {
153 return context_->NotifyThatModelIsUnsat(message);
155 if (expr.vars().empty()) {
156 if (domain.
Contains(expr.offset()))
return true;
157 ExpandAlwaysFalseConstraint(constraint_, context_, message);
160 return IntersectDomainWith(expr.vars(0),
161 domain.AdditionWith(Domain(-expr.offset()))
162 .InverseMultiplicationBy(expr.coeffs(0)),
163 message, domain_modified);
166 void MaybeAddEnforcedDomainConstraints()
const {
167 if (constraint_->enforcement_literal().empty())
return;
168 std::vector<int> vars;
169 for (
const auto& [var, _] : domains_) {
172 std::sort(vars.begin(), vars.end());
173 for (
const int var : vars) {
175 LinearConstraintProto*
const lin =
176 context_->AddEnforcedConstraint(constraint_)->mutable_linear();
184 ConstraintProto* constraint_;
185 PresolveContext* context_;
186 absl::flat_hash_map<int, Domain> domains_;
189void ExpandEnforcedAtMostOneOrExactlyOneConstraint(
ConstraintProto* ct,
int c,
191 if (ct->enforcement_literal().empty()) {
195 if (ct->has_at_most_one()) {
200 ct->mutable_linear()->
Swap(&linear);
201 context->CanonicalizeLinearConstraint(ct);
202 context->UpdateConstraintVariableUsage(c);
207void ExpandReservoirUsingCircuit(int64_t sum_of_positive_demand,
208 int64_t sum_of_negative_demand,
217 context->working_model->add_constraints()->mutable_circuit();
219 const int64_t var_min =
220 std::max(reservoir.min_level(), sum_of_negative_demand);
221 const int64_t var_max =
222 std::min(reservoir.max_level(), sum_of_positive_demand);
223 std::vector<int> level_vars(num_events);
224 for (
int i = 0;
i < num_events; ++
i) {
225 level_vars[
i] = context->NewIntVar(Domain(var_min, var_max));
231 const int all_inactive = context->NewBoolVar(
"reservoir expansion");
232 circuit->add_tails(num_events);
233 circuit->add_heads(num_events);
234 circuit->add_literals(all_inactive);
237 for (
int i = 0;
i < num_events; ++
i) {
238 if (!reservoir.active_literals().empty()) {
240 circuit->add_tails(
i);
241 circuit->add_heads(
i);
242 circuit->add_literals(
NegatedRef(reservoir.active_literals(
i)));
249 const int start_var = context->NewBoolVar(
"reservoir expansion");
250 circuit->add_tails(num_events);
251 circuit->add_heads(
i);
252 circuit->add_literals(start_var);
256 ConstraintProto* new_ct = context->AddEnforcedConstraint(reservoir_ct);
260 lin->add_vars(level_vars[
i]);
264 context->CanonicalizeLinearConstraint(new_ct);
268 const int end_var = context->NewBoolVar(
"reservoir expansion");
269 circuit->add_tails(
i);
270 circuit->add_heads(num_events);
271 circuit->add_literals(end_var);
274 for (
int j = 0; j < num_events; ++j) {
275 if (
i == j)
continue;
287 const int arc_i_j = context->NewBoolVar(
"reservoir expansion");
288 circuit->add_tails(
i);
289 circuit->add_heads(j);
290 circuit->add_literals(arc_i_j);
294 ConstraintProto* new_ct = context->AddEnforcedConstraint(reservoir_ct);
300 context->CanonicalizeLinearConstraint(new_ct);
305 ConstraintProto* new_ct = context->AddEnforcedConstraint(reservoir_ct);
309 lin->add_vars(level_vars[j]);
311 lin->add_vars(level_vars[
i]);
315 context->CanonicalizeLinearConstraint(new_ct);
319 context->solution_crush().SetReservoirCircuitVars(reservoir, var_min, var_max,
320 level_vars, *circuit);
322 reservoir_ct->Clear();
323 context->UpdateRuleStats(
"reservoir: expanded using circuit.");
326void ExpandReservoirUsingPrecedences(
bool max_level_is_constraining,
327 bool min_level_is_constraining,
332 const int true_literal = context->GetTrueLiteral();
333 const auto is_active_literal = [&reservoir, true_literal](
int index) {
334 if (reservoir.active_literals_size() == 0)
return true_literal;
335 return reservoir.active_literals(index);
340 for (
int i = 0;
i < num_events; ++
i) {
341 const int active_i = is_active_literal(
i);
342 if (context->LiteralIsFalse(active_i))
continue;
344 const int64_t demand_i = context->FixedValue(reservoir.level_changes(
i));
345 if (demand_i == 0)
continue;
349 if (demand_i > 0 && !max_level_is_constraining)
continue;
350 if (demand_i < 0 && !min_level_is_constraining)
continue;
352 ConstraintProto* new_cumul = context->AddEnforcedConstraint(reservoir_ct);
358 for (
int j = 0; j < num_events; ++j) {
359 if (
i == j)
continue;
360 const int active_j = is_active_literal(j);
361 if (context->LiteralIsFalse(active_j))
continue;
362 const int64_t demand_j = context->FixedValue(reservoir.level_changes(j));
363 if (demand_j == 0)
continue;
371 const int j_lesseq_i = context->GetOrCreateReifiedPrecedenceLiteral(
372 time_j, time_i, active_j, active_i);
389 reservoir.max_level() - offset, new_linear);
392 std::numeric_limits<int64_t>::max(), new_linear);
396 context->CanonicalizeLinearConstraint(new_cumul);
399 new_linear->coeffs()));
402 reservoir_ct->Clear();
403 context->UpdateRuleStats(
"reservoir: expanded using precedences");
407 if (reservoir_ct->reservoir().min_level() >
408 reservoir_ct->reservoir().max_level()) {
409 ExpandAlwaysFalseConstraint(reservoir_ct, context,
410 "Empty level domain in reservoir constraint.");
417 int num_positives = 0;
418 int num_negatives = 0;
419 bool all_demands_are_fixed =
true;
420 int64_t sum_of_positive_demand = 0;
421 int64_t sum_of_negative_demand = 0;
423 if (!context->IsFixed(demand_expr)) {
424 all_demands_are_fixed =
false;
426 const int64_t max_demand = context->MaxOf(demand_expr);
427 if (max_demand > 0) {
429 sum_of_positive_demand += max_demand;
431 const int64_t min_demand = context->MinOf(demand_expr);
432 if (min_demand < 0) {
434 sum_of_negative_demand += min_demand;
438 if (sum_of_negative_demand >= reservoir.min_level() &&
439 sum_of_positive_demand <= reservoir.max_level()) {
440 context->UpdateRuleStats(
"reservoir: always true");
441 reservoir_ct->Clear();
448 if (num_negatives == 0 || num_positives == 0) {
449 const int true_literal = context->GetTrueLiteral();
450 ConstraintProto* new_ct = context->AddEnforcedConstraint(reservoir_ct);
453 for (
int i = 0;
i < num_events; ++
i) {
454 const int active = reservoir.active_literals().empty()
456 : reservoir.active_literals(
i);
458 if (context->IsFixed(demand)) {
459 const int64_t change = context->FixedValue(reservoir.level_changes(
i));
461 sum->add_vars(active);
462 sum->add_coeffs(change);
465 sum->add_vars(true_literal);
466 sum->add_coeffs(change);
468 sum->add_coeffs(-change);
470 }
else if (context->LiteralIsTrue(active)) {
473 const int new_var = context->NewIntVar(
474 Domain(context->MinOf(demand), context->MaxOf(demand))
475 .UnionWith(Domain(0)));
476 sum->add_vars(new_var);
484 lin->add_vars(new_var);
487 context->CanonicalizeLinearConstraint(demand_ct);
488 context->solution_crush().SetVarToLinearExpressionIf(new_var, demand,
493 context->AddImplyInDomain(
NegatedRef(active), new_var, Domain(0));
494 context->solution_crush().SetVarToValueIf(new_var, 0,
498 context->CanonicalizeLinearConstraint(new_ct);
500 context->UpdateRuleStats(
"reservoir: simple expansion with sum");
501 reservoir_ct->Clear();
506 if (context->params().expand_reservoir_using_circuit()) {
507 ExpandReservoirUsingCircuit(sum_of_positive_demand, sum_of_negative_demand,
508 reservoir_ct, context);
511 if (all_demands_are_fixed) {
512 ExpandReservoirUsingPrecedences(
513 sum_of_positive_demand > reservoir_ct->reservoir().max_level(),
514 sum_of_negative_demand < reservoir_ct->reservoir().min_level(),
515 reservoir_ct, context);
517 context->UpdateRuleStats(
518 "reservoir: skipped expansion due to variable demands");
526 if (!context->IsFixed(ct->cumulative().capacity())) {
527 context->UpdateRuleStats(
528 "cumulative -> reservoir: expansion is not supported with variable "
537 auto* reservoir = reservoir_ct.mutable_reservoir();
538 reservoir->set_min_level(std::numeric_limits<int64_t>::min());
539 reservoir->set_max_level(context->FixedValue(ct->cumulative().capacity()));
541 const int true_literal = context->GetTrueLiteral();
542 const int num_intervals = ct->cumulative().intervals().size();
543 for (
int i = 0;
i < num_intervals; ++
i) {
544 const auto& interval_ct =
545 context->working_model->constraints(ct->cumulative().intervals(
i));
546 const auto& interval = interval_ct.interval();
547 *reservoir->add_time_exprs() = interval.start();
548 *reservoir->add_time_exprs() = interval.end();
551 *reservoir->add_level_changes() = demand;
554 for (
int j = 0; j < demand.vars().size(); ++j) {
555 negated.add_vars(demand.vars(j));
556 negated.add_coeffs(-demand.coeffs(j));
559 if (interval_ct.enforcement_literal().empty()) {
560 reservoir->add_active_literals(true_literal);
561 reservoir->add_active_literals(true_literal);
563 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
564 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
565 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
571 context->UpdateRuleStats(
"cumulative: expanded into reservoir");
572 ExpandReservoir(&reservoir_ct, context);
578 if (context->IsFixed(mod_expr))
return;
582 EnforcedDomains enforced_domains(ct, context);
585 if (!enforced_domains.IntersectDomainWith(
586 target_expr, context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
587 context->DomainSuperSetOf(mod_expr)))) {
592 const int div_var = context->NewIntVar(
593 context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
594 context->DomainSuperSetOf(mod_expr)));
597 div_expr.add_coeffs(1);
600 context->AddEnforcedConstraint(ct)->mutable_int_div();
602 *div_proto->add_exprs() = expr;
603 *div_proto->add_exprs() = mod_expr;
606 const Domain prod_domain =
607 context->DomainOf(div_var)
608 .ContinuousMultiplicationBy(context->DomainSuperSetOf(mod_expr))
609 .IntersectionWith(context->DomainSuperSetOf(expr).AdditionWith(
610 context->DomainSuperSetOf(target_expr).Negation()));
611 if (prod_domain.IsEmpty()) {
612 ExpandAlwaysFalseConstraint(ct, context,
"int_mod: empty target domain");
615 const int prod_var = context->NewIntVar(prod_domain);
618 prod_expr.add_coeffs(1);
621 context->AddEnforcedConstraint(ct)->mutable_int_prod();
623 *int_prod->add_exprs() = div_expr;
624 *int_prod->add_exprs() = mod_expr;
628 context->AddEnforcedConstraint(ct)->mutable_linear();
634 context->solution_crush().SetIntModExpandedVars(*ct, div_var, prod_var,
635 context->MinOf(div_var),
636 context->MinOf(prod_var));
638 context->UpdateRuleStats(
"int_mod: expanded");
642 if (ct->int_prod().exprs_size() <= 2)
return;
643 std::deque<LinearExpressionProto> terms(
644 {ct->int_prod().exprs().begin(), ct->int_prod().exprs().end()});
645 std::vector<int> new_vars;
646 while (terms.size() > 2) {
649 const Domain new_domain =
650 context->DomainSuperSetOf(left).ContinuousMultiplicationBy(
651 context->DomainSuperSetOf(right));
652 const int new_var = context->NewIntVar(new_domain);
653 new_vars.push_back(new_var);
660 *int_prod->add_exprs() = right;
661 int_prod->mutable_target()->
add_vars(new_var);
662 int_prod->mutable_target()->add_coeffs(1);
664 terms.front() = int_prod->target();
670 *final_int_prod->add_exprs() = terms[1];
671 *final_int_prod->mutable_target() = ct->int_prod().target();
673 context->solution_crush().SetIntProdExpandedVars(ct->int_prod(), new_vars);
674 context->UpdateRuleStats(absl::StrCat(
675 "int_prod: expanded int_prod with arity ", ct->int_prod().exprs_size()));
680 const auto& f_direct = ct->inverse().f_direct();
681 const auto& f_inverse = ct->inverse().f_inverse();
682 const int n = f_direct.size();
683 CHECK_EQ(n, f_inverse.size());
691 EnforcedDomains enforced_domains(ct, context);
692 for (
const int ref : f_direct) {
693 if (!enforced_domains.IntersectDomainWith(
694 ref, Domain(0, n - 1),
695 "Empty domain for a variable in ExpandInverse()")) {
699 for (
const int ref : f_inverse) {
700 if (!enforced_domains.IntersectDomainWith(
701 ref, Domain(0, n - 1),
702 "Empty domain for a variable in ExpandInverse()")) {
709 if (enforced_domains.size() != 2 * n) {
710 for (
int i = 0;
i < n; ++
i) {
711 for (
int j = 0; j < n; ++j) {
716 if (
i == j)
continue;
717 if (!enforced_domains.IntersectDomainWith(
719 "Empty domain for a variable in ExpandInverse()")) {
728 std::vector<int64_t> possible_values;
731 const auto filter_inverse_domain = [&enforced_domains, n, &possible_values](
733 const auto& inverse) {
735 for (
int i = 0;
i < n; ++
i) {
736 possible_values.clear();
737 const Domain domain = enforced_domains.DomainOf(direct[
i]);
738 bool removed_value =
false;
739 for (
const int64_t j : domain.Values()) {
740 if (enforced_domains.DomainOf(inverse[j]).Contains(
i)) {
741 possible_values.push_back(j);
743 removed_value =
true;
747 if (!enforced_domains.IntersectDomainWith(
749 "Empty domain for a variable in ExpandInverse()")) {
759 if (!filter_inverse_domain(f_direct, f_inverse))
return;
760 if (!filter_inverse_domain(f_inverse, f_direct))
return;
762 enforced_domains.MaybeAddEnforcedDomainConstraints();
768 for (
int i = 0;
i < n; ++
i) {
769 const int f_i = f_direct[
i];
770 for (
const int64_t j : enforced_domains.DomainOf(f_i).Values()) {
771 const int r_j = f_inverse[j];
772 if (ct->enforcement_literal().empty()) {
775 if (enforced_domains.DomainContains(r_j,
i) &&
776 context->HasVarValueEncoding(r_j,
i, &r_j_i)) {
777 if (!context->InsertVarValueEncoding(r_j_i, f_i, j)) {
781 const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
782 if (!context->InsertVarValueEncoding(f_i_j, r_j,
i)) {
789 const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
790 const int r_j_i = context->GetOrCreateVarValueEncoding(r_j,
i);
791 if (f_i_j != r_j_i) {
794 eq_direct->mutable_bool_and()->add_literals(r_j_i);
798 eq_inverse->mutable_bool_and()->add_literals(f_i_j);
805 context->UpdateRuleStats(
"inverse: expanded");
809 const int num_exprs = ct->lin_max().exprs().size();
810 if (num_exprs < 2)
return;
826 context->CanonicalizeLinearConstraint(new_ct);
831 std::vector<int> enforcement_literals;
832 enforcement_literals.reserve(num_exprs);
833 if (num_exprs == 2 && ct->enforcement_literal().empty()) {
834 const int new_bool = context->NewBoolVar(
"lin max expansion");
835 enforcement_literals.push_back(new_bool);
836 enforcement_literals.push_back(
NegatedRef(new_bool));
839 context->AddEnforcedConstraint(ct)->mutable_exactly_one();
840 for (
int i = 0;
i < num_exprs; ++
i) {
841 const int new_bool = context->NewBoolVar(
"lin max expansion");
842 exactly_one->add_literals(new_bool);
843 enforcement_literals.push_back(new_bool);
847 for (
int i = 0;
i < num_exprs; ++
i) {
849 context->AddEnforcedConstraint({enforcement_literals[
i]});
854 context->CanonicalizeLinearConstraint(new_ct);
857 context->solution_crush().SetLinMaxExpandedVars(ct->lin_max(),
858 enforcement_literals);
859 context->UpdateRuleStats(
"lin_max: expanded lin_max");
864void ExpandElementWhenTargetShareVarWithIndex(
866 const Domain& reduced_index_var_domain) {
870 DCHECK_EQ(index.vars_size(), 1);
871 const int index_var = index.vars(0);
873 DCHECK_EQ(target.vars_size(), 1);
874 DCHECK_EQ(target.vars(0), index_var);
876 for (
const int64_t v : reduced_index_var_domain.Values()) {
882 context->GetOrCreateVarValueEncoding(index_var, v));
885 context->CanonicalizeLinearConstraint(imply);
888 context->UpdateRuleStats(
889 "element: expanded when the index and the target share the same var");
895 const Domain& reduced_index_var_domain) {
898 DCHECK_EQ(index.vars_size(), 1);
899 const int index_var = index.vars(0);
902 absl::btree_map<int64_t, std::vector<int>> support_literals;
903 std::vector<int64_t> invalid_index_values;
904 for (
const int64_t v : reduced_index_var_domain.Values()) {
906 const int64_t expr_value = context->FixedValue(element.exprs(index_value));
907 if (!context->DomainContains(target, expr_value)) {
908 if (ct->enforcement_literal().empty()) {
909 invalid_index_values.push_back(v);
911 context->AddEnforcedConstraint(ct)->mutable_bool_and()->add_literals(
912 NegatedRef(context->GetOrCreateVarValueEncoding(index_var, v)));
913 context->UpdateRuleStats(
"element: value not in target domain");
916 const int index_lit = context->GetOrCreateVarValueEncoding(index_var, v);
917 support_literals[expr_value].push_back(index_lit);
921 if (!invalid_index_values.empty()) {
922 if (!context->IntersectDomainWith(
924 VLOG(1) <<
"Empty domain for the index variable in "
925 "ExpandConstantArrayElement()";
928 context->UpdateRuleStats(
"element: reduce index domain during expansion");
931 for (
const auto& [expr_value, index_var_literals] : support_literals) {
932 const int target_literal =
933 context->GetOrCreateAffineValueEncoding(target, expr_value);
934 if (index_var_literals.size() == 1 && ct->enforcement_literal().empty()) {
935 if (!context->StoreBooleanEqualityRelation(target_literal,
936 index_var_literals[0])) {
943 for (
const int64_t lit : index_var_literals) {
944 link->mutable_exactly_one()->add_literals(lit);
949 context->UpdateRuleStats(
"element: expanded value element");
955 const Domain& reduced_index_var_domain) {
958 DCHECK_EQ(index.vars_size(), 1);
959 const int index_var = index.vars(0);
963 const bool presolve_is_on = context->params().cp_model_presolve();
965 if (presolve_is_on && context->VariableIsUniqueAndRemovable(index_var)) {
967 bool is_unique =
true;
968 for (
int i = 0;
i < element.exprs_size(); ++
i) {
975 const int64_t min_index = context->DomainOf(index_var).Min();
977 context->NewMappingConstraint(__FILE__, __LINE__);
979 mapping_ct->mutable_linear()->add_coeffs(1);
980 int64_t mapping_offset = min_index;
983 context->AddEnforcedConstraint(ct)->mutable_exactly_one();
985 const Domain index_domain = context->DomainOf(index_var);
986 for (
const int64_t v : index_domain.Values()) {
987 const int64_t literal =
988 context->NewBoolVar(
"element with unused index expansion");
989 context->solution_crush().MaybeSetLiteralToValueEncoding(literal,
993 const int64_t mapping_coeff = (v - min_index);
994 if (mapping_coeff != 0) {
996 mapping_ct->mutable_linear()->add_vars(literal);
997 mapping_ct->mutable_linear()->add_coeffs(-mapping_coeff);
999 mapping_offset += mapping_coeff;
1000 mapping_ct->mutable_linear()->add_vars(
PositiveRef(literal));
1001 mapping_ct->mutable_linear()->add_coeffs(mapping_coeff);
1006 exactly_one->add_literals(literal);
1015 imply->mutable_linear());
1017 context->CanonicalizeLinearConstraint(imply);
1020 mapping_ct->mutable_linear()->add_domain(mapping_offset);
1021 mapping_ct->mutable_linear()->add_domain(mapping_offset);
1023 context->UpdateNewConstraintsVariableUsage();
1024 context->MarkVariableAsRemoved(index_var);
1025 context->UpdateRuleStats(
1026 "element: expanded variable element with unused index");
1032 if (presolve_is_on && !context->IsFixed(target) &&
1033 context->VariableIsUniqueAndRemovable(target.vars(0))) {
1034 DCHECK_EQ(target.vars_size(), 1);
1035 const int target_var = target.vars(0);
1036 bool domain_is_exact =
true;
1037 const Domain target_domain =
1038 context->DomainOf(target_var)
1039 .MultiplicationBy(target.coeffs(0), &domain_is_exact)
1040 .AdditionWith(Domain(target.offset()));
1042 bool is_unique =
true;
1043 for (
int i = 0;
i < element.exprs_size(); ++
i) {
1050 if (domain_is_exact && is_unique) {
1051 for (
const int64_t v : context->DomainOf(index_var).Values()) {
1052 const int64_t index_lit =
1053 context->GetOrCreateVarValueEncoding(index_var, v);
1055 DCHECK_GE(index_value, 0);
1056 DCHECK_LT(index_value, ct->element().exprs_size());
1063 context->CanonicalizeLinearConstraint(imply);
1065 context->UpdateNewConstraintsVariableUsage();
1066 context->UpdateRuleStats(
1067 "element: expanded variable element with unused target");
1068 context->MarkVariableAsRemoved(target_var);
1069 context->NewMappingConstraint(*ct, __FILE__, __LINE__);
1073 context->UpdateRuleStats(
"TODO element: target not used elsewhere");
1077 const auto add_imply_eq_value_ct = [ct, context](
1079 int64_t value,
int literal) {
1083 if (context->HasAffineValueEncoding(expr, value, &expr_lit)) {
1084 imply->mutable_bool_and()->add_literals(expr_lit);
1091 std::vector<int64_t> invalid_index_values;
1092 context->CanonicalizeLinearExpression(
1093 {}, ct->mutable_element()->mutable_linear_target());
1094 for (
const int64_t v : reduced_index_var_domain.Values()) {
1096 DCHECK_GE(index_value, 0);
1097 DCHECK_LT(index_value, element.exprs_size());
1098 context->CanonicalizeLinearExpression(
1099 {}, ct->mutable_element()->mutable_exprs(index_value));
1101 const bool index_is_valid =
1102 context->IntersectionOfAffineExprsIsNotEmpty(target, expr);
1104 if (!index_is_valid) {
1105 if (ct->enforcement_literal().empty()) {
1106 invalid_index_values.push_back(v);
1108 context->AddEnforcedConstraint(ct)->mutable_bool_and()->add_literals(
1109 NegatedRef(context->GetOrCreateVarValueEncoding(index_var, v)));
1110 context->UpdateRuleStats(
"element: value not in target domain");
1113 const int index_lit = context->GetOrCreateVarValueEncoding(index_var, v);
1114 if (context->IsFixed(target)) {
1115 if (context->IsFixed(expr)) {
1116 DCHECK_EQ(context->FixedValue(target), context->FixedValue(expr));
1118 add_imply_eq_value_ct(expr, context->FixedValue(target), index_lit);
1120 }
else if (context->IsFixed(expr)) {
1121 add_imply_eq_value_ct(target, context->FixedValue(expr), index_lit);
1127 imply->mutable_linear());
1129 context->CanonicalizeLinearConstraint(imply);
1133 imply->mutable_linear()->vars(),
1134 imply->mutable_linear()->coeffs()))
1135 << google::protobuf::ShortFormat(*imply);
1140 if (!invalid_index_values.empty()) {
1141 if (!context->IntersectDomainWith(
1143 ExpandAlwaysFalseConstraint(
1145 "Empty domain for the index variable in ExpandArrayElement()");
1148 context->UpdateRuleStats(
"element: reduce index domain during expansion");
1150 VLOG(3) <<
"Expanded element: |index| = " << reduced_index_var_domain.Size()
1152 << (target.vars().empty() ? 1
1153 : context->DomainOf(target.vars(0)).Size());
1155 context->UpdateRuleStats(
"element: expanded");
1164 const int size = element.exprs_size();
1168 const Domain reduced_index_var_domain =
1169 index.vars_size() == 1
1170 ? context->DomainOf(index.vars(0))
1171 .IntersectionWith(Domain(0, size - 1)
1172 .AdditionWith(Domain(-index.offset()))
1173 .InverseMultiplicationBy(index.coeffs(0)))
1176 if (ct->enforcement_literal().empty()) {
1177 if (!context->IntersectDomainWith(index, Domain(0, size - 1))) {
1178 VLOG(1) <<
"Empty domain for the index variable in ExpandElement()";
1182 const bool reduced_index_domain_is_empty =
1183 index.vars_size() == 1 ? reduced_index_var_domain.IsEmpty()
1184 : (index.offset() < 0 || index.offset() >= size);
1185 if (reduced_index_domain_is_empty) {
1186 ExpandAlwaysFalseConstraint(ct, context);
1193 context->CanonicalizeLinearConstraint(index_ct);
1196 const bool reduced_index_domain_is_fixed =
1197 index.vars_size() == 0 || reduced_index_var_domain.IsFixed();
1198 if (reduced_index_domain_is_fixed) {
1199 DCHECK(!ct->enforcement_literal().empty() || context->IsFixed(index));
1203 const int64_t reduced_index_fixed_value =
1204 index.vars_size() == 0
1207 reduced_index_var_domain.FixedValue());
1209 ct->element().exprs(reduced_index_fixed_value), -1,
1210 eq->mutable_linear());
1211 context->CanonicalizeLinearConstraint(eq);
1212 context->UpdateRuleStats(
"element: expanded with fixed index");
1218 if (index.vars_size() == 1 && target.vars_size() == 1 &&
1219 index.vars(0) == target.vars(0)) {
1220 ExpandElementWhenTargetShareVarWithIndex(ct, context,
1221 reduced_index_var_domain);
1226 bool all_constants =
true;
1227 for (
const int64_t v : reduced_index_var_domain.Values()) {
1229 if (!context->IsFixed(element.exprs(index_value))) {
1230 all_constants =
false;
1235 if (all_constants) {
1236 ExpandConstantArrayElement(ct, context, reduced_index_var_domain);
1238 ExpandVariableElement(ct, context, reduced_index_var_domain);
1248void LinkLiteralsAndValues(absl::Span<const int> enforcement_literals,
1249 absl::Span<const int> literals,
1250 absl::Span<const int64_t> values,
1251 const absl::flat_hash_map<int64_t, int>& encoding,
1253 CHECK_EQ(literals.size(), values.size());
1258 absl::btree_map<int, std::vector<int>> encoding_lit_to_support;
1260 for (
int i = 0;
i < values.size(); ++
i) {
1261 encoding_lit_to_support[encoding.at(values[
i])].push_back(literals[
i]);
1267 for (
const auto& [encoding_lit, support] : encoding_lit_to_support) {
1268 CHECK(!support.empty());
1269 if (support.size() == 1 && enforcement_literals.empty()) {
1270 if (!context->StoreBooleanEqualityRelation(encoding_lit, support[0])) {
1275 context->AddEnforcedConstraint(enforcement_literals)
1276 ->mutable_exactly_one();
1278 for (
const int lit : support) {
1279 exo->add_literals(lit);
1288void AddImplyInReachableValues(absl::Span<const int> enforcement_literals,
1290 std::vector<int64_t>& reachable_values,
1291 const absl::flat_hash_map<int64_t, int> encoding,
1294 if (reachable_values.size() == encoding.size())
return;
1295 if (reachable_values.size() <= encoding.size() / 2) {
1297 ConstraintProto* ct = context->AddEnforcedConstraint(enforcement_literals);
1300 for (
const int64_t v : reachable_values) {
1301 bool_or->add_literals(encoding.at(v));
1305 absl::flat_hash_set<int64_t> set(reachable_values.begin(),
1306 reachable_values.end());
1307 ConstraintProto* ct = context->AddEnforcedConstraint(enforcement_literals);
1310 for (
const auto [value, literal] : encoding) {
1311 if (!set.contains(value)) {
1321 if (proto.exprs_size() == 0) {
1322 const int64_t initial_state = proto.starting_state();
1323 for (
const int64_t final_state : proto.final_states()) {
1324 if (initial_state == final_state) {
1325 context->UpdateRuleStats(
"automaton: empty and trivially feasible");
1331 ExpandAlwaysFalseConstraint(
1333 "automaton: empty with an initial state not in the final states.");
1335 }
else if (proto.transition_label_size() == 0) {
1336 ExpandAlwaysFalseConstraint(ct, context,
1337 "automaton: non-empty with no transition.");
1341 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
1342 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
1350 absl::flat_hash_map<int64_t, int> encoding;
1351 absl::flat_hash_map<int64_t, int> in_encoding;
1352 absl::flat_hash_map<int64_t, int> out_encoding;
1353 bool removed_values =
false;
1354 EnforcedDomains enforced_domains(ct, context);
1356 const int n = proto.exprs_size();
1357 std::vector<SolutionCrush::StateVar> new_state_vars;
1358 std::vector<SolutionCrush::TransitionVar> new_transition_vars;
1359 for (
int time = 0; time < n; ++time) {
1360 if (context->time_limit()->LimitReached())
return;
1364 std::vector<int64_t> in_states;
1365 std::vector<int64_t> labels;
1366 std::vector<int64_t> out_states;
1367 absl::flat_hash_set<int64_t> still_reachable_after_domain_change;
1368 for (
int i = 0;
i < proto.transition_label_size(); ++
i) {
1369 const int64_t tail = proto.transition_tail(
i);
1370 const int64_t label = proto.transition_label(
i);
1371 const int64_t head = proto.transition_head(
i);
1373 if (!reachable_states[time].contains(tail))
continue;
1374 if (!reachable_states[time + 1].contains(head))
continue;
1375 if (!enforced_domains.DomainContains(proto.exprs(time), label))
continue;
1377 still_reachable_after_domain_change.insert(head);
1381 in_states.push_back(tail);
1382 labels.push_back(label);
1386 out_states.push_back(time + 1 == n ? 0 : head);
1389 reachable_states[time + 1] = still_reachable_after_domain_change;
1392 const int num_tuples = in_states.size();
1393 if (num_tuples == 1) {
1394 if (!enforced_domains.IntersectDomainWith(proto.exprs(time),
1395 Domain(labels.front()),
1396 "Infeasible automaton.")) {
1403 std::vector<int> at_false;
1404 for (
const auto [value, literal] : in_encoding) {
1405 if (value != in_states[0]) {
1406 if (!enforced_domains.SetLiteralToFalse(literal))
return;
1410 in_encoding.clear();
1416 std::vector<int64_t> transitions = labels;
1421 if (!enforced_domains.IntersectDomainWith(
1429 if (!enforced_domains.IsFixed(expr)) {
1430 const int var = expr.vars(0);
1431 for (
const int64_t v : enforced_domains.DomainOf(var).Values()) {
1433 context->GetOrCreateVarValueEncoding(var, v);
1440 absl::flat_hash_map<int64_t, int> in_count;
1441 absl::flat_hash_map<int64_t, int> transition_count;
1442 absl::flat_hash_map<int64_t, int> out_count;
1443 for (
int i = 0;
i < num_tuples; ++
i) {
1444 in_count[in_states[
i]]++;
1445 transition_count[labels[
i]]++;
1446 out_count[out_states[
i]]++;
1453 std::vector<int64_t> states = out_states;
1456 out_encoding.clear();
1457 if (states.size() == 2) {
1458 const int var = context->NewBoolVar(
"automaton expansion");
1459 new_state_vars.push_back({var, time + 1, states[0]});
1460 out_encoding[states[0]] = var;
1462 }
else if (states.size() > 2) {
1463 struct UniqueDetector {
1464 void Set(int64_t v) {
1465 if (!is_unique)
return;
1467 if (v != value) is_unique =
false;
1473 bool is_set =
false;
1474 bool is_unique =
true;
1480 absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
1481 absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
1482 for (
int i = 0;
i < num_tuples; ++
i) {
1483 out_to_in[out_states[
i]].Set(in_states[
i]);
1484 out_to_transition[out_states[
i]].Set(labels[
i]);
1487 for (
const int64_t state : states) {
1490 if (!in_encoding.empty() && out_to_in[state].is_unique) {
1491 const int64_t unique_in = out_to_in[state].value;
1492 if (in_count[unique_in] == out_count[state]) {
1493 out_encoding[state] = in_encoding[unique_in];
1500 if (!encoding.empty() && out_to_transition[state].is_unique) {
1501 const int64_t unique_transition = out_to_transition[state].value;
1502 if (transition_count[unique_transition] == out_count[state]) {
1503 out_encoding[state] = encoding[unique_transition];
1508 out_encoding[state] = context->NewBoolVar(
"automaton expansion");
1509 new_state_vars.push_back({out_encoding[state], time + 1, state});
1527 const int num_involved_variables =
1528 in_encoding.size() + encoding.size() + out_encoding.size();
1529 const bool use_light_encoding = (num_tuples > num_involved_variables);
1530 if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
1531 !out_encoding.empty()) {
1535 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
1536 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
1537 for (
int i = 0;
i < num_tuples; ++
i) {
1538 in_to_label[in_states[
i]].push_back(labels[
i]);
1539 in_to_out[in_states[
i]].push_back(out_states[
i]);
1542 std::vector<std::pair<int64_t, int>> in_to_label_pairs(
1543 in_encoding.begin(), in_encoding.end());
1544 absl::c_sort(in_to_label_pairs);
1545 for (
const auto [in_value, in_literal] : in_to_label_pairs) {
1546 AddImplyInReachableValues(ct->enforcement_literal(), in_literal,
1547 in_to_label[in_value], encoding, context);
1548 AddImplyInReachableValues(ct->enforcement_literal(), in_literal,
1549 in_to_out[in_value], out_encoding, context);
1554 for (
int i = 0;
i < num_tuples; ++
i) {
1555 auto* bool_or = context->AddEnforcedConstraint(ct)->mutable_bool_or();
1556 bool_or->add_literals(
NegatedRef(in_encoding.at(in_states[
i])));
1557 bool_or->add_literals(
NegatedRef(encoding.at(labels[
i])));
1558 bool_or->add_literals(out_encoding.at(out_states[
i]));
1561 in_encoding.swap(out_encoding);
1562 out_encoding.clear();
1570 std::vector<int> tuple_literals;
1571 if (num_tuples == 2) {
1572 const int bool_var = context->NewBoolVar(
"automaton expansion");
1573 new_transition_vars.push_back({bool_var, time, in_states[0], labels[0]});
1574 tuple_literals.push_back(bool_var);
1575 tuple_literals.push_back(
NegatedRef(bool_var));
1581 context->AddEnforcedConstraint(ct)->mutable_exactly_one();
1582 for (
int i = 0;
i < num_tuples; ++
i) {
1584 if (in_count[in_states[
i]] == 1 && !in_encoding.empty()) {
1585 tuple_literal = in_encoding[in_states[
i]];
1586 }
else if (transition_count[labels[
i]] == 1 && !encoding.empty()) {
1587 tuple_literal = encoding[labels[
i]];
1588 }
else if (out_count[out_states[
i]] == 1 && !out_encoding.empty()) {
1589 tuple_literal = out_encoding[out_states[
i]];
1591 tuple_literal = context->NewBoolVar(
"automaton expansion");
1592 new_transition_vars.push_back(
1593 {tuple_literal, time, in_states[
i], labels[
i]});
1596 tuple_literals.push_back(tuple_literal);
1597 exactly_one->add_literals(tuple_literal);
1601 if (!in_encoding.empty()) {
1602 LinkLiteralsAndValues(ct->enforcement_literal(), tuple_literals,
1603 in_states, in_encoding, context);
1605 if (!encoding.empty()) {
1606 LinkLiteralsAndValues(ct->enforcement_literal(), tuple_literals, labels,
1609 if (!out_encoding.empty()) {
1610 LinkLiteralsAndValues(ct->enforcement_literal(), tuple_literals,
1611 out_states, out_encoding, context);
1614 in_encoding.swap(out_encoding);
1615 out_encoding.clear();
1618 enforced_domains.MaybeAddEnforcedDomainConstraints();
1620 context->solution_crush().SetAutomatonExpandedVars(proto, new_state_vars,
1621 new_transition_vars);
1622 if (removed_values) {
1623 context->UpdateRuleStats(
"automaton: reduced variable domains");
1625 context->UpdateRuleStats(
"automaton: expanded");
1631 if (!table.vars().empty()) {
1632 LOG(ERROR) <<
"Table is in the legacy format.";
1635 if (table.values().empty()) {
1636 if (table.exprs().empty()) {
1639 if (table.exprs_size() != 1) {
1640 LOG(ERROR) <<
"Table is empty but has more than one expression.";
1643 if (table.exprs(0).offset() != 0) {
1644 LOG(ERROR) <<
"Table is empty but has an expression with a non-zero "
1648 if (!table.exprs(0).vars().empty()) {
1649 LOG(ERROR) <<
"Table is empty but has an expression with a non-constant "
1656 if (expr.offset() != 0) {
1657 LOG(ERROR) <<
"Expression contains an non-zero offset.";
1660 if (expr.coeffs().size() == 1 && expr.coeffs(0) != 1) {
1661 LOG(ERROR) <<
"Expression contains a single variable with a coefficient "
1662 "different from 1.";
1665 if (expr.vars().empty()) {
1666 LOG(ERROR) <<
"Constant expression.";
1674 DCHECK(TableIsInCanonicalForm(ct));
1676 if (table.values().empty()) {
1677 context->UpdateRuleStats(
"table: empty negated constraint");
1683 DCHECK_GT(num_exprs, 0);
1684 const int num_original_tuples = table.values_size() / num_exprs;
1685 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1687 for (
int i = 0;
i < num_original_tuples; ++
i) {
1688 for (
int j = 0; j < num_exprs; ++j) {
1689 tuples[
i].push_back(table.values(count++));
1694 std::vector<int64_t> domain_sizes;
1695 for (
int i = 0;
i < num_exprs; ++
i) {
1696 domain_sizes.push_back(context->DomainOf(table.exprs(
i).vars(0)).Size());
1701 std::vector<int> clause;
1702 for (
const std::vector<int64_t>& tuple : tuples) {
1704 for (
int i = 0;
i < num_exprs; ++
i) {
1705 const int64_t value = tuple[
i];
1709 context->GetOrCreateVarValueEncoding(table.exprs(
i).vars(0), value);
1716 for (
const int lit : clause) {
1717 bool_or->add_literals(lit);
1720 context->UpdateRuleStats(
"table: expanded negated constraint");
1729void ProcessOneCompressedColumn(
1730 int variable, absl::Span<const int> tuple_literals,
1731 absl::Span<
const absl::InlinedVector<int64_t, 2>> values,
1732 std::optional<int> table_is_active_literal,
PresolveContext* context) {
1733 DCHECK_EQ(tuple_literals.size(), values.size());
1737 absl::flat_hash_set<int64_t> value_is_multiple;
1738 std::vector<int> any_values_literals;
1739 std::vector<std::pair<int64_t, int>> pairs;
1740 for (
int i = 0;
i < values.size(); ++
i) {
1741 if (values[
i].empty()) {
1742 any_values_literals.push_back(tuple_literals[
i]);
1745 for (
const int64_t v : values[
i]) {
1746 pairs.emplace_back(v, tuple_literals[
i]);
1748 if (values[
i].size() > 1) {
1749 value_is_multiple.insert(values[
i].
begin(), values[
i].
end());
1754 bool use_exo =
true;
1755 if (table_is_active_literal.has_value()) use_exo =
false;
1756 if (!any_values_literals.empty()) use_exo =
false;
1759 for (
int i = 0;
i < values.size(); ++
i) {
1760 if (values[
i].empty())
continue;
1762 if (use_exo && values[
i].size() == 1 &&
1763 !value_is_multiple.contains(values[
i][0])) {
1769 if (values[
i].size() == 1) {
1772 const int v = values[
i][0];
1773 ct->mutable_bool_and()->add_literals(
1774 context->GetOrCreateVarValueEncoding(variable, v));
1781 for (
const int64_t v : values[
i]) {
1782 DCHECK(context->DomainOf(variable).Contains(v));
1783 literals->add_literals(context->GetOrCreateVarValueEncoding(variable, v));
1789 std::sort(pairs.begin(), pairs.end());
1790 for (
int i = 0;
i < pairs.size();) {
1791 const int64_t value = pairs[
i].first;
1800 use_exo && !value_is_multiple.contains(value)
1801 ? context->working_model->add_constraints()->mutable_exactly_one()
1802 : context->working_model->add_constraints()->mutable_bool_or();
1804 for (;
i < pairs.size() && pairs[
i].first == value; ++
i) {
1805 no_support->add_literals(pairs[
i].second);
1807 for (
const int lit : any_values_literals) {
1808 no_support->add_literals(lit);
1810 if (table_is_active_literal.has_value()) {
1811 no_support->add_literals(
NegatedRef(table_is_active_literal.value()));
1815 const int value_literal =
1816 context->GetOrCreateVarValueEncoding(variable, value);
1822void AddSizeTwoTable(
1823 absl::Span<const int> vars, absl::Span<
const std::vector<int64_t>> tuples,
1824 absl::Span<
const absl::flat_hash_set<int64_t>> values_per_var,
1826 CHECK_EQ(vars.size(), 2);
1827 const int left_var = vars[0];
1828 const int right_var = vars[1];
1829 if (context->DomainOf(left_var).IsFixed() ||
1830 context->DomainOf(right_var).IsFixed()) {
1836 absl::btree_map<int, std::vector<int>> left_to_right;
1837 absl::btree_map<int, std::vector<int>> right_to_left;
1839 for (
const auto& tuple : tuples) {
1840 const int64_t left_value(tuple[0]);
1841 const int64_t right_value(tuple[1]);
1842 DCHECK(context->DomainOf(left_var).Contains(left_value));
1843 DCHECK(context->DomainOf(right_var).Contains(right_value));
1845 const int left_literal =
1846 context->GetOrCreateVarValueEncoding(left_var, left_value);
1847 const int right_literal =
1848 context->GetOrCreateVarValueEncoding(right_var, right_value);
1849 left_to_right[left_literal].push_back(right_literal);
1850 right_to_left[right_literal].push_back(left_literal);
1853 int num_implications = 0;
1854 int num_clause_added = 0;
1855 int num_large_clause_added = 0;
1856 int num_exo_added = 0;
1857 int num_equivalences_added = 0;
1858 auto add_support_constraint =
1859 [context, &num_clause_added, &num_large_clause_added, &num_implications,
1860 &num_exo_added, &num_equivalences_added](
1861 int lit, absl::Span<const int> support_literals,
int max_support_size,
1862 const absl::btree_map<int, std::vector<int>>& other_map) {
1863 if (support_literals.size() == max_support_size)
return;
1864 if (support_literals.size() == 1) {
1865 const int support_literal = support_literals.front();
1866 const auto& it = other_map.find(support_literal);
1867 CHECK(it != other_map.end());
1868 if (it->second.size() > 1) {
1869 context->AddImplication(lit, support_literal);
1872 if (!context->StoreBooleanEqualityRelation(lit, support_literal)) {
1875 ++num_equivalences_added;
1878 bool exclusive =
true;
1879 for (
const int support_literal : support_literals) {
1880 const auto& it = other_map.find(support_literal);
1881 CHECK(it != other_map.end());
1882 if (it->second.size() > 1) {
1889 ->mutable_exactly_one();
1890 for (
const int support_literal : support_literals) {
1891 exo->add_literals(support_literal);
1897 context->working_model->add_constraints()->mutable_bool_or();
1898 for (
const int support_literal : support_literals) {
1899 bool_or->add_literals(support_literal);
1903 if (support_literals.size() > max_support_size / 2) {
1904 num_large_clause_added++;
1910 for (
const auto& it : left_to_right) {
1911 add_support_constraint(it.first, it.second, values_per_var[1].size(),
1914 for (
const auto& it : right_to_left) {
1915 add_support_constraint(it.first, it.second, values_per_var[0].size(),
1918 VLOG(3) <<
"Table: 2 variables, " << tuples.size() <<
" tuples encoded using "
1919 << num_clause_added <<
" clauses, including "
1920 << num_large_clause_added <<
" large clauses, " << num_implications
1921 <<
" implications, " << num_exo_added <<
" exactly ones, "
1922 << num_equivalences_added <<
" equivalences.";
1929bool ReduceTableInPresenceOfUniqueVariableWithCosts(
1930 std::vector<int>* vars, std::vector<std::vector<int64_t>>* tuples,
1932 const int num_vars = vars->size();
1934 std::vector<bool> only_here_and_in_objective(num_vars,
false);
1935 std::vector<int64_t> objective_coeffs(num_vars, 0.0);
1936 std::vector<int> new_vars;
1937 std::vector<int> deleted_vars;
1938 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1939 const int var = (*vars)[var_index];
1944 if (context->VariableWithCostIsUnique(var)) {
1945 context->UpdateRuleStats(
"table: removed unused column with cost");
1946 only_here_and_in_objective[var_index] =
true;
1947 objective_coeffs[var_index] =
1950 context->RemoveVariableFromObjective(var);
1951 context->MarkVariableAsRemoved(var);
1952 deleted_vars.push_back(var);
1953 }
else if (context->VarToConstraints(var).size() == 1) {
1956 context->UpdateRuleStats(
"table: removed unused column");
1957 only_here_and_in_objective[var_index] =
true;
1958 objective_coeffs[var_index] = 0;
1959 context->MarkVariableAsRemoved(var);
1960 deleted_vars.push_back(var);
1962 new_vars.push_back(var);
1965 if (new_vars.size() == num_vars)
return false;
1969 int64_t min_cost = std::numeric_limits<int64_t>::max();
1970 std::vector<int64_t> temp;
1971 for (
int i = 0;
i < tuples->size(); ++
i) {
1975 for (
int var_index = 0; var_index < num_vars; ++var_index) {
1976 const int64_t value = (*tuples)[
i][var_index];
1977 if (only_here_and_in_objective[var_index]) {
1978 temp.push_back(value);
1979 const int64_t objective_coeff = objective_coeffs[var_index];
1980 cost += value * objective_coeff;
1982 (*tuples)[
i][new_size++] = value;
1985 (*tuples)[
i].resize(new_size);
1986 (*tuples)[
i].push_back(cost);
1987 min_cost = std::min(min_cost, cost);
1991 (*tuples)[
i].insert((*tuples)[
i].
end(), temp.begin(), temp.end());
1999 const int old_size = tuples->size();
2000 std::sort(tuples->begin(), tuples->end());
2001 for (
int i = 0;
i < tuples->size(); ++
i) {
2005 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
2006 if ((*tuples)[
i][var_index] != (*tuples)[new_size - 1][var_index]) {
2016 for (
int j = 0; j < deleted_vars.size(); ++j) {
2018 context->NewMappingConstraint(__FILE__, __LINE__);
2019 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
2020 mapping_ct->add_enforcement_literal(
2021 context->GetOrCreateVarValueEncoding(new_vars[var_index],
2022 (*tuples)[
i][var_index]));
2025 new_lin->
add_vars(deleted_vars[j]);
2026 new_lin->add_coeffs(1);
2029 (*tuples)[
i].resize(new_vars.size() + 1);
2030 (*tuples)[new_size++] = (*tuples)[
i];
2032 tuples->resize(new_size);
2033 if (new_size < old_size) {
2034 context->UpdateRuleStats(
2035 "table: removed duplicate tuples with different costs");
2040 context->AddToObjectiveOffset(min_cost);
2041 context->UpdateRuleStats(
"table: transferred min_cost to objective offset");
2042 for (
int i = 0;
i < tuples->size(); ++
i) {
2043 (*tuples)[
i].back() -= min_cost;
2058 for (
int var_index = 0; var_index < new_vars.size(); ++var_index) {
2059 absl::flat_hash_map<int64_t, int64_t> value_to_min_cost;
2060 const int num_tuples = tuples->size();
2061 for (
int i = 0;
i < num_tuples; ++
i) {
2062 const int64_t v = (*tuples)[
i][var_index];
2063 const int64_t cost = (*tuples)[
i].back();
2064 auto insert = value_to_min_cost.insert({v, cost});
2065 if (!insert.second) {
2066 insert.first->second = std::min(insert.first->second, cost);
2069 for (
int i = 0;
i < num_tuples; ++
i) {
2070 const int64_t v = (*tuples)[
i][var_index];
2071 (*tuples)[
i].back() -= value_to_min_cost.at(v);
2073 for (
const auto entry : value_to_min_cost) {
2074 if (entry.second == 0)
continue;
2075 context->UpdateRuleStats(
"table: transferred cost to encoding");
2076 const int value_literal = context->GetOrCreateVarValueEncoding(
2077 new_vars[var_index], entry.first);
2078 context->AddLiteralToObjective(value_literal, entry.second);
2083 context->UpdateRuleStats(absl::StrCat(
2084 "table: expansion with column(s) only in objective. Arity = ",
2094 bool last_column_is_cost,
2095 absl::Span<const int> vars,
2096 std::vector<std::vector<int64_t>>* tuples,
2098 const int num_tuples_before_compression = tuples->size();
2102 std::vector<int64_t> domain_sizes;
2103 for (
const int var : vars) {
2104 domain_sizes.push_back(context->DomainOf(var).Size());
2106 if (last_column_is_cost) {
2107 domain_sizes.push_back(std::numeric_limits<int64_t>::max());
2111 const int compression_level = context->params().table_compression_level();
2112 if (compression_level > 0) {
2115 const int num_tuples_after_first_compression = tuples->size();
2128 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> compressed_table;
2129 if (compression_level > 2 ||
2130 (compression_level == 2 && num_tuples_after_first_compression > 1000)) {
2132 if (compressed_table.size() < num_tuples_before_compression) {
2133 context->UpdateRuleStats(
"table: fully compress tuples");
2137 for (
int i = 0;
i < tuples->size(); ++
i) {
2138 compressed_table.push_back({});
2139 for (
const int64_t v : (*tuples)[
i]) {
2141 compressed_table.back().push_back({});
2143 compressed_table.back().push_back({v});
2147 if (compressed_table.size() < num_tuples_before_compression) {
2148 context->UpdateRuleStats(
"table: compress tuples");
2152 VLOG(3) <<
"Table compression"
2153 <<
" var=" << vars.size()
2154 <<
" cost=" << domain_sizes.size() - vars.size()
2155 <<
" tuples= " << num_tuples_before_compression <<
" -> "
2156 << num_tuples_after_first_compression <<
" -> "
2157 << compressed_table.size();
2160 std::sort(compressed_table.begin(), compressed_table.end());
2162 const int num_vars = vars.size();
2163 if (compressed_table.size() == 1 && ct->enforcement_literal().empty()) {
2165 context->UpdateRuleStats(
"table: one tuple");
2166 if (last_column_is_cost) {
2169 context->AddToObjectiveOffset(compressed_table[0].back()[0]);
2177 std::vector<bool> has_any(num_vars,
false);
2178 std::vector<absl::flat_hash_map<int64_t, int>> var_index_to_value_count(
2180 for (
int i = 0;
i < compressed_table.size(); ++
i) {
2181 for (
int var_index = 0; var_index < num_vars; ++var_index) {
2182 if (compressed_table[
i][var_index].empty()) {
2183 has_any[var_index] =
true;
2186 for (
const int64_t v : compressed_table[
i][var_index]) {
2188 DCHECK(context->DomainOf(vars[var_index]).Contains(v));
2189 var_index_to_value_count[var_index][v]++;
2197 context->working_model->add_constraints()->mutable_exactly_one();
2199 std::optional<int> table_is_active_literal = std::nullopt;
2201 if (ct->enforcement_literal().size() == 1) {
2202 table_is_active_literal = ct->enforcement_literal(0);
2203 }
else if (ct->enforcement_literal().size() > 1) {
2204 table_is_active_literal =
2205 context->NewBoolVarWithConjunction(ct->enforcement_literal());
2209 context->working_model->add_constraints()->mutable_bool_or();
2210 bool_or->
add_literals(table_is_active_literal.value());
2211 for (
const int lit : ct->enforcement_literal()) {
2212 context->AddImplication(table_is_active_literal.value(), lit);
2216 std::vector<int> existing_row_literals;
2217 std::vector<SolutionCrush::TableRowLiteral> new_row_literals;
2218 if (table_is_active_literal.has_value()) {
2219 const int inactive_lit =
NegatedRef(table_is_active_literal.value());
2220 exactly_one->add_literals(inactive_lit);
2221 existing_row_literals.push_back(inactive_lit);
2224 int num_reused_variables = 0;
2225 std::vector<int> tuples_with_new_variable;
2226 std::vector<int> tuple_literals(compressed_table.size());
2227 for (
int i = 0;
i < compressed_table.size(); ++
i) {
2228 bool create_new_var =
true;
2229 for (
int var_index = 0; var_index < num_vars; ++var_index) {
2230 if (has_any[var_index])
continue;
2231 if (compressed_table[
i][var_index].size() != 1 ||
2232 !ct->enforcement_literal().empty()) {
2235 const int64_t v = compressed_table[
i][var_index][0];
2236 if (var_index_to_value_count[var_index][v] != 1)
continue;
2238 ++num_reused_variables;
2239 create_new_var =
false;
2241 context->GetOrCreateVarValueEncoding(vars[var_index], v);
2242 existing_row_literals.push_back(tuple_literals[
i]);
2245 if (create_new_var) {
2246 tuple_literals[
i] = context->NewBoolVar(
"table expansion");
2247 new_row_literals.push_back({tuple_literals[
i], compressed_table[
i]});
2249 exactly_one->add_literals(tuple_literals[
i]);
2251 if (num_reused_variables > 0) {
2252 context->UpdateRuleStats(
"table: reused literals");
2257 if (last_column_is_cost) {
2258 for (
int i = 0;
i < tuple_literals.size(); ++
i) {
2259 context->AddLiteralToObjective(tuple_literals[
i],
2260 compressed_table[
i].back()[0]);
2264 std::vector<absl::InlinedVector<int64_t, 2>> column;
2265 for (
int var_index = 0; var_index < num_vars; ++var_index) {
2266 if (context->IsFixed(vars[var_index]))
continue;
2269 for (
int i = 0;
i < tuple_literals.size(); ++
i) {
2270 column.push_back(compressed_table[
i][var_index]);
2272 ProcessOneCompressedColumn(vars[var_index], tuple_literals, column,
2273 table_is_active_literal, context);
2276 context->solution_crush().SetTableExpandedVars(vars, existing_row_literals,
2278 context->UpdateRuleStats(
"table: expanded positive constraint");
2289 DCHECK(TableIsInCanonicalForm(ct));
2291 if (table.exprs().empty()) {
2292 CHECK(table.values().empty());
2293 context->UpdateRuleStats(
"table: empty trivial");
2298 const int num_original_tuples = table.values_size() / num_exprs;
2301 std::vector<int> vars;
2302 vars.reserve(table.exprs_size());
2303 if (table.values().empty()) {
2304 DCHECK(table.exprs_size() == 1 && table.exprs(0).vars().empty());
2307 vars.push_back(expr.vars(0));
2310 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
2312 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
2313 for (
int var_index = 0; var_index < num_exprs; ++var_index) {
2314 tuples[tuple_index].push_back(table.values(count++));
2320 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_exprs);
2322 for (
int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
2324 for (
int var_index = 0; var_index < num_exprs; ++var_index) {
2325 const int64_t value = tuples[tuple_index][var_index];
2326 if (!context->VarCanTakeValue(vars[var_index], value)) {
2332 for (
int var_index = 0; var_index < num_exprs; ++var_index) {
2333 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
2335 std::swap(tuples[tuple_index], tuples[new_size]);
2339 tuples.resize(new_size);
2341 if (tuples.empty()) {
2342 if (ct->enforcement_literal().empty()) {
2343 context->UpdateRuleStats(
"table: empty");
2344 return (
void)context->NotifyThatModelIsUnsat();
2346 context->UpdateRuleStats(
"table: enforced and empty");
2348 context->working_model->add_constraints()->mutable_bool_or();
2349 for (
const int lit : ct->enforcement_literal()) {
2360 if (ct->enforcement_literal().empty()) {
2361 int num_fixed_variables = 0;
2362 for (
int var_index = 0; var_index < num_exprs; ++var_index) {
2363 CHECK(context->IntersectDomainWith(
2366 values_per_var[var_index].end()})));
2367 if (context->DomainOf(vars[var_index]).IsFixed()) {
2368 num_fixed_variables++;
2372 if (num_fixed_variables == num_exprs - 1) {
2373 context->UpdateRuleStats(
"table: one variable not fixed");
2376 }
else if (num_fixed_variables == num_exprs) {
2377 context->UpdateRuleStats(
"table: all variables fixed");
2388 if (num_exprs == 2 && !context->params().detect_table_with_cost() &&
2389 ct->enforcement_literal().empty()) {
2390 AddSizeTwoTable(vars, tuples, values_per_var, context);
2391 context->UpdateRuleStats(
2392 "table: expanded positive constraint with two variables");
2397 bool last_column_is_cost =
false;
2398 if (context->params().detect_table_with_cost() &&
2399 ct->enforcement_literal().empty()) {
2400 last_column_is_cost =
2401 ReduceTableInPresenceOfUniqueVariableWithCosts(&vars, &tuples, context);
2404 CompressAndExpandPositiveTable(ct, last_column_is_cost, vars, &tuples,
2409bool AllDiffShouldBeExpanded(
const Domain& union_of_domains,
2412 if (union_of_domains.Size() > context->params().max_alldiff_domain_size()) {
2418 int num_fully_encoded = 0;
2419 for (
int i = 0;
i < num_exprs; ++
i) {
2420 if (context->IsFullyEncoded(proto.exprs(
i))) {
2421 num_fully_encoded++;
2425 if ((union_of_domains.Size() <= 2 * proto.exprs_size()) ||
2426 (union_of_domains.Size() <= 32)) {
2431 if (num_fully_encoded == num_exprs) {
2452 if (ct->linear().domain().size() <= 2)
return;
2453 if (ct->linear().vars().size() == 1)
return;
2456 if (params.encode_complex_linear_constraint_with_integer()) {
2462 const int slack = context->NewIntVar(rhs);
2463 context->solution_crush().SetVarToLinearExpression(
2464 slack, ct->linear().vars(), ct->linear().coeffs());
2465 ct->mutable_linear()->add_vars(slack);
2466 ct->mutable_linear()->add_coeffs(-1);
2467 ct->mutable_linear()->clear_domain();
2473 if (ct->enforcement_literal().empty() && ct->linear().domain_size() == 4) {
2476 single_bool = context->NewBoolVar(
"complex linear expansion");
2478 clause = context->working_model->add_constraints()->mutable_bool_or();
2479 for (
const int ref : ct->enforcement_literal()) {
2485 const std::vector<int> enforcement_literals(
2486 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
2487 ct->mutable_enforcement_literal()->Clear();
2488 std::vector<int> domain_literals;
2489 for (
int i = 0;
i < ct->linear().domain_size();
i += 2) {
2490 const int64_t lb = ct->linear().domain(
i);
2491 const int64_t ub = ct->linear().domain(
i + 1);
2493 int subdomain_literal;
2494 if (clause !=
nullptr) {
2495 subdomain_literal = context->NewBoolVar(
"complex linear expansion");
2496 clause->add_literals(subdomain_literal);
2497 domain_literals.push_back(subdomain_literal);
2499 if (
i == 0) domain_literals.push_back(single_bool);
2500 subdomain_literal =
i == 0 ? single_bool :
NegatedRef(single_bool);
2510 context->solution_crush().SetLinearWithComplexDomainExpandedVars(
2511 ct->linear(), domain_literals);
2514 if (context->params().enumerate_all_solutions() &&
2515 !enforcement_literals.empty()) {
2516 int linear_is_enforced;
2517 if (enforcement_literals.size() == 1) {
2518 linear_is_enforced = enforcement_literals[0];
2520 linear_is_enforced = context->NewBoolVar(
"complex linear expansion");
2522 context->working_model->add_constraints()->mutable_bool_or();
2523 for (
const int e_lit : enforcement_literals) {
2526 maintain_linear_is_enforced->add_literals(
NegatedRef(e_lit));
2528 maintain_linear_is_enforced->
add_literals(linear_is_enforced);
2529 context->solution_crush().SetVarToConjunction(linear_is_enforced,
2530 enforcement_literals);
2533 for (
const int lit : domain_literals) {
2534 context->AddImplication(
NegatedRef(linear_is_enforced),
2541 context->UpdateRuleStats(
"linear: expanded complex rhs");
2542 context->InitializeNewDomains();
2543 context->UpdateNewConstraintsVariableUsage();
2544 context->UpdateConstraintVariableUsage(c);
2549 if (lin.vars_size() != 1)
return false;
2553 if (rhs.IsFixed())
return true;
2556 const Domain not_implied =
2557 rhs.InverseMultiplicationBy(lin.coeffs(0))
2559 .IntersectionWith(context->DomainOf(lin.vars(0)));
2560 if (not_implied.IsEmpty())
return false;
2561 return not_implied.IsFixed();
2577void ScanModelAndDecideAllDiffExpansion(
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,
bool& expand,
bool& keep) {
2584 bool at_least_one_var_domain_is_used =
false;
2585 bool at_least_one_var_bound_is_used =
false;
2590 if (expr.vars().empty())
continue;
2591 DCHECK_EQ(1, expr.vars_size());
2592 const int var = expr.vars(0);
2594 if (context->IsFixed(var))
continue;
2596 bool at_least_one_var_domain_is_used =
false;
2597 bool at_least_one_var_bound_is_used =
false;
2600 if (!processed_variables.insert(var).second) {
2601 at_least_one_var_domain_is_used = bounds_of_var_are_used.contains(var);
2602 at_least_one_var_bound_is_used = domain_of_var_is_used.contains(var);
2604 bool domain_is_used =
false;
2605 bool bounds_are_used =
false;
2608 for (
const int ct_index : context->VarToConstraints(var)) {
2610 if (ct_index < 0)
continue;
2613 context->working_model->constraints(ct_index);
2614 switch (other_ct.constraint_case()) {
2630 bounds_are_used =
true;
2635 if (IsVarEqOrNeqValue(context, other_ct.linear()) &&
2636 var == other_ct.linear().vars(0)) {
2638 domain_is_used =
true;
2639 }
else if (other_ct.linear().vars_size() > 2 &&
2640 other_ct.linear().domain_size() == 2 &&
2641 other_ct.linear().domain(0) ==
2642 other_ct.linear().domain(1)) {
2645 bounds_are_used =
true;
2656 if (other_ct.element().index() == var) {
2657 domain_is_used =
true;
2665 domain_is_used =
true;
2670 domain_is_used =
true;
2673 domain_is_used =
true;
2676 bounds_are_used =
true;
2692 if (domain_is_used && bounds_are_used)
break;
2696 if (domain_is_used) domain_of_var_is_used.insert(var);
2697 if (bounds_are_used) bounds_of_var_are_used.insert(var);
2700 at_least_one_var_domain_is_used |= domain_is_used;
2701 at_least_one_var_bound_is_used |= bounds_are_used;
2704 if (at_least_one_var_domain_is_used && at_least_one_var_bound_is_used) {
2709 expand = at_least_one_var_domain_is_used;
2710 keep = at_least_one_var_bound_is_used;
2714 absl::flat_hash_set<int>& domain_of_var_is_used,
2715 absl::flat_hash_set<int>& bounds_of_var_are_used,
2716 absl::flat_hash_set<int>& processed_variable) {
2717 const bool expand_all_diff_from_parameters =
2718 context->params().expand_alldiff_constraints();
2720 if (proto.exprs_size() <= 1)
return;
2721 if (context->ModelIsUnsat())
return;
2723 bool keep_after_expansion =
false;
2724 bool expand_all_diff_from_usage =
false;
2725 ScanModelAndDecideAllDiffExpansion(
2726 ct, context, domain_of_var_is_used, bounds_of_var_are_used,
2727 processed_variable, expand_all_diff_from_usage, keep_after_expansion);
2729 const int num_exprs = proto.exprs_size();
2730 Domain union_of_domains = context->DomainSuperSetOf(proto.exprs(0));
2731 for (
int i = 1;
i < num_exprs; ++
i) {
2733 union_of_domains.UnionWith(context->DomainSuperSetOf(proto.exprs(
i)));
2736 const bool expand_all_diff_from_size =
2737 AllDiffShouldBeExpanded(union_of_domains, ct, context);
2744 const bool should_expand =
2745 expand_all_diff_from_parameters ||
2746 (expand_all_diff_from_size &&
2747 (expand_all_diff_from_usage || !keep_after_expansion));
2748 if (!should_expand)
return;
2750 const bool is_a_permutation = num_exprs == union_of_domains.Size();
2755 for (
const int64_t v : union_of_domains.Values()) {
2757 std::vector<LinearExpressionProto> possible_exprs;
2758 int fixed_expression_count = 0;
2760 if (!context->DomainContains(expr, v))
continue;
2761 possible_exprs.push_back(expr);
2762 if (context->IsFixed(expr)) {
2763 fixed_expression_count++;
2767 if (fixed_expression_count > 1) {
2769 ExpandAlwaysFalseConstraint(ct, context);
2771 }
else if (fixed_expression_count == 1 &&
2772 ct->enforcement_literal().empty()) {
2775 if (context->IsFixed(expr))
continue;
2776 if (!context->IntersectDomainWith(expr, Domain(v).Complement())) {
2777 VLOG(1) <<
"Empty domain for a variable in MaybeExpandAllDiff()";
2785 is_a_permutation ? new_ct->mutable_exactly_one()
2786 : new_ct->mutable_at_most_one();
2790 if (!context->DomainContains(expr, v))
continue;
2795 const int encoding = context->GetOrCreateAffineValueEncoding(expr, v);
2796 at_most_or_equal_one->add_literals(encoding);
2800 context->UpdateRuleStats(
2801 absl::StrCat(
"all_diff:", is_a_permutation ?
" permutation" :
"",
2802 " expanded", keep_after_expansion ?
" and kept" :
""));
2803 if (!keep_after_expansion) ct->Clear();
2823 bool has_all_diffs =
false;
2835 ExpandComplexLinearConstraint(c, ct, context);
2840 ExpandReservoir(ct, context);
2845 EncodeCumulativeAsReservoir(ct, context);
2849 ExpandIntMod(ct, context);
2852 ExpandIntProd(ct, context);
2855 ExpandElement(ct, context);
2858 ExpandInverse(ct, context);
2861 ExpandAutomaton(ct, context);
2869 ExpandNegativeTable(ct, context);
2871 ExpandPositiveTable(ct, context);
2877 ExpandLinMax(ct, context);
2881 has_all_diffs =
true;
2908 absl::flat_hash_set<int> domain_of_var_is_used;
2909 absl::flat_hash_set<int> bounds_of_var_are_used;
2910 absl::flat_hash_set<int> processed_variables;
2919 ExpandEnforcedAtMostOneOrExactlyOneConstraint(ct,
i, context);
2922 MaybeExpandAllDiff(ct, context, domain_of_var_is_used,
2923 bounds_of_var_are_used, processed_variables);
2971 ExpandComplexLinearConstraint(c, ct, context);
static Domain FromValues(std::vector< int64_t > values)
Domain Complement() const
void add_literals(::int32_t value)
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
const ::operations_research::sat::TableConstraintProto & table() const
ConstraintCase constraint_case() const
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::LinearArgumentProto & lin_max() const
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
void add_enforcement_literal(::int32_t value)
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_enforcement_literal()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
int constraints_size() const
int variables_size() const
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL mutable_variables(int index)
::operations_research::sat::LinearExpressionProto *PROTOBUF_NONNULL mutable_target()
const ::operations_research::sat::LinearExpressionProto & exprs(int index) const
::operations_research::sat::LinearExpressionProto *PROTOBUF_NONNULL add_exprs()
void Swap(LinearConstraintProto *PROTOBUF_NONNULL other)
void add_vars(::int32_t value)
::int64_t domain(int index) const
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
void set_offset(::int64_t value)
void add_vars(::int32_t value)
const SatParameters & params() const
CpModelProto * working_model
void ClearPrecedenceCache()
void NotifyThatModelIsExpanded()
bool ModelIsExpanded() const
void InitializeNewDomains()
void UpdateConstraintVariableUsage(int c)
SolverLogger * logger() const
void UpdateNewConstraintsVariableUsage()
const Domain & DomainOf(int var) const
bool ModelIsUnsat() const
int time_exprs_size() const
bool expand_reservoir_constraints() const
bool encode_cumulative_as_reservoir() const
::int32_t max_lin_max_size_for_expansion() const
bool disable_constraint_expansion() const
bool cp_model_presolve() const
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)
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset, std::pair< int64_t, int64_t > *implied_domain)
void LiteralsToLinear(absl::Span< const int > literals, int64_t lb, int64_t ub, LinearConstraintProto *linear)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void FinalExpansionForLinearConstraint(PresolveContext *context)
int64_t AffineExpressionValueAt(const LinearExpressionProto &expr, int64_t value)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
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)
void AddWeightedLiteralToLinearConstraint(int lit, int64_t coeff, LinearConstraintProto *linear, int64_t *offset)
void CanonicalizeTable(PresolveContext *context, ConstraintProto *ct)
bool AffineExpressionContainsVar(const LinearExpressionProto &expr, int var)
ClosedInterval::Iterator end(ClosedInterval interval)
std::string ProtobufShortDebugString(const P &message)
ClosedInterval::Iterator begin(ClosedInterval interval)
bool Contains(int64_t value) const
#define SOLVER_LOG(logger,...)