;;; -*- Mode: LISP; Syntax: Common-lisp; Package: DTP; Base: 10 -*- ;;; This file was stolen from Matt Ginsberg's MVL 3/8/93 (in-package "DTP") (defparameter *logical-operators* '(<= => <=> and or not)) (defun sentence-to-cnf (sentence) "Rewrite constant predicates, then call CNF" (when (find (car sentence) *logical-operators*) (setq sentence (cons (car sentence) (mapcar #'rewrite-predicate (cdr sentence)) ))) (cnf sentence) ) (defun rewrite-predicate (predicate) "P becomes (P), and (NOT Q) becomes (NOT (Q)), else unchanged" (cond ((atom predicate) (list predicate) ) ((and (eq 'not (first predicate)) (atom (second predicate)) ) (list 'not (list (second predicate))) ) (t predicate ))) (defun simplify-dnf (dnf) (if (and (eq (car dnf) 'or) (null (cddr dnf)) ) (second dnf) dnf )) ;;;---------------------------------------------------------------------------- ;; stuff to manipulate propositions ;; functions defined: ;; ;; cnf (p) returns conjunctive normal form ;; dnf (p) disjunctive normal form ;; standardize-operators removes => <= <=> if iff ;; negate (p) negates it ;; cnf-to-logic (cnf) inverts cnf ;; dnf-to-logic (dnf) inverts dnf ;; normal-form (p) returns something suitable for stating ;; conjunctive normal form. Remove the nonstandard operators from p and ;; then look at (car p): ;; 1. If it's NOT, then (cnf p) is the result of negating each term ;; inside (dnf (not (p))). ;; 2. If it's OR, then we have to combine the results of cnf'ing each ;; of the disjuncts. cnf-merge-lists does this, one disjunct at a ;; time. ;; 3. If it's AND, we just call the cnf'er and nconc the results ;; together. ;; 4. If it's none of these, it must be a term and we return ((p)). (defun cnf (p) (case (car (setq p (standardize-operators p))) (not (napcar #'(lambda (l) (napcar #'negate l)) (dnf (second p)))) (or (let (ans) (dolist (item (cdr p) (nreverse ans)) (setq ans (cnf-merge-lists (cnf item) ans))))) (and (mapcan #'cnf (cdr p))) (otherwise (list (list p))))) ;; given a partial cnf expression d, and a new cnf exp c to be merged, ;; construct a list of all entries that have an entry from d followed by ;; one from c. As we go through, the list is maintained backwards, so ;; that we have terms with entries late in d early in the returned ;; answer. Three cases: ;; 1. If c is NIL, there is nothing to do. Return d. ;; 2. If d is NIL, there is still nothing to do, but we reverse c to ;; get d into "backwards" form. ;; 3. Otherwise, work through c and for each entry in it, work through ;; d, appending each entry of d onto that entry of c and pushing the ;; result onto the answer being accumulated. Note that this maintains ;; the "backwardness" of the answer. We make sure that each term in ;; the final answer is a fresh copy. (defun cnf-merge-lists (c d) (cond ((null c) d) ((null d) (nreverse c)) (t (let (ans) (dolist (item c ans) (push (append (car d) item) ans) (dolist (x (cdr d)) (push (append x (copy-list item)) ans))))))) ;; remove nonstandard connectives from p. Handles =>, <=, <=>, if and ;; iff. ;; 1. (=> p1 ... pn q) means (if (and p1 ... pn) q), or ;; (or (not (and p1 ... pn)) q). ;; 2. (<= q p1 ... pn) means (if (and p1 ... pn) q) as well. ;; 3. (if p q) means (or (not p) q) ;; 4. (iff p q) means (or (and p q) (and (not p) (not q))); <=> is ;; similar. (defun standardize-operators (p) (case (car p) (|=>| `(or (not (and . ,(butlast (cdr p)))) ,(car (last p)))) (|<=| `(or ,(second p) (not (and . ,(cddr p))))) (if `(or ,(third p) (not ,(second p)))) ((|<=>| iff) `(or (and ,(second p) ,(third p)) (and ,(negate (second p)) ,(negate (third p))))) (otherwise p))) ;; dnf is a lot like cnf. (defun dnf (p) (case (car (setq p (standardize-operators p))) (not (napcar #'(lambda (l) (napcar #'negate l)) (cnf (second p)))) (and (let (ans) (dolist (item (cdr p) (nreverse ans)) (setq ans (cnf-merge-lists (dnf item) ans))))) (or (mapcan #'dnf (cdr p))) (otherwise (list (list p))))) ;; negate a sentence. If it begins with not, return what's left. ;; Otherwise put a not on the front and return that. (defun negate (p) (if (eq 'not (car p)) (cadr p) (list 'not p))) ;; utility functions to turn a cnf expression, or a conjunct/disjunct, ;; into a logical sentence. They're all simple -- if the thing is a ;; list, push "and" or "or" on the front and proceed. Otherwise, just ;; return it. (defun cnf-to-logic (cnf) (conj-to-logic (mapcar #'disj-to-logic cnf))) (defun dnf-to-logic (dnf) (disj-to-logic (mapcar #'conj-to-logic dnf))) (defun conj-to-logic (list) (if (cdr list) (cons 'and list) (car list))) (defun disj-to-logic (list) (if (cdr list) (cons 'or list) (car list))) ;; *if-translation* controls how if is converted. If bc (the default), ;; two backward-chaining versions are created. If fc, two ;; forward-chaining versions. If mix, (if a b) translates to (<= b a) ;; and (=> a b). (defparameter *if-translation* 'bc) ;; *equivalence-translation* controls how <=> is converted. If bc (the ;; default), two backward-chaining versions are created. If fc, two ;; forward-chaining versions. If mix, (<=> a b) translates to (<= a b) ;; and (=> a b). (defparameter *equivalence-translation* 'bc) ;; normal form. Depends on (car p): ;; => invokes nf-forward ;; <= invokes nf-backward ;; if is treated by examining *if-translation* ;; <=>is treated by examining *equivalence-translation* ;; (or p1 ... pn) is turned into (<= p1 (not (and p2 ... pn))) ;; (iff a b) is (and (if a b) (if b a)) ;; everything else is treated by calling the cnf'er and then calling ;; disj-to-logic on each term (defun normal-form (p) (case (car p) (|=>| (nf-forward p)) (|<=| (nf-backward p)) (if (case *if-translation* (bc (nconc (nf-backward p) (nf-backward `(if (not ,(third p)) (not ,(second p)))))) (fc (nconc (nf-forward p) (nf-forward `(if (not ,(third p)) (not ,(second p)))))) (mix (nconc (nf-backward p) (nf-forward p))))) (|<=>| (case *equivalence-translation* (bc (nconc (nf-backward (cons '|<=| (cdr p))) (nf-backward (list '|<=| (third p) (second p))))) (fc (nconc (nf-forward (cons '|=>| (cdr p))) (nf-forward (list '|=>| (third p) (second p))))) (mix (nconc (nf-backward (cons '|<=| (cdr p))) (nf-forward (cons '|=>| (cdr p))))))) (or (if (cddr p) (normal-form `(<= ,(second p) (not (and . ,(cddr p))))) (normal-form (second p)))) (iff (nconc (normal-form (cons 'if (cdr p))) (normal-form (list 'if (third p) (second p))))) (and (mapcan #'normal-form (cdr p))) (otherwise (napcar #'disj-to-logic (cnf p))))) ;; normal form for forward-chaining. Convert to cnf, then for each ;; term (or p1 ... pn), rewrite it as p1 (if n=1) or ;; (=> (not p1) ... (not pn-1) pn) (defun nf-forward (p) (napcar #'(lambda (x) (if (cdr x) `(=> ,.(napcar #'negate (butlast x)) ,(car (last x))) (car x))) (cnf p))) ;; backward-chaining. Convert to cnf, then for each term (or p1 ... pn), ;; rewrite it as p1 (if n=1) or (<= p1 (not p2) ... (not pn)) (defun nf-backward (p) (napcar #'(lambda (x) (if (cdr x) `(<= ,(car x) . ,(napcar #'negate (cdr x))) (car x))) (cnf p)))