;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- File logic/prop.lisp ;;;; Propositional Logic (defstructure prop-kb "A simple KB implementation that builds a big conjoined sentence." ;; The sentence slot will be, e.g., (and P (not Q) R (or S T) ...) (sentence (make-exp 'and))) (defstructure truth-table symbols ; The propositional symbols sentences ; Sentences that head the columns rows ; Lists of t or nil truth values ) ;;;; Tell, Ask, and Retract (defmethod tell ((kb prop-kb) sentence) "Add a sentence to a propositional knowledge base." (push (logic sentence) (args (prop-kb-sentence kb))) t) (defmethod ask-each ((kb prop-kb) query fn) "Ask a propositional knowledge base if the query is entailed by the kb." (when (eq (validity (make-exp '=> (prop-kb-sentence kb) (logic query))) 'valid) (funcall fn +no-bindings+))) (defmethod retract ((kb prop-kb) sentence) "Remove a sentence from a knowledge base." ;; This only retracts sentences that were explicitly told to the kb. (deletef sentence (args (prop-kb-sentence kb)) :test #'equal) t) ;;;; Other Useful Top-Level Functions (defun validity (sentence) "Return either VALID, SATISFIABLE or UNSATISFIABLE." (let* ((table (build-truth-table (logic sentence) :short t)) (rows (truth-table-rows table))) (cond ((every #'last1 rows) 'valid) ((some #'last1 rows) 'satisfiable) (t 'unsatisfiable)))) (defun truth-table (sentence) "Build and print a truth table for this sentence, with columns for all the propositions and all the non-trivial component sentences. Iff the sentence is valid, the last column will have all T's. Example: (truth-table '(<=> P (not (not P))))." (print-truth-table (build-truth-table (logic sentence)))) ;;;; Auxiliary Functions (defun eval-truth (sentence &optional interpretation) "Evaluate the truth of the sentence under an interpretation. The interpretation is a list of (proposition . truth-value) pairs, where a truth-value is t or nil, e.g., ((P . t) (Q . nil)). It is an error if there are any propositional symbols in the sentence that are not given a value in the interpretation." (cond (interpretation (eval-truth (sublis interpretation sentence) nil)) ((eq sentence 'true) t) ((eq sentence 'false) nil) ((atom sentence) (error "No interpretation for ~A." sentence)) (t (case (op sentence) (or (some #'eval-truth (args sentence))) (and (every #'eval-truth (args sentence))) (not (not (eval-truth (arg1 sentence)))) (=> (or (eval-truth (arg2 sentence)) (not (eval-truth (arg1 sentence))))) (<=> (eq (eval-truth (arg1 sentence)) (eval-truth (arg2 sentence)))) (otherwise (error "Unknown connective ~A in ~A" (op sentence) sentence)))))) ;; Note: a more efficient implementation of interpretations would be ;; a sequence of n propositional symbols and a number from 0 to (2^n)-1. ;; Symbol i is true iff bit i in the number is 1. ;;;; Truth Tables (defun build-truth-table (sentence &key short) "Build a truth table whose last column is the sentence. If SHORT is true, then that is the only column. If SHORT is false, all the sub-sentences are also included as columns (except constants)." (let* ((symbols (prop-symbols-in sentence)) (sentences (if short (list sentence) (append symbols (complex-sentences-in sentence))))) (make-truth-table :symbols symbols :sentences sentences :rows (compute-truth-entries symbols sentences)))) (defun print-truth-table (table &optional (stream t)) "Print a truth table." (let* ((headers (mapcar #'sentence-output-form (truth-table-sentences table))) (width (+ (* 2 (length headers)) (sum headers #'length)))) ;; Each sentence is printed as a column header, surrounded by 2 spaces (print-dashes width stream t) (format stream "~{ ~A ~}~%" headers) (print-dashes width stream t) (dolist (row (truth-table-rows table)) (mapcar #'(lambda (entry header) (print-centered (if entry "T" "F") (+ 2 (length header)) stream)) row headers) (format stream "~%")) (print-dashes width stream t))) (defun compute-truth-entries (symbols sentences) "Compute the truth of each sentence under all interpretations of symbols." (mapcar #'(lambda (interpretation) (mapcar #'(lambda (sentence) (eval-truth sentence interpretation)) sentences)) (all-truth-interpretations symbols))) (defun all-truth-interpretations (symbols) "Return all 2^n interpretations for a list of n symbols." (if (null symbols) (list nil) (let ((symbol1 (first symbols))) (mapcan #'(lambda (sub-rest) `(((,symbol1 . false) . ,sub-rest) ((,symbol1 . true) . ,sub-rest))) (all-truth-interpretations (rest symbols)))))) (defun prop-symbols-in (sentence) "Return a list of all the propositional symbols in sentence." (cond ((member sentence '(true false)) nil) ((atom sentence) (list sentence)) (t (delete-duplicates (mapcan #'prop-symbols-in (args sentence)) :from-end t)))) (defun complex-sentences-in (sentence) "Return a list of all non-atom sub-sentences of sentence." (cond ((atom sentence) nil) (t (delete-duplicates (nconc (mapcan #'complex-sentences-in (args sentence)) (list sentence)))))) (defun sentence-output-form (sentence) "Convert a prefix sentence back into an infix notation with brief operators." (format nil "~{~A~^ ~}" (mklist (sublis '((and . "^") (not . "~") (or . "|")) (prefix->infix sentence)))))