libprop_sat_solver/formula/
propositional_formula.rs

1//! A propositional formula.
2
3use std::convert::{From, Into};
4
5use super::Variable;
6
7/// A propositional formula is defined inductively, conforming to the following BNF:
8///
9/// ```ebnf
10/// <formula>
11///     ::= <propositional-variable>
12///     | ( - <formula> )
13///     | ( <formula> ^ <formula>)
14///     | ( <formula> | <formula> )
15///     | ( <formula> -> <formula> )
16///     | ( <formula> <-> <formula> )
17/// ```
18///
19/// Notice the requirement for explicit parentheses around the unary and binary operators, which
20/// eliminates the requirement for operator precedence due to grammar ambiguity at the cost of being
21/// more verbose.
22///
23/// # Ownership, Interior Mutability and Optional Sub-formulas
24///
25/// Since we don't need any fancy multiple-threading or multi-owner business, we'll stick with the
26/// most trivial `Box` pointer indirection instead of the fancier alternatives:
27///
28/// - Reference-counted smart pointer `Rc`
29/// - Atomically-reference-counted smart pointer `Arc`
30/// - Lifetime-bounded references `&'a`
31///
32/// We do not support interior mutability as we do not need it for our use cases with respect to the
33/// propositional formula AST.
34///
35/// We need sub-formulas to be wrapped in `Option` for construction purposes, so we can build a
36/// `PropPropositionalFormula` during parsing.
37///
38/// # No Default
39///
40/// We cannot soundly define a sane default for a `PropositionalFormula` – even in the base case of
41/// a single propositional variable, what would the default propositional variable be?
42#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
43pub enum PropositionalFormula {
44    /// Base case: a single propositional variable.
45    Variable(Variable),
46    /// Unary case: negated formula.
47    Negation(Option<Box<PropositionalFormula>>),
48    /// Binary formula with the main connective being the logical AND connective.
49    Conjunction(
50        Option<Box<PropositionalFormula>>,
51        Option<Box<PropositionalFormula>>,
52    ),
53    /// Binary formula with the main connective being the logical OR operator.
54    Disjunction(
55        Option<Box<PropositionalFormula>>,
56        Option<Box<PropositionalFormula>>,
57    ),
58    /// Binary formula with the main connective being the implication operator.
59    Implication(
60        Option<Box<PropositionalFormula>>,
61        Option<Box<PropositionalFormula>>,
62    ),
63    /// Binary formula with the main connective being the biimplication operator.
64    Biimplication(
65        Option<Box<PropositionalFormula>>,
66        Option<Box<PropositionalFormula>>,
67    ),
68}
69
70// Convenience methods for constructing a `PropositionalFormula`.
71//
72// Inspired by the blog post at https://endler.dev/2017/boxes-and-trees/.
73impl PropositionalFormula {
74    /// Construct a new propositional formula from a propositional `Variable`.
75    ///
76    /// # Example
77    ///
78    /// ```
79    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
80    /// let formula = PropositionalFormula::variable(Variable::new("a"));
81    /// println!("{:#?}", formula);
82    /// ```
83    #[inline]
84    pub fn variable(v: Variable) -> Self {
85        Self::Variable(v)
86    }
87
88    /// Construct a new propositional formula from a sub propositional formula with negation.
89    ///
90    /// # Example
91    ///
92    /// ```
93    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
94    /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
95    /// let formula = PropositionalFormula::negated(Box::new(sub_formula.clone()));
96    /// println!("{:#?}", formula);
97    /// ```
98    #[inline]
99    pub fn negated(formula: Box<PropositionalFormula>) -> Self {
100        Self::Negation(Some(formula))
101    }
102
103    /// Construct a new propositional formula from two propositional sub-formulas with a conjunction
104    /// main connective.
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
110    /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
111    /// let formula = PropositionalFormula::conjunction(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
112    /// println!("{:#?}", formula);
113    /// ```
114    #[inline]
115    pub fn conjunction(
116        left_sub_formula: Box<PropositionalFormula>,
117        right_sub_formula: Box<PropositionalFormula>,
118    ) -> Self {
119        Self::Conjunction(Some(left_sub_formula), Some(right_sub_formula))
120    }
121
122    /// Construct a new propositional formula from two propositional sub-formulas with a disjunction
123    /// main connective.
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
129    /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
130    /// let formula = PropositionalFormula::disjunction(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
131    /// println!("{:#?}", formula);
132    /// ```
133    #[inline]
134    pub fn disjunction(
135        left_sub_formula: Box<PropositionalFormula>,
136        right_sub_formula: Box<PropositionalFormula>,
137    ) -> Self {
138        Self::Disjunction(Some(left_sub_formula), Some(right_sub_formula))
139    }
140
141    /// Construct a new propositional formula from two propositional sub-formulas with an
142    /// implication main connective.
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
148    /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
149    /// let formula = PropositionalFormula::implication(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
150    /// println!("{:#?}", formula);
151    /// ```
152    #[inline]
153    pub fn implication(
154        left_sub_formula: Box<PropositionalFormula>,
155        right_sub_formula: Box<PropositionalFormula>,
156    ) -> Self {
157        Self::Implication(Some(left_sub_formula), Some(right_sub_formula))
158    }
159
160    /// Construct a new propositional formula from two propositional sub-formulas with a
161    /// biimplication main connective.
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// use libprop_sat_solver::formula::{PropositionalFormula, Variable};
167    /// let sub_formula = PropositionalFormula::variable(Variable::new("a"));
168    /// let formula = PropositionalFormula::biimplication(Box::new(sub_formula.clone()), Box::new(sub_formula.clone()));
169    /// println!("{:#?}", formula);
170    /// ```
171    #[inline]
172    pub fn biimplication(
173        left_sub_formula: Box<PropositionalFormula>,
174        right_sub_formula: Box<PropositionalFormula>,
175    ) -> Self {
176        Self::Biimplication(Some(left_sub_formula), Some(right_sub_formula))
177    }
178
179    /// Checks if the given `PropositionalFormula` is a literal (either a propositional variable
180    /// like `p` or its negation `-p`).
181    pub fn is_literal(&self) -> bool {
182        match self {
183            // A propositional variable `p` is trivially a literal.
184            Self::Variable(_) => true,
185            // The negation of a propositional variable `(-p)` is also trivially a literal.
186            Self::Negation(Some(ref inner_formula)) if inner_formula.is_literal() => true,
187            // Any other complex propositional formula is not a literal.
188            _ => false,
189        }
190    }
191}
192
193impl<V> From<V> for PropositionalFormula
194where
195    V: Into<Variable>,
196{
197    fn from(v: V) -> Self {
198        Self::Variable(v.into())
199    }
200}