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