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