1use crate::cdcl::{Lit, SolveResult, Solver, Var};
10use crate::{ProofExpr, ProofTerm};
11use std::collections::{HashMap, HashSet};
12
13const OP_AND: u8 = 0;
15const OP_OR: u8 = 1;
16const OP_IMPL: u8 = 2;
17const OP_IFF: u8 = 3;
18
19#[inline]
22fn order2(a: Lit, b: Lit) -> (Lit, Lit) {
23 if (a.var(), a.is_positive()) <= (b.var(), b.is_positive()) {
24 (a, b)
25 } else {
26 (b, a)
27 }
28}
29
30#[derive(Clone, Default)]
35pub struct Cnf {
36 atom_of: HashMap<String, Var>,
37 num_vars: usize,
38 clauses: Vec<Vec<Lit>>,
39 expr_cache: HashMap<(u8, Lit, Lit), Lit>,
43 clause_seen: HashSet<Vec<Lit>>,
46}
47
48impl Cnf {
49 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn from_premises(premises: &[ProofExpr]) -> Option<Cnf> {
58 let mut cnf = Cnf::new();
59 for p in premises {
60 cnf.assert(p)?;
61 }
62 Some(cnf)
63 }
64
65 pub fn num_vars(&self) -> usize {
67 self.num_vars
68 }
69
70 pub fn num_atoms(&self) -> usize {
72 self.atom_of.len()
73 }
74
75 pub fn clauses(&self) -> &[Vec<Lit>] {
77 &self.clauses
78 }
79
80 pub fn atom_value(&self, e: &ProofExpr, model: &[bool]) -> Option<bool> {
85 let key = atom_key(e)?;
86 let v = *self.atom_of.get(&key)?;
87 model.get(v as usize).copied()
88 }
89
90 fn fresh(&mut self) -> Var {
91 let v = self.num_vars as Var;
92 self.num_vars += 1;
93 v
94 }
95
96 fn atom_var(&mut self, key: String) -> Var {
98 if let Some(&v) = self.atom_of.get(&key) {
99 return v;
100 }
101 let v = self.fresh();
102 self.atom_of.insert(key, v);
103 v
104 }
105
106 fn push_clause(&mut self, mut lits: Vec<Lit>) {
110 lits.sort_by_key(|l| (l.var(), l.is_positive()));
111 lits.dedup();
112 if lits.windows(2).any(|w| w[0].var() == w[1].var()) {
114 return;
115 }
116 if self.clause_seen.insert(lits.clone()) {
117 self.clauses.push(lits);
118 }
119 }
120
121 pub fn encode(&mut self, e: &ProofExpr) -> Option<Lit> {
126 match e {
127 ProofExpr::Not(p) => Some(self.encode(p)?.negated()),
128 ProofExpr::And(p, q) => {
129 let (a, b) = order2(self.encode(p)?, self.encode(q)?);
130 if let Some(&x) = self.expr_cache.get(&(OP_AND, a, b)) {
131 return Some(x);
132 }
133 let x = Lit::pos(self.fresh());
134 self.push_clause(vec![x.negated(), a]);
136 self.push_clause(vec![x.negated(), b]);
137 self.push_clause(vec![x, a.negated(), b.negated()]);
138 self.expr_cache.insert((OP_AND, a, b), x);
139 Some(x)
140 }
141 ProofExpr::Or(p, q) => {
142 let (a, b) = order2(self.encode(p)?, self.encode(q)?);
143 if let Some(&x) = self.expr_cache.get(&(OP_OR, a, b)) {
144 return Some(x);
145 }
146 let x = Lit::pos(self.fresh());
147 self.push_clause(vec![x.negated(), a, b]);
149 self.push_clause(vec![x, a.negated()]);
150 self.push_clause(vec![x, b.negated()]);
151 self.expr_cache.insert((OP_OR, a, b), x);
152 Some(x)
153 }
154 ProofExpr::Implies(p, q) => {
155 let a = self.encode(p)?;
157 let b = self.encode(q)?;
158 if let Some(&x) = self.expr_cache.get(&(OP_IMPL, a, b)) {
159 return Some(x);
160 }
161 let x = Lit::pos(self.fresh());
162 self.push_clause(vec![x.negated(), a.negated(), b]);
164 self.push_clause(vec![x, a]);
165 self.push_clause(vec![x, b.negated()]);
166 self.expr_cache.insert((OP_IMPL, a, b), x);
167 Some(x)
168 }
169 ProofExpr::Iff(p, q) => {
170 let (a, b) = order2(self.encode(p)?, self.encode(q)?);
171 if let Some(&x) = self.expr_cache.get(&(OP_IFF, a, b)) {
172 return Some(x);
173 }
174 let x = Lit::pos(self.fresh());
175 self.push_clause(vec![x.negated(), a.negated(), b]);
177 self.push_clause(vec![x.negated(), a, b.negated()]);
178 self.push_clause(vec![x, a, b]);
179 self.push_clause(vec![x, a.negated(), b.negated()]);
180 self.expr_cache.insert((OP_IFF, a, b), x);
181 Some(x)
182 }
183 _ => {
184 let key = atom_key(e)?;
186 Some(Lit::pos(self.atom_var(key)))
187 }
188 }
189 }
190
191 pub fn assert(&mut self, e: &ProofExpr) -> Option<()> {
199 match e {
200 ProofExpr::And(a, b) => {
201 self.assert(a)?;
202 self.assert(b)
203 }
204 ProofExpr::Implies(a, b) => {
205 let mut lits = self.clause_lits(&negate_expr(a))?;
207 lits.extend(self.clause_lits(b)?);
208 self.push_clause(lits);
209 Some(())
210 }
211 _ => {
212 let lits = self.clause_lits(e)?;
213 self.push_clause(lits);
214 Some(())
215 }
216 }
217 }
218
219 pub fn assert_neg(&mut self, e: &ProofExpr) -> Option<()> {
222 self.assert(&negate_expr(e))
223 }
224
225 fn clause_lits(&mut self, e: &ProofExpr) -> Option<Vec<Lit>> {
229 if let Some(l) = self.lit_of_atom(e) {
230 return Some(vec![l]);
231 }
232 match e {
233 ProofExpr::Or(a, b) => {
234 let mut v = self.clause_lits(a)?;
235 v.extend(self.clause_lits(b)?);
236 Some(v)
237 }
238 ProofExpr::Implies(a, b) => {
239 let mut v = self.clause_lits(&negate_expr(a))?;
240 v.extend(self.clause_lits(b)?);
241 Some(v)
242 }
243 ProofExpr::Not(inner) => match inner.as_ref() {
248 ProofExpr::And(a, b) => {
249 let mut v = self.clause_lits(&negate_expr(a))?;
250 v.extend(self.clause_lits(&negate_expr(b))?);
251 Some(v)
252 }
253 ProofExpr::Not(x) => self.clause_lits(x),
254 _ => Some(vec![self.encode(e)?]),
255 },
256 _ => Some(vec![self.encode(e)?]),
258 }
259 }
260
261 fn lit_of_atom(&mut self, e: &ProofExpr) -> Option<Lit> {
263 match e {
264 ProofExpr::Not(p) => {
265 let key = atom_key(p)?;
266 Some(Lit::pos(self.atom_var(key)).negated())
267 }
268 ProofExpr::Predicate { .. } | ProofExpr::Identity(..) | ProofExpr::Atom(_) => {
269 let key = atom_key(e)?;
270 Some(Lit::pos(self.atom_var(key)))
271 }
272 _ => None,
273 }
274 }
275
276 pub fn into_solver(self) -> Solver {
278 self.into_solver_with_atoms().0
279 }
280
281 pub fn into_solver_with_atoms(self) -> (Solver, HashMap<String, Var>) {
285 let mut s = Solver::new(self.num_vars);
286 for c in self.clauses {
287 s.add_clause(c);
288 }
289 (s, self.atom_of)
290 }
291}
292
293pub fn cdcl_entails(premises: &[ProofExpr], goal: &ProofExpr) -> Option<bool> {
298 let mut cnf = Cnf::new();
299 for p in premises {
300 cnf.assert(p)?;
301 }
302 cnf.assert_neg(goal)?;
303 let mut solver = cnf.into_solver();
304 Some(solver.solve() == SolveResult::Unsat)
305}
306
307fn negate_expr(e: &ProofExpr) -> ProofExpr {
309 match e {
310 ProofExpr::Not(p) => (**p).clone(),
311 _ => ProofExpr::Not(Box::new(e.clone())),
312 }
313}
314
315fn atom_key(e: &ProofExpr) -> Option<String> {
318 match e {
319 ProofExpr::Predicate { name, args, .. } => {
320 let mut s = String::new();
321 s.push_str(name);
322 s.push('(');
323 for (i, a) in args.iter().enumerate() {
324 if i > 0 {
325 s.push(',');
326 }
327 s.push_str(&term_key(a));
328 }
329 s.push(')');
330 Some(s)
331 }
332 ProofExpr::Identity(a, b) => {
333 let (ka, kb) = (term_key(a), term_key(b));
334 let (lo, hi) = if ka <= kb { (ka, kb) } else { (kb, ka) };
335 Some(format!("={}={}", lo, hi))
336 }
337 ProofExpr::Atom(name) => Some(format!("atom:{name}")),
338 _ => None,
339 }
340}
341
342fn term_key(t: &ProofTerm) -> String {
343 match t {
344 ProofTerm::Constant(s) => s.clone(),
345 ProofTerm::Variable(s) | ProofTerm::BoundVarRef(s) => format!("?{s}"),
346 ProofTerm::Function(n, args) => {
347 let inner: Vec<String> = args.iter().map(term_key).collect();
348 format!("{n}({})", inner.join(","))
349 }
350 ProofTerm::Group(_) => "<group>".to_string(),
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357 use crate::{ProofExpr, ProofTerm};
358
359 fn c(s: &str) -> ProofTerm {
360 ProofTerm::Constant(s.to_string())
361 }
362 fn pred(name: &str, args: Vec<ProofTerm>) -> ProofExpr {
363 ProofExpr::Predicate { name: name.to_string(), args, world: None }
364 }
365 fn not(e: ProofExpr) -> ProofExpr {
366 ProofExpr::Not(Box::new(e))
367 }
368 fn or(a: ProofExpr, b: ProofExpr) -> ProofExpr {
369 ProofExpr::Or(Box::new(a), Box::new(b))
370 }
371 fn and(a: ProofExpr, b: ProofExpr) -> ProofExpr {
372 ProofExpr::And(Box::new(a), Box::new(b))
373 }
374 fn imp(a: ProofExpr, b: ProofExpr) -> ProofExpr {
375 ProofExpr::Implies(Box::new(a), Box::new(b))
376 }
377 fn id(a: ProofTerm, b: ProofTerm) -> ProofExpr {
378 ProofExpr::Identity(a, b)
379 }
380
381 #[test]
382 fn reflexive_entailment() {
383 let p = pred("P", vec![c("a")]);
384 assert_eq!(cdcl_entails(&[p.clone()], &p), Some(true));
385 }
386
387 #[test]
388 fn disjunctive_syllogism() {
389 let p = pred("P", vec![c("a")]);
391 let q = pred("Q", vec![c("a")]);
392 let prem = vec![or(p.clone(), q.clone()), not(p.clone())];
393 assert_eq!(cdcl_entails(&prem, &q), Some(true));
394 assert_eq!(cdcl_entails(&[or(p.clone(), q.clone())], &q), Some(false));
396 }
397
398 #[test]
399 fn modus_ponens() {
400 let p = pred("P", vec![c("a")]);
402 let q = pred("Q", vec![c("a")]);
403 let prem = vec![p.clone(), imp(p.clone(), q.clone())];
404 assert_eq!(cdcl_entails(&prem, &q), Some(true));
405 }
406
407 #[test]
408 fn non_entailment_is_false_not_none() {
409 let p = pred("P", vec![c("a")]);
410 let q = pred("Q", vec![c("a")]);
411 assert_eq!(cdcl_entails(&[p], &q), Some(false));
412 }
413
414 #[test]
415 fn identity_is_symmetric() {
416 let prem = vec![id(c("a"), c("b"))];
418 assert_eq!(cdcl_entails(&prem, &id(c("b"), c("a"))), Some(true));
419 }
420
421 #[test]
422 fn two_value_grid_forces_the_cell() {
423 let in_ = |t: &str, s: &str| pred("In", vec![c(t), c(s)]);
426 let fl = "Florida";
427 let me = "Maine";
428 let prem = vec![
429 or(in_("Alpha", fl), in_("Alpha", me)),
431 or(in_("Beta", fl), in_("Beta", me)),
432 imp(in_("Alpha", fl), not(in_("Beta", fl))),
434 imp(in_("Alpha", me), not(in_("Beta", me))),
435 or(in_("Alpha", fl), in_("Beta", fl)),
437 or(in_("Alpha", me), in_("Beta", me)),
438 in_("Alpha", fl),
440 ];
441 assert_eq!(cdcl_entails(&prem, &in_("Beta", me)), Some(true), "Beta∈Maine is forced");
442 assert_eq!(cdcl_entails(&prem, &in_("Beta", fl)), Some(false), "Beta∈Florida is refuted");
443 }
444
445 #[test]
446 fn negated_conjunction_clausifies_directly_without_aux() {
447 let p = pred("P", vec![c("a")]);
450 let q = pred("Q", vec![c("a")]);
451 let mut cnf = Cnf::new();
452 cnf.assert(¬(and(p, q))).unwrap();
453 assert_eq!(cnf.num_atoms(), 2, "only P and Q are atoms");
454 assert_eq!(cnf.num_vars(), 2, "no auxiliary variable should be minted");
455 assert_eq!(cnf.clauses().len(), 1, "exactly one clause");
456 assert_eq!(cnf.clauses()[0].len(), 2, "the binary clause ¬P ∨ ¬Q");
457 }
458
459 #[test]
460 fn negated_conjunction_preserves_models() {
461 let p = pred("P", vec![c("a")]);
463 let q = pred("Q", vec![c("a")]);
464 assert_eq!(
465 cdcl_entails(&[p.clone(), not(and(p.clone(), q.clone()))], ¬(q.clone())),
466 Some(true)
467 );
468 assert_eq!(cdcl_entails(&[not(and(p, q.clone()))], ¬(q)), Some(false));
469 }
470
471 #[test]
472 fn nested_demorgan_and_double_negation_clausify() {
473 let (p, q, r) = (pred("P", vec![c("a")]), pred("Q", vec![c("a")]), pred("R", vec![c("a")]));
475 let mut cnf = Cnf::new();
476 cnf.assert(¬(and(and(p, q), r))).unwrap();
477 assert_eq!(cnf.num_vars(), 3, "no aux for a nested negated conjunction");
478 assert_eq!(cnf.clauses().len(), 1);
479 assert_eq!(cnf.clauses()[0].len(), 3, "ternary clause ¬P ∨ ¬Q ∨ ¬R");
480
481 let p2 = pred("P", vec![c("a")]);
482 let mut cnf2 = Cnf::new();
483 cnf2.assert(¬(not(p2))).unwrap();
484 assert_eq!(cnf2.num_vars(), 1, "¬¬P is just P");
485 }
486}