OR-Tools  9.1
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"
33#include "ortools/sat/util.h"
35
36namespace operations_research {
37namespace sat {
38
39namespace {
40
41// Converts the vector representation returned by FullDomainEncoding() to a map.
42absl::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.
57void 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.
98void 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.
179void 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): 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)
250void 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
460void 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
547std::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
594std::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:491
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_GT(val1, val2)
Definition: base/logging.h:703
#define VLOG(verboselevel)
Definition: base/logging.h:979
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:682
bool UpdateInitialDomain(IntegerVariable var, Domain domain)
Definition: integer.cc:686
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
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
void AddTableConstraint(absl::Span< const IntegerVariable > vars, std::vector< std::vector< int64_t > > tuples, Model *model)
Definition: sat/table.cc:250
std::function< void(Model *)> ClauseConstraint(absl::Span< const Literal > literals)
Definition: sat_solver.h:906
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< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1524
std::function< BooleanVariable(Model *)> NewBooleanVariable()
Definition: integer.h:1469
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< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition: integer.h:1567
std::function< void(Model *)> Implication(const std::vector< Literal > &enforcement_literals, IntegerLiteral i)
Definition: integer.h:1595
void AddNegatedTableConstraint(absl::Span< const IntegerVariable > vars, std::vector< std::vector< int64_t > > tuples, Model *model)
Definition: sat/table.cc:460
std::function< int64_t(const Model &)> UpperBound(IntegerVariable v)
Definition: integer.h:1530
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1552
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
std::function< void(Model *)> ExactlyOneConstraint(const std::vector< Literal > &literals)
Definition: sat_solver.h:878
std::function< std::vector< IntegerEncoder::ValueLiteralPair >(Model *)> FullyEncodeVariable(IntegerVariable var)
Definition: integer.h:1645
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:1315
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1309
std::string message
Definition: trace.cc:398
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41