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