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