libprop_sat_solver/tableaux_solver/
theory.rs

1//! A `Theory` is a set of alternative `PropositionalFormula`s, which corresponds to a branch in a
2//! tableau tree.
3
4use std::collections::{HashMap, HashSet};
5
6use crate::formula::PropositionalFormula;
7
8use log::debug;
9
10/// A `Theory` is a set of alternative `PropositionalFormula`s.
11///
12/// It corresponds to one particular branch of the tableau tree.
13#[derive(Debug, PartialEq, Clone)]
14pub struct Theory {
15	formulas: HashSet<PropositionalFormula>,
16}
17
18impl Theory {
19	/// Construct an empty theory.
20	pub fn new() -> Self {
21		Self {
22			formulas: HashSet::new(),
23		}
24	}
25
26	/// Construct a `Theory` from a given propositional formula.
27	pub fn from_propositional_formula(formula: PropositionalFormula) -> Self {
28		let mut formulas: HashSet<PropositionalFormula> = HashSet::new();
29		formulas.insert(formula);
30
31		Self { formulas }
32	}
33
34	/// Get the formulas.
35	pub fn formulas(&self) -> impl Iterator<Item = &PropositionalFormula> {
36		self.formulas.iter()
37	}
38
39	/// Add a propositional formula to the theory iff the theory does not already contain the
40	/// formula.
41	pub fn add(&mut self, formula: PropositionalFormula) {
42		self.formulas.insert(formula);
43	}
44
45	/// Checks if the `Theory` is _fully expanded_, i.e. each propositional_formula in the given
46	/// `Theory` is a _literal_ (e.g. `p`, `-(p)`, a propositional variable or its negation).
47	pub fn is_fully_expanded(&self) -> bool {
48		self.formulas.iter().all(PropositionalFormula::is_literal)
49	}
50
51	/// Checks if a `Theory` contains _contradictions_. That is, if the `Theory` contains a literal
52	/// `p` AND its negation `-p`.
53	///
54	/// # Space and Time Complexity
55	///
56	/// This function uses a [`HashMap`] (specifically, a map from some `&str` to the tuple
57	/// `(has_literal, has_negation): (bool, bool)`. As soon as we encounter the case where
58	/// `has_literal && has_negation` then we have found a _contradiction_.
59	///
60	/// - Worst-case time complexity: `O(n)` because we iterate through all of the formulas
61	///   for the given theory.
62	/// - Worst-case space complexity: `O(k)` for `k` propositional variables appearing in literals.
63	///
64	/// [`HashMap`]: https://doc.rust-lang.org/std/collections/struct.HashMap.html
65	pub fn has_contradictions(&self) -> bool {
66		// Mapping from the variable name `&str` to `(has_literal, has_negation)`.
67		let mut literal_occurrence_map: HashMap<&str, (bool, bool)> = HashMap::new();
68
69		for formula in &self.formulas {
70			if self.check_formula(formula, &mut literal_occurrence_map) {
71				return true;
72			}
73		}
74
75		debug!("for the formulas:\n{:#?}", &self.formulas);
76		debug!("construct the HashSet:\n{:#?}", &literal_occurrence_map);
77		debug!("the theory contains no contradictions:\n{:#?}", &self);
78
79		// We've gone through the entire collection of formulas in the `Theory` and did not find any
80		// contradictions.
81		false
82	}
83
84	fn check_formula<'a>(
85		&self,
86		formula: &'a PropositionalFormula,
87		literal_occurrence_map: &mut HashMap<&'a str, (bool, bool)>,
88	) -> bool {
89		match formula {
90			PropositionalFormula::Variable(v) => {
91				if let Some((has_literal, has_negation)) = literal_occurrence_map.get_mut(v.name())
92				{
93					if *has_negation {
94						// We've already seen the negated literal, and now we have the literal, so
95						// we've found a contradiction.
96						true
97					} else {
98						*has_literal = true;
99						false
100					}
101				} else {
102					literal_occurrence_map.insert(v.name(), (true, false));
103					false
104				}
105			}
106			PropositionalFormula::Negation(Some(f)) => match &**f {
107				PropositionalFormula::Variable(v) => {
108					if let Some((has_literal, has_negation)) =
109						literal_occurrence_map.get_mut(v.name())
110					{
111						if *has_literal {
112							// We've already seen the literal, and now we have the negation, so
113							// we've found a contradiction.
114							true
115						} else {
116							*has_negation = true;
117							false
118						}
119					} else {
120						literal_occurrence_map.insert(v.name(), (false, true));
121						false
122					}
123				}
124				PropositionalFormula::Negation(Some(ref g)) => {
125					// Now (-(-A)) == A so we've covered the base cases and we can simply
126					// recursively call `self.check_formula()` to handle the inductive cases with
127					// deeply nested negated literals.
128					self.check_formula(g, literal_occurrence_map)
129				}
130				_ => false,
131			},
132			_ => false,
133		}
134	}
135
136	/// Get a non-literal formula (not a propositional variable or its negation) from the current
137	/// `Theory`.
138	pub fn get_non_literal_formula(&mut self) -> Option<PropositionalFormula> {
139		self.formulas.iter().cloned().find(|f| !f.is_literal())
140	}
141
142	/// Replace existing formula with a new formula.
143	pub fn swap_formula(
144		&mut self,
145		existing: &PropositionalFormula,
146		replacement: PropositionalFormula,
147	) {
148		if self.formulas.remove(existing) {
149			self.formulas.insert(replacement);
150		}
151	}
152
153	/// Replace existing formula with two new formulas.
154	pub fn swap_formula2(
155		&mut self,
156		existing: &PropositionalFormula,
157		replacements: (PropositionalFormula, PropositionalFormula),
158	) {
159		if self.formulas.remove(existing) {
160			self.formulas.insert(replacements.0);
161			self.formulas.insert(replacements.1);
162		}
163	}
164}
165
166#[cfg(test)]
167mod tests {
168	use super::*;
169	use crate::formula::Variable;
170	use assert2::check;
171
172	#[test]
173	fn test_construction() {
174		let theory =
175			Theory::from_propositional_formula(PropositionalFormula::variable(Variable::new("a")));
176
177		check!(theory.formulas().count() == 1);
178	}
179
180	#[test]
181	fn test_get_formulas() {
182		let formula_1 = PropositionalFormula::variable(Variable::new("a"));
183		let formula_2 = PropositionalFormula::variable(Variable::new("b"));
184
185		let mut theory = Theory::new();
186		theory.add(formula_1);
187		theory.add(formula_2);
188
189		check!(theory.formulas().count() == 2);
190	}
191
192	#[test]
193	fn test_add_fresh_formula() {
194		let formula_1 = PropositionalFormula::variable(Variable::new("a"));
195
196		let mut theory = Theory::new();
197		check!(theory.formulas().count() == 0);
198
199		theory.add(formula_1);
200		check!(theory.formulas().count() == 1);
201	}
202
203	#[test]
204	fn test_add_duplicate_formula() {
205		let formula_1 = PropositionalFormula::variable(Variable::new("a"));
206
207		let mut theory = Theory::new();
208		check!(theory.formulas().count() == 0);
209
210		theory.add(formula_1.clone());
211		check!(theory.formulas().count() == 1);
212
213		theory.add(formula_1.clone());
214		check!(theory.formulas().count() == 1);
215	}
216
217	#[test]
218	fn test_all_fully_expanded() {
219		let formula_1 = PropositionalFormula::variable(Variable::new("a"));
220		let formula_2 = PropositionalFormula::negated(Box::new(PropositionalFormula::variable(
221			Variable::new("b"),
222		)));
223		let formula_3 = PropositionalFormula::variable(Variable::new("a"));
224
225		let mut theory = Theory::new();
226		theory.add(formula_1);
227		theory.add(formula_2);
228		theory.add(formula_3);
229
230		check!(theory.is_fully_expanded());
231	}
232
233	#[test]
234	fn test_partially_expanded() {
235		let formula_1 = PropositionalFormula::variable(Variable::new("a"));
236		let formula_2 = PropositionalFormula::negated(Box::new(PropositionalFormula::conjunction(
237			Box::new(PropositionalFormula::variable(Variable::new("b"))),
238			Box::new(PropositionalFormula::variable(Variable::new("c"))),
239		)));
240		let formula_3 = PropositionalFormula::variable(Variable::new("d"));
241
242		let mut theory = Theory::new();
243		theory.add(formula_1);
244		theory.add(formula_2);
245		theory.add(formula_3);
246
247		check!(!theory.is_fully_expanded());
248	}
249
250	#[test]
251	fn test_none_fully_expanded() {
252		let formula_1 =
253			PropositionalFormula::negated(Box::new(PropositionalFormula::biimplication(
254				Box::new(PropositionalFormula::variable(Variable::new("e"))),
255				Box::new(PropositionalFormula::variable(Variable::new("a"))),
256			)));
257		let formula_2 = PropositionalFormula::negated(Box::new(PropositionalFormula::conjunction(
258			Box::new(PropositionalFormula::variable(Variable::new("b"))),
259			Box::new(PropositionalFormula::variable(Variable::new("c"))),
260		)));
261		let formula_3 = PropositionalFormula::negated(Box::new(PropositionalFormula::negated(
262			Box::new(PropositionalFormula::variable(Variable::new("f"))),
263		)));
264
265		let mut theory = Theory::new();
266		theory.add(formula_1);
267		theory.add(formula_2);
268		theory.add(formula_3);
269
270		check!(!theory.is_fully_expanded());
271	}
272
273	#[test]
274	fn test_simple_has_contradictions() {
275		let literal_a = PropositionalFormula::variable(Variable::new("a"));
276		let negated_literal_a = PropositionalFormula::negated(Box::new(literal_a.clone()));
277
278		let mut theory = Theory::new();
279		theory.add(literal_a);
280		theory.add(negated_literal_a);
281
282		check!(theory.has_contradictions());
283	}
284
285	#[test]
286	fn test_simple_has_no_contradictions() {
287		let literal_a = PropositionalFormula::variable(Variable::new("a"));
288		let literal_b = PropositionalFormula::variable(Variable::new("b"));
289
290		let mut theory = Theory::new();
291		theory.add(literal_a);
292		theory.add(literal_b);
293
294		check!(!theory.has_contradictions());
295	}
296
297	#[test]
298	fn test_complex_has_contradictions() {
299		let literal_a = PropositionalFormula::variable(Variable::new("a"));
300		let non_literal_1 =
301			PropositionalFormula::negated(Box::new(PropositionalFormula::conjunction(
302				Box::new(PropositionalFormula::variable(Variable::new("b"))),
303				Box::new(PropositionalFormula::variable(Variable::new("c"))),
304			)));
305		let literal_d = PropositionalFormula::variable(Variable::new("d"));
306		let negated_literal_a = PropositionalFormula::negated(Box::new(
307			PropositionalFormula::variable(Variable::new("a")),
308		));
309
310		let mut theory = Theory::new();
311		theory.add(literal_a);
312		theory.add(non_literal_1);
313		theory.add(literal_d);
314		theory.add(negated_literal_a);
315
316		check!(theory.has_contradictions());
317	}
318
319	#[test]
320	fn test_complex_has_no_contradictions() {
321		let literal_a = PropositionalFormula::variable(Variable::new("a"));
322		let non_literal_1 =
323			PropositionalFormula::negated(Box::new(PropositionalFormula::conjunction(
324				Box::new(PropositionalFormula::variable(Variable::new("b"))),
325				Box::new(PropositionalFormula::variable(Variable::new("c"))),
326			)));
327		let literal_d = PropositionalFormula::variable(Variable::new("d"));
328		let negated_literal_f = PropositionalFormula::negated(Box::new(
329			PropositionalFormula::variable(Variable::new("f")),
330		));
331
332		let mut theory = Theory::new();
333		theory.add(literal_a);
334		theory.add(non_literal_1);
335		theory.add(literal_d);
336		theory.add(negated_literal_f);
337
338		check!(!theory.has_contradictions());
339	}
340
341	#[test]
342	fn test_double_negation_no_contradiction() {
343		// { a, --a } should have no contradictions
344		let literal_a = PropositionalFormula::variable(Variable::new("a"));
345		let double_negated_literal_a =
346			PropositionalFormula::negated(Box::new(PropositionalFormula::negated(Box::new(
347				PropositionalFormula::variable(Variable::new("a")),
348			))));
349
350		let mut theory = Theory::new();
351		theory.add(literal_a);
352		theory.add(double_negated_literal_a);
353
354		check!(!theory.has_contradictions());
355	}
356
357	#[test]
358	fn test_recursive_negation_no_contradictions() {
359		// { -a, ---a } should have no contradictions
360		let negated_literal_a = PropositionalFormula::negated(Box::new(
361			PropositionalFormula::variable(Variable::new("a")),
362		));
363		let triple_negated_literal_a = PropositionalFormula::negated(Box::new(
364			PropositionalFormula::negated(Box::new(PropositionalFormula::negated(Box::new(
365				PropositionalFormula::variable(Variable::new("a")),
366			)))),
367		));
368
369		let mut theory = Theory::new();
370		theory.add(negated_literal_a);
371		theory.add(triple_negated_literal_a);
372
373		check!(!theory.has_contradictions());
374	}
375
376	#[test]
377	fn test_recursive_negation_has_contradictions() {
378		// { -a, ----a } should have contradictions
379		let negated_literal_a = PropositionalFormula::negated(Box::new(
380			PropositionalFormula::variable(Variable::new("a")),
381		));
382		let quad_negated_literal_a =
383			PropositionalFormula::negated(Box::new(PropositionalFormula::negated(Box::new(
384				PropositionalFormula::negated(Box::new(PropositionalFormula::negated(Box::new(
385					PropositionalFormula::variable(Variable::new("a")),
386				)))),
387			))));
388
389		let mut theory = Theory::new();
390		theory.add(negated_literal_a);
391		theory.add(quad_negated_literal_a);
392
393		check!(theory.has_contradictions());
394	}
395}