Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
presolve_encoding.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 <cstdint>
17#include <cstdlib>
18#include <functional>
19#include <limits>
20#include <optional>
21#include <utility>
22#include <vector>
23
24#include "absl/algorithm/container.h"
25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/container/inlined_vector.h"
28#include "absl/log/check.h"
29#include "absl/log/log.h"
30#include "google/protobuf/repeated_field.h"
34#include "ortools/util/bitset.h"
36
37namespace operations_research {
38namespace sat {
39
40namespace {
41bool ConstraintIsEncodingBound(const ConstraintProto& ct) {
42 if (ct.constraint_case() != ConstraintProto::kLinear) return false;
43 if (ct.linear().vars_size() != 1) return false;
44 if (ct.linear().coeffs(0) != 1) return false;
45 if (ct.enforcement_literal_size() != 1) return false;
46 if (PositiveRef(ct.enforcement_literal(0)) == ct.linear().vars(0)) {
47 return false;
48 }
49 return true;
50}
51} // namespace
52
53std::vector<VariableEncodingLocalModel> CreateVariableEncodingLocalModels(
54 PresolveContext* context) {
55 // In this function we want to make sure we don't waste too much time on
56 // problems that do not have many linear1. Thus, the first thing we do is to
57 // filter out as soon and cheaply as possible the bare minimum of constraints
58 // that could be relevant to the final output.
59
60 // Constraints taking a list of literals that can, under some conditions,
61 // accept the following substitution:
62 // constraint(a, b, ...) => constraint(a | b, ...)
63 // one obvious case is bool_or. But if we can know that a and b cannot be
64 // both true, we can also apply this to at_most_one and exactly_one.
65 //
66 // Note that in the implementation we might for simplicity refer to the
67 // constraints we are interested in as "bool_or" but this is just to avoid
68 // mentioning all the three types over and over.
69 // TODO(user): this should also work for linear constraints with the two
70 // booleans having the same coefficient?
71 std::vector<int> constraint_encoding_or; // bool_or, exactly_one, at_most_one
72
73 // Do a pass to gather all linear1 constraints.
74 absl::flat_hash_map<int, absl::InlinedVector<int, 1>> var_to_linear1;
75 for (int i = 0; i < context->working_model->constraints_size(); ++i) {
76 const ConstraintProto& ct = context->working_model->constraints(i);
80 constraint_encoding_or.push_back(i);
81 continue;
82 }
83 if (!ConstraintIsEncodingBound(ct)) {
84 continue;
85 }
86 var_to_linear1[ct.linear().vars(0)].push_back(i);
87 }
88
89 // Filter out the variables that do not have an interesting encoding.
90 absl::erase_if(var_to_linear1, [context](const auto& p) {
91 if (p.second.size() > 1) return false;
92 return context->VarToConstraints(p.first).size() > 2;
93 });
94
95 if (var_to_linear1.empty()) return {};
96
97 absl::flat_hash_map<int, absl::InlinedVector<int, 2>> bool_to_var_encodings;
98
99 // Now we use the linear1 we found to see which bool_or/amo/exactly_one are
100 // linking two encodings of the same variable. But first, since some models
101 // have a lot of bool_or, we use a simple heuristic to filter out all that are
102 // not related to the encodings. We use a bitset to keep track of all boolean
103 // potentially encoding a domain for any variable and we filter out all
104 // bool_or that are not linked to at least two of these booleans.
105 Bitset64<int> booleans_potentially_encoding_domain(
106 context->working_model->variables_size());
107
108 for (const auto& [var, linear1_cts] : var_to_linear1) {
109 for (const int c : linear1_cts) {
110 const ConstraintProto& ct = context->working_model->constraints(c);
111 const int bool_var = PositiveRef(ct.enforcement_literal(0));
112 booleans_potentially_encoding_domain.Set(bool_var);
113 bool_to_var_encodings[bool_var].push_back(var);
114 }
115 }
116 for (auto& [bool_var, var_encodings] : bool_to_var_encodings) {
117 // Remove the potential duplicate for the negation.
118 gtl::STLSortAndRemoveDuplicates(&var_encodings);
119 }
120 int new_encoding_or_count = 0;
121 for (int i = 0; i < constraint_encoding_or.size(); ++i) {
122 const int c = constraint_encoding_or[i];
123 const ConstraintProto& ct = context->working_model->constraints(c);
124 const BoolArgumentProto& bool_ct =
126 ? ct.at_most_one()
128 ? ct.exactly_one()
129 : ct.bool_or());
130 if (absl::c_count_if(
131 bool_ct.literals(),
132 [booleans_potentially_encoding_domain](int ref) {
133 return booleans_potentially_encoding_domain[PositiveRef(ref)];
134 }) < 2) {
135 continue;
136 }
137 constraint_encoding_or[new_encoding_or_count++] = c;
138 }
139 constraint_encoding_or.resize(new_encoding_or_count);
140
141 // Track the number of times a given boolean appears in the local model for a
142 // given variable.
143 struct VariableAndBoolInfo {
144 // Can only be 1 or 2 (for negation) if properly presolved.
145 int linear1_count = 0;
146 // Number of times the boolean will appear in
147 // `constraints_linking_two_encoding_booleans`.
148 int bool_or_count = 0;
149 };
150 absl::flat_hash_map<std::pair<int, int>, VariableAndBoolInfo> var_bool_counts;
151
152 // Now that we have a potentially smaller set of bool_or, we actually check
153 // which of them are linking two encodings of the same variable.
154 absl::flat_hash_map<int, std::vector<int>> var_to_constraints_encoding_or;
155
156 // Map from variable to the bools that appear in a given bool_or.
157 absl::flat_hash_map<int, std::vector<int>> var_to_bools;
158
159 for (const int c : constraint_encoding_or) {
160 var_to_bools.clear();
161 const ConstraintProto& ct = context->working_model->constraints(c);
162 const BoolArgumentProto& bool_ct =
164 ? ct.at_most_one()
166 ? ct.exactly_one()
167 : ct.bool_or());
168 for (const int ref : bool_ct.literals()) {
169 const int bool_var = PositiveRef(ref);
170 if (!booleans_potentially_encoding_domain[bool_var]) continue;
171 for (const int var : bool_to_var_encodings[bool_var]) {
172 var_to_bools[var].push_back(bool_var);
173 }
174 }
175 for (const auto& [var, bools] : var_to_bools) {
176 if (bools.size() >= 2) {
177 // We have two encodings of `var` in the same constraint `c`. Thus `c`
178 // should be part of the local model for `var`.
179 var_to_constraints_encoding_or[var].push_back(c);
180 for (const int bool_var : bools) {
181 var_bool_counts[{var, bool_var}].bool_or_count++;
182 }
183 }
184 }
185 }
186
187 std::vector<VariableEncodingLocalModel> local_models;
188 // Now that we have all the information, we can create the local models.
189 for (const auto& [var, linear1_cts] : var_to_linear1) {
190 VariableEncodingLocalModel& encoding_model = local_models.emplace_back();
191 encoding_model.var = var;
192 encoding_model.linear1_constraints.assign(linear1_cts.begin(),
193 linear1_cts.end());
195 var_to_constraints_encoding_or[var];
196 absl::c_sort(encoding_model.constraints_linking_two_encoding_booleans);
198 (context->VarToConstraints(var).size() - linear1_cts.size() > 1);
199 for (const int ct : linear1_cts) {
200 const int bool_var = PositiveRef(
202 encoding_model.bools_only_used_inside_the_local_model.insert(bool_var);
203 var_bool_counts[{var, bool_var}].linear1_count++;
204 }
205 absl::erase_if(encoding_model.bools_only_used_inside_the_local_model,
206 [context, v = var, &var_bool_counts](int bool_var) {
207 const auto& counts = var_bool_counts[{v, bool_var}];
208 return context->VarToConstraints(bool_var).size() !=
209 counts.linear1_count + counts.bool_or_count;
210 });
211 auto it = context->ObjectiveMap().find(var);
212 if (it != context->ObjectiveMap().end()) {
213 encoding_model.variable_coeff_in_objective = it->second;
214 }
215 }
216 absl::c_sort(local_models, [](const VariableEncodingLocalModel& a,
217 const VariableEncodingLocalModel& b) {
218 return a.var < b.var;
219 });
220 return local_models;
221}
222
224 PresolveContext* context, VariableEncodingLocalModel& local_model,
225 absl::flat_hash_map<int, Domain>* result, bool* changed) {
226 *changed = false;
227 absl::flat_hash_map<int, int> ref_to_linear1;
228
229 // Fill ref_to_linear1 and do some basic presolving.
230 const Domain var_domain = context->DomainOf(local_model.var);
231 for (const int ct : local_model.linear1_constraints) {
232 ConstraintProto* ct_proto = context->working_model->mutable_constraints(ct);
233 DCHECK(ConstraintIsEncodingBound(*ct_proto));
234 const int ref = ct_proto->enforcement_literal(0);
235 Domain domain = ReadDomainFromProto(ct_proto->linear());
236 if (!domain.IsIncludedIn(var_domain)) {
237 *changed = true;
238 domain = domain.IntersectionWith(context->DomainOf(local_model.var));
239 if (domain.IsEmpty()) {
240 context->UpdateRuleStats(
241 "variables: linear1 with domain not included in variable domain");
242 if (!context->SetLiteralToFalse(ref)) {
243 return false;
244 }
245 ct_proto->Clear();
247 continue;
248 }
249 FillDomainInProto(domain, ct_proto->mutable_linear());
250 }
251 auto [it, inserted] = ref_to_linear1.insert({ref, ct});
252 if (!inserted) {
253 *changed = true;
254 ConstraintProto* old_ct_proto =
255 context->working_model->mutable_constraints(it->second);
256 const Domain old_ct_domain = ReadDomainFromProto(old_ct_proto->linear());
257 const Domain new_domain = domain.IntersectionWith(old_ct_domain);
258 ct_proto->Clear();
260 if (new_domain.IsEmpty()) {
261 context->UpdateRuleStats(
262 "variables: linear1 with same variable and enforcement and "
263 "non-overlapping domain, setting enforcement to false");
264 if (!context->SetLiteralToFalse(ref)) {
265 return false;
266 }
267 old_ct_proto->Clear();
268 context->UpdateConstraintVariableUsage(it->second);
269 ref_to_linear1.erase(ref);
270 } else {
271 FillDomainInProto(new_domain, old_ct_proto->mutable_linear());
272 context->UpdateRuleStats(
273 "variables: merged two linear1 with same variable and enforcement");
274 }
275 }
276 }
277
278 // Remove from the local model anything that was removed in the loop above.
279 int new_linear1_size = 0;
280 for (int i = 0; i < local_model.linear1_constraints.size(); ++i) {
281 const int ct = local_model.linear1_constraints[i];
282 const ConstraintProto& ct_proto = context->working_model->constraints(ct);
283 if (ct_proto.constraint_case() != ConstraintProto::kLinear) continue;
284 if (context->IsFixed(ct_proto.enforcement_literal(0))) {
285 continue;
286 }
287 DCHECK(ConstraintIsEncodingBound(ct_proto));
288 local_model.linear1_constraints[new_linear1_size++] = ct;
289 }
290 if (new_linear1_size != local_model.linear1_constraints.size()) {
291 *changed = true;
292 local_model.linear1_constraints.resize(new_linear1_size);
293 // Rerun the presolve loop to recompute ref_to_linear1.
294 return true;
295 }
296
297 for (const auto& [ref, ct] : ref_to_linear1) {
298 auto it = ref_to_linear1.find(NegatedRef(ref));
299 if (it == ref_to_linear1.end()) continue;
300 const ConstraintProto& positive_ct =
301 context->working_model->constraints(ct);
302 const ConstraintProto& negative_ct =
303 context->working_model->constraints(it->second);
304 const Domain positive_domain = ReadDomainFromProto(positive_ct.linear());
305 const Domain negative_domain = ReadDomainFromProto(negative_ct.linear());
306 if (!positive_domain.IntersectionWith(negative_domain).IsEmpty()) {
307 // This is not a fully encoded domain. For example, it could be
308 // l => x in {-inf,inf}
309 // ~l => x in {-inf,inf}
310 // which actually means that `l` doesn't really encode anything.
311 continue;
312 }
313 bool domain_modified = false;
314 if (!context->IntersectDomainWith(
315 local_model.var, positive_domain.UnionWith(negative_domain),
316 &domain_modified)) {
317 return false;
318 }
319 *changed = *changed || domain_modified;
320 result->insert({ref, positive_domain});
321 result->insert({NegatedRef(ref), negative_domain});
322 }
323
324 // Now detect a different way of fully encoding a domain:
325 // l1 => x in D1
326 // l2 => x in D2
327 // l3 => x in D3
328 // ...
329 // l_n => x in D_n
330 // bool_or(l1, l2, l3, ..., l_n)
331 //
332 // where D1, D2, ..., D_n are non overlapping. This works too for exactly_one.
333 for (const int ct : local_model.constraints_linking_two_encoding_booleans) {
334 const ConstraintProto& ct_proto = context->working_model->constraints(ct);
335 if (ct_proto.constraint_case() != ConstraintProto::kBoolOr &&
337 continue;
338 }
339 if (!ct_proto.enforcement_literal().empty()) continue;
340 const BoolArgumentProto& bool_or =
342 ? ct_proto.exactly_one()
343 : ct_proto.bool_or();
344 if (bool_or.literals().size() < 2) continue;
345 bool encoding_detected = true;
346 Domain non_overlapping_domain;
347 std::vector<std::pair<int, Domain>> ref_and_domains;
348 for (const int ref : bool_or.literals()) {
349 auto it = ref_to_linear1.find(ref);
350 if (it == ref_to_linear1.end()) {
351 encoding_detected = false;
352 break;
353 }
354 const Domain domain = ReadDomainFromProto(
355 context->working_model->constraints(it->second).linear());
356 ref_and_domains.push_back({ref, domain});
357 if (!non_overlapping_domain.IntersectionWith(domain).IsEmpty()) {
358 encoding_detected = false;
359 break;
360 }
361 non_overlapping_domain = non_overlapping_domain.UnionWith(domain);
362 }
363 if (encoding_detected) {
364 context->UpdateRuleStats("variables: detected fully encoded domain");
365 bool domain_modified = false;
366 if (!context->IntersectDomainWith(local_model.var, non_overlapping_domain,
367 &domain_modified)) {
368 return false;
369 }
370 if (domain_modified) {
371 context->UpdateRuleStats(
372 "variables: restricted domain to fully encoded domain");
373 }
374 *changed = *changed || domain_modified;
375 for (const auto& [ref, domain] : ref_and_domains) {
376 result->insert({ref, domain});
377 result->insert({NegatedRef(ref),
378 var_domain.IntersectionWith(domain.Complement())});
379 }
380 // Promote a bool_or to an exactly_one.
381 if (ct_proto.constraint_case() == ConstraintProto::kBoolOr) {
382 context->UpdateRuleStats(
383 "variables: promoted bool_or to exactly_one for fully encoded "
384 "domain");
385 std::vector<int> new_enforcement_literals(bool_or.literals().begin(),
386 bool_or.literals().end());
391 ->Add(new_enforcement_literals.begin(),
392 new_enforcement_literals.end());
393 *changed = true;
394 }
395 }
396 }
397 return true;
398}
399
400// Return false on unsat
402 PresolveContext* context, int ct_index,
403 VariableEncodingLocalModel& local_model,
404 absl::flat_hash_map<int, Domain>* fully_encoded_domains, bool* changed) {
405 ConstraintProto* ct = context->working_model->mutable_constraints(ct_index);
406 *changed = false;
407
408 if (context->ModelIsUnsat()) return false;
412
413 // Handling exaclty_one, at_most_one and bool_or is pretty similar. If we have
414 // l1 <=> v \in D1
415 // l2 <=> v \in D2
416 //
417 // We built
418 // l <=> v \in (D1 U D2).
419 //
420 // Moreover, if we have exactly_one(l1, l2, ...) or at_most_one(l1, l2, ...),
421 // we know that v cannot be in the intersection of D1 and D2. Thus, we first
422 // unconditionally remove (D1 ∩ D2) from the domain of v, making
423 // (l1=true and l2=true) impossible and allowing us to write our clauses as
424 // exactly_one(l1 or l2, ...) or at_most_one(l1 or l2, ...).
425 //
426 // Thus, other than the domain reduction that should not be done for the
427 // bool_or, all we need is to create a variable
428 // (l1 or l2) == l <=> (v \in (D1 U D2)).
429 google::protobuf::RepeatedField<int32_t>& literals =
435 if (literals.size() <= 1) return true;
436
437 if (!ct->enforcement_literal().empty()) {
438 // TODO(user): support this case if it any problem needs it.
439 return true;
440 }
441
442 // When we have
443 // lit => var in D1
444 // ~lit => var in D2
445 // we can represent this on a line:
446 //
447 // ----------------D1----------------
448 // ----------------D2---------------
449 // |+++++++++++|*********************|++++++++++|
450 // lit=false lit unconstrained lit=true
451 //
452 // Handling the case where the variable is unconstrained by the lit is a
453 // bit of a pain: we want to replace two literals in a exactly_one by a
454 // single one, and if they are both unconstrained we might be forced to pick
455 // one arbitrarily to set to true. In any case, this is not a proper
456 // encoding of a complex domain, so we just ignore it.
457 // TODO(user): This can be implemented if it turns out to be common.
458
459 std::optional<int> maybe_lit1;
460 Domain domain_lit1;
461 std::optional<int> maybe_lit2;
462 Domain domain_lit2;
463 for (const int lit_var : literals) {
464 if (!local_model.bools_only_used_inside_the_local_model.contains(
465 PositiveRef(lit_var))) {
466 continue;
467 }
468 auto it = fully_encoded_domains->find(lit_var);
469 if (it == fully_encoded_domains->end()) {
470 continue;
471 }
472
473 if (!maybe_lit1) {
474 maybe_lit1 = lit_var;
475 domain_lit1 = it->second;
476 } else {
477 maybe_lit2 = lit_var;
478 domain_lit2 = it->second;
479 break;
480 }
481 }
482
483 if (!maybe_lit2.has_value()) return true;
484 DCHECK(maybe_lit1.has_value());
485 const int lit1 = *maybe_lit1;
486 const int lit2 = *maybe_lit2;
487
488 // We found two literals that each fully encodes an interval and are both only
489 // used in the encoding and in the bool_or/exactly_one/at_most_one. We can
490 // thus replace the two literals by their OR. Since this code is already
491 // rather complex, so we will just simplify a pair of literals at a time, and
492 // leave for the presolve fixpoint to do the rest.
493 *changed = true;
494
495 context->UpdateRuleStats(
496 "variables: detected encoding of a complex domain with multiple "
497 "linear1");
498
500 // In virtue of the AMO, var must not be in the intersection of the two
501 // domains where both literals are true.
502 if (!context->IntersectDomainWith(
503 local_model.var,
504 domain_lit2.IntersectionWith(domain_lit1).Complement())) {
505 return false;
506 }
507 }
508 const Domain var_domain = context->DomainOf(local_model.var);
509 const Domain domain_new_var_false = var_domain.IntersectionWith(
510 domain_lit1.Complement().IntersectionWith(domain_lit2.Complement()));
511 const Domain domain_new_var_true =
512 var_domain.IntersectionWith(domain_new_var_false.Complement());
513
514 // Now we want to build a lit3 = (lit1 or lit2) to use in the AMO/bool_or.
515 const int new_var = context->NewBoolVarWithClause({lit1, lit2});
516
517 if (domain_new_var_true.IsEmpty()) {
518 CHECK(context->SetLiteralToFalse(new_var));
519 } else if (domain_new_var_false.IsEmpty()) {
520 CHECK(context->SetLiteralToTrue(new_var));
521 } else {
522 local_model.linear1_constraints.push_back(
523 context->working_model->constraints_size());
524 ConstraintProto* new_ct = context->working_model->add_constraints();
525 new_ct->add_enforcement_literal(new_var);
526 new_ct->mutable_linear()->add_vars(local_model.var);
527 new_ct->mutable_linear()->add_coeffs(1);
528 FillDomainInProto(domain_new_var_true, new_ct->mutable_linear());
529 local_model.linear1_constraints.push_back(
530 context->working_model->constraints_size());
531 new_ct = context->working_model->add_constraints();
532 new_ct->add_enforcement_literal(NegatedRef(new_var));
533 new_ct->mutable_linear()->add_vars(local_model.var);
534 new_ct->mutable_linear()->add_coeffs(1);
535 FillDomainInProto(domain_new_var_false, new_ct->mutable_linear());
537 }
538
539 // Remove the two literals from the AMO.
540 int new_size = 0;
541 for (int i = 0; i < literals.size(); ++i) {
542 if (literals.Get(i) != lit1 && literals.Get(i) != lit2) {
543 literals.Set(new_size++, literals.Get(i));
544 }
545 }
546 literals.Truncate(new_size);
547 literals.Add(new_var);
548 context->UpdateConstraintVariableUsage(ct_index);
549
550 // Finally, move the four linear1 to the mapping model.
551 fully_encoded_domains->insert({new_var, domain_new_var_true});
552 fully_encoded_domains->insert({NegatedRef(new_var), domain_new_var_false});
553 fully_encoded_domains->erase(lit1);
554 fully_encoded_domains->erase(lit2);
555 fully_encoded_domains->erase(NegatedRef(lit1));
556 fully_encoded_domains->erase(NegatedRef(lit2));
557 context->MarkVariableAsRemoved(PositiveRef(lit1));
558 context->MarkVariableAsRemoved(PositiveRef(lit2));
559 int new_linear1_size = 0;
560 for (int i = 0; i < local_model.linear1_constraints.size(); ++i) {
561 const int ct = local_model.linear1_constraints[i];
562 ConstraintProto* ct_proto = context->working_model->mutable_constraints(ct);
563 if (PositiveRef(ct_proto->enforcement_literal(0)) == PositiveRef(lit1) ||
564 PositiveRef(ct_proto->enforcement_literal(0)) == PositiveRef(lit2)) {
565 context->NewMappingConstraint(*ct_proto, __FILE__, __LINE__);
566 ct_proto->Clear();
568 continue;
569 }
570 local_model.linear1_constraints[new_linear1_size++] = ct;
571 }
572 local_model.linear1_constraints.resize(new_linear1_size);
573
574 return true;
575}
576
578 VariableEncodingLocalModel& local_model) {
579 absl::flat_hash_map<int, Domain> fully_encoded_domains;
580 bool changed_on_basic_presolve = false;
581 if (!BasicPresolveAndGetFullyEncodedDomains(context, local_model,
582 &fully_encoded_domains,
583 &changed_on_basic_presolve)) {
584 return false;
585 }
586 if (local_model.constraints_linking_two_encoding_booleans.size() != 1) {
587 return true;
588 }
589 const int ct = local_model.constraints_linking_two_encoding_booleans[0];
590 bool changed = true;
591 while (changed) {
592 if (!DetectEncodedComplexDomain(context, ct, local_model,
593 &fully_encoded_domains, &changed)) {
594 return false;
595 }
596 }
597 return true;
598}
599
601 VariableEncodingLocalModel& local_model, PresolveContext* context) {
602 if (local_model.var == -1) return true;
603 if (local_model.variable_coeff_in_objective != 0) return true;
605 -1) {
606 return true;
607 }
608 const int other_c =
610
611 const std::vector<int>& to_rewrite = local_model.linear1_constraints;
612
613 // In general constraint with more than two variable can't be removed.
614 // Similarly for linear2 with non-fixed rhs as we would need to check the form
615 // of all implied domain.
616 const auto& other_ct = context->working_model->constraints(other_c);
617 if (context->ConstraintToVars(other_c).size() != 2 ||
618 !other_ct.enforcement_literal().empty() ||
619 other_ct.constraint_case() == ConstraintProto::kLinear) {
620 return true;
621 }
622
623 // This will be the rewriting function. It takes the implied domain of var
624 // from linear1, and return a pair {new_var, new_var_implied_domain}.
625 std::function<std::pair<int, Domain>(const Domain& implied)> transfer_f =
626 nullptr;
627
628 const int var = local_model.var;
629 // We only support a few cases.
630 //
631 // TODO(user): implement more! Note that the linear2 case was tempting, but if
632 // we don't have an equality, we can't transfer, and if we do, we actually
633 // have affine equivalence already.
634 if (other_ct.constraint_case() == ConstraintProto::kLinMax &&
635 other_ct.lin_max().target().vars().size() == 1 &&
636 other_ct.lin_max().target().vars(0) == var &&
637 std::abs(other_ct.lin_max().target().coeffs(0)) == 1 &&
638 IsAffineIntAbs(other_ct)) {
639 context->UpdateRuleStats("linear1: transferred from abs(X) to X");
640 const LinearExpressionProto& target = other_ct.lin_max().target();
641 const LinearExpressionProto& expr = other_ct.lin_max().exprs(0);
642 transfer_f = [target = target, expr = expr](const Domain& implied) {
643 Domain target_domain =
644 implied.ContinuousMultiplicationBy(target.coeffs(0))
645 .AdditionWith(Domain(target.offset()));
646 target_domain = target_domain.IntersectionWith(
647 Domain(0, std::numeric_limits<int64_t>::max()));
648
649 // We have target = abs(expr).
650 const Domain expr_domain =
651 target_domain.UnionWith(target_domain.Negation());
652 const Domain new_domain = expr_domain.AdditionWith(Domain(-expr.offset()))
654 return std::make_pair(expr.vars(0), new_domain);
655 };
656 }
657
658 if (transfer_f == nullptr) {
659 context->UpdateRuleStats(
660 "TODO linear1: appear in only one extra 2-var constraint");
661 return true;
662 }
663
664 // Applies transfer_f to all linear1.
665 const Domain var_domain = context->DomainOf(var);
666 for (const int c : to_rewrite) {
668 if (ct->linear().vars(0) != var || ct->linear().coeffs(0) != 1) {
669 // This shouldn't happen.
670 LOG(INFO) << "Aborted in MaybeTransferLinear1ToAnotherVariable()";
671 return true;
672 }
673
674 const Domain implied =
676 auto [new_var, new_domain] = transfer_f(implied);
677 const Domain current = context->DomainOf(new_var);
678 new_domain = new_domain.IntersectionWith(current);
679 if (new_domain.IsEmpty()) {
680 if (!context->MarkConstraintAsFalse(ct, "linear1: unsat transfer")) {
681 return false;
682 }
683 } else if (new_domain == current) {
684 // Note that we don't need to remove this constraint from
685 // local_model.linear1_constraints since we will set
686 // local_model.var = -1 below.
687 ct->Clear();
688 } else {
689 ct->mutable_linear()->set_vars(0, new_var);
690 FillDomainInProto(new_domain, ct->mutable_linear());
691 }
693 }
694
695 // Copy other_ct to the mapping model and delete var!
696 context->NewMappingConstraint(other_ct, __FILE__, __LINE__);
697 context->working_model->mutable_constraints(other_c)->Clear();
698 context->UpdateConstraintVariableUsage(other_c);
699 context->MarkVariableAsRemoved(var);
700 local_model.var = -1;
701 return true;
702}
703} // namespace sat
704} // namespace operations_research
void Set(IndexType i)
Definition bitset.h:543
Domain IntersectionWith(const Domain &domain) const
Domain ContinuousMultiplicationBy(int64_t coeff) const
Domain AdditionWith(const Domain &domain) const
bool IsIncludedIn(const Domain &domain) const
Domain UnionWith(const Domain &domain) const
Domain InverseMultiplicationBy(int64_t coeff) const
::google::protobuf::RepeatedField<::int32_t > *PROTOBUF_NONNULL mutable_literals()
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
const ::operations_research::sat::LinearConstraintProto & linear() const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
ABSL_ATTRIBUTE_REINITIALIZES void Clear() PROTOBUF_FINAL
const ::operations_research::sat::BoolArgumentProto & bool_or() const
::operations_research::sat::LinearConstraintProto *PROTOBUF_NONNULL mutable_linear()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_at_most_one()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL mutable_constraints(int index)
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL add_constraints()
void set_vars(int index, ::int32_t value)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
absl::Span< const int > ConstraintToVars(int c) const
int NewBoolVarWithClause(absl::Span< const int > clause)
bool MarkConstraintAsFalse(ConstraintProto *ct, std::string_view reason)
ConstraintProto * NewMappingConstraint(absl::string_view file, int line)
const absl::flat_hash_set< int > & VarToConstraints(int var) const
const absl::flat_hash_map< int, int64_t > & ObjectiveMap() const
void UpdateRuleStats(std::string_view name, int num_times=1)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
std::vector< VariableEncodingLocalModel > CreateVariableEncodingLocalModels(PresolveContext *context)
bool BasicPresolveAndGetFullyEncodedDomains(PresolveContext *context, VariableEncodingLocalModel &local_model, absl::flat_hash_map< int, Domain > *result, bool *changed)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
bool MaybeTransferLinear1ToAnotherVariable(VariableEncodingLocalModel &local_model, PresolveContext *context)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
bool IsAffineIntAbs(const ConstraintProto &ct)
bool DetectEncodedComplexDomain(PresolveContext *context, int ct_index, VariableEncodingLocalModel &local_model, absl::flat_hash_map< int, Domain > *fully_encoded_domains, bool *changed)
bool DetectAllEncodedComplexDomain(PresolveContext *context, VariableEncodingLocalModel &local_model)
OR-Tools root namespace.
absl::flat_hash_set< int > bools_only_used_inside_the_local_model