OR-Tools  9.6
cp_model_expand.cc
Go to the documentation of this file.
1 // Copyright 2010-2022 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 <string>
20 #include <utility>
21 #include <vector>
22 
23 #include "absl/container/btree_map.h"
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/strings/str_cat.h"
27 #include "absl/types/span.h"
29 #include "ortools/base/logging.h"
30 #include "ortools/base/stl_util.h"
32 #include "ortools/sat/cp_model.pb.h"
35 #include "ortools/sat/sat_parameters.pb.h"
36 #include "ortools/sat/util.h"
37 #include "ortools/util/logging.h"
40 
41 namespace operations_research {
42 namespace sat {
43 
44 // TODO(user): Note that if we have duplicate variables controlling different
45 // time point, this might not reach the fixed point. Fix? it is not that
46 // important as the expansion take care of this case anyway.
47 void PropagateAutomaton(const AutomatonConstraintProto& proto,
48  const PresolveContext& context,
49  std::vector<absl::flat_hash_set<int64_t>>* states,
50  std::vector<absl::flat_hash_set<int64_t>>* labels) {
51  const int n = proto.vars_size();
52  const absl::flat_hash_set<int64_t> final_states(
53  {proto.final_states().begin(), proto.final_states().end()});
54 
55  labels->clear();
56  labels->resize(n);
57  states->clear();
58  states->resize(n + 1);
59  (*states)[0].insert(proto.starting_state());
60 
61  // Forward pass.
62  for (int time = 0; time < n; ++time) {
63  for (int t = 0; t < proto.transition_tail_size(); ++t) {
64  const int64_t tail = proto.transition_tail(t);
65  const int64_t label = proto.transition_label(t);
66  const int64_t head = proto.transition_head(t);
67  if (!(*states)[time].contains(tail)) continue;
68  if (!context.DomainContains(proto.vars(time), label)) continue;
69  if (time == n - 1 && !final_states.contains(head)) continue;
70  (*labels)[time].insert(label);
71  (*states)[time + 1].insert(head);
72  }
73  }
74 
75  // Backward pass.
76  for (int time = n - 1; time >= 0; --time) {
77  absl::flat_hash_set<int64_t> new_states;
78  absl::flat_hash_set<int64_t> new_labels;
79  for (int t = 0; t < proto.transition_tail_size(); ++t) {
80  const int64_t tail = proto.transition_tail(t);
81  const int64_t label = proto.transition_label(t);
82  const int64_t head = proto.transition_head(t);
83 
84  if (!(*states)[time].contains(tail)) continue;
85  if (!(*labels)[time].contains(label)) continue;
86  if (!(*states)[time + 1].contains(head)) continue;
87  new_labels.insert(label);
88  new_states.insert(tail);
89  }
90  (*labels)[time].swap(new_labels);
91  (*states)[time].swap(new_states);
92  }
93 }
94 
95 namespace {
96 
97 void ExpandReservoir(ConstraintProto* ct, PresolveContext* context) {
98  if (ct->reservoir().min_level() > ct->reservoir().max_level()) {
99  VLOG(1) << "Empty level domain in reservoir constraint.";
100  return (void)context->NotifyThatModelIsUnsat();
101  }
102 
103  const ReservoirConstraintProto& reservoir = ct->reservoir();
104  const int num_events = reservoir.time_exprs_size();
105  const int true_literal = context->GetTrueLiteral();
106  const auto is_active_literal = [&reservoir, true_literal](int index) {
107  if (reservoir.active_literals_size() == 0) return true_literal;
108  return reservoir.active_literals(index);
109  };
110 
111  int num_positives = 0;
112  int num_negatives = 0;
113  for (const LinearExpressionProto& demand_expr : reservoir.level_changes()) {
114  const int64_t demand = context->FixedValue(demand_expr);
115  if (demand > 0) {
116  num_positives++;
117  } else if (demand < 0) {
118  num_negatives++;
119  }
120  }
121 
122  absl::flat_hash_map<std::pair<int, int>, int> precedence_cache;
123 
124  if (num_positives > 0 && num_negatives > 0) {
125  // Creates Boolean variables equivalent to (start[i] <= start[j]) i != j
126  for (int i = 0; i < num_events - 1; ++i) {
127  const int active_i = is_active_literal(i);
128  if (context->LiteralIsFalse(active_i)) continue;
129  const LinearExpressionProto& time_i = reservoir.time_exprs(i);
130 
131  for (int j = i + 1; j < num_events; ++j) {
132  const int active_j = is_active_literal(j);
133  if (context->LiteralIsFalse(active_j)) continue;
134  const LinearExpressionProto& time_j = reservoir.time_exprs(j);
135 
136  const int i_lesseq_j = context->GetOrCreateReifiedPrecedenceLiteral(
137  time_i, time_j, active_i, active_j);
138  context->working_model->mutable_variables(i_lesseq_j)
139  ->set_name(absl::StrCat(i, " before ", j));
140  precedence_cache[{i, j}] = i_lesseq_j;
141  const int j_lesseq_i = context->GetOrCreateReifiedPrecedenceLiteral(
142  time_j, time_i, active_j, active_i);
143  context->working_model->mutable_variables(j_lesseq_i)
144  ->set_name(absl::StrCat(j, " before ", i));
145  precedence_cache[{j, i}] = j_lesseq_i;
146  }
147  }
148 
149  // Constrains the running level to be consistent at all time_exprs.
150  // For this we only add a constraint at the time a given demand
151  // take place. We also have a constraint for time zero if needed
152  // (added below).
153  for (int i = 0; i < num_events; ++i) {
154  const int active_i = is_active_literal(i);
155  if (context->LiteralIsFalse(active_i)) continue;
156 
157  // Accumulates level_changes of all predecessors.
158  ConstraintProto* const level = context->working_model->add_constraints();
159  level->add_enforcement_literal(active_i);
160 
161  // Add contributions from previous events.
162  int64_t offset = 0;
163  for (int j = 0; j < num_events; ++j) {
164  if (i == j) continue;
165  const int active_j = is_active_literal(j);
166  if (context->LiteralIsFalse(active_j)) continue;
167 
168  const auto prec_it = precedence_cache.find({j, i});
169  CHECK(prec_it != precedence_cache.end());
170  const int prec_lit = prec_it->second;
171  const int64_t demand = context->FixedValue(reservoir.level_changes(j));
172  if (RefIsPositive(prec_lit)) {
173  level->mutable_linear()->add_vars(prec_lit);
174  level->mutable_linear()->add_coeffs(demand);
175  } else {
176  level->mutable_linear()->add_vars(prec_lit);
177  level->mutable_linear()->add_coeffs(-demand);
178  offset -= demand;
179  }
180  }
181 
182  // Accounts for own demand in the domain of the sum.
183  const int64_t demand_i = context->FixedValue(reservoir.level_changes(i));
184  level->mutable_linear()->add_domain(
185  CapAdd(CapSub(reservoir.min_level(), demand_i), offset));
186  level->mutable_linear()->add_domain(
187  CapAdd(CapSub(reservoir.max_level(), demand_i), offset));
188  }
189  } else {
190  // If all level_changes have the same sign, we do not care about the order,
191  // just the sum.
192  auto* const sum =
193  context->working_model->add_constraints()->mutable_linear();
194  for (int i = 0; i < num_events; ++i) {
195  sum->add_vars(is_active_literal(i));
196  sum->add_coeffs(context->FixedValue(reservoir.level_changes(i)));
197  }
198  sum->add_domain(reservoir.min_level());
199  sum->add_domain(reservoir.max_level());
200  }
201 
202  ct->Clear();
203  context->UpdateRuleStats("reservoir: expanded");
204 }
205 
206 void ExpandIntMod(ConstraintProto* ct, PresolveContext* context) {
207  const LinearArgumentProto& int_mod = ct->int_mod();
208  const LinearExpressionProto& mod_expr = int_mod.exprs(1);
209  if (context->IsFixed(mod_expr)) return;
210 
211  const LinearExpressionProto& expr = int_mod.exprs(0);
212  const LinearExpressionProto& target_expr = int_mod.target();
213 
214  // We reduce the domain of target_expr to avoid later overflow.
215  if (!context->IntersectDomainWith(
216  target_expr, context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
217  context->DomainSuperSetOf(mod_expr)))) {
218  return;
219  }
220 
221  // Create a new constraint with the same enforcement as ct.
222  auto new_enforced_constraint = [&]() {
223  ConstraintProto* new_ct = context->working_model->add_constraints();
224  *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
225  return new_ct;
226  };
227 
228  // div_expr = expr / mod_expr.
229  const int div_var = context->NewIntVar(
230  context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
231  context->DomainSuperSetOf(mod_expr)));
232  LinearExpressionProto div_expr;
233  div_expr.add_vars(div_var);
234  div_expr.add_coeffs(1);
235 
236  LinearArgumentProto* const div_proto =
237  new_enforced_constraint()->mutable_int_div();
238  *div_proto->mutable_target() = div_expr;
239  *div_proto->add_exprs() = expr;
240  *div_proto->add_exprs() = mod_expr;
241 
242  // Create prod_expr = div_expr * mod_expr.
243  const Domain prod_domain =
244  context->DomainOf(div_var)
245  .ContinuousMultiplicationBy(context->DomainSuperSetOf(mod_expr))
246  .IntersectionWith(context->DomainSuperSetOf(expr).AdditionWith(
247  context->DomainSuperSetOf(target_expr).Negation()));
248  const int prod_var = context->NewIntVar(prod_domain);
249  LinearExpressionProto prod_expr;
250  prod_expr.add_vars(prod_var);
251  prod_expr.add_coeffs(1);
252 
253  LinearArgumentProto* const int_prod =
254  new_enforced_constraint()->mutable_int_prod();
255  *int_prod->mutable_target() = prod_expr;
256  *int_prod->add_exprs() = div_expr;
257  *int_prod->add_exprs() = mod_expr;
258 
259  // expr - prod_expr = target_expr.
260  LinearConstraintProto* const lin =
261  new_enforced_constraint()->mutable_linear();
262  lin->add_domain(0);
263  lin->add_domain(0);
265  AddLinearExpressionToLinearConstraint(prod_expr, -1, lin);
266  AddLinearExpressionToLinearConstraint(target_expr, -1, lin);
267 
268  ct->Clear();
269  context->UpdateRuleStats("int_mod: expanded");
270 }
271 
272 // TODO(user): Move this into the presolve instead?
273 void ExpandIntProdWithBoolean(int bool_ref,
274  const LinearExpressionProto& int_expr,
275  const LinearExpressionProto& product_expr,
276  PresolveContext* context) {
277  ConstraintProto* const one = context->working_model->add_constraints();
278  one->add_enforcement_literal(bool_ref);
279  one->mutable_linear()->add_domain(0);
280  one->mutable_linear()->add_domain(0);
281  AddLinearExpressionToLinearConstraint(int_expr, 1, one->mutable_linear());
282  AddLinearExpressionToLinearConstraint(product_expr, -1,
283  one->mutable_linear());
284 
285  ConstraintProto* const zero = context->working_model->add_constraints();
286  zero->add_enforcement_literal(NegatedRef(bool_ref));
287  zero->mutable_linear()->add_domain(0);
288  zero->mutable_linear()->add_domain(0);
290  zero->mutable_linear());
291 }
292 
293 void ExpandIntProd(ConstraintProto* ct, PresolveContext* context) {
294  const LinearArgumentProto& int_prod = ct->int_prod();
295  if (int_prod.exprs_size() != 2) return;
296  const LinearExpressionProto& a = int_prod.exprs(0);
297  const LinearExpressionProto& b = int_prod.exprs(1);
298  const LinearExpressionProto& p = int_prod.target();
299  int literal;
300  const bool a_is_literal = context->ExpressionIsALiteral(a, &literal);
301  const bool b_is_literal = context->ExpressionIsALiteral(b, &literal);
302 
303  // We expand if exactly one of {a, b} is a literal. If both are literals, it
304  // will be presolved into a better version.
305  if (a_is_literal && !b_is_literal) {
306  ExpandIntProdWithBoolean(literal, b, p, context);
307  ct->Clear();
308  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
309  } else if (b_is_literal) {
310  ExpandIntProdWithBoolean(literal, a, p, context);
311  ct->Clear();
312  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
313  }
314 }
315 
316 void ExpandInverse(ConstraintProto* ct, PresolveContext* context) {
317  const auto& f_direct = ct->inverse().f_direct();
318  const auto& f_inverse = ct->inverse().f_inverse();
319  const int n = f_direct.size();
320  CHECK_EQ(n, f_inverse.size());
321 
322  // Make sure the domains are included in [0, n - 1).
323  // Note that if a variable and its negation appear, the domains will be set to
324  // zero here.
325  //
326  // TODO(user): Add support for UNSAT at expansion. This should create empty
327  // domain if UNSAT, so it should still work correctly.
328  absl::flat_hash_set<int> used_variables;
329  for (const int ref : f_direct) {
330  used_variables.insert(PositiveRef(ref));
331  if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
332  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
333  return;
334  }
335  }
336  for (const int ref : f_inverse) {
337  used_variables.insert(PositiveRef(ref));
338  if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
339  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
340  return;
341  }
342  }
343 
344  // If we have duplicate variables, we make sure the domain are reduced
345  // as the loop below might not detect incompatibilities.
346  if (used_variables.size() != 2 * n) {
347  for (int i = 0; i < n; ++i) {
348  for (int j = 0; j < n; ++j) {
349  // Note that if we don't have the same sign, both domain are at zero.
350  if (PositiveRef(f_direct[i]) != PositiveRef(f_inverse[j])) continue;
351 
352  // We can't have i or j as value if i != j.
353  if (i == j) continue;
354  if (!context->IntersectDomainWith(
355  f_direct[i], Domain::FromValues({i, j}).Complement())) {
356  return;
357  }
358  }
359  }
360  }
361 
362  // Reduce the domains of each variable by checking that the inverse value
363  // exists.
364  std::vector<int64_t> possible_values;
365 
366  // Propagate from one vector to its counterpart.
367  const auto filter_inverse_domain =
368  [context, n, &possible_values](const auto& direct, const auto& inverse) {
369  // Propagate from the inverse vector to the direct vector.
370  for (int i = 0; i < n; ++i) {
371  possible_values.clear();
372  const Domain domain = context->DomainOf(direct[i]);
373  bool removed_value = false;
374  for (const int64_t j : domain.Values()) {
375  if (context->DomainOf(inverse[j]).Contains(i)) {
376  possible_values.push_back(j);
377  } else {
378  removed_value = true;
379  }
380  }
381  if (removed_value) {
382  if (!context->IntersectDomainWith(
383  direct[i], Domain::FromValues(possible_values))) {
384  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
385  return false;
386  }
387  }
388  }
389  return true;
390  };
391 
392  // Note that this should reach the fixed point in one pass.
393  // However, if we have duplicate variable, I am not sure.
394  if (!filter_inverse_domain(f_direct, f_inverse)) return;
395  if (!filter_inverse_domain(f_inverse, f_direct)) return;
396 
397  // Expand the inverse constraint by associating literal to var == value
398  // and sharing them between the direct and inverse variables.
399  //
400  // Note that this is only correct because the domain are tight now.
401  for (int i = 0; i < n; ++i) {
402  const int f_i = f_direct[i];
403  for (const int64_t j : context->DomainOf(f_i).Values()) {
404  // We have f[i] == j <=> r[j] == i;
405  const int r_j = f_inverse[j];
406  int r_j_i;
407  if (context->HasVarValueEncoding(r_j, i, &r_j_i)) {
408  context->InsertVarValueEncoding(r_j_i, f_i, j);
409  } else {
410  const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
411  context->InsertVarValueEncoding(f_i_j, r_j, i);
412  }
413  }
414  }
415 
416  ct->Clear();
417  context->UpdateRuleStats("inverse: expanded");
418 }
419 
420 // A[V] == V means for all i, V == i => A_i == i
421 void ExpandElementWithTargetEqualIndex(ConstraintProto* ct,
422  PresolveContext* context) {
423  const ElementConstraintProto& element = ct->element();
424  DCHECK_EQ(element.index(), element.target());
425 
426  const int index_ref = element.index();
427  std::vector<int64_t> valid_indices;
428  for (const int64_t v : context->DomainOf(index_ref).Values()) {
429  if (!context->DomainContains(element.vars(v), v)) continue;
430  valid_indices.push_back(v);
431  }
432  if (valid_indices.size() < context->DomainOf(index_ref).Size()) {
433  if (!context->IntersectDomainWith(index_ref,
434  Domain::FromValues(valid_indices))) {
435  VLOG(1) << "No compatible variable domains in "
436  "ExpandElementWithTargetEqualIndex()";
437  return;
438  }
439  context->UpdateRuleStats("element: reduced index domain");
440  }
441 
442  for (const int64_t v : context->DomainOf(index_ref).Values()) {
443  const int var = element.vars(v);
444  if (context->MinOf(var) == v && context->MaxOf(var) == v) continue;
445  context->AddImplyInDomain(
446  context->GetOrCreateVarValueEncoding(index_ref, v), var, Domain(v));
447  }
448  context->UpdateRuleStats(
449  "element: expanded with special case target = index");
450  ct->Clear();
451 }
452 
453 // Special case if the array of the element is filled with constant values.
454 void ExpandConstantArrayElement(ConstraintProto* ct, PresolveContext* context) {
455  const ElementConstraintProto& element = ct->element();
456  const int index_ref = element.index();
457  const int target_ref = element.target();
458 
459  // Index and target domain have been reduced before calling this function.
460  const Domain index_domain = context->DomainOf(index_ref);
461  const Domain target_domain = context->DomainOf(target_ref);
462 
463  // This BoolOrs implements the deduction that if all index literals pointing
464  // to the same value in the constant array are false, then this value is no
465  // no longer valid for the target variable. They are created only for values
466  // that have multiples literals supporting them.
467  // Order is not important.
468  absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
469  {
470  absl::flat_hash_map<int64_t, int> constant_var_values_usage;
471  for (const int64_t v : index_domain.Values()) {
472  DCHECK(context->IsFixed(element.vars(v)));
473  const int64_t value = context->MinOf(element.vars(v));
474  if (++constant_var_values_usage[value] == 2) {
475  // First time we cross > 1.
476  BoolArgumentProto* const support =
477  context->working_model->add_constraints()->mutable_bool_or();
478  const int target_literal =
479  context->GetOrCreateVarValueEncoding(target_ref, value);
480  support->add_literals(NegatedRef(target_literal));
481  supports[value] = support;
482  }
483  }
484  }
485 
486  {
487  // While this is not stricly needed since all value in the index will be
488  // covered, it allows to easily detect this fact in the presolve.
489  auto* exactly_one =
490  context->working_model->add_constraints()->mutable_exactly_one();
491  for (const int64_t v : index_domain.Values()) {
492  const int index_literal =
493  context->GetOrCreateVarValueEncoding(index_ref, v);
494  exactly_one->add_literals(index_literal);
495 
496  const int64_t value = context->MinOf(element.vars(v));
497  const auto& it = supports.find(value);
498  if (it != supports.end()) {
499  // The encoding literal for 'value' of the target_ref has been
500  // created before.
501  const int target_literal =
502  context->GetOrCreateVarValueEncoding(target_ref, value);
503  context->AddImplication(index_literal, target_literal);
504  it->second->add_literals(index_literal);
505  } else {
506  // Try to reuse the literal of the index.
507  context->InsertVarValueEncoding(index_literal, target_ref, value);
508  }
509  }
510  }
511 
512  context->UpdateRuleStats("element: expanded value element");
513  ct->Clear();
514 }
515 
516 // General element when the array contains non fixed variables.
517 void ExpandVariableElement(ConstraintProto* ct, PresolveContext* context) {
518  const ElementConstraintProto& element = ct->element();
519  const int index_ref = element.index();
520  const int target_ref = element.target();
521  const Domain index_domain = context->DomainOf(index_ref);
522 
523  BoolArgumentProto* exactly_one =
524  context->working_model->add_constraints()->mutable_exactly_one();
525 
526  for (const int64_t v : index_domain.Values()) {
527  const int var = element.vars(v);
528  const Domain var_domain = context->DomainOf(var);
529  const int index_lit = context->GetOrCreateVarValueEncoding(index_ref, v);
530  exactly_one->add_literals(index_lit);
531 
532  if (var_domain.IsFixed()) {
533  context->AddImplyInDomain(index_lit, target_ref, var_domain);
534  } else {
535  ConstraintProto* const ct = context->working_model->add_constraints();
536  ct->add_enforcement_literal(index_lit);
537  ct->mutable_linear()->add_vars(var);
538  ct->mutable_linear()->add_coeffs(1);
539  ct->mutable_linear()->add_vars(target_ref);
540  ct->mutable_linear()->add_coeffs(-1);
541  ct->mutable_linear()->add_domain(0);
542  ct->mutable_linear()->add_domain(0);
543  }
544  }
545 
546  context->UpdateRuleStats("element: expanded");
547  ct->Clear();
548 }
549 
550 void ExpandElement(ConstraintProto* ct, PresolveContext* context) {
551  const ElementConstraintProto& element = ct->element();
552 
553  const int index_ref = element.index();
554  const int target_ref = element.target();
555  const int size = element.vars_size();
556 
557  // Reduce the domain of the index to be compatible with the array of
558  // variables. Note that the element constraint is 0 based.
559  if (!context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
560  VLOG(1) << "Empty domain for the index variable in ExpandElement()";
561  return;
562  }
563 
564  // Special case when index = target.
565  if (index_ref == target_ref) {
566  ExpandElementWithTargetEqualIndex(ct, context);
567  return;
568  }
569 
570  // Reduces the domain of the index and the target.
571  bool all_constants = true;
572  std::vector<int64_t> valid_indices;
573  const Domain index_domain = context->DomainOf(index_ref);
574  const Domain target_domain = context->DomainOf(target_ref);
575  Domain reached_domain;
576  for (const int64_t v : index_domain.Values()) {
577  const Domain var_domain = context->DomainOf(element.vars(v));
578  if (var_domain.IntersectionWith(target_domain).IsEmpty()) continue;
579 
580  valid_indices.push_back(v);
581  reached_domain = reached_domain.UnionWith(var_domain);
582  if (var_domain.Min() != var_domain.Max()) {
583  all_constants = false;
584  }
585  }
586 
587  if (valid_indices.size() < index_domain.Size()) {
588  if (!context->IntersectDomainWith(index_ref,
589  Domain::FromValues(valid_indices))) {
590  VLOG(1) << "No compatible variable domains in ExpandElement()";
591  return;
592  }
593 
594  context->UpdateRuleStats("element: reduced index domain");
595  }
596 
597  // We know the target_domain is not empty as this would have triggered the
598  // above check.
599  bool target_domain_changed = false;
600  if (!context->IntersectDomainWith(target_ref, reached_domain,
601  &target_domain_changed)) {
602  return;
603  }
604 
605  if (target_domain_changed) {
606  context->UpdateRuleStats("element: reduced target domain");
607  }
608 
609  if (all_constants) {
610  ExpandConstantArrayElement(ct, context);
611  return;
612  }
613 
614  ExpandVariableElement(ct, context);
615 }
616 
617 // Adds clauses so that literals[i] true <=> encoding[values[i]] true.
618 // This also implicitly use the fact that exactly one alternative is true.
619 void LinkLiteralsAndValues(const std::vector<int>& literals,
620  const std::vector<int64_t>& values,
621  const absl::flat_hash_map<int64_t, int>& encoding,
622  PresolveContext* context) {
623  CHECK_EQ(literals.size(), values.size());
624 
625  // We use a map to make this method deterministic.
626  //
627  // TODO(user): Make sure this does not appear in the profile.
628  absl::btree_map<int, std::vector<int>> encoding_lit_to_support;
629 
630  // If a value is false (i.e not possible), then the tuple with this
631  // value is false too (i.e not possible). Conversely, if the tuple is
632  // selected, the value must be selected.
633  for (int i = 0; i < values.size(); ++i) {
634  encoding_lit_to_support[encoding.at(values[i])].push_back(literals[i]);
635  }
636 
637  // If all tuples supporting a value are false, then this value must be
638  // false.
639  for (const auto& [encoding_lit, support] : encoding_lit_to_support) {
640  CHECK(!support.empty());
641  if (support.size() == 1) {
642  context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
643  } else {
644  BoolArgumentProto* bool_or =
645  context->working_model->add_constraints()->mutable_bool_or();
646  bool_or->add_literals(NegatedRef(encoding_lit));
647  for (const int lit : support) {
648  bool_or->add_literals(lit);
649  context->AddImplication(lit, encoding_lit);
650  }
651  }
652  }
653 }
654 
655 // Add the constraint literal => one_of(encoding[v]), for v in reachable_values.
656 // Note that all possible values are the ones appearing in encoding.
657 void AddImplyInReachableValues(int literal,
658  std::vector<int64_t>& reachable_values,
659  const absl::flat_hash_map<int64_t, int> encoding,
660  PresolveContext* context) {
661  gtl::STLSortAndRemoveDuplicates(&reachable_values);
662  if (reachable_values.size() == encoding.size()) return; // No constraint.
663  if (reachable_values.size() <= encoding.size() / 2) {
664  // Bool or encoding.
665  ConstraintProto* ct = context->working_model->add_constraints();
666  ct->add_enforcement_literal(literal);
667  BoolArgumentProto* bool_or = ct->mutable_bool_or();
668  for (const int64_t v : reachable_values) {
669  bool_or->add_literals(encoding.at(v));
670  }
671  } else {
672  // Bool and encoding.
673  absl::flat_hash_set<int64_t> set(reachable_values.begin(),
674  reachable_values.end());
675  ConstraintProto* ct = context->working_model->add_constraints();
676  ct->add_enforcement_literal(literal);
677  BoolArgumentProto* bool_and = ct->mutable_bool_and();
678  for (const auto [value, literal] : encoding) {
679  if (!set.contains(value)) {
680  bool_and->add_literals(NegatedRef(literal));
681  }
682  }
683  }
684 }
685 
686 void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
687  AutomatonConstraintProto& proto = *ct->mutable_automaton();
688 
689  if (proto.vars_size() == 0) {
690  const int64_t initial_state = proto.starting_state();
691  for (const int64_t final_state : proto.final_states()) {
692  if (initial_state == final_state) {
693  context->UpdateRuleStats("automaton: empty and trivially feasible");
694  ct->Clear();
695  return;
696  }
697  }
698  return (void)context->NotifyThatModelIsUnsat(
699  "automaton: empty with an initial state not in the final states.");
700  } else if (proto.transition_label_size() == 0) {
701  return (void)context->NotifyThatModelIsUnsat(
702  "automaton: non-empty with no transition.");
703  }
704 
705  std::vector<absl::flat_hash_set<int64_t>> reachable_states;
706  std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
707  PropagateAutomaton(proto, *context, &reachable_states, &reachable_labels);
708 
709  // We will model at each time step the current automaton state using Boolean
710  // variables. We will have n+1 time step. At time zero, we start in the
711  // initial state, and at time n we should be in one of the final states. We
712  // don't need to create Booleans at at time when there is just one possible
713  // state (like at time zero).
714  absl::flat_hash_map<int64_t, int> encoding;
715  absl::flat_hash_map<int64_t, int> in_encoding;
716  absl::flat_hash_map<int64_t, int> out_encoding;
717  bool removed_values = false;
718 
719  const int n = proto.vars_size();
720  const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
721  for (int time = 0; time < n; ++time) {
722  // All these vector have the same size. We will use them to enforce a
723  // local table constraint representing one step of the automaton at the
724  // given time.
725  std::vector<int64_t> in_states;
726  std::vector<int64_t> labels;
727  std::vector<int64_t> out_states;
728  for (int i = 0; i < proto.transition_label_size(); ++i) {
729  const int64_t tail = proto.transition_tail(i);
730  const int64_t label = proto.transition_label(i);
731  const int64_t head = proto.transition_head(i);
732 
733  if (!reachable_states[time].contains(tail)) continue;
734  if (!reachable_states[time + 1].contains(head)) continue;
735  if (!context->DomainContains(vars[time], label)) continue;
736 
737  // TODO(user): if this transition correspond to just one in-state or
738  // one-out state or one variable value, we could reuse the corresponding
739  // Boolean variable instead of creating a new one!
740  in_states.push_back(tail);
741  labels.push_back(label);
742 
743  // On the last step we don't need to distinguish the output states, so
744  // we use zero.
745  out_states.push_back(time + 1 == n ? 0 : head);
746  }
747 
748  // Deal with single tuple.
749  const int num_tuples = in_states.size();
750  if (num_tuples == 1) {
751  if (!context->IntersectDomainWith(vars[time], Domain(labels.front()))) {
752  VLOG(1) << "Infeasible automaton.";
753  return;
754  }
755 
756  // Tricky: when the same variable is used more than once, the propagation
757  // above might not reach the fixed point, so we do need to fix literal
758  // at false.
759  std::vector<int> at_false;
760  for (const auto [value, literal] : in_encoding) {
761  if (value != in_states[0]) at_false.push_back(literal);
762  }
763  for (const int literal : at_false) {
764  if (!context->SetLiteralToFalse(literal)) return;
765  }
766 
767  in_encoding.clear();
768  continue;
769  }
770 
771  // Fully encode vars[time].
772  {
773  std::vector<int64_t> transitions = labels;
774  gtl::STLSortAndRemoveDuplicates(&transitions);
775 
776  encoding.clear();
777  if (!context->IntersectDomainWith(
778  vars[time], Domain::FromValues(transitions), &removed_values)) {
779  VLOG(1) << "Infeasible automaton.";
780  return;
781  }
782 
783  // Fully encode the variable.
784  // We can leave the encoding empty for fixed vars.
785  if (!context->IsFixed(vars[time])) {
786  for (const int64_t v : context->DomainOf(vars[time]).Values()) {
787  encoding[v] = context->GetOrCreateVarValueEncoding(vars[time], v);
788  }
789  }
790  }
791 
792  // Count how many time each value appear.
793  // We use this to reuse literals if possible.
794  absl::flat_hash_map<int64_t, int> in_count;
795  absl::flat_hash_map<int64_t, int> transition_count;
796  absl::flat_hash_map<int64_t, int> out_count;
797  for (int i = 0; i < num_tuples; ++i) {
798  in_count[in_states[i]]++;
799  transition_count[labels[i]]++;
800  out_count[out_states[i]]++;
801  }
802 
803  // For each possible out states, create one Boolean variable.
804  //
805  // TODO(user): Add exactly one?
806  {
807  std::vector<int64_t> states = out_states;
809 
810  out_encoding.clear();
811  if (states.size() == 2) {
812  const int var = context->NewBoolVar();
813  out_encoding[states[0]] = var;
814  out_encoding[states[1]] = NegatedRef(var);
815  } else if (states.size() > 2) {
816  struct UniqueDetector {
817  void Set(int64_t v) {
818  if (!is_unique) return;
819  if (is_set) {
820  if (v != value) is_unique = false;
821  } else {
822  is_set = true;
823  value = v;
824  }
825  }
826  bool is_set = false;
827  bool is_unique = true;
828  int64_t value = 0;
829  };
830 
831  // Optimization to detect if we have an in state that is only matched to
832  // a single out state. Same with transition.
833  absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
834  absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
835  for (int i = 0; i < num_tuples; ++i) {
836  out_to_in[out_states[i]].Set(in_states[i]);
837  out_to_transition[out_states[i]].Set(labels[i]);
838  }
839 
840  for (const int64_t state : states) {
841  // If we have a relation in_state <=> out_state, then we can reuse
842  // the in Boolean and do not need to create a new one.
843  if (!in_encoding.empty() && out_to_in[state].is_unique) {
844  const int64_t unique_in = out_to_in[state].value;
845  if (in_count[unique_in] == out_count[state]) {
846  out_encoding[state] = in_encoding[unique_in];
847  continue;
848  }
849  }
850 
851  // Same if we have an unique transition value that correspond only to
852  // this state.
853  if (!encoding.empty() && out_to_transition[state].is_unique) {
854  const int64_t unique_transition = out_to_transition[state].value;
855  if (transition_count[unique_transition] == out_count[state]) {
856  out_encoding[state] = encoding[unique_transition];
857  continue;
858  }
859  }
860 
861  out_encoding[state] = context->NewBoolVar();
862  }
863  }
864  }
865 
866  // Simple encoding. This is enough to properly enforce the constraint, but
867  // it propagate less. It creates a lot less Booleans though. Note that we
868  // use implicit "exactly one" on the encoding and do not add any extra
869  // exactly one if the simple encoding is used.
870  //
871  // We currently decide which encoding to use depending on the number of new
872  // literals needed by the "heavy" encoding compared to the number of states
873  // and labels. When the automaton is small, using the full encoding is
874  // better, see for instance on rotating-workforce_Example789 were the simple
875  // encoding make the problem hard to solve but the full encoding allow the
876  // solver to solve it in a couple of seconds!
877  //
878  // Note that both encoding create about the same number of constraints.
879  const int num_involved_variables =
880  in_encoding.size() + encoding.size() + out_encoding.size();
881  const bool use_light_encoding = (num_tuples > num_involved_variables);
882  if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
883  !out_encoding.empty()) {
884  // Part 1: If a in_state is selected, restrict the set of possible labels.
885  // We also restrict the set of possible out states, but this is not needed
886  // for correctness.
887  absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
888  absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
889  for (int i = 0; i < num_tuples; ++i) {
890  in_to_label[in_states[i]].push_back(labels[i]);
891  in_to_out[in_states[i]].push_back(out_states[i]);
892  }
893  for (const auto [in_value, in_literal] : in_encoding) {
894  AddImplyInReachableValues(in_literal, in_to_label[in_value], encoding,
895  context);
896  AddImplyInReachableValues(in_literal, in_to_out[in_value], out_encoding,
897  context);
898  }
899 
900  // Part2, add all 3-clauses: (in_state, label) => out_state.
901  for (int i = 0; i < num_tuples; ++i) {
902  auto* bool_or =
903  context->working_model->add_constraints()->mutable_bool_or();
904  bool_or->add_literals(NegatedRef(in_encoding.at(in_states[i])));
905  bool_or->add_literals(NegatedRef(encoding.at(labels[i])));
906  bool_or->add_literals(out_encoding.at(out_states[i]));
907  }
908 
909  in_encoding.swap(out_encoding);
910  out_encoding.clear();
911  continue;
912  }
913 
914  // Create the tuple literals.
915  //
916  // TODO(user): Call and use the same heuristics as the table constraint to
917  // expand this small table with 3 columns (i.e. compress, negate, etc...).
918  std::vector<int> tuple_literals;
919  if (num_tuples == 2) {
920  const int bool_var = context->NewBoolVar();
921  tuple_literals.push_back(bool_var);
922  tuple_literals.push_back(NegatedRef(bool_var));
923  } else {
924  // Note that we do not need the ExactlyOneConstraint(tuple_literals)
925  // because it is already implicitly encoded since we have exactly one
926  // transition value. But adding one seems to help.
927  BoolArgumentProto* exactly_one =
928  context->working_model->add_constraints()->mutable_exactly_one();
929  for (int i = 0; i < num_tuples; ++i) {
930  int tuple_literal;
931  if (in_count[in_states[i]] == 1 && !in_encoding.empty()) {
932  tuple_literal = in_encoding[in_states[i]];
933  } else if (transition_count[labels[i]] == 1 && !encoding.empty()) {
934  tuple_literal = encoding[labels[i]];
935  } else if (out_count[out_states[i]] == 1 && !out_encoding.empty()) {
936  tuple_literal = out_encoding[out_states[i]];
937  } else {
938  tuple_literal = context->NewBoolVar();
939  }
940 
941  tuple_literals.push_back(tuple_literal);
942  exactly_one->add_literals(tuple_literal);
943  }
944  }
945 
946  if (!in_encoding.empty()) {
947  LinkLiteralsAndValues(tuple_literals, in_states, in_encoding, context);
948  }
949  if (!encoding.empty()) {
950  LinkLiteralsAndValues(tuple_literals, labels, encoding, context);
951  }
952  if (!out_encoding.empty()) {
953  LinkLiteralsAndValues(tuple_literals, out_states, out_encoding, context);
954  }
955 
956  in_encoding.swap(out_encoding);
957  out_encoding.clear();
958  }
959 
960  if (removed_values) {
961  context->UpdateRuleStats("automaton: reduced variable domains");
962  }
963  context->UpdateRuleStats("automaton: expanded");
964  ct->Clear();
965 }
966 
967 void ExpandNegativeTable(ConstraintProto* ct, PresolveContext* context) {
968  TableConstraintProto& table = *ct->mutable_table();
969  const int num_vars = table.vars_size();
970  const int num_original_tuples = table.values_size() / num_vars;
971  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
972  int count = 0;
973  for (int i = 0; i < num_original_tuples; ++i) {
974  for (int j = 0; j < num_vars; ++j) {
975  tuples[i].push_back(table.values(count++));
976  }
977  }
978 
979  if (tuples.empty()) { // Early exit.
980  context->UpdateRuleStats("table: empty negated constraint");
981  ct->Clear();
982  return;
983  }
984 
985  // Compress tuples.
986  std::vector<int64_t> domain_sizes;
987  for (int i = 0; i < num_vars; ++i) {
988  domain_sizes.push_back(context->DomainOf(table.vars(i)).Size());
989  }
990  CompressTuples(domain_sizes, &tuples);
991 
992  // For each tuple, forbid the variables values to be this tuple.
993  std::vector<int> clause;
994  for (const std::vector<int64_t>& tuple : tuples) {
995  clause.clear();
996  for (int i = 0; i < num_vars; ++i) {
997  const int64_t value = tuple[i];
998  if (value == kTableAnyValue) continue;
999 
1000  const int literal =
1001  context->GetOrCreateVarValueEncoding(table.vars(i), value);
1002  clause.push_back(NegatedRef(literal));
1003  }
1004 
1005  // Note: if the clause is empty, then the model is infeasible.
1006  BoolArgumentProto* bool_or =
1007  context->working_model->add_constraints()->mutable_bool_or();
1008  for (const int lit : clause) {
1009  bool_or->add_literals(lit);
1010  }
1011  }
1012  context->UpdateRuleStats("table: expanded negated constraint");
1013  ct->Clear();
1014 }
1015 
1016 // Add the implications and clauses to link one variable (i.e. column) of a
1017 // table to the literals controlling if the tuples are possible or not.
1018 //
1019 // We list of each tuple the possible values the variable can take.
1020 // If the list is empty, then this encode "any value".
1021 void ProcessOneCompressedColumn(
1022  int variable, const std::vector<int>& tuple_literals,
1023  const std::vector<absl::InlinedVector<int64_t, 2>>& values,
1024  PresolveContext* context) {
1025  DCHECK_EQ(tuple_literals.size(), values.size());
1026 
1027  // Collect pairs of value-literal.
1028  // Add the constraint literal => one of values.
1029  //
1030  // TODO(user): If we have n - 1 values, we could add the constraint that
1031  // tuple literal => not(last_value) instead?
1032  std::vector<std::pair<int64_t, int>> pairs;
1033  std::vector<int> any_values_literals;
1034  for (int i = 0; i < values.size(); ++i) {
1035  if (values[i].empty()) {
1036  any_values_literals.push_back(tuple_literals[i]);
1037  continue;
1038  }
1039  ConstraintProto* clause = context->working_model->add_constraints();
1040  clause->add_enforcement_literal(tuple_literals[i]);
1041  for (const int64_t v : values[i]) {
1042  DCHECK(context->DomainContains(variable, v));
1043  clause->mutable_bool_or()->add_literals(
1044  context->GetOrCreateVarValueEncoding(variable, v));
1045  pairs.emplace_back(v, tuple_literals[i]);
1046  }
1047  }
1048 
1049  // Regroup literal with the same value and add for each the clause: If all the
1050  // tuples containing a value are false, then this value must be false too.
1051  std::vector<int> selected;
1052  std::sort(pairs.begin(), pairs.end());
1053  for (int i = 0; i < pairs.size();) {
1054  selected.clear();
1055  const int64_t value = pairs[i].first;
1056  for (; i < pairs.size() && pairs[i].first == value; ++i) {
1057  selected.push_back(pairs[i].second);
1058  }
1059 
1060  BoolArgumentProto* no_support =
1061  context->working_model->add_constraints()->mutable_bool_or();
1062  for (const int lit : selected) {
1063  no_support->add_literals(lit);
1064  }
1065  for (const int lit : any_values_literals) {
1066  no_support->add_literals(lit);
1067  }
1068 
1069  // And the "value" literal.
1070  const int value_literal =
1071  context->GetOrCreateVarValueEncoding(variable, value);
1072  no_support->add_literals(NegatedRef(value_literal));
1073  }
1074 }
1075 
1076 // Simpler encoding for table constraints with 2 variables.
1077 void AddSizeTwoTable(
1078  const std::vector<int>& vars,
1079  const std::vector<std::vector<int64_t>>& tuples,
1080  const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1081  PresolveContext* context) {
1082  CHECK_EQ(vars.size(), 2);
1083  const int left_var = vars[0];
1084  const int right_var = vars[1];
1085  if (context->DomainOf(left_var).IsFixed() ||
1086  context->DomainOf(right_var).IsFixed()) {
1087  // A table constraint with at most one variable not fixed is trivially
1088  // enforced after domain reduction.
1089  return;
1090  }
1091 
1092  absl::btree_map<int, std::vector<int>> left_to_right;
1093  absl::btree_map<int, std::vector<int>> right_to_left;
1094 
1095  for (const auto& tuple : tuples) {
1096  const int64_t left_value(tuple[0]);
1097  const int64_t right_value(tuple[1]);
1098  DCHECK(context->DomainContains(left_var, left_value));
1099  DCHECK(context->DomainContains(right_var, right_value));
1100 
1101  const int left_literal =
1102  context->GetOrCreateVarValueEncoding(left_var, left_value);
1103  const int right_literal =
1104  context->GetOrCreateVarValueEncoding(right_var, right_value);
1105  left_to_right[left_literal].push_back(right_literal);
1106  right_to_left[right_literal].push_back(left_literal);
1107  }
1108 
1109  int num_implications = 0;
1110  int num_clause_added = 0;
1111  int num_large_clause_added = 0;
1112  auto add_support_constraint =
1113  [context, &num_clause_added, &num_large_clause_added, &num_implications](
1114  int lit, const std::vector<int>& support_literals,
1115  int max_support_size) {
1116  if (support_literals.size() == max_support_size) return;
1117  if (support_literals.size() == 1) {
1118  context->AddImplication(lit, support_literals.front());
1119  num_implications++;
1120  } else {
1121  BoolArgumentProto* bool_or =
1122  context->working_model->add_constraints()->mutable_bool_or();
1123  for (const int support_literal : support_literals) {
1124  bool_or->add_literals(support_literal);
1125  }
1126  bool_or->add_literals(NegatedRef(lit));
1127  num_clause_added++;
1128  if (support_literals.size() > max_support_size / 2) {
1129  num_large_clause_added++;
1130  }
1131  }
1132  };
1133 
1134  for (const auto& it : left_to_right) {
1135  add_support_constraint(it.first, it.second, values_per_var[1].size());
1136  }
1137  for (const auto& it : right_to_left) {
1138  add_support_constraint(it.first, it.second, values_per_var[0].size());
1139  }
1140  VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
1141  << num_clause_added << " clauses, including "
1142  << num_large_clause_added << " large clauses, " << num_implications
1143  << " implications";
1144 }
1145 
1146 // A "WCSP" (weighted constraint programming) problem is usually encoded as
1147 // a set of table, with one or more variable only there to carry a cost.
1148 //
1149 // If this is the case, we can do special presolving.
1150 bool ReduceTableInPresenceOfUniqueVariableWithCosts(
1151  std::vector<int>* vars, std::vector<std::vector<int64_t>>* tuples,
1152  PresolveContext* context) {
1153  const int num_vars = vars->size();
1154 
1155  std::vector<bool> only_here_and_in_objective(num_vars, false);
1156  std::vector<int64_t> objective_coeffs(num_vars, 0.0);
1157  std::vector<int> new_vars;
1158  std::vector<int> deleted_vars;
1159  for (int var_index = 0; var_index < num_vars; ++var_index) {
1160  const int var = (*vars)[var_index];
1161  // We do not use VariableWithCostIsUniqueAndRemovable() since this one
1162  // return false if the objective is constraining but we don't care here.
1163  if (context->VariableWithCostIsUniqueAndRemovable(var)) {
1164  context->UpdateRuleStats("table: removed unused column with cost");
1165  only_here_and_in_objective[var_index] = true;
1166  objective_coeffs[var_index] =
1167  RefIsPositive(var) ? context->ObjectiveMap().at(var)
1168  : -context->ObjectiveMap().at(PositiveRef(var));
1169  context->RemoveVariableFromObjective(var);
1170  context->MarkVariableAsRemoved(var);
1171  deleted_vars.push_back(var);
1172  } else if (context->VariableIsUniqueAndRemovable(var)) {
1173  // If there is no cost, we can remove that variable using the same code by
1174  // just setting the cost to zero.
1175  context->UpdateRuleStats("table: removed unused column");
1176  only_here_and_in_objective[var_index] = true;
1177  objective_coeffs[var_index] = 0;
1178  context->MarkVariableAsRemoved(var);
1179  deleted_vars.push_back(var);
1180  } else {
1181  new_vars.push_back(var);
1182  }
1183  }
1184  if (new_vars.size() == num_vars) return false;
1185 
1186  // Rewrite the tuples.
1187  // put the cost last.
1188  int64_t min_cost = std::numeric_limits<int64_t>::max();
1189  std::vector<int64_t> temp;
1190  for (int i = 0; i < tuples->size(); ++i) {
1191  int64_t cost = 0;
1192  int new_size = 0;
1193  temp.clear();
1194  for (int var_index = 0; var_index < num_vars; ++var_index) {
1195  const int64_t value = (*tuples)[i][var_index];
1196  if (only_here_and_in_objective[var_index]) {
1197  temp.push_back(value);
1198  const int64_t objective_coeff = objective_coeffs[var_index];
1199  cost += value * objective_coeff;
1200  } else {
1201  (*tuples)[i][new_size++] = value;
1202  }
1203  }
1204  (*tuples)[i].resize(new_size);
1205  (*tuples)[i].push_back(cost);
1206  min_cost = std::min(min_cost, cost);
1207 
1208  // Hack: we store the deleted value here so that we can properly encode
1209  // the postsolve constraints below.
1210  (*tuples)[i].insert((*tuples)[i].end(), temp.begin(), temp.end());
1211  }
1212 
1213  // Remove tuples that only differ by their cost.
1214  // Make sure we will assign the proper value of the removed variable at
1215  // postsolve.
1216  {
1217  int new_size = 0;
1218  const int old_size = tuples->size();
1219  std::sort(tuples->begin(), tuples->end());
1220  for (int i = 0; i < tuples->size(); ++i) {
1221  // If the prefix (up to new_vars.size()) is the same, skip this tuple.
1222  if (new_size > 0) {
1223  bool skip = true;
1224  for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1225  if ((*tuples)[i][var_index] != (*tuples)[new_size - 1][var_index]) {
1226  skip = false;
1227  break;
1228  }
1229  }
1230  if (skip) continue;
1231  }
1232 
1233  // If this tuple is selected, then fix the removed variable value in the
1234  // mapping model.
1235  for (int j = 0; j < deleted_vars.size(); ++j) {
1236  ConstraintProto* new_ct = context->mapping_model->add_constraints();
1237  for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1238  new_ct->add_enforcement_literal(context->GetOrCreateVarValueEncoding(
1239  new_vars[var_index], (*tuples)[i][var_index]));
1240  }
1241  new_ct->mutable_linear()->add_vars(deleted_vars[j]);
1242  new_ct->mutable_linear()->add_coeffs(1);
1243  new_ct->mutable_linear()->add_domain(
1244  (*tuples)[i][new_vars.size() + 1 + j]);
1245  new_ct->mutable_linear()->add_domain(
1246  (*tuples)[i][new_vars.size() + 1 + j]);
1247  }
1248  (*tuples)[i].resize(new_vars.size() + 1);
1249  (*tuples)[new_size++] = (*tuples)[i];
1250  }
1251  tuples->resize(new_size);
1252  if (new_size < old_size) {
1253  context->UpdateRuleStats(
1254  "table: removed duplicate tuples with different costs");
1255  }
1256  }
1257 
1258  if (min_cost > 0) {
1259  context->AddToObjectiveOffset(min_cost);
1260  context->UpdateRuleStats("table: transferred min_cost to objective offset");
1261  for (int i = 0; i < tuples->size(); ++i) {
1262  (*tuples)[i].back() -= min_cost;
1263  }
1264  }
1265 
1266  // This comes from the WCSP litterature. Basically, if by fixing a variable to
1267  // a value, we have only tuples with a non-zero cost, we can substract the
1268  // minimum cost of these tuples and transfer it to the variable cost.
1269  for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1270  absl::flat_hash_map<int64_t, int64_t> value_to_min_cost;
1271  const int num_tuples = tuples->size();
1272  for (int i = 0; i < num_tuples; ++i) {
1273  const int64_t v = (*tuples)[i][var_index];
1274  const int64_t cost = (*tuples)[i].back();
1275  auto insert = value_to_min_cost.insert({v, cost});
1276  if (!insert.second) {
1277  insert.first->second = std::min(insert.first->second, cost);
1278  }
1279  }
1280  for (int i = 0; i < num_tuples; ++i) {
1281  const int64_t v = (*tuples)[i][var_index];
1282  (*tuples)[i].back() -= value_to_min_cost.at(v);
1283  }
1284  for (const auto entry : value_to_min_cost) {
1285  if (entry.second == 0) continue;
1286  context->UpdateRuleStats("table: transferred cost to encoding");
1287  const int value_literal = context->GetOrCreateVarValueEncoding(
1288  new_vars[var_index], entry.first);
1289  context->AddLiteralToObjective(value_literal, entry.second);
1290  }
1291  }
1292 
1293  context->UpdateRuleStats(absl::StrCat(
1294  "table: expansion with column(s) only in objective. Arity = ",
1295  new_vars.size()));
1296 
1297  *vars = new_vars;
1298  return true;
1299 }
1300 
1301 // Important: the table and variable domains must be presolved before this
1302 // is called. Some checks will fail otherwise.
1303 void CompressAndExpandPositiveTable(bool last_column_is_cost,
1304  const std::vector<int>& vars,
1305  std::vector<std::vector<int64_t>>* tuples,
1306  PresolveContext* context) {
1307  const int num_tuples_before_compression = tuples->size();
1308 
1309  // If the last column is actually the tuple cost, we compress the table like
1310  // if this was a normal variable, but afterwards we treat it differently.
1311  std::vector<int64_t> domain_sizes;
1312  for (const int var : vars) {
1313  domain_sizes.push_back(context->DomainOf(var).Size());
1314  }
1315  if (last_column_is_cost) {
1316  domain_sizes.push_back(std::numeric_limits<int64_t>::max());
1317  }
1318 
1319  // We start by compressing the table with kTableAnyValue only.
1320  const int compression_level = context->params().table_compression_level();
1321  if (compression_level > 0) {
1322  CompressTuples(domain_sizes, tuples);
1323  }
1324  const int num_tuples_after_first_compression = tuples->size();
1325 
1326  // Tricky: If the table is big, it is better to compress it as much as
1327  // possible to reduce the number of created booleans. Otherwise, the more
1328  // verbose encoding can lead to better linear relaxation. Probably because the
1329  // tuple literal can encode each variable as sum literal * value. Also because
1330  // we have more direct implied bounds, which might lead to better cuts.
1331  //
1332  // For instance, on lot_sizing_cp_pigment15c.psp, compressing the table more
1333  // is a lot worse (at least until we can produce better cut).
1334  //
1335  // TODO(user): Tweak the heuristic, maybe compute the reduction achieve and
1336  // decide based on that.
1337  std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> compressed_table;
1338  if (compression_level > 2 ||
1339  (compression_level == 2 && num_tuples_after_first_compression > 1000)) {
1340  compressed_table = FullyCompressTuples(domain_sizes, tuples);
1341  if (compressed_table.size() < num_tuples_before_compression) {
1342  context->UpdateRuleStats("table: fully compress tuples");
1343  }
1344  } else {
1345  // Convert the kTableAnyValue to an empty list format.
1346  for (int i = 0; i < tuples->size(); ++i) {
1347  compressed_table.push_back({});
1348  for (const int64_t v : (*tuples)[i]) {
1349  if (v == kTableAnyValue) {
1350  compressed_table.back().push_back({});
1351  } else {
1352  compressed_table.back().push_back({v});
1353  }
1354  }
1355  }
1356  if (compressed_table.size() < num_tuples_before_compression) {
1357  context->UpdateRuleStats("table: compress tuples");
1358  }
1359  }
1360 
1361  VLOG(2) << "Table compression"
1362  << " var=" << vars.size()
1363  << " cost=" << domain_sizes.size() - vars.size()
1364  << " tuples= " << num_tuples_before_compression << " -> "
1365  << num_tuples_after_first_compression << " -> "
1366  << compressed_table.size();
1367 
1368  // Affect mznc2017_aes_opt_r10 instance!
1369  std::sort(compressed_table.begin(), compressed_table.end());
1370 
1371  const int num_vars = vars.size();
1372  if (compressed_table.size() == 1) {
1373  // Domains are propagated. We can remove the constraint.
1374  context->UpdateRuleStats("table: one tuple");
1375  if (last_column_is_cost) {
1376  // TODO(user): Because we transfer the cost, this should always be zero,
1377  // so not needed.
1378  context->AddToObjectiveOffset(compressed_table[0].back()[0]);
1379  }
1380  return;
1381  }
1382 
1383  // Optimization. If a value is unique and appear alone in a cell, we can use
1384  // the encoding literal for this line tuple literal instead of creating a new
1385  // one.
1386  std::vector<bool> has_any(num_vars, false);
1387  std::vector<absl::flat_hash_map<int64_t, int>> var_index_to_value_count(
1388  num_vars);
1389  for (int i = 0; i < compressed_table.size(); ++i) {
1390  for (int var_index = 0; var_index < num_vars; ++var_index) {
1391  if (compressed_table[i][var_index].empty()) {
1392  has_any[var_index] = true;
1393  continue;
1394  }
1395  for (const int64_t v : compressed_table[i][var_index]) {
1396  DCHECK_NE(v, kTableAnyValue);
1397  DCHECK(context->DomainContains(vars[var_index], v));
1398  var_index_to_value_count[var_index][v]++;
1399  }
1400  }
1401  }
1402 
1403  // Create one Boolean variable per tuple to indicate if it can still be
1404  // selected or not. Enforce an exactly one between them.
1405  BoolArgumentProto* exactly_one =
1406  context->working_model->add_constraints()->mutable_exactly_one();
1407 
1408  int64_t num_reused_variables = 0;
1409  std::vector<int> tuple_literals(compressed_table.size());
1410  for (int i = 0; i < compressed_table.size(); ++i) {
1411  bool create_new_var = true;
1412  for (int var_index = 0; var_index < num_vars; ++var_index) {
1413  if (has_any[var_index]) continue;
1414  if (compressed_table[i][var_index].size() != 1) continue;
1415  const int64_t v = compressed_table[i][var_index][0];
1416  if (var_index_to_value_count[var_index][v] != 1) continue;
1417 
1418  ++num_reused_variables;
1419  create_new_var = false;
1420  tuple_literals[i] =
1421  context->GetOrCreateVarValueEncoding(vars[var_index], v);
1422  break;
1423  }
1424  if (create_new_var) {
1425  tuple_literals[i] = context->NewBoolVar();
1426  }
1427  exactly_one->add_literals(tuple_literals[i]);
1428  }
1429  if (num_reused_variables > 0) {
1430  context->UpdateRuleStats("table: reused literals");
1431  }
1432 
1433  // Set the cost to the corresponding tuple literal. If there is more than one
1434  // cost, we just choose the first one which is the smallest one.
1435  if (last_column_is_cost) {
1436  for (int i = 0; i < tuple_literals.size(); ++i) {
1437  context->AddLiteralToObjective(tuple_literals[i],
1438  compressed_table[i].back()[0]);
1439  }
1440  }
1441 
1442  std::vector<absl::InlinedVector<int64_t, 2>> column;
1443  for (int var_index = 0; var_index < num_vars; ++var_index) {
1444  if (context->IsFixed(vars[var_index])) continue;
1445 
1446  column.clear();
1447  for (int i = 0; i < tuple_literals.size(); ++i) {
1448  column.push_back(compressed_table[i][var_index]);
1449  }
1450  ProcessOneCompressedColumn(vars[var_index], tuple_literals, column,
1451  context);
1452  }
1453 
1454  context->UpdateRuleStats("table: expanded positive constraint");
1455 }
1456 
1457 // TODO(user): reinvestigate ExploreSubsetOfVariablesAndAddNegatedTables.
1458 //
1459 // TODO(user): if 2 table constraints share the same valid prefix, the
1460 // tuple literals can be reused.
1461 //
1462 // TODO(user): investigate different encoding for prefix tables. Maybe
1463 // we can remove the need to create tuple literals.
1464 void ExpandPositiveTable(ConstraintProto* ct, PresolveContext* context) {
1465  const TableConstraintProto& table = ct->table();
1466  const int num_vars = table.vars_size();
1467  const int num_original_tuples = table.values_size() / num_vars;
1468 
1469  // Read tuples flat array and recreate the vector of tuples.
1470  std::vector<int> vars(table.vars().begin(), table.vars().end());
1471  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1472  int count = 0;
1473  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1474  for (int var_index = 0; var_index < num_vars; ++var_index) {
1475  tuples[tuple_index].push_back(table.values(count++));
1476  }
1477  }
1478 
1479  // Compute the set of possible values for each variable (from the table).
1480  // Remove invalid tuples along the way.
1481  std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1482  int new_size = 0;
1483  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1484  bool keep = true;
1485  for (int var_index = 0; var_index < num_vars; ++var_index) {
1486  const int64_t value = tuples[tuple_index][var_index];
1487  if (!context->DomainContains(vars[var_index], value)) {
1488  keep = false;
1489  break;
1490  }
1491  }
1492  if (keep) {
1493  for (int var_index = 0; var_index < num_vars; ++var_index) {
1494  values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1495  }
1496  std::swap(tuples[tuple_index], tuples[new_size]);
1497  new_size++;
1498  }
1499  }
1500  tuples.resize(new_size);
1501 
1502  if (tuples.empty()) {
1503  context->UpdateRuleStats("table: empty");
1504  return (void)context->NotifyThatModelIsUnsat();
1505  }
1506 
1507  // Update variable domains. It is redundant with presolve, but we could be
1508  // here with presolve = false.
1509  // Also counts the number of fixed variables.
1510  int num_fixed_variables = 0;
1511  for (int var_index = 0; var_index < num_vars; ++var_index) {
1512  CHECK(context->IntersectDomainWith(
1513  vars[var_index],
1514  Domain::FromValues({values_per_var[var_index].begin(),
1515  values_per_var[var_index].end()})));
1516  if (context->DomainOf(vars[var_index]).IsFixed()) {
1517  num_fixed_variables++;
1518  }
1519  }
1520 
1521  if (num_fixed_variables == num_vars - 1) {
1522  context->UpdateRuleStats("table: one variable not fixed");
1523  ct->Clear();
1524  return;
1525  } else if (num_fixed_variables == num_vars) {
1526  context->UpdateRuleStats("table: all variables fixed");
1527  ct->Clear();
1528  return;
1529  }
1530 
1531  // Tables with two variables do not need tuple literals.
1532  //
1533  // TODO(user): If there is an unique variable with cost, it is better to
1534  // detect it. But if the detection fail, we should still call
1535  // AddSizeTwoTable() unlike what happen here.
1536  if (num_vars == 2 && !context->params().detect_table_with_cost()) {
1537  AddSizeTwoTable(vars, tuples, values_per_var, context);
1538  context->UpdateRuleStats(
1539  "table: expanded positive constraint with two variables");
1540  ct->Clear();
1541  return;
1542  }
1543 
1544  bool last_column_is_cost = false;
1545  if (context->params().detect_table_with_cost()) {
1546  last_column_is_cost =
1547  ReduceTableInPresenceOfUniqueVariableWithCosts(&vars, &tuples, context);
1548  }
1549 
1550  CompressAndExpandPositiveTable(last_column_is_cost, vars, &tuples, context);
1551  ct->Clear();
1552 }
1553 
1554 bool AllDiffShouldBeExpanded(const Domain& union_of_domains,
1555  ConstraintProto* ct, PresolveContext* context) {
1556  const AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
1557  const int num_exprs = proto.exprs_size();
1558  int num_fully_encoded = 0;
1559  for (int i = 0; i < num_exprs; ++i) {
1560  if (context->IsFullyEncoded(proto.exprs(i))) {
1561  num_fully_encoded++;
1562  }
1563  }
1564 
1565  if ((union_of_domains.Size() <= 2 * proto.exprs_size()) ||
1566  (union_of_domains.Size() <= 32)) {
1567  // Small domains.
1568  return true;
1569  }
1570 
1571  if (num_fully_encoded == num_exprs && union_of_domains.Size() < 256) {
1572  // All variables fully encoded, and domains are small enough.
1573  return true;
1574  }
1575  return false;
1576 }
1577 
1578 // Replaces a constraint literal => ax + by != cte by a set of clauses.
1579 // This is performed if the domains are small enough, and the variables are
1580 // fully encoded.
1581 //
1582 // We do it during the expansion as we want the first pass of the presolve to be
1583 // complete.
1584 void ExpandSomeLinearOfSizeTwo(ConstraintProto* ct, PresolveContext* context) {
1585  const LinearConstraintProto& arg = ct->linear();
1586  if (arg.vars_size() != 2) return;
1587 
1588  const int var1 = arg.vars(0);
1589  const int var2 = arg.vars(1);
1590  if (context->IsFixed(var1) || context->IsFixed(var2)) return;
1591 
1592  const int64_t coeff1 = arg.coeffs(0);
1593  const int64_t coeff2 = arg.coeffs(1);
1594  const Domain reachable_rhs_superset =
1595  context->DomainOf(var1)
1596  .MultiplicationBy(coeff1)
1597  .RelaxIfTooComplex()
1598  .AdditionWith(context->DomainOf(var2)
1599  .MultiplicationBy(coeff2)
1600  .RelaxIfTooComplex());
1601  const Domain infeasible_reachable_values =
1602  reachable_rhs_superset.IntersectionWith(
1603  ReadDomainFromProto(arg).Complement());
1604 
1605  // We only deal with != cte constraints.
1606  if (infeasible_reachable_values.Size() != 1) return;
1607 
1608  // coeff1 * v1 + coeff2 * v2 != cte.
1609  int64_t a = coeff1;
1610  int64_t b = coeff2;
1611  int64_t cte = infeasible_reachable_values.FixedValue();
1612  int64_t x0 = 0;
1613  int64_t y0 = 0;
1614  if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
1615  // no solution.
1616  context->UpdateRuleStats("linear: expand always feasible ax + by != cte");
1617  ct->Clear();
1618  return;
1619  }
1620  const Domain reduced_domain =
1621  context->DomainOf(var1)
1622  .AdditionWith(Domain(-x0))
1623  .InverseMultiplicationBy(b)
1624  .IntersectionWith(context->DomainOf(var2)
1625  .AdditionWith(Domain(-y0))
1626  .InverseMultiplicationBy(-a));
1627 
1628  if (reduced_domain.Size() > 16) return;
1629 
1630  // Check if all the needed values are encoded.
1631  // TODO(user): Do we force encoding for very small domains? Current
1632  // experiments says no, but revisit later.
1633  const int64_t size1 = context->DomainOf(var1).Size();
1634  const int64_t size2 = context->DomainOf(var2).Size();
1635  for (const int64_t z : reduced_domain.Values()) {
1636  const int64_t value1 = x0 + b * z;
1637  const int64_t value2 = y0 - a * z;
1638  DCHECK(context->DomainContains(var1, value1)) << "value1 = " << value1;
1639  DCHECK(context->DomainContains(var2, value2)) << "value2 = " << value2;
1640  DCHECK_EQ(coeff1 * value1 + coeff2 * value2,
1641  infeasible_reachable_values.FixedValue());
1642  // TODO(user): Presolve if one or two variables are Boolean.
1643  if (!context->HasVarValueEncoding(var1, value1, nullptr) || size1 == 2) {
1644  return;
1645  }
1646  if (!context->HasVarValueEncoding(var2, value2, nullptr) || size2 == 2) {
1647  return;
1648  }
1649  }
1650 
1651  // All encoding literals already exist and the number of clauses to create
1652  // is small enough. We can encode the constraint using just clauses.
1653  for (const int64_t z : reduced_domain.Values()) {
1654  const int64_t value1 = x0 + b * z;
1655  const int64_t value2 = y0 - a * z;
1656  // We cannot have both lit1 and lit2 true.
1657  const int lit1 = context->GetOrCreateVarValueEncoding(var1, value1);
1658  const int lit2 = context->GetOrCreateVarValueEncoding(var2, value2);
1659  auto* bool_or =
1660  context->working_model->add_constraints()->mutable_bool_or();
1661  bool_or->add_literals(NegatedRef(lit1));
1662  bool_or->add_literals(NegatedRef(lit2));
1663  for (const int lit : ct->enforcement_literal()) {
1664  bool_or->add_literals(NegatedRef(lit));
1665  }
1666  }
1667 
1668  context->UpdateRuleStats("linear: expand small ax + by != cte");
1669  ct->Clear();
1670 }
1671 
1672 // Note that we used to do that at loading time, but we prefer to do that as
1673 // part of the presolve so that all variables are available for sharing between
1674 // subworkers and also are accessible by the linear relaxation.
1675 //
1676 // TODO(user): Note that currently both encoding introduce extra solutions
1677 // if the constraint has some enforcement literal(). We can either fix this by
1678 // supporting enumeration on a subset of variable. Or add extra constraint to
1679 // fix all new Boolean to false if the initial constraint is not enforced.
1680 void ExpandComplexLinearConstraint(int c, ConstraintProto* ct,
1681  PresolveContext* context) {
1682  // TODO(user): We treat the linear of size 1 differently because we need them
1683  // as is to recognize value encoding. Try to still creates needed Boolean now
1684  // so that we can share more between the different workers. Or revisit how
1685  // linear1 are propagated.
1686  if (ct->linear().domain().size() <= 2) return;
1687  if (ct->linear().vars().size() == 1) return;
1688 
1689  const SatParameters& params = context->params();
1690  if (params.encode_complex_linear_constraint_with_integer()) {
1691  // Integer encoding.
1692  //
1693  // Here we add a slack with domain equal to rhs and transform
1694  // expr \in rhs to expr - slack = 0
1695  const Domain rhs = ReadDomainFromProto(ct->linear());
1696  const int slack = context->NewIntVar(rhs);
1697  ct->mutable_linear()->add_vars(slack);
1698  ct->mutable_linear()->add_coeffs(-1);
1699  ct->mutable_linear()->clear_domain();
1700  ct->mutable_linear()->add_domain(0);
1701  ct->mutable_linear()->add_domain(0);
1702  } else {
1703  // Boolean encoding.
1704  int single_bool;
1705  BoolArgumentProto* clause = nullptr;
1706  std::vector<int> domain_literals;
1707  if (ct->enforcement_literal().empty() && ct->linear().domain_size() == 4) {
1708  // We cover the special case of no enforcement and two choices by creating
1709  // a single Boolean.
1710  single_bool = context->NewBoolVar();
1711  } else {
1712  clause = context->working_model->add_constraints()->mutable_bool_or();
1713  for (const int ref : ct->enforcement_literal()) {
1714  clause->add_literals(NegatedRef(ref));
1715  }
1716  }
1717 
1718  // Save enforcement literals for the enumeration.
1719  const std::vector<int> enforcement_literals(
1720  ct->enforcement_literal().begin(), ct->enforcement_literal().end());
1721  ct->mutable_enforcement_literal()->Clear();
1722  for (int i = 0; i < ct->linear().domain_size(); i += 2) {
1723  const int64_t lb = ct->linear().domain(i);
1724  const int64_t ub = ct->linear().domain(i + 1);
1725 
1726  int subdomain_literal;
1727  if (clause != nullptr) {
1728  subdomain_literal = context->NewBoolVar();
1729  clause->add_literals(subdomain_literal);
1730  domain_literals.push_back(subdomain_literal);
1731  } else {
1732  if (i == 0) domain_literals.push_back(single_bool);
1733  subdomain_literal = i == 0 ? single_bool : NegatedRef(single_bool);
1734  }
1735 
1736  // Create a new constraint which is a copy of the original, but with a
1737  // simple sub-domain and enforcement literal.
1738  ConstraintProto* new_ct = context->working_model->add_constraints();
1739  *new_ct = *ct;
1740  new_ct->add_enforcement_literal(subdomain_literal);
1741  FillDomainInProto(Domain(lb, ub), new_ct->mutable_linear());
1742  }
1743 
1744  // Make sure all booleans are tights when enumerating all solutions.
1745  if (context->params().enumerate_all_solutions() &&
1746  !enforcement_literals.empty()) {
1747  int linear_is_enforced;
1748  if (enforcement_literals.size() == 1) {
1749  linear_is_enforced = enforcement_literals[0];
1750  } else {
1751  linear_is_enforced = context->NewBoolVar();
1752  BoolArgumentProto* maintain_linear_is_enforced =
1753  context->working_model->add_constraints()->mutable_bool_or();
1754  for (const int e_lit : enforcement_literals) {
1755  context->AddImplication(NegatedRef(e_lit),
1756  NegatedRef(linear_is_enforced));
1757  maintain_linear_is_enforced->add_literals(NegatedRef(e_lit));
1758  }
1759  maintain_linear_is_enforced->add_literals(linear_is_enforced);
1760  }
1761 
1762  for (const int lit : domain_literals) {
1763  context->AddImplication(NegatedRef(linear_is_enforced),
1764  NegatedRef(lit));
1765  }
1766  }
1767  ct->Clear();
1768  }
1769 
1770  context->UpdateRuleStats("linear: expanded complex rhs");
1771  context->InitializeNewDomains();
1772  context->UpdateNewConstraintsVariableUsage();
1773  context->UpdateConstraintVariableUsage(c);
1774 }
1775 
1776 bool IsVarEqOrNeqValue(PresolveContext* context,
1777  const LinearConstraintProto& lin) {
1778  if (lin.vars_size() != 1) return false;
1779  const Domain rhs = ReadDomainFromProto(lin);
1780  if (rhs.IsFixed()) return true;
1781  return rhs.InverseMultiplicationBy(lin.coeffs(0))
1782  .Complement()
1783  .IntersectionWith(context->DomainOf(lin.vars(0)))
1784  .IsFixed();
1785 }
1786 
1787 // This method will scan all constraints of all variables appearing in an
1788 // all_diff.
1789 // There are 3 outcomes:
1790 // - maybe expand to Boolean variables (depending on the size)
1791 // - keep integer all_different constraint (and cuts)
1792 // - expand and keep
1793 //
1794 // Expand is selected if the variable is fully encoded, or will be when
1795 // expanding other constraints: index of element, table, automaton.
1796 // It will check AllDiffShouldBeExpanded() before doing the actual expansion.
1797 // Keep is forced is the variable appears in a linear equation with at least 3
1798 // terms, and with a tight domain ( == cst).
1799 // TODO(user): The above rule is complex. Revisit.
1800 void ScanModelAndDecideAllDiffExpansion(
1801  ConstraintProto* all_diff_ct, PresolveContext* context,
1802  absl::flat_hash_set<int>& domain_of_var_is_used,
1803  absl::flat_hash_set<int>& bounds_of_var_are_used,
1804  absl::flat_hash_set<int>& processed_variables, bool& expand, bool& keep) {
1805  CHECK_EQ(all_diff_ct->constraint_case(), ConstraintProto::kAllDiff);
1806 
1807  bool at_least_one_var_domain_is_used = false;
1808  bool at_least_one_var_bound_is_used = false;
1809 
1810  // Scan variables.
1811  for (const LinearExpressionProto& expr : all_diff_ct->all_diff().exprs()) {
1812  // Skip constant expressions.
1813  if (expr.vars().empty()) continue;
1814  DCHECK_EQ(1, expr.vars_size());
1815  const int var = expr.vars(0);
1816  DCHECK(RefIsPositive(var));
1817  if (context->IsFixed(var)) continue;
1818 
1819  bool at_least_one_var_domain_is_used = false;
1820  bool at_least_one_var_bound_is_used = false;
1821 
1822  // Check cache.
1823  if (!processed_variables.insert(var).second) {
1824  at_least_one_var_domain_is_used = bounds_of_var_are_used.contains(var);
1825  at_least_one_var_bound_is_used = domain_of_var_is_used.contains(var);
1826  } else {
1827  bool domain_is_used = false;
1828  bool bounds_are_used = false;
1829 
1830  // Note: Boolean constraints are ignored.
1831  for (const int ct_index : context->VarToConstraints(var)) {
1832  // Skip artificial constraints.
1833  if (ct_index < 0) continue;
1834 
1835  const ConstraintProto& other_ct =
1836  context->working_model->constraints(ct_index);
1837  switch (other_ct.constraint_case()) {
1838  case ConstraintProto::ConstraintCase::kBoolOr:
1839  break;
1840  case ConstraintProto::ConstraintCase::kBoolAnd:
1841  break;
1842  case ConstraintProto::ConstraintCase::kAtMostOne:
1843  break;
1844  case ConstraintProto::ConstraintCase::kExactlyOne:
1845  break;
1846  case ConstraintProto::ConstraintCase::kBoolXor:
1847  break;
1848  case ConstraintProto::ConstraintCase::kIntDiv:
1849  break;
1850  case ConstraintProto::ConstraintCase::kIntMod:
1851  break;
1852  case ConstraintProto::ConstraintCase::kLinMax:
1853  bounds_are_used = true;
1854  break;
1855  case ConstraintProto::ConstraintCase::kIntProd:
1856  break;
1857  case ConstraintProto::ConstraintCase::kLinear:
1858  if (IsVarEqOrNeqValue(context, other_ct.linear()) &&
1859  var == other_ct.linear().vars(0)) {
1860  // Encoding literals.
1861  domain_is_used = true;
1862  } else if (other_ct.linear().vars_size() > 2 &&
1863  other_ct.linear().domain_size() == 2 &&
1864  other_ct.linear().domain(0) ==
1865  other_ct.linear().domain(1)) {
1866  // We assume all_diff cuts will only be useful if the linear
1867  // constraint has a fixed domain.
1868  bounds_are_used = true;
1869  }
1870  break;
1871  case ConstraintProto::ConstraintCase::kAllDiff:
1872  // We ignore all_diffs as we are trying to decide their expansion
1873  // from the rest of the model.
1874  break;
1875  case ConstraintProto::ConstraintCase::kDummyConstraint:
1876  break;
1877  case ConstraintProto::ConstraintCase::kElement:
1878  // Note: elements should have been expanded.
1879  if (other_ct.element().index() == var) {
1880  domain_is_used = true;
1881  }
1882  break;
1883  case ConstraintProto::ConstraintCase::kCircuit:
1884  break;
1885  case ConstraintProto::ConstraintCase::kRoutes:
1886  break;
1887  case ConstraintProto::ConstraintCase::kInverse:
1888  domain_is_used = true;
1889  break;
1890  case ConstraintProto::ConstraintCase::kReservoir:
1891  break;
1892  case ConstraintProto::ConstraintCase::kTable:
1893  domain_is_used = true;
1894  break;
1895  case ConstraintProto::ConstraintCase::kAutomaton:
1896  domain_is_used = true;
1897  break;
1898  case ConstraintProto::ConstraintCase::kInterval:
1899  bounds_are_used = true;
1900  break;
1901  case ConstraintProto::ConstraintCase::kNoOverlap:
1902  // Will be covered by the interval case.
1903  break;
1904  case ConstraintProto::ConstraintCase::kNoOverlap2D:
1905  // Will be covered by the interval case.
1906  break;
1907  case ConstraintProto::ConstraintCase::kCumulative:
1908  // Will be covered by the interval case.
1909  break;
1910  case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
1911  break;
1912  }
1913 
1914  // Exit early.
1915  if (domain_is_used && bounds_are_used) break;
1916  } // Loop on other_ct.
1917 
1918  // Update cache.
1919  if (domain_is_used) domain_of_var_is_used.insert(var);
1920  if (bounds_are_used) bounds_of_var_are_used.insert(var);
1921 
1922  // Update the usage of the variable.
1923  at_least_one_var_domain_is_used |= domain_is_used;
1924  at_least_one_var_bound_is_used |= bounds_are_used;
1925  } // End of model scanning.
1926 
1927  if (at_least_one_var_domain_is_used && at_least_one_var_bound_is_used) {
1928  break; // No need to scan the rest of the all_diff.
1929  }
1930  } // End of var processing.
1931 
1932  expand = at_least_one_var_domain_is_used;
1933  keep = at_least_one_var_bound_is_used;
1934 }
1935 
1936 void MaybeExpandAllDiff(ConstraintProto* ct, PresolveContext* context,
1937  absl::flat_hash_set<int>& domain_of_var_is_used,
1938  absl::flat_hash_set<int>& bounds_of_var_are_used,
1939  absl::flat_hash_set<int>& processed_variable) {
1940  const bool expand_all_diff_from_parameters =
1941  context->params().expand_alldiff_constraints();
1942  AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
1943  if (proto.exprs_size() <= 1) return;
1944 
1945  bool keep_after_expansion = false;
1946  bool expand_all_diff_from_usage = false;
1947  ScanModelAndDecideAllDiffExpansion(
1948  ct, context, domain_of_var_is_used, bounds_of_var_are_used,
1949  processed_variable, expand_all_diff_from_usage, keep_after_expansion);
1950 
1951  const int num_exprs = proto.exprs_size();
1952  Domain union_of_domains = context->DomainSuperSetOf(proto.exprs(0));
1953  for (int i = 1; i < num_exprs; ++i) {
1954  union_of_domains =
1955  union_of_domains.UnionWith(context->DomainSuperSetOf(proto.exprs(i)));
1956  }
1957 
1958  const bool expand_all_diff_from_size =
1959  AllDiffShouldBeExpanded(union_of_domains, ct, context);
1960 
1961  // Decide expansion:
1962  // - always expand if expand_all_diff_from_parameters
1963  // - expand if size is compatible (expand_all_diff_from_size) and
1964  // expansion is desired:
1965  // expand_all_diff_from_usage || !keep_after_expansion
1966  const bool should_expand =
1967  expand_all_diff_from_parameters ||
1968  (expand_all_diff_from_size &&
1969  (expand_all_diff_from_usage || !keep_after_expansion));
1970  if (!should_expand) return;
1971 
1972  const bool is_a_permutation = num_exprs == union_of_domains.Size();
1973 
1974  // Collect all possible variables that can take each value, and add one linear
1975  // equation per value stating that this value can be assigned at most once, or
1976  // exactly once in case of permutation.
1977  for (const int64_t v : union_of_domains.Values()) {
1978  // Collect references which domain contains v.
1979  std::vector<LinearExpressionProto> possible_exprs;
1980  int fixed_expression_count = 0;
1981  for (const LinearExpressionProto& expr : proto.exprs()) {
1982  if (!context->DomainContains(expr, v)) continue;
1983  possible_exprs.push_back(expr);
1984  if (context->IsFixed(expr)) {
1985  fixed_expression_count++;
1986  }
1987  }
1988 
1989  if (fixed_expression_count > 1) {
1990  // Violates the definition of AllDifferent.
1991  return (void)context->NotifyThatModelIsUnsat();
1992  } else if (fixed_expression_count == 1) {
1993  // Remove values from other domains.
1994  for (const LinearExpressionProto& expr : possible_exprs) {
1995  if (context->IsFixed(expr)) continue;
1996  if (!context->IntersectDomainWith(expr, Domain(v).Complement())) {
1997  VLOG(1) << "Empty domain for a variable in MaybeExpandAllDiff()";
1998  return;
1999  }
2000  }
2001  }
2002 
2003  BoolArgumentProto* at_most_or_equal_one =
2004  is_a_permutation
2005  ? context->working_model->add_constraints()->mutable_exactly_one()
2006  : context->working_model->add_constraints()->mutable_at_most_one();
2007  for (const LinearExpressionProto& expr : possible_exprs) {
2008  // The above propagation can remove a value after the expressions was
2009  // added to possible_exprs.
2010  if (!context->DomainContains(expr, v)) continue;
2011 
2012  // If the expression is fixed, the created literal will be the true
2013  // literal. We still need to fail if two expressions are fixed to the same
2014  // value.
2015  const int encoding = context->GetOrCreateAffineValueEncoding(expr, v);
2016  at_most_or_equal_one->add_literals(encoding);
2017  }
2018  }
2019 
2020  context->UpdateRuleStats(
2021  absl::StrCat("all_diff:", is_a_permutation ? " permutation" : "",
2022  " expanded", keep_after_expansion ? " and kept" : ""));
2023  if (!keep_after_expansion) ct->Clear();
2024 }
2025 
2026 } // namespace
2027 
2029  if (context->params().disable_constraint_expansion()) return;
2030  if (context->ModelIsUnsat()) return;
2031 
2032  // None of the function here need to be run twice. This is because we never
2033  // create constraint that need to be expanded during presolve.
2034  if (context->ModelIsExpanded()) return;
2035 
2036  // Make sure all domains are initialized.
2037  context->InitializeNewDomains();
2038 
2039  // Clear the precedence cache.
2040  context->ClearPrecedenceCache();
2041 
2042  bool has_all_diffs = false;
2043 
2044  // First pass: we look at constraints that may fully encode variables.
2045  for (int c = 0; c < context->working_model->constraints_size(); ++c) {
2046  ConstraintProto* const ct = context->working_model->mutable_constraints(c);
2047  bool skip = false;
2048  switch (ct->constraint_case()) {
2049  case ConstraintProto::kLinear:
2050  // If we only do expansion, we do that as part of the main loop.
2051  // This way we don't need to call FinalExpansionForLinearConstraint().
2052  if (ct->linear().domain().size() > 2 &&
2053  !context->params().cp_model_presolve()) {
2054  ExpandComplexLinearConstraint(c, ct, context);
2055  }
2056  break;
2057  case ConstraintProto::kReservoir:
2058  if (context->params().expand_reservoir_constraints()) {
2059  for (const LinearExpressionProto& demand_expr :
2060  ct->reservoir().level_changes()) {
2061  if (!context->IsFixed(demand_expr)) {
2062  skip = true;
2063  break;
2064  }
2065  }
2066  if (skip) {
2067  context->UpdateRuleStats(
2068  "reservoir: expansion is not supported with variable level "
2069  "changes");
2070  } else {
2071  ExpandReservoir(ct, context);
2072  }
2073  }
2074  break;
2075  case ConstraintProto::kIntMod:
2076  ExpandIntMod(ct, context);
2077  break;
2078  case ConstraintProto::kIntProd:
2079  ExpandIntProd(ct, context);
2080  break;
2081  case ConstraintProto::kElement:
2082  ExpandElement(ct, context);
2083  break;
2084  case ConstraintProto::kInverse:
2085  ExpandInverse(ct, context);
2086  break;
2087  case ConstraintProto::kAutomaton:
2088  ExpandAutomaton(ct, context);
2089  break;
2090  case ConstraintProto::kTable:
2091  if (ct->table().negated()) {
2092  ExpandNegativeTable(ct, context);
2093  } else {
2094  ExpandPositiveTable(ct, context);
2095  }
2096  break;
2097  case ConstraintProto::kAllDiff:
2098  has_all_diffs = true;
2099  skip = true;
2100  break;
2101  default:
2102  skip = true;
2103  break;
2104  }
2105  if (skip) continue; // Nothing was done for this constraint.
2106 
2107  // Update variable-constraint graph.
2108  context->UpdateNewConstraintsVariableUsage();
2109  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2110  context->UpdateConstraintVariableUsage(c);
2111  }
2112 
2113  // Early exit if the model is unsat.
2114  if (context->ModelIsUnsat()) {
2115  SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
2117  return;
2118  }
2119  }
2120 
2121  // Second pass. We may decide to expand constraints if all their variables
2122  // are fully encoded.
2123  //
2124  // Cache for variable scanning.
2125  absl::flat_hash_set<int> domain_of_var_is_used;
2126  absl::flat_hash_set<int> bounds_of_var_are_used;
2127  absl::flat_hash_set<int> processed_variables;
2128  for (int i = 0; i < context->working_model->constraints_size(); ++i) {
2129  ConstraintProto* const ct = context->working_model->mutable_constraints(i);
2130  bool skip = false;
2131  switch (ct->constraint_case()) {
2132  case ConstraintProto::kAllDiff:
2133  MaybeExpandAllDiff(ct, context, domain_of_var_is_used,
2134  bounds_of_var_are_used, processed_variables);
2135  break;
2136  case ConstraintProto::kLinear:
2137  ExpandSomeLinearOfSizeTwo(ct, context);
2138  break;
2139  default:
2140  skip = true;
2141  break;
2142  }
2143 
2144  if (skip) continue; // Nothing was done for this constraint.
2145 
2146  // Update variable-constraint graph.
2147  context->UpdateNewConstraintsVariableUsage();
2148  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2149  context->UpdateConstraintVariableUsage(i);
2150  }
2151 
2152  // Early exit if the model is unsat.
2153  if (context->ModelIsUnsat()) {
2154  SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
2156  return;
2157  }
2158  }
2159 
2160  // The precedence cache can become invalid during presolve as it does not
2161  // handle variable substitution. It is safer just to clear it at the end
2162  // of the expansion phase.
2163  context->ClearPrecedenceCache();
2164 
2165  // Make sure the context is consistent.
2166  context->InitializeNewDomains();
2167 
2168  // Update any changed domain from the context.
2169  for (int i = 0; i < context->working_model->variables_size(); ++i) {
2170  FillDomainInProto(context->DomainOf(i),
2171  context->working_model->mutable_variables(i));
2172  }
2173 
2174  context->NotifyThatModelIsExpanded();
2175 }
2176 
2178  if (context->params().disable_constraint_expansion()) return;
2179  if (context->ModelIsUnsat()) return;
2180  for (int c = 0; c < context->working_model->constraints_size(); ++c) {
2181  ConstraintProto* const ct = context->working_model->mutable_constraints(c);
2182  switch (ct->constraint_case()) {
2183  case ConstraintProto::kLinear:
2184  if (ct->linear().domain().size() > 2) {
2185  ExpandComplexLinearConstraint(c, ct, context);
2186  }
2187  break;
2188  default:
2189  break;
2190  }
2191  }
2192 }
2193 
2194 } // namespace sat
2195 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
Domain Complement() const
Returns the set Int64 ∖ D.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
int64_t b
int64_t a
CpModelProto proto
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
bool RefIsPositive(int ref)
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t >> *tuples)
Definition: sat/util.cc:386
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t >> *tuples)
Definition: sat/util.cc:783
void ExpandCpModel(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition: sat/util.cc:164
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
void FinalExpansionForLinearConstraint(PresolveContext *context)
constexpr int64_t kTableAnyValue
Definition: sat/util.h:358
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t >> *states, std::vector< absl::flat_hash_set< int64_t >> *labels)
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
Literal literal
Definition: optimization.cc:88
int column
Definition: parse_proto.cc:32
int64_t demand
Definition: resource.cc:126
int64_t time
Definition: resource.cc:1694
int64_t tail
int64_t cost
int64_t head
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define VLOG(verboselevel)
Definition: vlog.h:39