OR-Tools  9.0
sat/table.cc
Go to the documentation of this file.
1 // Copyright 2010-2021 Google LLC
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13 
14 #include "ortools/sat/table.h"
15 
16 #include <algorithm>
17 #include <cstdint>
18 #include <limits>
19 #include <memory>
20 #include <set>
21 #include <utility>
22 
23 #include "absl/container/flat_hash_map.h"
24 #include "absl/container/flat_hash_set.h"
25 #include "absl/strings/str_cat.h"
26 #include "absl/strings/str_join.h"
27 #include "ortools/base/int_type.h"
28 #include "ortools/base/logging.h"
29 #include "ortools/base/map_util.h"
30 #include "ortools/base/stl_util.h"
31 #include "ortools/sat/sat_base.h"
32 #include "ortools/sat/sat_solver.h"
33 #include "ortools/sat/util.h"
35 
36 namespace operations_research {
37 namespace sat {
38 
39 namespace {
40 
41 // Converts the vector representation returned by FullDomainEncoding() to a map.
42 absl::flat_hash_map<IntegerValue, Literal> GetEncoding(IntegerVariable var,
43  Model* model) {
44  absl::flat_hash_map<IntegerValue, Literal> encoding;
45  IntegerEncoder* encoder = model->GetOrCreate<IntegerEncoder>();
46  for (const auto& entry : encoder->FullDomainEncoding(var)) {
47  encoding[entry.value] = entry.literal;
48  }
49  return encoding;
50 }
51 
52 // Add the implications and clauses to link one column of a table to the Literal
53 // controling if the lines are possible or not. The column has the given values,
54 // and the Literal of the column variable can be retrieved using the encoding
55 // map. Thew tuples_with_any vector provides a list of line_literals that will
56 // support any value.
57 void ProcessOneColumn(
58  const std::vector<Literal>& line_literals,
59  const std::vector<IntegerValue>& values,
60  const absl::flat_hash_map<IntegerValue, Literal>& encoding,
61  const std::vector<Literal>& tuples_with_any, Model* model) {
62  CHECK_EQ(line_literals.size(), values.size());
63  std::vector<std::pair<IntegerValue, Literal>> pairs;
64 
65  // If a value is false (i.e not possible), then the tuple with this value
66  // is false too (i.e not possible). Conversely, if the tuple is selected,
67  // the value must be selected.
68  for (int i = 0; i < values.size(); ++i) {
69  const IntegerValue v = values[i];
70  if (!encoding.contains(v)) {
71  model->Add(ClauseConstraint({line_literals[i].Negated()}));
72  } else {
73  pairs.emplace_back(v, line_literals[i]);
74  model->Add(Implication(line_literals[i], gtl::FindOrDie(encoding, v)));
75  }
76  }
77 
78  // Regroup literal with the same value and add for each the clause: If all the
79  // tuples containing a value are false, then this value must be false too.
80  std::sort(pairs.begin(), pairs.end());
81  std::vector<Literal> clause = tuples_with_any;
82  for (int i = 0; i < pairs.size();) {
83  // We always keep the tuples_with_any at the beginning of the clause.
84  clause.resize(tuples_with_any.size());
85 
86  const IntegerValue value = pairs[i].first;
87  for (; i < pairs.size() && pairs[i].first == value; ++i) {
88  clause.push_back(pairs[i].second);
89  }
90 
91  // And the "value" literal and load the clause.
92  clause.push_back(gtl::FindOrDie(encoding, value).Negated());
93  model->Add(ClauseConstraint(clause));
94  }
95 }
96 
97 // Simpler encoding for table constraints with 2 variables.
98 void AddSizeTwoTable(
99  absl::Span<const IntegerVariable> vars,
100  const std::vector<std::vector<int64_t>>& tuples,
101  const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
102  Model* model) {
103  const int n = vars.size();
104  CHECK_EQ(n, 2);
105  IntegerTrail* const integer_trail = model->GetOrCreate<IntegerTrail>();
106 
107  std::vector<absl::flat_hash_map<IntegerValue, Literal>> encodings(n);
108  for (int i = 0; i < n; ++i) {
109  const std::vector<int64_t> reached_values(values_per_var[i].begin(),
110  values_per_var[i].end());
111  integer_trail->UpdateInitialDomain(vars[i],
112  Domain::FromValues(reached_values));
113  if (values_per_var.size() > 1) {
114  model->Add(FullyEncodeVariable(vars[i]));
115  encodings[i] = GetEncoding(vars[i], model);
116  }
117  }
118 
119  // One variable is fixed. Propagation is complete.
120  if (values_per_var[0].size() == 1 || values_per_var[1].size() == 1) return;
121 
122  std::map<LiteralIndex, std::vector<Literal>> left_to_right;
123  std::map<LiteralIndex, std::vector<Literal>> right_to_left;
124 
125  for (const auto& tuple : tuples) {
126  const IntegerValue left_value(tuple[0]);
127  const IntegerValue right_value(tuple[1]);
128  if (!encodings[0].contains(left_value) ||
129  !encodings[1].contains(right_value)) {
130  continue;
131  }
132 
133  const Literal left = gtl::FindOrDie(encodings[0], left_value);
134  const Literal right = gtl::FindOrDie(encodings[1], right_value);
135  left_to_right[left.Index()].push_back(right);
136  right_to_left[right.Index()].push_back(left);
137  }
138 
139  int num_implications = 0;
140  int num_clause_added = 0;
141  int num_large_clause_added = 0;
142  std::vector<Literal> clause;
143  auto add_support_constraint =
144  [model, &num_clause_added, &num_large_clause_added, &num_implications,
145  &clause](LiteralIndex lit, const std::vector<Literal>& supports,
146  int max_support_size) {
147  if (supports.size() == max_support_size) return;
148  if (supports.size() == 1) {
149  model->Add(Implication(Literal(lit), supports.front()));
150  num_implications++;
151  } else {
152  clause.assign(supports.begin(), supports.end());
153  clause.push_back(Literal(lit).Negated());
154  model->Add(ClauseConstraint(clause));
155  num_clause_added++;
156  if (supports.size() > max_support_size / 2) {
157  num_large_clause_added++;
158  }
159  }
160  };
161 
162  for (const auto& it : left_to_right) {
163  add_support_constraint(it.first, it.second, values_per_var[1].size());
164  }
165  for (const auto& it : right_to_left) {
166  add_support_constraint(it.first, it.second, values_per_var[0].size());
167  }
168  VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
169  << num_clause_added << " clauses, " << num_large_clause_added
170  << " large clauses, " << num_implications << " implications";
171 }
172 
173 // This method heuristically explores subsets of variables and decide if the
174 // projection of all tuples nearly fills all the possible combination of
175 // projected variables domains.
176 //
177 // In that case, it creates the complement of the projected tuples and add that
178 // as a forbidden assignment constraint.
179 void ExploreSubsetOfVariablesAndAddNegatedTables(
180  const std::vector<std::vector<int64_t>>& tuples,
181  const std::vector<std::vector<int64_t>>& var_domains,
182  absl::Span<const IntegerVariable> vars, Model* model) {
183  const int num_vars = var_domains.size();
184  for (int start = 0; start < num_vars; ++start) {
185  const int limit = start == 0 ? num_vars : std::min(num_vars, start + 3);
186  for (int end = start + 1; end < limit; ++end) {
187  // TODO(user,user): If we add negated table for more than one value of
188  // end, because the set of variables will be included in each other, we
189  // could reduce the number of clauses added. I.e if we excluded
190  // (x=2, y=3) there is no need to exclude any of the tuples
191  // (x=2, y=3, z=*).
192 
193  // Compute the maximum number of such prefix tuples.
194  int64_t max_num_prefix_tuples = 1;
195  for (int i = start; i <= end; ++i) {
196  max_num_prefix_tuples =
197  CapProd(max_num_prefix_tuples, var_domains[i].size());
198  }
199 
200  // Abort early.
201  if (max_num_prefix_tuples > 2 * tuples.size()) break;
202 
203  absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
204  bool skip = false;
205  for (const std::vector<int64_t>& tuple : tuples) {
206  prefixes.insert(absl::MakeSpan(&tuple[start], end - start + 1));
207  if (prefixes.size() == max_num_prefix_tuples) {
208  // Nothing to add with this range [start..end].
209  skip = true;
210  break;
211  }
212  }
213  if (skip) continue;
214  const int num_prefix_tuples = prefixes.size();
215 
216  std::vector<std::vector<int64_t>> negated_tuples;
217 
218  int created = 0;
219  if (num_prefix_tuples < max_num_prefix_tuples &&
220  max_num_prefix_tuples < num_prefix_tuples * 2) {
221  std::vector<int64_t> tmp_tuple;
222  for (int i = 0; i < max_num_prefix_tuples; ++i) {
223  tmp_tuple.clear();
224  int index = i;
225  for (int j = start; j <= end; ++j) {
226  tmp_tuple.push_back(var_domains[j][index % var_domains[j].size()]);
227  index /= var_domains[j].size();
228  }
229  if (!prefixes.contains(tmp_tuple)) {
230  negated_tuples.push_back(tmp_tuple);
231  created++;
232  }
233  }
234  AddNegatedTableConstraint(vars.subspan(start, end - start + 1),
235  negated_tuples, model);
236  VLOG(2) << " add negated tables with " << created
237  << " tuples on the range [" << start << "," << end << "]";
238  }
239  }
240  }
241 }
242 
243 } // namespace
244 
245 // Makes a static decomposition of a table constraint into clauses.
246 // This uses an auxiliary vector of Literals tuple_literals.
247 // For every column col, and every value val of that column,
248 // the decomposition uses clauses corresponding to the equivalence:
249 // (\/_{row | tuples[row][col] = val} tuple_literals[row]) <=> (vars[col] = val)
250 void AddTableConstraint(absl::Span<const IntegerVariable> vars,
251  std::vector<std::vector<int64_t>> tuples,
252  Model* model) {
253  const int n = vars.size();
254  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
255  const int num_original_tuples = tuples.size();
256 
257  // Compute the set of possible values for each variable (from the table).
258  // Remove invalid tuples along the way.
259  std::vector<absl::flat_hash_set<int64_t>> values_per_var(n);
260  int index = 0;
261  for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
262  bool keep = true;
263  for (int i = 0; i < n; ++i) {
264  const int64_t value = tuples[tuple_index][i];
265  if (!values_per_var[i].contains(value) /* cached */ &&
266  !integer_trail->InitialVariableDomain(vars[i]).Contains(value)) {
267  keep = false;
268  break;
269  }
270  }
271  if (keep) {
272  std::swap(tuples[tuple_index], tuples[index]);
273  for (int i = 0; i < n; ++i) {
274  values_per_var[i].insert(tuples[index][i]);
275  }
276  index++;
277  }
278  }
279  tuples.resize(index);
280  const int num_valid_tuples = tuples.size();
281 
282  if (tuples.empty()) {
283  model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
284  return;
285  }
286 
287  if (n == 2) {
288  AddSizeTwoTable(vars, tuples, values_per_var, model);
289  return;
290  }
291 
292  // It is easier to compute this before compression, as compression will merge
293  // tuples.
294  int num_prefix_tuples = 0;
295  {
296  absl::flat_hash_set<absl::Span<const int64_t>> prefixes;
297  for (const std::vector<int64_t>& tuple : tuples) {
298  prefixes.insert(absl::MakeSpan(tuple.data(), n - 1));
299  }
300  num_prefix_tuples = prefixes.size();
301  }
302 
303  std::vector<std::vector<int64_t>> var_domains(n);
304  for (int j = 0; j < n; ++j) {
305  var_domains[j].assign(values_per_var[j].begin(), values_per_var[j].end());
306  std::sort(var_domains[j].begin(), var_domains[j].end());
307  }
308  CHECK_GT(vars.size(), 2);
309  ExploreSubsetOfVariablesAndAddNegatedTables(tuples, var_domains, vars, model);
310 
311  // The variable domains have been computed. Fully encode variables.
312  // Note that in some corner cases (like duplicate vars), as we call
313  // UpdateInitialDomain(), the domain of other variable could become more
314  // restricted that values_per_var. For now, we do not try to reach a fixed
315  // point here.
316  std::vector<absl::flat_hash_map<IntegerValue, Literal>> encodings(n);
317  for (int i = 0; i < n; ++i) {
318  const std::vector<int64_t> reached_values(values_per_var[i].begin(),
319  values_per_var[i].end());
320  integer_trail->UpdateInitialDomain(vars[i],
321  Domain::FromValues(reached_values));
322  if (values_per_var.size() > 1) {
323  model->Add(FullyEncodeVariable(vars[i]));
324  encodings[i] = GetEncoding(vars[i], model);
325  }
326  }
327 
328  // Compress tuples.
329  const int64_t any_value = std::numeric_limits<int64_t>::min();
330  std::vector<int64_t> domain_sizes;
331  for (int i = 0; i < n; ++i) {
332  domain_sizes.push_back(values_per_var[i].size());
333  }
334  CompressTuples(domain_sizes, any_value, &tuples);
335  const int num_compressed_tuples = tuples.size();
336 
337  // Detect if prefix tuples are all different.
338  const bool prefixes_are_all_different = num_prefix_tuples == num_valid_tuples;
339  if (VLOG_IS_ON(2)) {
340  // Compute the maximum number of prefix tuples.
341  int64_t max_num_prefix_tuples = 1;
342  for (int i = 0; i + 1 < n; ++i) {
343  max_num_prefix_tuples =
344  CapProd(max_num_prefix_tuples, values_per_var[i].size());
345  }
346 
347  std::string message = absl::StrCat(
348  "Table: ", n, " variables, original tuples = ", num_original_tuples);
349  if (num_valid_tuples != num_original_tuples) {
350  absl::StrAppend(&message, ", valid tuples = ", num_valid_tuples);
351  }
352  if (prefixes_are_all_different) {
353  if (num_prefix_tuples < max_num_prefix_tuples) {
354  absl::StrAppend(&message, ", partial prefix = ", num_prefix_tuples, "/",
355  max_num_prefix_tuples);
356  } else {
357  absl::StrAppend(&message, ", full prefix = true");
358  }
359  } else {
360  absl::StrAppend(&message, ", num prefix tuples = ", num_prefix_tuples);
361  }
362  if (num_compressed_tuples != num_valid_tuples) {
363  absl::StrAppend(&message,
364  ", compressed tuples = ", num_compressed_tuples);
365  }
366  VLOG(2) << message;
367  }
368 
369  if (tuples.size() == 1) {
370  // Nothing more to do.
371  return;
372  }
373 
374  // Create one Boolean variable per tuple to indicate if it can still be
375  // selected or not. Note that we don't enforce exactly one tuple to be
376  // selected because these variables are just used by this constraint, so
377  // only the information "can't be selected" is important.
378  //
379  // TODO(user): If a value in one column is unique, we don't need to
380  // create a new BooleanVariable corresponding to this line since we can use
381  // the one corresponding to this value in that column.
382  //
383  // Note that if there is just one tuple, there is no need to create such
384  // variables since they are not used.
385  std::vector<Literal> tuple_literals;
386  tuple_literals.reserve(tuples.size());
387  if (tuples.size() == 2) {
388  tuple_literals.emplace_back(model->Add(NewBooleanVariable()), true);
389  tuple_literals.emplace_back(tuple_literals[0].Negated());
390  } else if (tuples.size() > 2) {
391  for (int i = 0; i < tuples.size(); ++i) {
392  tuple_literals.emplace_back(model->Add(NewBooleanVariable()), true);
393  }
394  model->Add(ClauseConstraint(tuple_literals));
395  }
396 
397  std::vector<Literal> active_tuple_literals;
398  std::vector<IntegerValue> active_values;
399  std::vector<Literal> any_tuple_literals;
400  for (int i = 0; i < n; ++i) {
401  if (values_per_var[i].size() == 1) continue;
402 
403  active_tuple_literals.clear();
404  active_values.clear();
405  any_tuple_literals.clear();
406  for (int j = 0; j < tuple_literals.size(); ++j) {
407  const int64_t v = tuples[j][i];
408 
409  if (v == any_value) {
410  any_tuple_literals.push_back(tuple_literals[j]);
411  } else {
412  active_tuple_literals.push_back(tuple_literals[j]);
413  active_values.push_back(IntegerValue(v));
414  }
415  }
416 
417  if (!active_tuple_literals.empty()) {
418  ProcessOneColumn(active_tuple_literals, active_values, encodings[i],
419  any_tuple_literals, model);
420  }
421  }
422 
423  if (prefixes_are_all_different) {
424  // The first n-1 columns are all different, this encodes the implication
425  // table (tuple of size n - 1) implies value. We can add an optional
426  // propagation that should lead to better explanation.
427  // For each tuple, we add a clause prefix => last value.
428  std::vector<Literal> clause;
429  for (int j = 0; j < tuples.size(); ++j) {
430  clause.clear();
431  bool tuple_is_valid = true;
432  for (int i = 0; i + 1 < n; ++i) {
433  // Ignore fixed variables.
434  if (values_per_var[i].size() == 1) continue;
435 
436  const int64_t v = tuples[j][i];
437  // Ignored 'any' created during compression.
438  if (v == any_value) continue;
439 
440  const IntegerValue value(v);
441  if (!encodings[i].contains(value)) {
442  tuple_is_valid = false;
443  break;
444  }
445  clause.push_back(gtl::FindOrDie(encodings[i], value).Negated());
446  }
447  if (!tuple_is_valid) continue;
448 
449  // Add the target of the implication.
450  const IntegerValue target_value = IntegerValue(tuples[j][n - 1]);
451  if (!encodings[n - 1].contains(target_value)) continue;
452  const Literal target_literal =
453  gtl::FindOrDie(encodings[n - 1], target_value);
454  clause.push_back(target_literal);
455  model->Add(ClauseConstraint(clause));
456  }
457  }
458 }
459 
460 void AddNegatedTableConstraint(absl::Span<const IntegerVariable> vars,
461  std::vector<std::vector<int64_t>> tuples,
462  Model* model) {
463  const int n = vars.size();
464  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
465  auto* integer_encoder = model->GetOrCreate<IntegerEncoder>();
466 
467  // Remove unreachable tuples.
468  int index = 0;
469  while (index < tuples.size()) {
470  bool remove = false;
471  for (int i = 0; i < n; ++i) {
472  if (!integer_trail->InitialVariableDomain(vars[i]).Contains(
473  tuples[index][i])) {
474  remove = true;
475  break;
476  }
477  }
478  if (remove) {
479  tuples[index] = tuples.back();
480  tuples.pop_back();
481  } else {
482  index++;
483  }
484  }
485 
486  if (tuples.empty()) {
487  return;
488  }
489 
490  // Compress tuples.
491  const int64_t any_value = std::numeric_limits<int64_t>::min();
492  std::vector<int64_t> domain_sizes;
493  for (int i = 0; i < n; ++i) {
494  domain_sizes.push_back(
495  integer_trail->InitialVariableDomain(vars[i]).Size());
496  }
497  CompressTuples(domain_sizes, any_value, &tuples);
498 
499  // Collect all relevant var == value literal.
500  std::vector<absl::flat_hash_map<int64_t, Literal>> mapping(n);
501  for (int i = 0; i < n; ++i) {
502  for (const auto pair : integer_encoder->PartialDomainEncoding(vars[i])) {
503  mapping[i][pair.value.value()] = pair.literal;
504  }
505  }
506 
507  // For each tuple, forbid the variables values to be this tuple.
508  std::vector<Literal> clause;
509  for (const std::vector<int64_t>& tuple : tuples) {
510  bool add_tuple = true;
511  clause.clear();
512  for (int i = 0; i < n; ++i) {
513  const int64_t value = tuple[i];
514  if (value == any_value) continue;
515 
516  // If a literal associated to var == value exist, use it, otherwise
517  // just use (and eventually create) the two literals var >= value + 1
518  // and var <= value - 1.
519  if (mapping[i].contains(value)) {
520  clause.push_back(gtl::FindOrDie(mapping[i], value).Negated());
521  } else {
522  const int64_t lb = model->Get(LowerBound(vars[i]));
523  const int64_t ub = model->Get(UpperBound(vars[i]));
524 
525  // TODO(user): test the full initial domain instead of just checking
526  // the bounds. That shouldn't change too much since the literals added
527  // below will be trivially true or false though.
528  if (value < lb || value > ub) {
529  add_tuple = false;
530  break;
531  }
532  if (value > lb) {
533  clause.push_back(integer_encoder->GetOrCreateAssociatedLiteral(
534  IntegerLiteral::LowerOrEqual(vars[i], IntegerValue(value - 1))));
535  }
536  if (value < ub) {
537  clause.push_back(integer_encoder->GetOrCreateAssociatedLiteral(
539  IntegerValue(value + 1))));
540  }
541  }
542  }
543  if (add_tuple) model->Add(ClauseConstraint(clause));
544  }
545 }
546 
547 std::function<void(Model*)> LiteralTableConstraint(
548  const std::vector<std::vector<Literal>>& literal_tuples,
549  const std::vector<Literal>& line_literals) {
550  return [=](Model* model) {
551  CHECK_EQ(literal_tuples.size(), line_literals.size());
552  const int num_tuples = line_literals.size();
553  if (num_tuples == 0) return;
554  const int tuple_size = literal_tuples[0].size();
555  if (tuple_size == 0) return;
556  for (int i = 1; i < num_tuples; ++i) {
557  CHECK_EQ(tuple_size, literal_tuples[i].size());
558  }
559 
560  absl::flat_hash_map<LiteralIndex, std::vector<LiteralIndex>>
561  line_literals_per_literal;
562  for (int i = 0; i < num_tuples; ++i) {
563  const LiteralIndex selected_index = line_literals[i].Index();
564  for (const Literal l : literal_tuples[i]) {
565  line_literals_per_literal[l.Index()].push_back(selected_index);
566  }
567  }
568 
569  // line_literals[i] == true => literal_tuples[i][j] == true.
570  // literal_tuples[i][j] == false => line_literals[i] == false.
571  for (int i = 0; i < num_tuples; ++i) {
572  const Literal line_is_selected = line_literals[i];
573  for (const Literal lit : literal_tuples[i]) {
574  model->Add(Implication(line_is_selected, lit));
575  }
576  }
577 
578  // Exactly one selected literal is true.
579  model->Add(ExactlyOneConstraint(line_literals));
580 
581  // If all selected literals of the lines containing a literal are false,
582  // then the literal is false.
583  for (const auto& p : line_literals_per_literal) {
584  std::vector<Literal> clause;
585  for (const auto& index : p.second) {
586  clause.push_back(Literal(index));
587  }
588  clause.push_back(Literal(p.first).Negated());
589  model->Add(ClauseConstraint(clause));
590  }
591  };
592 }
593 
594 std::function<void(Model*)> TransitionConstraint(
595  const std::vector<IntegerVariable>& vars,
596  const std::vector<std::vector<int64_t>>& automaton, int64_t initial_state,
597  const std::vector<int64_t>& final_states) {
598  return [=](Model* model) {
599  IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
600  const int n = vars.size();
601  CHECK_GT(n, 0) << "No variables in TransitionConstraint().";
602 
603  // Test precondition.
604  {
605  std::set<std::pair<int64_t, int64_t>> unique_transition_checker;
606  for (const std::vector<int64_t>& transition : automaton) {
607  CHECK_EQ(transition.size(), 3);
608  const std::pair<int64_t, int64_t> p{transition[0], transition[1]};
609  CHECK(!gtl::ContainsKey(unique_transition_checker, p))
610  << "Duplicate outgoing transitions with value " << transition[1]
611  << " from state " << transition[0] << ".";
612  unique_transition_checker.insert(p);
613  }
614  }
615 
616  // Construct a table with the possible values of each vars.
617  std::vector<absl::flat_hash_set<int64_t>> possible_values(n);
618  for (int time = 0; time < n; ++time) {
619  const auto domain = integer_trail->InitialVariableDomain(vars[time]);
620  for (const std::vector<int64_t>& transition : automaton) {
621  // TODO(user): quadratic algo, improve!
622  if (domain.Contains(transition[1])) {
623  possible_values[time].insert(transition[1]);
624  }
625  }
626  }
627 
628  // Compute the set of reachable state at each time point.
629  std::vector<std::set<int64_t>> reachable_states(n + 1);
630  reachable_states[0].insert(initial_state);
631  reachable_states[n] = {final_states.begin(), final_states.end()};
632 
633  // Forward.
634  //
635  // TODO(user): filter using the domain of vars[time] that may not contain
636  // all the possible transitions.
637  for (int time = 0; time + 1 < n; ++time) {
638  for (const std::vector<int64_t>& transition : automaton) {
639  if (!gtl::ContainsKey(reachable_states[time], transition[0])) continue;
640  if (!gtl::ContainsKey(possible_values[time], transition[1])) continue;
641  reachable_states[time + 1].insert(transition[2]);
642  }
643  }
644 
645  // Backward.
646  for (int time = n - 1; time > 0; --time) {
647  std::set<int64_t> new_set;
648  for (const std::vector<int64_t>& transition : automaton) {
649  if (!gtl::ContainsKey(reachable_states[time], transition[0])) continue;
650  if (!gtl::ContainsKey(possible_values[time], transition[1])) continue;
651  if (!gtl::ContainsKey(reachable_states[time + 1], transition[2]))
652  continue;
653  new_set.insert(transition[0]);
654  }
655  reachable_states[time].swap(new_set);
656  }
657 
658  // We will model at each time step the current automaton state using Boolean
659  // variables. We will have n+1 time step. At time zero, we start in the
660  // initial state, and at time n we should be in one of the final states. We
661  // don't need to create Booleans at at time when there is just one possible
662  // state (like at time zero).
663  absl::flat_hash_map<IntegerValue, Literal> encoding;
664  absl::flat_hash_map<IntegerValue, Literal> in_encoding;
665  absl::flat_hash_map<IntegerValue, Literal> out_encoding;
666  for (int time = 0; time < n; ++time) {
667  // All these vector have the same size. We will use them to enforce a
668  // local table constraint representing one step of the automaton at the
669  // given time.
670  std::vector<Literal> tuple_literals;
671  std::vector<IntegerValue> in_states;
672  std::vector<IntegerValue> transition_values;
673  std::vector<IntegerValue> out_states;
674  for (const std::vector<int64_t>& transition : automaton) {
675  if (!gtl::ContainsKey(reachable_states[time], transition[0])) continue;
676  if (!gtl::ContainsKey(possible_values[time], transition[1])) continue;
677  if (!gtl::ContainsKey(reachable_states[time + 1], transition[2]))
678  continue;
679 
680  // TODO(user): if this transition correspond to just one in-state or
681  // one-out state or one variable value, we could reuse the corresponding
682  // Boolean variable instead of creating a new one!
683  tuple_literals.push_back(
684  Literal(model->Add(NewBooleanVariable()), true));
685  in_states.push_back(IntegerValue(transition[0]));
686 
687  transition_values.push_back(IntegerValue(transition[1]));
688 
689  // On the last step we don't need to distinguish the output states, so
690  // we use zero.
691  out_states.push_back(time + 1 == n ? IntegerValue(0)
692  : IntegerValue(transition[2]));
693  }
694 
695  // Fully instantiate vars[time].
696  // Tricky: because we started adding constraints that can propagate, the
697  // possible values returned by encoding might not contains all the value
698  // computed in transition_values.
699  {
700  std::vector<IntegerValue> s = transition_values;
702 
703  encoding.clear();
704  if (s.size() > 1) {
705  std::vector<int64_t> values;
706  values.reserve(s.size());
707  for (IntegerValue v : s) values.push_back(v.value());
708  integer_trail->UpdateInitialDomain(vars[time],
709  Domain::FromValues(values));
710  model->Add(FullyEncodeVariable(vars[time]));
711  encoding = GetEncoding(vars[time], model);
712  } else {
713  // Fix vars[time] to its unique possible value.
714  CHECK_EQ(s.size(), 1);
715  const int64_t unique_value = s.begin()->value();
716  model->Add(LowerOrEqual(vars[time], unique_value));
717  model->Add(GreaterOrEqual(vars[time], unique_value));
718  }
719  }
720 
721  // For each possible out states, create one Boolean variable.
722  {
723  std::vector<IntegerValue> s = out_states;
725 
726  out_encoding.clear();
727  if (s.size() == 2) {
728  const BooleanVariable var = model->Add(NewBooleanVariable());
729  out_encoding[s.front()] = Literal(var, true);
730  out_encoding[s.back()] = Literal(var, false);
731  } else if (s.size() > 1) {
732  for (const IntegerValue state : s) {
733  const Literal l = Literal(model->Add(NewBooleanVariable()), true);
734  out_encoding[state] = l;
735  }
736  }
737  }
738 
739  // Now we link everything together.
740  //
741  // Note that we do not need the ExactlyOneConstraint(tuple_literals)
742  // because it is already implicitely encoded since we have exactly one
743  // transition value.
744  if (!in_encoding.empty()) {
745  ProcessOneColumn(tuple_literals, in_states, in_encoding, {}, model);
746  }
747  if (!encoding.empty()) {
748  ProcessOneColumn(tuple_literals, transition_values, encoding, {},
749  model);
750  }
751  if (!out_encoding.empty()) {
752  ProcessOneColumn(tuple_literals, out_states, out_encoding, {}, model);
753  }
754  in_encoding = out_encoding;
755  }
756  };
757 }
758 
759 } // namespace sat
760 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:705
#define CHECK_GT(val1, val2)
Definition: base/logging.h:710
#define VLOG(verboselevel)
Definition: base/logging.h:986
bool Contains(int64_t value) const
Returns true iff value is in Domain.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
const Domain & InitialVariableDomain(IntegerVariable var) const
Definition: integer.cc:647
bool UpdateInitialDomain(IntegerVariable var, Domain domain)
Definition: integer.cc:651
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition: stl_util.h:58
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1500
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition: integer.h:1478
std::function< void(Model *)> ClauseConstraint(absl::Span< const Literal > literals)
Definition: sat_solver.h:906
std::function< void(Model *)> ExactlyOneConstraint(const std::vector< Literal > &literals)
Definition: sat_solver.h:878
std::function< BooleanVariable(Model *)> NewBooleanVariable()
Definition: integer.h:1417
void AddNegatedTableConstraint(absl::Span< const IntegerVariable > vars, std::vector< std::vector< int64_t >> tuples, Model *model)
Definition: sat/table.cc:460
void CompressTuples(absl::Span< const int64_t > domain_sizes, int64_t any_value, std::vector< std::vector< int64_t >> *tuples)
Definition: sat/util.cc:113
std::function< std::vector< IntegerEncoder::ValueLiteralPair >Model *)> FullyEncodeVariable(IntegerVariable var)
Definition: integer.h:1593
std::function< void(Model *)> LiteralTableConstraint(const std::vector< std::vector< Literal >> &literal_tuples, const std::vector< Literal > &line_literals)
Definition: sat/table.cc:547
std::function< void(Model *)> Implication(const std::vector< Literal > &enforcement_literals, IntegerLiteral i)
Definition: integer.h:1543
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition: integer.h:1515
std::function< void(Model *)> TransitionConstraint(const std::vector< IntegerVariable > &vars, const std::vector< std::vector< int64_t >> &automaton, int64_t initial_state, const std::vector< int64_t > &final_states)
Definition: sat/table.cc:594
void AddTableConstraint(absl::Span< const IntegerVariable > vars, std::vector< std::vector< int64_t >> tuples, Model *model)
Definition: sat/table.cc:250
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1472
Collection of objects used to extend the Constraint Solver library.
int64_t CapProd(int64_t x, int64_t y)
int index
Definition: pack.cc:509
int64_t time
Definition: resource.cc:1691
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1275
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1269
std::string message
Definition: trace.cc:398
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41