Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
pb_constraint.h
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
14#ifndef ORTOOLS_SAT_PB_CONSTRAINT_H_
15#define ORTOOLS_SAT_PB_CONSTRAINT_H_
16
17#include <algorithm>
18#include <cstdint>
19#include <limits>
20#include <memory>
21#include <ostream>
22#include <string>
23#include <tuple>
24#include <vector>
25
26#include "absl/container/flat_hash_map.h"
27#include "absl/log/check.h"
28#include "absl/types/span.h"
32#include "ortools/sat/model.h"
35#include "ortools/util/bitset.h"
36#include "ortools/util/stats.h"
38
39namespace operations_research {
40namespace sat {
41
42// The type of the integer coefficients in a pseudo-Boolean constraint.
43// This is also used for the current value of a constraint or its bounds.
45
46// IMPORTANT: We can't use numeric_limits<Coefficient>::max() which will compile
47// but just returns zero!!
48const Coefficient kCoefficientMax(
49 std::numeric_limits<Coefficient::ValueType>::max());
50
51// Represents a term in a pseudo-Boolean formula.
53 LiteralWithCoeff() = default;
54 LiteralWithCoeff(Literal l, Coefficient c) : literal(l), coefficient(c) {}
55 LiteralWithCoeff(Literal l, int64_t c) : literal(l), coefficient(c) {}
57 Coefficient coefficient;
58 bool operator==(const LiteralWithCoeff& other) const {
59 return literal.Index() == other.literal.Index() &&
60 coefficient == other.coefficient;
61 }
62};
63
64template <typename H>
65H AbslHashValue(H h, const LiteralWithCoeff& term) {
66 return H::combine(std::move(h), term.literal.Index(),
67 term.coefficient.value());
68}
69
70inline std::ostream& operator<<(std::ostream& os, LiteralWithCoeff term) {
71 os << term.coefficient << "[" << term.literal.DebugString() << "]";
72 return os;
73}
74
75// Puts the given Boolean linear expression in canonical form:
76// - Merge all the literal corresponding to the same variable.
77// - Remove zero coefficients.
78// - Make all the coefficients positive.
79// - Sort the terms by increasing coefficient values.
80//
81// This function also computes:
82// - max_value: the maximum possible value of the formula.
83// - bound_shift: which allows to updates initial bounds. That is, if an
84// initial pseudo-Boolean constraint was
85// lhs < initial_pb_formula < rhs
86// then the new one is:
87// lhs + bound_shift < canonical_form < rhs + bound_shift
88//
89// Finally, this will return false, if some integer overflow or underflow
90// occurred during the reduction to the canonical form.
92 std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
93 Coefficient* max_value);
94
95// Maps all the literals of the given constraint using the given mapping. The
96// mapping may map a literal index to kTrueLiteralIndex or kFalseLiteralIndex in
97// which case the literal will be considered fixed to the appropriate value.
98//
99// Note that this function also canonicalizes the constraint and updates
100// bound_shift and max_value like ComputeBooleanLinearExpressionCanonicalForm()
101// does.
102//
103// Finally, this will return false if some integer overflow or underflow
104// occurred during the constraint simplification.
107 std::vector<LiteralWithCoeff>* cst, Coefficient* bound_shift,
108 Coefficient* max_value);
109
110// From a constraint 'expr <= ub' and the result (bound_shift, max_value) of
111// calling ComputeBooleanLinearExpressionCanonicalForm() on 'expr', this returns
112// a new rhs such that 'canonical expression <= rhs' is an equivalent
113// constraint. This function deals with all the possible overflow corner cases.
114//
115// The result will be in [-1, max_value] where -1 means unsatisfiable and
116// max_value means trivialy satisfiable.
117Coefficient ComputeCanonicalRhs(Coefficient upper_bound,
118 Coefficient bound_shift, Coefficient max_value);
119
120// Same as ComputeCanonicalRhs(), but uses the initial constraint lower bound
121// instead. From a constraint 'lb <= expression', this returns a rhs such that
122// 'canonical expression with literals negated <= rhs'.
123//
124// Note that the range is also [-1, max_value] with the same meaning.
125Coefficient ComputeNegatedCanonicalRhs(Coefficient lower_bound,
126 Coefficient bound_shift,
127 Coefficient max_value);
128
129// Returns true iff the enforced Boolean linear expression is in canonical form.
130// The enforcement literals must be sorted and unique, and cst must be in the
131// form returned by ComputeBooleanLinearExpressionCanonicalForm(). Moreover the
132// enforcement literals should not appear in cst.
134 absl::Span<const Literal> enforcement_literals,
135 absl::Span<const LiteralWithCoeff> cst);
136
137// Given a Boolean linear constraint in canonical form, simplify its
138// coefficients using simple heuristics.
140 std::vector<LiteralWithCoeff>* cst, Coefficient* rhs);
141
142// Holds a set of boolean linear constraints in canonical form:
143// - The constraint is a linear sum of LiteralWithCoeff <= rhs.
144// - The linear sum satisfies the properties described in
145// ComputeBooleanLinearExpressionCanonicalForm().
146//
147// TODO(user): Simplify further the constraints.
148//
149// TODO(user): Remove the duplication between this and what the sat solver
150// is doing in AddLinearConstraint() which is basically the same.
151//
152// TODO(user): Remove duplicate constraints? some problems have them, and
153// this is not ideal for the symmetry computation since it leads to a lot of
154// symmetries of the associated graph that are not useful.
156 public:
158
159 // This type is neither copyable nor movable.
162 const CanonicalBooleanLinearProblem&) = delete;
163
164 // Adds a new constraint to the problem. The bounds are inclusive.
165 // Returns false in case of a possible overflow or if the constraint is
166 // never satisfiable.
167 //
168 // TODO(user): Use a return status to distinguish errors if needed.
169 bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound,
170 bool use_upper_bound, Coefficient upper_bound,
171 std::vector<LiteralWithCoeff>* cst);
172
173 // Getters. All the constraints are guaranteed to be in canonical form.
174 int NumConstraints() const { return constraints_.size(); }
175 Coefficient Rhs(int i) const { return rhs_[i]; }
176 const std::vector<LiteralWithCoeff>& Constraint(int i) const {
177 return constraints_[i];
178 }
179
180 private:
181 bool AddConstraint(absl::Span<const LiteralWithCoeff> cst,
182 Coefficient max_value, Coefficient rhs);
183
184 std::vector<Coefficient> rhs_;
185 std::vector<std::vector<LiteralWithCoeff>> constraints_;
186};
187
188// Encode a constraint sum term <= rhs, where each term is a positive
189// Coefficient times a literal. This class allows efficient modification of the
190// constraint and is used during pseudo-Boolean resolution.
192 public:
193 // This must be called before any other functions is used with an higher
194 // variable index.
195 void ClearAndResize(int num_variables);
196
197 // Reset the constraint to 0 <= 0.
198 // Note that the constraint size stays the same.
199 void ClearAll();
200
201 // Returns the coefficient (>= 0) of the given variable.
202 Coefficient GetCoefficient(BooleanVariable var) const {
203 return AbsCoefficient(terms_[var]);
204 }
205
206 // Returns the literal under which the given variable appear in the
207 // constraint. Note that if GetCoefficient(var) == 0 this just returns
208 // Literal(var, true).
209 Literal GetLiteral(BooleanVariable var) const {
210 return Literal(var, terms_[var] > 0);
211 }
212
213 // If we have a lower bounded constraint sum terms >= rhs, then it is trivial
214 // to see that the coefficient of any term can be reduced to rhs if it is
215 // bigger. This does exactly this operation, but on the upper bounded
216 // representation.
217 //
218 // If we take a constraint sum ci.xi <= rhs, take its negation and add max_sum
219 // on both side, we have sum ci.(1 - xi) >= max_sum - rhs
220 // So every ci > (max_sum - rhs) can be replaced by (max_sum - rhs).
221 // Not that this operation also change the original rhs of the constraint.
222 void ReduceCoefficients();
223
224 // Same as ReduceCoefficients() but only consider the coefficient of the given
225 // variable.
226 void ReduceGivenCoefficient(BooleanVariable var) {
227 const Coefficient bound = max_sum_ - rhs_;
228 const Coefficient diff = GetCoefficient(var) - bound;
229 if (diff > 0) {
230 rhs_ -= diff;
231 max_sum_ -= diff;
232 terms_[var] = (terms_[var] > 0) ? bound : -bound;
233 }
234 }
235
236 // Compute the constraint slack assuming that only the variables with index <
237 // trail_index are assigned.
238 Coefficient ComputeSlackForTrailPrefix(const Trail& trail,
239 int trail_index) const;
240
241 // Same as ReduceCoefficients() followed by ComputeSlackForTrailPrefix(). It
242 // allows to loop only once over all the terms of the constraint instead of
243 // doing it twice. This helps since doing that can be the main bottleneck.
244 //
245 // Note that this function assumes that the returned slack will be negative.
246 // This allow to DCHECK some assumptions on what coefficients can be reduced
247 // or not.
248 //
249 // TODO(user): Ideally the slack should be maintainable incrementally.
251 const Trail& trail, int trail_index);
252
253 // Relaxes the constraint so that:
254 // - ComputeSlackForTrailPrefix(trail, trail_index) == target;
255 // - All the variables that were propagated given the assignment < trail_index
256 // are still propagated.
257 //
258 // As a precondition, ComputeSlackForTrailPrefix(trail, trail_index) >= target
259 // Note that nothing happen if the slack is already equals to target.
260 //
261 // Algorithm: Let diff = slack - target (>= 0). We will split the constraint
262 // linear expression in 3 parts:
263 // - P1: the true variables (only the one assigned < trail_index).
264 // - P2: the other variables with a coeff > diff.
265 // Note that all these variables were the propagated ones.
266 // - P3: the other variables with a coeff <= diff.
267 // We can then transform P1 + P2 + P3 <= rhs_ into P1 + P2' <= rhs_ - diff
268 // Where P2' is the same sum as P2 with all the coefficient reduced by diff.
269 //
270 // Proof: Given the old constraint, we want to show that the relaxed one is
271 // always true. If all the variable in P2' are false, then
272 // P1 <= rhs_ - slack <= rhs_ - diff is always true. If at least one of the
273 // P2' variable is true, then P2 >= P2' + diff and we have
274 // P1 + P2' + diff <= P1 + P2 <= rhs_.
275 void ReduceSlackTo(const Trail& trail, int trail_index,
276 Coefficient initial_slack, Coefficient target);
277
278 // Copies this constraint into a vector<LiteralWithCoeff> representation.
279 void CopyIntoVector(std::vector<LiteralWithCoeff>* output);
280
281 // Adds a non-negative value to this constraint Rhs().
282 void AddToRhs(Coefficient value) {
283 CHECK_GE(value, 0);
284 rhs_ += value;
285 }
286 Coefficient Rhs() const { return rhs_; }
287 Coefficient MaxSum() const { return max_sum_; }
288
289 // Adds a term to this constraint. This is in the .h for efficiency.
290 // The encoding used internally is described below in the terms_ comment.
291 void AddTerm(Literal literal, Coefficient coeff) {
292 CHECK_GT(coeff, 0);
293 const BooleanVariable var = literal.Variable();
294 const Coefficient term_encoding = literal.IsPositive() ? coeff : -coeff;
295 if (literal != GetLiteral(var)) {
296 // The two terms are of opposite sign, a "cancelation" happens.
297 // We need to change the encoding of the lower magnitude term.
298 // - If term > 0, term . x -> term . (x - 1) + term
299 // - If term < 0, term . (x - 1) -> term . x - term
300 // In both cases, rhs -= abs(term).
301 rhs_ -= std::min(coeff, AbsCoefficient(terms_[var]));
302 max_sum_ += AbsCoefficient(term_encoding + terms_[var]) -
303 AbsCoefficient(terms_[var]);
304 } else {
305 // Both terms are of the same sign (or terms_[var] is zero).
306 max_sum_ += coeff;
307 }
308 CHECK_GE(max_sum_, 0) << "Overflow";
309 terms_[var] += term_encoding;
310 non_zeros_.Set(var);
311 }
312
313 // Returns the "cancelation" amount of AddTerm(literal, coeff).
314 Coefficient CancelationAmount(Literal literal, Coefficient coeff) const {
315 DCHECK_GT(coeff, 0);
316 const BooleanVariable var = literal.Variable();
317 if (literal == GetLiteral(var)) return Coefficient(0);
318 return std::min(coeff, AbsCoefficient(terms_[var]));
319 }
320
321 // Returns a set of positions that contains all the non-zeros terms of the
322 // constraint. Note that this set can also contains some zero terms.
323 const std::vector<BooleanVariable>& PossibleNonZeros() const {
324 return non_zeros_.PositionsSetAtLeastOnce();
325 }
326
327 // Returns a string representation of the constraint.
328 std::string DebugString();
329
330 private:
331 Coefficient AbsCoefficient(Coefficient a) const { return a > 0 ? a : -a; }
332
333 // Only used for DCHECK_EQ(max_sum_, ComputeMaxSum());
334 Coefficient ComputeMaxSum() const;
335
336 // The encoding is special:
337 // - If terms_[x] > 0, then the associated term is 'terms_[x] . x'
338 // - If terms_[x] < 0, then the associated term is 'terms_[x] . (x - 1)'
340
341 // The right hand side of the constraint (sum terms <= rhs_).
342 Coefficient rhs_;
343
344 // The constraint maximum sum (i.e. sum of the absolute term coefficients).
345 // Note that checking the integer overflow on this sum is enough.
346 Coefficient max_sum_;
347
348 // Contains the possibly non-zeros terms_ value.
350};
351
352// A simple "helper" class to enqueue a propagated literal on the trail and
353// keep the information needed to explain it when requested.
354class UpperBoundedLinearConstraint;
355
357 void Enqueue(Literal l, int source_trail_index,
359 reasons[trail->Index()] = {source_trail_index, ct};
360 trail->Enqueue(l, propagator_id);
361 }
362
363 // The propagator id of PbConstraints.
365
366 // A temporary vector to store the last conflict.
367 std::vector<Literal> conflict;
368
369 // Information needed to recover the reason of an Enqueue().
370 // Indexed by trail_index.
375 std::vector<ReasonInfo> reasons;
376
377 // A temporary vector of tuples used in FillReason().
378 mutable std::vector<std::tuple<int, int, int>> temporary_tuples;
379};
380
381// This class contains half the propagation logic for a constraint of the form
382//
383// sum ci * li <= rhs, ci positive coefficients, li literals.
384//
385// The other half is implemented by the PbConstraints class below which takes
386// care of updating the 'threshold' value of this constraint:
387// - 'slack' is rhs minus all the ci of the variables xi assigned to
388// true. Note that it is not updated as soon as xi is assigned, but only
389// later when this assignment is "processed" by the PbConstraints class.
390// - 'threshold' is the distance from 'slack' to the largest coefficient ci
391// smaller or equal to slack. By definition, all the literals with
392// even larger coefficients that are yet 'processed' must be false for the
393// constraint to be satisfiable.
395 public:
396 // Takes a pseudo-Boolean formula in canonical form.
397 UpperBoundedLinearConstraint(const std::vector<Literal>& enforcement_literals,
398 const std::vector<LiteralWithCoeff>& cst);
399
400 EnforcementId enforcement_id() const { return enforcement_id_; };
401 void set_enforcement_id(EnforcementId enforcement_id) {
402 enforcement_id_ = enforcement_id;
403 }
404
405 // Returns true if the given terms and enforcement literals are the same as
406 // the one in this constraint.
408 absl::Span<const Literal> enforcement_literals,
409 absl::Span<const LiteralWithCoeff> cst,
410 EnforcementPropagator* enforcement_propagator);
411 Coefficient Rhs() const { return rhs_; }
412
413 // Sets the rhs of this constraint. Compute the initial threshold value using
414 // only the literal with a trail index smaller than the given one. Enqueues on
415 // the trail any propagated literals.
416 //
417 // Returns false if the preconditions described in
418 // PbConstraints::AddConstraint() are not meet.
419 bool InitializeRhs(EnforcementStatus enforcement_status,
420 absl::Span<const Literal> enforcement_literals,
421 Coefficient rhs, int trail_index, Coefficient* threshold,
422 Trail* trail, PbConstraintsEnqueueHelper* helper);
423
424 // Tests for propagation and enqueues propagated literals on the trail.
425 // Returns false if a conflict was detected, in which case conflict is filled.
426 //
427 // Preconditions:
428 // - For each "processed" literal, the given threshold value must have been
429 // decreased by its associated coefficient in the constraint. It must now
430 // be stricly negative.
431 // - The given trail_index is the index of a true literal in the trail which
432 // just caused threshold to become stricly negative. All literals with
433 // smaller index must have been "processed". All assigned literals with
434 // greater trail index are not yet "processed".
435 //
436 // The threshold is updated to its new value.
437 bool Propagate(int trail_index, Coefficient* threshold, Trail* trail,
438 EnforcementStatus enforcement_status,
439 absl::Span<const Literal> enforcement_literals,
441 bool* need_untrail_inspection = nullptr);
442
443 // Updates the given threshold and the internal state. This is the opposite of
444 // Propagate(). Each time a literal in unassigned, the threshold value must
445 // have been increased by its coefficient. This update the threshold to its
446 // new value.
447 void Untrail(Coefficient* threshold, int trail_index);
448
449 // Provided that the literal with given source_trail_index was the one that
450 // propagated the conflict or the literal we want to explain, then this will
451 // compute the reason. temporary_tuples is only used as a temporary storage to
452 // avoid allocating a vector at each call.
453 //
454 // Some properties of the reason:
455 // - Literals of level 0 are removed.
456 // - It will always contain the literal with given source_trail_index (except
457 // if it is of level 0).
458 // - We make the reason more compact by greedily removing terms with small
459 // coefficients that would not have changed the propagation.
460 //
461 // TODO(user): Maybe it is possible to derive a better reason by using more
462 // information. For instance one could use the mask of literals that are
463 // better to use during conflict minimization (namely the one already in the
464 // 1-UIP conflict).
465 void FillReason(const Trail& trail, int source_trail_index,
466 absl::Span<const Literal> enforcement_literals,
467 BooleanVariable propagated_variable,
468 std::vector<std::tuple<int, int, int>>* temporary_tuples,
469 std::vector<Literal>* reason);
470
471 // Same operation as SatSolver::ResolvePBConflict(), the only difference is
472 // that here the reason for var is *this.
473 void ResolvePBConflict(const Trail& trail, BooleanVariable var,
475 Coefficient* conflict_slack);
476
477 // Adds this pb constraint into the given mutable one.
478 //
479 // TODO(user): Provides instead an easy to use iterator over an
480 // UpperBoundedLinearConstraint and move this function to
481 // MutableUpperBoundedLinearConstraint.
483
484 // Compute the sum of the "cancelation" in AddTerm() if *this is added to
485 // the given conflict. The sum doesn't take into account literal assigned with
486 // a trail index smaller than the given one.
487 //
488 // Note(user): Currently, this is only used in DCHECKs.
489 Coefficient ComputeCancelation(
490 const Trail& trail, int trail_index,
492
493 // API to mark a constraint for deletion before actually deleting it.
494 void MarkForDeletion() { is_marked_for_deletion_ = true; }
495 bool is_marked_for_deletion() const { return is_marked_for_deletion_; }
496
497 // Only learned constraints are considered for deletion during the constraint
498 // cleanup phase. We also can't delete variables used as a reason.
500 CHECK(!is_learned || enforcement_id_ < 0);
501 is_learned_ = is_learned;
502 }
503 bool is_learned() const { return is_learned_; }
504 bool is_used_as_a_reason() const { return first_reason_trail_index_ != -1; }
505
506 // Activity of the constraint. Only low activity constraint will be deleted
507 // during the constraint cleanup phase.
508 void set_activity(double activity) { activity_ = activity; }
509 double activity() const { return activity_; }
510
511 // Returns a fingerprint of the constraint linear expression (without rhs).
512 // This is used for duplicate detection.
513 uint64_t hash() const { return hash_; }
514
515 // This is used to get statistics of the number of literals inspected by
516 // a Propagate() call.
517 int already_propagated_end() const { return already_propagated_end_; }
518
519 private:
520 Coefficient GetSlackFromThreshold(Coefficient threshold) const {
521 return (index_ < 0) ? threshold : coeffs_[index_] + threshold;
522 }
523 void Update(Coefficient slack, Coefficient* threshold) {
524 *threshold = (index_ < 0) ? slack : slack - coeffs_[index_];
525 already_propagated_end_ = starts_[index_ + 1];
526 }
527
528 // Constraint management fields.
529 // TODO(user): Rearrange and specify bit size to minimize memory usage.
530 bool is_marked_for_deletion_;
531 bool is_learned_;
532 int first_reason_trail_index_;
533 double activity_;
534
535 // Constraint propagation fields.
536 int index_;
537 int already_propagated_end_;
538
539 // In the internal representation, we merge the terms with the same
540 // coefficient.
541 // - literals_ contains all the literal of the constraint sorted by
542 // increasing coefficients.
543 // - coeffs_ contains unique increasing coefficients.
544 // - starts_[i] is the index in literals_ of the first literal with
545 // coefficient coeffs_[i].
546 EnforcementId enforcement_id_;
547 std::vector<Coefficient> coeffs_;
548 std::vector<int> starts_;
549 std::vector<Literal> literals_;
550 Coefficient rhs_;
551
552 uint64_t hash_;
553};
554
555// Class responsible for managing a set of pseudo-Boolean constraints and their
556// propagation.
558 public:
559 explicit PbConstraints(Model* model)
560 : SatPropagator("PbConstraints"),
561 enforcement_propagator_(model->GetOrCreate<EnforcementPropagator>()),
562 conflicting_constraint_index_(-1),
563 num_learned_constraint_before_cleanup_(0),
564 constraint_activity_increment_(1.0),
565 parameters_(model->GetOrCreate<SatParameters>()),
566 stats_("PbConstraints"),
567 num_constraint_lookups_(0),
568 num_inspected_constraint_literals_(0),
569 num_threshold_updates_(0) {
570 model->GetOrCreate<Trail>()->RegisterPropagator(this);
571 }
572
573 // This type is neither copyable nor movable.
574 PbConstraints(const PbConstraints&) = delete;
576 ~PbConstraints() override {
578 LOG(INFO) << stats_.StatString();
579 LOG(INFO) << "num_constraint_lookups_: " << num_constraint_lookups_;
580 LOG(INFO) << "num_threshold_updates_: " << num_threshold_updates_;
581 });
582 }
583
584 bool Propagate(Trail* trail) final;
585 void Untrail(const Trail& trail, int trail_index) final;
586 absl::Span<const Literal> Reason(const Trail& trail, int trail_index,
587 int64_t conflict_id) const final;
588
589 // Changes the number of variables.
590 void Resize(int num_variables) {
591 // Note that we avoid using up memory in the common case where there are no
592 // pb constraints at all. If there is 10 million variables, this vector
593 // alone will take 480 MB!
594 if (!constraints_.empty()) {
595 to_update_.resize(num_variables << 1);
596 enqueue_helper_.reasons.resize(num_variables);
597 }
598 }
599
600 // Adds a constraint in canonical form to the set of managed constraints. Note
601 // that this detects constraints with exactly the same terms. In this case,
602 // the constraint rhs is updated if the new one is lower or nothing is done
603 // otherwise.
604 //
605 // There are some preconditions, and the function will return false if they
606 // are not met. The constraint can be added when the trail is not empty,
607 // however given the current propagated assignment:
608 // - The constraint cannot be conflicting.
609 // - The constraint cannot have propagated at an earlier decision level.
610 bool AddConstraint(const std::vector<LiteralWithCoeff>& cst, Coefficient rhs,
611 Trail* trail) {
612 return AddConstraint(/*enforcement_literals=*/{}, cst, rhs, trail);
613 }
614 bool AddConstraint(const std::vector<Literal>& enforcement_literals,
615 const std::vector<LiteralWithCoeff>& cst, Coefficient rhs,
616 Trail* trail);
617
618 // Same as AddConstraint(), but also marks the added constraint as learned
619 // so that it can be deleted during the constraint cleanup phase.
620 bool AddLearnedConstraint(const std::vector<LiteralWithCoeff>& cst,
621 Coefficient rhs, Trail* trail);
622
623 // Returns the number of constraints managed by this class.
624 int NumberOfConstraints() const { return constraints_.size(); }
625 bool IsEmpty() const final { return constraints_.empty(); }
626
627 // ConflictingConstraint() returns the last PB constraint that caused a
628 // conflict. Calling ClearConflictingConstraint() reset this to nullptr.
629 //
630 // TODO(user): This is a hack to get the PB conflict, because the rest of
631 // the solver API assume only clause conflict. Find a cleaner way?
632 void ClearConflictingConstraint() { conflicting_constraint_index_ = -1; }
634 if (conflicting_constraint_index_ == -1) return nullptr;
635 return constraints_[conflicting_constraint_index_.value()].get();
636 }
637
638 // Returns the underlying UpperBoundedLinearConstraint responsible for
639 // assigning the literal at given trail index.
640 UpperBoundedLinearConstraint* ReasonPbConstraint(int trail_index) const;
641
642 // Activity update functions.
643 // TODO(user): Remove duplication with other activity update functions.
645 void RescaleActivities(double scaling_factor);
647
648 // Only used for testing.
649 void DeleteConstraint(int index) {
650 constraints_[index]->MarkForDeletion();
651 DeleteConstraintMarkedForDeletion();
652 }
653
654 // Some statistics.
655 int64_t num_constraint_lookups() const { return num_constraint_lookups_; }
657 return num_inspected_constraint_literals_;
658 }
659 int64_t num_threshold_updates() const { return num_threshold_updates_; }
660
661 private:
662 DEFINE_STRONG_INDEX_TYPE(ConstraintIndex);
663
664 bool PropagateNext(Trail* trail);
665 bool PropagateConstraint(ConstraintIndex index, Trail* trail,
666 int source_trail_index,
667 bool* need_untrail_inspection = nullptr);
668
669 // Same function as the clause related one is SatSolver().
670 // TODO(user): Remove duplication.
671 void ComputeNewLearnedConstraintLimit();
672 void DeleteSomeLearnedConstraintIfNeeded();
673
674 // Deletes all the UpperBoundedLinearConstraint for which
675 // is_marked_for_deletion() is true. This is relatively slow in O(number of
676 // terms in all constraints).
677 void DeleteConstraintMarkedForDeletion();
678
679 // Each constraint managed by this class is associated with an index.
680 // The set of indices is always [0, num_constraints_).
681 //
682 // Note(user): this complicate things during deletion, but the propagation is
683 // about two times faster with this implementation than one with direct
684 // pointer to an UpperBoundedLinearConstraint. The main reason for this is
685 // probably that the thresholds_ vector is a lot more efficient cache-wise.
686 struct ConstraintIndexWithCoeff {
687 ConstraintIndexWithCoeff() = default; // Needed for vector.resize()
688 ConstraintIndexWithCoeff(bool n, ConstraintIndex i, Coefficient c)
689 : need_untrail_inspection(n), index(i), coefficient(c) {}
690 bool need_untrail_inspection;
691 ConstraintIndex index;
692 Coefficient coefficient;
693 };
694
695 // The set of all pseudo-boolean constraint managed by this class.
696 std::vector<std::unique_ptr<UpperBoundedLinearConstraint>> constraints_;
697
698 // The current value of the threshold for each constraints.
700
701 // For each literal, the list of all the constraints that contains it together
702 // with the literal coefficient in these constraints.
704 to_update_;
705
706 // The indices of the constraints that need to be updated because of an
707 // enforcement status change.
708 SparseBitset<ConstraintIndex> enforcement_status_changed_;
709
710 // Bitset used to optimize the Untrail() function.
711 SparseBitset<ConstraintIndex> to_untrail_;
712
713 // Pointers to the constraints grouped by their hash.
714 // This is used to find duplicate constraints by AddConstraint().
715 absl::flat_hash_map<int64_t, std::vector<UpperBoundedLinearConstraint*>>
716 possible_duplicates_;
717
718 // Helper to enqueue propagated literals on the trail and store their reasons.
719 PbConstraintsEnqueueHelper enqueue_helper_;
720
721 EnforcementPropagator* enforcement_propagator_;
722
723 // Last conflicting PB constraint index. This is reset to -1 when
724 // ClearConflictingConstraint() is called.
725 ConstraintIndex conflicting_constraint_index_;
726
727 // Used for the constraint cleaning policy.
728 int target_number_of_learned_constraint_;
729 int num_learned_constraint_before_cleanup_;
730 double constraint_activity_increment_;
731
732 // Algorithm parameters.
733 SatParameters* parameters_;
734
735 // Some statistics.
736 mutable StatsGroup stats_;
737 int64_t num_constraint_lookups_;
738 int64_t num_inspected_constraint_literals_;
739 int64_t num_threshold_updates_;
740};
741
742// Boolean linear constraints can propagate a lot of literals at the same time.
743// As a result, all these literals will have exactly the same reason. It is
744// important to take advantage of that during the conflict
745// computation/minimization. On some problem, this can have a huge impact.
746//
747// TODO(user): With the new SAME_REASON_AS mechanism, this is more general so
748// move out of pb_constraint.
750 public:
752 : trail_(trail) {}
753
754 // This type is neither copyable nor movable.
756 delete;
758 const VariableWithSameReasonIdentifier&) = delete;
759
760 void Resize(int num_variables) {
761 first_variable_.resize(num_variables);
762 seen_.ClearAndResize(BooleanVariable(num_variables));
763 }
764
765 // Clears the cache. Call this before each conflict analysis.
766 void Clear() { seen_.ResetAllToFalse(); }
767
768 // Returns the first variable with exactly the same reason as 'var' on which
769 // this function was called since the last Clear(). Note that if no variable
770 // had the same reason, then var is returned.
771 BooleanVariable FirstVariableWithSameReason(BooleanVariable var) {
772 if (seen_[var]) return first_variable_[var];
773 const BooleanVariable reference_var =
774 trail_.ReferenceVarWithSameReason(var);
775 if (reference_var == var) return var;
776 if (seen_[reference_var]) return first_variable_[reference_var];
777 seen_.Set(reference_var);
778 first_variable_[reference_var] = var;
779 return var;
780 }
781
782 private:
783 const Trail& trail_;
786};
787
788} // namespace sat
789} // namespace operations_research
790
791#endif // ORTOOLS_SAT_PB_CONSTRAINT_H_
CanonicalBooleanLinearProblem & operator=(const CanonicalBooleanLinearProblem &)=delete
const std::vector< LiteralWithCoeff > & Constraint(int i) const
CanonicalBooleanLinearProblem(const CanonicalBooleanLinearProblem &)=delete
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
std::string DebugString() const
Definition sat_base.h:101
LiteralIndex Index() const
Definition sat_base.h:92
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)
PbConstraints & operator=(const PbConstraints &)=delete
PbConstraints(const PbConstraints &)=delete
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)
UpperBoundedLinearConstraint * ConflictingConstraint()
void Untrail(const Trail &trail, int trail_index) final
SatPropagator(const std::string &name)
Definition sat_base.h:786
void Enqueue(Literal true_literal, int propagator_id)
Definition sat_base.h:350
void RegisterPropagator(SatPropagator *propagator)
Definition sat_base.h:919
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)
void set_enforcement_id(EnforcementId enforcement_id)
BooleanVariable FirstVariableWithSameReason(BooleanVariable var)
VariableWithSameReasonIdentifier(const VariableWithSameReasonIdentifier &)=delete
VariableWithSameReasonIdentifier & operator=(const VariableWithSameReasonIdentifier &)=delete
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)
std::ostream & operator<<(std::ostream &os, const BoolVar &var)
Definition cp_model.cc:89
Coefficient ComputeCanonicalRhs(Coefficient upper_bound, Coefficient bound_shift, Coefficient max_value)
H AbslHashValue(H h, const IntVar &i)
Definition cp_model.h:515
const Coefficient kCoefficientMax(std::numeric_limits< Coefficient::ValueType >::max())
bool ComputeBooleanLinearExpressionCanonicalForm(std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
OR-Tools root namespace.
#define IF_STATS_ENABLED(instructions)
Definition stats.h:412
#define DEFINE_STRONG_INT64_TYPE(integer_type_name)
#define DEFINE_STRONG_INDEX_TYPE(index_type_name)
LiteralWithCoeff(Literal l, Coefficient c)
bool operator==(const LiteralWithCoeff &other) const
void Enqueue(Literal l, int source_trail_index, UpperBoundedLinearConstraint *ct, Trail *trail)
std::vector< std::tuple< int, int, int > > temporary_tuples