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