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