sat_solver/sat/
clause.rs

1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
2
3//! Contains details of a clause, a fundamental component in SAT solvers.
4//!
5//! A clause is a disjunction of literals (e.g. `x1 OR !x2 OR x3`).
6//! This module defines the `Clause` struct, which stores literals and associated metadata
7//! relevant for Conflict-Driven Clause Learning (CDCL) SAT solvers, such as
8//! Literal Blocks Distance (LBD) and activity scores for clause deletion strategies.
9
10use crate::sat::clause_storage::LiteralStorage;
11use crate::sat::literal::{Literal, PackedLiteral};
12use crate::sat::trail::Trail;
13use bit_vec::BitVec;
14use core::ops::{Index, IndexMut};
15use itertools::Itertools;
16use ordered_float::OrderedFloat;
17use rustc_hash::{FxBuildHasher, FxHashSet};
18use smallvec::SmallVec;
19use std::hash::Hash;
20use std::marker::PhantomData;
21
22/// Represents a clause in a SAT formula.
23///
24/// A clause is a set of literals, typically interpreted as a disjunction.
25/// For example, the clause `{L1, L2, L3}` represents `L1 OR L2 OR L3`.
26///
27/// # Type Parameters
28///
29/// * `L`: The type of literal stored in the clause. Defaults to `PackedLiteral`.
30///   Must implement the `Literal` trait.
31/// * `S`: The storage type for the literals. Defaults to `SmallVec<[L; 8]>`.
32///   Must implement the `LiteralStorage<L>` trait, providing storage for literals.
33#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
34pub struct Clause<L: Literal = PackedLiteral, S: LiteralStorage<L> = SmallVec<[L; 8]>> {
35    /// The collection of literals forming the clause.
36    /// The specific storage (e.g. `Vec`, `SmallVec`) is determined by the `S` type parameter.
37    pub literals: S,
38    /// Literal Blocks Distance (LBD) of the clause.
39    /// LBD is a measure of the "locality" of a learnt clause in terms of decision levels.
40    /// Lower LBD often indicates higher quality learnt clauses. Calculated for learnt clauses.
41    pub lbd: u32,
42    /// Flag indicating if the clause has been marked for deletion.
43    /// Deleted clauses are removed by the solver during database cleaning.
44    pub deleted: bool,
45    /// Flag indicating if the clause was learnt during conflict analysis.
46    /// Original clauses (from the input problem) are not marked as learnt.
47    pub is_learnt: bool,
48    /// Activity score of the clause, used in clause deletion heuristics (e.g. VSIDS-like).
49    /// Higher activity suggests the clause has been more recently involved in conflicts or propagations.
50    pub activity: OrderedFloat<f64>,
51    /// `PhantomData` to ensure handling of the generic type `L`.
52    data: PhantomData<*const L>,
53}
54
55impl<L: Literal, S: LiteralStorage<L>> AsRef<[L]> for Clause<L, S> {
56    /// Returns a slice containing the literals in the clause.
57    /// This allows the clause to be treated as a slice for read-only operations.
58    fn as_ref(&self) -> &[L] {
59        self.literals.as_ref()
60    }
61}
62
63impl<L: Literal, S: LiteralStorage<L>> FromIterator<L> for Clause<L, S> {
64    /// Creates a new clause from an iterator of literals.
65    ///
66    /// The literals from the iterator are collected into the `literals` field.
67    ///
68    /// Literals are deduplicated during creation using `itertools::Itertools::unique`.
69    /// For example, `[L1, L1, L2]` becomes `{L1, L2}`.
70    ///
71    /// The clause is initialised as not learnt, not deleted, with LBD 0 and activity 0.0.
72    /// Other fields (`lbd`, `deleted`, `is_learnt`, `activity`) are initialised to default values.
73    fn from_iter<I: IntoIterator<Item = L>>(iter: I) -> Self {
74        Self {
75            literals: iter.into_iter().unique().collect(),
76            lbd: 0,
77            deleted: false,
78            is_learnt: false,
79            activity: OrderedFloat::from(0.0),
80            data: PhantomData,
81        }
82    }
83}
84
85impl<L: Literal, S: LiteralStorage<L>> Clause<L, S> {
86    /// Creates a new clause from a slice of literals.
87    ///
88    /// # Arguments
89    ///
90    /// * `literals_slice`: A slice of literals to form the clause.
91    #[must_use]
92    pub fn new(literals_slice: &[L]) -> Self {
93        literals_slice.iter().copied().collect()
94    }
95
96    /// Performs resolution between this clause and another clause on a pivot literal.
97    ///
98    /// The resolution rule is: `(A V x) AND (B V !x) => (A V B)`.
99    /// In this context, `self` represents `(A V x)` and `other` represents `(B V !x)`.
100    /// The `pivot` argument is the literal `x`.
101    ///
102    /// - If `pivot` (i.e. `x`) is not found in `self.literals`, or `pivot.negated()` (i.e. `!x`)
103    ///   is not found in `other.literals`, resolution is not applicable with the given pivot.
104    ///   In this case, a clone of `self` is returned.
105    /// - The resolvent clause is formed by taking the union of literals from `self` (excluding `pivot`)
106    ///   and `other` (excluding `pivot.negated()`), with duplicates removed.
107    /// - If the resulting resolvent is a tautology (e.g. contains both `y` and `!y`),
108    ///   a default (typically empty and non-learnt) clause is returned. A tautological resolvent
109    ///   is logically true and provides no new constraints.
110    ///
111    /// # Arguments
112    ///
113    /// * `other`: The other clause to resolve with.
114    /// * `pivot`: The literal `x` to resolve upon. `self` should contain `pivot`, and
115    ///   `other` should contain `pivot.negated()`.
116    ///
117    /// # Returns
118    ///
119    /// The resolved clause. This is a new `Clause` instance.
120    #[must_use]
121    pub fn resolve(&self, other: &Self, pivot: L) -> Self {
122        if !self.literals.iter().contains(&pivot)
123            || !other.literals.iter().contains(&pivot.negated())
124        {
125            return self.clone();
126        }
127
128        let mut resolved_literals: FxHashSet<L> = FxHashSet::default();
129
130        for &lit in self.literals.iter() {
131            if lit != pivot {
132                resolved_literals.insert(lit);
133            }
134        }
135        for &lit in other.literals.iter() {
136            if lit != pivot.negated() {
137                resolved_literals.insert(lit);
138            }
139        }
140
141        let resolved_clause = resolved_literals.into_iter().collect::<Self>();
142
143        if resolved_clause.is_tautology() {
144            Self::default()
145        } else {
146            resolved_clause
147        }
148    }
149
150    /// Performs binary resolution: resolves `self` with a binary clause `binary`.
151    ///
152    /// A binary clause contains exactly two literals. Let `binary` be `(L1 V L2)`.
153    /// - If `self` contains `!L1`, the result is `(self - {!L1}) V {L2}`.
154    /// - If `self` contains `!L2`, the result is `(self - {!L2}) V {L1}`.
155    ///   The literals in the resulting clause are unique.
156    ///   If the resulting clause is a tautology, `None` is returned.
157    ///
158    /// # Arguments
159    ///
160    /// * `binary`: A binary clause (must have exactly two literals).
161    ///
162    /// # Returns
163    ///
164    /// `Some(resolved_clause)` if resolution is possible and the result is not a tautology.
165    /// `None` if `binary` is not a binary clause, resolution is not applicable (no matching negated literal found),
166    /// or the result of resolution is a tautology.
167    ///
168    /// # Panics
169    /// The `unwrap_or_else` for `position` might lead to unexpected behavior if the literal to be removed
170    /// is not found (which shouldn't happen if `lit` was found in `self.literals` and `new_clause` is a clone).
171    /// If `position` returns `None`, `remove_literal` would attempt to remove the last element of `new_clause.literals`.
172    #[must_use]
173    pub fn resolve_binary(&self, binary: &Self) -> Option<Self> {
174        if binary.len() != 2 {
175            return None;
176        }
177
178        let bin_lit1 = binary.literals[0];
179        let bin_lit2 = binary.literals[1];
180
181        for &lit_in_self in self.literals.iter() {
182            if lit_in_self == bin_lit1.negated() {
183                let mut new_clause = self.clone();
184                let pos = new_clause
185                    .literals
186                    .iter()
187                    .position(|&x| x == lit_in_self)
188                    .unwrap_or_else(|| new_clause.literals.len().saturating_sub(1));
189                if !new_clause.literals.is_empty() {
190                    new_clause.remove_literal(pos);
191                }
192                new_clause.push(bin_lit2);
193
194                return if new_clause.is_tautology() {
195                    None
196                } else {
197                    Some(new_clause)
198                };
199            } else if lit_in_self == bin_lit2.negated() {
200                let mut new_clause = self.clone();
201                let pos = new_clause
202                    .literals
203                    .iter()
204                    .position(|&x| x == lit_in_self)
205                    .unwrap_or_else(|| new_clause.literals.len().saturating_sub(1));
206                if !new_clause.literals.is_empty() {
207                    new_clause.remove_literal(pos);
208                }
209                new_clause.push(bin_lit1);
210
211                return if new_clause.is_tautology() {
212                    None
213                } else {
214                    Some(new_clause)
215                };
216            }
217        }
218
219        None
220    }
221
222    /// Adds a literal to the clause, if it is not already present.
223    ///
224    /// # Arguments
225    ///
226    /// * `literal`: The literal to add.
227    pub fn push(&mut self, literal: L) {
228        if !self.literals.iter().contains(&literal) {
229            self.literals.push(literal);
230        }
231    }
232
233    /// Checks if the clause is a tautology.
234    ///
235    /// A clause is a tautology if it contains both a literal and its negation
236    /// (e.g. `x OR !x`). Such clauses are always true and not useful
237    ///
238    /// # Returns
239    ///
240    /// `true` if the clause is a tautology, `false` otherwise.
241    #[must_use]
242    pub fn is_tautology(&self) -> bool {
243        let mut set = FxHashSet::with_capacity_and_hasher(self.len(), FxBuildHasher);
244
245        for lit_ref in self.literals.iter() {
246            let lit = *lit_ref;
247            if set.contains(&lit.negated()) {
248                return true;
249            }
250            set.insert(lit);
251        }
252        false
253    }
254
255    /// Returns the number of literals in the clause.
256    #[must_use]
257    pub fn len(&self) -> usize {
258        self.literals.len()
259    }
260
261    /// Returns an iterator over the literals in the clause.
262    pub fn iter(&self) -> impl Iterator<Item = &L> {
263        self.literals.iter()
264    }
265
266    /// Returns a mutable iterator over the literals in the clause.
267    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut L> {
268        self.literals.iter_mut()
269    }
270
271    /// Swaps two literals in the clause by their indices.
272    ///
273    /// # Arguments
274    ///
275    /// * `i`: The index of the first literal.
276    /// * `j`: The index of the second literal.
277    ///
278    /// # Panics
279    ///
280    /// Panics if `i` or `j` are out of bounds for `self.literals`.
281    pub fn swap(&mut self, i: usize, j: usize) {
282        self.literals.swap(i, j);
283    }
284
285    /// Checks if the clause is a unit clause.
286    ///
287    /// A unit clause is a clause with exactly one literal.
288    ///
289    /// # Returns
290    ///
291    /// `true` if the clause has exactly one literal, `false` otherwise.
292    #[must_use]
293    pub fn is_unit(&self) -> bool {
294        self.len() == 1
295    }
296
297    /// Checks if the clause is empty.
298    ///
299    /// An empty clause (containing no literals) represents a contradiction (logical false).
300    ///
301    /// # Returns
302    ///
303    /// `true` if the clause has no literals, `false` otherwise.
304    #[must_use]
305    pub fn is_empty(&self) -> bool {
306        self.literals.is_empty()
307    }
308
309    /// Checks if the clause is marked as deleted.
310    #[must_use]
311    pub const fn is_deleted(&self) -> bool {
312        self.deleted
313    }
314
315    /// Marks the clause as deleted.
316    ///
317    /// This only sets the `deleted` flag to `true`. It does not remove the clause
318    pub const fn delete(&mut self) {
319        self.deleted = true;
320    }
321
322    /// Removes a literal from the clause at the given index.
323    ///
324    /// This method uses `swap_remove` for efficiency, which means the order of
325    /// the remaining literals in the clause may change (the last literal is swapped
326    /// into the `idx` position, and then the length is reduced).
327    ///
328    /// # Arguments
329    ///
330    /// * `idx`: The index of the literal to remove.
331    ///
332    /// # Panics
333    ///
334    /// Panics if `idx` is out of bounds for `self.literals`.
335    pub fn remove_literal(&mut self, idx: usize) {
336        self.literals.swap_remove(idx);
337    }
338
339    /// Calculates and updates the Literal Block Distance (LBD) of the clause.
340    ///
341    /// LBD is defined as the number of distinct decision levels (excluding level 0)
342    /// of the variables in the clause's literals. This metric is used
343    /// to estimate the "quality" or "usefulness" of learnt clauses
344    ///
345    /// Special LBD rules applied:
346    /// - If the clause is empty, LBD is 0.
347    /// - If all literals are at decision level 0 (or unassigned and treated as level 0),
348    ///   and the clause is not empty, LBD is 1.
349    /// - For learnt, non-empty clauses, LBD is ensured to be at least 1.
350    ///
351    /// # Arguments
352    ///
353    /// * `trail`: The solver's assignment trail, used to find the decision level
354    ///   of each literal's variable.
355    ///
356    /// # Panics
357    /// Behavior of `BitVec` indexing with `level` (a `u32`) depends on `usize` size.
358    pub fn calculate_lbd(&mut self, trail: &Trail<L, S>) {
359        if self.is_empty() {
360            self.lbd = 0;
361            return;
362        }
363
364        let max_level_in_clause = self
365            .literals
366            .iter()
367            .map(|lit| trail.level(lit.variable()))
368            .max()
369            .unwrap_or(0);
370
371        let mut levels_seen = BitVec::from_elem(max_level_in_clause.wrapping_add(1), false);
372        let mut count = 0u32;
373
374        for &lit in self.literals.iter() {
375            let level = trail.level(lit.variable());
376            if level > 0 {
377                let level_idx = level;
378                if !levels_seen.get(level_idx).unwrap_or(true) {
379                    levels_seen.set(level_idx, true);
380                    count = count.wrapping_add(1);
381                }
382            }
383        }
384
385        if count == 0 {
386            self.lbd = if !self.is_empty()
387                && self.literals.iter().any(|l| trail.level(l.variable()) == 0)
388            {
389                1
390            } else {
391                u32::from(!self.is_empty())
392            };
393        } else {
394            self.lbd = count;
395        }
396
397        if self.is_learnt && !self.is_empty() && self.lbd == 0 {
398            self.lbd = 1;
399        }
400    }
401
402    /// Converts this clause into a clause with different literal or storage types.
403    ///
404    /// This is useful for benchmarking primarily
405    /// The literals are converted one by one using `T::new(original_lit.variable(), original_lit.polarity())`.
406    /// Metadata like LBD, deleted status, learnt status, and activity are copied.
407    ///
408    /// # Type Parameters
409    ///
410    /// * `T`: The target `Literal` type for the new clause.
411    /// * `U`: The target `LiteralStorage<T>` type for the new clause.
412    ///
413    /// # Returns
414    ///
415    /// A new `Clause<T, U>` with equivalent literals and copied metadata.
416    pub fn convert<T: Literal, U: LiteralStorage<T>>(&self) -> Clause<T, U> {
417        let literals_as_t: Vec<T> = self
418            .literals
419            .iter()
420            .map(|lit| T::new(lit.variable(), lit.polarity()))
421            .collect_vec();
422
423        let mut new_clause = Clause::<T, U>::new(&literals_as_t);
424        new_clause.lbd = self.lbd;
425        new_clause.deleted = self.deleted;
426        new_clause.is_learnt = self.is_learnt;
427        new_clause.activity = self.activity;
428        new_clause
429    }
430
431    /// Increases the activity score of the clause by a specified increment.
432    ///
433    /// This is part of VSIDS-like (Variable State Independent Decaying Sum) heuristics,
434    /// where clauses involved in recent conflict analysis get their activity "bumped",
435    /// making them less likely to be deleted.
436    ///
437    /// # Arguments
438    ///
439    /// * `increment`: The positive amount to add to the clause's activity score.
440    pub fn bump_activity(&mut self, increment: f64) {
441        self.activity += increment;
442    }
443
444    /// Decays the activity score of the clause by a multiplicative factor.
445    ///
446    /// # Arguments
447    ///
448    /// * `factor`: The factor to multiply the activity score by (typically between 0 and 1).
449    pub fn decay_activity(&mut self, factor: f64) {
450        self.activity *= factor;
451    }
452
453    /// Returns the raw floating-point activity score of the clause.
454    #[must_use]
455    pub const fn activity(&self) -> f64 {
456        self.activity.0
457    }
458}
459
460impl<T: Literal, S: LiteralStorage<T>> Index<usize> for Clause<T, S> {
461    type Output = T;
462
463    /// Accesses the literal at the given `index` within the clause.
464    ///
465    /// # Panics
466    ///
467    /// Panics if `index` is out of bounds for `self.literals`.
468    ///
469    /// # Safety
470    ///
471    /// This implementation uses `get_unchecked` for performance
472    fn index(&self, index: usize) -> &Self::Output {
473        // Safety: Caller must ensure `index` is within bounds `[0, self.literals.len())`.
474        // This is fine is used within the correct context.
475        unsafe { self.literals.get_unchecked(index) }
476    }
477}
478
479impl<T: Literal, S: LiteralStorage<T>> IndexMut<usize> for Clause<T, S> {
480    /// Mutably accesses the literal at the given `index` within the clause.
481    ///
482    /// # Panics
483    ///
484    /// Panics if `index` is out of bounds for `self.literals`.
485    ///
486    /// # Safety
487    ///
488    /// This implementation uses `get_unchecked_mut` for performance.
489    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
490        // Safety: Caller must ensure `index` is within bounds `[0, self.literals.len())`.
491        // This is fine is used within the correct context.
492        unsafe { self.literals.get_unchecked_mut(index) }
493    }
494}
495
496impl<T: Literal, S: LiteralStorage<T>> From<&Clause<T, S>> for Vec<T> {
497    /// Converts a reference to a clause into a `Vec` of its literals.
498    ///
499    /// The literals are copied from the clause's internal storage into a new `Vec<T>`.
500    fn from(clause: &Clause<T, S>) -> Self {
501        clause.literals.iter().copied().collect()
502    }
503}
504
505impl<L: Literal, S: LiteralStorage<L>> From<Vec<i32>> for Clause<L, S> {
506    /// Creates a clause from a `Vec<i32>`, where integers represent literals
507    /// in DIMACS format (e.g. `1` for variable 1 true, `-2` for variable 2 false).
508    ///
509    /// This constructor uses `Clause::new`, so literals will be deduplicated.
510    /// Other fields (`lbd`, `deleted`, etc.) are initialised to defaults.
511    fn from(literals_dimacs: Vec<i32>) -> Self {
512        literals_dimacs.iter().copied().map(L::from_i32).collect()
513    }
514}
515
516impl<L: Literal, S: LiteralStorage<L>> From<Vec<L>> for Clause<L, S> {
517    /// Creates a clause directly from a `Vec<L>`.
518    fn from(literals_vec: Vec<L>) -> Self {
519        literals_vec.into_iter().collect()
520    }
521}
522
523impl<L: Literal, S: LiteralStorage<L>> From<&Vec<L>> for Clause<L, S> {
524    /// Creates a clause from a reference to a `Vec<L>`, cloning the literals.
525    ///
526    /// The literals from `literals_vec` are cloned to create the `literals` field.
527    fn from(literals_vec: &Vec<L>) -> Self {
528        literals_vec.iter().copied().collect()
529    }
530}
531
532impl<L: Literal, S: LiteralStorage<L>> FromIterator<i32> for Clause<L, S> {
533    /// Creates a clause from an iterator of `i32` (DIMACS literals).
534    ///
535    /// This is similar to `From<Vec<i32>>`. Literals are converted from DIMACS format.
536    /// See `From<Vec<i32>>` for more details on DIMACS conversion and variable indexing.
537    fn from_iter<I: IntoIterator<Item = i32>>(iter: I) -> Self {
538        iter.into_iter().map(L::from_i32).collect()
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    #[test]
547    fn test_new_from_i32_vec_and_len() {
548        let clause: Clause = Clause::from(vec![1, 2, 3]);
549        assert_eq!(clause.len(), 3, "Clause length should be 3");
550
551        let expected_lit = PackedLiteral::new(1_u32, true);
552        assert!(
553            clause.literals.iter().any(|&l| l == expected_lit),
554            "Clause should contain literal for var 1 positive"
555        );
556    }
557
558    #[test]
559    fn test_swap_no_assert() {
560        let mut clause: Clause = Clause::from(vec![1, 2, 3]);
561
562        if clause.len() == 3 {
563            let lit0_before = clause.literals[0];
564            let lit2_before = clause.literals[2];
565            clause.swap(0, 2);
566            assert_eq!(
567                clause.literals[0], lit2_before,
568                "Literal at index 0 should be the original literal from index 2"
569            );
570            assert_eq!(
571                clause.literals[2], lit0_before,
572                "Literal at index 2 should be the original literal from index 0"
573            );
574        } else {
575            panic!("Clause length was not 3 after creation from vec![1,2,3]");
576        }
577    }
578
579    #[test]
580    fn test_is_tautology() {
581        let tautology_clause: Clause = Clause::from(vec![1, -1]);
582        assert!(
583            tautology_clause.is_tautology(),
584            "Clause should be tautology"
585        );
586
587        let non_tautology_clause: Clause = Clause::from(vec![1, 2]);
588        assert!(
589            !non_tautology_clause.is_tautology(),
590            "Clause should not be tautology"
591        );
592    }
593
594    #[test]
595    fn test_resolve() {
596        let clause1: Clause = Clause::from(vec![1, 2]);
597        let clause2: Clause = Clause::from(vec![-1, 3]);
598        let pivot = PackedLiteral::new(1_u32, true);
599
600        let resolved_clause = clause1.resolve(&clause2, pivot);
601        assert_eq!(
602            resolved_clause.literals.len(),
603            2,
604            "Resolved clause should have 2 literals"
605        );
606        assert!(
607            resolved_clause
608                .literals
609                .iter()
610                .any(|&l| l == PackedLiteral::new(2_u32, true))
611        );
612        assert!(
613            resolved_clause
614                .literals
615                .iter()
616                .any(|&l| l == PackedLiteral::new(3_u32, true))
617        );
618    }
619
620    #[test]
621    fn test_is_unit() {
622        let unit_clause: Clause = Clause::from(vec![1]);
623        assert!(unit_clause.is_unit(), "Clause should be unit");
624
625        let non_unit_clause: Clause = Clause::from(vec![1, 2]);
626        assert!(!non_unit_clause.is_unit(), "Clause should not be unit");
627    }
628
629    #[test]
630    fn test_is_empty() {
631        let empty_clause: Clause = Clause::default();
632        assert!(empty_clause.is_empty(), "Clause should be empty");
633
634        let non_empty_clause: Clause = Clause::from(vec![1]);
635        assert!(!non_empty_clause.is_empty(), "Clause should not be empty");
636    }
637}