Skip to main content

rustsat/encodings/
pb.rs

1//! # CNF Encodings for Pseudo-Boolean Constraints
2//!
3//! The module contains implementations of CNF encodings for pseudo-boolean
4//! constraints. It defines traits for (non-)incremental PB constraints and
5//! encodings implementing these traits.
6//!
7//! ## Example Usage
8//!
9//! ```
10//! # use rustsat::{
11//! #     clause,
12//! #     encodings::pb::{BoundBoth, DefIncBothBounding, Encode},
13//! #     instances::{BasicVarManager, Cnf, ManageVars},
14//! #     lit, solvers, var,
15//! #     types::RsHashMap,
16//! # };
17//! #
18//! let mut var_manager = BasicVarManager::default();
19//! var_manager.increase_next_free(var![4]);
20//!
21//! let mut lits = RsHashMap::default();
22//! lits.insert(lit![0], 4);
23//! lits.insert(lit![1], 2);
24//! lits.insert(lit![2], 2);
25//! lits.insert(lit![3], 6);
26//! let mut enc = DefIncBothBounding::from(lits);
27//! let mut encoding = Cnf::new();
28//! enc.encode_both(4..=4, &mut encoding, &mut var_manager);
29//! ```
30//!
31//! When using cardinality and pseudo-boolean encodings at the same time, it is
32//! recommended to import only the modules or rename the traits, e.g., `use
33//! card::Encode as EncodePB`.
34
35use std::{
36    cmp,
37    ops::{Bound, Range, RangeBounds},
38};
39
40use super::{card, CollectClauses, ConstraintEncodingError, EnforceError};
41use crate::{
42    clause,
43    instances::ManageVars,
44    types::{
45        constraints::{PbConstraint, PbEqConstr, PbLbConstr, PbUbConstr},
46        Clause, Lit,
47    },
48    utils::unreachable_err,
49};
50
51pub mod gte;
52pub use gte::GeneralizedTotalizer;
53
54pub mod simulators;
55/// Inverted generalized totalizer that can be used for lower bounding PB expressions
56pub type InvertedGeneralizedTotalizer = simulators::Inverted<GeneralizedTotalizer>;
57/// Double generalized totalizer that can be used for upper and lower bounding PB expressions
58pub type DoubleGeneralizedTotalizer =
59    simulators::Double<GeneralizedTotalizer, InvertedGeneralizedTotalizer>;
60
61pub mod dpw;
62pub use dpw::DynamicPolyWatchdog;
63
64pub mod adder;
65pub use adder::BinaryAdder;
66
67#[cfg(feature = "proof-logging")]
68pub mod cert;
69
70/// Trait for all pseudo-boolean encodings of form `weighted sum of lits <> rhs`
71pub trait Encode {
72    /// Get the sum of weights in the encoding
73    fn weight_sum(&self) -> usize;
74    /// Gets the next higher value possible to be achieved by the weighted sum.
75    /// Might simply return `val + 1` if no stronger value can be inferred.
76    fn next_higher(&self, val: usize) -> usize {
77        val + 1
78    }
79    /// Gets the next lower value possible to be achieved by the weighted sum.
80    /// Might simply return `val - 1` if no stronger value can be inferred.
81    fn next_lower(&self, val: usize) -> usize {
82        val - 1
83    }
84}
85
86/// Trait for pseudo-boolean encodings that allow upper bounding of the form `sum
87/// of lits <= ub`
88pub trait BoundUpper: Encode {
89    /// Lazily builds the pseudo-boolean encoding to enable upper bounds within
90    /// a given range. `var_manager` is the variable manager to use for tracking
91    /// new variables. A specific encoding might ignore the upper or lower end
92    /// of the range.
93    ///
94    /// # Errors
95    ///
96    /// If the clause collector runs out of memory.
97    fn encode_ub<Col, R>(
98        &mut self,
99        range: R,
100        collector: &mut Col,
101        var_manager: &mut dyn ManageVars,
102    ) -> Result<(), crate::OutOfMemory>
103    where
104        Col: CollectClauses,
105        R: RangeBounds<usize>;
106    /// Returns assumptions/units for enforcing an upper bound (`weighted sum of
107    /// lits <= ub`). Make sure that [`BoundUpper::encode_ub`] has been called
108    /// adequately and nothing has been called afterwards.
109    ///
110    /// # Errors
111    ///
112    /// If [`BoundUpper::encode_ub`] has not been called adequately, or `lb` is larger than the
113    /// sum of weights in the encoding.
114    fn enforce_ub(&self, ub: usize) -> Result<Vec<Lit>, EnforceError>;
115    /// Encodes an upper bound pseudo-boolean constraint to CNF
116    ///
117    /// # Errors
118    ///
119    /// If the constraint is unsatisfiable or the collector runs out of memory.
120    fn encode_ub_constr<Col>(
121        constr: PbUbConstr,
122        collector: &mut Col,
123        var_manager: &mut dyn ManageVars,
124    ) -> Result<(), ConstraintEncodingError>
125    where
126        Col: CollectClauses,
127        Self: FromIterator<(Lit, usize)> + Sized,
128    {
129        let (lits, ub) = constr.decompose();
130        if ub < 0 {
131            return Err(ConstraintEncodingError::Unsat);
132        }
133        let ub = ub.unsigned_abs();
134        let mut enc = Self::from_iter(lits);
135        enc.encode_ub(ub..=ub, collector, var_manager)?;
136        collector.extend_clauses(
137            enc.enforce_ub(ub)
138                .unwrap()
139                .into_iter()
140                .map(|unit| clause![unit]),
141        )?;
142        Ok(())
143    }
144    /// Gets the next smaller upper bound value that can be _easily_ encoded. This
145    /// is used for coarse convergence, e.g., with the [`DynamicPolyWatchdog`]
146    /// encoding.
147    fn coarse_ub(&self, ub: usize) -> usize {
148        ub
149    }
150}
151
152/// Trait for pseudo-boolean encodings that allow lower bounding of the form `sum
153/// of lits >= lb`
154pub trait BoundLower: Encode {
155    /// Lazily builds the pseudo-boolean encoding to enable lower bounds in a
156    /// given range. `var_manager` is the variable manager to use for tracking
157    /// new variables. A specific encoding might ignore the upper or lower end
158    /// of the range.
159    ///
160    /// # Errors
161    ///
162    /// If the collector runs out of memory.
163    fn encode_lb<Col, R>(
164        &mut self,
165        range: R,
166        collector: &mut Col,
167        var_manager: &mut dyn ManageVars,
168    ) -> Result<(), crate::OutOfMemory>
169    where
170        Col: CollectClauses,
171        R: RangeBounds<usize>;
172    /// Returns assumptions/units for enforcing a lower bound (`sum of lits >=
173    /// lb`). Make sure that [`BoundLower::encode_lb`] has been called
174    /// adequately and nothing has been added afterwards.
175    ///
176    /// # Errors
177    ///
178    /// If [`BoundLower::encode_lb`] has not been called adequately, or `lb` is larger than the
179    /// sum of weights in the encoding.
180    fn enforce_lb(&self, lb: usize) -> Result<Vec<Lit>, EnforceError>;
181    /// Encodes a lower bound pseudo-boolean constraint to CNF
182    ///
183    /// # Errors
184    ///
185    /// If the constraint is unsatisfiable or the collector runs out of memory.
186    fn encode_lb_constr<Col>(
187        constr: PbLbConstr,
188        collector: &mut Col,
189        var_manager: &mut dyn ManageVars,
190    ) -> Result<(), ConstraintEncodingError>
191    where
192        Col: CollectClauses,
193        Self: FromIterator<(Lit, usize)> + Sized,
194    {
195        let (lits, lb) = constr.decompose();
196        let lb = if lb < 0 {
197            return Ok(()); // tautology
198        } else {
199            lb.unsigned_abs()
200        };
201        if lb > lits.iter().fold(0, |sum, (_, w)| sum + *w) {
202            return Err(ConstraintEncodingError::Unsat);
203        }
204        let mut enc = Self::from_iter(lits);
205        enc.encode_lb(lb..=lb, collector, var_manager)?;
206        collector.extend_clauses(
207            enc.enforce_lb(lb)
208                .unwrap()
209                .into_iter()
210                .map(|unit| clause![unit]),
211        )?;
212        Ok(())
213    }
214    /// Gets the next greater lower bound value that can be _easily_ encoded. This
215    /// is used for coarse convergence.
216    fn coarse_lb(&self, lb: usize) -> usize {
217        lb
218    }
219}
220
221/// Trait for pseudo-boolean encodings that allow upper and lower bounding
222pub trait BoundBoth: BoundUpper + BoundLower {
223    /// Lazily builds the pseudo-boolean encoding to enable both bounds in a
224    /// given range. `var_manager` is the variable manager to use for tracking
225    /// new variables. A specific encoding might ignore the upper or lower end
226    /// of the range.
227    ///
228    /// # Errors
229    ///
230    /// If the collector runs out of memory.
231    fn encode_both<Col, R>(
232        &mut self,
233        range: R,
234        collector: &mut Col,
235        var_manager: &mut dyn ManageVars,
236    ) -> Result<(), crate::OutOfMemory>
237    where
238        Col: CollectClauses,
239        R: RangeBounds<usize> + Clone,
240    {
241        self.encode_ub(range.clone(), collector, var_manager)?;
242        self.encode_lb(range, collector, var_manager)?;
243        Ok(())
244    }
245    /// Returns assumptions for enforcing an equality (`sum of lits = b`) or an
246    /// error if the encoding does not support one of the two required bound
247    /// types. Make sure the adequate `encode_x` methods have been called before
248    /// this method and nothing has been added afterwards.
249    ///
250    /// # Errors
251    ///
252    /// If not adequately encoded, or `b` is larger than the sum of weights in the encoding.
253    fn enforce_eq(&self, b: usize) -> Result<Vec<Lit>, EnforceError> {
254        let mut assumps = self.enforce_ub(b)?;
255        assumps.extend(self.enforce_lb(b)?);
256        Ok(assumps)
257    }
258    /// Encodes an equality pseudo-boolean constraint to CNF
259    ///
260    /// # Errors
261    ///
262    /// If the constraint is unsatisfiable or the collector runs out of memory.
263    fn encode_eq_constr<Col>(
264        constr: PbEqConstr,
265        collector: &mut Col,
266        var_manager: &mut dyn ManageVars,
267    ) -> Result<(), ConstraintEncodingError>
268    where
269        Col: CollectClauses,
270        Self: FromIterator<(Lit, usize)> + Sized,
271    {
272        let (lits, b) = constr.decompose();
273        if b < 0 {
274            return Err(ConstraintEncodingError::Unsat);
275        }
276        let b = b.unsigned_abs();
277        if b > lits.iter().fold(0, |sum, (_, w)| sum + *w) {
278            return Err(ConstraintEncodingError::Unsat);
279        }
280        let mut enc = Self::from_iter(lits);
281        enc.encode_both(b..=b, collector, var_manager)?;
282        collector.extend_clauses(
283            enc.enforce_eq(b)
284                .unwrap()
285                .into_iter()
286                .map(|unit| clause![unit]),
287        )?;
288        Ok(())
289    }
290    /// Encodes any pseudo-boolean constraint to CNF
291    ///
292    /// # Errors
293    ///
294    /// If the constraint is unsatisfiable or the collector runs out of memory.
295    fn encode_constr<Col>(
296        constr: PbConstraint,
297        collector: &mut Col,
298        var_manager: &mut dyn ManageVars,
299    ) -> Result<(), ConstraintEncodingError>
300    where
301        Col: CollectClauses,
302        Self: FromIterator<(Lit, usize)> + Sized,
303    {
304        match constr {
305            PbConstraint::Ub(constr) => Self::encode_ub_constr(constr, collector, var_manager),
306            PbConstraint::Lb(constr) => Self::encode_lb_constr(constr, collector, var_manager),
307            PbConstraint::Eq(constr) => Self::encode_eq_constr(constr, collector, var_manager),
308        }
309    }
310}
311
312/// Trait for all pseudo-boolean encodings of form `sum of lits <> rhs`
313pub trait EncodeIncremental: Encode {
314    /// Reserves all variables this encoding might need
315    fn reserve(&mut self, var_manager: &mut dyn ManageVars);
316}
317
318/// Trait for incremental pseudo-boolean encodings that allow upper bounding of the
319/// form `sum of lits <= ub`
320pub trait BoundUpperIncremental: BoundUpper + EncodeIncremental {
321    /// Lazily builds the _change in_ pseudo-boolean encoding to enable upper
322    /// bounds from within the range. A change might be added literals or
323    /// changed bounds. `var_manager` is the variable manager to use for
324    /// tracking new variables. A specific encoding might ignore the lower or
325    /// upper end of the range.
326    ///
327    /// # Errors
328    ///
329    /// If the clause collector runs out of memory.
330    fn encode_ub_change<Col, R>(
331        &mut self,
332        range: R,
333        collector: &mut Col,
334        var_manager: &mut dyn ManageVars,
335    ) -> Result<(), crate::OutOfMemory>
336    where
337        Col: CollectClauses,
338        R: RangeBounds<usize>;
339}
340
341/// Trait for incremental pseudo-boolean encodings that allow lower bounding of the
342/// form `sum of lits >= lb`
343pub trait BoundLowerIncremental: BoundLower + EncodeIncremental {
344    /// Lazily builds the _change in_ pseudo-boolean encoding to enable lower
345    /// bounds within the range. `var_manager` is the variable manager to use
346    /// for tracking new variables. A specific encoding might ignore the lower
347    /// or upper end of the range.
348    ///
349    /// # Errors
350    ///
351    /// If the clause collector runs out of memory.
352    fn encode_lb_change<Col, R>(
353        &mut self,
354        range: R,
355        collector: &mut Col,
356        var_manager: &mut dyn ManageVars,
357    ) -> Result<(), crate::OutOfMemory>
358    where
359        Col: CollectClauses,
360        R: RangeBounds<usize>;
361}
362
363/// Trait for incremental pseudo-boolean encodings that allow upper and lower bounding
364pub trait BoundBothIncremental: BoundUpperIncremental + BoundLowerIncremental + BoundBoth {
365    /// Lazily builds the _change in_ pseudo-boolean encoding to enable both
366    /// bounds from `min_b` to `max_b`. `var_manager` is the variable manager to
367    /// use for tracking new variables. A specific encoding might ignore the
368    /// lower or upper end of the range.
369    ///
370    /// # Errors
371    ///
372    /// If the clause collector runs out of memory.
373    fn encode_both_change<Col, R>(
374        &mut self,
375        range: R,
376        collector: &mut Col,
377        var_manager: &mut dyn ManageVars,
378    ) -> Result<(), crate::OutOfMemory>
379    where
380        Col: CollectClauses,
381        R: RangeBounds<usize> + Clone,
382    {
383        self.encode_ub_change(range.clone(), collector, var_manager)?;
384        self.encode_lb_change(range, collector, var_manager)?;
385        Ok(())
386    }
387}
388
389/// The default upper bound encoding. For now this is a [`GeneralizedTotalizer`].
390pub type DefUpperBounding = GeneralizedTotalizer;
391/// The default lower bound encoding. For now this is a [`InvertedGeneralizedTotalizer`].
392pub type DefLowerBounding = InvertedGeneralizedTotalizer;
393/// The default encoding for both bounds. For now this is a [`DoubleGeneralizedTotalizer`].
394pub type DefBothBounding = DoubleGeneralizedTotalizer;
395/// The default incremental upper bound encoding. For now this is a [`GeneralizedTotalizer`].
396pub type DefIncUpperBounding = GeneralizedTotalizer;
397/// The default incremental lower bound encoding. For now this is a [`InvertedGeneralizedTotalizer`].
398pub type DefIncLowerBounding = InvertedGeneralizedTotalizer;
399/// The default incremental encoding for both bounds. For now this is a [`DoubleGeneralizedTotalizer`].
400pub type DefIncBothBounding = DoubleGeneralizedTotalizer;
401
402/// Constructs a default upper bounding pseudo-boolean encoding.
403#[must_use]
404pub fn new_default_ub() -> impl BoundUpper + Extend<(Lit, usize)> {
405    DefUpperBounding::default()
406}
407
408/// Constructs a default lower bounding pseudo-boolean encoding.
409#[must_use]
410pub fn new_default_lb() -> impl BoundLower + Extend<(Lit, usize)> {
411    DefLowerBounding::default()
412}
413
414/// Constructs a default double bounding pseudo-boolean encoding.
415#[must_use]
416pub fn new_default_both() -> impl BoundBoth + Extend<(Lit, usize)> {
417    DefBothBounding::default()
418}
419
420/// Constructs a default incremental upper bounding pseudo-boolean encoding.
421#[must_use]
422pub fn new_default_inc_ub() -> impl BoundUpperIncremental + Extend<(Lit, usize)> {
423    DefIncUpperBounding::default()
424}
425
426/// Constructs a default incremental lower bounding pseudo-boolean encoding.
427#[must_use]
428pub fn new_default_inc_lb() -> impl BoundLower + Extend<(Lit, usize)> {
429    DefIncLowerBounding::default()
430}
431
432/// Constructs a default incremental double bounding pseudo-boolean encoding.
433#[must_use]
434pub fn new_default_inc_both() -> impl BoundBoth + Extend<(Lit, usize)> {
435    DefIncBothBounding::default()
436}
437
438/// A default encoder for any pseudo-boolean constraint. This uses a
439/// [`DefBothBounding`] to encode true pseudo-boolean constraints and
440/// [`card::default_encode_cardinality_constraint`] for cardinality constraints.
441///
442/// # Errors
443///
444/// If the clause collector runs out of memory.
445pub fn default_encode_pb_constraint<Col: CollectClauses>(
446    constr: PbConstraint,
447    collector: &mut Col,
448    var_manager: &mut dyn ManageVars,
449) -> Result<(), crate::OutOfMemory> {
450    encode_pb_constraint::<DefBothBounding, Col>(constr, collector, var_manager)
451}
452
453/// An encoder for any pseudo-boolean constraint with an encoding of choice
454///
455/// # Errors
456///
457/// If the clause collector runs out of memory.
458pub fn encode_pb_constraint<PBE: BoundBoth + FromIterator<(Lit, usize)>, Col: CollectClauses>(
459    constr: PbConstraint,
460    collector: &mut Col,
461    var_manager: &mut dyn ManageVars,
462) -> Result<(), crate::OutOfMemory> {
463    if constr.is_tautology() {
464        return Ok(());
465    }
466    if constr.is_unsat() {
467        return collector.add_clause(Clause::new());
468    }
469    if constr.is_positive_assignment() {
470        return collector
471            .extend_clauses(constr.into_lits().into_iter().map(|(lit, _)| clause![lit]));
472    }
473    if constr.is_negative_assignment() {
474        return collector
475            .extend_clauses(constr.into_lits().into_iter().map(|(lit, _)| clause![!lit]));
476    }
477    if constr.is_clause() {
478        return collector.add_clause(unreachable_err!(constr.into_clause()));
479    }
480    if constr.is_card() {
481        let card = unreachable_err!(constr.into_card_constr());
482        return card::default_encode_cardinality_constraint(card, collector, var_manager);
483    }
484    match PBE::encode_constr(constr, collector, var_manager) {
485        Ok(()) => Ok(()),
486        Err(ConstraintEncodingError::OutOfMemory(err)) => Err(err),
487        Err(ConstraintEncodingError::Unsat) => unreachable!(),
488    }
489}
490
491fn prepare_ub_range<Enc: Encode, R: RangeBounds<usize>>(enc: &Enc, range: R) -> Range<usize> {
492    (match range.start_bound() {
493        Bound::Included(b) => *b,
494        Bound::Excluded(b) => b + 1,
495        Bound::Unbounded => 0,
496    })..match range.end_bound() {
497        Bound::Included(b) => cmp::min(b + 1, enc.weight_sum()),
498        Bound::Excluded(b) => cmp::min(*b, enc.weight_sum()),
499        Bound::Unbounded => enc.weight_sum(),
500    }
501}
502
503fn prepare_lb_range<Enc: Encode, R: RangeBounds<usize>>(enc: &Enc, range: R) -> Range<usize> {
504    (match range.start_bound() {
505        Bound::Included(b) => cmp::max(*b, 1),
506        Bound::Excluded(b) => cmp::max(b + 1, 1),
507        Bound::Unbounded => 1,
508    })..match range.end_bound() {
509        Bound::Included(b) => cmp::min(b + 1, enc.weight_sum() + 1),
510        Bound::Excluded(b) => cmp::min(*b, enc.weight_sum() + 1),
511        Bound::Unbounded => enc.weight_sum() + 1,
512    }
513}
514
515fn prepare_both_range<Enc: Encode, R: RangeBounds<usize>>(enc: &Enc, range: R) -> Range<usize> {
516    (match range.start_bound() {
517        Bound::Included(b) => *b,
518        Bound::Excluded(b) => b + 1,
519        Bound::Unbounded => 1,
520    })..match range.end_bound() {
521        Bound::Included(b) => cmp::min(b + 1, enc.weight_sum() + 1),
522        Bound::Excluded(b) => cmp::min(*b, enc.weight_sum() + 1),
523        Bound::Unbounded => enc.weight_sum() + 1,
524    }
525}