1use crate::cdcl::{Lit, Var};
11use crate::proof::ProofStep;
12
13fn resolve(a: &[Lit], b: &[Lit], x: Var) -> Option<Vec<Lit>> {
17 let mut r: Vec<Lit> = a.iter().copied().filter(|l| l.var() != x).collect();
18 for &l in b {
19 if l.var() != x && !r.contains(&l) {
20 r.push(l);
21 }
22 }
23 if r.iter().any(|&l| r.contains(&l.negated())) {
24 return None; }
26 Some(r)
27}
28
29pub fn eliminate_variable(clauses: &[Vec<Lit>], x: Var) -> Option<(Vec<Vec<Lit>>, Vec<ProofStep>)> {
34 let (pos, neg) = (Lit::pos(x), Lit::neg(x));
35 let pos_clauses: Vec<&Vec<Lit>> = clauses.iter().filter(|c| c.contains(&pos)).collect();
36 let neg_clauses: Vec<&Vec<Lit>> = clauses.iter().filter(|c| c.contains(&neg)).collect();
37 if pos_clauses.is_empty() && neg_clauses.is_empty() {
38 return None; }
40 let mut resolvents: Vec<Vec<Lit>> = Vec::new();
42 for a in &pos_clauses {
43 for b in &neg_clauses {
44 if let Some(r) = resolve(a, b, x) {
45 resolvents.push(r);
46 }
47 }
48 }
49 if resolvents.len() > pos_clauses.len() + neg_clauses.len() {
51 return None;
52 }
53 let mut steps: Vec<ProofStep> = resolvents.iter().map(|r| ProofStep::Rup(r.clone())).collect();
57 for c in pos_clauses.iter().chain(neg_clauses.iter()) {
58 steps.push(ProofStep::Delete((*c).clone()));
59 }
60 let mut new_clauses: Vec<Vec<Lit>> =
61 clauses.iter().filter(|c| !c.contains(&pos) && !c.contains(&neg)).cloned().collect();
62 new_clauses.extend(resolvents);
63 Some((new_clauses, steps))
64}
65
66pub fn bve(num_vars: usize, clauses: &[Vec<Lit>]) -> (Vec<Vec<Lit>>, Vec<ProofStep>) {
70 let mut cur = clauses.to_vec();
71 let mut steps: Vec<ProofStep> = Vec::new();
72 for _pass in 0..4 {
73 let mut changed = false;
74 for x in 0..num_vars as Var {
75 if let Some((next, s)) = eliminate_variable(&cur, x) {
76 cur = next;
77 steps.extend(s);
78 changed = true;
79 }
80 }
81 if !changed {
82 break;
83 }
84 }
85 (cur, steps)
86}
87
88fn lit_node(l: Lit) -> usize {
95 (l.var() as usize) * 2 + if l.is_positive() { 0 } else { 1 }
96}
97pub enum EquivResult {
98 Unsat(Vec<ProofStep>),
100 Classes(Vec<usize>),
102}
103pub fn equivalent_literal_scc(num_vars: usize, clauses: &[Vec<Lit>]) -> EquivResult {
104 let n = num_vars * 2;
105 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
106 for c in clauses {
107 if c.len() == 2 {
108 let (a, b) = (c[0], c[1]);
109 adj[lit_node(a.negated())].push(lit_node(b)); adj[lit_node(b.negated())].push(lit_node(a)); }
112 }
113 let mut index = vec![usize::MAX; n];
115 let mut low = vec![0usize; n];
116 let mut on_stack = vec![false; n];
117 let mut stack: Vec<usize> = Vec::new();
118 let mut comp = vec![usize::MAX; n];
119 let mut next_index = 0usize;
120 let mut next_comp = 0usize;
121 for start in 0..n {
122 if index[start] != usize::MAX {
123 continue;
124 }
125 let mut call: Vec<(usize, usize)> = vec![(start, 0)];
126 while let Some(&(v, pi)) = call.last() {
127 if pi == 0 {
128 index[v] = next_index;
129 low[v] = next_index;
130 next_index += 1;
131 stack.push(v);
132 on_stack[v] = true;
133 }
134 if pi < adj[v].len() {
135 let w = adj[v][pi];
136 call.last_mut().unwrap().1 += 1;
137 if index[w] == usize::MAX {
138 call.push((w, 0));
139 } else if on_stack[w] {
140 low[v] = low[v].min(index[w]);
141 }
142 } else {
143 if low[v] == index[v] {
144 loop {
145 let w = stack.pop().unwrap();
146 on_stack[w] = false;
147 comp[w] = next_comp;
148 if w == v {
149 break;
150 }
151 }
152 next_comp += 1;
153 }
154 call.pop();
155 if let Some(&(p, _)) = call.last() {
156 low[p] = low[p].min(low[v]);
157 }
158 }
159 }
160 }
161 for v in 0..num_vars {
162 if comp[v * 2] == comp[v * 2 + 1] {
163 let (x, nx) = (Lit::pos(v as Var), Lit::neg(v as Var));
164 return EquivResult::Unsat(vec![ProofStep::Rup(vec![nx]), ProofStep::Rup(vec![x]), ProofStep::Rup(vec![])]);
165 }
166 }
167 EquivResult::Classes(comp)
168}
169
170pub fn bucket_elimination_refute(num_vars: usize, clauses: &[Vec<Lit>], width_cap: usize) -> Option<Vec<ProofStep>> {
177 let mut cur: Vec<Vec<Lit>> = clauses.to_vec();
178 let mut steps: Vec<ProofStep> = Vec::new();
179 for _ in 0..=num_vars {
180 if cur.iter().any(|c| c.is_empty()) {
181 return Some(steps);
182 }
183 let mut vars: Vec<Var> = cur.iter().flatten().map(|l| l.var()).collect();
184 vars.sort_unstable();
185 vars.dedup();
186 if vars.is_empty() {
187 break;
188 }
189 let v = *vars.iter().min_by_key(|&&v| cur.iter().filter(|c| c.iter().any(|l| l.var() == v)).count()).unwrap();
190 let (pos, neg) = (Lit::pos(v), Lit::neg(v));
191 let pos_c: Vec<Vec<Lit>> = cur.iter().filter(|c| c.contains(&pos)).cloned().collect();
192 let neg_c: Vec<Vec<Lit>> = cur.iter().filter(|c| c.contains(&neg)).cloned().collect();
193 if pos_c.len().saturating_mul(neg_c.len()) > 16_384 {
200 return None;
201 }
202 let mut resolvents: Vec<Vec<Lit>> = Vec::new();
203 for a in &pos_c {
204 for b in &neg_c {
205 if let Some(r) = resolve(a, b, v) {
206 if r.len() > width_cap {
207 return None; }
209 resolvents.push(r);
210 }
211 }
212 }
213 for r in &resolvents {
214 steps.push(ProofStep::Rup(r.clone()));
215 }
216 for c in pos_c.iter().chain(neg_c.iter()) {
217 steps.push(ProofStep::Delete(c.clone()));
218 }
219 cur.retain(|c| !c.contains(&pos) && !c.contains(&neg));
220 cur.extend(resolvents);
221 if cur.len() > 50_000 {
222 return None; }
224 }
225 if cur.iter().any(|c| c.is_empty()) {
226 Some(steps)
227 } else {
228 None
229 }
230}
231
232pub fn vivify_clause(num_vars: usize, db: &[Vec<Lit>], c: &[Lit]) -> Option<Vec<Lit>> {
236 let mut cur = c.to_vec();
237 let mut improved = false;
238 let mut i = 0;
239 while cur.len() > 1 && i < cur.len() {
240 let mut without = cur.clone();
241 without.remove(i);
242 if crate::rup::is_rup(num_vars, db, &without) {
243 cur = without; improved = true;
245 } else {
246 i += 1;
247 }
248 }
249 improved.then_some(cur)
250}
251
252pub fn vivify(num_vars: usize, clauses: &[Vec<Lit>]) -> (Vec<Vec<Lit>>, Vec<ProofStep>) {
256 let mut cur = clauses.to_vec();
257 let mut steps: Vec<ProofStep> = Vec::new();
258 for idx in 0..cur.len() {
259 let c = cur[idx].clone();
260 if c.len() <= 1 {
261 continue;
262 }
263 if let Some(c2) = vivify_clause(num_vars, &cur, &c) {
264 steps.push(ProofStep::Rup(c2.clone()));
265 steps.push(ProofStep::Delete(c.clone()));
266 cur[idx] = c2;
267 }
268 }
269 (cur, steps)
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use crate::cdcl::{SolveResult, Solver};
276 use crate::families;
277 use crate::pr::check_pr_refutation;
278
279 fn sat_brute(num_vars: usize, clauses: &[Vec<Lit>]) -> bool {
280 (0u32..(1u32 << num_vars)).any(|mask| {
281 let model: Vec<bool> = (0..num_vars).map(|v| (mask >> v) & 1 == 1).collect();
282 clauses.iter().all(|c| c.iter().any(|l| model[l.var() as usize] == l.is_positive()))
283 })
284 }
285
286 #[test]
287 fn bve_preserves_satisfiability_brute_force() {
288 let mut state = 0xB5E_1234_5678u64;
290 let mut next = || {
291 state ^= state << 13;
292 state ^= state >> 7;
293 state ^= state << 17;
294 state
295 };
296 for _ in 0..2000 {
297 let nv = 3 + (next() % 5) as usize; let nc = (next() % 14) as usize;
299 let clauses: Vec<Vec<Lit>> = (0..nc)
300 .map(|_| {
301 let len = 1 + (next() % 3) as usize;
302 let mut c = Vec::new();
303 for _ in 0..len {
304 let l = Lit::new((next() % nv as u64) as Var, next() & 1 == 0);
305 if !c.contains(&l) && !c.contains(&l.negated()) {
306 c.push(l);
307 }
308 }
309 c
310 })
311 .filter(|c: &Vec<Lit>| !c.is_empty())
312 .collect();
313 let (simplified, _) = bve(nv, &clauses);
314 assert_eq!(
315 sat_brute(nv, &clauses),
316 sat_brute(nv, &simplified),
317 "BVE changed satisfiability for {clauses:?}"
318 );
319 }
320 }
321
322 #[test]
323 fn bve_produces_a_certified_refutation_of_php() {
324 for n in 2..=4 {
327 let (cnf, _) = families::php(n);
328 let (simplified, mut steps) = bve(cnf.num_vars, &cnf.clauses);
329
330 let mut solver = Solver::new(cnf.num_vars);
331 for c in &simplified {
332 solver.add_clause(c.clone());
333 }
334 assert_eq!(solver.solve(), SolveResult::Unsat, "simplified PHP({n}) is UNSAT");
335 for lc in solver.learned() {
336 steps.push(ProofStep::Rup(lc.lits.clone()));
337 }
338 assert!(
339 check_pr_refutation(cnf.num_vars, &cnf.clauses, &steps),
340 "BVE-composed proof must refute the original PHP({n})"
341 );
342 }
343 }
344
345 #[test]
346 fn eliminate_variable_yields_the_resolvent() {
347 let (x, a, b) = (0u32, 1u32, 2u32);
351 let clauses = vec![vec![Lit::pos(x), Lit::pos(a)], vec![Lit::neg(x), Lit::pos(b)]];
352 let (simplified, steps) = eliminate_variable(&clauses, x).expect("x is eliminable");
353 assert!(simplified.iter().all(|c| !c.iter().any(|l| l.var() == x)), "x is gone");
354 assert!(simplified.contains(&vec![Lit::pos(a), Lit::pos(b)]), "resolvent (a ∨ b) present");
355 assert!(steps.iter().any(|s| matches!(s, ProofStep::Rup(_))), "emits the RUP resolvent");
356 assert_eq!(steps.iter().filter(|s| matches!(s, ProofStep::Delete(_))).count(), 2, "deletes both x-clauses");
357 }
358
359 #[test]
360 fn vivify_strengthens_via_propagation() {
361 let (a, b) = (0u32, 1u32);
363 let db = vec![vec![Lit::pos(a), Lit::pos(b)], vec![Lit::neg(a), Lit::pos(b)]];
364 let c2 = vivify_clause(2, &db, &db[0]).expect("the clause is strengthenable");
365 assert_eq!(c2, vec![Lit::pos(b)], "vivified to the unit (b)");
366 }
367
368 #[test]
369 fn vivify_preserves_satisfiability_brute_force() {
370 let mut state = 0x171F_1ED_9999u64;
371 let mut next = || {
372 state ^= state << 13;
373 state ^= state >> 7;
374 state ^= state << 17;
375 state
376 };
377 for _ in 0..2000 {
378 let nv = 3 + (next() % 5) as usize;
379 let nc = (next() % 14) as usize;
380 let clauses: Vec<Vec<Lit>> = (0..nc)
381 .map(|_| {
382 let len = 1 + (next() % 3) as usize;
383 let mut c = Vec::new();
384 for _ in 0..len {
385 let l = Lit::new((next() % nv as u64) as Var, next() & 1 == 0);
386 if !c.contains(&l) && !c.contains(&l.negated()) {
387 c.push(l);
388 }
389 }
390 c
391 })
392 .filter(|c: &Vec<Lit>| !c.is_empty())
393 .collect();
394 let (simplified, _) = vivify(nv, &clauses);
395 assert_eq!(sat_brute(nv, &clauses), sat_brute(nv, &simplified), "vivify changed SAT for {clauses:?}");
396 }
397 }
398
399 #[test]
400 fn vivify_produces_a_certified_refutation_of_php() {
401 for n in 2..=4 {
402 let (cnf, _) = families::php(n);
403 let (simplified, mut steps) = vivify(cnf.num_vars, &cnf.clauses);
404 let mut solver = Solver::new(cnf.num_vars);
405 for c in &simplified {
406 solver.add_clause(c.clone());
407 }
408 assert_eq!(solver.solve(), SolveResult::Unsat);
409 for lc in solver.learned() {
410 steps.push(ProofStep::Rup(lc.lits.clone()));
411 }
412 assert!(
413 check_pr_refutation(cnf.num_vars, &cnf.clauses, &steps),
414 "vivify-composed proof must refute the original PHP({n})"
415 );
416 }
417 }
418
419 #[test]
420 fn symmetry_then_bve_then_vivify_compose_into_one_certified_refutation() {
421 for n in 2..=4 {
425 let (cnf, _) = families::php(n);
426 let gens = crate::symmetry_detect::find_generators(cnf.num_vars, &cnf.clauses);
427 let (sb_db, nv, mut steps) =
429 crate::sym_certify::symmetry_break_certified(cnf.num_vars, &cnf.clauses, &gens);
430 let (bve_db, bve_steps) = bve(nv, &sb_db);
432 steps.extend(bve_steps);
433 let (viv_db, viv_steps) = vivify(nv, &bve_db);
435 steps.extend(viv_steps);
436 let mut solver = Solver::new(nv);
438 for c in &viv_db {
439 solver.add_clause(c.clone());
440 }
441 assert_eq!(solver.solve(), SolveResult::Unsat, "fully-simplified PHP({n}) is UNSAT");
442 for lc in solver.learned() {
443 steps.push(ProofStep::Rup(lc.lits.clone()));
444 }
445 assert!(
446 check_pr_refutation(nv, &cnf.clauses, &steps),
447 "symmetry + BVE + vivify + solve must compose into ONE certified refutation of PHP({n})"
448 );
449 }
450 }
451}