OR-Tools  9.2
cp_model_expand.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <limits>
19 #include <map>
20 #include <string>
21 
22 #include "absl/container/flat_hash_map.h"
23 #include "ortools/base/hash.h"
24 #include "ortools/base/map_util.h"
25 #include "ortools/base/stl_util.h"
30 #include "ortools/sat/util.h"
33 
34 namespace operations_research {
35 namespace sat {
36 namespace {
37 
38 void ExpandReservoir(ConstraintProto* ct, PresolveContext* context) {
39  if (ct->reservoir().min_level() > ct->reservoir().max_level()) {
40  VLOG(1) << "Empty level domain in reservoir constraint.";
41  return (void)context->NotifyThatModelIsUnsat();
42  }
43 
44  const ReservoirConstraintProto& reservoir = ct->reservoir();
45  const int num_events = reservoir.time_exprs_size();
46 
47  const int true_literal = context->GetOrCreateConstantVar(1);
48 
49  const auto is_active_literal = [&reservoir, true_literal](int index) {
50  if (reservoir.active_literals_size() == 0) return true_literal;
51  return reservoir.active_literals(index);
52  };
53 
54  int num_positives = 0;
55  int num_negatives = 0;
56  for (const int64_t demand : reservoir.level_changes()) {
57  if (demand > 0) {
58  num_positives++;
59  } else if (demand < 0) {
60  num_negatives++;
61  }
62  }
63 
64  absl::flat_hash_map<std::pair<int, int>, int> precedence_cache;
65 
66  if (num_positives > 0 && num_negatives > 0) {
67  // Creates Boolean variables equivalent to (start[i] <= start[j]) i != j
68  for (int i = 0; i < num_events - 1; ++i) {
69  const int active_i = is_active_literal(i);
70  if (context->LiteralIsFalse(active_i)) continue;
71  const LinearExpressionProto& time_i = reservoir.time_exprs(i);
72 
73  for (int j = i + 1; j < num_events; ++j) {
74  const int active_j = is_active_literal(j);
75  if (context->LiteralIsFalse(active_j)) continue;
76  const LinearExpressionProto& time_j = reservoir.time_exprs(j);
77 
78  const int i_lesseq_j = context->GetOrCreateReifiedPrecedenceLiteral(
79  time_i, time_j, active_i, active_j);
80  context->working_model->mutable_variables(i_lesseq_j)
81  ->set_name(absl::StrCat(i, " before ", j));
82  precedence_cache[{i, j}] = i_lesseq_j;
83  const int j_lesseq_i = context->GetOrCreateReifiedPrecedenceLiteral(
84  time_j, time_i, active_j, active_i);
85  context->working_model->mutable_variables(j_lesseq_i)
86  ->set_name(absl::StrCat(j, " before ", i));
87  precedence_cache[{j, i}] = j_lesseq_i;
88  }
89  }
90 
91  // Constrains the running level to be consistent at all time_exprs.
92  // For this we only add a constraint at the time a given demand
93  // take place. We also have a constraint for time zero if needed
94  // (added below).
95  for (int i = 0; i < num_events; ++i) {
96  const int active_i = is_active_literal(i);
97  if (context->LiteralIsFalse(active_i)) continue;
98 
99  // Accumulates level_changes of all predecessors.
100  ConstraintProto* const level = context->working_model->add_constraints();
101  level->add_enforcement_literal(active_i);
102 
103  // Add contributions from previous events.
104  int64_t offset = 0;
105  for (int j = 0; j < num_events; ++j) {
106  if (i == j) continue;
107  const int active_j = is_active_literal(j);
108  if (context->LiteralIsFalse(active_j)) continue;
109 
110  const auto prec_it = precedence_cache.find({j, i});
111  CHECK(prec_it != precedence_cache.end());
112  const int prec_lit = prec_it->second;
113  const int64_t demand = reservoir.level_changes(j);
114  if (RefIsPositive(prec_lit)) {
115  level->mutable_linear()->add_vars(prec_lit);
116  level->mutable_linear()->add_coeffs(demand);
117  } else {
118  level->mutable_linear()->add_vars(prec_lit);
119  level->mutable_linear()->add_coeffs(-demand);
120  offset -= demand;
121  }
122  }
123 
124  // Accounts for own demand in the domain of the sum.
125  const int64_t demand_i = reservoir.level_changes(i);
126  level->mutable_linear()->add_domain(
127  CapAdd(CapSub(reservoir.min_level(), demand_i), offset));
128  level->mutable_linear()->add_domain(
129  CapAdd(CapSub(reservoir.max_level(), demand_i), offset));
130  }
131  } else {
132  // If all level_changes have the same sign, we do not care about the order,
133  // just the sum.
134  auto* const sum =
135  context->working_model->add_constraints()->mutable_linear();
136  for (int i = 0; i < num_events; ++i) {
137  sum->add_vars(is_active_literal(i));
138  sum->add_coeffs(reservoir.level_changes(i));
139  }
140  sum->add_domain(reservoir.min_level());
141  sum->add_domain(reservoir.max_level());
142  }
143 
144  ct->Clear();
145  context->UpdateRuleStats("reservoir: expanded");
146 }
147 
148 void ExpandIntMod(ConstraintProto* ct, PresolveContext* context) {
149  const LinearArgumentProto& int_mod = ct->int_mod();
150  const LinearExpressionProto& mod_expr = int_mod.exprs(1);
151  if (context->IsFixed(mod_expr)) return;
152 
153  const LinearExpressionProto& expr = int_mod.exprs(0);
154  const LinearExpressionProto& target_expr = int_mod.target();
155 
156  // We reduce the domain of target_expr to avoid later overflow.
157  if (!context->IntersectDomainWith(
158  target_expr, context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
159  context->DomainSuperSetOf(mod_expr)))) {
160  return;
161  }
162 
163  // Create a new constraint with the same enforcement as ct.
164  auto new_enforced_constraint = [&]() {
165  ConstraintProto* new_ct = context->working_model->add_constraints();
166  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
167  return new_ct;
168  };
169 
170  // div_expr = expr / mod_expr.
171  const int div_var = context->NewIntVar(
172  context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
173  context->DomainSuperSetOf(mod_expr)));
174  LinearExpressionProto div_expr;
175  div_expr.add_vars(div_var);
176  div_expr.add_coeffs(1);
177 
178  LinearArgumentProto* const div_proto =
179  new_enforced_constraint()->mutable_int_div();
180  *div_proto->mutable_target() = div_expr;
181  *div_proto->add_exprs() = expr;
182  *div_proto->add_exprs() = mod_expr;
183 
184  // Create prod_expr = div_expr * mod_expr.
185  const Domain prod_domain =
186  context->DomainOf(div_var)
187  .ContinuousMultiplicationBy(context->DomainSuperSetOf(mod_expr))
188  .IntersectionWith(context->DomainSuperSetOf(expr).AdditionWith(
189  context->DomainSuperSetOf(target_expr).Negation()));
190  const int prod_var = context->NewIntVar(prod_domain);
191  LinearExpressionProto prod_expr;
192  prod_expr.add_vars(prod_var);
193  prod_expr.add_coeffs(1);
194 
195  LinearArgumentProto* const int_prod =
196  new_enforced_constraint()->mutable_int_prod();
197  *int_prod->mutable_target() = prod_expr;
198  *int_prod->add_exprs() = div_expr;
199  *int_prod->add_exprs() = mod_expr;
200 
201  // expr - prod_expr = target_expr.
202  LinearConstraintProto* const lin =
203  new_enforced_constraint()->mutable_linear();
204  lin->add_domain(0);
205  lin->add_domain(0);
207  AddLinearExpressionToLinearConstraint(prod_expr, -1, lin);
208  AddLinearExpressionToLinearConstraint(target_expr, -1, lin);
209 
210  ct->Clear();
211  context->UpdateRuleStats("int_mod: expanded");
212 }
213 
214 // TODO(user): Move this into the presolve instead?
215 void ExpandIntProdWithBoolean(int bool_ref,
216  const LinearExpressionProto& int_expr,
217  const LinearExpressionProto& product_expr,
218  PresolveContext* context) {
219  ConstraintProto* const one = context->working_model->add_constraints();
220  one->add_enforcement_literal(bool_ref);
221  one->mutable_linear()->add_domain(0);
222  one->mutable_linear()->add_domain(0);
223  AddLinearExpressionToLinearConstraint(int_expr, 1, one->mutable_linear());
224  AddLinearExpressionToLinearConstraint(product_expr, -1,
225  one->mutable_linear());
226 
227  ConstraintProto* const zero = context->working_model->add_constraints();
228  zero->add_enforcement_literal(NegatedRef(bool_ref));
229  zero->mutable_linear()->add_domain(0);
230  zero->mutable_linear()->add_domain(0);
232  zero->mutable_linear());
233 }
234 
235 void ExpandIntProd(ConstraintProto* ct, PresolveContext* context) {
236  const LinearArgumentProto& int_prod = ct->int_prod();
237  if (int_prod.exprs_size() != 2) return;
238  const LinearExpressionProto& a = int_prod.exprs(0);
239  const LinearExpressionProto& b = int_prod.exprs(1);
240  const LinearExpressionProto& p = int_prod.target();
241  int literal;
242  const bool a_is_literal = context->ExpressionIsALiteral(a, &literal);
243  const bool b_is_literal = context->ExpressionIsALiteral(b, &literal);
244 
245  // We expand if exactly one of {a, b} is a literal. If both are literals, it
246  // will be presolved into a better version.
247  if (a_is_literal && !b_is_literal) {
248  ExpandIntProdWithBoolean(literal, b, p, context);
249  ct->Clear();
250  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
251  } else if (b_is_literal) {
252  ExpandIntProdWithBoolean(literal, a, p, context);
253  ct->Clear();
254  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
255  }
256 }
257 
258 void ExpandInverse(ConstraintProto* ct, PresolveContext* context) {
259  const auto& f_direct = ct->inverse().f_direct();
260  const auto& f_inverse = ct->inverse().f_inverse();
261  const int n = f_direct.size();
262  CHECK_EQ(n, f_inverse.size());
263 
264  // Make sure the domains are included in [0, n - 1).
265  // Note that if a variable and its negation appear, the domains will be set to
266  // zero here.
267  //
268  // TODO(user): Add support for UNSAT at expansion. This should create empty
269  // domain if UNSAT, so it should still work correctly.
270  absl::flat_hash_set<int> used_variables;
271  for (const int ref : f_direct) {
272  used_variables.insert(PositiveRef(ref));
273  if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
274  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
275  return;
276  }
277  }
278  for (const int ref : f_inverse) {
279  used_variables.insert(PositiveRef(ref));
280  if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
281  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
282  return;
283  }
284  }
285 
286  // If we have duplicate variables, we make sure the domain are reduced
287  // as the loop below might not detect incompatibilities.
288  if (used_variables.size() != 2 * n) {
289  for (int i = 0; i < n; ++i) {
290  for (int j = 0; j < n; ++j) {
291  // Note that if we don't have the same sign, both domain are at zero.
292  if (PositiveRef(f_direct[i]) != PositiveRef(f_inverse[j])) continue;
293 
294  // We can't have i or j as value if i != j.
295  if (i == j) continue;
296  if (!context->IntersectDomainWith(
297  f_direct[i], Domain::FromValues({i, j}).Complement())) {
298  return;
299  }
300  }
301  }
302  }
303 
304  // Reduce the domains of each variable by checking that the inverse value
305  // exists.
306  std::vector<int64_t> possible_values;
307 
308  // Propagate from one vector to its counterpart.
309  const auto filter_inverse_domain =
310  [context, n, &possible_values](const auto& direct, const auto& inverse) {
311  // Propagate from the inverse vector to the direct vector.
312  for (int i = 0; i < n; ++i) {
313  possible_values.clear();
314  const Domain domain = context->DomainOf(direct[i]);
315  bool removed_value = false;
316  for (const int64_t j : domain.Values()) {
317  if (context->DomainOf(inverse[j]).Contains(i)) {
318  possible_values.push_back(j);
319  } else {
320  removed_value = true;
321  }
322  }
323  if (removed_value) {
324  if (!context->IntersectDomainWith(
325  direct[i], Domain::FromValues(possible_values))) {
326  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
327  return false;
328  }
329  }
330  }
331  return true;
332  };
333 
334  // Note that this should reach the fixed point in one pass.
335  // However, if we have duplicate variable, I am not sure.
336  if (!filter_inverse_domain(f_direct, f_inverse)) return;
337  if (!filter_inverse_domain(f_inverse, f_direct)) return;
338 
339  // Expand the inverse constraint by associating literal to var == value
340  // and sharing them between the direct and inverse variables.
341  //
342  // Note that this is only correct because the domain are tight now.
343  for (int i = 0; i < n; ++i) {
344  const int f_i = f_direct[i];
345  for (const int64_t j : context->DomainOf(f_i).Values()) {
346  // We have f[i] == j <=> r[j] == i;
347  const int r_j = f_inverse[j];
348  int r_j_i;
349  if (context->HasVarValueEncoding(r_j, i, &r_j_i)) {
350  context->InsertVarValueEncoding(r_j_i, f_i, j);
351  } else {
352  const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
353  context->InsertVarValueEncoding(f_i_j, r_j, i);
354  }
355  }
356  }
357 
358  ct->Clear();
359  context->UpdateRuleStats("inverse: expanded");
360 }
361 
362 // A[V] == V means for all i, V == i => A_i == i
363 void ExpandElementWithTargetEqualIndex(ConstraintProto* ct,
364  PresolveContext* context) {
365  const ElementConstraintProto& element = ct->element();
366  DCHECK_EQ(element.index(), element.target());
367 
368  const int index_ref = element.index();
369  std::vector<int64_t> valid_indices;
370  for (const int64_t v : context->DomainOf(index_ref).Values()) {
371  if (!context->DomainContains(element.vars(v), v)) continue;
372  valid_indices.push_back(v);
373  }
374  if (valid_indices.size() < context->DomainOf(index_ref).Size()) {
375  if (!context->IntersectDomainWith(index_ref,
376  Domain::FromValues(valid_indices))) {
377  VLOG(1) << "No compatible variable domains in "
378  "ExpandElementWithTargetEqualIndex()";
379  return;
380  }
381  context->UpdateRuleStats("element: reduced index domain");
382  }
383 
384  for (const int64_t v : context->DomainOf(index_ref).Values()) {
385  const int var = element.vars(v);
386  if (context->MinOf(var) == v && context->MaxOf(var) == v) continue;
387  context->AddImplyInDomain(
388  context->GetOrCreateVarValueEncoding(index_ref, v), var, Domain(v));
389  }
390  context->UpdateRuleStats(
391  "element: expanded with special case target = index");
392  ct->Clear();
393 }
394 
395 // Special case if the array of the element is filled with constant values.
396 void ExpandConstantArrayElement(ConstraintProto* ct, PresolveContext* context) {
397  const ElementConstraintProto& element = ct->element();
398  const int index_ref = element.index();
399  const int target_ref = element.target();
400 
401  // Index and target domain have been reduced before calling this function.
402  const Domain index_domain = context->DomainOf(index_ref);
403  const Domain target_domain = context->DomainOf(target_ref);
404 
405  // This BoolOrs implements the deduction that if all index literals pointing
406  // to the same value in the constant array are false, then this value is no
407  // no longer valid for the target variable. They are created only for values
408  // that have multiples literals supporting them.
409  // Order is not important.
410  absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
411  {
412  absl::flat_hash_map<int64_t, int> constant_var_values_usage;
413  for (const int64_t v : index_domain.Values()) {
414  DCHECK(context->IsFixed(element.vars(v)));
415  const int64_t value = context->MinOf(element.vars(v));
416  if (++constant_var_values_usage[value] == 2) {
417  // First time we cross > 1.
418  BoolArgumentProto* const support =
419  context->working_model->add_constraints()->mutable_bool_or();
420  const int target_literal =
421  context->GetOrCreateVarValueEncoding(target_ref, value);
422  support->add_literals(NegatedRef(target_literal));
423  supports[value] = support;
424  }
425  }
426  }
427 
428  {
429  // While this is not stricly needed since all value in the index will be
430  // covered, it allows to easily detect this fact in the presolve.
431  auto* exactly_one =
432  context->working_model->add_constraints()->mutable_exactly_one();
433  for (const int64_t v : index_domain.Values()) {
434  const int index_literal =
435  context->GetOrCreateVarValueEncoding(index_ref, v);
436  exactly_one->add_literals(index_literal);
437 
438  const int64_t value = context->MinOf(element.vars(v));
439  const auto& it = supports.find(value);
440  if (it != supports.end()) {
441  // The encoding literal for 'value' of the target_ref has been
442  // created before.
443  const int target_literal =
444  context->GetOrCreateVarValueEncoding(target_ref, value);
445  context->AddImplication(index_literal, target_literal);
446  it->second->add_literals(index_literal);
447  } else {
448  // Try to reuse the literal of the index.
449  context->InsertVarValueEncoding(index_literal, target_ref, value);
450  }
451  }
452  }
453 
454  context->UpdateRuleStats("element: expanded value element");
455  ct->Clear();
456 }
457 
458 // General element when the array contains non fixed variables.
459 void ExpandVariableElement(ConstraintProto* ct, PresolveContext* context) {
460  const ElementConstraintProto& element = ct->element();
461  const int index_ref = element.index();
462  const int target_ref = element.target();
463  const Domain index_domain = context->DomainOf(index_ref);
464 
465  BoolArgumentProto* bool_or =
466  context->working_model->add_constraints()->mutable_bool_or();
467 
468  for (const int64_t v : index_domain.Values()) {
469  const int var = element.vars(v);
470  const Domain var_domain = context->DomainOf(var);
471  const int index_lit = context->GetOrCreateVarValueEncoding(index_ref, v);
472  bool_or->add_literals(index_lit);
473 
474  if (var_domain.IsFixed()) {
475  context->AddImplyInDomain(index_lit, target_ref, var_domain);
476  } else {
477  ConstraintProto* const ct = context->working_model->add_constraints();
478  ct->add_enforcement_literal(index_lit);
479  ct->mutable_linear()->add_vars(var);
480  ct->mutable_linear()->add_coeffs(1);
481  ct->mutable_linear()->add_vars(target_ref);
482  ct->mutable_linear()->add_coeffs(-1);
483  ct->mutable_linear()->add_domain(0);
484  ct->mutable_linear()->add_domain(0);
485  }
486  }
487 
488  context->UpdateRuleStats("element: expanded");
489  ct->Clear();
490 }
491 
492 void ExpandElement(ConstraintProto* ct, PresolveContext* context) {
493  const ElementConstraintProto& element = ct->element();
494 
495  const int index_ref = element.index();
496  const int target_ref = element.target();
497  const int size = element.vars_size();
498 
499  // Reduce the domain of the index to be compatible with the array of
500  // variables. Note that the element constraint is 0 based.
501  if (!context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
502  VLOG(1) << "Empty domain for the index variable in ExpandElement()";
503  return;
504  }
505 
506  // Special case when index = target.
507  if (index_ref == target_ref) {
508  ExpandElementWithTargetEqualIndex(ct, context);
509  return;
510  }
511 
512  // Reduces the domain of the index and the target.
513  bool all_constants = true;
514  std::vector<int64_t> valid_indices;
515  const Domain index_domain = context->DomainOf(index_ref);
516  const Domain target_domain = context->DomainOf(target_ref);
517  Domain reached_domain;
518  for (const int64_t v : index_domain.Values()) {
519  const Domain var_domain = context->DomainOf(element.vars(v));
520  if (var_domain.IntersectionWith(target_domain).IsEmpty()) continue;
521 
522  valid_indices.push_back(v);
523  reached_domain = reached_domain.UnionWith(var_domain);
524  if (var_domain.Min() != var_domain.Max()) {
525  all_constants = false;
526  }
527  }
528 
529  if (valid_indices.size() < index_domain.Size()) {
530  if (!context->IntersectDomainWith(index_ref,
531  Domain::FromValues(valid_indices))) {
532  VLOG(1) << "No compatible variable domains in ExpandElement()";
533  return;
534  }
535 
536  context->UpdateRuleStats("element: reduced index domain");
537  }
538 
539  // We know the target_domain is not empty as this would have triggered the
540  // above check.
541  bool target_domain_changed = false;
542  if (!context->IntersectDomainWith(target_ref, reached_domain,
543  &target_domain_changed)) {
544  return;
545  }
546 
547  if (target_domain_changed) {
548  context->UpdateRuleStats("element: reduced target domain");
549  }
550 
551  if (all_constants) {
552  ExpandConstantArrayElement(ct, context);
553  return;
554  }
555 
556  ExpandVariableElement(ct, context);
557 }
558 
559 // Adds clauses so that literals[i] true <=> encoding[values[i]] true.
560 // This also implicitly use the fact that exactly one alternative is true.
561 void LinkLiteralsAndValues(const std::vector<int>& literals,
562  const std::vector<int64_t>& values,
563  const absl::flat_hash_map<int64_t, int>& encoding,
564  PresolveContext* context) {
565  CHECK_EQ(literals.size(), values.size());
566 
567  // We use a map to make this method deterministic.
568  //
569  // TODO(user): Make sure this does not appear in the profile. We could use
570  // the same code as in ProcessOneVariable() otherwise.
571  std::map<int, std::vector<int>> encoding_lit_to_support;
572 
573  // If a value is false (i.e not possible), then the tuple with this
574  // value is false too (i.e not possible). Conversely, if the tuple is
575  // selected, the value must be selected.
576  for (int i = 0; i < values.size(); ++i) {
577  encoding_lit_to_support[encoding.at(values[i])].push_back(literals[i]);
578  }
579 
580  // If all tuples supporting a value are false, then this value must be
581  // false.
582  for (const auto& [encoding_lit, support] : encoding_lit_to_support) {
583  CHECK(!support.empty());
584  if (support.size() == 1) {
585  context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
586  } else {
587  BoolArgumentProto* bool_or =
588  context->working_model->add_constraints()->mutable_bool_or();
589  bool_or->add_literals(NegatedRef(encoding_lit));
590  for (const int lit : support) {
591  bool_or->add_literals(lit);
592  context->AddImplication(lit, encoding_lit);
593  }
594  }
595  }
596 }
597 
598 // Add the constraint literal => one_of(encoding[v]), for v in reachable_values.
599 // Note that all possible values are the ones appearing in encoding.
600 void AddImplyInReachableValues(int literal,
601  std::vector<int64_t>& reachable_values,
602  const absl::flat_hash_map<int64_t, int> encoding,
603  PresolveContext* context) {
604  gtl::STLSortAndRemoveDuplicates(&reachable_values);
605  if (reachable_values.size() == encoding.size()) return; // No constraint.
606  if (reachable_values.size() <= encoding.size() / 2) {
607  // Bool or encoding.
608  ConstraintProto* ct = context->working_model->add_constraints();
609  ct->add_enforcement_literal(literal);
610  BoolArgumentProto* bool_or = ct->mutable_bool_or();
611  for (const int64_t v : reachable_values) {
612  bool_or->add_literals(encoding.at(v));
613  }
614  } else {
615  // Bool and encoding.
616  absl::flat_hash_set<int64_t> set(reachable_values.begin(),
617  reachable_values.end());
618  ConstraintProto* ct = context->working_model->add_constraints();
619  ct->add_enforcement_literal(literal);
620  BoolArgumentProto* bool_and = ct->mutable_bool_and();
621  for (const auto [value, literal] : encoding) {
622  if (!set.contains(value)) {
623  bool_and->add_literals(NegatedRef(literal));
624  }
625  }
626  }
627 }
628 
629 void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
630  AutomatonConstraintProto& proto = *ct->mutable_automaton();
631 
632  if (proto.vars_size() == 0) {
633  const int64_t initial_state = proto.starting_state();
634  for (const int64_t final_state : proto.final_states()) {
635  if (initial_state == final_state) {
636  context->UpdateRuleStats("automaton: empty and trivially feasible");
637  ct->Clear();
638  return;
639  }
640  }
641  return (void)context->NotifyThatModelIsUnsat(
642  "automaton: empty with an initial state not in the final states.");
643  } else if (proto.transition_label_size() == 0) {
644  return (void)context->NotifyThatModelIsUnsat(
645  "automaton: non-empty with no transition.");
646  }
647 
648  const int n = proto.vars_size();
649  const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
650 
651  // Compute the set of reachable state at each time point.
652  const absl::flat_hash_set<int64_t> final_states(
653  {proto.final_states().begin(), proto.final_states().end()});
654  std::vector<absl::flat_hash_set<int64_t>> reachable_states(n + 1);
655  reachable_states[0].insert(proto.starting_state());
656 
657  // Forward pass.
658  for (int time = 0; time < n; ++time) {
659  for (int t = 0; t < proto.transition_tail_size(); ++t) {
660  const int64_t tail = proto.transition_tail(t);
661  const int64_t label = proto.transition_label(t);
662  const int64_t head = proto.transition_head(t);
663  if (!reachable_states[time].contains(tail)) continue;
664  if (!context->DomainContains(vars[time], label)) continue;
665  if (time == n - 1 && !final_states.contains(head)) continue;
666  reachable_states[time + 1].insert(head);
667  }
668  }
669 
670  // Backward pass.
671  for (int time = n - 1; time >= 0; --time) {
672  absl::flat_hash_set<int64_t> new_set;
673  for (int t = 0; t < proto.transition_tail_size(); ++t) {
674  const int64_t tail = proto.transition_tail(t);
675  const int64_t label = proto.transition_label(t);
676  const int64_t head = proto.transition_head(t);
677 
678  if (!reachable_states[time].contains(tail)) continue;
679  if (!context->DomainContains(vars[time], label)) continue;
680  if (!reachable_states[time + 1].contains(head)) continue;
681  new_set.insert(tail);
682  }
683  reachable_states[time].swap(new_set);
684  }
685 
686  // We will model at each time step the current automaton state using Boolean
687  // variables. We will have n+1 time step. At time zero, we start in the
688  // initial state, and at time n we should be in one of the final states. We
689  // don't need to create Booleans at at time when there is just one possible
690  // state (like at time zero).
691  absl::flat_hash_map<int64_t, int> encoding;
692  absl::flat_hash_map<int64_t, int> in_encoding;
693  absl::flat_hash_map<int64_t, int> out_encoding;
694  bool removed_values = false;
695 
696  for (int time = 0; time < n; ++time) {
697  // All these vector have the same size. We will use them to enforce a
698  // local table constraint representing one step of the automaton at the
699  // given time.
700  std::vector<int64_t> in_states;
701  std::vector<int64_t> labels;
702  std::vector<int64_t> out_states;
703  for (int i = 0; i < proto.transition_label_size(); ++i) {
704  const int64_t tail = proto.transition_tail(i);
705  const int64_t label = proto.transition_label(i);
706  const int64_t head = proto.transition_head(i);
707 
708  if (!reachable_states[time].contains(tail)) continue;
709  if (!reachable_states[time + 1].contains(head)) continue;
710  if (!context->DomainContains(vars[time], label)) continue;
711 
712  // TODO(user): if this transition correspond to just one in-state or
713  // one-out state or one variable value, we could reuse the corresponding
714  // Boolean variable instead of creating a new one!
715  in_states.push_back(tail);
716  labels.push_back(label);
717 
718  // On the last step we don't need to distinguish the output states, so
719  // we use zero.
720  out_states.push_back(time + 1 == n ? 0 : head);
721  }
722 
723  // Deal with single tuple.
724  const int num_tuples = in_states.size();
725  if (num_tuples == 1) {
726  if (!context->IntersectDomainWith(vars[time], Domain(labels.front()))) {
727  VLOG(1) << "Infeasible automaton.";
728  return;
729  }
730  in_encoding.clear();
731  continue;
732  }
733 
734  // Fully encode vars[time].
735  {
736  std::vector<int64_t> transitions = labels;
737  gtl::STLSortAndRemoveDuplicates(&transitions);
738 
739  encoding.clear();
740  if (!context->IntersectDomainWith(
741  vars[time], Domain::FromValues(transitions), &removed_values)) {
742  VLOG(1) << "Infeasible automaton.";
743  return;
744  }
745 
746  // Fully encode the variable.
747  // We can leave the encoding empty for fixed vars.
748  if (!context->IsFixed(vars[time])) {
749  for (const int64_t v : context->DomainOf(vars[time]).Values()) {
750  encoding[v] = context->GetOrCreateVarValueEncoding(vars[time], v);
751  }
752  }
753  }
754 
755  // Count how many time each value appear.
756  // We use this to reuse literals if possible.
757  absl::flat_hash_map<int64_t, int> in_count;
758  absl::flat_hash_map<int64_t, int> transition_count;
759  absl::flat_hash_map<int64_t, int> out_count;
760  for (int i = 0; i < num_tuples; ++i) {
761  in_count[in_states[i]]++;
762  transition_count[labels[i]]++;
763  out_count[out_states[i]]++;
764  }
765 
766  // For each possible out states, create one Boolean variable.
767  //
768  // TODO(user): Add exactly one?
769  {
770  std::vector<int64_t> states = out_states;
772 
773  out_encoding.clear();
774  if (states.size() == 2) {
775  const int var = context->NewBoolVar();
776  out_encoding[states[0]] = var;
777  out_encoding[states[1]] = NegatedRef(var);
778  } else if (states.size() > 2) {
779  struct UniqueDetector {
780  void Set(int64_t v) {
781  if (!is_unique) return;
782  if (is_set) {
783  if (v != value) is_unique = false;
784  } else {
785  is_set = true;
786  value = v;
787  }
788  }
789  bool is_set = false;
790  bool is_unique = true;
791  int64_t value = 0;
792  };
793 
794  // Optimization to detect if we have an in state that is only matched to
795  // a single out state. Same with transition.
796  absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
797  absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
798  for (int i = 0; i < num_tuples; ++i) {
799  out_to_in[out_states[i]].Set(in_states[i]);
800  out_to_transition[out_states[i]].Set(labels[i]);
801  }
802 
803  for (const int64_t state : states) {
804  // If we have a relation in_state <=> out_state, then we can reuse
805  // the in Boolean and do not need to create a new one.
806  if (!in_encoding.empty() && out_to_in[state].is_unique) {
807  const int64_t unique_in = out_to_in[state].value;
808  if (in_count[unique_in] == out_count[state]) {
809  out_encoding[state] = in_encoding[unique_in];
810  continue;
811  }
812  }
813 
814  // Same if we have an unique transition value that correspond only to
815  // this state.
816  if (!encoding.empty() && out_to_transition[state].is_unique) {
817  const int64_t unique_transition = out_to_transition[state].value;
818  if (transition_count[unique_transition] == out_count[state]) {
819  out_encoding[state] = encoding[unique_transition];
820  continue;
821  }
822  }
823 
824  out_encoding[state] = context->NewBoolVar();
825  }
826  }
827  }
828 
829  // Simple encoding. This is enough to properly enforce the constraint, but
830  // it propagate less. It creates a lot less Booleans though. Note that we
831  // use implicit "exactly one" on the encoding and do not add any extra
832  // exacly one if the simple encoding is used.
833  //
834  // We currently decide which encoding to use depending on the number of new
835  // literals needed by the "heavy" encoding compared to the number of states
836  // and labels. When the automaton is small, using the full encoding is
837  // better, see for instance on rotating-workforce_Example789 were the simple
838  // encoding make the problem hard to solve but the full encoding allow the
839  // solver to solve it in a couple of seconds!
840  //
841  // Note that both encoding create about the same number of constraints.
842  const int num_involved_variables =
843  in_encoding.size() + encoding.size() + out_encoding.size();
844  const bool use_light_encoding = (num_tuples > num_involved_variables);
845  if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
846  !out_encoding.empty()) {
847  // Part 1: If a in_state is selected, restrict the set of possible labels.
848  // We also restrict the set of possible out states, but this is not needed
849  // for correctness.
850  absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
851  absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
852  for (int i = 0; i < num_tuples; ++i) {
853  in_to_label[in_states[i]].push_back(labels[i]);
854  in_to_out[in_states[i]].push_back(out_states[i]);
855  }
856  for (const auto [in_value, in_literal] : in_encoding) {
857  AddImplyInReachableValues(in_literal, in_to_label[in_value], encoding,
858  context);
859  AddImplyInReachableValues(in_literal, in_to_out[in_value], out_encoding,
860  context);
861  }
862 
863  // Part2, add all 3-clauses: (in_state, label) => out_state.
864  for (int i = 0; i < num_tuples; ++i) {
865  auto* bool_or =
866  context->working_model->add_constraints()->mutable_bool_or();
867  bool_or->add_literals(NegatedRef(in_encoding.at(in_states[i])));
868  bool_or->add_literals(NegatedRef(encoding.at(labels[i])));
869  bool_or->add_literals(out_encoding.at(out_states[i]));
870  }
871 
872  in_encoding.swap(out_encoding);
873  out_encoding.clear();
874  continue;
875  }
876 
877  // Create the tuple literals.
878  //
879  // TODO(user): Call and use the same heuristics as the table constraint to
880  // expand this small table with 3 columns (i.e. compress, negate, etc...).
881  std::vector<int> tuple_literals;
882  if (num_tuples == 2) {
883  const int bool_var = context->NewBoolVar();
884  tuple_literals.push_back(bool_var);
885  tuple_literals.push_back(NegatedRef(bool_var));
886  } else {
887  // Note that we do not need the ExactlyOneConstraint(tuple_literals)
888  // because it is already implicitly encoded since we have exactly one
889  // transition value. But adding one seems to help.
890  BoolArgumentProto* exactly_one =
891  context->working_model->add_constraints()->mutable_exactly_one();
892  for (int i = 0; i < num_tuples; ++i) {
893  int tuple_literal;
894  if (in_count[in_states[i]] == 1 && !in_encoding.empty()) {
895  tuple_literal = in_encoding[in_states[i]];
896  } else if (transition_count[labels[i]] == 1 && !encoding.empty()) {
897  tuple_literal = encoding[labels[i]];
898  } else if (out_count[out_states[i]] == 1 && !out_encoding.empty()) {
899  tuple_literal = out_encoding[out_states[i]];
900  } else {
901  tuple_literal = context->NewBoolVar();
902  }
903 
904  tuple_literals.push_back(tuple_literal);
905  exactly_one->add_literals(tuple_literal);
906  }
907  }
908 
909  if (!in_encoding.empty()) {
910  LinkLiteralsAndValues(tuple_literals, in_states, in_encoding, context);
911  }
912  if (!encoding.empty()) {
913  LinkLiteralsAndValues(tuple_literals, labels, encoding, context);
914  }
915  if (!out_encoding.empty()) {
916  LinkLiteralsAndValues(tuple_literals, out_states, out_encoding, context);
917  }
918 
919  in_encoding.swap(out_encoding);
920  out_encoding.clear();
921  }
922 
923  if (removed_values) {
924  context->UpdateRuleStats("automaton: reduced variable domains");
925  }
926  context->UpdateRuleStats("automaton: expanded");
927  ct->Clear();
928 }
929 
930 void ExpandNegativeTable(ConstraintProto* ct, PresolveContext* context) {
931  TableConstraintProto& table = *ct->mutable_table();
932  const int num_vars = table.vars_size();
933  const int num_original_tuples = table.values_size() / num_vars;
934  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
935  int count = 0;
936  for (int i = 0; i < num_original_tuples; ++i) {
937  for (int j = 0; j < num_vars; ++j) {
938  tuples[i].push_back(table.values(count++));
939  }
940  }
941 
942  if (tuples.empty()) { // Early exit.
943  context->UpdateRuleStats("table: empty negated constraint");
944  ct->Clear();
945  return;
946  }
947 
948  // Compress tuples.
949  const int64_t any_value = std::numeric_limits<int64_t>::min();
950  std::vector<int64_t> domain_sizes;
951  for (int i = 0; i < num_vars; ++i) {
952  domain_sizes.push_back(context->DomainOf(table.vars(i)).Size());
953  }
954  CompressTuples(domain_sizes, any_value, &tuples);
955 
956  // For each tuple, forbid the variables values to be this tuple.
957  std::vector<int> clause;
958  for (const std::vector<int64_t>& tuple : tuples) {
959  clause.clear();
960  for (int i = 0; i < num_vars; ++i) {
961  const int64_t value = tuple[i];
962  if (value == any_value) continue;
963 
964  const int literal =
965  context->GetOrCreateVarValueEncoding(table.vars(i), value);
966  clause.push_back(NegatedRef(literal));
967  }
968 
969  // Note: if the clause is empty, then the model is infeasible.
970  BoolArgumentProto* bool_or =
971  context->working_model->add_constraints()->mutable_bool_or();
972  for (const int lit : clause) {
973  bool_or->add_literals(lit);
974  }
975  }
976  context->UpdateRuleStats("table: expanded negated constraint");
977  ct->Clear();
978 }
979 
980 // Add the implications and clauses to link one variable of a table to the
981 // literals controlling if the tuples are possible or not. The parallel vectors
982 // (tuple_literals, values) contains all valid projected tuples.
983 //
984 // The special value "any_value" is used to indicate literal that will support
985 // any value.
986 void ProcessOneVariable(const std::vector<int>& tuple_literals,
987  const std::vector<int64_t>& values, int variable,
988  int64_t any_value, PresolveContext* context) {
989  VLOG(2) << "Process var(" << variable << ") with domain "
990  << context->DomainOf(variable) << " and " << values.size()
991  << " tuples.";
992  CHECK_EQ(tuple_literals.size(), values.size());
993 
994  // Collect pairs of value-literal.
995  std::vector<int> tuples_with_any;
996  std::vector<std::pair<int64_t, int>> pairs;
997  for (int i = 0; i < values.size(); ++i) {
998  const int64_t value = values[i];
999  if (value == any_value) {
1000  tuples_with_any.push_back(tuple_literals[i]);
1001  continue;
1002  }
1003  CHECK(context->DomainContains(variable, value));
1004  pairs.emplace_back(value, tuple_literals[i]);
1005  }
1006 
1007  // Regroup literal with the same value and add for each the clause: If all the
1008  // tuples containing a value are false, then this value must be false too.
1009  std::vector<int> selected;
1010  std::sort(pairs.begin(), pairs.end());
1011  for (int i = 0; i < pairs.size();) {
1012  selected.clear();
1013  const int64_t value = pairs[i].first;
1014  for (; i < pairs.size() && pairs[i].first == value; ++i) {
1015  selected.push_back(pairs[i].second);
1016  }
1017 
1018  CHECK(!selected.empty() || !tuples_with_any.empty());
1019  if (selected.size() == 1 && tuples_with_any.empty()) {
1020  context->InsertVarValueEncoding(selected.front(), variable, value);
1021  } else {
1022  const int value_literal =
1023  context->GetOrCreateVarValueEncoding(variable, value);
1024  BoolArgumentProto* no_support =
1025  context->working_model->add_constraints()->mutable_bool_or();
1026  for (const int lit : selected) {
1027  no_support->add_literals(lit);
1028  context->AddImplication(lit, value_literal);
1029  }
1030  for (const int lit : tuples_with_any) {
1031  no_support->add_literals(lit);
1032  }
1033 
1034  // And the "value" literal.
1035  no_support->add_literals(NegatedRef(value_literal));
1036  }
1037  }
1038 }
1039 
1040 // Simpler encoding for table constraints with 2 variables.
1041 void AddSizeTwoTable(
1042  const std::vector<int>& vars,
1043  const std::vector<std::vector<int64_t>>& tuples,
1044  const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1045  PresolveContext* context) {
1046  CHECK_EQ(vars.size(), 2);
1047  const int left_var = vars[0];
1048  const int right_var = vars[1];
1049  if (context->DomainOf(left_var).IsFixed() ||
1050  context->DomainOf(right_var).IsFixed()) {
1051  // A table constraint with at most one variable not fixed is trivially
1052  // enforced after domain reduction.
1053  return;
1054  }
1055 
1056  std::map<int, std::vector<int>> left_to_right;
1057  std::map<int, std::vector<int>> right_to_left;
1058 
1059  for (const auto& tuple : tuples) {
1060  const int64_t left_value(tuple[0]);
1061  const int64_t right_value(tuple[1]);
1062  CHECK(context->DomainContains(left_var, left_value));
1063  CHECK(context->DomainContains(right_var, right_value));
1064 
1065  const int left_literal =
1066  context->GetOrCreateVarValueEncoding(left_var, left_value);
1067  const int right_literal =
1068  context->GetOrCreateVarValueEncoding(right_var, right_value);
1069  left_to_right[left_literal].push_back(right_literal);
1070  right_to_left[right_literal].push_back(left_literal);
1071  }
1072 
1073  int num_implications = 0;
1074  int num_clause_added = 0;
1075  int num_large_clause_added = 0;
1076  auto add_support_constraint =
1077  [context, &num_clause_added, &num_large_clause_added, &num_implications](
1078  int lit, const std::vector<int>& support_literals,
1079  int max_support_size) {
1080  if (support_literals.size() == max_support_size) return;
1081  if (support_literals.size() == 1) {
1082  context->AddImplication(lit, support_literals.front());
1083  num_implications++;
1084  } else {
1085  BoolArgumentProto* bool_or =
1086  context->working_model->add_constraints()->mutable_bool_or();
1087  for (const int support_literal : support_literals) {
1088  bool_or->add_literals(support_literal);
1089  }
1090  bool_or->add_literals(NegatedRef(lit));
1091  num_clause_added++;
1092  if (support_literals.size() > max_support_size / 2) {
1093  num_large_clause_added++;
1094  }
1095  }
1096  };
1097 
1098  for (const auto& it : left_to_right) {
1099  add_support_constraint(it.first, it.second, values_per_var[1].size());
1100  }
1101  for (const auto& it : right_to_left) {
1102  add_support_constraint(it.first, it.second, values_per_var[0].size());
1103  }
1104  VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
1105  << num_clause_added << " clauses, including "
1106  << num_large_clause_added << " large clauses, " << num_implications
1107  << " implications";
1108 }
1109 
1110 void ExpandPositiveTable(ConstraintProto* ct, PresolveContext* context) {
1111  const TableConstraintProto& table = ct->table();
1112  const int num_vars = table.vars_size();
1113  const int num_original_tuples = table.values_size() / num_vars;
1114 
1115  // Read tuples flat array and recreate the vector of tuples.
1116  const std::vector<int> vars(table.vars().begin(), table.vars().end());
1117  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1118  int count = 0;
1119  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1120  for (int var_index = 0; var_index < num_vars; ++var_index) {
1121  tuples[tuple_index].push_back(table.values(count++));
1122  }
1123  }
1124 
1125  // Compute the set of possible values for each variable (from the table).
1126  // Remove invalid tuples along the way.
1127  std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1128  int new_size = 0;
1129  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1130  bool keep = true;
1131  for (int var_index = 0; var_index < num_vars; ++var_index) {
1132  const int64_t value = tuples[tuple_index][var_index];
1133  if (!context->DomainContains(vars[var_index], value)) {
1134  keep = false;
1135  break;
1136  }
1137  }
1138  if (keep) {
1139  for (int var_index = 0; var_index < num_vars; ++var_index) {
1140  values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1141  }
1142  std::swap(tuples[tuple_index], tuples[new_size]);
1143  new_size++;
1144  }
1145  }
1146  tuples.resize(new_size);
1147  const int num_valid_tuples = tuples.size();
1148 
1149  if (tuples.empty()) {
1150  context->UpdateRuleStats("table: empty");
1151  return (void)context->NotifyThatModelIsUnsat();
1152  }
1153 
1154  // Update variable domains. It is redundant with presolve, but we could be
1155  // here with presolve = false.
1156  // Also counts the number of fixed variables.
1157  int num_fixed_variables = 0;
1158  for (int var_index = 0; var_index < num_vars; ++var_index) {
1159  CHECK(context->IntersectDomainWith(
1160  vars[var_index],
1161  Domain::FromValues({values_per_var[var_index].begin(),
1162  values_per_var[var_index].end()})));
1163  if (context->DomainOf(vars[var_index]).IsFixed()) {
1164  num_fixed_variables++;
1165  }
1166  }
1167 
1168  if (num_fixed_variables == num_vars - 1) {
1169  context->UpdateRuleStats("table: one variable not fixed");
1170  ct->Clear();
1171  return;
1172  } else if (num_fixed_variables == num_vars) {
1173  context->UpdateRuleStats("table: all variables fixed");
1174  ct->Clear();
1175  return;
1176  }
1177 
1178  // Tables with two variables do not need tuple literals.
1179  if (num_vars == 2) {
1180  AddSizeTwoTable(vars, tuples, values_per_var, context);
1181  context->UpdateRuleStats(
1182  "table: expanded positive constraint with two variables");
1183  ct->Clear();
1184  return;
1185  }
1186 
1187  // It is easier to compute this before compression, as compression will merge
1188  // tuples.
1189  int num_prefix_tuples = 0;
1190  {
1191  absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
1192  for (const std::vector<int64_t>& tuple : tuples) {
1193  prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1194  }
1195  num_prefix_tuples = prefixes.size();
1196  }
1197 
1198  // TODO(user): reinvestigate ExploreSubsetOfVariablesAndAddNegatedTables.
1199 
1200  // Compress tuples.
1201  const int64_t any_value = std::numeric_limits<int64_t>::min();
1202  std::vector<int64_t> domain_sizes;
1203  for (int i = 0; i < num_vars; ++i) {
1204  domain_sizes.push_back(values_per_var[i].size());
1205  }
1206  const int num_tuples_before_compression = tuples.size();
1207  CompressTuples(domain_sizes, any_value, &tuples);
1208  const int num_compressed_tuples = tuples.size();
1209  if (num_compressed_tuples < num_tuples_before_compression) {
1210  context->UpdateRuleStats("table: compress tuples");
1211  }
1212 
1213  if (num_compressed_tuples == 1) {
1214  // Domains are propagated. We can remove the constraint.
1215  context->UpdateRuleStats("table: one tuple");
1216  ct->Clear();
1217  return;
1218  }
1219 
1220  // Detect if prefix tuples are all different.
1221  const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1222  if (prefixes_are_all_different) {
1223  context->UpdateRuleStats(
1224  "TODO table: last value implied by previous values");
1225  }
1226  // TODO(user): if 2 table constraints share the same valid prefix, the
1227  // tuple literals can be reused.
1228  // TODO(user): investigate different encoding for prefix tables. Maybe
1229  // we can remove the need to create tuple literals.
1230 
1231  // Debug message to log the status of the expansion.
1232  if (VLOG_IS_ON(2)) {
1233  // Compute the maximum number of prefix tuples.
1234  int64_t max_num_prefix_tuples = 1;
1235  for (int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1236  max_num_prefix_tuples =
1237  CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1238  }
1239 
1240  std::string message =
1241  absl::StrCat("Table: ", num_vars,
1242  " variables, original tuples = ", num_original_tuples);
1243  if (num_valid_tuples != num_original_tuples) {
1244  absl::StrAppend(&message, ", valid tuples = ", num_valid_tuples);
1245  }
1246  if (prefixes_are_all_different) {
1247  if (num_prefix_tuples < max_num_prefix_tuples) {
1248  absl::StrAppend(&message, ", partial prefix = ", num_prefix_tuples, "/",
1249  max_num_prefix_tuples);
1250  } else {
1251  absl::StrAppend(&message, ", full prefix = true");
1252  }
1253  } else {
1254  absl::StrAppend(&message, ", num prefix tuples = ", num_prefix_tuples);
1255  }
1256  if (num_compressed_tuples != num_valid_tuples) {
1257  absl::StrAppend(&message,
1258  ", compressed tuples = ", num_compressed_tuples);
1259  }
1260  VLOG(2) << message;
1261  }
1262 
1263  // Log if we have only two tuples.
1264  if (num_compressed_tuples == 2) {
1265  context->UpdateRuleStats("TODO table: two tuples");
1266  }
1267 
1268  // Create one Boolean variable per tuple to indicate if it can still be
1269  // selected or not.
1270  std::vector<int> tuple_literals(num_compressed_tuples);
1271  BoolArgumentProto* exactly_one =
1272  context->working_model->add_constraints()->mutable_exactly_one();
1273  for (int i = 0; i < num_compressed_tuples; ++i) {
1274  tuple_literals[i] = context->NewBoolVar();
1275  exactly_one->add_literals(tuple_literals[i]);
1276  }
1277 
1278  std::vector<int64_t> values(num_compressed_tuples);
1279  for (int var_index = 0; var_index < num_vars; ++var_index) {
1280  if (values_per_var[var_index].size() == 1) continue;
1281  for (int i = 0; i < num_compressed_tuples; ++i) {
1282  values[i] = tuples[i][var_index];
1283  }
1284  ProcessOneVariable(tuple_literals, values, vars[var_index], any_value,
1285  context);
1286  }
1287 
1288  context->UpdateRuleStats("table: expanded positive constraint");
1289  ct->Clear();
1290 }
1291 
1292 bool AllDiffShouldBeExpanded(const Domain& union_of_domains,
1293  ConstraintProto* ct, PresolveContext* context) {
1294  const AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
1295  const int num_exprs = proto.exprs_size();
1296  int num_fully_encoded = 0;
1297  for (int i = 0; i < num_exprs; ++i) {
1298  if (context->IsFullyEncoded(proto.exprs(i))) {
1299  num_fully_encoded++;
1300  }
1301  }
1302 
1303  if ((union_of_domains.Size() <= 2 * proto.exprs_size()) ||
1304  (union_of_domains.Size() <= 32)) {
1305  // Small domains.
1306  return true;
1307  }
1308 
1309  if (num_fully_encoded == num_exprs && union_of_domains.Size() < 256) {
1310  // All variables fully encoded, and domains are small enough.
1311  return true;
1312  }
1313  return false;
1314 }
1315 
1316 void ExpandAllDiff(bool force_alldiff_expansion, ConstraintProto* ct,
1317  PresolveContext* context) {
1318  AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
1319  if (proto.exprs_size() <= 1) return;
1320 
1321  const int num_exprs = proto.exprs_size();
1322  Domain union_of_domains = context->DomainSuperSetOf(proto.exprs(0));
1323  for (int i = 1; i < num_exprs; ++i) {
1324  union_of_domains =
1325  union_of_domains.UnionWith(context->DomainSuperSetOf(proto.exprs(i)));
1326  }
1327 
1328  if (!AllDiffShouldBeExpanded(union_of_domains, ct, context) &&
1329  !force_alldiff_expansion) {
1330  return;
1331  }
1332 
1333  const bool is_a_permutation = num_exprs == union_of_domains.Size();
1334 
1335  // Collect all possible variables that can take each value, and add one linear
1336  // equation per value stating that this value can be assigned at most once, or
1337  // exactly once in case of permutation.
1338  for (const int64_t v : union_of_domains.Values()) {
1339  // Collect references which domain contains v.
1340  std::vector<LinearExpressionProto> possible_exprs;
1341  int fixed_expression_count = 0;
1342  for (const LinearExpressionProto& expr : proto.exprs()) {
1343  if (!context->DomainContains(expr, v)) continue;
1344  possible_exprs.push_back(expr);
1345  if (context->IsFixed(expr)) {
1346  fixed_expression_count++;
1347  }
1348  }
1349 
1350  if (fixed_expression_count > 1) {
1351  // Violates the definition of AllDifferent.
1352  return (void)context->NotifyThatModelIsUnsat();
1353  } else if (fixed_expression_count == 1) {
1354  // Remove values from other domains.
1355  for (const LinearExpressionProto& expr : possible_exprs) {
1356  if (context->IsFixed(expr)) continue;
1357  if (!context->IntersectDomainWith(expr, Domain(v).Complement())) {
1358  VLOG(1) << "Empty domain for a variable in ExpandAllDiff()";
1359  return;
1360  }
1361  }
1362  }
1363 
1364  BoolArgumentProto* at_most_or_equal_one =
1365  is_a_permutation
1366  ? context->working_model->add_constraints()->mutable_exactly_one()
1367  : context->working_model->add_constraints()->mutable_at_most_one();
1368  for (const LinearExpressionProto& expr : possible_exprs) {
1369  DCHECK(context->DomainContains(expr, v));
1370  DCHECK(!context->IsFixed(expr));
1371  const int encoding = context->GetOrCreateAffineValueEncoding(expr, v);
1372  at_most_or_equal_one->add_literals(encoding);
1373  }
1374  }
1375  if (is_a_permutation) {
1376  context->UpdateRuleStats("all_diff: permutation expanded");
1377  } else {
1378  context->UpdateRuleStats("all_diff: expanded");
1379  }
1380  ct->Clear();
1381 }
1382 
1383 // Replaces a constraint literal => ax + by != cte by a set of clauses.
1384 // This is performed if the domains are small enough, and the variables are
1385 // fully encoded.
1386 //
1387 // We do it during the expansion as we want the first pass of the presolve to be
1388 // complete.
1389 void ExpandSomeLinearOfSizeTwo(ConstraintProto* ct, PresolveContext* context) {
1390  const LinearConstraintProto& arg = ct->linear();
1391  if (arg.vars_size() != 2) return;
1392 
1393  const int var1 = arg.vars(0);
1394  const int var2 = arg.vars(1);
1395  if (context->IsFixed(var1) || context->IsFixed(var2)) return;
1396 
1397  const int64_t coeff1 = arg.coeffs(0);
1398  const int64_t coeff2 = arg.coeffs(1);
1399 
1400  const Domain reachable_rhs_superset =
1401  context->DomainOf(var1).MultiplicationBy(coeff1).AdditionWith(
1402  context->DomainOf(var2).MultiplicationBy(coeff2));
1403 
1404  const Domain infeasible_reachable_values =
1405  reachable_rhs_superset.IntersectionWith(
1406  ReadDomainFromProto(arg).Complement());
1407 
1408  // We only deal with != cte constraints.
1409  if (infeasible_reachable_values.Size() != 1) return;
1410 
1411  // coeff1 * v1 + coeff2 * v2 != cte.
1412  int64_t a = coeff1;
1413  int64_t b = coeff2;
1414  int64_t cte = infeasible_reachable_values.FixedValue();
1415  int64_t x0 = 0;
1416  int64_t y0 = 0;
1417  if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
1418  // no solution.
1419  context->UpdateRuleStats("linear: expand always feasible ax + by != cte");
1420  ct->Clear();
1421  return;
1422  }
1423  const Domain reduced_domain =
1424  context->DomainOf(var1)
1425  .AdditionWith(Domain(-x0))
1426  .InverseMultiplicationBy(b)
1427  .IntersectionWith(context->DomainOf(var2)
1428  .AdditionWith(Domain(-y0))
1429  .InverseMultiplicationBy(-a));
1430 
1431  if (reduced_domain.Size() > 16) return;
1432 
1433  // Check if all the needed values are encoded.
1434  // TODO(user): Do we force encoding for very small domains? Current
1435  // experiments says no, but revisit later.
1436  const int64_t size1 = context->DomainOf(var1).Size();
1437  const int64_t size2 = context->DomainOf(var2).Size();
1438  for (const int64_t z : reduced_domain.Values()) {
1439  const int64_t value1 = x0 + b * z;
1440  const int64_t value2 = y0 - a * z;
1441  DCHECK(context->DomainContains(var1, value1)) << "value1 = " << value1;
1442  DCHECK(context->DomainContains(var2, value2)) << "value2 = " << value2;
1443  DCHECK_EQ(coeff1 * value1 + coeff2 * value2,
1444  infeasible_reachable_values.FixedValue());
1445  // TODO(user, fdid): Presolve if one or two variables are Boolean.
1446  if (!context->HasVarValueEncoding(var1, value1, nullptr) || size1 == 2) {
1447  return;
1448  }
1449  if (!context->HasVarValueEncoding(var2, value2, nullptr) || size2 == 2) {
1450  return;
1451  }
1452  }
1453 
1454  // All encoding literals already exist and the number of clauses to create
1455  // is small enough. We can encode the constraint using just clauses.
1456  for (const int64_t z : reduced_domain.Values()) {
1457  const int64_t value1 = x0 + b * z;
1458  const int64_t value2 = y0 - a * z;
1459  // We cannot have both lit1 and lit2 true.
1460  const int lit1 = context->GetOrCreateVarValueEncoding(var1, value1);
1461  const int lit2 = context->GetOrCreateVarValueEncoding(var2, value2);
1462  auto* bool_or =
1463  context->working_model->add_constraints()->mutable_bool_or();
1464  bool_or->add_literals(NegatedRef(lit1));
1465  bool_or->add_literals(NegatedRef(lit2));
1466  for (const int lit : ct->enforcement_literal()) {
1467  bool_or->add_literals(NegatedRef(lit));
1468  }
1469  }
1470 
1471  context->UpdateRuleStats("linear: expand small ax + by != cte");
1472  ct->Clear();
1473 }
1474 
1475 } // namespace
1476 
1478  if (context->params().disable_constraint_expansion()) return;
1479  if (context->ModelIsUnsat()) return;
1480 
1481  // None of the function here need to be run twice. This is because we never
1482  // create constraint that need to be expanded during presolve.
1483  if (context->ModelIsExpanded()) return;
1484 
1485  // Make sure all domains are initialized.
1486  context->InitializeNewDomains();
1487 
1488  // Clear the precedence cache.
1489  context->ClearPrecedenceCache();
1490 
1491  // First pass: we look at constraints that may fully encode variables.
1492  for (int i = 0; i < context->working_model->constraints_size(); ++i) {
1493  ConstraintProto* const ct = context->working_model->mutable_constraints(i);
1494  bool skip = false;
1495  switch (ct->constraint_case()) {
1496  case ConstraintProto::ConstraintCase::kReservoir:
1497  ExpandReservoir(ct, context);
1498  break;
1499  case ConstraintProto::ConstraintCase::kIntMod:
1500  ExpandIntMod(ct, context);
1501  break;
1502  case ConstraintProto::ConstraintCase::kIntProd:
1503  ExpandIntProd(ct, context);
1504  break;
1505  case ConstraintProto::ConstraintCase::kElement:
1506  ExpandElement(ct, context);
1507  break;
1508  case ConstraintProto::ConstraintCase::kInverse:
1509  ExpandInverse(ct, context);
1510  break;
1511  case ConstraintProto::ConstraintCase::kAutomaton:
1512  ExpandAutomaton(ct, context);
1513  break;
1514  case ConstraintProto::ConstraintCase::kTable:
1515  if (ct->table().negated()) {
1516  ExpandNegativeTable(ct, context);
1517  } else {
1518  ExpandPositiveTable(ct, context);
1519  }
1520  break;
1521  default:
1522  skip = true;
1523  break;
1524  }
1525  if (skip) continue; // Nothing was done for this constraint.
1526 
1527  // Update variable-constraint graph.
1528  context->UpdateNewConstraintsVariableUsage();
1529  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1530  context->UpdateConstraintVariableUsage(i);
1531  }
1532 
1533  // Early exit if the model is unsat.
1534  if (context->ModelIsUnsat()) {
1535  SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
1537  return;
1538  }
1539  }
1540 
1541  // Second pass. We may decide to expand constraints if all their variables
1542  // are fully encoded.
1543  for (int i = 0; i < context->working_model->constraints_size(); ++i) {
1544  ConstraintProto* const ct = context->working_model->mutable_constraints(i);
1545  bool skip = false;
1546  switch (ct->constraint_case()) {
1547  case ConstraintProto::ConstraintCase::kAllDiff:
1548  ExpandAllDiff(context->params().expand_alldiff_constraints(), ct,
1549  context);
1550  break;
1551  case ConstraintProto::ConstraintCase::kLinear:
1552  ExpandSomeLinearOfSizeTwo(ct, context);
1553  break;
1554  default:
1555  skip = true;
1556  break;
1557  }
1558 
1559  if (skip) continue; // Nothing was done for this constraint.
1560 
1561  // Update variable-constraint graph.
1562  context->UpdateNewConstraintsVariableUsage();
1563  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1564  context->UpdateConstraintVariableUsage(i);
1565  }
1566 
1567  // Early exit if the model is unsat.
1568  if (context->ModelIsUnsat()) {
1569  SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
1571  return;
1572  }
1573  }
1574 
1575  // The precedence cache can become invalid during presolve as it does not
1576  // handle variable substitution. It is safer just to clear it at the end
1577  // of the expansion phase.
1578  context->ClearPrecedenceCache();
1579 
1580  // Make sure the context is consistent.
1581  context->InitializeNewDomains();
1582 
1583  // Update any changed domain from the context.
1584  for (int i = 0; i < context->working_model->variables_size(); ++i) {
1585  FillDomainInProto(context->DomainOf(i),
1586  context->working_model->mutable_variables(i));
1587  }
1588 
1589  context->NotifyThatModelIsExpanded();
1590 }
1591 
1592 } // namespace sat
1593 } // namespace operations_research
int64_t head
#define CHECK(condition)
Definition: base/logging.h:495
int64_t CapSub(int64_t x, int64_t y)
int64_t min
Definition: alldiff_cst.cc:139
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG(verboselevel)
Definition: base/logging.h:983
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
int64_t CapProd(int64_t x, int64_t y)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
int64_t tail
Domain Complement() const
Returns the set Int64 ∖ D.
int64_t b
CpModelProto proto
int64_t CapAdd(int64_t x, int64_t y)
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
void ExpandCpModel(PresolveContext *context)
int64_t demand
Definition: resource.cc:125
int index
Definition: pack.cc:509
std::string message
Definition: trace.cc:398
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition: sat/util.cc:126
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
friend void swap(CpModelProto &a, CpModelProto &b)
Definition: cp_model.pb.h:6161
#define DCHECK(condition)
Definition: base/logging.h:889
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:890
std::string ProtobufShortDebugString(const P &message)
Collection of objects used to extend the Constraint Solver library.
int64_t time
Definition: resource.cc:1691
bool RefIsPositive(int ref)
IntVar * var
Definition: expr_array.cc:1874
void CompressTuples(absl::Span< const int64_t > domain_sizes, int64_t any_value, std::vector< std::vector< int64_t >> *tuples)
Definition: sat/util.cc:278
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:44
GurobiMPCallbackContext * context
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
int64_t value
Literal literal
Definition: optimization.cc:85
const Constraint * ct
int64_t a