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