Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_expand.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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 <deque>
19#include <limits>
20#include <optional>
21#include <string>
22#include <utility>
23#include <vector>
24
25#include "absl/container/btree_map.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/container/inlined_vector.h"
29#include "absl/log/check.h"
30#include "absl/meta/type_traits.h"
31#include "absl/strings/str_cat.h"
32#include "google/protobuf/message.h"
36#include "ortools/sat/cp_model.pb.h"
40#include "ortools/sat/sat_parameters.pb.h"
41#include "ortools/sat/util.h"
44
45namespace operations_research {
46namespace sat {
47
48// TODO(user): Note that if we have duplicate variables controlling different
49// time point, this might not reach the fixed point. Fix? it is not that
50// important as the expansion take care of this case anyway.
51void PropagateAutomaton(const AutomatonConstraintProto& proto,
53 std::vector<absl::flat_hash_set<int64_t>>* states,
54 std::vector<absl::flat_hash_set<int64_t>>* labels) {
55 const int n = proto.vars_size();
56 const absl::flat_hash_set<int64_t> final_states(
57 {proto.final_states().begin(), proto.final_states().end()});
58
59 labels->clear();
60 labels->resize(n);
61 states->clear();
62 states->resize(n + 1);
63 (*states)[0].insert(proto.starting_state());
64
65 // Forward pass.
66 for (int time = 0; time < n; ++time) {
67 for (int t = 0; t < proto.transition_tail_size(); ++t) {
68 const int64_t tail = proto.transition_tail(t);
69 const int64_t label = proto.transition_label(t);
70 const int64_t head = proto.transition_head(t);
71 if (!(*states)[time].contains(tail)) continue;
72 if (!context.DomainContains(proto.vars(time), label)) continue;
73 if (time == n - 1 && !final_states.contains(head)) continue;
74 (*labels)[time].insert(label);
75 (*states)[time + 1].insert(head);
76 }
77 }
78
79 // Backward pass.
80 for (int time = n - 1; time >= 0; --time) {
81 absl::flat_hash_set<int64_t> new_states;
82 absl::flat_hash_set<int64_t> new_labels;
83 for (int t = 0; t < proto.transition_tail_size(); ++t) {
84 const int64_t tail = proto.transition_tail(t);
85 const int64_t label = proto.transition_label(t);
86 const int64_t head = proto.transition_head(t);
87
88 if (!(*states)[time].contains(tail)) continue;
89 if (!(*labels)[time].contains(label)) continue;
90 if (!(*states)[time + 1].contains(head)) continue;
91 new_labels.insert(label);
92 new_states.insert(tail);
93 }
94 (*labels)[time].swap(new_labels);
95 (*states)[time].swap(new_states);
96 }
97}
98
99namespace {
100
101// Different encoding that support general demands. This is usually a pretty bad
102// encoding, at least until we improve the solver on such models.
103void ExpandReservoirUsingCircuit(int64_t sum_of_positive_demand,
104 int64_t sum_of_negative_demand,
105 ConstraintProto* reservoir_ct,
106 PresolveContext* context) {
107 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
108 const int num_events = reservoir.time_exprs_size();
109
110 // The encoding will create a circuit constraint and on integer variable per
111 // events representing the level a that event time.
112 CircuitConstraintProto* circuit =
113 context->working_model->add_constraints()->mutable_circuit();
114
115 const int64_t var_min =
116 std::max(reservoir.min_level(), sum_of_negative_demand);
117 const int64_t var_max =
118 std::min(reservoir.max_level(), sum_of_positive_demand);
119 std::vector<int> level_vars(num_events);
120 for (int i = 0; i < num_events; ++i) {
121 level_vars[i] = context->NewIntVar(Domain(var_min, var_max));
122 }
123
124 // For the corner case where all events are absent, we need a potential
125 // self-arc on the start/end circuit node.
126 {
127 circuit->add_tails(num_events);
128 circuit->add_heads(num_events);
129 circuit->add_literals(context->NewBoolVar());
130 }
131
132 for (int i = 0; i < num_events; ++i) {
133 if (!reservoir.active_literals().empty()) {
134 // Add self arc to represent absence.
135 circuit->add_tails(i);
136 circuit->add_heads(i);
137 circuit->add_literals(NegatedRef(reservoir.active_literals(i)));
138 }
139
140 // We need an extra circuit node for start/end of circuit.
141 // We use the available index 'num_events'.
142 {
143 // Circuit starts at i, level_vars[i] == demand_expr[i].
144 const int start_var = context->NewBoolVar();
145 circuit->add_tails(num_events);
146 circuit->add_heads(i);
147 circuit->add_literals(start_var);
148
149 // Add enforced linear for demand.
150 {
151 ConstraintProto* new_ct = context->working_model->add_constraints();
152 new_ct->add_enforcement_literal(start_var);
153 LinearConstraintProto* lin = new_ct->mutable_linear();
154 lin->add_domain(0);
155 lin->add_domain(0);
156 lin->add_vars(level_vars[i]);
157 lin->add_coeffs(1);
158 AddLinearExpressionToLinearConstraint(reservoir.level_changes(i), -1,
159 lin);
160 context->CanonicalizeLinearConstraint(new_ct);
161 }
162
163 // Circuit ends at i, no extra constraint there.
164 circuit->add_tails(i);
165 circuit->add_heads(num_events);
166 circuit->add_literals(context->NewBoolVar());
167 }
168
169 for (int j = 0; j < num_events; ++j) {
170 if (i == j) continue;
171
172 // If arc_i_j is true then:
173 // - active_i is true (enforced by circuit).
174 // - active_j is true (enforced by circuit).
175 // - time_i <= time_j
176 // - level_j == level_i + demand_j
177 //
178 // TODO(user): Unfortunately we cannot share these literal between
179 // reservoir except if the set of time point is exactly the same!
180 // otherwise if we miss one, then A "after" B in one circuit do not
181 // implies that there is no C in between in another!
182 const int arc_i_j = context->NewBoolVar();
183 circuit->add_tails(i);
184 circuit->add_heads(j);
185 circuit->add_literals(arc_i_j);
186
187 // Add enforced linear for time.
188 {
189 ConstraintProto* new_ct = context->working_model->add_constraints();
190 new_ct->add_enforcement_literal(arc_i_j);
191 LinearConstraintProto* lin = new_ct->mutable_linear();
192 lin->add_domain(0);
193 lin->add_domain(std::numeric_limits<int64_t>::max());
194 AddLinearExpressionToLinearConstraint(reservoir.time_exprs(j), 1, lin);
195 AddLinearExpressionToLinearConstraint(reservoir.time_exprs(i), -1, lin);
196 context->CanonicalizeLinearConstraint(new_ct);
197 }
198
199 // Add enforced linear for demand.
200 {
201 ConstraintProto* new_ct = context->working_model->add_constraints();
202 new_ct->add_enforcement_literal(arc_i_j);
203 LinearConstraintProto* lin = new_ct->mutable_linear();
204 lin->add_domain(0);
205 lin->add_domain(0);
206 lin->add_vars(level_vars[j]);
207 lin->add_coeffs(1);
208 lin->add_vars(level_vars[i]);
209 lin->add_coeffs(-1);
210 AddLinearExpressionToLinearConstraint(reservoir.level_changes(j), -1,
211 lin);
212 context->CanonicalizeLinearConstraint(new_ct);
213 }
214 }
215 }
216
217 reservoir_ct->Clear();
218 context->UpdateRuleStats("reservoir: expanded using circuit.");
219}
220
221void ExpandReservoirUsingPrecedences(int64_t sum_of_positive_demand,
222 int64_t sum_of_negative_demand,
223 ConstraintProto* reservoir_ct,
224 PresolveContext* context) {
225 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
226 const int num_events = reservoir.time_exprs_size();
227 const int true_literal = context->GetTrueLiteral();
228 const auto is_active_literal = [&reservoir, true_literal](int index) {
229 if (reservoir.active_literals_size() == 0) return true_literal;
230 return reservoir.active_literals(index);
231 };
232
233 // Constrains the running level to be consistent at all time_exprs.
234 // For this we only add a constraint at the time a given demand take place.
235 for (int i = 0; i < num_events; ++i) {
236 const int active_i = is_active_literal(i);
237 if (context->LiteralIsFalse(active_i)) continue;
238
239 const int64_t demand_i = context->FixedValue(reservoir.level_changes(i));
240 if (demand_i == 0) continue;
241
242 // No need for some constraints if the reservoir is just constrained in
243 // one direction.
244 if (demand_i > 0 && sum_of_positive_demand <= reservoir.max_level()) {
245 continue;
246 }
247 if (demand_i < 0 && sum_of_negative_demand >= reservoir.min_level()) {
248 continue;
249 }
250
251 ConstraintProto* new_ct = context->working_model->add_constraints();
252 LinearConstraintProto* new_linear = new_ct->mutable_linear();
253
254 // Add contributions from previous events.
255 int64_t offset = 0;
256 const LinearExpressionProto& time_i = reservoir.time_exprs(i);
257 for (int j = 0; j < num_events; ++j) {
258 if (i == j) continue;
259 const int active_j = is_active_literal(j);
260 if (context->LiteralIsFalse(active_j)) continue;
261
262 // Get or create the literal equivalent to
263 // active_i && active_j && time[j] <= time[i].
264 //
265 // TODO(user): we could get rid of active_i in the equivalence above.
266 // Experiments when we have enough benchmarks.
267 const LinearExpressionProto& time_j = reservoir.time_exprs(j);
268 const int j_lesseq_i = context->GetOrCreateReifiedPrecedenceLiteral(
269 time_j, time_i, active_j, active_i);
270 context->working_model->mutable_variables(j_lesseq_i)
271 ->set_name(absl::StrCat(j, " before ", i));
272
273 const int64_t demand = context->FixedValue(reservoir.level_changes(j));
274 if (RefIsPositive(j_lesseq_i)) {
275 new_linear->add_vars(j_lesseq_i);
276 new_linear->add_coeffs(demand);
277 } else {
278 new_linear->add_vars(NegatedRef(j_lesseq_i));
279 new_linear->add_coeffs(-demand);
280 offset -= demand;
281 }
282 }
283
284 // Add contribution from event i.
285 //
286 // TODO(user): Alternatively we can mark the whole constraint as enforced
287 // only if active_i is true. Experiments with both version, right now we
288 // miss enough benchmarks to conclude.
289 if (RefIsPositive(active_i)) {
290 new_linear->add_vars(active_i);
291 new_linear->add_coeffs(demand_i);
292 } else {
293 new_linear->add_vars(NegatedRef(active_i));
294 new_linear->add_coeffs(-demand_i);
295 offset -= demand_i;
296 }
297
298 // Note that according to the sign of demand_i, we only need one side.
299 if (demand_i > 0) {
300 new_linear->add_domain(std::numeric_limits<int64_t>::min());
301 new_linear->add_domain(reservoir.max_level());
302 } else {
303 new_linear->add_domain(reservoir.min_level());
304 new_linear->add_domain(std::numeric_limits<int64_t>::max());
305 }
306
307 context->CanonicalizeLinearConstraint(new_ct);
308 }
309
310 reservoir_ct->Clear();
311 context->UpdateRuleStats("reservoir: expanded using precedences");
312}
313
314void ExpandReservoir(ConstraintProto* reservoir_ct, PresolveContext* context) {
315 if (reservoir_ct->reservoir().min_level() >
316 reservoir_ct->reservoir().max_level()) {
317 VLOG(1) << "Empty level domain in reservoir constraint.";
318 return (void)context->NotifyThatModelIsUnsat();
319 }
320
321 const ReservoirConstraintProto& reservoir = reservoir_ct->reservoir();
322 const int num_events = reservoir.time_exprs_size();
323
324 int num_positives = 0;
325 int num_negatives = 0;
326 bool all_demands_are_fixed = true;
327 int64_t sum_of_positive_demand = 0;
328 int64_t sum_of_negative_demand = 0;
329 for (const LinearExpressionProto& demand_expr : reservoir.level_changes()) {
330 if (!context->IsFixed(demand_expr)) {
331 all_demands_are_fixed = false;
332 }
333 const int64_t max_demand = context->MaxOf(demand_expr);
334 if (max_demand > 0) {
335 num_positives++;
336 sum_of_positive_demand += max_demand;
337 }
338 const int64_t min_demand = context->MinOf(demand_expr);
339 if (min_demand < 0) {
340 num_negatives++;
341 sum_of_negative_demand += min_demand;
342 }
343 }
344
345 if (sum_of_negative_demand >= reservoir.min_level() &&
346 sum_of_positive_demand <= reservoir.max_level()) {
347 context->UpdateRuleStats("reservoir: always true");
348 reservoir_ct->Clear();
349 return;
350 }
351
352 // If all level_changes have the same sign, we do not care about the order,
353 // just the sum. We might need to create intermediate variable for quadratic
354 // terms though.
355 if (num_negatives == 0 || num_positives == 0) {
356 const int true_literal = context->GetTrueLiteral();
357 ConstraintProto* new_ct = context->working_model->add_constraints();
358 LinearConstraintProto* sum = new_ct->mutable_linear();
359 for (int i = 0; i < num_events; ++i) {
360 const int active = reservoir.active_literals().empty()
361 ? true_literal
362 : reservoir.active_literals(i);
363 const LinearExpressionProto& demand = reservoir.level_changes(i);
364 if (context->IsFixed(demand)) {
365 const int64_t change = context->FixedValue(reservoir.level_changes(i));
366 if (RefIsPositive(active)) {
367 sum->add_vars(active);
368 sum->add_coeffs(change);
369 } else {
370 // Add (1 - not(active)) * level_change.
371 sum->add_vars(true_literal);
372 sum->add_coeffs(change);
373 sum->add_vars(NegatedRef(active));
374 sum->add_coeffs(-change);
375 }
376 } else if (context->LiteralIsTrue(active)) {
378 } else {
379 const int new_var = context->NewIntVar(
380 Domain(context->MinOf(demand), context->MaxOf(demand))
381 .UnionWith(Domain(0)));
382 sum->add_vars(new_var);
383 sum->add_coeffs(1);
384
385 // Active => new_var == demand.
386 {
387 ConstraintProto* demand_ct =
388 context->working_model->add_constraints();
389 demand_ct->add_enforcement_literal(active);
390 LinearConstraintProto* lin = demand_ct->mutable_linear();
391 lin->add_domain(0);
392 lin->add_domain(0);
393 lin->add_vars(new_var);
394 lin->add_coeffs(1);
396 context->CanonicalizeLinearConstraint(demand_ct);
397 }
398
399 // not(active) => new_var == 0.
400 context->AddImplyInDomain(NegatedRef(active), new_var, Domain(0));
401 }
402 }
403 sum->add_domain(reservoir.min_level());
404 sum->add_domain(reservoir.max_level());
405 context->CanonicalizeLinearConstraint(new_ct);
406
407 context->UpdateRuleStats("reservoir: simple expansion with sum");
408 reservoir_ct->Clear();
409 return;
410 }
411
412 // Call the correct expansion according to our parameter.
413 if (context->params().expand_reservoir_using_circuit()) {
414 ExpandReservoirUsingCircuit(sum_of_positive_demand, sum_of_negative_demand,
415 reservoir_ct, context);
416 } else {
417 // This one is the faster option usually.
418 if (all_demands_are_fixed) {
419 ExpandReservoirUsingPrecedences(sum_of_positive_demand,
420 sum_of_negative_demand, reservoir_ct,
421 context);
422 } else {
423 context->UpdateRuleStats(
424 "reservoir: skipped expansion due to variable demands");
425 }
426 }
427}
428
429// This is mainly used for testing the reservoir implementation.
430void EncodeCumulativeAsReservoir(ConstraintProto* ct,
431 PresolveContext* context) {
432 if (!context->IsFixed(ct->cumulative().capacity())) {
433 context->UpdateRuleStats(
434 "cumulative -> reservoir: expansion is not supported with variable "
435 "capacity.");
436 return;
437 }
438
439 // Note that we know that the min_level can never go below zero, so we can
440 // just ignore this part of the constraint here.
441 ConstraintProto reservoir_ct;
442 auto* reservoir = reservoir_ct.mutable_reservoir();
443 reservoir->set_min_level(std::numeric_limits<int64_t>::min());
444 reservoir->set_max_level(context->FixedValue(ct->cumulative().capacity()));
445
446 const int true_literal = context->GetTrueLiteral();
447 const int num_intervals = ct->cumulative().intervals().size();
448 for (int i = 0; i < num_intervals; ++i) {
449 const auto& interval_ct =
450 context->working_model->constraints(ct->cumulative().intervals(i));
451 const auto& interval = interval_ct.interval();
452 *reservoir->add_time_exprs() = interval.start();
453 *reservoir->add_time_exprs() = interval.end();
454
455 const LinearExpressionProto& demand = ct->cumulative().demands(i);
456 *reservoir->add_level_changes() = demand;
457 LinearExpressionProto& negated = *reservoir->add_level_changes();
458 negated.set_offset(-demand.offset());
459 for (int j = 0; j < demand.vars().size(); ++j) {
460 negated.add_vars(demand.vars(j));
461 negated.add_coeffs(-demand.coeffs(j));
462 }
463
464 if (interval_ct.enforcement_literal().empty()) {
465 reservoir->add_active_literals(true_literal);
466 reservoir->add_active_literals(true_literal);
467 } else {
468 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
469 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
470 reservoir->add_active_literals(interval_ct.enforcement_literal(0));
471 }
472 }
473
474 // Now expand it and clear the cumulative.
475 ct->Clear();
476 context->UpdateRuleStats("cumulative: expanded into reservoir");
477 ExpandReservoir(&reservoir_ct, context);
478}
479
480void ExpandIntMod(ConstraintProto* ct, PresolveContext* context) {
481 const LinearArgumentProto& int_mod = ct->int_mod();
482 const LinearExpressionProto& mod_expr = int_mod.exprs(1);
483 if (context->IsFixed(mod_expr)) return;
484
485 const LinearExpressionProto& expr = int_mod.exprs(0);
486 const LinearExpressionProto& target_expr = int_mod.target();
487
488 // We reduce the domain of target_expr to avoid later overflow.
489 if (!context->IntersectDomainWith(
490 target_expr, context->DomainSuperSetOf(expr).PositiveModuloBySuperset(
491 context->DomainSuperSetOf(mod_expr)))) {
492 return;
493 }
494
495 // Create a new constraint with the same enforcement as ct.
496 auto new_enforced_constraint = [&]() {
497 ConstraintProto* new_ct = context->working_model->add_constraints();
498 *new_ct->mutable_enforcement_literal() = ct->enforcement_literal();
499 return new_ct;
500 };
501
502 // div_expr = expr / mod_expr.
503 const int div_var = context->NewIntVar(
504 context->DomainSuperSetOf(expr).PositiveDivisionBySuperset(
505 context->DomainSuperSetOf(mod_expr)));
506 LinearExpressionProto div_expr;
507 div_expr.add_vars(div_var);
508 div_expr.add_coeffs(1);
509
510 LinearArgumentProto* const div_proto =
511 new_enforced_constraint()->mutable_int_div();
512 *div_proto->mutable_target() = div_expr;
513 *div_proto->add_exprs() = expr;
514 *div_proto->add_exprs() = mod_expr;
515
516 // Create prod_expr = div_expr * mod_expr.
517 const Domain prod_domain =
518 context->DomainOf(div_var)
519 .ContinuousMultiplicationBy(context->DomainSuperSetOf(mod_expr))
520 .IntersectionWith(context->DomainSuperSetOf(expr).AdditionWith(
521 context->DomainSuperSetOf(target_expr).Negation()));
522 const int prod_var = context->NewIntVar(prod_domain);
523 LinearExpressionProto prod_expr;
524 prod_expr.add_vars(prod_var);
525 prod_expr.add_coeffs(1);
526
527 LinearArgumentProto* const int_prod =
528 new_enforced_constraint()->mutable_int_prod();
529 *int_prod->mutable_target() = prod_expr;
530 *int_prod->add_exprs() = div_expr;
531 *int_prod->add_exprs() = mod_expr;
532
533 // expr - prod_expr = target_expr.
534 LinearConstraintProto* const lin =
535 new_enforced_constraint()->mutable_linear();
536 lin->add_domain(0);
537 lin->add_domain(0);
539 AddLinearExpressionToLinearConstraint(prod_expr, -1, lin);
540 AddLinearExpressionToLinearConstraint(target_expr, -1, lin);
541
542 ct->Clear();
543 context->UpdateRuleStats("int_mod: expanded");
544}
545
546void ExpandNonBinaryIntProd(ConstraintProto* ct, PresolveContext* context) {
547 CHECK_GT(ct->int_prod().exprs_size(), 2);
548 std::deque<LinearExpressionProto> terms(
549 {ct->int_prod().exprs().begin(), ct->int_prod().exprs().end()});
550 while (terms.size() > 2) {
551 const LinearExpressionProto& left = terms[0];
552 const LinearExpressionProto& right = terms[1];
553 const Domain new_domain =
554 context->DomainSuperSetOf(left).ContinuousMultiplicationBy(
555 context->DomainSuperSetOf(right));
556 const int new_var = context->NewIntVar(new_domain);
557 LinearArgumentProto* const int_prod =
558 context->working_model->add_constraints()->mutable_int_prod();
559 *int_prod->add_exprs() = left;
560 *int_prod->add_exprs() = right;
561 int_prod->mutable_target()->add_vars(new_var);
562 int_prod->mutable_target()->add_coeffs(1);
563 terms.pop_front();
564 terms.pop_front();
565 terms.push_back(int_prod->target());
566 }
567
568 LinearArgumentProto* const final_int_prod =
569 context->working_model->add_constraints()->mutable_int_prod();
570 *final_int_prod->add_exprs() = terms[0];
571 *final_int_prod->add_exprs() = terms[1];
572 *final_int_prod->mutable_target() = ct->int_prod().target();
573
574 context->UpdateRuleStats(absl::StrCat(
575 "int_prod: expanded int_prod with arity ", ct->int_prod().exprs_size()));
576 ct->Clear();
577}
578
579// TODO(user): Move this into the presolve instead?
580void ExpandIntProdWithBoolean(int bool_ref,
581 const LinearExpressionProto& int_expr,
582 const LinearExpressionProto& product_expr,
583 PresolveContext* context) {
584 ConstraintProto* const one = context->working_model->add_constraints();
585 one->add_enforcement_literal(bool_ref);
586 one->mutable_linear()->add_domain(0);
587 one->mutable_linear()->add_domain(0);
588 AddLinearExpressionToLinearConstraint(int_expr, 1, one->mutable_linear());
590 one->mutable_linear());
591
592 ConstraintProto* const zero = context->working_model->add_constraints();
593 zero->add_enforcement_literal(NegatedRef(bool_ref));
594 zero->mutable_linear()->add_domain(0);
595 zero->mutable_linear()->add_domain(0);
597 zero->mutable_linear());
598}
599
600void ExpandIntProd(ConstraintProto* ct, PresolveContext* context) {
601 const LinearArgumentProto& int_prod = ct->int_prod();
602 if (int_prod.exprs_size() > 2) {
603 ExpandNonBinaryIntProd(ct, context);
604 return;
605 }
606 if (int_prod.exprs_size() != 2) return;
607 const LinearExpressionProto& a = int_prod.exprs(0);
608 const LinearExpressionProto& b = int_prod.exprs(1);
609 const LinearExpressionProto& p = int_prod.target();
610 int literal;
611 const bool a_is_literal = context->ExpressionIsALiteral(a, &literal);
612 const bool b_is_literal = context->ExpressionIsALiteral(b, &literal);
613
614 // We expand if exactly one of {a, b} is a literal. If both are literals, it
615 // will be presolved into a better version.
616 if (a_is_literal && !b_is_literal) {
617 ExpandIntProdWithBoolean(literal, b, p, context);
618 ct->Clear();
619 context->UpdateRuleStats("int_prod: expanded product with Boolean var");
620 } else if (b_is_literal) {
621 ExpandIntProdWithBoolean(literal, a, p, context);
622 ct->Clear();
623 context->UpdateRuleStats("int_prod: expanded product with Boolean var");
624 }
625}
626
627void ExpandInverse(ConstraintProto* ct, PresolveContext* context) {
628 const auto& f_direct = ct->inverse().f_direct();
629 const auto& f_inverse = ct->inverse().f_inverse();
630 const int n = f_direct.size();
631 CHECK_EQ(n, f_inverse.size());
632
633 // Make sure the domains are included in [0, n - 1).
634 // Note that if a variable and its negation appear, the domains will be set to
635 // zero here.
636 //
637 // TODO(user): Add support for UNSAT at expansion. This should create empty
638 // domain if UNSAT, so it should still work correctly.
639 absl::flat_hash_set<int> used_variables;
640 for (const int ref : f_direct) {
641 used_variables.insert(PositiveRef(ref));
642 if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
643 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
644 return;
645 }
646 }
647 for (const int ref : f_inverse) {
648 used_variables.insert(PositiveRef(ref));
649 if (!context->IntersectDomainWith(ref, Domain(0, n - 1))) {
650 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
651 return;
652 }
653 }
654
655 // If we have duplicate variables, we make sure the domain are reduced
656 // as the loop below might not detect incompatibilities.
657 if (used_variables.size() != 2 * n) {
658 for (int i = 0; i < n; ++i) {
659 for (int j = 0; j < n; ++j) {
660 // Note that if we don't have the same sign, both domain are at zero.
661 if (PositiveRef(f_direct[i]) != PositiveRef(f_inverse[j])) continue;
662
663 // We can't have i or j as value if i != j.
664 if (i == j) continue;
665 if (!context->IntersectDomainWith(
666 f_direct[i], Domain::FromValues({i, j}).Complement())) {
667 return;
668 }
669 }
670 }
671 }
672
673 // Reduce the domains of each variable by checking that the inverse value
674 // exists.
675 std::vector<int64_t> possible_values;
676
677 // Propagate from one vector to its counterpart.
678 const auto filter_inverse_domain =
679 [context, n, &possible_values](const auto& direct, const auto& inverse) {
680 // Propagate from the inverse vector to the direct vector.
681 for (int i = 0; i < n; ++i) {
682 possible_values.clear();
683 const Domain domain = context->DomainOf(direct[i]);
684 bool removed_value = false;
685 for (const int64_t j : domain.Values()) {
686 if (context->DomainOf(inverse[j]).Contains(i)) {
687 possible_values.push_back(j);
688 } else {
689 removed_value = true;
690 }
691 }
692 if (removed_value) {
693 if (!context->IntersectDomainWith(
694 direct[i], Domain::FromValues(possible_values))) {
695 VLOG(1) << "Empty domain for a variable in ExpandInverse()";
696 return false;
697 }
698 }
699 }
700 return true;
701 };
702
703 // Note that this should reach the fixed point in one pass.
704 // However, if we have duplicate variable, I am not sure.
705 if (!filter_inverse_domain(f_direct, f_inverse)) return;
706 if (!filter_inverse_domain(f_inverse, f_direct)) return;
707
708 // Expand the inverse constraint by associating literal to var == value
709 // and sharing them between the direct and inverse variables.
710 //
711 // Note that this is only correct because the domain are tight now.
712 for (int i = 0; i < n; ++i) {
713 const int f_i = f_direct[i];
714 for (const int64_t j : context->DomainOf(f_i).Values()) {
715 // We have f[i] == j <=> r[j] == i;
716 const int r_j = f_inverse[j];
717 int r_j_i;
718 if (context->HasVarValueEncoding(r_j, i, &r_j_i)) {
719 context->InsertVarValueEncoding(r_j_i, f_i, j);
720 } else {
721 const int f_i_j = context->GetOrCreateVarValueEncoding(f_i, j);
722 context->InsertVarValueEncoding(f_i_j, r_j, i);
723 }
724 }
725 }
726
727 ct->Clear();
728 context->UpdateRuleStats("inverse: expanded");
729}
730
731void ExpandLinMax(ConstraintProto* ct, PresolveContext* context) {
732 const int num_exprs = ct->lin_max().exprs().size();
733 if (num_exprs < 2) return;
734
735 // We have a special treatment for Abs, Earliness, Tardiness, and all
736 // affine_max where there is only one variable present in all the expressions.
737 if (ExpressionsContainsOnlyOneVar(ct->lin_max().exprs())) return;
738
739 // We will create 2 * num_exprs constraints for target = max(a1, .., an).
740
741 // First.
742 // - target >= ai
743 for (const LinearExpressionProto& expr : ct->lin_max().exprs()) {
744 ConstraintProto* new_ct = context->working_model->add_constraints();
745 LinearConstraintProto* lin = new_ct->mutable_linear();
746 lin->add_domain(0);
747 lin->add_domain(std::numeric_limits<int64_t>::max());
748 AddLinearExpressionToLinearConstraint(ct->lin_max().target(), 1, lin);
750 context->CanonicalizeLinearConstraint(new_ct);
751 }
752
753 // Second, for each expr, create a new boolean bi, and add bi => target >= ai
754 // With exactly_one(bi)
755 std::vector<int> enforcement_literals;
756 enforcement_literals.reserve(num_exprs);
757 if (num_exprs == 2) {
758 const int new_bool = context->NewBoolVar();
759 enforcement_literals.push_back(new_bool);
760 enforcement_literals.push_back(NegatedRef(new_bool));
761 } else {
762 ConstraintProto* exactly_one = context->working_model->add_constraints();
763 for (int i = 0; i < num_exprs; ++i) {
764 const int new_bool = context->NewBoolVar();
765 exactly_one->mutable_exactly_one()->add_literals(new_bool);
766 enforcement_literals.push_back(new_bool);
767 }
768 }
769
770 for (int i = 0; i < num_exprs; ++i) {
771 ConstraintProto* new_ct = context->working_model->add_constraints();
772 new_ct->add_enforcement_literal(enforcement_literals[i]);
773 LinearConstraintProto* lin = new_ct->mutable_linear();
774 lin->add_domain(std::numeric_limits<int64_t>::min());
775 lin->add_domain(0);
776 AddLinearExpressionToLinearConstraint(ct->lin_max().target(), 1, lin);
777 AddLinearExpressionToLinearConstraint(ct->lin_max().exprs(i), -1, lin);
778 context->CanonicalizeLinearConstraint(new_ct);
779 }
780
781 context->UpdateRuleStats("lin_max: expanded lin_max");
782 ct->Clear();
783}
784
785// A[V] == V means for all i, V == i => A_i == i
786void ExpandElementWithTargetEqualIndex(ConstraintProto* ct,
787 PresolveContext* context) {
788 const ElementConstraintProto& element = ct->element();
789 DCHECK_EQ(element.index(), element.target());
790
791 const int index_ref = element.index();
792 std::vector<int64_t> valid_indices;
793 for (const int64_t v : context->DomainOf(index_ref).Values()) {
794 if (!context->DomainContains(element.vars(v), v)) continue;
795 valid_indices.push_back(v);
796 }
797 if (valid_indices.size() < context->DomainOf(index_ref).Size()) {
798 if (!context->IntersectDomainWith(index_ref,
799 Domain::FromValues(valid_indices))) {
800 VLOG(1) << "No compatible variable domains in "
801 "ExpandElementWithTargetEqualIndex()";
802 return;
803 }
804 context->UpdateRuleStats("element: reduced index domain");
805 }
806
807 for (const int64_t v : context->DomainOf(index_ref).Values()) {
808 const int var = element.vars(v);
809 if (context->MinOf(var) == v && context->MaxOf(var) == v) continue;
810 context->AddImplyInDomain(
811 context->GetOrCreateVarValueEncoding(index_ref, v), var, Domain(v));
812 }
813 context->UpdateRuleStats(
814 "element: expanded with special case target = index");
815 ct->Clear();
816}
817
818// Special case if the array of the element is filled with constant values.
819void ExpandConstantArrayElement(ConstraintProto* ct, PresolveContext* context) {
820 const ElementConstraintProto& element = ct->element();
821 const int index_ref = element.index();
822 const int target_ref = element.target();
823
824 // Index and target domain have been reduced before calling this function.
825 const Domain index_domain = context->DomainOf(index_ref);
826 const Domain target_domain = context->DomainOf(target_ref);
827
828 // This BoolOrs implements the deduction that if all index literals pointing
829 // to the same value in the constant array are false, then this value is no
830 // no longer valid for the target variable. They are created only for values
831 // that have multiples literals supporting them.
832 // Order is not important.
833 absl::flat_hash_map<int64_t, BoolArgumentProto*> supports;
834 {
835 absl::flat_hash_map<int64_t, int> constant_var_values_usage;
836 for (const int64_t v : index_domain.Values()) {
837 DCHECK(context->IsFixed(element.vars(v)));
838 const int64_t value = context->MinOf(element.vars(v));
839 if (++constant_var_values_usage[value] == 2) {
840 // First time we cross > 1.
841 BoolArgumentProto* const support =
842 context->working_model->add_constraints()->mutable_bool_or();
843 const int target_literal =
844 context->GetOrCreateVarValueEncoding(target_ref, value);
845 support->add_literals(NegatedRef(target_literal));
846 supports[value] = support;
847 }
848 }
849 }
850
851 {
852 // While this is not strictly needed since all value in the index will be
853 // covered, it allows to easily detect this fact in the presolve.
854 auto* exactly_one =
855 context->working_model->add_constraints()->mutable_exactly_one();
856 for (const int64_t v : index_domain.Values()) {
857 const int index_literal =
858 context->GetOrCreateVarValueEncoding(index_ref, v);
859 exactly_one->add_literals(index_literal);
860
861 const int64_t value = context->MinOf(element.vars(v));
862 const auto& it = supports.find(value);
863 if (it != supports.end()) {
864 // The encoding literal for 'value' of the target_ref has been
865 // created before.
866 const int target_literal =
867 context->GetOrCreateVarValueEncoding(target_ref, value);
868 context->AddImplication(index_literal, target_literal);
869 it->second->add_literals(index_literal);
870 } else {
871 // Try to reuse the literal of the index.
872 context->InsertVarValueEncoding(index_literal, target_ref, value);
873 }
874 }
875 }
876
877 context->UpdateRuleStats("element: expanded value element");
878 ct->Clear();
879}
880
881// General element when the array contains non fixed variables.
882void ExpandVariableElement(ConstraintProto* ct, PresolveContext* context) {
883 const ElementConstraintProto& element = ct->element();
884 const int index_ref = element.index();
885 const int target_ref = element.target();
886 const Domain index_domain = context->DomainOf(index_ref);
887
888 BoolArgumentProto* exactly_one =
889 context->working_model->add_constraints()->mutable_exactly_one();
890
891 for (const int64_t v : index_domain.Values()) {
892 const int var = element.vars(v);
893 const Domain var_domain = context->DomainOf(var);
894 const int index_lit = context->GetOrCreateVarValueEncoding(index_ref, v);
895 exactly_one->add_literals(index_lit);
896
897 if (var_domain.IsFixed()) {
898 context->AddImplyInDomain(index_lit, target_ref, var_domain);
899 } else {
900 // We make sure we only use positive ref.
901 //
902 // TODO(user): Get rid of this code once we accept affine in element
903 // constraint.
904 ConstraintProto* const ct = context->working_model->add_constraints();
905 ct->add_enforcement_literal(index_lit);
906 if (RefIsPositive(var)) {
907 ct->mutable_linear()->add_vars(var);
908 ct->mutable_linear()->add_coeffs(1);
909 } else {
910 ct->mutable_linear()->add_vars(NegatedRef(var));
911 ct->mutable_linear()->add_coeffs(-1);
912 }
913 if (RefIsPositive(target_ref)) {
914 ct->mutable_linear()->add_vars(target_ref);
915 ct->mutable_linear()->add_coeffs(-1);
916 } else {
917 ct->mutable_linear()->add_vars(NegatedRef(target_ref));
918 ct->mutable_linear()->add_coeffs(1);
919 }
920 ct->mutable_linear()->add_domain(0);
921 ct->mutable_linear()->add_domain(0);
922
923 // Note that this should have been checked at model validation.
924 DCHECK(!PossibleIntegerOverflow(*context->working_model,
925 ct->mutable_linear()->vars(),
926 ct->mutable_linear()->coeffs()))
927 << google::protobuf::ShortFormat(*ct);
928 }
929 }
930
931 context->UpdateRuleStats("element: expanded");
932 ct->Clear();
933}
934
935void ExpandElement(ConstraintProto* ct, PresolveContext* context) {
936 const ElementConstraintProto& element = ct->element();
937
938 const int index_ref = element.index();
939 const int target_ref = element.target();
940 const int size = element.vars_size();
941
942 // Reduce the domain of the index to be compatible with the array of
943 // variables. Note that the element constraint is 0 based.
944 if (!context->IntersectDomainWith(index_ref, Domain(0, size - 1))) {
945 VLOG(1) << "Empty domain for the index variable in ExpandElement()";
946 return;
947 }
948
949 // Special case when index = target.
950 if (index_ref == target_ref) {
951 ExpandElementWithTargetEqualIndex(ct, context);
952 return;
953 }
954
955 // Reduces the domain of the index and the target.
956 bool all_constants = true;
957 std::vector<int64_t> valid_indices;
958 const Domain index_domain = context->DomainOf(index_ref);
959 const Domain target_domain = context->DomainOf(target_ref);
960 Domain reached_domain;
961 for (const int64_t v : index_domain.Values()) {
962 const Domain var_domain = context->DomainOf(element.vars(v));
963 if (var_domain.IntersectionWith(target_domain).IsEmpty()) continue;
964
965 valid_indices.push_back(v);
966 reached_domain = reached_domain.UnionWith(var_domain);
967 if (var_domain.Min() != var_domain.Max()) {
968 all_constants = false;
969 }
970 }
971
972 if (valid_indices.size() < index_domain.Size()) {
973 if (!context->IntersectDomainWith(index_ref,
974 Domain::FromValues(valid_indices))) {
975 VLOG(1) << "No compatible variable domains in ExpandElement()";
976 return;
977 }
978
979 context->UpdateRuleStats("element: reduced index domain");
980 }
981
982 // We know the target_domain is not empty as this would have triggered the
983 // above check.
984 bool target_domain_changed = false;
985 if (!context->IntersectDomainWith(target_ref, reached_domain,
986 &target_domain_changed)) {
987 return;
988 }
989
990 if (target_domain_changed) {
991 context->UpdateRuleStats("element: reduced target domain");
992 }
993
994 if (all_constants) {
995 ExpandConstantArrayElement(ct, context);
996 return;
997 }
998
999 ExpandVariableElement(ct, context);
1000}
1001
1002// Adds clauses so that literals[i] true <=> encoding[values[i]] true.
1003// This also implicitly use the fact that exactly one alternative is true.
1004void LinkLiteralsAndValues(const std::vector<int>& literals,
1005 const std::vector<int64_t>& values,
1006 const absl::flat_hash_map<int64_t, int>& encoding,
1007 PresolveContext* context) {
1008 CHECK_EQ(literals.size(), values.size());
1009
1010 // We use a map to make this method deterministic.
1011 //
1012 // TODO(user): Make sure this does not appear in the profile.
1013 absl::btree_map<int, std::vector<int>> encoding_lit_to_support;
1014
1015 // If a value is false (i.e not possible), then the tuple with this
1016 // value is false too (i.e not possible). Conversely, if the tuple is
1017 // selected, the value must be selected.
1018 for (int i = 0; i < values.size(); ++i) {
1019 encoding_lit_to_support[encoding.at(values[i])].push_back(literals[i]);
1020 }
1021
1022 // If all tuples supporting a value are false, then this value must be
1023 // false.
1024 for (const auto& [encoding_lit, support] : encoding_lit_to_support) {
1025 CHECK(!support.empty());
1026 if (support.size() == 1) {
1027 context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
1028 } else {
1029 BoolArgumentProto* bool_or =
1030 context->working_model->add_constraints()->mutable_bool_or();
1031 bool_or->add_literals(NegatedRef(encoding_lit));
1032 for (const int lit : support) {
1033 bool_or->add_literals(lit);
1034 context->AddImplication(lit, encoding_lit);
1035 }
1036 }
1037 }
1038}
1039
1040// Add the constraint literal => one_of(encoding[v]), for v in reachable_values.
1041// Note that all possible values are the ones appearing in encoding.
1042void AddImplyInReachableValues(int literal,
1043 std::vector<int64_t>& reachable_values,
1044 const absl::flat_hash_map<int64_t, int> encoding,
1045 PresolveContext* context) {
1046 gtl::STLSortAndRemoveDuplicates(&reachable_values);
1047 if (reachable_values.size() == encoding.size()) return; // No constraint.
1048 if (reachable_values.size() <= encoding.size() / 2) {
1049 // Bool or encoding.
1050 ConstraintProto* ct = context->working_model->add_constraints();
1051 ct->add_enforcement_literal(literal);
1052 BoolArgumentProto* bool_or = ct->mutable_bool_or();
1053 for (const int64_t v : reachable_values) {
1054 bool_or->add_literals(encoding.at(v));
1055 }
1056 } else {
1057 // Bool and encoding.
1058 absl::flat_hash_set<int64_t> set(reachable_values.begin(),
1059 reachable_values.end());
1060 ConstraintProto* ct = context->working_model->add_constraints();
1061 ct->add_enforcement_literal(literal);
1062 BoolArgumentProto* bool_and = ct->mutable_bool_and();
1063 for (const auto [value, literal] : encoding) {
1064 if (!set.contains(value)) {
1065 bool_and->add_literals(NegatedRef(literal));
1066 }
1067 }
1068 }
1069}
1070
1071void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
1072 AutomatonConstraintProto& proto = *ct->mutable_automaton();
1073
1074 if (proto.vars_size() == 0) {
1075 const int64_t initial_state = proto.starting_state();
1076 for (const int64_t final_state : proto.final_states()) {
1077 if (initial_state == final_state) {
1078 context->UpdateRuleStats("automaton: empty and trivially feasible");
1079 ct->Clear();
1080 return;
1081 }
1082 }
1083 return (void)context->NotifyThatModelIsUnsat(
1084 "automaton: empty with an initial state not in the final states.");
1085 } else if (proto.transition_label_size() == 0) {
1086 return (void)context->NotifyThatModelIsUnsat(
1087 "automaton: non-empty with no transition.");
1088 }
1089
1090 std::vector<absl::flat_hash_set<int64_t>> reachable_states;
1091 std::vector<absl::flat_hash_set<int64_t>> reachable_labels;
1092 PropagateAutomaton(proto, *context, &reachable_states, &reachable_labels);
1093
1094 // We will model at each time step the current automaton state using Boolean
1095 // variables. We will have n+1 time step. At time zero, we start in the
1096 // initial state, and at time n we should be in one of the final states. We
1097 // don't need to create Booleans at at time when there is just one possible
1098 // state (like at time zero).
1099 absl::flat_hash_map<int64_t, int> encoding;
1100 absl::flat_hash_map<int64_t, int> in_encoding;
1101 absl::flat_hash_map<int64_t, int> out_encoding;
1102 bool removed_values = false;
1103
1104 const int n = proto.vars_size();
1105 const std::vector<int> vars = {proto.vars().begin(), proto.vars().end()};
1106 for (int time = 0; time < n; ++time) {
1107 // All these vector have the same size. We will use them to enforce a
1108 // local table constraint representing one step of the automaton at the
1109 // given time.
1110 std::vector<int64_t> in_states;
1111 std::vector<int64_t> labels;
1112 std::vector<int64_t> out_states;
1113 for (int i = 0; i < proto.transition_label_size(); ++i) {
1114 const int64_t tail = proto.transition_tail(i);
1115 const int64_t label = proto.transition_label(i);
1116 const int64_t head = proto.transition_head(i);
1117
1118 if (!reachable_states[time].contains(tail)) continue;
1119 if (!reachable_states[time + 1].contains(head)) continue;
1120 if (!context->DomainContains(vars[time], label)) continue;
1121
1122 // TODO(user): if this transition correspond to just one in-state or
1123 // one-out state or one variable value, we could reuse the corresponding
1124 // Boolean variable instead of creating a new one!
1125 in_states.push_back(tail);
1126 labels.push_back(label);
1127
1128 // On the last step we don't need to distinguish the output states, so
1129 // we use zero.
1130 out_states.push_back(time + 1 == n ? 0 : head);
1131 }
1132
1133 // Deal with single tuple.
1134 const int num_tuples = in_states.size();
1135 if (num_tuples == 1) {
1136 if (!context->IntersectDomainWith(vars[time], Domain(labels.front()))) {
1137 VLOG(1) << "Infeasible automaton.";
1138 return;
1139 }
1140
1141 // Tricky: when the same variable is used more than once, the propagation
1142 // above might not reach the fixed point, so we do need to fix literal
1143 // at false.
1144 std::vector<int> at_false;
1145 for (const auto [value, literal] : in_encoding) {
1146 if (value != in_states[0]) at_false.push_back(literal);
1147 }
1148 for (const int literal : at_false) {
1149 if (!context->SetLiteralToFalse(literal)) return;
1150 }
1151
1152 in_encoding.clear();
1153 continue;
1154 }
1155
1156 // Fully encode vars[time].
1157 {
1158 std::vector<int64_t> transitions = labels;
1159 gtl::STLSortAndRemoveDuplicates(&transitions);
1160
1161 encoding.clear();
1162 if (!context->IntersectDomainWith(
1163 vars[time], Domain::FromValues(transitions), &removed_values)) {
1164 VLOG(1) << "Infeasible automaton.";
1165 return;
1166 }
1167
1168 // Fully encode the variable.
1169 // We can leave the encoding empty for fixed vars.
1170 if (!context->IsFixed(vars[time])) {
1171 for (const int64_t v : context->DomainOf(vars[time]).Values()) {
1172 encoding[v] = context->GetOrCreateVarValueEncoding(vars[time], v);
1173 }
1174 }
1175 }
1176
1177 // Count how many time each value appear.
1178 // We use this to reuse literals if possible.
1179 absl::flat_hash_map<int64_t, int> in_count;
1180 absl::flat_hash_map<int64_t, int> transition_count;
1181 absl::flat_hash_map<int64_t, int> out_count;
1182 for (int i = 0; i < num_tuples; ++i) {
1183 in_count[in_states[i]]++;
1184 transition_count[labels[i]]++;
1185 out_count[out_states[i]]++;
1186 }
1187
1188 // For each possible out states, create one Boolean variable.
1189 //
1190 // TODO(user): Add exactly one?
1191 {
1192 std::vector<int64_t> states = out_states;
1194
1195 out_encoding.clear();
1196 if (states.size() == 2) {
1197 const int var = context->NewBoolVar();
1198 out_encoding[states[0]] = var;
1199 out_encoding[states[1]] = NegatedRef(var);
1200 } else if (states.size() > 2) {
1201 struct UniqueDetector {
1202 void Set(int64_t v) {
1203 if (!is_unique) return;
1204 if (is_set) {
1205 if (v != value) is_unique = false;
1206 } else {
1207 is_set = true;
1208 value = v;
1209 }
1210 }
1211 bool is_set = false;
1212 bool is_unique = true;
1213 int64_t value = 0;
1214 };
1215
1216 // Optimization to detect if we have an in state that is only matched to
1217 // a single out state. Same with transition.
1218 absl::flat_hash_map<int64_t, UniqueDetector> out_to_in;
1219 absl::flat_hash_map<int64_t, UniqueDetector> out_to_transition;
1220 for (int i = 0; i < num_tuples; ++i) {
1221 out_to_in[out_states[i]].Set(in_states[i]);
1222 out_to_transition[out_states[i]].Set(labels[i]);
1223 }
1224
1225 for (const int64_t state : states) {
1226 // If we have a relation in_state <=> out_state, then we can reuse
1227 // the in Boolean and do not need to create a new one.
1228 if (!in_encoding.empty() && out_to_in[state].is_unique) {
1229 const int64_t unique_in = out_to_in[state].value;
1230 if (in_count[unique_in] == out_count[state]) {
1231 out_encoding[state] = in_encoding[unique_in];
1232 continue;
1233 }
1234 }
1235
1236 // Same if we have an unique transition value that correspond only to
1237 // this state.
1238 if (!encoding.empty() && out_to_transition[state].is_unique) {
1239 const int64_t unique_transition = out_to_transition[state].value;
1240 if (transition_count[unique_transition] == out_count[state]) {
1241 out_encoding[state] = encoding[unique_transition];
1242 continue;
1243 }
1244 }
1245
1246 out_encoding[state] = context->NewBoolVar();
1247 }
1248 }
1249 }
1250
1251 // Simple encoding. This is enough to properly enforce the constraint, but
1252 // it propagate less. It creates a lot less Booleans though. Note that we
1253 // use implicit "exactly one" on the encoding and do not add any extra
1254 // exactly one if the simple encoding is used.
1255 //
1256 // We currently decide which encoding to use depending on the number of new
1257 // literals needed by the "heavy" encoding compared to the number of states
1258 // and labels. When the automaton is small, using the full encoding is
1259 // better, see for instance on rotating-workforce_Example789 were the simple
1260 // encoding make the problem hard to solve but the full encoding allow the
1261 // solver to solve it in a couple of seconds!
1262 //
1263 // Note that both encoding create about the same number of constraints.
1264 const int num_involved_variables =
1265 in_encoding.size() + encoding.size() + out_encoding.size();
1266 const bool use_light_encoding = (num_tuples > num_involved_variables);
1267 if (use_light_encoding && !in_encoding.empty() && !encoding.empty() &&
1268 !out_encoding.empty()) {
1269 // Part 1: If a in_state is selected, restrict the set of possible labels.
1270 // We also restrict the set of possible out states, but this is not needed
1271 // for correctness.
1272 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_label;
1273 absl::flat_hash_map<int64_t, std::vector<int64_t>> in_to_out;
1274 for (int i = 0; i < num_tuples; ++i) {
1275 in_to_label[in_states[i]].push_back(labels[i]);
1276 in_to_out[in_states[i]].push_back(out_states[i]);
1277 }
1278 for (const auto [in_value, in_literal] : in_encoding) {
1279 AddImplyInReachableValues(in_literal, in_to_label[in_value], encoding,
1280 context);
1281 AddImplyInReachableValues(in_literal, in_to_out[in_value], out_encoding,
1282 context);
1283 }
1284
1285 // Part2, add all 3-clauses: (in_state, label) => out_state.
1286 for (int i = 0; i < num_tuples; ++i) {
1287 auto* bool_or =
1288 context->working_model->add_constraints()->mutable_bool_or();
1289 bool_or->add_literals(NegatedRef(in_encoding.at(in_states[i])));
1290 bool_or->add_literals(NegatedRef(encoding.at(labels[i])));
1291 bool_or->add_literals(out_encoding.at(out_states[i]));
1292 }
1293
1294 in_encoding.swap(out_encoding);
1295 out_encoding.clear();
1296 continue;
1297 }
1298
1299 // Create the tuple literals.
1300 //
1301 // TODO(user): Call and use the same heuristics as the table constraint to
1302 // expand this small table with 3 columns (i.e. compress, negate, etc...).
1303 std::vector<int> tuple_literals;
1304 if (num_tuples == 2) {
1305 const int bool_var = context->NewBoolVar();
1306 tuple_literals.push_back(bool_var);
1307 tuple_literals.push_back(NegatedRef(bool_var));
1308 } else {
1309 // Note that we do not need the ExactlyOneConstraint(tuple_literals)
1310 // because it is already implicitly encoded since we have exactly one
1311 // transition value. But adding one seems to help.
1312 BoolArgumentProto* exactly_one =
1313 context->working_model->add_constraints()->mutable_exactly_one();
1314 for (int i = 0; i < num_tuples; ++i) {
1315 int tuple_literal;
1316 if (in_count[in_states[i]] == 1 && !in_encoding.empty()) {
1317 tuple_literal = in_encoding[in_states[i]];
1318 } else if (transition_count[labels[i]] == 1 && !encoding.empty()) {
1319 tuple_literal = encoding[labels[i]];
1320 } else if (out_count[out_states[i]] == 1 && !out_encoding.empty()) {
1321 tuple_literal = out_encoding[out_states[i]];
1322 } else {
1323 tuple_literal = context->NewBoolVar();
1324 }
1325
1326 tuple_literals.push_back(tuple_literal);
1327 exactly_one->add_literals(tuple_literal);
1328 }
1329 }
1330
1331 if (!in_encoding.empty()) {
1332 LinkLiteralsAndValues(tuple_literals, in_states, in_encoding, context);
1333 }
1334 if (!encoding.empty()) {
1335 LinkLiteralsAndValues(tuple_literals, labels, encoding, context);
1336 }
1337 if (!out_encoding.empty()) {
1338 LinkLiteralsAndValues(tuple_literals, out_states, out_encoding, context);
1339 }
1340
1341 in_encoding.swap(out_encoding);
1342 out_encoding.clear();
1343 }
1344
1345 if (removed_values) {
1346 context->UpdateRuleStats("automaton: reduced variable domains");
1347 }
1348 context->UpdateRuleStats("automaton: expanded");
1349 ct->Clear();
1350}
1351
1352void ExpandNegativeTable(ConstraintProto* ct, PresolveContext* context) {
1353 TableConstraintProto& table = *ct->mutable_table();
1354 const int num_vars = table.vars_size();
1355 const int num_original_tuples = table.values_size() / num_vars;
1356 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1357 int count = 0;
1358 for (int i = 0; i < num_original_tuples; ++i) {
1359 for (int j = 0; j < num_vars; ++j) {
1360 tuples[i].push_back(table.values(count++));
1361 }
1362 }
1363
1364 if (tuples.empty()) { // Early exit.
1365 context->UpdateRuleStats("table: empty negated constraint");
1366 ct->Clear();
1367 return;
1368 }
1369
1370 // Compress tuples.
1371 std::vector<int64_t> domain_sizes;
1372 for (int i = 0; i < num_vars; ++i) {
1373 domain_sizes.push_back(context->DomainOf(table.vars(i)).Size());
1374 }
1375 CompressTuples(domain_sizes, &tuples);
1376
1377 // For each tuple, forbid the variables values to be this tuple.
1378 std::vector<int> clause;
1379 for (const std::vector<int64_t>& tuple : tuples) {
1380 clause.clear();
1381 for (int i = 0; i < num_vars; ++i) {
1382 const int64_t value = tuple[i];
1383 if (value == kTableAnyValue) continue;
1384
1385 const int literal =
1386 context->GetOrCreateVarValueEncoding(table.vars(i), value);
1387 clause.push_back(NegatedRef(literal));
1388 }
1389
1390 // Note: if the clause is empty, then the model is infeasible.
1391 ConstraintProto* tuple_ct = context->working_model->add_constraints();
1392 *tuple_ct->mutable_enforcement_literal() = ct->enforcement_literal();
1393 BoolArgumentProto* bool_or = tuple_ct->mutable_bool_or();
1394 for (const int lit : clause) {
1395 bool_or->add_literals(lit);
1396 }
1397 }
1398 context->UpdateRuleStats("table: expanded negated constraint");
1399 ct->Clear();
1400}
1401
1402// Add the implications and clauses to link one variable (i.e. column) of a
1403// table to the literals controlling if the tuples are possible or not.
1404//
1405// We list for each tuple the possible values the variable can take.
1406// If the list is empty, then this encode "any value".
1407void ProcessOneCompressedColumn(
1408 int variable, const std::vector<int>& tuple_literals,
1409 const std::vector<absl::InlinedVector<int64_t, 2>>& values,
1410 std::optional<int> table_is_active_literal, PresolveContext* context) {
1411 DCHECK_EQ(tuple_literals.size(), values.size());
1412
1413 // Collect pairs of value-literal.
1414 // Add the constraint literal => one of values.
1415 //
1416 // TODO(user): If we have n - 1 values, we could add the constraint that
1417 // tuple literal => not(last_value) instead?
1418 std::vector<std::pair<int64_t, int>> pairs;
1419 std::vector<int> any_values_literals;
1420 for (int i = 0; i < values.size(); ++i) {
1421 if (values[i].empty()) {
1422 any_values_literals.push_back(tuple_literals[i]);
1423 continue;
1424 }
1425
1426 ConstraintProto* ct = context->working_model->add_constraints();
1427 ct->add_enforcement_literal(tuple_literals[i]);
1428
1429 // It is slightly better to use a bool_and if size is 1 instead of
1430 // reconverting it at a later stage.
1431 auto* literals =
1432 values[i].size() == 1 ? ct->mutable_bool_and() : ct->mutable_bool_or();
1433 for (const int64_t v : values[i]) {
1434 DCHECK(context->DomainContains(variable, v));
1435 literals->add_literals(context->GetOrCreateVarValueEncoding(variable, v));
1436 pairs.emplace_back(v, tuple_literals[i]);
1437 }
1438 }
1439
1440 // Regroup literal with the same value and add for each the clause: If all the
1441 // tuples containing a value are false, then this value must be false too.
1442 std::vector<int> selected;
1443 std::sort(pairs.begin(), pairs.end());
1444 for (int i = 0; i < pairs.size();) {
1445 selected.clear();
1446 const int64_t value = pairs[i].first;
1447 for (; i < pairs.size() && pairs[i].first == value; ++i) {
1448 selected.push_back(pairs[i].second);
1449 }
1450
1451 // A value is supported if one tuple is still active, or a covering 'any'
1452 // tuple is still active, or the table can still be inactive.
1453 BoolArgumentProto* no_support =
1454 context->working_model->add_constraints()->mutable_bool_or();
1455 for (const int lit : selected) {
1456 no_support->add_literals(lit);
1457 }
1458 for (const int lit : any_values_literals) {
1459 no_support->add_literals(lit);
1460 }
1461 if (table_is_active_literal.has_value()) {
1462 no_support->add_literals(NegatedRef(table_is_active_literal.value()));
1463 }
1464
1465 // And the "value" literal.
1466 const int value_literal =
1467 context->GetOrCreateVarValueEncoding(variable, value);
1468 no_support->add_literals(NegatedRef(value_literal));
1469 }
1470}
1471
1472// Simpler encoding for table constraints with 2 variables.
1473void AddSizeTwoTable(
1474 const std::vector<int>& vars,
1475 const std::vector<std::vector<int64_t>>& tuples,
1476 const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
1477 PresolveContext* context) {
1478 CHECK_EQ(vars.size(), 2);
1479 const int left_var = vars[0];
1480 const int right_var = vars[1];
1481 if (context->DomainOf(left_var).IsFixed() ||
1482 context->DomainOf(right_var).IsFixed()) {
1483 // A table constraint with at most one variable not fixed is trivially
1484 // enforced after domain reduction.
1485 return;
1486 }
1487
1488 absl::btree_map<int, std::vector<int>> left_to_right;
1489 absl::btree_map<int, std::vector<int>> right_to_left;
1490
1491 for (const auto& tuple : tuples) {
1492 const int64_t left_value(tuple[0]);
1493 const int64_t right_value(tuple[1]);
1494 DCHECK(context->DomainContains(left_var, left_value));
1495 DCHECK(context->DomainContains(right_var, right_value));
1496
1497 const int left_literal =
1498 context->GetOrCreateVarValueEncoding(left_var, left_value);
1499 const int right_literal =
1500 context->GetOrCreateVarValueEncoding(right_var, right_value);
1501 left_to_right[left_literal].push_back(right_literal);
1502 right_to_left[right_literal].push_back(left_literal);
1503 }
1504
1505 int num_implications = 0;
1506 int num_clause_added = 0;
1507 int num_large_clause_added = 0;
1508 auto add_support_constraint =
1509 [context, &num_clause_added, &num_large_clause_added, &num_implications](
1510 int lit, const std::vector<int>& support_literals,
1511 int max_support_size) {
1512 if (support_literals.size() == max_support_size) return;
1513 if (support_literals.size() == 1) {
1514 context->AddImplication(lit, support_literals.front());
1515 num_implications++;
1516 } else {
1517 BoolArgumentProto* bool_or =
1518 context->working_model->add_constraints()->mutable_bool_or();
1519 for (const int support_literal : support_literals) {
1520 bool_or->add_literals(support_literal);
1521 }
1522 bool_or->add_literals(NegatedRef(lit));
1523 num_clause_added++;
1524 if (support_literals.size() > max_support_size / 2) {
1525 num_large_clause_added++;
1526 }
1527 }
1528 };
1529
1530 for (const auto& it : left_to_right) {
1531 add_support_constraint(it.first, it.second, values_per_var[1].size());
1532 }
1533 for (const auto& it : right_to_left) {
1534 add_support_constraint(it.first, it.second, values_per_var[0].size());
1535 }
1536 VLOG(2) << "Table: 2 variables, " << tuples.size() << " tuples encoded using "
1537 << num_clause_added << " clauses, including "
1538 << num_large_clause_added << " large clauses, " << num_implications
1539 << " implications";
1540}
1541
1542// A "WCSP" (weighted constraint programming) problem is usually encoded as
1543// a set of table, with one or more variable only there to carry a cost.
1544//
1545// If this is the case, we can do special presolving.
1546bool ReduceTableInPresenceOfUniqueVariableWithCosts(
1547 std::vector<int>* vars, std::vector<std::vector<int64_t>>* tuples,
1548 PresolveContext* context) {
1549 const int num_vars = vars->size();
1550
1551 std::vector<bool> only_here_and_in_objective(num_vars, false);
1552 std::vector<int64_t> objective_coeffs(num_vars, 0.0);
1553 std::vector<int> new_vars;
1554 std::vector<int> deleted_vars;
1555 for (int var_index = 0; var_index < num_vars; ++var_index) {
1556 const int var = (*vars)[var_index];
1557
1558 // We do not use VariableWithCostIsUniqueAndRemovable() since this one
1559 // return false if the objective is constraining but we don't care here.
1560 // Our transformation also do not loose solutions.
1561 if (context->VariableWithCostIsUnique(var)) {
1562 context->UpdateRuleStats("table: removed unused column with cost");
1563 only_here_and_in_objective[var_index] = true;
1564 objective_coeffs[var_index] =
1565 RefIsPositive(var) ? context->ObjectiveMap().at(var)
1566 : -context->ObjectiveMap().at(PositiveRef(var));
1567 context->RemoveVariableFromObjective(var);
1568 context->MarkVariableAsRemoved(var);
1569 deleted_vars.push_back(var);
1570 } else if (context->VarToConstraints(var).size() == 1) {
1571 // If there is no cost, we can remove that variable using the same code by
1572 // just setting the cost to zero.
1573 context->UpdateRuleStats("table: removed unused column");
1574 only_here_and_in_objective[var_index] = true;
1575 objective_coeffs[var_index] = 0;
1576 context->MarkVariableAsRemoved(var);
1577 deleted_vars.push_back(var);
1578 } else {
1579 new_vars.push_back(var);
1580 }
1581 }
1582 if (new_vars.size() == num_vars) return false;
1583
1584 // Rewrite the tuples.
1585 // put the cost last.
1586 int64_t min_cost = std::numeric_limits<int64_t>::max();
1587 std::vector<int64_t> temp;
1588 for (int i = 0; i < tuples->size(); ++i) {
1589 int64_t cost = 0;
1590 int new_size = 0;
1591 temp.clear();
1592 for (int var_index = 0; var_index < num_vars; ++var_index) {
1593 const int64_t value = (*tuples)[i][var_index];
1594 if (only_here_and_in_objective[var_index]) {
1595 temp.push_back(value);
1596 const int64_t objective_coeff = objective_coeffs[var_index];
1597 cost += value * objective_coeff;
1598 } else {
1599 (*tuples)[i][new_size++] = value;
1600 }
1601 }
1602 (*tuples)[i].resize(new_size);
1603 (*tuples)[i].push_back(cost);
1604 min_cost = std::min(min_cost, cost);
1605
1606 // Hack: we store the deleted value here so that we can properly encode
1607 // the postsolve constraints below.
1608 (*tuples)[i].insert((*tuples)[i].end(), temp.begin(), temp.end());
1609 }
1610
1611 // Remove tuples that only differ by their cost.
1612 // Make sure we will assign the proper value of the removed variable at
1613 // postsolve.
1614 {
1615 int new_size = 0;
1616 const int old_size = tuples->size();
1617 std::sort(tuples->begin(), tuples->end());
1618 for (int i = 0; i < tuples->size(); ++i) {
1619 // If the prefix (up to new_vars.size()) is the same, skip this tuple.
1620 if (new_size > 0) {
1621 bool skip = true;
1622 for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1623 if ((*tuples)[i][var_index] != (*tuples)[new_size - 1][var_index]) {
1624 skip = false;
1625 break;
1626 }
1627 }
1628 if (skip) continue;
1629 }
1630
1631 // If this tuple is selected, then fix the removed variable value in the
1632 // mapping model.
1633 for (int j = 0; j < deleted_vars.size(); ++j) {
1634 ConstraintProto* mapping_ct =
1635 context->NewMappingConstraint(__FILE__, __LINE__);
1636 for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1637 mapping_ct->add_enforcement_literal(
1638 context->GetOrCreateVarValueEncoding(new_vars[var_index],
1639 (*tuples)[i][var_index]));
1640 }
1641 LinearConstraintProto* new_lin = mapping_ct->mutable_linear();
1642 new_lin->add_vars(deleted_vars[j]);
1643 new_lin->add_coeffs(1);
1644 new_lin->add_domain((*tuples)[i][new_vars.size() + 1 + j]);
1645 new_lin->add_domain((*tuples)[i][new_vars.size() + 1 + j]);
1646 }
1647 (*tuples)[i].resize(new_vars.size() + 1);
1648 (*tuples)[new_size++] = (*tuples)[i];
1649 }
1650 tuples->resize(new_size);
1651 if (new_size < old_size) {
1652 context->UpdateRuleStats(
1653 "table: removed duplicate tuples with different costs");
1654 }
1655 }
1656
1657 if (min_cost > 0) {
1658 context->AddToObjectiveOffset(min_cost);
1659 context->UpdateRuleStats("table: transferred min_cost to objective offset");
1660 for (int i = 0; i < tuples->size(); ++i) {
1661 (*tuples)[i].back() -= min_cost;
1662 }
1663 }
1664
1665 // This comes from the WCSP litterature. Basically, if by fixing a variable to
1666 // a value, we have only tuples with a non-zero cost, we can substract the
1667 // minimum cost of these tuples and transfer it to the variable cost.
1668 //
1669 // TODO(user): Doing this before table compression can prevent good
1670 // compression. We should probably exploit this during compression to make
1671 // sure we compress as much as possible, and once compressed, do it again. Or
1672 // do it in a more general IP settings when one literal implies that a set of
1673 // literals with >0 cost are in EXO. We can transfer the min of their cost to
1674 // that Boolean.
1675 if (/*DISABLES CODE*/ (false)) {
1676 for (int var_index = 0; var_index < new_vars.size(); ++var_index) {
1677 absl::flat_hash_map<int64_t, int64_t> value_to_min_cost;
1678 const int num_tuples = tuples->size();
1679 for (int i = 0; i < num_tuples; ++i) {
1680 const int64_t v = (*tuples)[i][var_index];
1681 const int64_t cost = (*tuples)[i].back();
1682 auto insert = value_to_min_cost.insert({v, cost});
1683 if (!insert.second) {
1684 insert.first->second = std::min(insert.first->second, cost);
1685 }
1686 }
1687 for (int i = 0; i < num_tuples; ++i) {
1688 const int64_t v = (*tuples)[i][var_index];
1689 (*tuples)[i].back() -= value_to_min_cost.at(v);
1690 }
1691 for (const auto entry : value_to_min_cost) {
1692 if (entry.second == 0) continue;
1693 context->UpdateRuleStats("table: transferred cost to encoding");
1694 const int value_literal = context->GetOrCreateVarValueEncoding(
1695 new_vars[var_index], entry.first);
1696 context->AddLiteralToObjective(value_literal, entry.second);
1697 }
1698 }
1699 }
1700
1701 context->UpdateRuleStats(absl::StrCat(
1702 "table: expansion with column(s) only in objective. Arity = ",
1703 new_vars.size()));
1704
1705 *vars = new_vars;
1706 return true;
1707}
1708
1709// Important: the table and variable domains must be pre-solved before this
1710// is called. Some checks will fail otherwise.
1711void CompressAndExpandPositiveTable(ConstraintProto* ct,
1712 bool last_column_is_cost,
1713 const std::vector<int>& vars,
1714 std::vector<std::vector<int64_t>>* tuples,
1715 PresolveContext* context) {
1716 const int num_tuples_before_compression = tuples->size();
1717
1718 // If the last column is actually the tuple cost, we compress the table like
1719 // if this was a normal variable, but afterwards we treat it differently.
1720 std::vector<int64_t> domain_sizes;
1721 for (const int var : vars) {
1722 domain_sizes.push_back(context->DomainOf(var).Size());
1723 }
1724 if (last_column_is_cost) {
1725 domain_sizes.push_back(std::numeric_limits<int64_t>::max());
1726 }
1727
1728 // We start by compressing the table with kTableAnyValue only.
1729 const int compression_level = context->params().table_compression_level();
1730 if (compression_level > 0) {
1731 CompressTuples(domain_sizes, tuples);
1732 }
1733 const int num_tuples_after_first_compression = tuples->size();
1734
1735 // Tricky: If the table is big, it is better to compress it as much as
1736 // possible to reduce the number of created booleans. Otherwise, the more
1737 // verbose encoding can lead to better linear relaxation. Probably because the
1738 // tuple literal can encode each variable as sum literal * value. Also because
1739 // we have more direct implied bounds, which might lead to better cuts.
1740 //
1741 // For instance, on lot_sizing_cp_pigment15c.psp, compressing the table more
1742 // is a lot worse (at least until we can produce better cut).
1743 //
1744 // TODO(user): Tweak the heuristic, maybe compute the reduction achieve and
1745 // decide based on that.
1746 std::vector<std::vector<absl::InlinedVector<int64_t, 2>>> compressed_table;
1747 if (compression_level > 2 ||
1748 (compression_level == 2 && num_tuples_after_first_compression > 1000)) {
1749 compressed_table = FullyCompressTuples(domain_sizes, tuples);
1750 if (compressed_table.size() < num_tuples_before_compression) {
1751 context->UpdateRuleStats("table: fully compress tuples");
1752 }
1753 } else {
1754 // Convert the kTableAnyValue to an empty list format.
1755 for (int i = 0; i < tuples->size(); ++i) {
1756 compressed_table.push_back({});
1757 for (const int64_t v : (*tuples)[i]) {
1758 if (v == kTableAnyValue) {
1759 compressed_table.back().push_back({});
1760 } else {
1761 compressed_table.back().push_back({v});
1762 }
1763 }
1764 }
1765 if (compressed_table.size() < num_tuples_before_compression) {
1766 context->UpdateRuleStats("table: compress tuples");
1767 }
1768 }
1769
1770 VLOG(2) << "Table compression" << " var=" << vars.size()
1771 << " cost=" << domain_sizes.size() - vars.size()
1772 << " tuples= " << num_tuples_before_compression << " -> "
1773 << num_tuples_after_first_compression << " -> "
1774 << compressed_table.size();
1775
1776 // Affect mznc2017_aes_opt_r10 instance!
1777 std::sort(compressed_table.begin(), compressed_table.end());
1778
1779 const int num_vars = vars.size();
1780 if (compressed_table.size() == 1 && ct->enforcement_literal().empty()) {
1781 // Domains are propagated. We can remove the constraint.
1782 context->UpdateRuleStats("table: one tuple");
1783 if (last_column_is_cost) {
1784 // TODO(user): Because we transfer the cost, this should always be zero,
1785 // so not needed.
1786 context->AddToObjectiveOffset(compressed_table[0].back()[0]);
1787 }
1788 return;
1789 }
1790
1791 // Optimization. If a value is unique and appear alone in a cell, we can use
1792 // the encoding literal for this line tuple literal instead of creating a new
1793 // one.
1794 std::vector<bool> has_any(num_vars, false);
1795 std::vector<absl::flat_hash_map<int64_t, int>> var_index_to_value_count(
1796 num_vars);
1797 for (int i = 0; i < compressed_table.size(); ++i) {
1798 for (int var_index = 0; var_index < num_vars; ++var_index) {
1799 if (compressed_table[i][var_index].empty()) {
1800 has_any[var_index] = true;
1801 continue;
1802 }
1803 for (const int64_t v : compressed_table[i][var_index]) {
1804 DCHECK_NE(v, kTableAnyValue);
1805 DCHECK(context->DomainContains(vars[var_index], v));
1806 var_index_to_value_count[var_index][v]++;
1807 }
1808 }
1809 }
1810
1811 // Create one Boolean variable per tuple to indicate if it can still be
1812 // selected or not. Enforce an exactly one between them.
1813 BoolArgumentProto* exactly_one =
1814 context->working_model->add_constraints()->mutable_exactly_one();
1815
1816 std::optional<int> table_is_active_literal = std::nullopt;
1817 // Process enforcement literals.
1818 if (ct->enforcement_literal().size() == 1) {
1819 table_is_active_literal = ct->enforcement_literal(0);
1820 } else if (ct->enforcement_literal().size() > 1) {
1821 table_is_active_literal = context->NewBoolVar();
1822
1823 // Adds table_is_active <=> and(enforcement_literals).
1824 BoolArgumentProto* bool_or =
1825 context->working_model->add_constraints()->mutable_bool_or();
1826 bool_or->add_literals(table_is_active_literal.value());
1827 for (const int lit : ct->enforcement_literal()) {
1828 context->AddImplication(table_is_active_literal.value(), lit);
1829 bool_or->add_literals(NegatedRef(lit));
1830 }
1831 }
1832
1833 int64_t num_reused_variables = 0;
1834 std::vector<int> tuple_literals(compressed_table.size());
1835 for (int i = 0; i < compressed_table.size(); ++i) {
1836 bool create_new_var = true;
1837 for (int var_index = 0; var_index < num_vars; ++var_index) {
1838 if (has_any[var_index]) continue;
1839 if (compressed_table[i][var_index].size() != 1 ||
1840 !ct->enforcement_literal().empty()) {
1841 continue;
1842 }
1843 const int64_t v = compressed_table[i][var_index][0];
1844 if (var_index_to_value_count[var_index][v] != 1) continue;
1845
1846 ++num_reused_variables;
1847 create_new_var = false;
1848 tuple_literals[i] =
1849 context->GetOrCreateVarValueEncoding(vars[var_index], v);
1850 break;
1851 }
1852 if (create_new_var) {
1853 tuple_literals[i] = context->NewBoolVar();
1854 }
1855 exactly_one->add_literals(tuple_literals[i]);
1856 }
1857 if (num_reused_variables > 0) {
1858 context->UpdateRuleStats("table: reused literals");
1859 }
1860
1861 // Set the cost to the corresponding tuple literal. If there is more than one
1862 // cost, we just choose the first one which is the smallest one.
1863 if (last_column_is_cost) {
1864 for (int i = 0; i < tuple_literals.size(); ++i) {
1865 context->AddLiteralToObjective(tuple_literals[i],
1866 compressed_table[i].back()[0]);
1867 }
1868 }
1869
1870 std::vector<absl::InlinedVector<int64_t, 2>> column;
1871 for (int var_index = 0; var_index < num_vars; ++var_index) {
1872 if (context->IsFixed(vars[var_index])) continue;
1873
1874 column.clear();
1875 for (int i = 0; i < tuple_literals.size(); ++i) {
1876 column.push_back(compressed_table[i][var_index]);
1877 }
1878 ProcessOneCompressedColumn(vars[var_index], tuple_literals, column,
1879 table_is_active_literal, context);
1880 }
1881
1882 if (table_is_active_literal.has_value()) {
1883 exactly_one->add_literals(NegatedRef(table_is_active_literal.value()));
1884 }
1885
1886 context->UpdateRuleStats("table: expanded positive constraint");
1887}
1888
1889// TODO(user): reinvestigate ExploreSubsetOfVariablesAndAddNegatedTables.
1890//
1891// TODO(user): if 2 table constraints share the same valid prefix, the
1892// tuple literals can be reused.
1893//
1894// TODO(user): investigate different encoding for prefix tables. Maybe
1895// we can remove the need to create tuple literals.
1896void ExpandPositiveTable(ConstraintProto* ct, PresolveContext* context) {
1897 const TableConstraintProto& table = ct->table();
1898 const int num_vars = table.vars_size();
1899 const int num_original_tuples = table.values_size() / num_vars;
1900
1901 // Read tuples flat array and recreate the vector of tuples.
1902 std::vector<int> vars(table.vars().begin(), table.vars().end());
1903 std::vector<std::vector<int64_t>> tuples(num_original_tuples);
1904 int count = 0;
1905 for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1906 for (int var_index = 0; var_index < num_vars; ++var_index) {
1907 tuples[tuple_index].push_back(table.values(count++));
1908 }
1909 }
1910
1911 // Compute the set of possible values for each variable (from the table).
1912 // Remove invalid tuples along the way.
1913 std::vector<absl::flat_hash_set<int64_t>> values_per_var(num_vars);
1914 int new_size = 0;
1915 for (int tuple_index = 0; tuple_index < num_original_tuples; ++tuple_index) {
1916 bool keep = true;
1917 for (int var_index = 0; var_index < num_vars; ++var_index) {
1918 const int64_t value = tuples[tuple_index][var_index];
1919 if (!context->DomainContains(vars[var_index], value)) {
1920 keep = false;
1921 break;
1922 }
1923 }
1924 if (keep) {
1925 for (int var_index = 0; var_index < num_vars; ++var_index) {
1926 values_per_var[var_index].insert(tuples[tuple_index][var_index]);
1927 }
1928 std::swap(tuples[tuple_index], tuples[new_size]);
1929 new_size++;
1930 }
1931 }
1932 tuples.resize(new_size);
1933
1934 if (tuples.empty()) {
1935 if (ct->enforcement_literal().empty()) {
1936 context->UpdateRuleStats("table: empty");
1937 return (void)context->NotifyThatModelIsUnsat();
1938 } else {
1939 context->UpdateRuleStats("table: enforced and empty");
1940 BoolArgumentProto* bool_or =
1941 context->working_model->add_constraints()->mutable_bool_or();
1942 for (const int lit : ct->enforcement_literal()) {
1943 bool_or->add_literals(NegatedRef(lit));
1944 }
1945 ct->Clear();
1946 return;
1947 }
1948 }
1949
1950 // Update variable domains. It is redundant with presolve, but we could be
1951 // here with presolve = false.
1952 // Also counts the number of fixed variables.
1953 if (ct->enforcement_literal().empty()) {
1954 int num_fixed_variables = 0;
1955 for (int var_index = 0; var_index < num_vars; ++var_index) {
1956 CHECK(context->IntersectDomainWith(
1957 vars[var_index],
1958 Domain::FromValues({values_per_var[var_index].begin(),
1959 values_per_var[var_index].end()})));
1960 if (context->DomainOf(vars[var_index]).IsFixed()) {
1961 num_fixed_variables++;
1962 }
1963 }
1964
1965 if (num_fixed_variables == num_vars - 1) {
1966 context->UpdateRuleStats("table: one variable not fixed");
1967 ct->Clear();
1968 return;
1969 } else if (num_fixed_variables == num_vars) {
1970 context->UpdateRuleStats("table: all variables fixed");
1971 ct->Clear();
1972 return;
1973 }
1974 }
1975
1976 // Tables with two variables do not need tuple literals.
1977 //
1978 // TODO(user): If there is an unique variable with cost, it is better to
1979 // detect it. But if the detection fail, we should still call
1980 // AddSizeTwoTable() unlike what happen here.
1981 if (num_vars == 2 && !context->params().detect_table_with_cost() &&
1982 ct->enforcement_literal().empty()) {
1983 AddSizeTwoTable(vars, tuples, values_per_var, context);
1984 context->UpdateRuleStats(
1985 "table: expanded positive constraint with two variables");
1986 ct->Clear();
1987 return;
1988 }
1989
1990 bool last_column_is_cost = false;
1991 if (context->params().detect_table_with_cost() &&
1992 ct->enforcement_literal().empty()) {
1993 last_column_is_cost =
1994 ReduceTableInPresenceOfUniqueVariableWithCosts(&vars, &tuples, context);
1995 }
1996
1997 CompressAndExpandPositiveTable(ct, last_column_is_cost, vars, &tuples,
1998 context);
1999 ct->Clear();
2000}
2001
2002bool AllDiffShouldBeExpanded(const Domain& union_of_domains,
2003 ConstraintProto* ct, PresolveContext* context) {
2004 const AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
2005 const int num_exprs = proto.exprs_size();
2006 int num_fully_encoded = 0;
2007 for (int i = 0; i < num_exprs; ++i) {
2008 if (context->IsFullyEncoded(proto.exprs(i))) {
2009 num_fully_encoded++;
2010 }
2011 }
2012
2013 if ((union_of_domains.Size() <= 2 * proto.exprs_size()) ||
2014 (union_of_domains.Size() <= 32)) {
2015 // Small domains.
2016 return true;
2017 }
2018
2019 if (num_fully_encoded == num_exprs && union_of_domains.Size() < 256) {
2020 // All variables fully encoded, and domains are small enough.
2021 return true;
2022 }
2023 return false;
2024}
2025
2026// Replaces a constraint literal => ax + by != cte by a set of clauses.
2027// This is performed if the domains are small enough, and the variables are
2028// fully encoded.
2029//
2030// We do it during the expansion as we want the first pass of the presolve to be
2031// complete.
2032void ExpandSomeLinearOfSizeTwo(ConstraintProto* ct, PresolveContext* context) {
2033 const LinearConstraintProto& arg = ct->linear();
2034 if (arg.vars_size() != 2) return;
2035
2036 const int var1 = arg.vars(0);
2037 const int var2 = arg.vars(1);
2038 if (context->IsFixed(var1) || context->IsFixed(var2)) return;
2039
2040 const int64_t coeff1 = arg.coeffs(0);
2041 const int64_t coeff2 = arg.coeffs(1);
2042 const Domain reachable_rhs_superset =
2043 context->DomainOf(var1)
2044 .MultiplicationBy(coeff1)
2045 .RelaxIfTooComplex()
2046 .AdditionWith(context->DomainOf(var2)
2047 .MultiplicationBy(coeff2)
2048 .RelaxIfTooComplex());
2049 const Domain infeasible_reachable_values =
2050 reachable_rhs_superset.IntersectionWith(
2051 ReadDomainFromProto(arg).Complement());
2052
2053 // We only deal with != cte constraints.
2054 if (infeasible_reachable_values.Size() != 1) return;
2055
2056 // coeff1 * v1 + coeff2 * v2 != cte.
2057 int64_t a = coeff1;
2058 int64_t b = coeff2;
2059 int64_t cte = infeasible_reachable_values.FixedValue();
2060 int64_t x0 = 0;
2061 int64_t y0 = 0;
2062 if (!SolveDiophantineEquationOfSizeTwo(a, b, cte, x0, y0)) {
2063 // no solution.
2064 context->UpdateRuleStats("linear: expand always feasible ax + by != cte");
2065 ct->Clear();
2066 return;
2067 }
2068 const Domain reduced_domain =
2069 context->DomainOf(var1)
2070 .AdditionWith(Domain(-x0))
2071 .InverseMultiplicationBy(b)
2072 .IntersectionWith(context->DomainOf(var2)
2073 .AdditionWith(Domain(-y0))
2074 .InverseMultiplicationBy(-a));
2075
2076 if (reduced_domain.Size() > 16) return;
2077
2078 // Check if all the needed values are encoded.
2079 // TODO(user): Do we force encoding for very small domains? Current
2080 // experiments says no, but revisit later.
2081 const int64_t size1 = context->DomainOf(var1).Size();
2082 const int64_t size2 = context->DomainOf(var2).Size();
2083 for (const int64_t z : reduced_domain.Values()) {
2084 const int64_t value1 = x0 + b * z;
2085 const int64_t value2 = y0 - a * z;
2086 DCHECK(context->DomainContains(var1, value1)) << "value1 = " << value1;
2087 DCHECK(context->DomainContains(var2, value2)) << "value2 = " << value2;
2088 DCHECK_EQ(coeff1 * value1 + coeff2 * value2,
2089 infeasible_reachable_values.FixedValue());
2090 // TODO(user): Presolve if one or two variables are Boolean.
2091 if (!context->HasVarValueEncoding(var1, value1, nullptr) || size1 == 2) {
2092 return;
2093 }
2094 if (!context->HasVarValueEncoding(var2, value2, nullptr) || size2 == 2) {
2095 return;
2096 }
2097 }
2098
2099 // All encoding literals already exist and the number of clauses to create
2100 // is small enough. We can encode the constraint using just clauses.
2101 for (const int64_t z : reduced_domain.Values()) {
2102 const int64_t value1 = x0 + b * z;
2103 const int64_t value2 = y0 - a * z;
2104 // We cannot have both lit1 and lit2 true.
2105 const int lit1 = context->GetOrCreateVarValueEncoding(var1, value1);
2106 const int lit2 = context->GetOrCreateVarValueEncoding(var2, value2);
2107 auto* bool_or =
2108 context->working_model->add_constraints()->mutable_bool_or();
2109 bool_or->add_literals(NegatedRef(lit1));
2110 bool_or->add_literals(NegatedRef(lit2));
2111 for (const int lit : ct->enforcement_literal()) {
2112 bool_or->add_literals(NegatedRef(lit));
2113 }
2114 }
2115
2116 context->UpdateRuleStats("linear: expand small ax + by != cte");
2117 ct->Clear();
2118}
2119
2120// Note that we used to do that at loading time, but we prefer to do that as
2121// part of the presolve so that all variables are available for sharing between
2122// subworkers and also are accessible by the linear relaxation.
2123//
2124// TODO(user): Note that currently both encoding introduce extra solutions
2125// if the constraint has some enforcement literal(). We can either fix this by
2126// supporting enumeration on a subset of variable. Or add extra constraint to
2127// fix all new Boolean to false if the initial constraint is not enforced.
2128void ExpandComplexLinearConstraint(int c, ConstraintProto* ct,
2129 PresolveContext* context) {
2130 // TODO(user): We treat the linear of size 1 differently because we need them
2131 // as is to recognize value encoding. Try to still creates needed Boolean now
2132 // so that we can share more between the different workers. Or revisit how
2133 // linear1 are propagated.
2134 if (ct->linear().domain().size() <= 2) return;
2135 if (ct->linear().vars().size() == 1) return;
2136
2137 const SatParameters& params = context->params();
2138 if (params.encode_complex_linear_constraint_with_integer()) {
2139 // Integer encoding.
2140 //
2141 // Here we add a slack with domain equal to rhs and transform
2142 // expr \in rhs to expr - slack = 0
2143 const Domain rhs = ReadDomainFromProto(ct->linear());
2144 const int slack = context->NewIntVar(rhs);
2145 ct->mutable_linear()->add_vars(slack);
2146 ct->mutable_linear()->add_coeffs(-1);
2147 ct->mutable_linear()->clear_domain();
2148 ct->mutable_linear()->add_domain(0);
2149 ct->mutable_linear()->add_domain(0);
2150 } else {
2151 // Boolean encoding.
2152 int single_bool;
2153 BoolArgumentProto* clause = nullptr;
2154 std::vector<int> domain_literals;
2155 if (ct->enforcement_literal().empty() && ct->linear().domain_size() == 4) {
2156 // We cover the special case of no enforcement and two choices by creating
2157 // a single Boolean.
2158 single_bool = context->NewBoolVar();
2159 } else {
2160 clause = context->working_model->add_constraints()->mutable_bool_or();
2161 for (const int ref : ct->enforcement_literal()) {
2162 clause->add_literals(NegatedRef(ref));
2163 }
2164 }
2165
2166 // Save enforcement literals for the enumeration.
2167 const std::vector<int> enforcement_literals(
2168 ct->enforcement_literal().begin(), ct->enforcement_literal().end());
2169 ct->mutable_enforcement_literal()->Clear();
2170 for (int i = 0; i < ct->linear().domain_size(); i += 2) {
2171 const int64_t lb = ct->linear().domain(i);
2172 const int64_t ub = ct->linear().domain(i + 1);
2173
2174 int subdomain_literal;
2175 if (clause != nullptr) {
2176 subdomain_literal = context->NewBoolVar();
2177 clause->add_literals(subdomain_literal);
2178 domain_literals.push_back(subdomain_literal);
2179 } else {
2180 if (i == 0) domain_literals.push_back(single_bool);
2181 subdomain_literal = i == 0 ? single_bool : NegatedRef(single_bool);
2182 }
2183
2184 // Create a new constraint which is a copy of the original, but with a
2185 // simple sub-domain and enforcement literal.
2186 ConstraintProto* new_ct = context->working_model->add_constraints();
2187 *new_ct = *ct;
2188 new_ct->add_enforcement_literal(subdomain_literal);
2189 FillDomainInProto(Domain(lb, ub), new_ct->mutable_linear());
2190 }
2191
2192 // Make sure all booleans are tights when enumerating all solutions.
2193 if (context->params().enumerate_all_solutions() &&
2194 !enforcement_literals.empty()) {
2195 int linear_is_enforced;
2196 if (enforcement_literals.size() == 1) {
2197 linear_is_enforced = enforcement_literals[0];
2198 } else {
2199 linear_is_enforced = context->NewBoolVar();
2200 BoolArgumentProto* maintain_linear_is_enforced =
2201 context->working_model->add_constraints()->mutable_bool_or();
2202 for (const int e_lit : enforcement_literals) {
2203 context->AddImplication(NegatedRef(e_lit),
2204 NegatedRef(linear_is_enforced));
2205 maintain_linear_is_enforced->add_literals(NegatedRef(e_lit));
2206 }
2207 maintain_linear_is_enforced->add_literals(linear_is_enforced);
2208 }
2209
2210 for (const int lit : domain_literals) {
2211 context->AddImplication(NegatedRef(linear_is_enforced),
2212 NegatedRef(lit));
2213 }
2214 }
2215 ct->Clear();
2216 }
2217
2218 context->UpdateRuleStats("linear: expanded complex rhs");
2219 context->InitializeNewDomains();
2220 context->UpdateNewConstraintsVariableUsage();
2221 context->UpdateConstraintVariableUsage(c);
2222}
2223
2224bool IsVarEqOrNeqValue(PresolveContext* context,
2225 const LinearConstraintProto& lin) {
2226 if (lin.vars_size() != 1) return false;
2227 const Domain rhs = ReadDomainFromProto(lin);
2228
2229 // This is literal => var == value.
2230 if (rhs.IsFixed()) return true;
2231
2232 // Is it literal => var != value ?
2233 const Domain not_implied =
2234 rhs.InverseMultiplicationBy(lin.coeffs(0))
2235 .Complement()
2236 .IntersectionWith(context->DomainOf(lin.vars(0)));
2237 if (not_implied.IsEmpty()) return false;
2238 return not_implied.IsFixed();
2239}
2240
2241// This method will scan all constraints of all variables appearing in an
2242// all_diff.
2243// There are 3 outcomes:
2244// - maybe expand to Boolean variables (depending on the size)
2245// - keep integer all_different constraint (and cuts)
2246// - expand and keep
2247//
2248// Expand is selected if the variable is fully encoded, or will be when
2249// expanding other constraints: index of element, table, automaton.
2250// It will check AllDiffShouldBeExpanded() before doing the actual expansion.
2251// Keep is forced is the variable appears in a linear equation with at least 3
2252// terms, and with a tight domain ( == cst).
2253// TODO(user): The above rule is complex. Revisit.
2254void ScanModelAndDecideAllDiffExpansion(
2255 ConstraintProto* all_diff_ct, PresolveContext* context,
2256 absl::flat_hash_set<int>& domain_of_var_is_used,
2257 absl::flat_hash_set<int>& bounds_of_var_are_used,
2258 absl::flat_hash_set<int>& processed_variables, bool& expand, bool& keep) {
2259 CHECK_EQ(all_diff_ct->constraint_case(), ConstraintProto::kAllDiff);
2260
2261 bool at_least_one_var_domain_is_used = false;
2262 bool at_least_one_var_bound_is_used = false;
2263
2264 // Scan variables.
2265 for (const LinearExpressionProto& expr : all_diff_ct->all_diff().exprs()) {
2266 // Skip constant expressions.
2267 if (expr.vars().empty()) continue;
2268 DCHECK_EQ(1, expr.vars_size());
2269 const int var = expr.vars(0);
2270 DCHECK(RefIsPositive(var));
2271 if (context->IsFixed(var)) continue;
2272
2273 bool at_least_one_var_domain_is_used = false;
2274 bool at_least_one_var_bound_is_used = false;
2275
2276 // Check cache.
2277 if (!processed_variables.insert(var).second) {
2278 at_least_one_var_domain_is_used = bounds_of_var_are_used.contains(var);
2279 at_least_one_var_bound_is_used = domain_of_var_is_used.contains(var);
2280 } else {
2281 bool domain_is_used = false;
2282 bool bounds_are_used = false;
2283
2284 // Note: Boolean constraints are ignored.
2285 for (const int ct_index : context->VarToConstraints(var)) {
2286 // Skip artificial constraints.
2287 if (ct_index < 0) continue;
2288
2289 const ConstraintProto& other_ct =
2290 context->working_model->constraints(ct_index);
2291 switch (other_ct.constraint_case()) {
2292 case ConstraintProto::ConstraintCase::kBoolOr:
2293 break;
2294 case ConstraintProto::ConstraintCase::kBoolAnd:
2295 break;
2296 case ConstraintProto::ConstraintCase::kAtMostOne:
2297 break;
2298 case ConstraintProto::ConstraintCase::kExactlyOne:
2299 break;
2300 case ConstraintProto::ConstraintCase::kBoolXor:
2301 break;
2302 case ConstraintProto::ConstraintCase::kIntDiv:
2303 break;
2304 case ConstraintProto::ConstraintCase::kIntMod:
2305 break;
2306 case ConstraintProto::ConstraintCase::kLinMax:
2307 bounds_are_used = true;
2308 break;
2309 case ConstraintProto::ConstraintCase::kIntProd:
2310 break;
2311 case ConstraintProto::ConstraintCase::kLinear:
2312 if (IsVarEqOrNeqValue(context, other_ct.linear()) &&
2313 var == other_ct.linear().vars(0)) {
2314 // Encoding literals.
2315 domain_is_used = true;
2316 } else if (other_ct.linear().vars_size() > 2 &&
2317 other_ct.linear().domain_size() == 2 &&
2318 other_ct.linear().domain(0) ==
2319 other_ct.linear().domain(1)) {
2320 // We assume all_diff cuts will only be useful if the linear
2321 // constraint has a fixed domain.
2322 bounds_are_used = true;
2323 }
2324 break;
2325 case ConstraintProto::ConstraintCase::kAllDiff:
2326 // We ignore all_diffs as we are trying to decide their expansion
2327 // from the rest of the model.
2328 break;
2329 case ConstraintProto::ConstraintCase::kDummyConstraint:
2330 break;
2331 case ConstraintProto::ConstraintCase::kElement:
2332 // Note: elements should have been expanded.
2333 if (other_ct.element().index() == var) {
2334 domain_is_used = true;
2335 }
2336 break;
2337 case ConstraintProto::ConstraintCase::kCircuit:
2338 break;
2339 case ConstraintProto::ConstraintCase::kRoutes:
2340 break;
2341 case ConstraintProto::ConstraintCase::kInverse:
2342 domain_is_used = true;
2343 break;
2344 case ConstraintProto::ConstraintCase::kReservoir:
2345 break;
2346 case ConstraintProto::ConstraintCase::kTable:
2347 domain_is_used = true;
2348 break;
2349 case ConstraintProto::ConstraintCase::kAutomaton:
2350 domain_is_used = true;
2351 break;
2352 case ConstraintProto::ConstraintCase::kInterval:
2353 bounds_are_used = true;
2354 break;
2355 case ConstraintProto::ConstraintCase::kNoOverlap:
2356 // Will be covered by the interval case.
2357 break;
2358 case ConstraintProto::ConstraintCase::kNoOverlap2D:
2359 // Will be covered by the interval case.
2360 break;
2361 case ConstraintProto::ConstraintCase::kCumulative:
2362 // Will be covered by the interval case.
2363 break;
2364 case ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET:
2365 break;
2366 }
2367
2368 // Exit early.
2369 if (domain_is_used && bounds_are_used) break;
2370 } // Loop on other_ct.
2371
2372 // Update cache.
2373 if (domain_is_used) domain_of_var_is_used.insert(var);
2374 if (bounds_are_used) bounds_of_var_are_used.insert(var);
2375
2376 // Update the usage of the variable.
2377 at_least_one_var_domain_is_used |= domain_is_used;
2378 at_least_one_var_bound_is_used |= bounds_are_used;
2379 } // End of model scanning.
2380
2381 if (at_least_one_var_domain_is_used && at_least_one_var_bound_is_used) {
2382 break; // No need to scan the rest of the all_diff.
2383 }
2384 } // End of var processing.
2385
2386 expand = at_least_one_var_domain_is_used;
2387 keep = at_least_one_var_bound_is_used;
2388}
2389
2390void MaybeExpandAllDiff(ConstraintProto* ct, PresolveContext* context,
2391 absl::flat_hash_set<int>& domain_of_var_is_used,
2392 absl::flat_hash_set<int>& bounds_of_var_are_used,
2393 absl::flat_hash_set<int>& processed_variable) {
2394 const bool expand_all_diff_from_parameters =
2395 context->params().expand_alldiff_constraints();
2396 AllDifferentConstraintProto& proto = *ct->mutable_all_diff();
2397 if (proto.exprs_size() <= 1) return;
2398 if (context->ModelIsUnsat()) return;
2399
2400 bool keep_after_expansion = false;
2401 bool expand_all_diff_from_usage = false;
2402 ScanModelAndDecideAllDiffExpansion(
2403 ct, context, domain_of_var_is_used, bounds_of_var_are_used,
2404 processed_variable, expand_all_diff_from_usage, keep_after_expansion);
2405
2406 const int num_exprs = proto.exprs_size();
2407 Domain union_of_domains = context->DomainSuperSetOf(proto.exprs(0));
2408 for (int i = 1; i < num_exprs; ++i) {
2409 union_of_domains =
2410 union_of_domains.UnionWith(context->DomainSuperSetOf(proto.exprs(i)));
2411 }
2412
2413 const bool expand_all_diff_from_size =
2414 AllDiffShouldBeExpanded(union_of_domains, ct, context);
2415
2416 // Decide expansion:
2417 // - always expand if expand_all_diff_from_parameters
2418 // - expand if size is compatible (expand_all_diff_from_size) and
2419 // expansion is desired:
2420 // expand_all_diff_from_usage || !keep_after_expansion
2421 const bool should_expand =
2422 expand_all_diff_from_parameters ||
2423 (expand_all_diff_from_size &&
2424 (expand_all_diff_from_usage || !keep_after_expansion));
2425 if (!should_expand) return;
2426
2427 const bool is_a_permutation = num_exprs == union_of_domains.Size();
2428
2429 // Collect all possible variables that can take each value, and add one linear
2430 // equation per value stating that this value can be assigned at most once, or
2431 // exactly once in case of permutation.
2432 for (const int64_t v : union_of_domains.Values()) {
2433 // Collect references which domain contains v.
2434 std::vector<LinearExpressionProto> possible_exprs;
2435 int fixed_expression_count = 0;
2436 for (const LinearExpressionProto& expr : proto.exprs()) {
2437 if (!context->DomainContains(expr, v)) continue;
2438 possible_exprs.push_back(expr);
2439 if (context->IsFixed(expr)) {
2440 fixed_expression_count++;
2441 }
2442 }
2443
2444 if (fixed_expression_count > 1) {
2445 // Violates the definition of AllDifferent.
2446 return (void)context->NotifyThatModelIsUnsat();
2447 } else if (fixed_expression_count == 1) {
2448 // Remove values from other domains.
2449 for (const LinearExpressionProto& expr : possible_exprs) {
2450 if (context->IsFixed(expr)) continue;
2451 if (!context->IntersectDomainWith(expr, Domain(v).Complement())) {
2452 VLOG(1) << "Empty domain for a variable in MaybeExpandAllDiff()";
2453 return;
2454 }
2455 }
2456 }
2457
2458 BoolArgumentProto* at_most_or_equal_one =
2459 is_a_permutation
2460 ? context->working_model->add_constraints()->mutable_exactly_one()
2461 : context->working_model->add_constraints()->mutable_at_most_one();
2462 for (const LinearExpressionProto& expr : possible_exprs) {
2463 // The above propagation can remove a value after the expressions was
2464 // added to possible_exprs.
2465 if (!context->DomainContains(expr, v)) continue;
2466
2467 // If the expression is fixed, the created literal will be the true
2468 // literal. We still need to fail if two expressions are fixed to the same
2469 // value.
2470 const int encoding = context->GetOrCreateAffineValueEncoding(expr, v);
2471 at_most_or_equal_one->add_literals(encoding);
2472 }
2473 }
2474
2475 context->UpdateRuleStats(
2476 absl::StrCat("all_diff:", is_a_permutation ? " permutation" : "",
2477 " expanded", keep_after_expansion ? " and kept" : ""));
2478 if (!keep_after_expansion) ct->Clear();
2479}
2480
2481} // namespace
2482
2484 if (context->params().disable_constraint_expansion()) return;
2485 if (context->ModelIsUnsat()) return;
2486
2487 // None of the function here need to be run twice. This is because we never
2488 // create constraint that need to be expanded during presolve.
2489 if (context->ModelIsExpanded()) return;
2490
2491 // Make sure all domains are initialized.
2492 context->InitializeNewDomains();
2493 if (context->ModelIsUnsat()) return;
2494
2495 // Clear the precedence cache.
2496 context->ClearPrecedenceCache();
2497
2498 bool has_all_diffs = false;
2499
2500 // First pass: we look at constraints that may fully encode variables.
2501 for (int c = 0; c < context->working_model->constraints_size(); ++c) {
2502 ConstraintProto* const ct = context->working_model->mutable_constraints(c);
2503 bool skip = false;
2504 switch (ct->constraint_case()) {
2505 case ConstraintProto::kLinear:
2506 // If we only do expansion, we do that as part of the main loop.
2507 // This way we don't need to call FinalExpansionForLinearConstraint().
2508 if (ct->linear().domain().size() > 2 &&
2509 !context->params().cp_model_presolve()) {
2510 ExpandComplexLinearConstraint(c, ct, context);
2511 }
2512 break;
2513 case ConstraintProto::kReservoir:
2514 if (context->params().expand_reservoir_constraints()) {
2515 ExpandReservoir(ct, context);
2516 }
2517 break;
2518 case ConstraintProto::kCumulative:
2519 if (context->params().encode_cumulative_as_reservoir()) {
2520 EncodeCumulativeAsReservoir(ct, context);
2521 }
2522 break;
2523 case ConstraintProto::kIntMod:
2524 ExpandIntMod(ct, context);
2525 break;
2526 case ConstraintProto::kIntProd:
2527 ExpandIntProd(ct, context);
2528 break;
2529 case ConstraintProto::kElement:
2530 ExpandElement(ct, context);
2531 break;
2532 case ConstraintProto::kInverse:
2533 ExpandInverse(ct, context);
2534 break;
2535 case ConstraintProto::kAutomaton:
2536 ExpandAutomaton(ct, context);
2537 break;
2538 case ConstraintProto::kTable:
2539 if (ct->table().negated()) {
2540 ExpandNegativeTable(ct, context);
2541 } else {
2542 ExpandPositiveTable(ct, context);
2543 }
2544 break;
2545 case ConstraintProto::kLinMax:
2546 if (ct->lin_max().exprs().size() <=
2547 context->params().max_lin_max_size_for_expansion()) {
2548 ExpandLinMax(ct, context);
2549 }
2550 break;
2551 case ConstraintProto::kAllDiff:
2552 has_all_diffs = true;
2553 skip = true;
2554 break;
2555 default:
2556 skip = true;
2557 break;
2558 }
2559 if (skip) continue; // Nothing was done for this constraint.
2560
2561 // Update variable-constraint graph.
2562 context->UpdateNewConstraintsVariableUsage();
2563 if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2564 context->UpdateConstraintVariableUsage(c);
2565 }
2566
2567 // Early exit if the model is unsat.
2568 if (context->ModelIsUnsat()) {
2569 SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
2571 return;
2572 }
2573 }
2574
2575 // Second pass. We may decide to expand constraints if all their variables
2576 // are fully encoded.
2577 //
2578 // Cache for variable scanning.
2579 absl::flat_hash_set<int> domain_of_var_is_used;
2580 absl::flat_hash_set<int> bounds_of_var_are_used;
2581 absl::flat_hash_set<int> processed_variables;
2582 for (int i = 0; i < context->working_model->constraints_size(); ++i) {
2583 ConstraintProto* const ct = context->working_model->mutable_constraints(i);
2584 bool skip = false;
2585 switch (ct->constraint_case()) {
2586 case ConstraintProto::kAllDiff:
2587 MaybeExpandAllDiff(ct, context, domain_of_var_is_used,
2588 bounds_of_var_are_used, processed_variables);
2589 break;
2590 case ConstraintProto::kLinear:
2591 ExpandSomeLinearOfSizeTwo(ct, context);
2592 break;
2593 default:
2594 skip = true;
2595 break;
2596 }
2597
2598 if (skip) continue; // Nothing was done for this constraint.
2599
2600 // Update variable-constraint graph.
2601 context->UpdateNewConstraintsVariableUsage();
2602 if (ct->constraint_case() == ConstraintProto::CONSTRAINT_NOT_SET) {
2603 context->UpdateConstraintVariableUsage(i);
2604 }
2605
2606 // Early exit if the model is unsat.
2607 if (context->ModelIsUnsat()) {
2608 SOLVER_LOG(context->logger(), "UNSAT after expansion of ",
2610 return;
2611 }
2612 }
2613
2614 // The precedence cache can become invalid during presolve as it does not
2615 // handle variable substitution. It is safer just to clear it at the end
2616 // of the expansion phase.
2617 context->ClearPrecedenceCache();
2618
2619 // Make sure the context is consistent.
2620 context->InitializeNewDomains();
2621
2622 // Update any changed domain from the context.
2623 for (int i = 0; i < context->working_model->variables_size(); ++i) {
2624 FillDomainInProto(context->DomainOf(i),
2625 context->working_model->mutable_variables(i));
2626 }
2627
2628 context->NotifyThatModelIsExpanded();
2629}
2630
2632 if (context->params().disable_constraint_expansion()) return;
2633 if (context->ModelIsUnsat()) return;
2634 for (int c = 0; c < context->working_model->constraints_size(); ++c) {
2635 ConstraintProto* const ct = context->working_model->mutable_constraints(c);
2636 switch (ct->constraint_case()) {
2637 case ConstraintProto::kLinear:
2638 if (ct->linear().domain().size() > 2) {
2639 ExpandComplexLinearConstraint(c, ct, context);
2640 }
2641 break;
2642 default:
2643 break;
2644 }
2645 }
2646}
2647
2648} // namespace sat
2649} // namespace operations_research
IntegerValue size
static Domain FromValues(std::vector< int64_t > values)
int64_t a
Definition table.cc:44
CpModelProto proto
The output proto.
const Constraint * ct
int64_t value
IntVar * var
GurobiMPCallbackContext * context
int lit
int literal
int ct_index
int index
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
constexpr int64_t kTableAnyValue
Definition util.h:601
void CompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
Definition util.cc:454
void ExpandCpModel(PresolveContext *context)
bool SolveDiophantineEquationOfSizeTwo(int64_t &a, int64_t &b, int64_t &cte, int64_t &x0, int64_t &y0)
Definition util.cc:209
std::vector< std::vector< absl::InlinedVector< int64_t, 2 > > > FullyCompressTuples(absl::Span< const int64_t > domain_sizes, std::vector< std::vector< int64_t > > *tuples)
Definition util.cc:878
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
bool PossibleIntegerOverflow(const CpModelProto &model, absl::Span< const int > vars, absl::Span< const int64_t > coeffs, int64_t offset)
void FinalExpansionForLinearConstraint(PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
bool ExpressionsContainsOnlyOneVar(const ExpressionList &exprs)
Returns true if there exactly one variable appearing in all the expressions.
void AddLinearExpressionToLinearConstraint(const LinearExpressionProto &expr, int64_t coefficient, LinearConstraintProto *linear)
void PropagateAutomaton(const AutomatonConstraintProto &proto, const PresolveContext &context, std::vector< absl::flat_hash_set< int64_t > > *states, std::vector< absl::flat_hash_set< int64_t > > *labels)
Fills and propagates the set of reachable states/labels.
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
In SWIG mode, we don't want anything besides these top-level includes.
std::string ProtobufShortDebugString(const P &message)
Definition proto_utils.h:41
int column
int64_t demand
Definition resource.cc:126
int64_t time
Definition resource.cc:1708
IntervalVar * interval
Definition resource.cc:101
int head
int tail
int var_index
Definition search.cc:3268
std::optional< int64_t > end
#define SOLVER_LOG(logger,...)
Definition logging.h:109