1use crate::ast::{JoinedBy, Key, Specificity};
2use std::{collections::BTreeSet, fmt::Display, hash::Hash, ops::Add};
3
4#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
6pub struct Clause {
7 literals: BTreeSet<Key>,
8}
9impl Clause {
10 pub fn with_literal(key: Key) -> Self {
11 Self {
12 literals: BTreeSet::from([key]),
13 }
14 }
15
16 pub fn with_literals(keys: impl IntoIterator<Item = Key>) -> Self {
17 Self {
18 literals: BTreeSet::from_iter(keys),
19 }
20 }
21
22 pub fn is_empty(&self) -> bool {
23 self.literals.is_empty()
24 }
25
26 pub fn first(&self) -> Option<Key> {
27 self.literals.iter().next().cloned()
28 }
29
30 pub fn is_subset(&self, other: &Clause) -> bool {
31 self.literals.is_subset(&other.literals)
32 }
33
34 pub fn is_strict_subset(&self, other: &Clause) -> bool {
35 self.is_subset(other) && self.literals.len() < other.literals.len()
36 }
37
38 pub fn elements(&self) -> &BTreeSet<Key> {
39 &self.literals
40 }
41
42 pub fn union(&self, other: &Clause) -> Clause {
43 Clause {
44 literals: self.literals.union(&other.literals).cloned().collect(),
45 }
46 }
47
48 pub fn specificity(&self) -> Specificity {
49 self.literals
50 .iter()
51 .map(|lit| lit.specificity)
52 .reduce(Add::add)
53 .unwrap_or(Specificity::zero())
54 }
55
56 pub fn len(&self) -> usize {
57 self.literals.len()
58 }
59}
60impl PartialOrd for Clause {
61 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
62 Some(self.cmp(other))
63 }
64}
65impl Ord for Clause {
67 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
68 self.literals
69 .len()
70 .cmp(&other.literals.len())
71 .then_with(|| self.literals.cmp(&other.literals))
72 }
73}
74impl Display for Clause {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 let literals: String = self.literals.iter().joined_by(" ");
77 write!(f, "{literals}")
78 }
79}
80impl From<Key> for Clause {
81 fn from(value: Key) -> Self {
82 Self::with_literal(value)
83 }
84}
85
86#[derive(Clone, Debug, Eq)]
87pub struct Formula {
88 clauses: BTreeSet<Clause>,
89 shared: BTreeSet<Clause>,
90}
91impl Default for Formula {
92 fn default() -> Self {
93 Self::with_clause(Clause::default())
94 }
95}
96
97impl Formula {
98 pub fn new(clauses: BTreeSet<Clause>, shared: BTreeSet<Clause>) -> Self {
99 Self { clauses, shared }
100 }
101
102 pub fn with_clause(clause: Clause) -> Self {
103 Self::new(BTreeSet::from([clause]), Default::default())
104 }
105
106 pub fn with_clauses(clauses: impl IntoIterator<Item = Clause>) -> Self {
107 Self::new(BTreeSet::from_iter(clauses), Default::default())
108 }
109
110 pub fn is_empty(&self) -> bool {
111 self.first().is_empty()
112 }
113
114 pub fn first(&self) -> &Clause {
115 self.clauses
116 .first()
117 .expect("Empty formula should be impossible!")
118 }
119
120 pub fn is_subset(&self, other: &Formula) -> bool {
121 self.clauses.is_subset(&other.clauses)
122 }
123
124 pub fn into_inner(self) -> (BTreeSet<Clause>, BTreeSet<Clause>) {
125 (self.clauses, self.shared)
126 }
127
128 pub fn elements(&self) -> &BTreeSet<Clause> {
129 &self.clauses
130 }
131
132 pub fn shared(&self) -> &BTreeSet<Clause> {
133 &self.shared
134 }
135
136 pub fn merge(self, other: Formula) -> Self {
137 let merged = Self::new(
138 self.clauses.into_union(other.clauses),
139 self.shared.into_union(other.shared),
140 );
141 merged.normalize()
142 }
143
144 pub fn len(&self) -> usize {
145 self.clauses.len()
146 }
147
148 pub fn normalize(self) -> Self {
158 let mut minimized = BTreeSet::<Clause>::default();
159 for c in self.clauses.into_iter() {
160 minimized.retain(|s| !subsumes(&c, s));
161 if !minimized.iter().any(|s| subsumes(s, &c)) {
162 minimized.insert(c);
163 }
164 }
165 let shared: BTreeSet<_> = self
167 .shared
168 .into_iter()
169 .filter(|s| minimized.iter().any(|c| s.is_strict_subset(c)))
170 .collect();
171
172 Self {
173 clauses: minimized,
174 shared,
175 }
176 }
177}
178impl Hash for Formula {
179 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
180 self.clauses.hash(state);
181 }
182}
183impl PartialEq for Formula {
184 fn eq(&self, other: &Self) -> bool {
185 self.clauses == other.clauses
186 }
187}
188impl PartialOrd for Formula {
189 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
190 Some(self.cmp(other))
191 }
192}
193impl Ord for Formula {
195 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
196 self.clauses
197 .len()
198 .cmp(&other.clauses.len())
199 .then_with(|| self.clauses.cmp(&other.clauses))
200 }
201}
202impl Display for Formula {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 let clauses: String = self.clauses.iter().joined_by(", ");
205 write!(f, "{clauses}")
206 }
207}
208
209fn subsumes(c: &Clause, d: &Clause) -> bool {
212 c.is_subset(d)
213}
214
215pub trait IntoUnion {
216 fn into_union(self, other: Self) -> Self;
217}
218impl IntoUnion for Clause {
219 fn into_union(self, other: Self) -> Self {
220 Clause {
221 literals: self.literals.into_union(other.literals),
222 }
223 }
224}
225impl<T: Clone + Ord> IntoUnion for BTreeSet<T> {
226 fn into_union(self, other: Self) -> Self {
227 self.union(&other).cloned().collect()
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::ast::macros::*;
235 use pretty_assertions::assert_eq;
236
237 #[test]
238 fn normalize() {
239 let form = Formula::with_clauses([
240 Clause::with_literals([key!("a"), key!("b")]),
241 Clause::with_literals([key!("b")]),
242 Clause::with_literals([key!("a")]),
243 Clause::with_literals([key!("c"), key!("d")]),
244 Clause::with_literals([key!("a"), key!("c"), key!("d")]),
245 Clause::with_literals([key!("c"), key!("d")]),
246 ]);
247 assert_eq!(form.to_string(), "a, b, a b, c d, a c d");
248 assert_eq!(form.normalize().to_string(), "a, b, c d");
249 }
250}