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