;;;; First Order Logic (FOL) Tell, Retract, and Ask-Each (defstruct fol-kb ;;; A FOL (First-Order Logic) KB stores clauses. ;;; Access to the KB is via POSSIBLE-RESOLVERS, which takes a ;;; literal (e.g. (not D), or B), and returns all the clauses that ;;; contain the literal. We also keep a list of temporary clauses, ;;; added to the KB during a proof and removed at the end. Internally, ;;; clauses are in minimal-cnf format, which is CNF without the and/or. ;;; So (and (or P Q) (or R (not S))) becomes ((P Q) (R (not S))) (positive-clauses (make-hash-table :test #'eq)) (negative-clauses (make-hash-table :test #'eq)) (temp-added nil)) (defmethod tell ((kb fol-kb) sentence) "Add a sentence to a FOL knowledge base." (for each clause in (->minimal-cnf sentence) do (tell-minimal-cnf-clause kb clause))) (defmethod retract ((kb fol-kb) sentence) "Delete each conjunct of sentence from KB." (retract-minimal-cnf-clauses kb (->minimal-cnf sentence))) (defmethod ask-each ((kb fol-kb) query fn) "Use resolution to decide if sentence is true." (prove-by-refutation kb (->minimal-cnf `(not ,query)) fn)) ;;;; FOL Knowledge Base Utility Functions (defun possible-resolvers (kb literal) "Find clauses that might resolve with a clause containing literal." (if (eq (op literal) 'not) (gethash (op (arg1 literal)) (fol-kb-negative-clauses kb)) (gethash (op literal) (fol-kb-positive-clauses kb)))) (defun tell-minimal-cnf-clause (kb clause) ;; We don't add tautologies like "P | ~P". ;; It would be good to eliminate subsumed clauses like ;; Eq(1,1) when Eq(x,x) is already in the kb. ;; Currently we don't check for that. (unless (tautology? clause) (for each literal in clause do (if (eq (op literal) 'not) (push clause (gethash (op (arg1 literal)) (fol-kb-negative-clauses kb))) (push clause (gethash (op literal) (fol-kb-positive-clauses kb))))))) (defun retract-minimal-cnf-clauses (kb clauses) "Remove the minimal-cnf clauses from the KB." (for each clause in clauses do (for each literal in clause do (if (eq (op literal) 'not) (deletef clause (gethash (op (arg1 literal)) (fol-kb-negative-clauses kb))) (deletef clause (gethash (op literal) (fol-kb-positive-clauses kb))))))) (defun ->minimal-cnf (sentence) "Convert a logical sentence to minimal CNF (no and/or connectives)." ;; E.g., (and (or P (not Q) R) S) becomes ((P (not Q) R) (S)) ;; Everything internal in the FOL module uses minimal-cnf ;; Only tell, retract, and ask-* use the regular logical form. (mapcar #'disjuncts (conjuncts (->cnf sentence)))) (defun undo-temp-changes (kb) "Undo the changes that were temporarilly made to KB." (retract-minimal-cnf-clauses kb (fol-kb-temp-added kb)) (setf (fol-kb-temp-added kb) nil)) (defun tautology? (clause) "Is clause a tautology (something that is always true)?" (some #'(lambda (literal) (and (eq (op literal) 'not) (member (arg1 literal) clause :test #'equal))) clause)) ;;;; Functions for Resolution Refutation Theorem Proving (defun prove-by-refutation (kb sos fn) "Try to prove that ~SOS is true (given KB) by resolution refutation." ;; Call FN on every substitution that leads to a proof. ;; Similar to OTTER [p. 311], the KB plays the role of the usable ;; (background) axioms, and SOS (set of support) is formed by the ;; negation of the query. Uses set of support heuristic and uses ;; shorter clauses first (which is a generalization of the unit ;; preference strategy). Filters out tautologies. (setf sos (sort sos #'< :key #'length)) (undo-temp-changes kb) (let (clause) (loop (when (null sos) (RETURN nil)) ;; Move clause from SOS to the usable KB (setf clause (pop sos)) (tell-minimal-cnf-clause kb clause) (push clause (fol-kb-temp-added kb)) ;; Process everything that resolves with CLAUSE (for each literal in clause do (for each r in (possible-resolvers kb literal) do (let ((b (unify ??? literal))) (when b (setf sos (insert clause sos #'< :key #'length)) (case (length clause) (0 (funcall fn b)) ;; refutation found!! ; should look for unit refutation if length is 1 )))))))) (defun resolve (literal clause) "Resolve a single literal against a clause" ) (defun insert (item list pred &key (key #'identity)) (merge 'list (list item) list pred :key key))