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