OR-Tools  9.0
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 <cstdint>
17 #include <limits>
18 #include <map>
19 
20 #include "absl/container/flat_hash_map.h"
21 #include "ortools/base/hash.h"
22 #include "ortools/base/map_util.h"
23 #include "ortools/base/stl_util.h"
28 #include "ortools/sat/util.h"
31 
32 namespace operations_research {
33 namespace sat {
34 namespace {
35 
36 void ExpandReservoir(ConstraintProto* ct, PresolveContext* context) {
37  if (ct->reservoir().min_level() > ct->reservoir().max_level()) {
38  VLOG(1) << "Empty level domain in reservoir constraint.";
39  return (void)context->NotifyThatModelIsUnsat();
40  }
41 
42  const ReservoirConstraintProto& reservoir = ct->reservoir();
43  const int num_events = reservoir.times_size();
44 
45  const int true_literal = context->GetOrCreateConstantVar(1);
46 
47  const auto is_active_literal = [&reservoir, true_literal](int index) {
48  if (reservoir.actives_size() == 0) return true_literal;
49  return reservoir.actives(index);
50  };
51 
52  int num_positives = 0;
53  int num_negatives = 0;
54  for (const int64_t demand : reservoir.demands()) {
55  if (demand > 0) {
56  num_positives++;
57  } else if (demand < 0) {
58  num_negatives++;
59  }
60  }
61 
62  if (num_positives > 0 && num_negatives > 0) {
63  // Creates Boolean variables equivalent to (start[i] <= start[j]) i != j
64  for (int i = 0; i < num_events - 1; ++i) {
65  const int active_i = is_active_literal(i);
66  if (context->LiteralIsFalse(active_i)) continue;
67  const int time_i = reservoir.times(i);
68 
69  for (int j = i + 1; j < num_events; ++j) {
70  const int active_j = is_active_literal(j);
71  if (context->LiteralIsFalse(active_j)) continue;
72  const int time_j = reservoir.times(j);
73 
74  const int i_lesseq_j = context->GetOrCreateReifiedPrecedenceLiteral(
75  time_i, time_j, active_i, active_j);
76  context->working_model->mutable_variables(i_lesseq_j)
77  ->set_name(absl::StrCat(i, " before ", j));
78  const int j_lesseq_i = context->GetOrCreateReifiedPrecedenceLiteral(
79  time_j, time_i, active_j, active_i);
80  context->working_model->mutable_variables(j_lesseq_i)
81  ->set_name(absl::StrCat(j, " before ", i));
82  }
83  }
84 
85  // Constrains the running level to be consistent at all times.
86  // For this we only add a constraint at the time a given demand
87  // take place. We also have a constraint for time zero if needed
88  // (added below).
89  for (int i = 0; i < num_events; ++i) {
90  const int active_i = is_active_literal(i);
91  if (context->LiteralIsFalse(active_i)) continue;
92  const int time_i = reservoir.times(i);
93 
94  // Accumulates demands of all predecessors.
95  ConstraintProto* const level = context->working_model->add_constraints();
96  level->add_enforcement_literal(active_i);
97 
98  // Add contributions from previous events.
99  for (int j = 0; j < num_events; ++j) {
100  if (i == j) continue;
101  const int active_j = is_active_literal(j);
102  if (context->LiteralIsFalse(active_j)) continue;
103 
104  const int time_j = reservoir.times(j);
105  level->mutable_linear()->add_vars(
106  context->GetOrCreateReifiedPrecedenceLiteral(time_j, time_i,
107  active_j, active_i));
108  level->mutable_linear()->add_coeffs(reservoir.demands(j));
109  }
110 
111  // Accounts for own demand in the domain of the sum.
112  const int64_t demand_i = reservoir.demands(i);
113  level->mutable_linear()->add_domain(
114  CapSub(reservoir.min_level(), demand_i));
115  level->mutable_linear()->add_domain(
116  CapSub(reservoir.max_level(), demand_i));
117  }
118  } else {
119  // If all demands have the same sign, we do not care about the order, just
120  // the sum.
121  auto* const sum =
122  context->working_model->add_constraints()->mutable_linear();
123  for (int i = 0; i < num_events; ++i) {
124  sum->add_vars(is_active_literal(i));
125  sum->add_coeffs(reservoir.demands(i));
126  }
127  sum->add_domain(reservoir.min_level());
128  sum->add_domain(reservoir.max_level());
129  }
130 
131  ct->Clear();
132  context->UpdateRuleStats("reservoir: expanded");
133 }
134 
135 // This is not an "expansion" per say, but just a mandatory presolve step to
136 // satisfy preconditions assumed by the rest of the code.
137 void ExpandIntDiv(ConstraintProto* ct, PresolveContext* context) {
138  const int divisor = ct->int_div().vars(1);
139  if (!context->IntersectDomainWith(divisor, Domain(0).Complement())) {
140  return (void)context->NotifyThatModelIsUnsat();
141  }
142 }
143 
144 void ExpandIntMod(ConstraintProto* ct, PresolveContext* context) {
145  const IntegerArgumentProto& int_mod = ct->int_mod();
146  const int var = int_mod.vars(0);
147  const int mod_var = int_mod.vars(1);
148  const int target_var = int_mod.target();
149 
150  const int64_t mod_lb = context->MinOf(mod_var);
151  CHECK_GE(mod_lb, 1);
152  const int64_t mod_ub = context->MaxOf(mod_var);
153 
154  const int64_t var_lb = context->MinOf(var);
155  const int64_t var_ub = context->MaxOf(var);
156 
157  // Compute domains of var / mod_var.
158  // TODO(user): implement Domain.ContinuousDivisionBy(domain).
159  const int div_var =
160  context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb));
161 
162  auto add_enforcement_literal_if_needed = [&]() {
163  if (ct->enforcement_literal_size() == 0) return;
164  const int literal = ct->enforcement_literal(0);
165  ConstraintProto* const last = context->working_model->mutable_constraints(
166  context->working_model->constraints_size() - 1);
167  last->add_enforcement_literal(literal);
168  };
169 
170  // div = var / mod.
171  IntegerArgumentProto* const div_proto =
172  context->working_model->add_constraints()->mutable_int_div();
173  div_proto->set_target(div_var);
174  div_proto->add_vars(var);
175  div_proto->add_vars(mod_var);
176  add_enforcement_literal_if_needed();
177 
178  // Checks if mod is constant.
179  if (mod_lb == mod_ub) {
180  // var - div_var * mod = target.
181  LinearConstraintProto* const lin =
182  context->working_model->add_constraints()->mutable_linear();
183  lin->add_vars(int_mod.vars(0));
184  lin->add_coeffs(1);
185  lin->add_vars(div_var);
186  lin->add_coeffs(-mod_lb);
187  lin->add_vars(target_var);
188  lin->add_coeffs(-1);
189  lin->add_domain(0);
190  lin->add_domain(0);
191  add_enforcement_literal_if_needed();
192  } else {
193  // Create prod_var = div_var * mod.
194  const int prod_var = context->NewIntVar(
195  context->DomainOf(div_var).ContinuousMultiplicationBy(
196  context->DomainOf(mod_var)));
197  IntegerArgumentProto* const int_prod =
198  context->working_model->add_constraints()->mutable_int_prod();
199  int_prod->set_target(prod_var);
200  int_prod->add_vars(div_var);
201  int_prod->add_vars(mod_var);
202  add_enforcement_literal_if_needed();
203 
204  // var - prod_var = target.
205  LinearConstraintProto* const lin =
206  context->working_model->add_constraints()->mutable_linear();
207  lin->add_vars(var);
208  lin->add_coeffs(1);
209  lin->add_vars(prod_var);
210  lin->add_coeffs(-1);
211  lin->add_vars(target_var);
212  lin->add_coeffs(-1);
213  lin->add_domain(0);
214  lin->add_domain(0);
215  add_enforcement_literal_if_needed();
216  }
217 
218  ct->Clear();
219  context->UpdateRuleStats("int_mod: expanded");
220 }
221 
222 void ExpandIntProdWithBoolean(int bool_ref, int int_ref, int product_ref,
223  PresolveContext* context) {
224  ConstraintProto* const one = context->working_model->add_constraints();
225  one->add_enforcement_literal(bool_ref);
226  one->mutable_linear()->add_vars(int_ref);
227  one->mutable_linear()->add_coeffs(1);
228  one->mutable_linear()->add_vars(product_ref);
229  one->mutable_linear()->add_coeffs(-1);
230  one->mutable_linear()->add_domain(0);
231  one->mutable_linear()->add_domain(0);
232 
233  ConstraintProto* const zero = context->working_model->add_constraints();
234  zero->add_enforcement_literal(NegatedRef(bool_ref));
235  zero->mutable_linear()->add_vars(product_ref);
236  zero->mutable_linear()->add_coeffs(1);
237  zero->mutable_linear()->add_domain(0);
238  zero->mutable_linear()->add_domain(0);
239 }
240 
241 void AddXEqualYOrXEqualZero(int x_eq_y, int x, int y,
242  PresolveContext* context) {
243  ConstraintProto* equality = context->working_model->add_constraints();
244  equality->add_enforcement_literal(x_eq_y);
245  equality->mutable_linear()->add_vars(x);
246  equality->mutable_linear()->add_coeffs(1);
247  equality->mutable_linear()->add_vars(y);
248  equality->mutable_linear()->add_coeffs(-1);
249  equality->mutable_linear()->add_domain(0);
250  equality->mutable_linear()->add_domain(0);
251  context->AddImplyInDomain(NegatedRef(x_eq_y), x, {0, 0});
252 }
253 
254 // a_ref spans across 0, b_ref does not.
255 void ExpandIntProdWithOneAcrossZero(int a_ref, int b_ref, int product_ref,
256  PresolveContext* context) {
257  DCHECK_LT(context->MinOf(a_ref), 0);
258  DCHECK_GT(context->MaxOf(a_ref), 0);
259  DCHECK(context->MinOf(b_ref) >= 0 || context->MaxOf(b_ref) <= 0);
260 
261  // Split the domain of a in two, controlled by a new literal.
262  const int a_is_positive = context->NewBoolVar();
263  context->AddImplyInDomain(a_is_positive, a_ref,
265  context->AddImplyInDomain(NegatedRef(a_is_positive), a_ref,
267  const int pos_a_ref = context->NewIntVar({0, context->MaxOf(a_ref)});
268  AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref, context);
269 
270  const int neg_a_ref = context->NewIntVar({context->MinOf(a_ref), 0});
271  AddXEqualYOrXEqualZero(NegatedRef(a_is_positive), neg_a_ref, a_ref, context);
272 
273  // Create product with the positive part ofa_ref.
274  const bool b_is_positive = context->MinOf(b_ref) >= 0;
275  const Domain pos_a_product_domain =
276  b_is_positive ? Domain({0, context->MaxOf(product_ref)})
277  : Domain({context->MinOf(product_ref), 0});
278  const int pos_a_product = context->NewIntVar(pos_a_product_domain);
279  IntegerArgumentProto* pos_product =
280  context->working_model->add_constraints()->mutable_int_prod();
281  pos_product->set_target(pos_a_product);
282  pos_product->add_vars(pos_a_ref);
283  pos_product->add_vars(b_ref);
284 
285  // Create product with the negative part of a_ref.
286  const Domain neg_a_product_domain =
287  b_is_positive ? Domain({context->MinOf(product_ref), 0})
288  : Domain({0, context->MaxOf(product_ref)});
289  const int neg_a_product = context->NewIntVar(neg_a_product_domain);
290  IntegerArgumentProto* neg_product =
291  context->working_model->add_constraints()->mutable_int_prod();
292  neg_product->set_target(neg_a_product);
293  neg_product->add_vars(neg_a_ref);
294  neg_product->add_vars(b_ref);
295 
296  // Link back to the original product.
297  LinearConstraintProto* lin =
298  context->working_model->add_constraints()->mutable_linear();
299  lin->add_vars(product_ref);
300  lin->add_coeffs(-1);
301  lin->add_vars(pos_a_product);
302  lin->add_coeffs(1);
303  lin->add_vars(neg_a_product);
304  lin->add_coeffs(1);
305  lin->add_domain(0);
306  lin->add_domain(0);
307 }
308 
309 void ExpandIntProdWithTwoAcrossZero(int a_ref, int b_ref, int product_ref,
310  PresolveContext* context) {
311  // Split a_ref domain in two, controlled by a new literal.
312  const int a_is_positive = context->NewBoolVar();
313  context->AddImplyInDomain(a_is_positive, a_ref,
315  context->AddImplyInDomain(NegatedRef(a_is_positive), a_ref,
317  const int64_t min_of_a = context->MinOf(a_ref);
318  const int64_t max_of_a = context->MaxOf(a_ref);
319 
320  const int pos_a_ref = context->NewIntVar({0, max_of_a});
321  AddXEqualYOrXEqualZero(a_is_positive, pos_a_ref, a_ref, context);
322 
323  const int neg_a_ref = context->NewIntVar({min_of_a, 0});
324  AddXEqualYOrXEqualZero(NegatedRef(a_is_positive), neg_a_ref, a_ref, context);
325 
326  // Create product with two sub parts of a_ref.
327  const int pos_product_ref =
328  context->NewIntVar(context->DomainOf(product_ref));
329  ExpandIntProdWithOneAcrossZero(b_ref, pos_a_ref, pos_product_ref, context);
330  const int neg_product_ref =
331  context->NewIntVar(context->DomainOf(product_ref));
332  ExpandIntProdWithOneAcrossZero(b_ref, neg_a_ref, neg_product_ref, context);
333 
334  // Link back to the original product.
335  LinearConstraintProto* lin =
336  context->working_model->add_constraints()->mutable_linear();
337  lin->add_vars(product_ref);
338  lin->add_coeffs(-1);
339  lin->add_vars(pos_product_ref);
340  lin->add_coeffs(1);
341  lin->add_vars(neg_product_ref);
342  lin->add_coeffs(1);
343  lin->add_domain(0);
344  lin->add_domain(0);
345 }
346 
347 void ExpandIntProd(ConstraintProto* ct, PresolveContext* context) {
348  const IntegerArgumentProto& int_prod = ct->int_prod();
349  if (int_prod.vars_size() != 2) return;
350  const int a = int_prod.vars(0);
351  const int b = int_prod.vars(1);
352  const int p = int_prod.target();
353  const bool a_is_boolean =
354  RefIsPositive(a) && context->MinOf(a) == 0 && context->MaxOf(a) == 1;
355  const bool b_is_boolean =
356  RefIsPositive(b) && context->MinOf(b) == 0 && context->MaxOf(b) == 1;
357 
358  // We expand if exactly one of {a, b} is Boolean. If both are Boolean, it
359  // will be presolved into a better version.
360  if (a_is_boolean && !b_is_boolean) {
361  ExpandIntProdWithBoolean(a, b, p, context);
362  ct->Clear();
363  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
364  return;
365  }
366  if (b_is_boolean && !a_is_boolean) {
367  ExpandIntProdWithBoolean(b, a, p, context);
368  ct->Clear();
369  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
370  return;
371  }
372 
373  const bool a_span_across_zero =
374  context->MinOf(a) < 0 && context->MaxOf(a) > 0;
375  const bool b_span_across_zero =
376  context->MinOf(b) < 0 && context->MaxOf(b) > 0;
377  if (a_span_across_zero && !b_span_across_zero) {
378  ExpandIntProdWithOneAcrossZero(a, b, p, context);
379  ct->Clear();
380  context->UpdateRuleStats(
381  "int_prod: expanded product with general integer variables");
382  return;
383  }
384  if (!a_span_across_zero && b_span_across_zero) {
385  ExpandIntProdWithOneAcrossZero(b, a, p, context);
386  ct->Clear();
387  context->UpdateRuleStats(
388  "int_prod: expanded product with general integer variables");
389  return;
390  }
391  if (a_span_across_zero && b_span_across_zero) {
392  ExpandIntProdWithTwoAcrossZero(a, b, p, context);
393  ct->Clear();
394  context->UpdateRuleStats(
395  "int_prod: expanded product with general integer variables");
396  return;
397  }
398 }
399 
400 void ExpandInverse(ConstraintProto* ct, PresolveContext* context) {
401  const int size = ct->inverse().f_direct().size();
402  CHECK_EQ(size, ct->inverse().f_inverse().size());
403 
404  // Make sure the domains are included in [0, size - 1).
405  //
406  // TODO(user): Add support for UNSAT at expansion. This should create empty
407  // domain if UNSAT, so it should still work correctly.
408  for (const int ref : ct->inverse().f_direct()) {
409  if (!context->IntersectDomainWith(ref, Domain(0, size - 1))) {
410  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
411  return;
412  }
413  }
414  for (const int ref : ct->inverse().f_inverse()) {
415  if (!context->IntersectDomainWith(ref, Domain(0, size - 1))) {
416  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
417  return;
418  }
419  }
420 
421  // Reduce the domains of each variable by checking that the inverse value
422  // exists.
423  std::vector<int64_t> possible_values;
424  // Propagate from one vector to its counterpart.
425  // Note this reaches the fixpoint as there is a one to one mapping between
426  // (variable-value) pairs in each vector.
427  const auto filter_inverse_domain = [context, size, &possible_values](
428  const auto& direct,
429  const auto& inverse) {
430  // Propagate for the inverse vector to the direct vector.
431  for (int i = 0; i < size; ++i) {
432  possible_values.clear();
433  const Domain domain = context->DomainOf(direct[i]);
434  bool removed_value = false;
435  for (const ClosedInterval& interval : domain) {
436  for (int64_t j = interval.start; j <= interval.end; ++j) {
437  if (context->DomainOf(inverse[j]).Contains(i)) {
438  possible_values.push_back(j);
439  } else {
440  removed_value = true;
441  }
442  }
443  }
444  if (removed_value) {
445  if (!context->IntersectDomainWith(
446  direct[i], Domain::FromValues(possible_values))) {
447  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
448  return false;
449  }
450  }
451  }
452  return true;
453  };
454 
455  if (!filter_inverse_domain(ct->inverse().f_direct(),
456  ct->inverse().f_inverse())) {
457  return;
458  }
459 
460  if (!filter_inverse_domain(ct->inverse().f_inverse(),
461  ct->inverse().f_direct())) {
462  return;
463  }
464 
465  // Expand the inverse constraint by associating literal to var == value
466  // and sharing them between the direct and inverse variables.
467  for (int i = 0; i < size; ++i) {
468  const int f_i = ct->inverse().f_direct(i);
469  const Domain domain = context->DomainOf(f_i);
470  for (const ClosedInterval& interval : domain) {
471  for (int64_t j = interval.start; j <= interval.end; ++j) {
472  // We have f[i] == j <=> r[j] == i;
473  const int r_j = ct->inverse().f_inverse(j);
474  int r_j_i;
475  if (context->HasVarValueEncoding(r_j, i, &r_j_i)) {
476  context->InsertVarValueEncoding(r_j_i, f_i, j);
477  } else {
478  const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
479  context->InsertVarValueEncoding(f_i_j, r_j, i);
480  }
481  }
482  }
483  }
484 
485  ct->Clear();
486  context->UpdateRuleStats("inverse: expanded");
487 }
488 
489 void ExpandElement(ConstraintProto* ct, PresolveContext* context) {
490  const ElementConstraintProto& element = ct->element();
491  const int index_ref = element.index();
492  const int target_ref = element.target();
493  const int size = element.vars_size();
494 
495  if (!context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
496  VLOG(1) << "Empty domain for the index variable in ExpandElement()";
497  return (void)context->NotifyThatModelIsUnsat();
498  }
499 
500  bool all_constants = true;
501  absl::flat_hash_map<int64_t, int> constant_var_values_usage;
502  std::vector<int64_t> constant_var_values;
503  std::vector<int64_t> invalid_indices;
504  Domain index_domain = context->DomainOf(index_ref);
505  Domain target_domain = context->DomainOf(target_ref);
506  for (const ClosedInterval& interval : index_domain) {
507  for (int64_t v = interval.start; v <= interval.end; ++v) {
508  const int var = element.vars(v);
509  const Domain var_domain = context->DomainOf(var);
510  if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
511  invalid_indices.push_back(v);
512  continue;
513  }
514  if (var_domain.Min() != var_domain.Max()) {
515  all_constants = false;
516  break;
517  }
518 
519  const int64_t value = var_domain.Min();
520  if (constant_var_values_usage[value]++ == 0) {
521  constant_var_values.push_back(value);
522  }
523  }
524  }
525 
526  if (!invalid_indices.empty() && target_ref != index_ref) {
527  if (!context->IntersectDomainWith(
528  index_ref, Domain::FromValues(invalid_indices).Complement())) {
529  VLOG(1) << "No compatible variable domains in ExpandElement()";
530  return (void)context->NotifyThatModelIsUnsat();
531  }
532 
533  // Re-read the domain.
534  index_domain = context->DomainOf(index_ref);
535  }
536 
537  // This BoolOrs implements the deduction that if all index literals pointing
538  // to the same values in the constant array are false, then this value is no
539  // no longer valid for the target variable. They are created only for values
540  // that have multiples literals supporting them.
541  // Order is not important.
542  absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
543  if (all_constants && target_ref != index_ref) {
544  if (!context->IntersectDomainWith(
545  target_ref, Domain::FromValues(constant_var_values))) {
546  VLOG(1) << "Empty domain for the target variable in ExpandElement()";
547  return;
548  }
549 
550  target_domain = context->DomainOf(target_ref);
551  if (target_domain.Size() == 1) {
552  context->UpdateRuleStats("element: one value array");
553  ct->Clear();
554  return;
555  }
556 
557  for (const ClosedInterval& interval : target_domain) {
558  for (int64_t v = interval.start; v <= interval.end; ++v) {
559  const int usage = gtl::FindOrDie(constant_var_values_usage, v);
560  if (usage > 1) {
561  const int lit = context->GetOrCreateVarValueEncoding(target_ref, v);
562  BoolArgumentProto* const support =
563  context->working_model->add_constraints()->mutable_bool_or();
564  supports[v] = support;
565  support->add_literals(NegatedRef(lit));
566  }
567  }
568  }
569  }
570 
571  // While this is not stricly needed since all value in the index will be
572  // covered, it allows to easily detect this fact in the presolve.
573  auto* bool_or = context->working_model->add_constraints()->mutable_bool_or();
574 
575  for (const ClosedInterval& interval : index_domain) {
576  for (int64_t v = interval.start; v <= interval.end; ++v) {
577  const int var = element.vars(v);
578  const int index_lit = context->GetOrCreateVarValueEncoding(index_ref, v);
579  const Domain var_domain = context->DomainOf(var);
580 
581  bool_or->add_literals(index_lit);
582 
583  if (target_ref == index_ref) {
584  // This adds extra code. But this information is really important,
585  // and hard to retrieve once lost.
586  context->AddImplyInDomain(index_lit, var, Domain(v));
587  } else if (target_domain.Size() == 1) {
588  // TODO(user): If we know all variables are different, then this
589  // becomes an equivalence.
590  context->AddImplyInDomain(index_lit, var, target_domain);
591  } else if (var_domain.Size() == 1) {
592  if (all_constants) {
593  const int64_t value = var_domain.Min();
594  if (constant_var_values_usage[value] > 1) {
595  // The encoding literal for 'value' of the target_ref has been
596  // created before.
597  const int target_lit =
598  context->GetOrCreateVarValueEncoding(target_ref, value);
599  context->AddImplication(index_lit, target_lit);
600  gtl::FindOrDie(supports, value)->add_literals(index_lit);
601  } else {
602  // Try to reuse the literal of the index.
603  context->InsertVarValueEncoding(index_lit, target_ref, value);
604  }
605  } else {
606  context->AddImplyInDomain(index_lit, target_ref, var_domain);
607  }
608  } else {
609  ConstraintProto* const ct = context->working_model->add_constraints();
610  ct->add_enforcement_literal(index_lit);
611  ct->mutable_linear()->add_vars(var);
612  ct->mutable_linear()->add_coeffs(1);
613  ct->mutable_linear()->add_vars(target_ref);
614  ct->mutable_linear()->add_coeffs(-1);
615  ct->mutable_linear()->add_domain(0);
616  ct->mutable_linear()->add_domain(0);
617  }
618  }
619  }
620 
621  if (all_constants) {
622  const int64_t var_min = target_domain.Min();
623 
624  // Scan all values to find the one with the most literals attached.
625  int64_t most_frequent_value = std::numeric_limits<int64_t>::max();
626  int usage = -1;
627  for (const auto it : constant_var_values_usage) {
628  if (it.second > usage ||
629  (it.second == usage && it.first < most_frequent_value)) {
630  usage = it.second;
631  most_frequent_value = it.first;
632  }
633  }
634 
635  // Add a linear constraint. This helps the linear relaxation.
636  //
637  // We try to minimize the size of the linear constraint (if the gain is
638  // meaningful compared to using the min that has the advantage that all
639  // coefficients are positive).
640  // TODO(user): Benchmark if using base is always beneficial.
641  // TODO(user): Try not to create this if max_usage == 1.
642  const int64_t base =
643  usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
644  if (base != var_min) {
645  VLOG(3) << "expand element: choose " << base << " with usage " << usage
646  << " over " << var_min << " among " << size << " values.";
647  }
648 
649  LinearConstraintProto* const linear =
650  context->working_model->add_constraints()->mutable_linear();
651  int64_t rhs = -base;
652  linear->add_vars(target_ref);
653  linear->add_coeffs(-1);
654  for (const ClosedInterval& interval : index_domain) {
655  for (int64_t v = interval.start; v <= interval.end; ++v) {
656  const int ref = element.vars(v);
657  const int index_lit =
658  context->GetOrCreateVarValueEncoding(index_ref, v);
659  const int64_t delta = context->DomainOf(ref).Min() - base;
660  if (RefIsPositive(index_lit)) {
661  linear->add_vars(index_lit);
662  linear->add_coeffs(delta);
663  } else {
664  linear->add_vars(NegatedRef(index_lit));
665  linear->add_coeffs(-delta);
666  rhs -= delta;
667  }
668  }
669  }
670  linear->add_domain(rhs);
671  linear->add_domain(rhs);
672 
673  context->UpdateRuleStats("element: expanded value element");
674  } else {
675  context->UpdateRuleStats("element: expanded");
676  }
677  ct->Clear();
678 }
679 
680 // Adds clauses so that literals[i] true <=> encoding[value[i]] true.
681 // This also implicitly use the fact that exactly one alternative is true.
682 void LinkLiteralsAndValues(
683  const std::vector<int>& value_literals, const std::vector<int64_t>& values,
684  const absl::flat_hash_map<int64_t, int>& target_encoding,
685  PresolveContext* context) {
686  CHECK_EQ(value_literals.size(), values.size());
687 
688  // TODO(user): Make sure this does not appear in the profile.
689  // We use a map to make this method deterministic.
690  std::map<int, std::vector<int>> value_literals_per_target_literal;
691 
692  // If a value is false (i.e not possible), then the tuple with this
693  // value is false too (i.e not possible). Conversely, if the tuple is
694  // selected, the value must be selected.
695  for (int i = 0; i < values.size(); ++i) {
696  const int64_t v = values[i];
697  CHECK(target_encoding.contains(v));
698  const int lit = gtl::FindOrDie(target_encoding, v);
699  value_literals_per_target_literal[lit].push_back(value_literals[i]);
700  }
701 
702  // If all tuples supporting a value are false, then this value must be
703  // false.
704  for (const auto& it : value_literals_per_target_literal) {
705  const int target_literal = it.first;
706  switch (it.second.size()) {
707  case 0: {
708  if (!context->SetLiteralToFalse(target_literal)) {
709  return;
710  }
711  break;
712  }
713  case 1: {
714  context->StoreBooleanEqualityRelation(target_literal,
715  it.second.front());
716  break;
717  }
718  default: {
719  BoolArgumentProto* const bool_or =
720  context->working_model->add_constraints()->mutable_bool_or();
721  bool_or->add_literals(NegatedRef(target_literal));
722  for (const int value_literal : it.second) {
723  bool_or->add_literals(value_literal);
724  context->AddImplication(value_literal, target_literal);
725  }
726  }
727  }
728  }
729 }
730 
731 void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
732  AutomatonConstraintProto& proto = *ct->mutable_automaton();
733 
734  if (proto.vars_size() == 0) {
735  const int64_t initial_state = proto.starting_state();
736  for (const int64_t final_state : proto.final_states()) {
737  if (initial_state == final_state) {
738  context->UpdateRuleStats("automaton: empty constraint");
739  ct->Clear();
740  return;
741  }
742  }
743  // The initial state is not in the final state. The model is unsat.
744  return (void)context->NotifyThatModelIsUnsat();
745  } else if (proto.transition_label_size() == 0) {
746  // Not transitions. The constraint is infeasible.
747  return (void)context->NotifyThatModelIsUnsat();
748  }
749 
750  const int n = proto.vars_size();
751  const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
752 
753  // Compute the set of reachable state at each time point.
754  const absl::flat_hash_set<int64_t> final_states(
755  {proto.final_states().begin(), proto.final_states().end()});
756  std::vector<absl::flat_hash_set<int64_t>> reachable_states(n + 1);
757  reachable_states[0].insert(proto.starting_state());
758 
759  // Forward pass.
760  for (int time = 0; time < n; ++time) {
761  for (int t = 0; t < proto.transition_tail_size(); ++t) {
762  const int64_t tail = proto.transition_tail(t);
763  const int64_t label = proto.transition_label(t);
764  const int64_t head = proto.transition_head(t);
765  if (!reachable_states[time].contains(tail)) continue;
766  if (!context->DomainContains(vars[time], label)) continue;
767  if (time == n - 1 && !final_states.contains(head)) continue;
768  reachable_states[time + 1].insert(head);
769  }
770  }
771 
772  // Backward pass.
773  for (int time = n - 1; time >= 0; --time) {
774  absl::flat_hash_set<int64_t> new_set;
775  for (int t = 0; t < proto.transition_tail_size(); ++t) {
776  const int64_t tail = proto.transition_tail(t);
777  const int64_t label = proto.transition_label(t);
778  const int64_t head = proto.transition_head(t);
779 
780  if (!reachable_states[time].contains(tail)) continue;
781  if (!context->DomainContains(vars[time], label)) continue;
782  if (!reachable_states[time + 1].contains(head)) continue;
783  new_set.insert(tail);
784  }
785  reachable_states[time].swap(new_set);
786  }
787 
788  // We will model at each time step the current automaton state using Boolean
789  // variables. We will have n+1 time step. At time zero, we start in the
790  // initial state, and at time n we should be in one of the final states. We
791  // don't need to create Booleans at at time when there is just one possible
792  // state (like at time zero).
793  absl::flat_hash_map<int64_t, int> encoding;
794  absl::flat_hash_map<int64_t, int> in_encoding;
795  absl::flat_hash_map<int64_t, int> out_encoding;
796  bool removed_values = false;
797 
798  for (int time = 0; time < n; ++time) {
799  // All these vector have the same size. We will use them to enforce a
800  // local table constraint representing one step of the automaton at the
801  // given time.
802  std::vector<int64_t> in_states;
803  std::vector<int64_t> transition_values;
804  std::vector<int64_t> out_states;
805  for (int i = 0; i < proto.transition_label_size(); ++i) {
806  const int64_t tail = proto.transition_tail(i);
807  const int64_t label = proto.transition_label(i);
808  const int64_t head = proto.transition_head(i);
809 
810  if (!reachable_states[time].contains(tail)) continue;
811  if (!reachable_states[time + 1].contains(head)) continue;
812  if (!context->DomainContains(vars[time], label)) continue;
813 
814  // TODO(user): if this transition correspond to just one in-state or
815  // one-out state or one variable value, we could reuse the corresponding
816  // Boolean variable instead of creating a new one!
817  in_states.push_back(tail);
818  transition_values.push_back(label);
819 
820  // On the last step we don't need to distinguish the output states, so
821  // we use zero.
822  out_states.push_back(time + 1 == n ? 0 : head);
823  }
824 
825  std::vector<int> tuple_literals;
826  if (transition_values.size() == 1) {
827  bool tmp_removed_values = false;
828  tuple_literals.push_back(context->GetOrCreateConstantVar(1));
829  CHECK_EQ(reachable_states[time + 1].size(), 1);
830  if (!context->IntersectDomainWith(vars[time],
831  Domain(transition_values.front()),
832  &tmp_removed_values)) {
833  return (void)context->NotifyThatModelIsUnsat();
834  }
835  in_encoding.clear();
836  continue;
837  } else if (transition_values.size() == 2) {
838  const int bool_var = context->NewBoolVar();
839  tuple_literals.push_back(bool_var);
840  tuple_literals.push_back(NegatedRef(bool_var));
841  } else {
842  // Note that we do not need the ExactlyOneConstraint(tuple_literals)
843  // because it is already implicitly encoded since we have exactly one
844  // transition value.
845  LinearConstraintProto* const exactly_one =
846  context->working_model->add_constraints()->mutable_linear();
847  exactly_one->add_domain(1);
848  exactly_one->add_domain(1);
849  for (int i = 0; i < transition_values.size(); ++i) {
850  const int tuple_literal = context->NewBoolVar();
851  tuple_literals.push_back(tuple_literal);
852  exactly_one->add_vars(tuple_literal);
853  exactly_one->add_coeffs(1);
854  }
855  }
856 
857  // Fully encode vars[time].
858  {
859  std::vector<int64_t> s = transition_values;
861 
862  encoding.clear();
863  if (!context->IntersectDomainWith(vars[time], Domain::FromValues(s),
864  &removed_values)) {
865  return (void)context->NotifyThatModelIsUnsat();
866  }
867 
868  // Fully encode the variable.
869  for (const ClosedInterval& interval : context->DomainOf(vars[time])) {
870  for (int64_t v = interval.start; v <= interval.end; ++v) {
871  encoding[v] = context->GetOrCreateVarValueEncoding(vars[time], v);
872  }
873  }
874  }
875 
876  // For each possible out states, create one Boolean variable.
877  {
878  std::vector<int64_t> s = out_states;
880 
881  out_encoding.clear();
882  if (s.size() == 2) {
883  const int var = context->NewBoolVar();
884  out_encoding[s.front()] = var;
885  out_encoding[s.back()] = NegatedRef(var);
886  } else if (s.size() > 2) {
887  for (const int64_t state : s) {
888  out_encoding[state] = context->NewBoolVar();
889  }
890  }
891  }
892 
893  if (!in_encoding.empty()) {
894  LinkLiteralsAndValues(tuple_literals, in_states, in_encoding, context);
895  }
896  if (!encoding.empty()) {
897  LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
898  context);
899  }
900  if (!out_encoding.empty()) {
901  LinkLiteralsAndValues(tuple_literals, out_states, out_encoding, context);
902  }
903  in_encoding.swap(out_encoding);
904  out_encoding.clear();
905  }
906 
907  if (removed_values) {
908  context->UpdateRuleStats("automaton: reduced variable domains");
909  }
910  context->UpdateRuleStats("automaton: expanded");
911  ct->Clear();
912 }
913 
914 void ExpandNegativeTable(ConstraintProto* ct, PresolveContext* context) {
915  TableConstraintProto& table = *ct->mutable_table();
916  const int num_vars = table.vars_size();
917  const int num_original_tuples = table.values_size() / num_vars;
918  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
919  int count = 0;
920  for (int i = 0; i < num_original_tuples; ++i) {
921  for (int j = 0; j < num_vars; ++j) {
922  tuples[i].push_back(table.values(count++));
923  }
924  }
925 
926  if (tuples.empty()) { // Early exit.
927  context->UpdateRuleStats("table: empty negated constraint");
928  ct->Clear();
929  return;
930  }
931 
932  // Compress tuples.
933  const int64_t any_value = std::numeric_limits<int64_t>::min();
934  std::vector<int64_t> domain_sizes;
935  for (int i = 0; i < num_vars; ++i) {
936  domain_sizes.push_back(context->DomainOf(table.vars(i)).Size());
937  }
938  CompressTuples(domain_sizes, any_value, &tuples);
939 
940  // For each tuple, forbid the variables values to be this tuple.
941  std::vector<int> clause;
942  for (const std::vector<int64_t>& tuple : tuples) {
943  clause.clear();
944  for (int i = 0; i < num_vars; ++i) {
945  const int64_t value = tuple[i];
946  if (value == any_value) continue;
947 
948  const int literal =
949  context->GetOrCreateVarValueEncoding(table.vars(i), value);
950  clause.push_back(NegatedRef(literal));
951  }
952  if (!clause.empty()) {
953  BoolArgumentProto* bool_or =
954  context->working_model->add_constraints()->mutable_bool_or();
955  for (const int lit : clause) {
956  bool_or->add_literals(lit);
957  }
958  }
959  }
960  context->UpdateRuleStats("table: expanded negated constraint");
961  ct->Clear();
962 }
963 
964 void ExpandLinMin(ConstraintProto* ct, PresolveContext* context) {
965  ConstraintProto* const lin_max = context->working_model->add_constraints();
966  for (int i = 0; i < ct->enforcement_literal_size(); ++i) {
967  lin_max->add_enforcement_literal(ct->enforcement_literal(i));
968  }
969 
970  // Target
971  SetToNegatedLinearExpression(ct->lin_min().target(),
972  lin_max->mutable_lin_max()->mutable_target());
973 
974  for (int i = 0; i < ct->lin_min().exprs_size(); ++i) {
975  LinearExpressionProto* const expr = lin_max->mutable_lin_max()->add_exprs();
976  SetToNegatedLinearExpression(ct->lin_min().exprs(i), expr);
977  }
978  ct->Clear();
979 }
980 
981 // Add the implications and clauses to link one variable of a table to the
982 // literals controling if the tuples are possible or not. The parallel vectors
983 // (tuple_literals, values) contains all valid projected tuples. The
984 // tuples_with_any vector provides a list of tuple_literals 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  const std::vector<int>& tuples_with_any,
989  PresolveContext* context) {
990  VLOG(2) << "Process var(" << variable << ") with domain "
991  << context->DomainOf(variable) << " and " << values.size()
992  << " active tuples, and " << tuples_with_any.size() << " any tuples";
993  CHECK_EQ(tuple_literals.size(), values.size());
994  std::vector<std::pair<int64_t, int>> pairs;
995 
996  // Collect pairs of value-literal.
997  for (int i = 0; i < values.size(); ++i) {
998  const int64_t value = values[i];
999  CHECK(context->DomainContains(variable, value));
1000  pairs.emplace_back(value, tuple_literals[i]);
1001  }
1002 
1003  // Regroup literal with the same value and add for each the clause: If all the
1004  // tuples containing a value are false, then this value must be false too.
1005  std::vector<int> selected;
1006  std::sort(pairs.begin(), pairs.end());
1007  for (int i = 0; i < pairs.size();) {
1008  selected.clear();
1009  const int64_t value = pairs[i].first;
1010  for (; i < pairs.size() && pairs[i].first == value; ++i) {
1011  selected.push_back(pairs[i].second);
1012  }
1013 
1014  CHECK(!selected.empty() || !tuples_with_any.empty());
1015  if (selected.size() == 1 && tuples_with_any.empty()) {
1016  context->InsertVarValueEncoding(selected.front(), variable, value);
1017  } else {
1018  const int value_literal =
1019  context->GetOrCreateVarValueEncoding(variable, value);
1020  BoolArgumentProto* no_support =
1021  context->working_model->add_constraints()->mutable_bool_or();
1022  for (const int lit : selected) {
1023  no_support->add_literals(lit);
1024  context->AddImplication(lit, value_literal);
1025  }
1026  for (const int lit : tuples_with_any) {
1027  no_support->add_literals(lit);
1028  }
1029 
1030  // And the "value" literal.
1031  no_support->add_literals(NegatedRef(value_literal));
1032  }
1033  }
1034 }
1035 
1036 // Simpler encoding for table constraints with 2 variables.
1037 void AddSizeTwoTable(
1038  const std::vector<int>& vars,
1039  const std::vector<std::vector<int64_t>>& tuples,
1040  const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1041  PresolveContext* context) {
1042  CHECK_EQ(vars.size(), 2);
1043  const int left_var = vars[0];
1044  const int right_var = vars[1];
1045  if (context->DomainOf(left_var).Size() == 1 ||
1046  context->DomainOf(right_var).Size() == 1) {
1047  // A table constraint with at most one variable not fixed is trivially
1048  // enforced after domain reduction.
1049  return;
1050  }
1051 
1052  std::map<int, std::vector<int>> left_to_right;
1053  std::map<int, std::vector<int>> right_to_left;
1054 
1055  for (const auto& tuple : tuples) {
1056  const int64_t left_value(tuple[0]);
1057  const int64_t right_value(tuple[1]);
1058  CHECK(context->DomainContains(left_var, left_value));
1059  CHECK(context->DomainContains(right_var, right_value));
1060 
1061  const int left_literal =
1062  context->GetOrCreateVarValueEncoding(left_var, left_value);
1063  const int right_literal =
1064  context->GetOrCreateVarValueEncoding(right_var, right_value);
1065  left_to_right[left_literal].push_back(right_literal);
1066  right_to_left[right_literal].push_back(left_literal);
1067  }
1068 
1069  int num_implications = 0;
1070  int num_clause_added = 0;
1071  int num_large_clause_added = 0;
1072  auto add_support_constraint =
1073  [context, &num_clause_added, &num_large_clause_added, &num_implications](
1074  int lit, const std::vector<int>& support_literals,
1075  int max_support_size) {
1076  if (support_literals.size() == max_support_size) return;
1077  if (support_literals.size() == 1) {
1078  context->AddImplication(lit, support_literals.front());
1079  num_implications++;
1080  } else {
1081  BoolArgumentProto* bool_or =
1082  context->working_model->add_constraints()->mutable_bool_or();
1083  for (const int support_literal : support_literals) {
1084  bool_or->add_literals(support_literal);
1085  }
1086  bool_or->add_literals(NegatedRef(lit));
1087  num_clause_added++;
1088  if (support_literals.size() > max_support_size / 2) {
1089  num_large_clause_added++;
1090  }
1091  }
1092  };
1093 
1094  for (const auto& it : left_to_right) {
1095  add_support_constraint(it.first, it.second, values_per_var[1].size());
1096  }
1097  for (const auto& it : right_to_left) {
1098  add_support_constraint(it.first, it.second, values_per_var[0].size());
1099  }
1100  VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
1101  << num_clause_added << " clauses, including "
1102  << num_large_clause_added << " large clauses, " << num_implications
1103  << " implications";
1104 }
1105 
1106 void ExpandPositiveTable(ConstraintProto* ct, PresolveContext* context) {
1107  const TableConstraintProto& table = ct->table();
1108  const std::vector<int> vars(table.vars().begin(), table.vars().end());
1109  const int num_vars = table.vars_size();
1110  const int num_original_tuples = table.values_size() / num_vars;
1111 
1112  // Read tuples flat array and recreate the vector of tuples.
1113  std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1114  int count = 0;
1115  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1116  for (int var_index = 0; var_index < num_vars; ++var_index) {
1117  tuples[tuple_index].push_back(table.values(count++));
1118  }
1119  }
1120 
1121  // Compute the set of possible values for each variable (from the table).
1122  // Remove invalid tuples along the way.
1123  std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1124  int new_size = 0;
1125  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1126  bool keep = true;
1127  for (int var_index = 0; var_index < num_vars; ++var_index) {
1128  const int64_t value = tuples[tuple_index][var_index];
1129  if (!context->DomainContains(vars[var_index], value)) {
1130  keep = false;
1131  break;
1132  }
1133  }
1134  if (keep) {
1135  for (int var_index = 0; var_index < num_vars; ++var_index) {
1136  values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1137  }
1138  std::swap(tuples[tuple_index], tuples[new_size]);
1139  new_size++;
1140  }
1141  }
1142  tuples.resize(new_size);
1143  const int num_valid_tuples = tuples.size();
1144 
1145  if (tuples.empty()) {
1146  context->UpdateRuleStats("table: empty");
1147  return (void)context->NotifyThatModelIsUnsat();
1148  }
1149 
1150  // Update variable domains. It is redundant with presolve, but we could be
1151  // here with presolve = false.
1152  // Also counts the number of fixed variables.
1153  int num_fixed_variables = 0;
1154  for (int var_index = 0; var_index < num_vars; ++var_index) {
1155  CHECK(context->IntersectDomainWith(
1156  vars[var_index],
1157  Domain::FromValues({values_per_var[var_index].begin(),
1158  values_per_var[var_index].end()})));
1159  if (context->DomainOf(vars[var_index]).Size() == 1) {
1160  num_fixed_variables++;
1161  }
1162  }
1163 
1164  if (num_fixed_variables == num_vars - 1) {
1165  context->UpdateRuleStats("table: one variable not fixed");
1166  ct->Clear();
1167  return;
1168  } else if (num_fixed_variables == num_vars) {
1169  context->UpdateRuleStats("table: all variables fixed");
1170  ct->Clear();
1171  return;
1172  }
1173 
1174  // Tables with two variables do not need tuple literals.
1175  if (num_vars == 2) {
1176  AddSizeTwoTable(vars, tuples, values_per_var, context);
1177  context->UpdateRuleStats(
1178  "table: expanded positive constraint with two variables");
1179  ct->Clear();
1180  return;
1181  }
1182 
1183  // It is easier to compute this before compression, as compression will merge
1184  // tuples.
1185  int num_prefix_tuples = 0;
1186  {
1187  absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
1188  for (const std::vector<int64_t>& tuple : tuples) {
1189  prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1190  }
1191  num_prefix_tuples = prefixes.size();
1192  }
1193 
1194  // TODO(user): reinvestigate ExploreSubsetOfVariablesAndAddNegatedTables.
1195 
1196  // Compress tuples.
1197  const int64_t any_value = std::numeric_limits<int64_t>::min();
1198  std::vector<int64_t> domain_sizes;
1199  for (int i = 0; i < num_vars; ++i) {
1200  domain_sizes.push_back(values_per_var[i].size());
1201  }
1202  CompressTuples(domain_sizes, any_value, &tuples);
1203  const int num_compressed_tuples = tuples.size();
1204 
1205  if (num_compressed_tuples == 1) {
1206  // Domains are propagated. We can remove the constraint.
1207  context->UpdateRuleStats("table: one tuple");
1208  ct->Clear();
1209  return;
1210  }
1211 
1212  // Detect if prefix tuples are all different.
1213  const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1214  if (prefixes_are_all_different) {
1215  context->UpdateRuleStats(
1216  "TODO table: last value implied by previous values");
1217  }
1218  // TODO(user): if 2 table constraints share the same valid prefix, the
1219  // tuple literals can be reused.
1220  // TODO(user): investigate different encoding for prefix tables. Maybe
1221  // we can remove the need to create tuple literals.
1222 
1223  // Debug message to log the status of the expansion.
1224  if (VLOG_IS_ON(2)) {
1225  // Compute the maximum number of prefix tuples.
1226  int64_t max_num_prefix_tuples = 1;
1227  for (int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1228  max_num_prefix_tuples =
1229  CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1230  }
1231 
1232  std::string message =
1233  absl::StrCat("Table: ", num_vars,
1234  " variables, original tuples = ", num_original_tuples);
1235  if (num_valid_tuples != num_original_tuples) {
1236  absl::StrAppend(&message, ", valid tuples = ", num_valid_tuples);
1237  }
1238  if (prefixes_are_all_different) {
1239  if (num_prefix_tuples < max_num_prefix_tuples) {
1240  absl::StrAppend(&message, ", partial prefix = ", num_prefix_tuples, "/",
1241  max_num_prefix_tuples);
1242  } else {
1243  absl::StrAppend(&message, ", full prefix = true");
1244  }
1245  } else {
1246  absl::StrAppend(&message, ", num prefix tuples = ", num_prefix_tuples);
1247  }
1248  if (num_compressed_tuples != num_valid_tuples) {
1249  absl::StrAppend(&message,
1250  ", compressed tuples = ", num_compressed_tuples);
1251  }
1252  VLOG(2) << message;
1253  }
1254 
1255  // Log if we have only two tuples.
1256  if (num_compressed_tuples == 2) {
1257  context->UpdateRuleStats("TODO table: two tuples");
1258  }
1259 
1260  // Create one Boolean variable per tuple to indicate if it can still be
1261  // selected or not. Note that we don't enforce exactly one tuple to be
1262  // selected as this is costly.
1263  //
1264  // TODO(user): Investigate adding the at_most_one if the number of tuples
1265  // is small.
1266  // TODO(user): Investigate it we could recover a linear constraint:
1267  // var = sum(tuple_literals[i] * values[i])
1268  // It could be done here or along the deductions grouping.
1269  std::vector<int> tuple_literals(num_compressed_tuples);
1270  BoolArgumentProto* at_least_one_tuple =
1271  context->working_model->add_constraints()->mutable_bool_or();
1272 
1273  // If we want to enumerate all solutions, we should not add new variables that
1274  // can take more than one value for a given feasible solution, otherwise we
1275  // will have a lot more solution than needed.
1276  //
1277  // TODO(user): Alternatively, we could mark those variable so that their
1278  // value do not count when excluding solution, but we do not have a
1279  // mecanism for that yet. It might not be easy to track them down when we
1280  // replace them with equivalent variable too.
1281  //
1282  // TODO(user): We use keep_all_feasible_solutions as a proxy for enumerate
1283  // all solution, but the concept are slightly different though.
1284  BoolArgumentProto* at_most_one_tuple = nullptr;
1285  if (context->keep_all_feasible_solutions) {
1286  at_most_one_tuple =
1287  context->working_model->add_constraints()->mutable_at_most_one();
1288  }
1289 
1290  for (int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1291  tuple_literals[var_index] = context->NewBoolVar();
1292  at_least_one_tuple->add_literals(tuple_literals[var_index]);
1293  if (at_most_one_tuple != nullptr) {
1294  at_most_one_tuple->add_literals(tuple_literals[var_index]);
1295  }
1296  }
1297 
1298  std::vector<int> active_tuple_literals;
1299  std::vector<int64_t> active_values;
1300  std::vector<int> any_tuple_literals;
1301  for (int var_index = 0; var_index < num_vars; ++var_index) {
1302  if (values_per_var[var_index].size() == 1) continue;
1303 
1304  active_tuple_literals.clear();
1305  active_values.clear();
1306  any_tuple_literals.clear();
1307  for (int tuple_index = 0; tuple_index < tuple_literals.size();
1308  ++tuple_index) {
1309  const int64_t value = tuples[tuple_index][var_index];
1310  const int tuple_literal = tuple_literals[tuple_index];
1311 
1312  if (value == any_value) {
1313  any_tuple_literals.push_back(tuple_literal);
1314  } else {
1315  active_tuple_literals.push_back(tuple_literal);
1316  active_values.push_back(value);
1317  }
1318  }
1319 
1320  if (!active_tuple_literals.empty()) {
1321  ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1322  any_tuple_literals, context);
1323  }
1324  }
1325 
1326  context->UpdateRuleStats("table: expanded positive constraint");
1327  ct->Clear();
1328 }
1329 
1330 void ExpandAllDiff(bool expand_non_permutations, ConstraintProto* ct,
1331  PresolveContext* context) {
1332  AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
1333  if (proto.vars_size() <= 2) return;
1334 
1335  const int num_vars = proto.vars_size();
1336 
1337  Domain union_of_domains = context->DomainOf(proto.vars(0));
1338  for (int i = 1; i < num_vars; ++i) {
1339  union_of_domains =
1340  union_of_domains.UnionWith(context->DomainOf(proto.vars(i)));
1341  }
1342 
1343  const bool is_permutation = proto.vars_size() == union_of_domains.Size();
1344 
1345  if (!is_permutation && !expand_non_permutations) return;
1346 
1347  // Collect all possible variables that can take each value, and add one linear
1348  // equation per value stating that this value can be assigned at most once, or
1349  // exactly once in case of permutation.
1350  for (const ClosedInterval& interval : union_of_domains) {
1351  for (int64_t v = interval.start; v <= interval.end; ++v) {
1352  // Collect references which domain contains v.
1353  std::vector<int> possible_refs;
1354  int fixed_variable_count = 0;
1355  for (const int ref : proto.vars()) {
1356  if (!context->DomainContains(ref, v)) continue;
1357  possible_refs.push_back(ref);
1358  if (context->DomainOf(ref).Size() == 1) {
1359  fixed_variable_count++;
1360  }
1361  }
1362 
1363  if (fixed_variable_count > 1) {
1364  // Violates the definition of AllDifferent.
1365  return (void)context->NotifyThatModelIsUnsat();
1366  } else if (fixed_variable_count == 1) {
1367  // Remove values from other domains.
1368  for (const int ref : possible_refs) {
1369  if (context->DomainOf(ref).Size() == 1) continue;
1370  if (!context->IntersectDomainWith(ref, Domain(v).Complement())) {
1371  VLOG(1) << "Empty domain for a variable in ExpandAllDiff()";
1372  return;
1373  }
1374  }
1375  }
1376 
1377  LinearConstraintProto* at_most_or_equal_one =
1378  context->working_model->add_constraints()->mutable_linear();
1379  int lb = is_permutation ? 1 : 0;
1380  int ub = 1;
1381  for (const int ref : possible_refs) {
1382  DCHECK(context->DomainContains(ref, v));
1383  DCHECK_GT(context->DomainOf(ref).Size(), 1);
1384  const int encoding = context->GetOrCreateVarValueEncoding(ref, v);
1385  if (RefIsPositive(encoding)) {
1386  at_most_or_equal_one->add_vars(encoding);
1387  at_most_or_equal_one->add_coeffs(1);
1388  } else {
1389  at_most_or_equal_one->add_vars(PositiveRef(encoding));
1390  at_most_or_equal_one->add_coeffs(-1);
1391  lb--;
1392  ub--;
1393  }
1394  }
1395  at_most_or_equal_one->add_domain(lb);
1396  at_most_or_equal_one->add_domain(ub);
1397  }
1398  }
1399  if (is_permutation) {
1400  context->UpdateRuleStats("alldiff: permutation expanded");
1401  } else {
1402  context->UpdateRuleStats("alldiff: expanded");
1403  }
1404  ct->Clear();
1405 }
1406 
1407 } // namespace
1408 
1410  if (context->params().disable_constraint_expansion()) return;
1411 
1412  if (context->ModelIsUnsat()) return;
1413 
1414  // Make sure all domains are initialized.
1415  context->InitializeNewDomains();
1416 
1417  // Clear the precedence cache.
1418  context->ClearPrecedenceCache();
1419 
1420  const int num_constraints = context->working_model->constraints_size();
1421  for (int i = 0; i < num_constraints; ++i) {
1422  ConstraintProto* const ct = context->working_model->mutable_constraints(i);
1423  bool skip = false;
1424  switch (ct->constraint_case()) {
1425  case ConstraintProto::ConstraintCase::kReservoir:
1426  if (context->params().expand_reservoir_constraints()) {
1427  ExpandReservoir(ct, context);
1428  }
1429  break;
1430  case ConstraintProto::ConstraintCase::kIntMod:
1431  ExpandIntMod(ct, context);
1432  break;
1433  case ConstraintProto::ConstraintCase::kIntDiv:
1434  ExpandIntDiv(ct, context);
1435  break;
1436  case ConstraintProto::ConstraintCase::kIntProd:
1437  ExpandIntProd(ct, context);
1438  break;
1439  case ConstraintProto::ConstraintCase::kLinMin:
1440  ExpandLinMin(ct, context);
1441  break;
1442  case ConstraintProto::ConstraintCase::kElement:
1443  if (context->params().expand_element_constraints()) {
1444  ExpandElement(ct, context);
1445  }
1446  break;
1447  case ConstraintProto::ConstraintCase::kInverse:
1448  ExpandInverse(ct, context);
1449  break;
1450  case ConstraintProto::ConstraintCase::kAutomaton:
1451  if (context->params().expand_automaton_constraints()) {
1452  ExpandAutomaton(ct, context);
1453  }
1454  break;
1455  case ConstraintProto::ConstraintCase::kTable:
1456  if (ct->table().negated()) {
1457  ExpandNegativeTable(ct, context);
1458  } else if (context->params().expand_table_constraints()) {
1459  ExpandPositiveTable(ct, context);
1460  }
1461  break;
1462  case ConstraintProto::ConstraintCase::kAllDiff:
1463  ExpandAllDiff(context->params().expand_alldiff_constraints(), ct,
1464  context);
1465  break;
1466  default:
1467  skip = true;
1468  break;
1469  }
1470  if (skip) continue; // Nothing was done for this constraint.
1471 
1472  // Update variable-constraint graph.
1473  context->UpdateNewConstraintsVariableUsage();
1474  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1475  context->UpdateConstraintVariableUsage(i);
1476  }
1477 
1478  // Early exit if the model is unsat.
1479  if (context->ModelIsUnsat()) {
1480  SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
1482  return;
1483  }
1484  }
1485 
1486  // The precedence cache can become invalid during presolve as it does not
1487  // handle variable substitution. It is safer just to clear it at the end
1488  // of the expansion phase.
1489  context->ClearPrecedenceCache();
1490 
1491  // Make sure the context is consistent.
1492  context->InitializeNewDomains();
1493 
1494  // Update any changed domain from the context.
1495  for (int i = 0; i < context->working_model->variables_size(); ++i) {
1496  FillDomainInProto(context->DomainOf(i),
1497  context->working_model->mutable_variables(i));
1498  }
1499 }
1500 
1501 } // namespace sat
1502 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GE(val1, val2)
Definition: base/logging.h:709
#define DCHECK_GT(val1, val2)
Definition: base/logging.h:898
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:896
#define DCHECK(condition)
Definition: base/logging.h:892
#define VLOG(verboselevel)
Definition: base/logging.h:986
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
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
bool RefIsPositive(int ref)
void SetToNegatedLinearExpression(const LinearExpressionProto &input_expr, LinearExpressionProto *output_negated_expr)
void CompressTuples(absl::Span< const int64_t > domain_sizes, int64_t any_value, std::vector< std::vector< int64_t >> *tuples)
Definition: sat/util.cc:113
void ExpandCpModel(PresolveContext *context)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Collection of objects used to extend the Constraint Solver library.
int64_t CapSub(int64_t x, int64_t y)
std::string ProtobufShortDebugString(const P &message)
int64_t CapProd(int64_t x, int64_t y)
Literal literal
Definition: optimization.cc:85
int index
Definition: pack.cc:509
int64_t demand
Definition: resource.cc:125
int64_t time
Definition: resource.cc:1691
int64_t delta
Definition: resource.cc:1692
IntervalVar * interval
Definition: resource.cc:100
int64_t tail
int64_t head
std::string message
Definition: trace.cc:398
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41