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