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 ExpandIntProd(ConstraintProto* ct, PresolveContext* context) {
321  const IntegerArgumentProto& int_prod = ct->int_prod();
322  if (int_prod.vars_size() != 2) return;
323  const int a = int_prod.vars(0);
324  const int b = int_prod.vars(1);
325  const int p = int_prod.target();
326  const bool a_is_boolean =
327  RefIsPositive(a) && context->MinOf(a) == 0 && context->MaxOf(a) == 1;
328  const bool b_is_boolean =
329  RefIsPositive(b) && context->MinOf(b) == 0 && context->MaxOf(b) == 1;
330 
331  // We expand if exactly one of {a, b} is Boolean. If both are Boolean, it
332  // will be presolved into a better version.
333  if (a_is_boolean && !b_is_boolean) {
334  ExpandIntProdWithBoolean(a, b, p, context);
335  ct->Clear();
336  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
337  } else if (b_is_boolean && !a_is_boolean) {
338  ExpandIntProdWithBoolean(b, a, p, context);
339  ct->Clear();
340  context->UpdateRuleStats("int_prod: expanded product with Boolean var");
341  }
342 }
343 
344 void ExpandInverse(ConstraintProto* ct, PresolveContext* context) {
345  const int size = ct->inverse().f_direct().size();
346  CHECK_EQ(size, ct->inverse().f_inverse().size());
347 
348  // Make sure the domains are included in [0, size - 1).
349  //
350  // TODO(user): Add support for UNSAT at expansion. This should create empty
351  // domain if UNSAT, so it should still work correctly.
352  for (const int ref : ct->inverse().f_direct()) {
353  if (!context->IntersectDomainWith(ref, Domain(0, size - 1))) {
354  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
355  return;
356  }
357  }
358  for (const int ref : ct->inverse().f_inverse()) {
359  if (!context->IntersectDomainWith(ref, Domain(0, size - 1))) {
360  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
361  return;
362  }
363  }
364 
365  // Reduce the domains of each variable by checking that the inverse value
366  // exists.
367  std::vector<int64> possible_values;
368  // Propagate from one vector to its counterpart.
369  // Note this reaches the fixpoint as there is a one to one mapping between
370  // (variable-value) pairs in each vector.
371  const auto filter_inverse_domain = [context, size, &possible_values](
372  const google::protobuf::RepeatedField<int>& direct,
373  const google::protobuf::RepeatedField<int>& inverse) {
374  // Propagate for the inverse vector to the direct vector.
375  for (int i = 0; i < size; ++i) {
376  possible_values.clear();
377  const Domain domain = context->DomainOf(direct[i]);
378  bool removed_value = false;
379  for (const ClosedInterval& interval : domain) {
380  for (int64 j = interval.start; j <= interval.end; ++j) {
381  if (context->DomainOf(inverse[j]).Contains(i)) {
382  possible_values.push_back(j);
383  } else {
384  removed_value = true;
385  }
386  }
387  }
388  if (removed_value) {
389  if (!context->IntersectDomainWith(
390  direct[i], Domain::FromValues(possible_values))) {
391  VLOG(1) << "Empty domain for a variable in ExpandInverse()";
392  return false;
393  }
394  }
395  }
396  return true;
397  };
398 
399  if (!filter_inverse_domain(ct->inverse().f_direct(),
400  ct->inverse().f_inverse())) {
401  return;
402  }
403 
404  if (!filter_inverse_domain(ct->inverse().f_inverse(),
405  ct->inverse().f_direct())) {
406  return;
407  }
408 
409  // Expand the inverse constraint by associating literal to var == value
410  // and sharing them between the direct and inverse variables.
411  for (int i = 0; i < size; ++i) {
412  const int f_i = ct->inverse().f_direct(i);
413  const Domain domain = context->DomainOf(f_i);
414  for (const ClosedInterval& interval : domain) {
415  for (int64 j = interval.start; j <= interval.end; ++j) {
416  // We have f[i] == j <=> r[j] == i;
417  const int r_j = ct->inverse().f_inverse(j);
418  int r_j_i;
419  if (context->HasVarValueEncoding(r_j, i, &r_j_i)) {
420  context->InsertVarValueEncoding(r_j_i, f_i, j);
421  } else {
422  const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
423  context->InsertVarValueEncoding(f_i_j, r_j, i);
424  }
425  }
426  }
427  }
428 
429  ct->Clear();
430  context->UpdateRuleStats("inverse: expanded");
431 }
432 
433 void ExpandElement(ConstraintProto* ct, PresolveContext* context) {
434  const ElementConstraintProto& element = ct->element();
435  const int index_ref = element.index();
436  const int target_ref = element.target();
437  const int size = element.vars_size();
438 
439  if (!context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
440  VLOG(1) << "Empty domain for the index variable in ExpandElement()";
441  return (void)context->NotifyThatModelIsUnsat();
442  }
443 
444  bool all_constants = true;
445  absl::flat_hash_map<int64, int> constant_var_values_usage;
446  std::vector<int64> constant_var_values;
447  std::vector<int64> invalid_indices;
448  Domain index_domain = context->DomainOf(index_ref);
449  Domain target_domain = context->DomainOf(target_ref);
450  for (const ClosedInterval& interval : index_domain) {
451  for (int64 v = interval.start; v <= interval.end; ++v) {
452  const int var = element.vars(v);
453  const Domain var_domain = context->DomainOf(var);
454  if (var_domain.IntersectionWith(target_domain).IsEmpty()) {
455  invalid_indices.push_back(v);
456  continue;
457  }
458  if (var_domain.Min() != var_domain.Max()) {
459  all_constants = false;
460  break;
461  }
462 
463  const int64 value = var_domain.Min();
464  if (constant_var_values_usage[value]++ == 0) {
465  constant_var_values.push_back(value);
466  }
467  }
468  }
469 
470  if (!invalid_indices.empty() && target_ref != index_ref) {
471  if (!context->IntersectDomainWith(
472  index_ref, Domain::FromValues(invalid_indices).Complement())) {
473  VLOG(1) << "No compatible variable domains in ExpandElement()";
474  return (void)context->NotifyThatModelIsUnsat();
475  }
476 
477  // Re-read the domain.
478  index_domain = context->DomainOf(index_ref);
479  }
480 
481  // This BoolOrs implements the deduction that if all index literals pointing
482  // to the same values in the constant array are false, then this value is no
483  // no longer valid for the target variable. They are created only for values
484  // that have multiples literals supporting them.
485  // Order is not important.
486  absl::flat_hash_map<int64, BoolArgumentProto*> supports;
487  if (all_constants && target_ref != index_ref) {
488  if (!context->IntersectDomainWith(
489  target_ref, Domain::FromValues(constant_var_values))) {
490  VLOG(1) << "Empty domain for the target variable in ExpandElement()";
491  return;
492  }
493 
494  target_domain = context->DomainOf(target_ref);
495  if (target_domain.Size() == 1) {
496  context->UpdateRuleStats("element: one value array");
497  ct->Clear();
498  return;
499  }
500 
501  for (const ClosedInterval& interval : target_domain) {
502  for (int64 v = interval.start; v <= interval.end; ++v) {
503  const int usage = gtl::FindOrDie(constant_var_values_usage, v);
504  if (usage > 1) {
505  const int lit = context->GetOrCreateVarValueEncoding(target_ref, v);
506  BoolArgumentProto* const support =
507  context->working_model->add_constraints()->mutable_bool_or();
508  supports[v] = support;
509  support->add_literals(NegatedRef(lit));
510  }
511  }
512  }
513  }
514 
515  // While this is not stricly needed since all value in the index will be
516  // covered, it allows to easily detect this fact in the presolve.
517  auto* bool_or = context->working_model->add_constraints()->mutable_bool_or();
518 
519  for (const ClosedInterval& interval : index_domain) {
520  for (int64 v = interval.start; v <= interval.end; ++v) {
521  const int var = element.vars(v);
522  const int index_lit = context->GetOrCreateVarValueEncoding(index_ref, v);
523  const Domain var_domain = context->DomainOf(var);
524 
525  bool_or->add_literals(index_lit);
526 
527  if (target_ref == index_ref) {
528  // This adds extra code. But this information is really important,
529  // and hard to retrieve once lost.
530  context->AddImplyInDomain(index_lit, var, Domain(v));
531  } else if (target_domain.Size() == 1) {
532  // TODO(user): If we know all variables are different, then this
533  // becomes an equivalence.
534  context->AddImplyInDomain(index_lit, var, target_domain);
535  } else if (var_domain.Size() == 1) {
536  if (all_constants) {
537  const int64 value = var_domain.Min();
538  if (constant_var_values_usage[value] > 1) {
539  // The encoding literal for 'value' of the target_ref has been
540  // created before.
541  const int target_lit =
542  context->GetOrCreateVarValueEncoding(target_ref, value);
543  context->AddImplication(index_lit, target_lit);
544  gtl::FindOrDie(supports, value)->add_literals(index_lit);
545  } else {
546  // Try to reuse the literal of the index.
547  context->InsertVarValueEncoding(index_lit, target_ref, value);
548  }
549  } else {
550  context->AddImplyInDomain(index_lit, target_ref, var_domain);
551  }
552  } else {
553  ConstraintProto* const ct = context->working_model->add_constraints();
554  ct->add_enforcement_literal(index_lit);
555  ct->mutable_linear()->add_vars(var);
556  ct->mutable_linear()->add_coeffs(1);
557  ct->mutable_linear()->add_vars(target_ref);
558  ct->mutable_linear()->add_coeffs(-1);
559  ct->mutable_linear()->add_domain(0);
560  ct->mutable_linear()->add_domain(0);
561  }
562  }
563  }
564 
565  if (all_constants) {
566  const int64 var_min = target_domain.Min();
567 
568  // Scan all values to find the one with the most literals attached.
569  int64 most_frequent_value = kint64max;
570  int usage = -1;
571  for (const auto it : constant_var_values_usage) {
572  if (it.second > usage ||
573  (it.second == usage && it.first < most_frequent_value)) {
574  usage = it.second;
575  most_frequent_value = it.first;
576  }
577  }
578 
579  // Add a linear constraint. This helps the linear relaxation.
580  //
581  // We try to minimize the size of the linear constraint (if the gain is
582  // meaningful compared to using the min that has the advantage that all
583  // coefficients are positive).
584  // TODO(user): Benchmark if using base is always beneficial.
585  // TODO(user): Try not to create this if max_usage == 1.
586  const int64 base =
587  usage > 2 && usage > size / 10 ? most_frequent_value : var_min;
588  if (base != var_min) {
589  VLOG(3) << "expand element: choose " << base << " with usage " << usage
590  << " over " << var_min << " among " << size << " values.";
591  }
592 
593  LinearConstraintProto* const linear =
594  context->working_model->add_constraints()->mutable_linear();
595  int64 rhs = -base;
596  linear->add_vars(target_ref);
597  linear->add_coeffs(-1);
598  for (const ClosedInterval& interval : index_domain) {
599  for (int64 v = interval.start; v <= interval.end; ++v) {
600  const int ref = element.vars(v);
601  const int index_lit =
602  context->GetOrCreateVarValueEncoding(index_ref, v);
603  const int64 delta = context->DomainOf(ref).Min() - base;
604  if (RefIsPositive(index_lit)) {
605  linear->add_vars(index_lit);
606  linear->add_coeffs(delta);
607  } else {
608  linear->add_vars(NegatedRef(index_lit));
609  linear->add_coeffs(-delta);
610  rhs -= delta;
611  }
612  }
613  }
614  linear->add_domain(rhs);
615  linear->add_domain(rhs);
616 
617  context->UpdateRuleStats("element: expanded value element");
618  } else {
619  context->UpdateRuleStats("element: expanded");
620  }
621  ct->Clear();
622 }
623 
624 // Adds clauses so that literals[i] true <=> encoding[value[i]] true.
625 // This also implicitely use the fact that exactly one alternative is true.
626 void LinkLiteralsAndValues(
627  const std::vector<int>& value_literals, const std::vector<int64>& values,
628  const absl::flat_hash_map<int64, int>& target_encoding,
629  PresolveContext* context) {
630  CHECK_EQ(value_literals.size(), values.size());
631 
632  // TODO(user): Make sure this does not appear in the profile.
633  // We use a map to make this method deterministic.
634  std::map<int, std::vector<int>> value_literals_per_target_literal;
635 
636  // If a value is false (i.e not possible), then the tuple with this
637  // value is false too (i.e not possible). Conversely, if the tuple is
638  // selected, the value must be selected.
639  for (int i = 0; i < values.size(); ++i) {
640  const int64 v = values[i];
641  CHECK(target_encoding.contains(v));
642  const int lit = gtl::FindOrDie(target_encoding, v);
643  value_literals_per_target_literal[lit].push_back(value_literals[i]);
644  }
645 
646  // If all tuples supporting a value are false, then this value must be
647  // false.
648  for (const auto& it : value_literals_per_target_literal) {
649  const int target_literal = it.first;
650  switch (it.second.size()) {
651  case 0: {
652  if (!context->SetLiteralToFalse(target_literal)) {
653  return;
654  }
655  break;
656  }
657  case 1: {
658  context->StoreBooleanEqualityRelation(target_literal,
659  it.second.front());
660  break;
661  }
662  default: {
663  BoolArgumentProto* const bool_or =
664  context->working_model->add_constraints()->mutable_bool_or();
665  bool_or->add_literals(NegatedRef(target_literal));
666  for (const int value_literal : it.second) {
667  bool_or->add_literals(value_literal);
668  context->AddImplication(value_literal, target_literal);
669  }
670  }
671  }
672  }
673 }
674 
675 void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
676  AutomatonConstraintProto& proto = *ct->mutable_automaton();
677 
678  if (proto.vars_size() == 0) {
679  const int64 initial_state = proto.starting_state();
680  for (const int64 final_state : proto.final_states()) {
681  if (initial_state == final_state) {
682  context->UpdateRuleStats("automaton: empty constraint");
683  ct->Clear();
684  return;
685  }
686  }
687  // The initial state is not in the final state. The model is unsat.
688  return (void)context->NotifyThatModelIsUnsat();
689  } else if (proto.transition_label_size() == 0) {
690  // Not transitions. The constraint is infeasible.
691  return (void)context->NotifyThatModelIsUnsat();
692  }
693 
694  const int n = proto.vars_size();
695  const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
696 
697  // Compute the set of reachable state at each time point.
698  const absl::flat_hash_set<int64> final_states(
699  {proto.final_states().begin(), proto.final_states().end()});
700  std::vector<absl::flat_hash_set<int64>> reachable_states(n + 1);
701  reachable_states[0].insert(proto.starting_state());
702 
703  // Forward pass.
704  for (int time = 0; time < n; ++time) {
705  for (int t = 0; t < proto.transition_tail_size(); ++t) {
706  const int64 tail = proto.transition_tail(t);
707  const int64 label = proto.transition_label(t);
708  const int64 head = proto.transition_head(t);
709  if (!reachable_states[time].contains(tail)) continue;
710  if (!context->DomainContains(vars[time], label)) continue;
711  if (time == n - 1 && !final_states.contains(head)) continue;
712  reachable_states[time + 1].insert(head);
713  }
714  }
715 
716  // Backward pass.
717  for (int time = n - 1; time >= 0; --time) {
718  absl::flat_hash_set<int64> new_set;
719  for (int t = 0; t < proto.transition_tail_size(); ++t) {
720  const int64 tail = proto.transition_tail(t);
721  const int64 label = proto.transition_label(t);
722  const int64 head = proto.transition_head(t);
723 
724  if (!reachable_states[time].contains(tail)) continue;
725  if (!context->DomainContains(vars[time], label)) continue;
726  if (!reachable_states[time + 1].contains(head)) continue;
727  new_set.insert(tail);
728  }
729  reachable_states[time].swap(new_set);
730  }
731 
732  // We will model at each time step the current automaton state using Boolean
733  // variables. We will have n+1 time step. At time zero, we start in the
734  // initial state, and at time n we should be in one of the final states. We
735  // don't need to create Booleans at at time when there is just one possible
736  // state (like at time zero).
737  absl::flat_hash_map<int64, int> encoding;
738  absl::flat_hash_map<int64, int> in_encoding;
739  absl::flat_hash_map<int64, int> out_encoding;
740  bool removed_values = false;
741 
742  for (int time = 0; time < n; ++time) {
743  // All these vector have the same size. We will use them to enforce a
744  // local table constraint representing one step of the automaton at the
745  // given time.
746  std::vector<int64> in_states;
747  std::vector<int64> transition_values;
748  std::vector<int64> out_states;
749  for (int i = 0; i < proto.transition_label_size(); ++i) {
750  const int64 tail = proto.transition_tail(i);
751  const int64 label = proto.transition_label(i);
752  const int64 head = proto.transition_head(i);
753 
754  if (!reachable_states[time].contains(tail)) continue;
755  if (!reachable_states[time + 1].contains(head)) continue;
756  if (!context->DomainContains(vars[time], label)) continue;
757 
758  // TODO(user): if this transition correspond to just one in-state or
759  // one-out state or one variable value, we could reuse the corresponding
760  // Boolean variable instead of creating a new one!
761  in_states.push_back(tail);
762  transition_values.push_back(label);
763 
764  // On the last step we don't need to distinguish the output states, so
765  // we use zero.
766  out_states.push_back(time + 1 == n ? 0 : head);
767  }
768 
769  std::vector<int> tuple_literals;
770  if (transition_values.size() == 1) {
771  bool tmp_removed_values = false;
772  tuple_literals.push_back(context->GetOrCreateConstantVar(1));
773  CHECK_EQ(reachable_states[time + 1].size(), 1);
774  if (!context->IntersectDomainWith(vars[time],
775  Domain(transition_values.front()),
776  &tmp_removed_values)) {
777  return (void)context->NotifyThatModelIsUnsat();
778  }
779  in_encoding.clear();
780  continue;
781  } else if (transition_values.size() == 2) {
782  const int bool_var = context->NewBoolVar();
783  tuple_literals.push_back(bool_var);
784  tuple_literals.push_back(NegatedRef(bool_var));
785  } else {
786  // Note that we do not need the ExactlyOneConstraint(tuple_literals)
787  // because it is already implicitely encoded since we have exactly one
788  // transition value.
789  LinearConstraintProto* const exactly_one =
790  context->working_model->add_constraints()->mutable_linear();
791  exactly_one->add_domain(1);
792  exactly_one->add_domain(1);
793  for (int i = 0; i < transition_values.size(); ++i) {
794  const int tuple_literal = context->NewBoolVar();
795  tuple_literals.push_back(tuple_literal);
796  exactly_one->add_vars(tuple_literal);
797  exactly_one->add_coeffs(1);
798  }
799  }
800 
801  // Fully encode vars[time].
802  {
803  std::vector<int64> s = transition_values;
805 
806  encoding.clear();
807  if (!context->IntersectDomainWith(vars[time], Domain::FromValues(s),
808  &removed_values)) {
809  return (void)context->NotifyThatModelIsUnsat();
810  }
811 
812  // Fully encode the variable.
813  for (const ClosedInterval& interval : context->DomainOf(vars[time])) {
814  for (int64 v = interval.start; v <= interval.end; ++v) {
815  encoding[v] = context->GetOrCreateVarValueEncoding(vars[time], v);
816  }
817  }
818  }
819 
820  // For each possible out states, create one Boolean variable.
821  {
822  std::vector<int64> s = out_states;
824 
825  out_encoding.clear();
826  if (s.size() == 2) {
827  const int var = context->NewBoolVar();
828  out_encoding[s.front()] = var;
829  out_encoding[s.back()] = NegatedRef(var);
830  } else if (s.size() > 2) {
831  for (const int64 state : s) {
832  out_encoding[state] = context->NewBoolVar();
833  }
834  }
835  }
836 
837  if (!in_encoding.empty()) {
838  LinkLiteralsAndValues(tuple_literals, in_states, in_encoding, context);
839  }
840  if (!encoding.empty()) {
841  LinkLiteralsAndValues(tuple_literals, transition_values, encoding,
842  context);
843  }
844  if (!out_encoding.empty()) {
845  LinkLiteralsAndValues(tuple_literals, out_states, out_encoding, context);
846  }
847  in_encoding.swap(out_encoding);
848  out_encoding.clear();
849  }
850 
851  if (removed_values) {
852  context->UpdateRuleStats("automaton: reduced variable domains");
853  }
854  context->UpdateRuleStats("automaton: expanded");
855  ct->Clear();
856 }
857 
858 void ExpandNegativeTable(ConstraintProto* ct, PresolveContext* context) {
859  TableConstraintProto& table = *ct->mutable_table();
860  const int num_vars = table.vars_size();
861  const int num_original_tuples = table.values_size() / num_vars;
862  std::vector<std::vector<int64>> tuples(num_original_tuples);
863  int count = 0;
864  for (int i = 0; i < num_original_tuples; ++i) {
865  for (int j = 0; j < num_vars; ++j) {
866  tuples[i].push_back(table.values(count++));
867  }
868  }
869 
870  if (tuples.empty()) { // Early exit.
871  context->UpdateRuleStats("table: empty negated constraint");
872  ct->Clear();
873  return;
874  }
875 
876  // Compress tuples.
877  const int64 any_value = kint64min;
878  std::vector<int64> domain_sizes;
879  for (int i = 0; i < num_vars; ++i) {
880  domain_sizes.push_back(context->DomainOf(table.vars(i)).Size());
881  }
882  CompressTuples(domain_sizes, any_value, &tuples);
883 
884  // For each tuple, forbid the variables values to be this tuple.
885  std::vector<int> clause;
886  for (const std::vector<int64>& tuple : tuples) {
887  clause.clear();
888  for (int i = 0; i < num_vars; ++i) {
889  const int64 value = tuple[i];
890  if (value == any_value) continue;
891 
892  const int literal =
893  context->GetOrCreateVarValueEncoding(table.vars(i), value);
894  clause.push_back(NegatedRef(literal));
895  }
896  if (!clause.empty()) {
897  BoolArgumentProto* bool_or =
898  context->working_model->add_constraints()->mutable_bool_or();
899  for (const int lit : clause) {
900  bool_or->add_literals(lit);
901  }
902  }
903  }
904  context->UpdateRuleStats("table: expanded negated constraint");
905  ct->Clear();
906 }
907 
908 void ExpandLinMin(ConstraintProto* ct, PresolveContext* context) {
909  ConstraintProto* const lin_max = context->working_model->add_constraints();
910  for (int i = 0; i < ct->enforcement_literal_size(); ++i) {
911  lin_max->add_enforcement_literal(ct->enforcement_literal(i));
912  }
913 
914  // Target
915  SetToNegatedLinearExpression(ct->lin_min().target(),
916  lin_max->mutable_lin_max()->mutable_target());
917 
918  for (int i = 0; i < ct->lin_min().exprs_size(); ++i) {
919  LinearExpressionProto* const expr = lin_max->mutable_lin_max()->add_exprs();
920  SetToNegatedLinearExpression(ct->lin_min().exprs(i), expr);
921  }
922  ct->Clear();
923 }
924 
925 // Add the implications and clauses to link one variable of a table to the
926 // literals controling if the tuples are possible or not. The parallel vectors
927 // (tuple_literals, values) contains all valid projected tuples. The
928 // tuples_with_any vector provides a list of tuple_literals that will support
929 // any value.
930 void ProcessOneVariable(const std::vector<int>& tuple_literals,
931  const std::vector<int64>& values, int variable,
932  const std::vector<int>& tuples_with_any,
933  PresolveContext* context) {
934  VLOG(2) << "Process var(" << variable << ") with domain "
935  << context->DomainOf(variable) << " and " << values.size()
936  << " active tuples, and " << tuples_with_any.size() << " any tuples";
937  CHECK_EQ(tuple_literals.size(), values.size());
938  std::vector<std::pair<int64, int>> pairs;
939 
940  // Collect pairs of value-literal.
941  for (int i = 0; i < values.size(); ++i) {
942  const int64 value = values[i];
943  CHECK(context->DomainContains(variable, value));
944  pairs.emplace_back(value, tuple_literals[i]);
945  }
946 
947  // Regroup literal with the same value and add for each the clause: If all the
948  // tuples containing a value are false, then this value must be false too.
949  std::vector<int> selected;
950  std::sort(pairs.begin(), pairs.end());
951  for (int i = 0; i < pairs.size();) {
952  selected.clear();
953  const int64 value = pairs[i].first;
954  for (; i < pairs.size() && pairs[i].first == value; ++i) {
955  selected.push_back(pairs[i].second);
956  }
957 
958  CHECK(!selected.empty() || !tuples_with_any.empty());
959  if (selected.size() == 1 && tuples_with_any.empty()) {
960  context->InsertVarValueEncoding(selected.front(), variable, value);
961  } else {
962  const int value_literal =
963  context->GetOrCreateVarValueEncoding(variable, value);
964  BoolArgumentProto* no_support =
965  context->working_model->add_constraints()->mutable_bool_or();
966  for (const int lit : selected) {
967  no_support->add_literals(lit);
968  context->AddImplication(lit, value_literal);
969  }
970  for (const int lit : tuples_with_any) {
971  no_support->add_literals(lit);
972  }
973 
974  // And the "value" literal.
975  no_support->add_literals(NegatedRef(value_literal));
976  }
977  }
978 }
979 
980 // Simpler encoding for table constraints with 2 variables.
981 void AddSizeTwoTable(
982  const std::vector<int>& vars, const std::vector<std::vector<int64>>& tuples,
983  const std::vector<absl::flat_hash_set<int64>>& values_per_var,
984  PresolveContext* context) {
985  CHECK_EQ(vars.size(), 2);
986  const int left_var = vars[0];
987  const int right_var = vars[1];
988  if (context->DomainOf(left_var).Size() == 1 ||
989  context->DomainOf(right_var).Size() == 1) {
990  // A table constraint with at most one variable not fixed is trivially
991  // enforced after domain reduction.
992  return;
993  }
994 
995  std::map<int, std::vector<int>> left_to_right;
996  std::map<int, std::vector<int>> right_to_left;
997 
998  for (const auto& tuple : tuples) {
999  const int64 left_value(tuple[0]);
1000  const int64 right_value(tuple[1]);
1001  CHECK(context->DomainContains(left_var, left_value));
1002  CHECK(context->DomainContains(right_var, right_value));
1003 
1004  const int left_literal =
1005  context->GetOrCreateVarValueEncoding(left_var, left_value);
1006  const int right_literal =
1007  context->GetOrCreateVarValueEncoding(right_var, right_value);
1008  left_to_right[left_literal].push_back(right_literal);
1009  right_to_left[right_literal].push_back(left_literal);
1010  }
1011 
1012  int num_implications = 0;
1013  int num_clause_added = 0;
1014  int num_large_clause_added = 0;
1015  auto add_support_constraint =
1016  [context, &num_clause_added, &num_large_clause_added, &num_implications](
1017  int lit, const std::vector<int>& support_literals,
1018  int max_support_size) {
1019  if (support_literals.size() == max_support_size) return;
1020  if (support_literals.size() == 1) {
1021  context->AddImplication(lit, support_literals.front());
1022  num_implications++;
1023  } else {
1024  BoolArgumentProto* bool_or =
1025  context->working_model->add_constraints()->mutable_bool_or();
1026  for (const int support_literal : support_literals) {
1027  bool_or->add_literals(support_literal);
1028  }
1029  bool_or->add_literals(NegatedRef(lit));
1030  num_clause_added++;
1031  if (support_literals.size() > max_support_size / 2) {
1032  num_large_clause_added++;
1033  }
1034  }
1035  };
1036 
1037  for (const auto& it : left_to_right) {
1038  add_support_constraint(it.first, it.second, values_per_var[1].size());
1039  }
1040  for (const auto& it : right_to_left) {
1041  add_support_constraint(it.first, it.second, values_per_var[0].size());
1042  }
1043  VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
1044  << num_clause_added << " clauses, including "
1045  << num_large_clause_added << " large clauses, " << num_implications
1046  << " implications";
1047 }
1048 
1049 void ExpandPositiveTable(ConstraintProto* ct, PresolveContext* context) {
1050  const TableConstraintProto& table = ct->table();
1051  const std::vector<int> vars(table.vars().begin(), table.vars().end());
1052  const int num_vars = table.vars_size();
1053  const int num_original_tuples = table.values_size() / num_vars;
1054 
1055  // Read tuples flat array and recreate the vector of tuples.
1056  std::vector<std::vector<int64>> tuples(num_original_tuples);
1057  int count = 0;
1058  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1059  for (int var_index = 0; var_index < num_vars; ++var_index) {
1060  tuples[tuple_index].push_back(table.values(count++));
1061  }
1062  }
1063 
1064  // Compute the set of possible values for each variable (from the table).
1065  // Remove invalid tuples along the way.
1066  std::vector<absl::flat_hash_set<int64>> values_per_var(num_vars);
1067  int new_size = 0;
1068  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1069  bool keep = true;
1070  for (int var_index = 0; var_index < num_vars; ++var_index) {
1071  const int64 value = tuples[tuple_index][var_index];
1072  if (!context->DomainContains(vars[var_index], value)) {
1073  keep = false;
1074  break;
1075  }
1076  }
1077  if (keep) {
1078  for (int var_index = 0; var_index < num_vars; ++var_index) {
1079  values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1080  }
1081  std::swap(tuples[tuple_index], tuples[new_size]);
1082  new_size++;
1083  }
1084  }
1085  tuples.resize(new_size);
1086  const int num_valid_tuples = tuples.size();
1087 
1088  if (tuples.empty()) {
1089  context->UpdateRuleStats("table: empty");
1090  return (void)context->NotifyThatModelIsUnsat();
1091  }
1092 
1093  // Update variable domains. It is redundant with presolve, but we could be
1094  // here with presolve = false.
1095  // Also counts the number of fixed variables.
1096  int num_fixed_variables = 0;
1097  for (int var_index = 0; var_index < num_vars; ++var_index) {
1098  CHECK(context->IntersectDomainWith(
1099  vars[var_index],
1100  Domain::FromValues({values_per_var[var_index].begin(),
1101  values_per_var[var_index].end()})));
1102  if (context->DomainOf(vars[var_index]).Size() == 1) {
1103  num_fixed_variables++;
1104  }
1105  }
1106 
1107  if (num_fixed_variables == num_vars - 1) {
1108  context->UpdateRuleStats("table: one variable not fixed");
1109  ct->Clear();
1110  return;
1111  } else if (num_fixed_variables == num_vars) {
1112  context->UpdateRuleStats("table: all variables fixed");
1113  ct->Clear();
1114  return;
1115  }
1116 
1117  // Tables with two variables do not need tuple literals.
1118  if (num_vars == 2) {
1119  AddSizeTwoTable(vars, tuples, values_per_var, context);
1120  context->UpdateRuleStats(
1121  "table: expanded positive constraint with two variables");
1122  ct->Clear();
1123  return;
1124  }
1125 
1126  // It is easier to compute this before compression, as compression will merge
1127  // tuples.
1128  int num_prefix_tuples = 0;
1129  {
1130  absl::flat_hash_set<absl::Span<const int64>> prefixes;
1131  for (const std::vector<int64>& tuple : tuples) {
1132  prefixes.insert(absl::MakeSpan(tuple.data(), num_vars - 1));
1133  }
1134  num_prefix_tuples = prefixes.size();
1135  }
1136 
1137  // TODO(user): reinvestigate ExploreSubsetOfVariablesAndAddNegatedTables.
1138 
1139  // Compress tuples.
1140  const int64 any_value = kint64min;
1141  std::vector<int64> domain_sizes;
1142  for (int i = 0; i < num_vars; ++i) {
1143  domain_sizes.push_back(values_per_var[i].size());
1144  }
1145  CompressTuples(domain_sizes, any_value, &tuples);
1146  const int num_compressed_tuples = tuples.size();
1147 
1148  if (num_compressed_tuples == 1) {
1149  // Domains are propagated. We can remove the constraint.
1150  context->UpdateRuleStats("table: one tuple");
1151  ct->Clear();
1152  return;
1153  }
1154 
1155  // Detect if prefix tuples are all different.
1156  const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
1157  if (prefixes_are_all_different) {
1158  context->UpdateRuleStats(
1159  "TODO table: last value implied by previous values");
1160  }
1161  // TODO(user): if 2 table constraints share the same valid prefix, the
1162  // tuple literals can be reused.
1163  // TODO(user): investigate different encoding for prefix tables. Maybe
1164  // we can remove the need to create tuple literals.
1165 
1166  // Debug message to log the status of the expansion.
1167  if (VLOG_IS_ON(2)) {
1168  // Compute the maximum number of prefix tuples.
1169  int64 max_num_prefix_tuples = 1;
1170  for (int var_index = 0; var_index + 1 < num_vars; ++var_index) {
1171  max_num_prefix_tuples =
1172  CapProd(max_num_prefix_tuples, values_per_var[var_index].size());
1173  }
1174 
1175  std::string message =
1176  absl::StrCat("Table: ", num_vars,
1177  " variables, original tuples = ", num_original_tuples);
1178  if (num_valid_tuples != num_original_tuples) {
1179  absl::StrAppend(&message, ", valid tuples = ", num_valid_tuples);
1180  }
1181  if (prefixes_are_all_different) {
1182  if (num_prefix_tuples < max_num_prefix_tuples) {
1183  absl::StrAppend(&message, ", partial prefix = ", num_prefix_tuples, "/",
1184  max_num_prefix_tuples);
1185  } else {
1186  absl::StrAppend(&message, ", full prefix = true");
1187  }
1188  } else {
1189  absl::StrAppend(&message, ", num prefix tuples = ", num_prefix_tuples);
1190  }
1191  if (num_compressed_tuples != num_valid_tuples) {
1192  absl::StrAppend(&message,
1193  ", compressed tuples = ", num_compressed_tuples);
1194  }
1195  VLOG(2) << message;
1196  }
1197 
1198  // Log if we have only two tuples.
1199  if (num_compressed_tuples == 2) {
1200  context->UpdateRuleStats("TODO table: two tuples");
1201  }
1202 
1203  // Create one Boolean variable per tuple to indicate if it can still be
1204  // selected or not. Note that we don't enforce exactly one tuple to be
1205  // selected as this is costly.
1206  //
1207  // TODO(user): Investigate adding the at_most_one if the number of tuples
1208  // is small.
1209  // TODO(user): Investigate it we could recover a linear constraint:
1210  // var = sum(tuple_literals[i] * values[i])
1211  // It could be done here or along the deductions grouping.
1212  std::vector<int> tuple_literals(num_compressed_tuples);
1213  BoolArgumentProto* at_least_one_tuple =
1214  context->working_model->add_constraints()->mutable_bool_or();
1215 
1216  // If we want to enumerate all solutions, we should not add new variables that
1217  // can take more than one value for a given feasible solution, otherwise we
1218  // will have a lot more solution than needed.
1219  //
1220  // TODO(user): Alternatively, we could mark those variable so that their
1221  // value do not count when excluding solution, but we do not have a
1222  // mecanism for that yet. It might not be easy to track them down when we
1223  // replace them with equivalent variable too.
1224  //
1225  // TODO(user): We use keep_all_feasible_solutions as a proxy for enumerate
1226  // all solution, but the concept are slightly different though.
1227  BoolArgumentProto* at_most_one_tuple = nullptr;
1228  if (context->keep_all_feasible_solutions) {
1229  at_most_one_tuple =
1230  context->working_model->add_constraints()->mutable_at_most_one();
1231  }
1232 
1233  for (int var_index = 0; var_index < num_compressed_tuples; ++var_index) {
1234  tuple_literals[var_index] = context->NewBoolVar();
1235  at_least_one_tuple->add_literals(tuple_literals[var_index]);
1236  if (at_most_one_tuple != nullptr) {
1237  at_most_one_tuple->add_literals(tuple_literals[var_index]);
1238  }
1239  }
1240 
1241  std::vector<int> active_tuple_literals;
1242  std::vector<int64> active_values;
1243  std::vector<int> any_tuple_literals;
1244  for (int var_index = 0; var_index < num_vars; ++var_index) {
1245  if (values_per_var[var_index].size() == 1) continue;
1246 
1247  active_tuple_literals.clear();
1248  active_values.clear();
1249  any_tuple_literals.clear();
1250  for (int tuple_index = 0; tuple_index < tuple_literals.size();
1251  ++tuple_index) {
1252  const int64 value = tuples[tuple_index][var_index];
1253  const int tuple_literal = tuple_literals[tuple_index];
1254 
1255  if (value == any_value) {
1256  any_tuple_literals.push_back(tuple_literal);
1257  } else {
1258  active_tuple_literals.push_back(tuple_literal);
1259  active_values.push_back(value);
1260  }
1261  }
1262 
1263  if (!active_tuple_literals.empty()) {
1264  ProcessOneVariable(active_tuple_literals, active_values, vars[var_index],
1265  any_tuple_literals, context);
1266  }
1267  }
1268 
1269  context->UpdateRuleStats("table: expanded positive constraint");
1270  ct->Clear();
1271 }
1272 } // namespace
1273 
1275  if (context->ModelIsUnsat()) return;
1276 
1277  // Make sure all domains are initialized.
1278  context->InitializeNewDomains();
1279 
1280  const int num_constraints = context->working_model->constraints_size();
1281  for (int i = 0; i < num_constraints; ++i) {
1282  ConstraintProto* const ct = context->working_model->mutable_constraints(i);
1283  bool skip = false;
1284  switch (ct->constraint_case()) {
1285  case ConstraintProto::ConstraintCase::kReservoir:
1286  ExpandReservoir(ct, context);
1287  break;
1288  case ConstraintProto::ConstraintCase::kIntMod:
1289  ExpandIntMod(ct, context);
1290  break;
1291  case ConstraintProto::ConstraintCase::kIntProd:
1292  ExpandIntProd(ct, context);
1293  break;
1294  case ConstraintProto::ConstraintCase::kLinMin:
1295  ExpandLinMin(ct, context);
1296  break;
1297  case ConstraintProto::ConstraintCase::kElement:
1298  if (options.parameters.expand_element_constraints()) {
1299  ExpandElement(ct, context);
1300  }
1301  break;
1302  case ConstraintProto::ConstraintCase::kInverse:
1303  ExpandInverse(ct, context);
1304  break;
1305  case ConstraintProto::ConstraintCase::kAutomaton:
1306  if (options.parameters.expand_automaton_constraints()) {
1307  ExpandAutomaton(ct, context);
1308  }
1309  break;
1310  case ConstraintProto::ConstraintCase::kTable:
1311  if (ct->table().negated()) {
1312  ExpandNegativeTable(ct, context);
1313  } else if (options.parameters.expand_table_constraints()) {
1314  ExpandPositiveTable(ct, context);
1315  }
1316  break;
1317  default:
1318  skip = true;
1319  break;
1320  }
1321  if (skip) continue; // Nothing was done for this constraint.
1322 
1323  // Update variable-contraint graph.
1324  context->UpdateNewConstraintsVariableUsage();
1325  if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
1326  context->UpdateConstraintVariableUsage(i);
1327  }
1328 
1329  // Early exit if the model is unsat.
1330  if (context->ModelIsUnsat()) return;
1331  }
1332 
1333  // Make sure the context is consistent.
1334  context->InitializeNewDomains();
1335 
1336  // Update any changed domain from the context.
1337  for (int i = 0; i < context->working_model->variables_size(); ++i) {
1338  FillDomainInProto(context->DomainOf(i),
1339  context->working_model->mutable_variables(i));
1340  }
1341 }
1342 
1343 } // namespace sat
1344 } // 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:1274
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
operations_research::sat::PresolveOptions
Definition: presolve_context.h:37
presolve_context.h
message
std::string message
Definition: trace.cc:396
value
int64 value
Definition: demon_profiler.cc:43
operations_research::sat::PresolveOptions::parameters
SatParameters parameters
Definition: presolve_context.h:39
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
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
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
a
int64 a
Definition: constraint_solver/table.cc:42
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
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::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::Domain::Complement
Domain Complement() const
Returns the set Int64 ∖ D.
Definition: sorted_interval_list.cc:245
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
operations_research::sat::PresolveContext
Definition: presolve_context.h:72
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