1use crate::ProofExpr;
23use std::collections::BTreeSet;
24
25fn atom(s: String) -> ProofExpr {
26 ProofExpr::Atom(s)
27}
28fn not(a: ProofExpr) -> ProofExpr {
29 ProofExpr::Not(Box::new(a))
30}
31fn or(a: ProofExpr, b: ProofExpr) -> ProofExpr {
32 ProofExpr::Or(Box::new(a), Box::new(b))
33}
34fn and(a: ProofExpr, b: ProofExpr) -> ProofExpr {
35 ProofExpr::And(Box::new(a), Box::new(b))
36}
37fn implies(a: ProofExpr, b: ProofExpr) -> ProofExpr {
38 ProofExpr::Implies(Box::new(a), Box::new(b))
39}
40fn iff(a: ProofExpr, b: ProofExpr) -> ProofExpr {
41 ProofExpr::Iff(Box::new(a), Box::new(b))
42}
43
44pub fn lex_le(x: &[ProofExpr], y: &[ProofExpr], aux: &str) -> ProofExpr {
51 assert_eq!(x.len(), y.len(), "lex_le needs equal-length vectors");
52 let m = x.len();
53 if m == 0 {
54 let t = atom(format!("{aux}_taut"));
55 return or(t.clone(), not(t));
56 }
57 let eq = |k: usize| atom(format!("{aux}_eq_{k}"));
59 let mut clauses: Vec<ProofExpr> = Vec::new();
60 clauses.push(or(not(x[0].clone()), y[0].clone()));
62 if m >= 2 {
63 clauses.push(iff(eq(0), iff(x[0].clone(), y[0].clone())));
64 for k in 1..m {
65 clauses.push(implies(eq(k - 1), or(not(x[k].clone()), y[k].clone())));
67 if k < m - 1 {
68 clauses.push(iff(eq(k), and(eq(k - 1), iff(x[k].clone(), y[k].clone()))));
69 }
70 }
71 }
72 clauses.into_iter().reduce(and).unwrap()
73}
74
75type Lit = (String, bool);
77type Clause = BTreeSet<Lit>;
79
80fn to_clauses(e: &ProofExpr) -> Option<Vec<Clause>> {
84 let mut conjuncts: Vec<&ProofExpr> = Vec::new();
85 fn flatten<'a>(e: &'a ProofExpr, out: &mut Vec<&'a ProofExpr>) {
86 match e {
87 ProofExpr::And(l, r) => {
88 flatten(l, out);
89 flatten(r, out);
90 }
91 other => out.push(other),
92 }
93 }
94 flatten(e, &mut conjuncts);
95 let mut clauses = Vec::with_capacity(conjuncts.len());
96 for c in conjuncts {
97 clauses.push(clause_of(c)?);
98 }
99 Some(clauses)
100}
101
102fn clause_of(e: &ProofExpr) -> Option<Clause> {
105 let mut lits = BTreeSet::new();
106 if collect_lits(e, true, &mut lits) {
107 Some(lits)
108 } else {
109 None
110 }
111}
112
113fn collect_lits(e: &ProofExpr, pos: bool, out: &mut Clause) -> bool {
114 match e {
115 ProofExpr::Atom(a) => {
116 out.insert((a.clone(), pos));
117 true
118 }
119 ProofExpr::Not(inner) => match inner.as_ref() {
120 ProofExpr::Atom(a) => {
121 out.insert((a.clone(), !pos));
122 true
123 }
124 ProofExpr::And(l, r) if pos => {
126 collect_lits(l, false, out) && collect_lits(r, false, out)
127 }
128 ProofExpr::Or(l, r) if !pos => {
129 collect_lits(l, false, out) && collect_lits(r, false, out)
130 }
131 ProofExpr::Not(x) => collect_lits(x, pos, out),
132 _ => false,
133 },
134 ProofExpr::Or(l, r) if pos => collect_lits(l, pos, out) && collect_lits(r, pos, out),
135 ProofExpr::And(l, r) if !pos => collect_lits(l, pos, out) && collect_lits(r, pos, out),
136 ProofExpr::Implies(l, r) if pos => {
137 collect_lits(l, false, out) && collect_lits(r, true, out)
138 }
139 _ => false,
140 }
141}
142
143fn positive_rows(clauses: &[Clause]) -> Vec<Vec<String>> {
147 clauses
148 .iter()
149 .filter(|c| c.len() >= 2 && c.iter().all(|(_, p)| *p))
150 .map(|c| c.iter().map(|(a, _)| a.clone()).collect())
151 .collect()
152}
153
154fn rename(clause: &Clause, map: &std::collections::HashMap<&str, &str>) -> Clause {
156 clause
157 .iter()
158 .map(|(a, p)| {
159 (
160 map.get(a.as_str())
161 .map(|s| s.to_string())
162 .unwrap_or_else(|| a.clone()),
163 *p,
164 )
165 })
166 .collect()
167}
168
169fn swap_is_automorphism(
172 clause_set: &BTreeSet<Clause>,
173 clauses: &[Clause],
174 row_a: &[String],
175 row_b: &[String],
176) -> bool {
177 if row_a.len() != row_b.len() {
178 return false;
179 }
180 let mut map = std::collections::HashMap::new();
181 for (a, b) in row_a.iter().zip(row_b.iter()) {
182 map.insert(a.as_str(), b.as_str());
183 map.insert(b.as_str(), a.as_str());
184 }
185 let image: BTreeSet<Clause> = clauses.iter().map(|c| rename(c, &map)).collect();
188 &image == clause_set
189}
190
191pub fn break_symmetries(e: &ProofExpr) -> ProofExpr {
195 let Some(clauses) = to_clauses(e) else {
196 return e.clone();
197 };
198 let rows = positive_rows(&clauses);
199 if rows.len() < 2 {
200 return e.clone();
201 }
202 let clause_set: BTreeSet<Clause> = clauses.iter().cloned().collect();
203 let mut sbps: Vec<ProofExpr> = Vec::new();
204 for i in 0..rows.len() - 1 {
205 let (ra, rb) = (&rows[i], &rows[i + 1]);
206 if ra.len() != rb.len() || ra.len() < 2 {
207 continue;
208 }
209 if swap_is_automorphism(&clause_set, &clauses, ra, rb) {
210 let x: Vec<ProofExpr> = ra.iter().map(|a| atom(a.clone())).collect();
211 let y: Vec<ProofExpr> = rb.iter().map(|a| atom(a.clone())).collect();
212 sbps.push(lex_le(&x, &y, &format!("__sym_{i}")));
213 }
214 }
215 if sbps.is_empty() {
216 return e.clone();
217 }
218 let sbp = sbps.into_iter().reduce(and).unwrap();
219 and(e.clone(), sbp)
220}
221
222#[cfg(test)]
223#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/symmetry/tests.rs"]
224mod tests;