OR-Tools  9.1
presolve_context.cc
Go to the documentation of this file.
1// Copyright 2010-2021 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <limits>
19#include <string>
20
25
26namespace operations_research {
27namespace sat {
28
30 return context->GetLiteralRepresentative(ref_);
31}
32
34 return context->GetVariableRepresentative(ref_);
35}
36
37void PresolveContext::ClearStats() { stats_by_rule_name_.clear(); }
38
41 FillDomainInProto(domain, var);
43 return working_model->variables_size() - 1;
44}
45
47
49 if (!gtl::ContainsKey(constant_to_ref_, cst)) {
50 constant_to_ref_[cst] = SavedVariable(working_model->variables_size());
52 var_proto->add_domain(cst);
53 var_proto->add_domain(cst);
55 }
56 return constant_to_ref_[cst].Get(this);
57}
58
59// a => b.
62 ct->add_enforcement_literal(a);
63 ct->mutable_bool_and()->add_literals(b);
64}
65
66// b => x in [lb, ub].
67void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) {
69
70 // Doing it like this seems to use slightly less memory.
71 // TODO(user): Find the best way to create such small proto.
72 imply->mutable_enforcement_literal()->Resize(1, b);
73 LinearConstraintProto* mutable_linear = imply->mutable_linear();
74 mutable_linear->mutable_vars()->Resize(1, x);
75 mutable_linear->mutable_coeffs()->Resize(1, 1);
76 FillDomainInProto(domain, mutable_linear);
77}
78
79bool PresolveContext::DomainIsEmpty(int ref) const {
80 return domains[PositiveRef(ref)].IsEmpty();
81}
82
83bool PresolveContext::IsFixed(int ref) const {
84 DCHECK_LT(PositiveRef(ref), domains.size());
85 DCHECK(!DomainIsEmpty(ref));
86 return domains[PositiveRef(ref)].IsFixed();
87}
88
90 const int var = PositiveRef(ref);
91 return domains[var].Min() >= 0 && domains[var].Max() <= 1;
92}
93
94bool PresolveContext::LiteralIsTrue(int lit) const {
96 if (RefIsPositive(lit)) {
97 return domains[lit].Min() == 1;
98 } else {
99 return domains[PositiveRef(lit)].Max() == 0;
100 }
101}
102
105 if (RefIsPositive(lit)) {
106 return domains[lit].Max() == 0;
107 } else {
108 return domains[PositiveRef(lit)].Min() == 1;
109 }
110}
111
112int64_t PresolveContext::MinOf(int ref) const {
113 DCHECK(!DomainIsEmpty(ref));
114 return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min()
115 : -domains[PositiveRef(ref)].Max();
116}
117
118int64_t PresolveContext::MaxOf(int ref) const {
119 DCHECK(!DomainIsEmpty(ref));
120 return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max()
121 : -domains[PositiveRef(ref)].Min();
122}
123
125 int64_t result = expr.offset();
126 for (int i = 0; i < expr.vars_size(); ++i) {
127 const int64_t coeff = expr.coeffs(i);
128 if (coeff > 0) {
129 result += coeff * MinOf(expr.vars(i));
130 } else {
131 result += coeff * MaxOf(expr.vars(i));
132 }
133 }
134 return result;
135}
136
138 int64_t result = expr.offset();
139 for (int i = 0; i < expr.vars_size(); ++i) {
140 const int64_t coeff = expr.coeffs(i);
141 if (coeff > 0) {
142 result += coeff * MaxOf(expr.vars(i));
143 } else {
144 result += coeff * MinOf(expr.vars(i));
145 }
146 }
147 return result;
148}
149
151 const LinearExpressionProto& expr) const {
152 Domain result(expr.offset());
153 for (int i = 0; i < expr.vars_size(); ++i) {
154 result = result.AdditionWith(
155 DomainOf(expr.vars(i)).MultiplicationBy(expr.coeffs(i)));
156 }
157 return result;
158}
159
161 const LinearExpressionProto& expr) const {
162 if (expr.vars().size() != 1) return false;
163 return CanBeUsedAsLiteral(expr.vars(0));
164}
165
167 const LinearExpressionProto& expr) const {
168 const int ref = expr.vars(0);
169 return RefIsPositive(ref) == (expr.coeffs(0) > 0) ? ref : NegatedRef(ref);
170}
171
172// Note that we only support converted intervals.
175 if (!proto.enforcement_literal().empty()) return false;
176 if (!proto.interval().has_start_view()) return false;
177 for (const int var : proto.interval().start_view().vars()) {
178 if (!IsFixed(var)) return false;
179 }
180 for (const int var : proto.interval().size_view().vars()) {
181 if (!IsFixed(var)) return false;
182 }
183 for (const int var : proto.interval().end_view().vars()) {
184 if (!IsFixed(var)) return false;
185 }
186 return true;
187}
188
189std::string PresolveContext::IntervalDebugString(int ct_ref) const {
190 if (IntervalIsConstant(ct_ref)) {
191 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), "..",
192 EndMax(ct_ref), ")");
193 } else if (ConstraintIsOptional(ct_ref)) {
194 const int literal =
196 if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
197 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
198 StartMin(ct_ref), " --(", SizeMin(ct_ref), ")--> ",
199 EndMax(ct_ref), ")");
200 } else {
201 return absl::StrCat("interval_", ct_ref, "(lit=", literal, ", ",
202 StartMin(ct_ref), " --(", SizeMin(ct_ref), "..",
203 SizeMax(ct_ref), ")--> ", EndMax(ct_ref), ")");
204 }
205 } else if (SizeMin(ct_ref) == SizeMax(ct_ref)) {
206 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
207 SizeMin(ct_ref), ")--> ", EndMax(ct_ref), ")");
208 } else {
209 return absl::StrCat("interval_", ct_ref, "(", StartMin(ct_ref), " --(",
210 SizeMin(ct_ref), "..", SizeMax(ct_ref), ")--> ",
211 EndMax(ct_ref), ")");
212 }
213}
214
215int64_t PresolveContext::StartMin(int ct_ref) const {
218 if (interval.has_start_view()) return MinOf(interval.start_view());
219 return MinOf(interval.start());
220}
221
222int64_t PresolveContext::StartMax(int ct_ref) const {
225 if (interval.has_start_view()) return MaxOf(interval.start_view());
226 return MaxOf(interval.start());
227}
228
229int64_t PresolveContext::EndMin(int ct_ref) const {
232 if (interval.has_end_view()) return MinOf(interval.end_view());
233 return MinOf(interval.end());
234}
235
236int64_t PresolveContext::EndMax(int ct_ref) const {
239 if (interval.has_end_view()) return MaxOf(interval.end_view());
240 return MaxOf(interval.end());
241}
242
243int64_t PresolveContext::SizeMin(int ct_ref) const {
246 if (interval.has_size_view()) return MinOf(interval.size_view());
247 return MinOf(interval.size());
248}
249
250int64_t PresolveContext::SizeMax(int ct_ref) const {
253 if (interval.has_size_view()) return MaxOf(interval.size_view());
254 return MaxOf(interval.size());
255}
256
257// Important: To be sure a variable can be removed, we need it to not be a
258// representative of both affine and equivalence relation.
259bool PresolveContext::VariableIsNotRepresentativeOfEquivalenceClass(
260 int var) const {
262 if (affine_relations_.ClassSize(var) > 1 &&
263 affine_relations_.Get(var).representative == var) {
264 return false;
265 }
266 if (var_equiv_relations_.ClassSize(var) > 1 &&
267 var_equiv_relations_.Get(var).representative == var) {
268 return false;
269 }
270 return true;
271}
272
274 const int var = PositiveRef(ref);
275 return VariableIsNotRepresentativeOfEquivalenceClass(var) &&
277}
278
279// Tricky: If this variable is equivalent to another one (but not the
280// representative) and appear in just one constraint, then this constraint must
281// be the affine defining one. And in this case the code using this function
282// should do the proper stuff.
284 if (!ConstraintVariableGraphIsUpToDate()) return false;
285 const int var = PositiveRef(ref);
286 return var_to_constraints_[var].size() == 1 && VariableIsRemovable(var);
287}
288
289// Tricky: Same remark as for VariableIsUniqueAndRemovable().
291 if (!ConstraintVariableGraphIsUpToDate()) return false;
292 const int var = PositiveRef(ref);
293 return VariableIsRemovable(var) &&
294 var_to_constraints_[var].contains(kObjectiveConstraint) &&
295 var_to_constraints_[var].size() == 2;
296}
297
298// Here, even if the variable is equivalent to others, if its affine defining
299// constraints where removed, then it is not needed anymore.
301 if (!ConstraintVariableGraphIsUpToDate()) return false;
302 return var_to_constraints_[PositiveRef(ref)].empty();
303}
304
306 removed_variables_.insert(PositiveRef(ref));
307}
308
309// Note(user): I added an indirection and a function for this to be able to
310// display debug information when this return false. This should actually never
311// return false in the cases where it is used.
313 // It is okay to reuse removed fixed variable.
314 if (IsFixed(ref)) return false;
315 if (!removed_variables_.contains(PositiveRef(ref))) return false;
316 if (!var_to_constraints_[PositiveRef(ref)].empty()) {
317 SOLVER_LOG(logger_, "Variable ", PositiveRef(ref),
318 " was removed, yet it appears in some constraints!");
319 SOLVER_LOG(logger_, "affine relation: ",
321 for (const int c : var_to_constraints_[PositiveRef(ref)]) {
323 logger_, "constraint #", c, " : ",
324 c >= 0 ? working_model->constraints(c).ShortDebugString() : "");
325 }
326 }
327 return true;
328}
329
331 int ref) const {
332 if (!ConstraintVariableGraphIsUpToDate()) return false;
333 const int var = PositiveRef(ref);
334 return var_to_num_linear1_[var] == var_to_constraints_[var].size() ||
335 (var_to_constraints_[var].contains(kObjectiveConstraint) &&
336 var_to_num_linear1_[var] + 1 == var_to_constraints_[var].size());
337}
338
340 Domain result;
341 if (RefIsPositive(ref)) {
342 result = domains[ref];
343 } else {
344 result = domains[PositiveRef(ref)].Negation();
345 }
346 return result;
347}
348
349bool PresolveContext::DomainContains(int ref, int64_t value) const {
350 if (!RefIsPositive(ref)) {
351 return domains[PositiveRef(ref)].Contains(-value);
352 }
353 return domains[ref].Contains(value);
354}
355
356ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
357 int ref, const Domain& domain, bool* domain_modified) {
358 DCHECK(!DomainIsEmpty(ref));
359 const int var = PositiveRef(ref);
360
361 if (RefIsPositive(ref)) {
362 if (domains[var].IsIncludedIn(domain)) {
363 return true;
364 }
365 domains[var] = domains[var].IntersectionWith(domain);
366 } else {
367 const Domain temp = domain.Negation();
368 if (domains[var].IsIncludedIn(temp)) {
369 return true;
370 }
371 domains[var] = domains[var].IntersectionWith(temp);
372 }
373
374 if (domain_modified != nullptr) {
375 *domain_modified = true;
376 }
378 if (domains[var].IsEmpty()) {
379 is_unsat_ = true;
380 return false;
381 }
382
383 // Propagate the domain of the representative right away.
384 // Note that the recursive call should only by one level deep.
386 if (r.representative == var) return true;
389 .AdditionWith(Domain(-r.offset))
391}
392
393ABSL_MUST_USE_RESULT bool PresolveContext::IntersectDomainWith(
394 const LinearExpressionProto& expr, const Domain& domain,
395 bool* domain_modified) {
396 if (expr.vars().empty()) {
397 return domain.Contains(expr.offset());
398 }
399 if (expr.vars().size() == 1) { // Affine
400 return IntersectDomainWith(expr.vars(0),
401 domain.AdditionWith(Domain(-expr.offset()))
403 domain_modified);
404 }
405
406 // We don't do anything for longer expression for now.
407 return true;
408}
409
410ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToFalse(int lit) {
411 const int var = PositiveRef(lit);
412 const int64_t value = RefIsPositive(lit) ? 0 : 1;
414}
415
416ABSL_MUST_USE_RESULT bool PresolveContext::SetLiteralToTrue(int lit) {
417 return SetLiteralToFalse(NegatedRef(lit));
418}
419
422 if (ct.constraint_case() ==
423 ConstraintProto::ConstraintCase::CONSTRAINT_NOT_SET) {
424 return true;
425 }
426 for (const int literal : ct.enforcement_literal()) {
427 if (LiteralIsFalse(literal)) return true;
428 }
429 return false;
430}
431
434 bool contains_one_free_literal = false;
435 for (const int literal : ct.enforcement_literal()) {
436 if (LiteralIsFalse(literal)) return false;
437 if (!LiteralIsTrue(literal)) contains_one_free_literal = true;
438 }
439 return contains_one_free_literal;
440}
441
442void PresolveContext::UpdateRuleStats(const std::string& name, int num_times) {
443 // We only count if we are going to display it.
444 if (logger_->LoggingIsEnabled()) {
445 VLOG(2) << num_presolve_operations << " : " << name;
446 stats_by_rule_name_[name] += num_times;
447 }
448 num_presolve_operations += num_times;
449}
450
451void PresolveContext::UpdateLinear1Usage(const ConstraintProto& ct, int c) {
452 const int old_var = constraint_to_linear1_var_[c];
453 if (old_var >= 0) {
454 var_to_num_linear1_[old_var]--;
455 }
456 if (ct.constraint_case() == ConstraintProto::ConstraintCase::kLinear &&
457 ct.linear().vars().size() == 1) {
458 const int var = PositiveRef(ct.linear().vars(0));
459 constraint_to_linear1_var_[c] = var;
460 var_to_num_linear1_[var]++;
461 }
462}
463
464void PresolveContext::AddVariableUsage(int c) {
465 const ConstraintProto& ct = working_model->constraints(c);
466 constraint_to_vars_[c] = UsedVariables(ct);
467 constraint_to_intervals_[c] = UsedIntervals(ct);
468 for (const int v : constraint_to_vars_[c]) {
470 var_to_constraints_[v].insert(c);
471 }
472 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
473 UpdateLinear1Usage(ct, c);
474}
475
477 if (is_unsat_) return;
478 DCHECK_EQ(constraint_to_vars_.size(), working_model->constraints_size());
480
481 // We don't optimize the interval usage as this is not super frequent.
482 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]--;
483 constraint_to_intervals_[c] = UsedIntervals(ct);
484 for (const int i : constraint_to_intervals_[c]) interval_usage_[i]++;
485
486 // For the variables, we avoid an erase() followed by an insert() for the
487 // variables that didn't change.
488 tmp_new_usage_ = UsedVariables(ct);
489 const std::vector<int>& old_usage = constraint_to_vars_[c];
490 const int old_size = old_usage.size();
491 int i = 0;
492 for (const int var : tmp_new_usage_) {
494 while (i < old_size && old_usage[i] < var) {
495 var_to_constraints_[old_usage[i]].erase(c);
496 ++i;
497 }
498 if (i < old_size && old_usage[i] == var) {
499 ++i;
500 } else {
501 var_to_constraints_[var].insert(c);
502 }
503 }
504 for (; i < old_size; ++i) var_to_constraints_[old_usage[i]].erase(c);
505 constraint_to_vars_[c] = tmp_new_usage_;
506
507 UpdateLinear1Usage(ct, c);
508}
509
511 return constraint_to_vars_.size() == working_model->constraints_size();
512}
513
515 if (is_unsat_) return;
516 const int old_size = constraint_to_vars_.size();
517 const int new_size = working_model->constraints_size();
518 CHECK_LE(old_size, new_size);
519 constraint_to_vars_.resize(new_size);
520 constraint_to_linear1_var_.resize(new_size, -1);
521 constraint_to_intervals_.resize(new_size);
522 interval_usage_.resize(new_size);
523 for (int c = old_size; c < new_size; ++c) {
524 AddVariableUsage(c);
525 }
526}
527
528// TODO(user): Also test var_to_constraints_ !!
530 if (is_unsat_) return true; // We do not care in this case.
531 if (constraint_to_vars_.size() != working_model->constraints_size()) {
532 LOG(INFO) << "Wrong constraint_to_vars size!";
533 return false;
534 }
535 for (int c = 0; c < constraint_to_vars_.size(); ++c) {
536 if (constraint_to_vars_[c] !=
538 LOG(INFO) << "Wrong variables usage for constraint: \n"
540 << "old_size: " << constraint_to_vars_[c].size();
541 return false;
542 }
543 }
544 int num_in_objective = 0;
545 for (int v = 0; v < var_to_constraints_.size(); ++v) {
546 if (var_to_constraints_[v].contains(kObjectiveConstraint)) {
547 ++num_in_objective;
548 if (!objective_map_.contains(v)) {
549 LOG(INFO) << "Variable " << v
550 << " is marked as part of the objective but isn't.";
551 return false;
552 }
553 }
554 }
555 if (num_in_objective != objective_map_.size()) {
556 LOG(INFO) << "Not all variables are marked as part of the objective";
557 return false;
558 }
559
560 return true;
561}
562
563// If a Boolean variable (one with domain [0, 1]) appear in this affine
564// equivalence class, then we want its representative to be Boolean. Note that
565// this is always possible because a Boolean variable can never be equal to a
566// multiple of another if std::abs(coeff) is greater than 1 and if it is not
567// fixed to zero. This is important because it allows to simply use the same
568// representative for any referenced literals.
569//
570// Note(user): When both domain contains [0,1] and later the wrong variable
571// become usable as boolean, then we have a bug. Because of that, the code
572// for GetLiteralRepresentative() is not as simple as it should be.
573bool PresolveContext::AddRelation(int x, int y, int64_t c, int64_t o,
574 AffineRelation* repo) {
575 // When the coefficient is larger than one, then if later one variable becomes
576 // Boolean, it must be the representative.
577 if (std::abs(c) != 1) return repo->TryAdd(x, y, c, o);
578
581
582 // To avoid integer overflow, we always want to use the representative with
583 // the smallest domain magnitude. Otherwise we might express a variable in say
584 // [0, 3] as ([x, x + 3] - x) for an arbitrary large x, and substituting
585 // something like this in a linear expression could break our overflow
586 // precondition.
587 //
588 // Note that if either rep_x or rep_y can be used as a literal, then it will
589 // also be the variable with the smallest domain magnitude (1 or 0 if fixed).
590 const int rep_x = repo->Get(x).representative;
591 const int rep_y = repo->Get(y).representative;
592 const int64_t m_x = std::max(std::abs(MinOf(rep_x)), std::abs(MaxOf(rep_x)));
593 const int64_t m_y = std::max(std::abs(MinOf(rep_y)), std::abs(MaxOf(rep_y)));
594 bool allow_rep_x = m_x < m_y;
595 bool allow_rep_y = m_y < m_x;
596 if (m_x == m_y) {
597 // If both magnitude are the same, we prefer a positive domain.
598 // This is important so we don't use [-1, 0] as a representative for [0, 1].
599 allow_rep_x = MinOf(rep_x) >= MinOf(rep_y);
600 allow_rep_y = MinOf(rep_y) >= MinOf(rep_x);
601 }
602 if (allow_rep_x && allow_rep_y) {
603 // If both representative are okay, we force the choice to the variable
604 // with lower index. This is needed because we have two "equivalence"
605 // relations, and we want the same representative in both.
606 if (rep_x < rep_y) {
607 allow_rep_y = false;
608 } else {
609 allow_rep_x = false;
610 }
611 }
612 return repo->TryAdd(x, y, c, o, allow_rep_x, allow_rep_y);
613}
614
615// Note that we just add the relation to the var_equiv_relations_, not to the
616// affine one. This is enough, and should prevent overflow in the affine
617// relation class: if we keep chaining variable fixed to zero, the coefficient
618// in the relation can overflow. For instance if x = 200 y and z = 200 t,
619// nothing prevent us if all end up being zero, to say y = z, which will result
620// in x = 200^2 t. If we make a few bad choices like this, then we can have an
621// overflow.
625 const int64_t min = MinOf(var);
626 if (gtl::ContainsKey(constant_to_ref_, min)) {
627 const int rep = constant_to_ref_[min].Get(this);
628 if (RefIsPositive(rep)) {
629 if (rep != var) {
630 AddRelation(var, rep, 1, 0, &var_equiv_relations_);
631 }
632 } else {
633 if (PositiveRef(rep) == var) {
634 CHECK_EQ(min, 0);
635 } else {
636 AddRelation(var, PositiveRef(rep), -1, 0, &var_equiv_relations_);
637 }
638 }
639 } else {
640 constant_to_ref_[min] = SavedVariable(var);
641 }
642}
643
645 const int var = PositiveRef(ref);
647 if (r.representative == var) return true;
648
649 // Propagate domains both ways.
650 // var = coeff * rep + offset
653 .AdditionWith(Domain(-r.offset))
655 return false;
656 }
659 .AdditionWith(Domain(r.offset)))) {
660 return false;
661 }
662
663 return true;
664}
665
667 for (auto& ref_map : var_to_constraints_) {
668 ref_map.erase(kAffineRelationConstraint);
669 }
670}
671
672// We only call that for a non representative variable that is only used in
673// the kAffineRelationConstraint. Such variable can be ignored and should never
674// be seen again in the presolve.
676 const int rep = GetAffineRelation(var).representative;
677
679 CHECK_NE(var, rep);
680 CHECK_EQ(var_to_constraints_[var].size(), 1);
681 CHECK(var_to_constraints_[var].contains(kAffineRelationConstraint));
682 CHECK(var_to_constraints_[rep].contains(kAffineRelationConstraint));
683
684 // We shouldn't reuse this variable again!
686
687 var_to_constraints_[var].erase(kAffineRelationConstraint);
688 affine_relations_.IgnoreFromClassSize(var);
689 var_equiv_relations_.IgnoreFromClassSize(var);
690
691 // If the representative is left alone, we can remove it from the special
692 // affine relation constraint too.
693 if (affine_relations_.ClassSize(rep) == 1 &&
694 var_equiv_relations_.ClassSize(rep) == 1) {
695 var_to_constraints_[rep].erase(kAffineRelationConstraint);
696 }
697
698 if (VLOG_IS_ON(2)) {
699 LOG(INFO) << "Removing affine relation: " << AffineRelationDebugString(var);
700 }
701}
702
703bool PresolveContext::StoreAffineRelation(int ref_x, int ref_y, int64_t coeff,
704 int64_t offset) {
705 CHECK_NE(coeff, 0);
706 if (is_unsat_) return false;
707
708 // TODO(user): I am not 100% sure why, but sometimes the representative is
709 // fixed but that is not propagated to ref_x or ref_y and this causes issues.
710 if (!PropagateAffineRelation(ref_x)) return true;
711 if (!PropagateAffineRelation(ref_y)) return true;
712
713 if (IsFixed(ref_x)) {
714 const int64_t lhs = DomainOf(ref_x).Min() - offset;
715 if (lhs % std::abs(coeff) != 0) {
716 is_unsat_ = true;
717 return true;
718 }
719 static_cast<void>(IntersectDomainWith(ref_y, Domain(lhs / coeff)));
720 UpdateRuleStats("affine: fixed");
721 return true;
722 }
723
724 if (IsFixed(ref_y)) {
725 const int64_t value_x = DomainOf(ref_y).Min() * coeff + offset;
726 static_cast<void>(IntersectDomainWith(ref_x, Domain(value_x)));
727 UpdateRuleStats("affine: fixed");
728 return true;
729 }
730
731 // If both are already in the same class, we need to make sure the relations
732 // are compatible.
735 if (rx.representative == ry.representative) {
736 // x = rx.coeff * rep + rx.offset;
737 // y = ry.coeff * rep + ry.offset_y;
738 // And x == coeff * ry.coeff * rep + (coeff * ry.offset + offset).
739 //
740 // So we get the relation a * rep == b with a and b defined here:
741 const int64_t a = coeff * ry.coeff - rx.coeff;
742 const int64_t b = coeff * ry.offset + offset - rx.offset;
743 if (a == 0) {
744 if (b != 0) is_unsat_ = true;
745 return true;
746 }
747 if (b % a != 0) {
748 is_unsat_ = true;
749 return true;
750 }
751 UpdateRuleStats("affine: unique solution");
752 const int64_t unique_value = -b / a;
753 if (!IntersectDomainWith(rx.representative, Domain(unique_value))) {
754 return true;
755 }
756 if (!IntersectDomainWith(ref_x,
757 Domain(unique_value * rx.coeff + rx.offset))) {
758 return true;
759 }
760 if (!IntersectDomainWith(ref_y,
761 Domain(unique_value * ry.coeff + ry.offset))) {
762 return true;
763 }
764 return true;
765 }
766
767 const int x = PositiveRef(ref_x);
768 const int y = PositiveRef(ref_y);
769 const int64_t c =
770 RefIsPositive(ref_x) == RefIsPositive(ref_y) ? coeff : -coeff;
771 const int64_t o = RefIsPositive(ref_x) ? offset : -offset;
772
773 // TODO(user): can we force the rep and remove GetAffineRelation()?
774 bool added = AddRelation(x, y, c, o, &affine_relations_);
775 if ((c == 1 || c == -1) && o == 0) {
776 added |= AddRelation(x, y, c, o, &var_equiv_relations_);
777 }
778 if (added) {
779 UpdateRuleStats("affine: new relation");
780
781 // Lets propagate again the new relation. We might as well do it as early
782 // as possible and not all call site do it.
783 if (!PropagateAffineRelation(ref_x)) return true;
784 if (!PropagateAffineRelation(ref_y)) return true;
785
786 // These maps should only contains representative, so only need to remap
787 // either x or y.
788 const int rep = GetAffineRelation(x).representative;
789 if (x != rep) encoding_remap_queue_.push_back(x);
790 if (y != rep) encoding_remap_queue_.push_back(y);
791
792 // The domain didn't change, but this notification allows to re-process any
793 // constraint containing these variables. Note that we do not need to
794 // retrigger a propagation of the constraint containing a variable whose
795 // representative didn't change.
796 if (x != rep) modified_domains.Set(x);
797 if (y != rep) modified_domains.Set(y);
798
799 var_to_constraints_[x].insert(kAffineRelationConstraint);
800 var_to_constraints_[y].insert(kAffineRelationConstraint);
801 return true;
802 }
803
804 UpdateRuleStats("affine: incompatible relation");
805 if (VLOG_IS_ON(1)) {
806 LOG(INFO) << "Cannot add relation " << DomainOf(ref_x) << " = " << coeff
807 << " * " << DomainOf(ref_y) << " + " << offset
808 << " because of incompatibilities with existing relation: ";
809 for (const int ref : {ref_x, ref_y}) {
810 const auto r = GetAffineRelation(ref);
811 LOG(INFO) << DomainOf(ref) << " = " << r.coeff << " * "
812 << DomainOf(r.representative) << " + " << r.offset;
813 }
814 }
815
816 return false;
817}
818
820 if (is_unsat_) return;
821
822 CHECK(!VariableWasRemoved(ref_a));
823 CHECK(!VariableWasRemoved(ref_b));
824 CHECK(!DomainOf(ref_a).IsEmpty());
825 CHECK(!DomainOf(ref_b).IsEmpty());
828
829 if (ref_a == ref_b) return;
830 if (ref_a == NegatedRef(ref_b)) {
831 is_unsat_ = true;
832 return;
833 }
834 const int var_a = PositiveRef(ref_a);
835 const int var_b = PositiveRef(ref_b);
836 if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) {
837 // a = b
838 CHECK(StoreAffineRelation(var_a, var_b, /*coeff=*/1, /*offset=*/0));
839 } else {
840 // a = 1 - b
841 CHECK(StoreAffineRelation(var_a, var_b, /*coeff=*/-1, /*offset=*/1));
842 }
843}
844
845bool PresolveContext::StoreAbsRelation(int target_ref, int ref) {
846 const auto insert_status = abs_relations_.insert(
847 std::make_pair(target_ref, SavedVariable(PositiveRef(ref))));
848 if (!insert_status.second) {
849 // Tricky: overwrite if the old value refer to a now unused variable.
850 const int candidate = insert_status.first->second.Get(this);
851 if (removed_variables_.contains(candidate)) {
852 insert_status.first->second = SavedVariable(PositiveRef(ref));
853 return true;
854 }
855 return false;
856 }
857 return true;
858}
859
860bool PresolveContext::GetAbsRelation(int target_ref, int* ref) {
861 auto it = abs_relations_.find(target_ref);
862 if (it == abs_relations_.end()) return false;
863
864 // Tricky: In some rare case the stored relation can refer to a deleted
865 // variable, so we need to ignore it.
866 //
867 // TODO(user): Incorporate this as part of SavedVariable/SavedLiteral so we
868 // make sure we never forget about this.
869 const int candidate = it->second.Get(this);
870 if (removed_variables_.contains(candidate)) {
871 abs_relations_.erase(it);
872 return false;
873 }
874 *ref = candidate;
876 return true;
877}
878
881
884 // Note(user): This can happen is some corner cases where the affine
885 // relation where added before the variable became usable as Boolean. When
886 // this is the case, the domain will be of the form [x, x + 1] and should be
887 // later remapped to a Boolean variable.
888 return ref;
889 }
890
891 // We made sure that the affine representative can always be used as a
892 // literal. However, if some variable are fixed, we might not have only
893 // (coeff=1 offset=0) or (coeff=-1 offset=1) and we might have something like
894 // (coeff=8 offset=0) which is only valid for both variable at zero...
895 //
896 // What is sure is that depending on the value, only one mapping can be valid
897 // because r.coeff can never be zero.
898 const bool positive_possible = (r.offset == 0 || r.coeff + r.offset == 1);
899 const bool negative_possible = (r.offset == 1 || r.coeff + r.offset == 0);
900 DCHECK_NE(positive_possible, negative_possible);
901 if (RefIsPositive(ref)) {
902 return positive_possible ? r.representative : NegatedRef(r.representative);
903 } else {
904 return positive_possible ? NegatedRef(r.representative) : r.representative;
905 }
906}
907
909 const AffineRelation::Relation r = var_equiv_relations_.Get(PositiveRef(ref));
910 CHECK_EQ(std::abs(r.coeff), 1);
911 CHECK_EQ(r.offset, 0);
912 return RefIsPositive(ref) == (r.coeff == 1) ? r.representative
914}
915
916// This makes sure that the affine relation only uses one of the
917// representative from the var_equiv_relations_.
919 AffineRelation::Relation r = affine_relations_.Get(PositiveRef(ref));
920 AffineRelation::Relation o = var_equiv_relations_.Get(r.representative);
922 if (o.coeff == -1) r.coeff = -r.coeff;
923 if (!RefIsPositive(ref)) {
924 r.coeff *= -1;
925 r.offset *= -1;
926 }
927 return r;
928}
929
930std::string PresolveContext::RefDebugString(int ref) const {
931 return absl::StrCat(RefIsPositive(ref) ? "X" : "-X", PositiveRef(ref),
932 DomainOf(ref).ToString());
933}
934
937 return absl::StrCat(RefDebugString(ref), " = ", r.coeff, " * ",
939}
940
941// Create the internal structure for any new variables in working_model.
943 for (int i = domains.size(); i < working_model->variables_size(); ++i) {
944 domains.emplace_back(ReadDomainFromProto(working_model->variables(i)));
945 if (domains.back().IsEmpty()) {
946 is_unsat_ = true;
947 return;
948 }
949 if (IsFixed(i)) ExploitFixedDomain(i);
950 }
951 modified_domains.Resize(domains.size());
952 var_to_constraints_.resize(domains.size());
953 var_to_num_linear1_.resize(domains.size());
954 var_to_ub_only_constraints.resize(domains.size());
955 var_to_lb_only_constraints.resize(domains.size());
956}
957
958bool PresolveContext::RemapEncodingMaps() {
959 // TODO(user): for now, while the code works most of the time, it triggers
960 // weird side effect that causes some issues in some LNS presolve...
961 // We should continue the investigation before activating it.
962 //
963 // Note also that because all our encoding constraints are present in the
964 // model, they will be remapped, and the new mapping re-added again. So while
965 // the current code might not be efficient, it should eventually reach the
966 // same effect.
967 encoding_remap_queue_.clear();
968
969 // Note that InsertVarValueEncodingInternal() will potentially add new entry
970 // to the encoding_ map, but for a different variables. So this code relies on
971 // the fact that the var_map shouldn't change content nor address of the
972 // "var_map" below while we iterate on them.
973 for (const int var : encoding_remap_queue_) {
976 if (r.representative == var) return true;
977 int num_remapping = 0;
978
979 // Encoding.
980 {
981 const absl::flat_hash_map<int64_t, SavedLiteral>& var_map =
982 encoding_[var];
983 for (const auto& entry : var_map) {
984 const int lit = entry.second.Get(this);
985 if (removed_variables_.contains(PositiveRef(lit))) continue;
986 if ((entry.first - r.offset) % r.coeff != 0) continue;
987 const int64_t rep_value = (entry.first - r.offset) / r.coeff;
988 ++num_remapping;
989 InsertVarValueEncodingInternal(lit, r.representative, rep_value,
990 /*add_constraints=*/false);
991 if (is_unsat_) return false;
992 }
993 encoding_.erase(var);
994 }
995
996 // Eq half encoding.
997 {
998 const absl::flat_hash_map<int64_t, absl::flat_hash_set<int>>& var_map =
999 eq_half_encoding_[var];
1000 for (const auto& entry : var_map) {
1001 if ((entry.first - r.offset) % r.coeff != 0) continue;
1002 const int64_t rep_value = (entry.first - r.offset) / r.coeff;
1003 for (int literal : entry.second) {
1004 ++num_remapping;
1005 InsertHalfVarValueEncoding(GetLiteralRepresentative(literal),
1006 r.representative, rep_value,
1007 /*imply_eq=*/true);
1008 if (is_unsat_) return false;
1009 }
1010 }
1011 eq_half_encoding_.erase(var);
1012 }
1013
1014 // Neq half encoding.
1015 {
1016 const absl::flat_hash_map<int64_t, absl::flat_hash_set<int>>& var_map =
1017 neq_half_encoding_[var];
1018 for (const auto& entry : var_map) {
1019 if ((entry.first - r.offset) % r.coeff != 0) continue;
1020 const int64_t rep_value = (entry.first - r.offset) / r.coeff;
1021 for (int literal : entry.second) {
1022 ++num_remapping;
1023 InsertHalfVarValueEncoding(GetLiteralRepresentative(literal),
1024 r.representative, rep_value,
1025 /*imply_eq=*/false);
1026 if (is_unsat_) return false;
1027 }
1028 }
1029 neq_half_encoding_.erase(var);
1030 }
1031
1032 if (num_remapping > 0) {
1033 VLOG(1) << "Remapped " << num_remapping << " encodings due to " << var
1034 << " -> " << r.representative << ".";
1035 }
1036 }
1037 encoding_remap_queue_.clear();
1038 return !is_unsat_;
1039}
1040
1043 CHECK_EQ(DomainOf(var).Size(), 2);
1044 const int64_t var_min = MinOf(var);
1045 const int64_t var_max = MaxOf(var);
1046
1047 if (is_unsat_) return;
1048
1049 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1050
1051 // Find encoding for min if present.
1052 auto min_it = var_map.find(var_min);
1053 if (min_it != var_map.end()) {
1054 const int old_var = PositiveRef(min_it->second.Get(this));
1055 if (removed_variables_.contains(old_var)) {
1056 var_map.erase(min_it);
1057 min_it = var_map.end();
1058 }
1059 }
1060
1061 // Find encoding for max if present.
1062 auto max_it = var_map.find(var_max);
1063 if (max_it != var_map.end()) {
1064 const int old_var = PositiveRef(max_it->second.Get(this));
1065 if (removed_variables_.contains(old_var)) {
1066 var_map.erase(max_it);
1067 max_it = var_map.end();
1068 }
1069 }
1070
1071 // Insert missing encoding.
1072 int min_literal;
1073 int max_literal;
1074 if (min_it != var_map.end() && max_it != var_map.end()) {
1075 min_literal = min_it->second.Get(this);
1076 max_literal = max_it->second.Get(this);
1077 if (min_literal != NegatedRef(max_literal)) {
1078 UpdateRuleStats("variables with 2 values: merge encoding literals");
1079 StoreBooleanEqualityRelation(min_literal, NegatedRef(max_literal));
1080 if (is_unsat_) return;
1081 }
1082 min_literal = GetLiteralRepresentative(min_literal);
1083 max_literal = GetLiteralRepresentative(max_literal);
1084 if (!IsFixed(min_literal)) CHECK_EQ(min_literal, NegatedRef(max_literal));
1085 } else if (min_it != var_map.end() && max_it == var_map.end()) {
1086 UpdateRuleStats("variables with 2 values: register other encoding");
1087 min_literal = min_it->second.Get(this);
1088 max_literal = NegatedRef(min_literal);
1089 var_map[var_max] = SavedLiteral(max_literal);
1090 } else if (min_it == var_map.end() && max_it != var_map.end()) {
1091 UpdateRuleStats("variables with 2 values: register other encoding");
1092 max_literal = max_it->second.Get(this);
1093 min_literal = NegatedRef(max_literal);
1094 var_map[var_min] = SavedLiteral(min_literal);
1095 } else {
1096 UpdateRuleStats("variables with 2 values: create encoding literal");
1097 max_literal = NewBoolVar();
1098 min_literal = NegatedRef(max_literal);
1099 var_map[var_min] = SavedLiteral(min_literal);
1100 var_map[var_max] = SavedLiteral(max_literal);
1101 }
1102
1103 if (IsFixed(min_literal) || IsFixed(max_literal)) {
1104 CHECK(IsFixed(min_literal));
1105 CHECK(IsFixed(max_literal));
1106 UpdateRuleStats("variables with 2 values: fixed encoding");
1107 if (LiteralIsTrue(min_literal)) {
1108 return static_cast<void>(IntersectDomainWith(var, Domain(var_min)));
1109 } else {
1110 return static_cast<void>(IntersectDomainWith(var, Domain(var_max)));
1111 }
1112 }
1113
1114 // Add affine relation.
1115 if (GetAffineRelation(var).representative != PositiveRef(min_literal)) {
1116 UpdateRuleStats("variables with 2 values: new affine relation");
1117 if (RefIsPositive(max_literal)) {
1119 var_max - var_min, var_min));
1120 } else {
1122 var_min - var_max, var_max));
1123 }
1124 }
1125}
1126
1127void PresolveContext::InsertVarValueEncodingInternal(int literal, int var,
1128 int64_t value,
1129 bool add_constraints) {
1133 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1134
1135 // The code below is not 100% correct if this is not the case.
1136 DCHECK(DomainOf(var).Contains(value));
1137
1138 // Ticky and rare: I have only observed this on the LNS of
1139 // radiation_m18_12_05_sat.fzn. The value was encoded, but maybe we never
1140 // used the involved variables / constraints, so it was removed (with the
1141 // encoding constraints) from the model already! We have to be careful.
1142 const auto it = var_map.find(value);
1143 if (it != var_map.end()) {
1144 const int old_var = PositiveRef(it->second.Get(this));
1145 if (removed_variables_.contains(old_var)) {
1146 var_map.erase(it);
1147 }
1148 }
1149
1150 const auto insert =
1151 var_map.insert(std::make_pair(value, SavedLiteral(literal)));
1152
1153 // If an encoding already exist, make the two Boolean equals.
1154 if (!insert.second) {
1155 const int previous_literal = insert.first->second.Get(this);
1156 CHECK(!VariableWasRemoved(previous_literal));
1157 if (literal != previous_literal) {
1159 "variables: merge equivalent var value encoding literals");
1160 StoreBooleanEqualityRelation(literal, previous_literal);
1161 }
1162 return;
1163 }
1164
1165 if (DomainOf(var).Size() == 2) {
1166 // TODO(user): There is a bug here if the var == value was not in the
1167 // domain, it will just be ignored.
1169 } else {
1170 VLOG(2) << "Insert lit(" << literal << ") <=> var(" << var
1171 << ") == " << value;
1172 eq_half_encoding_[var][value].insert(literal);
1173 neq_half_encoding_[var][value].insert(NegatedRef(literal));
1174 if (add_constraints) {
1175 UpdateRuleStats("variables: add encoding constraint");
1176 AddImplyInDomain(literal, var, Domain(value));
1177 AddImplyInDomain(NegatedRef(literal), var, Domain(value).Complement());
1178 }
1179 }
1180}
1181
1182bool PresolveContext::InsertHalfVarValueEncoding(int literal, int var,
1183 int64_t value, bool imply_eq) {
1184 if (is_unsat_) return false;
1186
1187 // Creates the linking sets on demand.
1188 // Insert the enforcement literal in the half encoding map.
1189 auto& direct_set =
1190 imply_eq ? eq_half_encoding_[var][value] : neq_half_encoding_[var][value];
1191 if (!direct_set.insert(literal).second) return false; // Already there.
1192
1193 VLOG(2) << "Collect lit(" << literal << ") implies var(" << var
1194 << (imply_eq ? ") == " : ") != ") << value;
1195 UpdateRuleStats("variables: detect half reified value encoding");
1196
1197 // Note(user): We don't expect a lot of literals in these sets, so doing
1198 // a scan should be okay.
1199 auto& other_set =
1200 imply_eq ? neq_half_encoding_[var][value] : eq_half_encoding_[var][value];
1201 for (const int other : other_set) {
1202 if (GetLiteralRepresentative(other) != NegatedRef(literal)) continue;
1203
1204 UpdateRuleStats("variables: detect fully reified value encoding");
1205 const int imply_eq_literal = imply_eq ? literal : NegatedRef(literal);
1206 InsertVarValueEncodingInternal(imply_eq_literal, var, value,
1207 /*add_constraints=*/false);
1208 break;
1209 }
1210
1211 return true;
1212}
1213
1214bool PresolveContext::CanonicalizeEncoding(int* ref, int64_t* value) {
1215 const AffineRelation::Relation r = GetAffineRelation(*ref);
1216 if ((*value - r.offset) % r.coeff != 0) return false;
1217 *ref = r.representative;
1218 *value = (*value - r.offset) / r.coeff;
1219 return true;
1220}
1221
1223 int64_t value) {
1224 if (!RemapEncodingMaps()) return false;
1225 if (!CanonicalizeEncoding(&ref, &value)) {
1226 return SetLiteralToFalse(literal);
1227 }
1229 InsertVarValueEncodingInternal(literal, ref, value, /*add_constraints=*/true);
1230 return true;
1231}
1232
1234 int64_t value) {
1235 if (!RemapEncodingMaps()) return false;
1236 if (!CanonicalizeEncoding(&var, &value)) return false;
1238 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/true);
1239}
1240
1242 int64_t value) {
1243 if (!RemapEncodingMaps()) return false;
1244 if (!CanonicalizeEncoding(&var, &value)) return false;
1246 return InsertHalfVarValueEncoding(literal, var, value, /*imply_eq=*/false);
1247}
1248
1250 int* literal) {
1252 if (!RemapEncodingMaps()) return false;
1253 if (!CanonicalizeEncoding(&ref, &value)) return false;
1254 const absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[ref];
1255 const auto it = var_map.find(value);
1256 if (it != var_map.end()) {
1257 if (literal != nullptr) {
1258 *literal = it->second.Get(this);
1259 }
1260 return true;
1261 }
1262 return false;
1263}
1264
1266 // TODO(user): Remove this precondition. For now it is needed because
1267 // we might remove encoding literal without updating the encoding map.
1268 // This is related to RemapEncodingMaps() which is currently disabled.
1270
1272 if (!RemapEncodingMaps()) return GetOrCreateConstantVar(0);
1273 if (!CanonicalizeEncoding(&ref, &value)) return GetOrCreateConstantVar(0);
1274
1275 // Positive after CanonicalizeEncoding().
1276 const int var = ref;
1277
1278 // Returns the false literal if the value is not in the domain.
1279 if (!domains[var].Contains(value)) {
1280 return GetOrCreateConstantVar(0);
1281 }
1282
1283 // Returns the associated literal if already present.
1284 absl::flat_hash_map<int64_t, SavedLiteral>& var_map = encoding_[var];
1285 auto it = var_map.find(value);
1286 if (it != var_map.end()) {
1287 return it->second.Get(this);
1288 }
1289
1290 // Special case for fixed domains.
1291 if (domains[var].Size() == 1) {
1292 const int true_literal = GetOrCreateConstantVar(1);
1293 var_map[value] = SavedLiteral(true_literal);
1294 return true_literal;
1295 }
1296
1297 // Special case for domains of size 2.
1298 const int64_t var_min = MinOf(var);
1299 const int64_t var_max = MaxOf(var);
1300 if (domains[var].Size() == 2) {
1301 // Checks if the other value is already encoded.
1302 const int64_t other_value = value == var_min ? var_max : var_min;
1303 auto other_it = var_map.find(other_value);
1304 if (other_it != var_map.end()) {
1305 // Update the encoding map. The domain could have been reduced to size
1306 // two after the creation of the first literal.
1307 const int literal = NegatedRef(other_it->second.Get(this));
1308 var_map[value] = SavedLiteral(literal);
1309 return literal;
1310 }
1311
1312 if (var_min == 0 && var_max == 1) {
1314 var_map[1] = SavedLiteral(representative);
1315 var_map[0] = SavedLiteral(NegatedRef(representative));
1317 } else {
1318 const int literal = NewBoolVar();
1321 return value == var_max ? representative : NegatedRef(representative);
1322 }
1323 }
1324
1325 const int literal = NewBoolVar();
1328}
1329
1332
1333 objective_offset_ = obj.offset();
1334 objective_scaling_factor_ = obj.scaling_factor();
1335 if (objective_scaling_factor_ == 0.0) {
1336 objective_scaling_factor_ = 1.0;
1337 }
1338 if (!obj.domain().empty()) {
1339 // We might relax this in CanonicalizeObjective() when we will compute
1340 // the possible objective domain from the domains of the variables.
1341 objective_domain_is_constraining_ = true;
1342 objective_domain_ = ReadDomainFromProto(obj);
1343 } else {
1344 objective_domain_is_constraining_ = false;
1345 objective_domain_ = Domain::AllValues();
1346 }
1347
1348 // This is an upper bound of the higher magnitude that can be reach by
1349 // summing an objective partial sum. Because of the model validation, this
1350 // shouldn't overflow, and we make sure it stays this way.
1351 objective_overflow_detection_ = 0;
1352
1353 objective_map_.clear();
1354 for (int i = 0; i < obj.vars_size(); ++i) {
1355 const int ref = obj.vars(i);
1356 int64_t coeff = obj.coeffs(i);
1357 if (!RefIsPositive(ref)) coeff = -coeff;
1358 int var = PositiveRef(ref);
1359
1360 objective_overflow_detection_ +=
1361 std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1362
1363 objective_map_[var] += coeff;
1364 if (objective_map_[var] == 0) {
1365 objective_map_.erase(var);
1366 var_to_constraints_[var].erase(kObjectiveConstraint);
1367 } else {
1368 var_to_constraints_[var].insert(kObjectiveConstraint);
1369 }
1370 }
1371}
1372
1374 int64_t offset_change = 0;
1375
1376 // We replace each entry by its affine representative.
1377 // Note that the non-deterministic loop is fine, but because we iterate
1378 // one the map while modifying it, it is safer to do a copy rather than to
1379 // try to handle that in one pass.
1380 tmp_entries_.clear();
1381 for (const auto& entry : objective_map_) {
1382 tmp_entries_.push_back(entry);
1383 }
1384
1385 // TODO(user): This is a bit duplicated with the presolve linear code.
1386 // We also do not propagate back any domain restriction from the objective to
1387 // the variables if any.
1388 for (const auto& entry : tmp_entries_) {
1389 const int var = entry.first;
1390 const auto it = objective_map_.find(var);
1391 if (it == objective_map_.end()) continue;
1392 const int64_t coeff = it->second;
1393
1394 // If a variable only appear in objective, we can fix it!
1395 // Note that we don't care if it was in affine relation, because if none
1396 // of the relations are left, then we can still fix it.
1397 if (!keep_all_feasible_solutions && !objective_domain_is_constraining_ &&
1399 var_to_constraints_[var].size() == 1 &&
1400 var_to_constraints_[var].contains(kObjectiveConstraint)) {
1401 UpdateRuleStats("objective: variable not used elsewhere");
1402 if (coeff > 0) {
1404 return false;
1405 }
1406 } else {
1408 return false;
1409 }
1410 }
1411 }
1412
1413 if (IsFixed(var)) {
1414 offset_change += coeff * MinOf(var);
1415 var_to_constraints_[var].erase(kObjectiveConstraint);
1416 objective_map_.erase(var);
1417 continue;
1418 }
1419
1421 if (r.representative == var) continue;
1422
1423 objective_map_.erase(var);
1424 var_to_constraints_[var].erase(kObjectiveConstraint);
1425
1426 // Do the substitution.
1427 offset_change += coeff * r.offset;
1428 const int64_t new_coeff = objective_map_[r.representative] +=
1429 coeff * r.coeff;
1430
1431 // Process new term.
1432 if (new_coeff == 0) {
1433 objective_map_.erase(r.representative);
1434 var_to_constraints_[r.representative].erase(kObjectiveConstraint);
1435 } else {
1436 var_to_constraints_[r.representative].insert(kObjectiveConstraint);
1437 if (IsFixed(r.representative)) {
1438 offset_change += new_coeff * MinOf(r.representative);
1439 var_to_constraints_[r.representative].erase(kObjectiveConstraint);
1440 objective_map_.erase(r.representative);
1441 }
1442 }
1443 }
1444
1445 Domain implied_domain(0);
1446 int64_t gcd(0);
1447
1448 // We need to sort the entries to be deterministic.
1449 tmp_entries_.clear();
1450 for (const auto& entry : objective_map_) {
1451 tmp_entries_.push_back(entry);
1452 }
1453 std::sort(tmp_entries_.begin(), tmp_entries_.end());
1454 for (const auto& entry : tmp_entries_) {
1455 const int var = entry.first;
1456 const int64_t coeff = entry.second;
1457 gcd = MathUtil::GCD64(gcd, std::abs(coeff));
1458 implied_domain =
1459 implied_domain.AdditionWith(DomainOf(var).MultiplicationBy(coeff))
1461 }
1462
1463 // This is the new domain.
1464 // Note that the domain never include the offset.
1465 objective_domain_ = objective_domain_.AdditionWith(Domain(-offset_change))
1466 .IntersectionWith(implied_domain);
1467 objective_domain_ =
1468 objective_domain_.SimplifyUsingImpliedDomain(implied_domain);
1469
1470 // Updat the offset.
1471 objective_offset_ += offset_change;
1472
1473 // Maybe divide by GCD.
1474 if (gcd > 1) {
1475 for (auto& entry : objective_map_) {
1476 entry.second /= gcd;
1477 }
1478 objective_domain_ = objective_domain_.InverseMultiplicationBy(gcd);
1479 objective_offset_ /= static_cast<double>(gcd);
1480 objective_scaling_factor_ *= static_cast<double>(gcd);
1481 }
1482
1483 if (objective_domain_.IsEmpty()) return false;
1484
1485 // Detect if the objective domain do not limit the "optimal" objective value.
1486 // If this is true, then we can apply any reduction that reduce the objective
1487 // value without any issues.
1488 objective_domain_is_constraining_ =
1489 !implied_domain
1491 objective_domain_.Max()))
1492 .IsIncludedIn(objective_domain_);
1493 return true;
1494}
1495
1497 objective_map_.erase(var);
1498 var_to_constraints_[var].erase(kObjectiveConstraint);
1499}
1500
1502 int64_t& map_ref = objective_map_[var];
1503 map_ref += value;
1504 if (map_ref == 0) {
1505 objective_map_.erase(var);
1506 var_to_constraints_[var].erase(kObjectiveConstraint);
1507 } else {
1508 var_to_constraints_[var].insert(kObjectiveConstraint);
1509 }
1510}
1511
1513 // Tricky: The objective domain is without the offset, so we need to shift it.
1514 objective_offset_ += static_cast<double>(value);
1515 objective_domain_ = objective_domain_.AdditionWith(Domain(-value));
1516}
1517
1519 int var_in_equality, int64_t coeff_in_equality,
1520 const ConstraintProto& equality, std::vector<int>* new_vars_in_objective) {
1521 CHECK(equality.enforcement_literal().empty());
1522 CHECK(RefIsPositive(var_in_equality));
1523
1524 if (new_vars_in_objective != nullptr) new_vars_in_objective->clear();
1525
1526 // We can only "easily" substitute if the objective coefficient is a multiple
1527 // of the one in the constraint.
1528 const int64_t coeff_in_objective =
1529 gtl::FindOrDie(objective_map_, var_in_equality);
1530 CHECK_NE(coeff_in_equality, 0);
1531 CHECK_EQ(coeff_in_objective % coeff_in_equality, 0);
1532 const int64_t multiplier = coeff_in_objective / coeff_in_equality;
1533
1534 // Abort if the new objective seems to violate our overflow preconditions.
1535 int64_t change = 0;
1536 for (int i = 0; i < equality.linear().vars().size(); ++i) {
1537 int var = equality.linear().vars(i);
1538 if (PositiveRef(var) == var_in_equality) continue;
1539 int64_t coeff = equality.linear().coeffs(i);
1540 change +=
1541 std::abs(coeff) * std::max(std::abs(MinOf(var)), std::abs(MaxOf(var)));
1542 }
1543 const int64_t new_value =
1544 CapAdd(CapProd(std::abs(multiplier), change),
1545 objective_overflow_detection_ -
1546 std::abs(coeff_in_equality) *
1547 std::max(std::abs(MinOf(var_in_equality)),
1548 std::abs(MaxOf(var_in_equality))));
1549 if (new_value == std::numeric_limits<int64_t>::max()) return false;
1550 objective_overflow_detection_ = new_value;
1551
1552 for (int i = 0; i < equality.linear().vars().size(); ++i) {
1553 int var = equality.linear().vars(i);
1554 int64_t coeff = equality.linear().coeffs(i);
1555 if (!RefIsPositive(var)) {
1556 var = NegatedRef(var);
1557 coeff = -coeff;
1558 }
1559 if (var == var_in_equality) continue;
1560
1561 int64_t& map_ref = objective_map_[var];
1562 if (map_ref == 0 && new_vars_in_objective != nullptr) {
1563 new_vars_in_objective->push_back(var);
1564 }
1565 map_ref -= coeff * multiplier;
1566
1567 if (map_ref == 0) {
1568 objective_map_.erase(var);
1569 var_to_constraints_[var].erase(kObjectiveConstraint);
1570 } else {
1571 var_to_constraints_[var].insert(kObjectiveConstraint);
1572 }
1573 }
1574
1575 objective_map_.erase(var_in_equality);
1576 var_to_constraints_[var_in_equality].erase(kObjectiveConstraint);
1577
1578 // Deal with the offset.
1579 Domain offset = ReadDomainFromProto(equality.linear());
1580 DCHECK_EQ(offset.Min(), offset.Max());
1581 bool exact = true;
1582 offset = offset.MultiplicationBy(multiplier, &exact);
1583 CHECK(exact);
1584 CHECK(!offset.IsEmpty());
1585
1586 // Tricky: The objective domain is without the offset, so we need to shift it.
1587 objective_offset_ += static_cast<double>(offset.Min());
1588 objective_domain_ = objective_domain_.AdditionWith(Domain(-offset.Min()));
1589
1590 // Because we can assume that the constraint we used was constraining
1591 // (otherwise it would have been removed), the objective domain should be now
1592 // constraining.
1593 objective_domain_is_constraining_ = true;
1594
1595 if (objective_domain_.IsEmpty()) {
1596 return NotifyThatModelIsUnsat();
1597 }
1598 return true;
1599}
1600
1602 absl::Span<const int> exactly_one) {
1603 int64_t min_coeff = std::numeric_limits<int64_t>::max();
1604 for (const int ref : exactly_one) {
1605 const auto it = objective_map_.find(PositiveRef(ref));
1606 if (it == objective_map_.end()) return false;
1607
1608 const int64_t coeff = it->second;
1609 if (RefIsPositive(ref)) {
1610 min_coeff = std::min(min_coeff, coeff);
1611 } else {
1612 // Objective = coeff * var = coeff * (1 - ref);
1613 min_coeff = std::min(min_coeff, -coeff);
1614 }
1615 }
1616
1617 int64_t offset = min_coeff;
1618 for (const int ref : exactly_one) {
1619 const int var = PositiveRef(ref);
1620 int64_t& map_ref = objective_map_.at(var);
1621 if (RefIsPositive(ref)) {
1622 map_ref -= min_coeff;
1623 if (map_ref == 0) {
1624 objective_map_.erase(var);
1625 var_to_constraints_[var].erase(kObjectiveConstraint);
1626 }
1627 } else {
1628 // Term = coeff * (1 - X) = coeff - coeff * X;
1629 // So -coeff -> -coeff -min_coeff
1630 // And Term = coeff + min_coeff - min_coeff - (coeff + min_coeff) * X
1631 // = (coeff + min_coeff) * (1 - X) - min_coeff;
1632 map_ref += min_coeff;
1633 if (map_ref == 0) {
1634 objective_map_.erase(var);
1635 var_to_constraints_[var].erase(kObjectiveConstraint);
1636 }
1637 offset -= min_coeff;
1638 }
1639 }
1640
1641 // Note that the domain never include the offset, so we need to update it.
1642 if (offset != 0) {
1643 objective_offset_ += offset;
1644 objective_domain_ = objective_domain_.AdditionWith(Domain(-offset));
1645 }
1646
1647 return true;
1648}
1649
1651 // We need to sort the entries to be deterministic.
1652 std::vector<std::pair<int, int64_t>> entries;
1653 for (const auto& entry : objective_map_) {
1654 entries.push_back(entry);
1655 }
1656 std::sort(entries.begin(), entries.end());
1657
1659 mutable_obj->set_offset(objective_offset_);
1660 mutable_obj->set_scaling_factor(objective_scaling_factor_);
1661 FillDomainInProto(objective_domain_, mutable_obj);
1662 mutable_obj->clear_vars();
1663 mutable_obj->clear_coeffs();
1664 for (const auto& entry : entries) {
1665 mutable_obj->add_vars(entry.first);
1666 mutable_obj->add_coeffs(entry.second);
1667 }
1668}
1669
1671 int active_i,
1672 int active_j) {
1673 CHECK(!LiteralIsFalse(active_i));
1674 CHECK(!LiteralIsFalse(active_j));
1675
1676 // Sort the active literals.
1677 if (active_j < active_i) std::swap(active_i, active_j);
1678
1679 const std::tuple<int, int, int, int> key =
1680 std::make_tuple(time_i, time_j, active_i, active_j);
1681 const auto& it = reified_precedences_cache_.find(key);
1682 if (it != reified_precedences_cache_.end()) return it->second;
1683
1684 const int result = NewBoolVar();
1685 reified_precedences_cache_[key] = result;
1686
1687 // result => (time_i <= time_j) && active_i && active_j.
1689 lesseq->add_enforcement_literal(result);
1690 lesseq->mutable_linear()->add_vars(time_i);
1691 lesseq->mutable_linear()->add_vars(time_j);
1692 lesseq->mutable_linear()->add_coeffs(-1);
1693 lesseq->mutable_linear()->add_coeffs(1);
1694 lesseq->mutable_linear()->add_domain(0);
1696 if (!LiteralIsTrue(active_i)) {
1697 AddImplication(result, active_i);
1698 }
1699 if (!LiteralIsTrue(active_j)) {
1700 AddImplication(result, active_j);
1701 }
1702
1703 // Not(result) && active_i && active_j => (time_i > time_j)
1704 ConstraintProto* const greater = working_model->add_constraints();
1705 greater->mutable_linear()->add_vars(time_i);
1706 greater->mutable_linear()->add_vars(time_j);
1707 greater->mutable_linear()->add_coeffs(-1);
1708 greater->mutable_linear()->add_coeffs(1);
1710 greater->mutable_linear()->add_domain(-1);
1711
1712 // Manages enforcement literal.
1713 greater->add_enforcement_literal(NegatedRef(result));
1714 if (!LiteralIsTrue(active_i)) {
1715 greater->add_enforcement_literal(active_i);
1716 }
1717 if (!LiteralIsTrue(active_j)) {
1718 greater->add_enforcement_literal(active_j);
1719 }
1720
1721 // This is redundant but should improves performance.
1722 //
1723 // If GetOrCreateReifiedPrecedenceLiteral(time_j, time_i, active_j, active_j)
1724 // (the reverse precedence) has been called too, then we can link the two
1725 // precedence literals, and the two active literals together.
1726 const auto& rev_it = reified_precedences_cache_.find(
1727 std::make_tuple(time_j, time_i, active_i, active_j));
1728 if (rev_it != reified_precedences_cache_.end()) {
1729 auto* const bool_or = working_model->add_constraints()->mutable_bool_or();
1730 bool_or->add_literals(result);
1731 bool_or->add_literals(rev_it->second);
1732 bool_or->add_literals(NegatedRef(active_i));
1733 bool_or->add_literals(NegatedRef(active_j));
1734 }
1735
1736 return result;
1737}
1738
1740 reified_precedences_cache_.clear();
1741}
1742
1744 SOLVER_LOG(logger_, "");
1745 SOLVER_LOG(logger_, "Presolve summary:");
1746 SOLVER_LOG(logger_, " - ", NumAffineRelations(),
1747 " affine relations were detected.");
1748 SOLVER_LOG(logger_, " - ", NumEquivRelations(),
1749 " variable equivalence relations were detected.");
1750 std::map<std::string, int> sorted_rules(stats_by_rule_name_.begin(),
1751 stats_by_rule_name_.end());
1752 for (const auto& entry : sorted_rules) {
1753 if (entry.second == 1) {
1754 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied 1 time.");
1755 } else {
1756 SOLVER_LOG(logger_, " - rule '", entry.first, "' was applied ",
1757 entry.second, " times.");
1758 }
1759 }
1760}
1761
1762} // namespace sat
1763} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:491
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:698
#define CHECK_NE(val1, val2)
Definition: base/logging.h:699
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
#define LOG(severity)
Definition: base/logging.h:416
#define DCHECK(condition)
Definition: base/logging.h:885
#define CHECK_LE(val1, val2)
Definition: base/logging.h:700
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
#define VLOG(verboselevel)
Definition: base/logging.h:979
bool TryAdd(int x, int y, int64_t coeff, int64_t offset)
We call domain any subset of Int64 = [kint64min, kint64max].
static Domain AllValues()
Returns the full domain Int64.
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
bool IsIncludedIn(const Domain &domain) const
Returns true iff D is included in the given domain.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
int64_t Min() const
Returns the min value of the domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t Max() const
Returns the max value of the domain.
Domain RelaxIfTooComplex() const
If NumIntervals() is too large, this return a superset of the domain.
Domain SimplifyUsingImpliedDomain(const Domain &implied_domain) const
Advanced usage.
static int64_t GCD64(int64_t x, int64_t y)
Definition: mathutil.h:107
void add_literals(::PROTOBUF_NAMESPACE_ID::int32 value)
Definition: cp_model.pb.h:6955
const ::operations_research::sat::IntervalConstraintProto & interval() const
::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::int32 > * mutable_enforcement_literal()
Definition: cp_model.pb.h:9409
const ::operations_research::sat::LinearConstraintProto & linear() const
::PROTOBUF_NAMESPACE_ID::int32 enforcement_literal(int index) const
Definition: cp_model.pb.h:9380
void add_enforcement_literal(::PROTOBUF_NAMESPACE_ID::int32 value)
Definition: cp_model.pb.h:9391
::operations_research::sat::LinearConstraintProto * mutable_linear()
::operations_research::sat::BoolArgumentProto * mutable_bool_or()
Definition: cp_model.pb.h:9482
const ::operations_research::sat::CpObjectiveProto & objective() const
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::ConstraintProto * add_constraints()
::operations_research::sat::IntegerVariableProto * add_variables()
::operations_research::sat::CpObjectiveProto * mutable_objective()
const ::operations_research::sat::ConstraintProto & constraints(int index) const
::PROTOBUF_NAMESPACE_ID::int64 coeffs(int index) const
void add_coeffs(::PROTOBUF_NAMESPACE_ID::int64 value)
::PROTOBUF_NAMESPACE_ID::int64 domain(int index) const
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
void add_vars(::PROTOBUF_NAMESPACE_ID::int32 value)
void add_domain(::PROTOBUF_NAMESPACE_ID::int64 value)
Definition: cp_model.pb.h:6904
void add_domain(::PROTOBUF_NAMESPACE_ID::int64 value)
Definition: cp_model.pb.h:7474
::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::int32 > * mutable_vars()
Definition: cp_model.pb.h:7398
::PROTOBUF_NAMESPACE_ID::int64 coeffs(int index) const
Definition: cp_model.pb.h:7416
void add_coeffs(::PROTOBUF_NAMESPACE_ID::int64 value)
Definition: cp_model.pb.h:7427
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
Definition: cp_model.pb.h:7369
::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::int64 > * mutable_coeffs()
Definition: cp_model.pb.h:7445
void add_vars(::PROTOBUF_NAMESPACE_ID::int32 value)
Definition: cp_model.pb.h:7380
::PROTOBUF_NAMESPACE_ID::int64 coeffs(int index) const
Definition: cp_model.pb.h:7113
::PROTOBUF_NAMESPACE_ID::int64 offset() const
Definition: cp_model.pb.h:7154
::PROTOBUF_NAMESPACE_ID::int32 vars(int index) const
Definition: cp_model.pb.h:7066
bool StoreAbsRelation(int target_ref, int ref)
void AddToObjective(int var, int64_t value)
ABSL_MUST_USE_RESULT bool IntersectDomainWith(int ref, const Domain &domain, bool *domain_modified=nullptr)
std::vector< absl::flat_hash_set< int > > var_to_lb_only_constraints
bool StoreLiteralImpliesVarNEqValue(int literal, int var, int64_t value)
bool VariableWithCostIsUniqueAndRemovable(int ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToTrue(int lit)
bool StoreAffineRelation(int ref_x, int ref_y, int64_t coeff, int64_t offset)
std::vector< absl::flat_hash_set< int > > var_to_ub_only_constraints
ABSL_MUST_USE_RESULT bool SubstituteVariableInObjective(int var_in_equality, int64_t coeff_in_equality, const ConstraintProto &equality, std::vector< int > *new_vars_in_objective=nullptr)
int GetOrCreateVarValueEncoding(int ref, int64_t value)
ABSL_MUST_USE_RESULT bool NotifyThatModelIsUnsat(const std::string &message="")
std::string AffineRelationDebugString(int ref) const
bool InsertVarValueEncoding(int literal, int ref, int64_t value)
void StoreBooleanEqualityRelation(int ref_a, int ref_b)
bool HasVarValueEncoding(int ref, int64_t value, int *literal=nullptr)
bool DomainContains(int ref, int64_t value) const
void UpdateRuleStats(const std::string &name, int num_times=1)
ABSL_MUST_USE_RESULT bool CanonicalizeObjective()
AffineRelation::Relation GetAffineRelation(int ref) const
std::string IntervalDebugString(int ct_ref) const
ABSL_MUST_USE_RESULT bool SetLiteralToFalse(int lit)
int LiteralForExpressionMax(const LinearExpressionProto &expr) const
int GetOrCreateReifiedPrecedenceLiteral(int time_i, int time_j, int active_i, int active_j)
bool ExpressionIsAffineBoolean(const LinearExpressionProto &expr) const
bool ExploitExactlyOneInObjective(absl::Span< const int > exactly_one)
Domain DomainSuperSetOf(const LinearExpressionProto &expr) const
void AddImplyInDomain(int b, int x, const Domain &domain)
bool VariableIsOnlyUsedInEncodingAndMaybeInObjective(int ref) const
bool GetAbsRelation(int target_ref, int *ref)
bool StoreLiteralImpliesVarEqValue(int literal, int var, int64_t value)
int Get(PresolveContext *context) const
int Get(PresolveContext *context) const
int64_t b
int64_t a
CpModelProto proto
const std::string name
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GurobiMPCallbackContext * context
const int INFO
Definition: log_severity.h:31
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:200
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
std::vector< int > UsedVariables(const ConstraintProto &ct)
bool RefIsPositive(int ref)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
constexpr int kAffineRelationConstraint
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
constexpr int kObjectiveConstraint
Collection of objects used to extend the Constraint Solver library.
int64_t CapAdd(int64_t x, int64_t y)
const absl::string_view ToString(MPSolver::OptimizationProblemType optimization_problem_type)
int64_t CapProd(int64_t x, int64_t y)
std::string ProtobufDebugString(const P &message)
Literal literal
Definition: optimization.cc:85
int index
Definition: pack.cc:509
ColIndex representative
IntervalVar * interval
Definition: resource.cc:100
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:63
#define VLOG_IS_ON(verboselevel)
Definition: vlog_is_on.h:41