Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
pb_constraint.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <memory>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/container/flat_hash_map.h"
24#include "absl/hash/hash.h"
25#include "absl/log/check.h"
26#include "absl/strings/str_format.h"
27#include "absl/types/span.h"
31#include "ortools/sat/sat_parameters.pb.h"
32#include "ortools/util/bitset.h"
34#include "ortools/util/stats.h"
36
37namespace operations_research {
38namespace sat {
39
40namespace {
41
42bool LiteralComparator(const LiteralWithCoeff& a, const LiteralWithCoeff& b) {
43 return a.literal.Index() < b.literal.Index();
44}
45
46bool CoeffComparator(const LiteralWithCoeff& a, const LiteralWithCoeff& b) {
47 if (a.coefficient == b.coefficient) {
48 return a.literal.Index() < b.literal.Index();
49 }
50 return a.coefficient < b.coefficient;
51}
52
53} // namespace
54
56 std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
57 Coefficient* max_value) {
58 // Note(user): For some reason, the IntType checking doesn't work here ?! that
59 // is a bit worrying, but the code seems to behave correctly.
60 *bound_shift = 0;
61 *max_value = 0;
62
63 // First, sort by literal to remove duplicate literals.
64 // This also remove term with a zero coefficient.
65 std::sort(cst->begin(), cst->end(), LiteralComparator);
66 int index = 0;
68 for (int i = 0; i < cst->size(); ++i) {
69 const LiteralWithCoeff current = (*cst)[i];
70 if (current.coefficient == 0) continue;
71 if (representative != nullptr &&
72 current.literal.Variable() == representative->literal.Variable()) {
73 if (current.literal == representative->literal) {
74 if (!SafeAddInto(current.coefficient, &(representative->coefficient))) {
75 return false;
76 }
77 } else {
78 // Here current_literal is equal to (1 - representative).
79 if (!SafeAddInto(-current.coefficient,
80 &(representative->coefficient))) {
81 return false;
82 }
83 if (!SafeAddInto(-current.coefficient, bound_shift)) return false;
84 }
85 } else {
86 if (representative != nullptr && representative->coefficient == 0) {
87 --index;
88 }
89 (*cst)[index] = current;
90 representative = &((*cst)[index]);
91 ++index;
92 }
93 }
94 if (representative != nullptr && representative->coefficient == 0) {
95 --index;
96 }
97 cst->resize(index);
98
99 // Then, make all coefficients positive by replacing a term "-c x" into
100 // "c(1-x) - c" which is the same as "c(not x) - c".
101 for (int i = 0; i < cst->size(); ++i) {
102 const LiteralWithCoeff current = (*cst)[i];
103 if (current.coefficient < 0) {
104 if (!SafeAddInto(-current.coefficient, bound_shift)) return false;
105 (*cst)[i].coefficient = -current.coefficient;
106 (*cst)[i].literal = current.literal.Negated();
107 }
108 if (!SafeAddInto((*cst)[i].coefficient, max_value)) return false;
109 }
110
111 // Finally sort by increasing coefficients.
112 std::sort(cst->begin(), cst->end(), CoeffComparator);
113 DCHECK_GE(*max_value, 0);
114 return true;
115}
116
119 std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
120 Coefficient* max_value) {
121 int index = 0;
122 Coefficient shift_due_to_fixed_variables(0);
123 for (const LiteralWithCoeff& entry : *cst) {
124 if (mapping[entry.literal] >= 0) {
125 (*cst)[index] =
126 LiteralWithCoeff(Literal(mapping[entry.literal]), entry.coefficient);
127 ++index;
128 } else if (mapping[entry.literal] == kTrueLiteralIndex) {
129 if (!SafeAddInto(-entry.coefficient, &shift_due_to_fixed_variables)) {
130 return false;
131 }
132 } else {
133 // Nothing to do if the literal is false.
134 DCHECK_EQ(mapping[entry.literal], kFalseLiteralIndex);
135 }
136 }
137 cst->resize(index);
138 if (cst->empty()) {
139 *bound_shift = shift_due_to_fixed_variables;
140 *max_value = 0;
141 return true;
142 }
143 const bool result =
144 ComputeBooleanLinearExpressionCanonicalForm(cst, bound_shift, max_value);
145 if (!SafeAddInto(shift_due_to_fixed_variables, bound_shift)) return false;
146 return result;
147}
148
149// TODO(user): Also check for no duplicates literals + unit tests.
151 absl::Span<const LiteralWithCoeff> cst) {
152 Coefficient previous(1);
153 for (LiteralWithCoeff term : cst) {
154 if (term.coefficient < previous) return false;
155 previous = term.coefficient;
156 }
157 return true;
158}
159
160// TODO(user): Use more complex simplification like dividing by the gcd of
161// everyone and using less different coefficients if possible.
163 std::vector<LiteralWithCoeff>* cst, Coefficient* rhs) {
164 // Replace all coefficient >= rhs by rhs + 1 (these literal must actually be
165 // false). Note that the linear sum of literals remains canonical.
166 //
167 // TODO(user): It is probably better to remove these literals and have other
168 // constraint setting them to false from the symmetry finder perspective.
169 for (LiteralWithCoeff& x : *cst) {
170 if (x.coefficient > *rhs) x.coefficient = *rhs + 1;
171 }
172}
173
174Coefficient ComputeCanonicalRhs(Coefficient upper_bound,
175 Coefficient bound_shift,
176 Coefficient max_value) {
177 Coefficient rhs = upper_bound;
178 if (!SafeAddInto(bound_shift, &rhs)) {
179 if (bound_shift > 0) {
180 // Positive overflow. The constraint is trivially true.
181 // This is because the canonical linear expression is in [0, max_value].
182 return max_value;
183 } else {
184 // Negative overflow. The constraint is infeasible.
185 return Coefficient(-1);
186 }
187 }
188 if (rhs < 0) return Coefficient(-1);
189 return std::min(max_value, rhs);
190}
191
193 Coefficient bound_shift,
194 Coefficient max_value) {
195 // The new bound is "max_value - (lower_bound + bound_shift)", but we must
196 // pay attention to possible overflows.
197 Coefficient shifted_lb = lower_bound;
198 if (!SafeAddInto(bound_shift, &shifted_lb)) {
199 if (bound_shift > 0) {
200 // Positive overflow. The constraint is infeasible.
201 return Coefficient(-1);
202 } else {
203 // Negative overflow. The constraint is trivialy satisfiable.
204 return max_value;
205 }
206 }
207 if (shifted_lb <= 0) {
208 // If shifted_lb <= 0 then the constraint is trivialy satisfiable. We test
209 // this so we are sure that max_value - shifted_lb doesn't overflow below.
210 return max_value;
211 }
212 return max_value - shifted_lb;
213}
214
216 bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound,
217 Coefficient upper_bound, std::vector<LiteralWithCoeff>* cst) {
218 // Canonicalize the linear expression of the constraint.
219 Coefficient bound_shift;
220 Coefficient max_value;
221 if (!ComputeBooleanLinearExpressionCanonicalForm(cst, &bound_shift,
222 &max_value)) {
223 return false;
224 }
225 if (use_upper_bound) {
226 const Coefficient rhs =
227 ComputeCanonicalRhs(upper_bound, bound_shift, max_value);
228 if (!AddConstraint(*cst, max_value, rhs)) return false;
229 }
230 if (use_lower_bound) {
231 // We transform the constraint into an upper-bounded one.
232 for (int i = 0; i < cst->size(); ++i) {
233 (*cst)[i].literal = (*cst)[i].literal.Negated();
234 }
235 const Coefficient rhs =
236 ComputeNegatedCanonicalRhs(lower_bound, bound_shift, max_value);
237 if (!AddConstraint(*cst, max_value, rhs)) return false;
238 }
239 return true;
240}
241
242bool CanonicalBooleanLinearProblem::AddConstraint(
243 absl::Span<const LiteralWithCoeff> cst, Coefficient max_value,
244 Coefficient rhs) {
245 if (rhs < 0) return false; // Trivially unsatisfiable.
246 if (rhs >= max_value) return true; // Trivially satisfiable.
247 constraints_.emplace_back(cst.begin(), cst.end());
248 rhs_.push_back(rhs);
249 SimplifyCanonicalBooleanLinearConstraint(&constraints_.back(), &rhs_.back());
250 return true;
251}
252
254 if (terms_.size() != num_variables) {
255 terms_.assign(num_variables, Coefficient(0));
256 non_zeros_.ClearAndResize(BooleanVariable(num_variables));
257 rhs_ = 0;
258 max_sum_ = 0;
259 } else {
260 ClearAll();
261 }
262}
263
265 // TODO(user): We could be more efficient and have only one loop here.
266 for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
267 terms_[var] = Coefficient(0);
268 }
269 non_zeros_.ClearAll();
270 rhs_ = 0;
271 max_sum_ = 0;
272}
273
274// TODO(user): Also reduce the trivially false literal when coeff > rhs_ ?
276 CHECK_LT(rhs_, max_sum_) << "Trivially sat.";
277 Coefficient removed_sum(0);
278 const Coefficient bound = max_sum_ - rhs_;
279 for (BooleanVariable var : PossibleNonZeros()) {
280 const Coefficient diff = GetCoefficient(var) - bound;
281 if (diff > 0) {
282 removed_sum += diff;
283 terms_[var] = (terms_[var] > 0) ? bound : -bound;
284 }
285 }
286 rhs_ -= removed_sum;
287 max_sum_ -= removed_sum;
288 DCHECK_EQ(max_sum_, ComputeMaxSum());
289}
290
292 std::string result;
293 for (BooleanVariable var : PossibleNonZeros()) {
294 if (!result.empty()) result += " + ";
295 result += absl::StrFormat("%d[%s]", GetCoefficient(var).value(),
297 }
298 result += absl::StrFormat(" <= %d", rhs_.value());
299 return result;
300}
301
302// TODO(user): Keep this for DCHECK(), but maintain the slack incrementally
303// instead of recomputing it.
305 const Trail& trail, int trail_index) const {
306 Coefficient activity(0);
307 for (BooleanVariable var : PossibleNonZeros()) {
308 if (GetCoefficient(var) == 0) continue;
309 if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
310 trail.Info(var).trail_index < trail_index) {
311 activity += GetCoefficient(var);
312 }
313 }
314 return rhs_ - activity;
315}
316
319 int trail_index) {
320 Coefficient activity(0);
321 Coefficient removed_sum(0);
322 const Coefficient bound = max_sum_ - rhs_;
323 for (BooleanVariable var : PossibleNonZeros()) {
324 if (GetCoefficient(var) == 0) continue;
325 const Coefficient diff = GetCoefficient(var) - bound;
326 if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
327 trail.Info(var).trail_index < trail_index) {
328 if (diff > 0) {
329 removed_sum += diff;
330 terms_[var] = (terms_[var] > 0) ? bound : -bound;
331 }
332 activity += GetCoefficient(var);
333 } else {
334 // Because we assume the slack (rhs - activity) to be negative, we have
335 // coeff + rhs - max_sum_ <= coeff + rhs - (activity + coeff)
336 // <= slack
337 // < 0
338 CHECK_LE(diff, 0);
339 }
340 }
341 rhs_ -= removed_sum;
342 max_sum_ -= removed_sum;
343 DCHECK_EQ(max_sum_, ComputeMaxSum());
344 return rhs_ - activity;
345}
346
348 const Trail& trail, int trail_index, Coefficient initial_slack,
349 Coefficient target) {
350 // Positive slack.
351 const Coefficient slack = initial_slack;
352 DCHECK_EQ(slack, ComputeSlackForTrailPrefix(trail, trail_index));
353 CHECK_LE(target, slack);
354 CHECK_GE(target, 0);
355
356 // This is not stricly needed, but true in our use case:
357 // The variable assigned at trail_index was causing a conflict.
358 const Coefficient coeff = GetCoefficient(trail[trail_index].Variable());
359 CHECK_LT(slack, coeff);
360
361 // Nothing to do if the slack is already target.
362 if (slack == target) return;
363
364 // Applies the algorithm described in the .h
365 const Coefficient diff = slack - target;
366 rhs_ -= diff;
367 for (BooleanVariable var : PossibleNonZeros()) {
368 if (GetCoefficient(var) == 0) continue;
369 if (trail.Assignment().LiteralIsTrue(GetLiteral(var)) &&
370 trail.Info(var).trail_index < trail_index) {
371 continue;
372 }
373 if (GetCoefficient(var) > diff) {
374 terms_[var] = (terms_[var] > 0) ? terms_[var] - diff : terms_[var] + diff;
375 max_sum_ -= diff;
376 } else {
377 max_sum_ -= GetCoefficient(var);
378 terms_[var] = 0;
379 }
380 }
381 DCHECK_EQ(max_sum_, ComputeMaxSum());
382}
383
385 std::vector<LiteralWithCoeff>* output) {
386 output->clear();
387 for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
388 const Coefficient coeff = GetCoefficient(var);
389 if (coeff != 0) {
390 output->push_back(LiteralWithCoeff(GetLiteral(var), GetCoefficient(var)));
391 }
392 }
393 std::sort(output->begin(), output->end(), CoeffComparator);
394}
395
396Coefficient MutableUpperBoundedLinearConstraint::ComputeMaxSum() const {
397 Coefficient result(0);
398 for (BooleanVariable var : non_zeros_.PositionsSetAtLeastOnce()) {
399 result += GetCoefficient(var);
400 }
401 return result;
402}
403
405 const std::vector<LiteralWithCoeff>& cst)
406 : is_marked_for_deletion_(false),
407 is_learned_(false),
408 first_reason_trail_index_(-1),
409 activity_(0.0) {
410 DCHECK(!cst.empty());
411 DCHECK(std::is_sorted(cst.begin(), cst.end(), CoeffComparator));
412 literals_.reserve(cst.size());
413
414 // Reserve the space for coeffs_ and starts_ (it is slightly more efficient).
415 {
416 int size = 0;
417 Coefficient prev(0); // Ignore initial zeros.
418 for (LiteralWithCoeff term : cst) {
419 if (term.coefficient != prev) {
420 prev = term.coefficient;
421 ++size;
422 }
423 }
424 coeffs_.reserve(size);
425 starts_.reserve(size + 1);
426 }
427
428 Coefficient prev(0);
429 for (LiteralWithCoeff term : cst) {
430 if (term.coefficient != prev) {
431 prev = term.coefficient;
432 coeffs_.push_back(term.coefficient);
433 starts_.push_back(literals_.size());
434 }
435 literals_.push_back(term.literal);
436 }
437
438 // Sentinel.
439 starts_.push_back(literals_.size());
440
441 hash_ = absl::Hash<std::vector<LiteralWithCoeff>>()(cst);
442}
443
446 int literal_index = 0;
447 int coeff_index = 0;
448 for (Literal literal : literals_) {
449 conflict->AddTerm(literal, coeffs_[coeff_index]);
450 ++literal_index;
451 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
452 }
453 conflict->AddToRhs(rhs_);
454}
455
457 absl::Span<const LiteralWithCoeff> cst) {
458 if (cst.size() != literals_.size()) return false;
459 int literal_index = 0;
460 int coeff_index = 0;
461 for (LiteralWithCoeff term : cst) {
462 if (literals_[literal_index] != term.literal) return false;
463 if (coeffs_[coeff_index] != term.coefficient) return false;
464 ++literal_index;
465 if (literal_index == starts_[coeff_index + 1]) {
466 ++coeff_index;
467 }
468 }
469 return true;
470}
471
473 Coefficient rhs, int trail_index, Coefficient* threshold, Trail* trail,
475 // Compute the slack from the assigned variables with a trail index
476 // smaller than the given trail_index. The variable at trail_index has not
477 // yet been propagated.
478 rhs_ = rhs;
479 Coefficient slack = rhs;
480
481 // sum_at_previous_level[i] is the sum of assigned literals with a level <
482 // i. Since we want the sums up to sum_at_previous_level[last_level + 1],
483 // the size of the vector must be last_level + 2.
484 const int last_level = trail->CurrentDecisionLevel();
485 std::vector<Coefficient> sum_at_previous_level(last_level + 2,
486 Coefficient(0));
487
488 int max_relevant_trail_index = 0;
489 if (trail_index > 0) {
490 int literal_index = 0;
491 int coeff_index = 0;
492 for (Literal literal : literals_) {
493 const BooleanVariable var = literal.Variable();
494 const Coefficient coeff = coeffs_[coeff_index];
495 if (trail->Assignment().LiteralIsTrue(literal) &&
496 trail->Info(var).trail_index < trail_index) {
497 max_relevant_trail_index =
498 std::max(max_relevant_trail_index, trail->Info(var).trail_index);
499 slack -= coeff;
500 sum_at_previous_level[trail->Info(var).level + 1] += coeff;
501 }
502 ++literal_index;
503 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
504 }
505
506 // The constraint is infeasible provided the current propagated trail.
507 if (slack < 0) return false;
508
509 // Cumulative sum.
510 for (int i = 1; i < sum_at_previous_level.size(); ++i) {
511 sum_at_previous_level[i] += sum_at_previous_level[i - 1];
512 }
513 }
514
515 // Check the no-propagation at earlier level precondition.
516 int literal_index = 0;
517 int coeff_index = 0;
518 for (Literal literal : literals_) {
519 const BooleanVariable var = literal.Variable();
520 const int level = trail->Assignment().VariableIsAssigned(var)
521 ? trail->Info(var).level
522 : last_level;
523 if (level > 0) {
524 CHECK_LE(coeffs_[coeff_index], rhs_ - sum_at_previous_level[level])
525 << "var should have been propagated at an earlier level !";
526 }
527 ++literal_index;
528 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
529 }
530
531 // Initial propagation.
532 //
533 // TODO(user): The source trail index for the propagation reason (i.e.
534 // max_relevant_trail_index) may be higher than necessary (for some of the
535 // propagated literals). Currently this works with FillReason(), but it was a
536 // source of a really nasty bug (see CL 68906167) because of the (rhs == 1)
537 // optim. Find a good way to test the logic.
538 index_ = coeffs_.size() - 1;
539 already_propagated_end_ = literals_.size();
540 Update(slack, threshold);
541 return *threshold < 0
542 ? Propagate(max_relevant_trail_index, threshold, trail, helper)
543 : true;
544}
545
547 int trail_index, Coefficient* threshold, Trail* trail,
549 DCHECK_LT(*threshold, 0);
550 const Coefficient slack = GetSlackFromThreshold(*threshold);
551 DCHECK_GE(slack, 0) << "The constraint is already a conflict!";
552 while (index_ >= 0 && coeffs_[index_] > slack) --index_;
553
554 // Check propagation.
555 BooleanVariable first_propagated_variable(-1);
556 for (int i = starts_[index_ + 1]; i < already_propagated_end_; ++i) {
557 if (trail->Assignment().LiteralIsFalse(literals_[i])) continue;
558 if (trail->Assignment().LiteralIsTrue(literals_[i])) {
559 if (trail->Info(literals_[i].Variable()).trail_index > trail_index) {
560 // Conflict.
561 FillReason(*trail, trail_index, literals_[i].Variable(),
562 &helper->conflict);
563 helper->conflict.push_back(literals_[i].Negated());
564 Update(slack, threshold);
565 return false;
566 }
567 } else {
568 // Propagation.
569 if (first_propagated_variable < 0) {
570 if (first_reason_trail_index_ == -1) {
571 first_reason_trail_index_ = trail->Index();
572 }
573 helper->Enqueue(literals_[i].Negated(), trail_index, this, trail);
574 first_propagated_variable = literals_[i].Variable();
575 } else {
576 // Note that the reason for first_propagated_variable is always a
577 // valid reason for literals_[i].Variable() because we process the
578 // variable in increasing coefficient order.
579 trail->EnqueueWithSameReasonAs(literals_[i].Negated(),
580 first_propagated_variable);
581 }
582 }
583 }
584 Update(slack, threshold);
585 DCHECK_GE(*threshold, 0);
586 return true;
587}
588
590 const Trail& trail, int source_trail_index,
591 BooleanVariable propagated_variable, std::vector<Literal>* reason) {
592 reason->clear();
593
594 // Optimization for an "at most one" constraint.
595 // Note that the source_trail_index set by InitializeRhs() is ok in this case.
596 if (rhs_ == 1) {
597 reason->push_back(trail[source_trail_index].Negated());
598 return;
599 }
600
601 // Optimization: This will be set to the index of the last literal in the
602 // reason.
603 int last_i = 0;
604 int last_coeff_index = 0;
605
606 // Compute the initial reason which is formed by all the literals of the
607 // constraint that were assigned to true at the time of the propagation.
608 // We remove literals with a level of 0 since they are not needed.
609 // We also compute the slack at the time.
610 Coefficient slack = rhs_;
611 Coefficient propagated_variable_coefficient(0);
612 int coeff_index = coeffs_.size() - 1;
613 for (int i = literals_.size() - 1; i >= 0; --i) {
614 const Literal literal = literals_[i];
615 if (literal.Variable() == propagated_variable) {
616 propagated_variable_coefficient = coeffs_[coeff_index];
617 } else {
618 if (trail.Assignment().LiteralIsTrue(literal) &&
619 trail.Info(literal.Variable()).trail_index <= source_trail_index) {
620 if (trail.Info(literal.Variable()).level > 0) {
621 reason->push_back(literal.Negated());
622 last_i = i;
623 last_coeff_index = coeff_index;
624 }
625 slack -= coeffs_[coeff_index];
626 }
627 }
628 if (i == starts_[coeff_index]) {
629 --coeff_index;
630 }
631 }
632 DCHECK_GT(propagated_variable_coefficient, slack);
633 DCHECK_GE(propagated_variable_coefficient, 0);
634
635 // In both cases, we can't minimize the reason further.
636 if (reason->size() <= 1 || coeffs_.size() == 1) return;
637
638 Coefficient limit = propagated_variable_coefficient - slack;
639 DCHECK_GE(limit, 1);
640
641 // Remove literals with small coefficients from the reason as long as the
642 // limit is still stricly positive.
643 coeff_index = last_coeff_index;
644 if (coeffs_[coeff_index] >= limit) return;
645 for (int i = last_i; i < literals_.size(); ++i) {
646 const Literal literal = literals_[i];
647 if (i == starts_[coeff_index + 1]) {
648 ++coeff_index;
649 if (coeffs_[coeff_index] >= limit) break;
650 }
651 if (literal.Negated() != reason->back()) continue;
652 limit -= coeffs_[coeff_index];
653 reason->pop_back();
654 if (coeffs_[coeff_index] >= limit) break;
655 }
656 DCHECK(!reason->empty());
657 DCHECK_GE(limit, 1);
658}
659
661 const Trail& trail, int trail_index,
662 const MutableUpperBoundedLinearConstraint& conflict) {
663 Coefficient result(0);
664 int literal_index = 0;
665 int coeff_index = 0;
666 for (Literal literal : literals_) {
667 if (!trail.Assignment().VariableIsAssigned(literal.Variable()) ||
668 trail.Info(literal.Variable()).trail_index >= trail_index) {
669 result += conflict.CancelationAmount(literal, coeffs_[coeff_index]);
670 }
671 ++literal_index;
672 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
673 }
674 return result;
675}
676
678 const Trail& trail, BooleanVariable var,
680 Coefficient* conflict_slack) {
681 const int limit_trail_index = trail.Info(var).trail_index;
682
683 // Compute the constraint activity at the time and the coefficient of the
684 // variable var.
685 Coefficient activity(0);
686 Coefficient var_coeff(0);
687 int literal_index = 0;
688 int coeff_index = 0;
689 for (Literal literal : literals_) {
690 if (literal.Variable() == var) {
691 // The variable must be of the opposite sign in the current conflict.
692 CHECK_NE(literal, conflict->GetLiteral(var));
693 var_coeff = coeffs_[coeff_index];
694 } else if (trail.Assignment().LiteralIsTrue(literal) &&
695 trail.Info(literal.Variable()).trail_index < limit_trail_index) {
696 activity += coeffs_[coeff_index];
697 }
698 ++literal_index;
699 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
700 }
701
702 // Special case.
703 if (activity > rhs_) {
704 // This constraint is already a conflict.
705 // Use this one instead to start the resolution.
706 //
707 // TODO(user): Investigate if this is a good idea. It doesn't happen often,
708 // but does happened. Maybe we can detect this before in Propagate()? The
709 // setup is:
710 // - At a given trail_index, var is propagated and added on the trail.
711 // - There is some constraint literals assigned to true with a trail index
712 // in (trail_index, var.trail_index).
713 // - Their sum is high enough to cause a conflict.
714 // - But individually, their coefficients are too small to be propagated, so
715 // the conflict is not yet detected. It will be when these variables are
716 // processed by PropagateNext().
717 conflict->ClearAll();
718 AddToConflict(conflict);
719 *conflict_slack = rhs_ - activity;
720 DCHECK_EQ(*conflict_slack,
721 conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
722 return;
723 }
724
725 // This is the slack of *this for the trail prefix < limit_trail_index.
726 const Coefficient slack = rhs_ - activity;
727 CHECK_GE(slack, 0);
728
729 // This is the slack of the conflict at the same level.
730 DCHECK_EQ(*conflict_slack,
731 conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
732
733 // TODO(user): If there is more "cancelation" than the min_coeffs below when
734 // we add the two constraints, the resulting slack may be even lower. Taking
735 // that into account is probably good.
736 const Coefficient cancelation =
737 DEBUG_MODE ? ComputeCancelation(trail, limit_trail_index, *conflict)
738 : Coefficient(0);
739
740 // When we add the two constraints together, the slack of the result for the
741 // trail < limit_trail_index - 1 must be negative. We know that its value is
742 // <= slack1 + slack2 - min(coeffs), so we have nothing to do if this bound is
743 // already negative.
744 const Coefficient conflict_var_coeff = conflict->GetCoefficient(var);
745 const Coefficient min_coeffs = std::min(var_coeff, conflict_var_coeff);
746 const Coefficient new_slack_ub = slack + *conflict_slack - min_coeffs;
747 CHECK_LT(*conflict_slack, conflict_var_coeff);
748 CHECK_LT(slack, var_coeff);
749 if (new_slack_ub < 0) {
750 AddToConflict(conflict);
751 DCHECK_EQ(*conflict_slack + slack - cancelation,
752 conflict->ComputeSlackForTrailPrefix(trail, limit_trail_index));
753 return;
754 }
755
756 // We need to relax one or both of the constraints so the new slack is < 0.
757 // Using the relaxation described in ReduceSlackTo(), we can have this new
758 // slack bound:
759 //
760 // (slack - diff) + (conflict_slack - conflict_diff)
761 // - min(var_coeff - diff, conflict_var_coeff - conflict_diff).
762 //
763 // For all diff in [0, slack)
764 // For all conflict_diff in [0, conflict_slack)
765 Coefficient diff(0);
766 Coefficient conflict_diff(0);
767
768 // Is relaxing the constraint with the highest coeff enough?
769 if (new_slack_ub < std::max(var_coeff, conflict_var_coeff) - min_coeffs) {
770 const Coefficient reduc = new_slack_ub + 1;
771 if (var_coeff < conflict_var_coeff) {
772 conflict_diff += reduc;
773 } else {
774 diff += reduc;
775 }
776 } else {
777 // Just reduce the slack of both constraints to zero.
778 //
779 // TODO(user): The best will be to relax as little as possible.
780 diff = slack;
781 conflict_diff = *conflict_slack;
782 }
783
784 // Relax the conflict.
785 CHECK_GE(conflict_diff, 0);
786 CHECK_LE(conflict_diff, *conflict_slack);
787 if (conflict_diff > 0) {
788 conflict->ReduceSlackTo(trail, limit_trail_index, *conflict_slack,
789 *conflict_slack - conflict_diff);
790 *conflict_slack -= conflict_diff;
791 }
792
793 // We apply the same algorithm as the one in ReduceSlackTo() but on
794 // the non-mutable representation and add it on the fly into conflict.
795 CHECK_GE(diff, 0);
796 CHECK_LE(diff, slack);
797 if (diff == 0) {
798 // Special case if there if no relaxation is needed.
799 AddToConflict(conflict);
800 return;
801 }
802
803 literal_index = 0;
804 coeff_index = 0;
805 for (Literal literal : literals_) {
806 if (trail.Assignment().LiteralIsTrue(literal) &&
807 trail.Info(literal.Variable()).trail_index < limit_trail_index) {
808 conflict->AddTerm(literal, coeffs_[coeff_index]);
809 } else {
810 const Coefficient new_coeff = coeffs_[coeff_index] - diff;
811 if (new_coeff > 0) {
812 // TODO(user): track the cancelation here so we can update
813 // *conflict_slack properly.
814 conflict->AddTerm(literal, new_coeff);
815 }
816 }
817 ++literal_index;
818 if (literal_index == starts_[coeff_index + 1]) ++coeff_index;
819 }
820
821 // And the rhs.
822 conflict->AddToRhs(rhs_ - diff);
823}
824
825void UpperBoundedLinearConstraint::Untrail(Coefficient* threshold,
826 int trail_index) {
827 const Coefficient slack = GetSlackFromThreshold(*threshold);
828 while (index_ + 1 < coeffs_.size() && coeffs_[index_ + 1] <= slack) ++index_;
829 Update(slack, threshold);
830 if (first_reason_trail_index_ >= trail_index) {
831 first_reason_trail_index_ = -1;
832 }
833}
834
835// TODO(user): This is relatively slow. Take the "transpose" all at once, and
836// maybe put small constraints first on the to_update_ lists.
837bool PbConstraints::AddConstraint(const std::vector<LiteralWithCoeff>& cst,
838 Coefficient rhs, Trail* trail) {
839 SCOPED_TIME_STAT(&stats_);
840 DCHECK(!cst.empty());
841 DCHECK(std::is_sorted(cst.begin(), cst.end(), CoeffComparator));
842
843 // Special case if this is the first constraint.
844 if (constraints_.empty()) {
845 to_update_.resize(trail->NumVariables() << 1);
846 enqueue_helper_.propagator_id = propagator_id_;
847 enqueue_helper_.reasons.resize(trail->NumVariables());
849 }
850
851 std::unique_ptr<UpperBoundedLinearConstraint> c(
853 std::vector<UpperBoundedLinearConstraint*>& duplicate_candidates =
854 possible_duplicates_[c->hash()];
855
856 // Optimization if the constraint terms are duplicates.
857 for (UpperBoundedLinearConstraint* candidate : duplicate_candidates) {
858 if (candidate->HasIdenticalTerms(cst)) {
859 if (rhs < candidate->Rhs()) {
860 // TODO(user): the index is needed to give the correct thresholds_ entry
861 // to InitializeRhs() below, but this linear scan is not super
862 // efficient.
863 ConstraintIndex i(0);
864 while (i < constraints_.size() &&
865 constraints_[i.value()].get() != candidate) {
866 ++i;
867 }
868 CHECK_LT(i, constraints_.size());
869 return candidate->InitializeRhs(rhs, propagation_trail_index_,
870 &thresholds_[i], trail,
871 &enqueue_helper_);
872 } else {
873 // The constraint is redundant, so there is nothing to do.
874 return true;
875 }
876 }
877 }
878
879 thresholds_.push_back(Coefficient(0));
880 if (!c->InitializeRhs(rhs, propagation_trail_index_, &thresholds_.back(),
881 trail, &enqueue_helper_)) {
882 thresholds_.pop_back();
883 return false;
884 }
885
886 const ConstraintIndex cst_index(constraints_.size());
887 duplicate_candidates.push_back(c.get());
888 constraints_.emplace_back(c.release());
889 for (LiteralWithCoeff term : cst) {
890 DCHECK_LT(term.literal.Index(), to_update_.size());
891 to_update_[term.literal].push_back(ConstraintIndexWithCoeff(
892 trail->Assignment().VariableIsAssigned(term.literal.Variable()),
893 cst_index, term.coefficient));
894 }
895 return true;
896}
897
899 const std::vector<LiteralWithCoeff>& cst, Coefficient rhs, Trail* trail) {
900 DeleteSomeLearnedConstraintIfNeeded();
901 const int old_num_constraints = constraints_.size();
902 const bool result = AddConstraint(cst, rhs, trail);
903
904 // The second test is to avoid marking a problem constraint as learned because
905 // of the "reuse last constraint" optimization.
906 if (result && constraints_.size() > old_num_constraints) {
907 constraints_.back()->set_is_learned(true);
908 }
909 return result;
910}
911
912bool PbConstraints::PropagateNext(Trail* trail) {
913 SCOPED_TIME_STAT(&stats_);
914 const int source_trail_index = propagation_trail_index_;
915 const Literal true_literal = (*trail)[propagation_trail_index_];
917
918 // We need to update ALL threshold, otherwise the Untrail() will not be
919 // synchronized.
920 bool conflict = false;
921 num_threshold_updates_ += to_update_[true_literal].size();
922 for (ConstraintIndexWithCoeff& update : to_update_[true_literal]) {
923 const Coefficient threshold =
924 thresholds_[update.index] - update.coefficient;
925 thresholds_[update.index] = threshold;
926 if (threshold < 0 && !conflict) {
928 constraints_[update.index.value()].get();
929 update.need_untrail_inspection = true;
930 ++num_constraint_lookups_;
931 const int old_value = cst->already_propagated_end();
932 if (!cst->Propagate(source_trail_index, &thresholds_[update.index], trail,
933 &enqueue_helper_)) {
934 trail->MutableConflict()->swap(enqueue_helper_.conflict);
935 conflicting_constraint_index_ = update.index;
936 conflict = true;
937
938 // We bump the activity of the conflict.
939 BumpActivity(constraints_[update.index.value()].get());
940 }
941 num_inspected_constraint_literals_ +=
942 old_value - cst->already_propagated_end();
943 }
944 }
945 return !conflict;
946}
947
949 const int old_index = trail->Index();
950 while (trail->Index() == old_index && propagation_trail_index_ < old_index) {
951 if (!PropagateNext(trail)) return false;
952 }
953 return true;
954}
955
956void PbConstraints::Untrail(const Trail& trail, int trail_index) {
957 SCOPED_TIME_STAT(&stats_);
958 to_untrail_.ClearAndResize(ConstraintIndex(constraints_.size()));
959 while (propagation_trail_index_ > trail_index) {
962 for (ConstraintIndexWithCoeff& update : to_update_[literal]) {
963 thresholds_[update.index] += update.coefficient;
964
965 // Only the constraints which were inspected during Propagate() need
966 // inspection during Untrail().
967 if (update.need_untrail_inspection) {
968 update.need_untrail_inspection = false;
969 to_untrail_.Set(update.index);
970 }
971 }
972 }
973 for (ConstraintIndex cst_index : to_untrail_.PositionsSetAtLeastOnce()) {
974 constraints_[cst_index.value()]->Untrail(&(thresholds_[cst_index]),
975 trail_index);
976 }
977}
978
979absl::Span<const Literal> PbConstraints::Reason(const Trail& trail,
980 int trail_index,
981 int64_t /*conflict_id*/) const {
982 SCOPED_TIME_STAT(&stats_);
983 const PbConstraintsEnqueueHelper::ReasonInfo& reason_info =
984 enqueue_helper_.reasons[trail_index];
985 std::vector<Literal>* reason = trail.GetEmptyVectorToStoreReason(trail_index);
986 reason_info.pb_constraint->FillReason(trail, reason_info.source_trail_index,
987 trail[trail_index].Variable(), reason);
988 return *reason;
989}
990
992 int trail_index) const {
993 const PbConstraintsEnqueueHelper::ReasonInfo& reason_info =
994 enqueue_helper_.reasons[trail_index];
995 return reason_info.pb_constraint;
996}
997
998// TODO(user): Because num_constraints also include problem constraints, the
999// policy may not be what we want if there is a big number of problem
1000// constraints. Fix this.
1001void PbConstraints::ComputeNewLearnedConstraintLimit() {
1002 const int num_constraints = constraints_.size();
1003 target_number_of_learned_constraint_ =
1004 num_constraints + parameters_->pb_cleanup_increment();
1005 num_learned_constraint_before_cleanup_ =
1006 static_cast<int>(target_number_of_learned_constraint_ /
1007 parameters_->pb_cleanup_ratio()) -
1008 num_constraints;
1009}
1010
1011void PbConstraints::DeleteSomeLearnedConstraintIfNeeded() {
1012 if (num_learned_constraint_before_cleanup_ == 0) {
1013 // First time.
1014 ComputeNewLearnedConstraintLimit();
1015 return;
1016 }
1017 --num_learned_constraint_before_cleanup_;
1018 if (num_learned_constraint_before_cleanup_ > 0) return;
1019 SCOPED_TIME_STAT(&stats_);
1020
1021 // Mark the constraint that needs to be deleted.
1022 // We do that in two pass, first we extract the activities.
1023 std::vector<double> activities;
1024 for (int i = 0; i < constraints_.size(); ++i) {
1025 const UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1026 if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1027 activities.push_back(constraint.activity());
1028 }
1029 }
1030
1031 // Then we compute the cutoff threshold.
1032 // Note that we can't delete constraint used as a reason!!
1033 std::sort(activities.begin(), activities.end());
1034 const int num_constraints_to_delete =
1035 constraints_.size() - target_number_of_learned_constraint_;
1036 CHECK_GT(num_constraints_to_delete, 0);
1037 if (num_constraints_to_delete >= activities.size()) {
1038 // Unlikely, but may happen, so in this case, we just delete all the
1039 // constraint that can possibly be deleted
1040 for (int i = 0; i < constraints_.size(); ++i) {
1041 UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1042 if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1043 constraint.MarkForDeletion();
1044 }
1045 }
1046 } else {
1047 const double limit_activity = activities[num_constraints_to_delete];
1048 int num_constraint_at_limit_activity = 0;
1049 for (int i = num_constraints_to_delete; i >= 0; --i) {
1050 if (activities[i] == limit_activity) {
1051 ++num_constraint_at_limit_activity;
1052 } else {
1053 break;
1054 }
1055 }
1056
1057 // Mark for deletion all the constraints under this threshold.
1058 // We only keep the most recent constraint amongst the one with the activity
1059 // exactly equal ot limit_activity, it is why the loop is in the reverse
1060 // order.
1061 for (int i = constraints_.size() - 1; i >= 0; --i) {
1062 UpperBoundedLinearConstraint& constraint = *(constraints_[i].get());
1063 if (constraint.is_learned() && !constraint.is_used_as_a_reason()) {
1064 if (constraint.activity() <= limit_activity) {
1065 if (constraint.activity() == limit_activity &&
1066 num_constraint_at_limit_activity > 0) {
1067 --num_constraint_at_limit_activity;
1068 } else {
1069 constraint.MarkForDeletion();
1070 }
1071 }
1072 }
1073 }
1074 }
1075
1076 // Finally we delete the marked constraint and compute the next limit.
1077 DeleteConstraintMarkedForDeletion();
1078 ComputeNewLearnedConstraintLimit();
1079}
1080
1082 if (!constraint->is_learned()) return;
1083 const double max_activity = parameters_->max_clause_activity_value();
1084 constraint->set_activity(constraint->activity() +
1085 constraint_activity_increment_);
1086 if (constraint->activity() > max_activity) {
1087 RescaleActivities(1.0 / max_activity);
1088 }
1089}
1090
1091void PbConstraints::RescaleActivities(double scaling_factor) {
1092 constraint_activity_increment_ *= scaling_factor;
1093 for (int i = 0; i < constraints_.size(); ++i) {
1094 constraints_[i]->set_activity(constraints_[i]->activity() * scaling_factor);
1095 }
1096}
1097
1099 const double decay = parameters_->clause_activity_decay();
1100 constraint_activity_increment_ *= 1.0 / decay;
1101}
1102
1103void PbConstraints::DeleteConstraintMarkedForDeletion() {
1105 constraints_.size(), ConstraintIndex(-1));
1106 ConstraintIndex new_index(0);
1107 for (ConstraintIndex i(0); i < constraints_.size(); ++i) {
1108 if (!constraints_[i.value()]->is_marked_for_deletion()) {
1109 index_mapping[i] = new_index;
1110 if (new_index < i) {
1111 constraints_[new_index.value()] = std::move(constraints_[i.value()]);
1112 thresholds_[new_index] = thresholds_[i];
1113 }
1114 ++new_index;
1115 } else {
1116 // Remove it from possible_duplicates_.
1117 UpperBoundedLinearConstraint* c = constraints_[i.value()].get();
1118 std::vector<UpperBoundedLinearConstraint*>& ref =
1119 possible_duplicates_[c->hash()];
1120 for (int i = 0; i < ref.size(); ++i) {
1121 if (ref[i] == c) {
1122 std::swap(ref[i], ref.back());
1123 ref.pop_back();
1124 break;
1125 }
1126 }
1127 }
1128 }
1129 constraints_.resize(new_index.value());
1130 thresholds_.resize(new_index.value());
1131
1132 // This is the slow part, we need to remap all the ConstraintIndex to the
1133 // new ones.
1134 for (LiteralIndex lit(0); lit < to_update_.size(); ++lit) {
1135 std::vector<ConstraintIndexWithCoeff>& updates = to_update_[lit];
1136 int new_index = 0;
1137 for (int i = 0; i < updates.size(); ++i) {
1138 const ConstraintIndex m = index_mapping[updates[i].index];
1139 if (m != -1) {
1140 updates[new_index] = updates[i];
1141 updates[new_index].index = m;
1142 ++new_index;
1143 }
1144 }
1145 updates.resize(new_index);
1146 }
1147}
1148
1149} // namespace sat
1150} // namespace operations_research
IntegerValue size
void ClearAndResize(IntegerType size)
Definition bitset.h:839
const std::vector< IntegerType > & PositionsSetAtLeastOnce() const
Definition bitset.h:878
void Set(IntegerType index)
Definition bitset.h:864
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
BooleanVariable Variable() const
Definition sat_base.h:87
std::string DebugString()
Returns a string representation of the constraint.
Coefficient ReduceCoefficientsAndComputeSlackForTrailPrefix(const Trail &trail, int trail_index)
Coefficient CancelationAmount(Literal literal, Coefficient coeff) const
Returns the "cancelation" amount of AddTerm(literal, coeff).
Coefficient GetCoefficient(BooleanVariable var) const
Returns the coefficient (>= 0) of the given variable.
Coefficient ComputeSlackForTrailPrefix(const Trail &trail, int trail_index) const
void AddToRhs(Coefficient value)
Adds a non-negative value to this constraint Rhs().
void CopyIntoVector(std::vector< LiteralWithCoeff > *output)
Copies this constraint into a vector<LiteralWithCoeff> representation.
const std::vector< BooleanVariable > & PossibleNonZeros() const
void ReduceSlackTo(const Trail &trail, int trail_index, Coefficient initial_slack, Coefficient target)
bool AddConstraint(const std::vector< LiteralWithCoeff > &cst, Coefficient rhs, Trail *trail)
UpperBoundedLinearConstraint * ReasonPbConstraint(int trail_index) const
absl::Span< const Literal > Reason(const Trail &trail, int trail_index, int64_t conflict_id) const final
void BumpActivity(UpperBoundedLinearConstraint *constraint)
bool AddLearnedConstraint(const std::vector< LiteralWithCoeff > &cst, Coefficient rhs, Trail *trail)
void RescaleActivities(double scaling_factor)
void Untrail(const Trail &trail, int trail_index) final
int NumVariables() const
Getters.
Definition sat_base.h:453
std::vector< Literal > * MutableConflict()
Definition sat_base.h:433
void EnqueueWithSameReasonAs(Literal true_literal, BooleanVariable reference_var)
Definition sat_base.h:335
const AssignmentInfo & Info(BooleanVariable var) const
Definition sat_base.h:463
std::vector< Literal > * GetEmptyVectorToStoreReason(int trail_index) const
Definition sat_base.h:393
const VariablesAssignment & Assignment() const
Definition sat_base.h:462
void ResolvePBConflict(const Trail &trail, BooleanVariable var, MutableUpperBoundedLinearConstraint *conflict, Coefficient *conflict_slack)
UpperBoundedLinearConstraint(const std::vector< LiteralWithCoeff > &cst)
Takes a pseudo-Boolean formula in canonical form.
Coefficient ComputeCancelation(const Trail &trail, int trail_index, const MutableUpperBoundedLinearConstraint &conflict)
bool Propagate(int trail_index, Coefficient *threshold, Trail *trail, PbConstraintsEnqueueHelper *helper)
void AddToConflict(MutableUpperBoundedLinearConstraint *conflict)
void FillReason(const Trail &trail, int source_trail_index, BooleanVariable propagated_variable, std::vector< Literal > *reason)
void Untrail(Coefficient *threshold, int trail_index)
bool HasIdenticalTerms(absl::Span< const LiteralWithCoeff > cst)
Returns true if the given terms are the same as the one in this constraint.
bool InitializeRhs(Coefficient rhs, int trail_index, Coefficient *threshold, Trail *trail, PbConstraintsEnqueueHelper *helper)
bool VariableIsAssigned(BooleanVariable var) const
Returns true iff the given variable is assigned.
Definition sat_base.h:196
bool LiteralIsFalse(Literal literal) const
Definition sat_base.h:185
bool LiteralIsTrue(Literal literal) const
Definition sat_base.h:188
void assign(size_type n, const value_type &val)
void push_back(const value_type &val)
void resize(size_type new_size)
int64_t a
Definition table.cc:44
int64_t value
IntVar * var
double upper_bound
double lower_bound
int lit
int literal
int index
const bool DEBUG_MODE
Definition macros.h:24
void SimplifyCanonicalBooleanLinearConstraint(std::vector< LiteralWithCoeff > *cst, Coefficient *rhs)
bool ApplyLiteralMapping(const util_intops::StrongVector< LiteralIndex, LiteralIndex > &mapping, std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
Coefficient ComputeNegatedCanonicalRhs(Coefficient lower_bound, Coefficient bound_shift, Coefficient max_value)
const LiteralIndex kTrueLiteralIndex(-2)
bool BooleanLinearExpressionIsCanonical(absl::Span< const LiteralWithCoeff > cst)
Returns true iff the Boolean linear expression is in canonical form.
const LiteralIndex kFalseLiteralIndex(-3)
Coefficient ComputeCanonicalRhs(Coefficient upper_bound, Coefficient bound_shift, Coefficient max_value)
bool ComputeBooleanLinearExpressionCanonicalForm(std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
In SWIG mode, we don't want anything besides these top-level includes.
bool SafeAddInto(IntegerType a, IntegerType *b)
ColIndex representative
const Variable x
Definition qp_tests.cc:127
int64_t bound
int64_t coefficient
#define SCOPED_TIME_STAT(stats)
Definition stats.h:418
int32_t trail_index
The index of this assignment in the trail.
Definition sat_base.h:263
Represents a term in a pseudo-Boolean formula.
void Enqueue(Literal l, int source_trail_index, UpperBoundedLinearConstraint *ct, Trail *trail)
int propagator_id
The propagator id of PbConstraints.
std::vector< Literal > conflict
A temporary vector to store the last conflict.