sat_solver/sat/
expr.rs

1//! Defines an arbitrary boolean expression type and functions for its manipulation.
2//!
3//! This module provides an `Expr` enum to represent boolean expressions involving
4//! variables, logical NOT, AND, OR, and constant boolean values (true/false).
5//! It includes helper methods for checking expression types, unwrapping values,
6//! and constructing complex expressions from slices.
7//!
8//! Furthermore, it implements several logical transformation functions:
9//! - `demorgans_laws`: Applies De Morgan's laws to push negations inwards.
10//! - `distributive_laws`: Applies distributive laws (e.g. AND over OR) to transform
11//!   expressions towards a conjunctive or disjunctive normal form.
12//! - `apply_laws`: A higher-level function that repeatedly applies De Morgan's and
13//!   distributive laws until the expression stabilizes, aiming to convert it into
14//!   a form suitable for CNF conversion (though full CNF conversion often requires
15//!   more, like Tseytin transformation for non-exponential results).
16
17#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
18
19use crate::sat::literal::Variable;
20
21/// Represents a boolean expression.
22///
23/// Expressions can be:
24/// - A `Var(Variable)`: A propositional variable.
25/// - A `Not(Box<Expr>)`: The logical negation of an expression.
26/// - An `And(Box<Expr>, Box<Expr>)`: The logical conjunction of two expressions.
27/// - An `Or(Box<Expr>, Box<Expr>)`: The logical disjunction of two expressions.
28/// - A `Val(bool)`: A constant boolean value (`true` or `false`).
29#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
30pub enum Expr {
31    /// A propositional variable, identified by `Variable`.
32    Var(Variable),
33    /// Logical negation of an inner expression.
34    Not(Box<Expr>),
35    /// Logical conjunction (AND) of two inner expressions.
36    And(Box<Expr>, Box<Expr>),
37    /// Logical disjunction (OR) of two inner expressions.
38    Or(Box<Expr>, Box<Expr>),
39    /// A constant boolean value (`true` or `false`).
40    Val(bool),
41}
42
43impl Expr {
44    /// Checks if the expression is a `Val` variant.
45    #[must_use]
46    pub const fn is_val(&self) -> bool {
47        matches!(self, Self::Val(_))
48    }
49
50    /// Checks if the expression is a `Var` variant.
51    #[must_use]
52    pub const fn is_var(&self) -> bool {
53        matches!(self, Self::Var(_))
54    }
55
56    /// Checks if the expression is a `Not` variant.
57    #[must_use]
58    pub const fn is_not(&self) -> bool {
59        matches!(self, Self::Not(_))
60    }
61
62    /// Checks if the expression is an `And` variant.
63    #[must_use]
64    pub const fn is_and(&self) -> bool {
65        matches!(self, Self::And(_, _))
66    }
67
68    /// Checks if the expression is an `Or` variant.
69    #[must_use]
70    pub const fn is_or(&self) -> bool {
71        matches!(self, Self::Or(_, _))
72    }
73
74    /// Checks if the expression is `Val(true)`.
75    #[must_use]
76    pub const fn is_true(&self) -> bool {
77        match self {
78            Self::Val(b) => *b,
79            _ => false,
80        }
81    }
82
83    /// Checks if the expression is `Val(false)`.
84    #[must_use]
85    pub const fn is_false(&self) -> bool {
86        match self {
87            Self::Val(b) => !*b,
88            _ => false,
89        }
90    }
91
92    /// Unwraps the boolean value if the expression is a `Val` variant.
93    ///
94    /// # Panics
95    ///
96    /// Panics if the expression is not `Expr::Val(_)`.
97    #[must_use]
98    pub fn unwrap_val(&self) -> bool {
99        match self {
100            Self::Val(b) => *b,
101            _ => panic!("Called unwrap_val on a non-Val expression: {self:?}"),
102        }
103    }
104
105    /// Unwraps the variable identifier if the expression is a `Var` variant.
106    ///
107    /// # Panics
108    ///
109    /// Panics if the expression is not `Expr::Var(_)`.
110    #[must_use]
111    pub fn unwrap_var(&self) -> Variable {
112        match self {
113            Self::Var(i) => *i,
114            _ => panic!("Called unwrap_var on a non-Var expression: {self:?}"),
115        }
116    }
117
118    /// Creates a new expression representing the disjunction (OR) of all expressions in the slice `e`.
119    ///
120    /// `e_1 OR e_2 OR ... OR e_n`.
121    /// The fold starts with `Expr::Val(false)` as the identity for OR (`x OR false == x`).
122    /// If `e` is empty, `Expr::Val(false)` is returned.
123    ///
124    /// # Arguments
125    ///
126    /// * `e`: A slice of `Expr` to be OR-ed together.
127    ///
128    /// # Returns
129    ///
130    /// A new `Expr` representing the OR of all expressions in `e`.
131    ///
132    /// # Panics
133    ///
134    /// Panics if `e` is empty, as the OR of no elements is not defined.
135    #[must_use]
136    pub fn ors(expressions: &[Self]) -> Self {
137        if expressions.is_empty() {
138            return Self::Val(false);
139        }
140        let mut iter = expressions.iter();
141        let first = iter.next().unwrap().clone();
142        iter.fold(first, |acc, x| Self::Or(Box::new(acc), Box::new(x.clone())))
143    }
144
145    /// Creates a new expression representing the conjunction (AND) of all expressions in the slice `e`.
146    ///
147    /// `e_1 AND e_2 AND ... AND e_n`.
148    /// The fold starts with `Expr::Val(true)` as the identity for AND (`x AND true == x`).
149    /// If `e` is empty, `Expr::Val(true)` is returned.
150    ///
151    /// # Arguments
152    ///
153    /// * `e`: A slice of `Expr` to be AND-ed together.
154    ///
155    /// # Returns
156    ///
157    /// A new `Expr` representing the AND of all expressions in `e`.
158    ///
159    /// # Panics
160    ///
161    /// Should not panic, but if `e` is empty, it returns `Expr::Val(true)`.
162    #[must_use]
163    pub fn ands(expressions: &[Self]) -> Self {
164        if expressions.is_empty() {
165            return Self::Val(true);
166        }
167        let mut iter = expressions.iter();
168        // SAFETY: We check that it's not empty above, so `next()` will always return Some.
169        unsafe {
170            let first = iter.next().unwrap_unchecked().clone();
171            iter.fold(first, |acc, x| {
172                Self::And(Box::new(acc), Box::new(x.clone()))
173            })
174        }
175    }
176}
177
178impl From<bool> for Expr {
179    /// Converts a `bool` into an `Expr::Val`.
180    fn from(b: bool) -> Self {
181        Self::Val(b)
182    }
183}
184
185impl From<Variable> for Expr {
186    /// Converts a `Variable` into an `Expr::Var`.
187    fn from(i: Variable) -> Self {
188        Self::Var(i)
189    }
190}
191
192impl From<i32> for Expr {
193    /// Converts an `i32` (DIMACS-style literal) into an `Expr`.
194    /// A positive `i` becomes `Expr::Var(i.abs())`.
195    /// A negative `i` becomes `Expr::Not(Box::new(Expr::Var(i.abs())))`.
196    /// `Variable` is assumed to be constructible from `u32`.
197    fn from(i: i32) -> Self {
198        let var_id = i.unsigned_abs();
199        if i < 0 {
200            Self::Not(Box::new(Self::Var(var_id)))
201        } else {
202            Self::Var(var_id)
203        }
204    }
205}
206
207/// Applies De Morgan's laws to an expression to push negations (`Not`) inwards.
208///
209/// - `Not(And(e1, e2))` becomes `Or(Not(e1), Not(e2))` (after recursive calls).
210/// - `Not(Or(e1, e2))` becomes `And(Not(e1), Not(e2))` (after recursive calls).
211/// - `Not(Not(e))` becomes `e`.
212/// - `Not(Var(i))` remains `Not(Var(i))`.
213/// - `Not(Val(b))` becomes `Val(!b)`.
214///
215/// The function recursively applies these transformations throughout the expression.
216/// Non-`Not` expressions are traversed, and `demorgans_laws` is applied to their sub-expressions.
217///
218/// # Arguments
219///
220/// * `expr`: The expression to transform.
221///
222/// # Returns
223///
224/// A new `Expr` with negations pushed inwards.
225#[must_use]
226pub fn demorgans_laws(expr: &Expr) -> Expr {
227    match expr {
228        Expr::Not(e_boxed) => match e_boxed.as_ref() {
229            Expr::Var(i) => Expr::Not(Box::new(Expr::Var(*i))),
230            Expr::Not(inner_e_boxed) => demorgans_laws(inner_e_boxed.as_ref()),
231            Expr::And(e1, e2) => Expr::Or(
232                Box::new(demorgans_laws(&Expr::Not(e1.clone()))),
233                Box::new(demorgans_laws(&Expr::Not(e2.clone()))),
234            ),
235            Expr::Or(e1, e2) => Expr::And(
236                Box::new(demorgans_laws(&Expr::Not(e1.clone()))),
237                Box::new(demorgans_laws(&Expr::Not(e2.clone()))),
238            ),
239            Expr::Val(b) => Expr::Val(!*b),
240        },
241        Expr::And(e1, e2) => Expr::And(Box::new(demorgans_laws(e1)), Box::new(demorgans_laws(e2))),
242        Expr::Or(e1, e2) => Expr::Or(Box::new(demorgans_laws(e1)), Box::new(demorgans_laws(e2))),
243        Expr::Val(b) => Expr::Val(*b),
244        Expr::Var(i) => Expr::Var(*i),
245    }
246}
247
248/// Applies distributive laws to an expression, primarily `A AND (B OR C) -> (A AND B) OR (A AND C)`
249/// and `(B OR C) AND A -> (B AND A) OR (C AND A)`.
250///
251/// This function aims to transform expressions towards a disjunctive normal form (DNF) if applied
252/// to distribute AND over OR.
253///
254/// It recursively applies these transformations.
255///
256/// # Arguments
257///
258/// * `expr`: The expression to transform.
259///
260/// # Panics
261///
262/// Panics if it encounters an `Expr::Not(_)` variant directly during distribution, as it
263/// expects negations to have been pushed to the literal level (NNF) by `demorgans_laws`
264/// before distribution is applied for typical CNF/DNF conversion steps.
265#[must_use]
266pub fn distributive_laws(expr: &Expr) -> Expr {
267    let current_expr = expr.clone();
268
269    match current_expr {
270        Expr::Or(e1_boxed, e2_boxed) => {
271            let e1_cnf = distributive_laws(&e1_boxed);
272            let e2_cnf = distributive_laws(&e2_boxed);
273
274            match (e1_cnf.clone(), e2_cnf.clone()) {
275                (Expr::And(e11, e12), _) => Expr::And(
276                    Box::new(distributive_laws(&Expr::Or(e11, Box::new(e2_cnf.clone())))),
277                    Box::new(distributive_laws(&Expr::Or(e12, Box::new(e2_cnf)))),
278                ),
279                (_, Expr::And(e21, e22)) => Expr::And(
280                    Box::new(distributive_laws(&Expr::Or(Box::new(e1_cnf.clone()), e21))),
281                    Box::new(distributive_laws(&Expr::Or(Box::new(e1_cnf), e22))),
282                ),
283                _ => Expr::Or(Box::new(e1_cnf), Box::new(e2_cnf)),
284            }
285        }
286        Expr::And(e1_boxed, e2_boxed) => Expr::And(
287            Box::new(distributive_laws(&e1_boxed)),
288            Box::new(distributive_laws(&e2_boxed)),
289        ),
290        Expr::Not(inner) => Expr::Not(Box::new(distributive_laws(inner.as_ref()))),
291        Expr::Val(b) => Expr::Val(b),
292        Expr::Var(i) => Expr::Var(i),
293    }
294}
295
296/// Applies De Morgan's laws and then distributive laws repeatedly to an expression
297/// until it no longer changes (reaches a fixed point).
298///
299/// This is a common step in converting an arbitrary boolean expression into a
300/// standard form, often as a preliminary step before full CNF or DNF conversion.
301/// The goal is to achieve Negation Normal Form (NNF) via `demorgans_laws`,
302/// and then apply `distributive_laws` to move towards a
303/// product-of-sums (CNF like) structure.
304///
305/// # Arguments
306///
307/// * `expr`: The initial expression.
308///
309/// # Returns
310///
311/// The transformed `Expr` after applying laws to a fixed point.
312#[must_use]
313pub fn apply_laws(expr: &Expr) -> Expr {
314    let mut current_expr = expr.clone();
315    loop {
316        let nnf_expr = demorgans_laws(&current_expr);
317        let distributed_expr = distributive_laws(&nnf_expr);
318
319        if distributed_expr == current_expr {
320            break;
321        }
322        current_expr = distributed_expr;
323    }
324    current_expr
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330
331    fn var(id: u32) -> Expr {
332        Expr::Var(id)
333    }
334    fn not(expr: Expr) -> Expr {
335        Expr::Not(Box::new(expr))
336    }
337    fn and(e1: Expr, e2: Expr) -> Expr {
338        Expr::And(Box::new(e1), Box::new(e2))
339    }
340    fn or(e1: Expr, e2: Expr) -> Expr {
341        Expr::Or(Box::new(e1), Box::new(e2))
342    }
343
344    #[test]
345    fn test_demorgans_laws_not_and() {
346        let expr = not(and(var(1), var(2)));
347        let expected = or(not(var(1)), not(var(2)));
348        assert_eq!(demorgans_laws(&expr), expected);
349    }
350
351    #[test]
352    fn test_demorgans_laws_not_or() {
353        let expr = not(or(var(1), var(2)));
354        let expected = and(not(var(1)), not(var(2)));
355        assert_eq!(demorgans_laws(&expr), expected);
356    }
357
358    #[test]
359    fn test_demorgans_laws_double_negation() {
360        let expr = not(not(var(1)));
361        let expected = var(1);
362        assert_eq!(demorgans_laws(&expr), expected);
363    }
364
365    #[test]
366    fn test_demorgans_laws_not_val() {
367        let expr = not(Expr::Val(true));
368        let expected = Expr::Val(false);
369        assert_eq!(demorgans_laws(&expr), expected);
370    }
371
372    #[test]
373    fn test_demorgans_laws_nested() {
374        let expr = not(and(var(1), not(or(var(2), var(3)))));
375        let expected = or(not(var(1)), or(var(2), var(3)));
376        assert_eq!(demorgans_laws(&expr), expected);
377    }
378
379    #[test]
380    fn test_distributive_laws_a_and_or_b_c() {
381        let expr = and(or(var(1), var(2)), var(3));
382        let expected = and(or(var(1), var(2)), var(3));
383        assert_eq!(distributive_laws(&expr), expected);
384    }
385
386    #[test]
387    fn test_distributive_laws_or_a_b_and_c() {
388        let expr = and(or(var(1), var(2)), var(3));
389        let expected = and(or(var(1), var(2)), var(3));
390        assert_eq!(distributive_laws(&expr), expected);
391    }
392
393    #[test]
394    fn test_distributive_laws_no_change() {
395        let expr = and(var(1), var(2));
396        assert_eq!(distributive_laws(&expr), expr.clone());
397
398        let expr_or = or(var(1), var(2));
399        assert_eq!(distributive_laws(&expr_or), expr_or.clone());
400    }
401
402    #[test]
403    fn test_distributive_laws_handles_not_nnf() {
404        let expr = and(not(var(1)), or(var(2), var(3)));
405        let expected = and(not(var(1)), or(var(2), var(3)));
406        assert_eq!(distributive_laws(&expr), expected);
407    }
408
409    #[test]
410    fn test_apply_laws_with_demorgans() {
411        let expr = and(not(and(var(1), var(2))), var(3));
412        let expected = and(or(not(var(1)), not(var(2))), var(3));
413        assert_eq!(apply_laws(&expr), expected);
414    }
415
416    #[test]
417    fn test_ors_and_ands_helpers() {
418        assert_eq!(Expr::ors(&[]), Expr::Val(false));
419        assert_eq!(Expr::ands(&[]), Expr::Val(true));
420
421        let exprs = [var(1), var(2), var(3)];
422        let or_expr = Expr::ors(&exprs);
423        let expected_or = or(or(var(1), var(2)), var(3));
424        assert_eq!(or_expr, expected_or);
425
426        let and_expr = Expr::ands(&exprs);
427        let expected_and = and(and(var(1), var(2)), var(3));
428        assert_eq!(and_expr, expected_and);
429
430        assert_eq!(Expr::ors(&[var(1)]), var(1));
431        assert_eq!(Expr::ands(&[var(1)]), var(1));
432    }
433}