;;;; -*- mode:Lisp; syntax:Common-Lisp; package:user -*- ;;;;
;;;; Copyright 1992 Patrick H. Winston.  All rights reserved.
;;;; Version 1.1.1, copied from master file on 23 Apr 93       
;;;; 
;;;; This software is licensed by Patrick H. Winston (licensor) for
;;;; instructional use with the textbooks ``Artificial Intelligence,'' by
;;;; Patrick H. Winston, and ``Lisp,'' by Patrick H. Winston and Berthold
;;;; K. P. Horn.  Your are free to make copies of this software and
;;;; modify it for such instructional use as long as:
;;;; 1. You keep this notice intact.
;;;; 2. You cause any modified files to carry a prominent notice stating
;;;;    that you modified the files and the date of your modifications.
;;;; This software is licensed ``AS IS'' without warranty and the licensor
;;;; shall have no liability for any alleged defect or damages.

;;;; VARIABLES

(defvar *clause-counter*)
(defvar *name-counter*)

;;;; RESOLUTION

(defun prove (&rest clauses &aux (level 0))
  "
  Purpose:	Prove theorems.
  Arguments:	A list of quoted clauses, including the negated theorem.
  Returns:	T if theorem is provable.
  Caveats:	Loops forever if theorem is not provable.
  Remarks:	Presumes everything has already been converted to clause form.
  "
  ;;Initialize the variable-nameing number:
  (setf *name-counter* 0)
  ;;Initialize the clause-numbering variable:
  (setf *clause-counter* (apply #'max (mapcar #'first clauses)))
  ;;Make all initial-clause variable unique:
  (setf clauses (mapcar #'make-variables-unique clauses))
  (format t "~%The initial clauses are:")
  (mapcar #'print-clause clauses)
  (catch 'exit
    (loop
      (format t "~%There are ~a clauses on level ~a."
	      (length clauses) (incf level))
      ;;Try to resolve all clause pairs, thus implementing the breadth-first
      ;;resolution strategy:
      (do* ((set1 clauses (rest set1))
	    (c1 (first set1) (first set1)))
	  ((endp set1))
	(do* ((set2 (rest set1) (rest set2))
	      (c2 (first set2) (first set2)))
	     ((endp set2))
	  #+comment
	  (format t "~%I am working on clause ~a and clause ~a."
		  (first c1) (first c2))
	  (let ((resolvents (resolve-clauses (remove-decorations c1)
					     (remove-decorations c2))))
	    (if (member nil resolvents)
		;;If a NIL resolvent is produced, the theorem is proved:
		(progn
		  (format t "~%I resolved ~a with ~a producing nil!"
			  (first c1) (first c2))
		  (format t "~%I proved the theorem!")
		  (format t "~%There were ~a clauses on ~a ~
			       levels at termination."
			  (length clauses)
			  level)
		  (throw 'exit t))
	      ;;Otherwise, add any new resolvents to the list of clauses:
	      (dolist (resolvent resolvents)
		(let ((result (insert-decorations resolvent)))
		  (format t "~%I resolved ~a with ~a producing"
			  (first c1) (first c2))
		  (print-clause result)
		  (push result clauses))))))))))

(defun resolve-literals (l1 l2)
  "
  Purpose:	Resolves two literals, if possible, using unification.
  Caveats:	Remember that UNIFY returns FAIL if the patterns do
		no unify, rather than NIL.  The NIL result means they
		unify, with an empty variable-binding list.
  "
  (let ((result (unify (list 'not l1) l2)))
    (if (eq 'fail result)
	(unify (list 'not l2) l1)
      result)))

(defun resolve-clauses (c1 c2 &aux resolvents)
  "
  Purpose:	Resolves two clauses, if possible.
  Remarks:	There may be zero, one, or more ways to resolve the clauses.
  "
  (dolist (l1 c1)
    (dolist (l2 (remove l1 c2))
      (let ((bindings (resolve-literals l1 l2)))
	(unless (eq 'fail bindings)
	  (push (make-variables-unique
		  (instantiate-variables 
		    ;;Delete resolving literals:
		    (append (remove l1 c1) (remove l2 c2))
		    ;;Instantiate with bindings produced by unification:
		    bindings))
		resolvents)))))
  resolvents)

;;;; AUXILIARY PROCEDURES

(defun remove-decorations (clause)
  "
  Purpose:	Get rid of syntactic sugar.
  Remarks:	Removes clause number and OR symbols (v).
  "
  (when (numberp (first clause))
    (setf clause (rest clause)))
  (remove 'v clause))

(defun insert-decorations (clause &optional &aux (result nil))
  "
  Purpose:	Insert syntactic sugar.
  Remarks:	Inserts clause number and OR symbols (v).
  "
  (setf clause (remove-decorations clause))
  (when clause
    (push (first clause) result)
    (dolist (literal (rest (remove 'v clause)))
      (push 'v result)
      (push literal result)))
  (cons (incf *clause-counter*) (reverse result)))

(defun print-clause (clause)
  "
  Purpose:	Prints a clause, more or less readably.
  Remarks:	Assumes clause has been syntactically sugared
		with a clause number and OR symbols (v).
  "
  #+comment
  (setf clause (make-variables-readable clause))
  (format t "~%(~a ~a" (first clause) (second clause))
  (do ((clause (nthcdr 2 clause) (nthcdr 2 clause)))
      ((endp clause))
    (format t "~%   ~a ~a" (first clause) (second clause))))

(defun make-variables-readable (clause)
  "
  Purpose:	Remove unique variable names, replacing them
		with leading characters in names---these should
		be the same as the ones that appear in the
		original clauses.
  "
  (setf clause (make-variables-unique clause))
  (let* ((variables (list-variables clause))
	 (letters
	   ;;Make substitution list:
	   (mapcar #'(lambda (variable)
		       (format nil "~c"
			       ;;Extract first character of printed form:
			       (char (format nil "~a" variable) 0)))
		   variables)))
    (instantiate-variables
	  clause
	  ;;Make binding list:
	  (mapcar #'(lambda (v l) (list v (list '? l)))
		  variables
		  letters))))

;;;; AUXILIARY PROCEDURES BORROWED FROM BACKWARD.LSP

(defun instantiate-variables (clause a-list)
  "
  Purpose:	Create unique variable names, basing them
		on characters that appear in the original
		clause.
  "
  (cond ((atom clause) clause)
        ((eq '? (first clause))
         (let ((binding (find-binding clause a-list)))
           (if binding
               (instantiate-variables (extract-value binding) a-list)
               clause)))
        (t (cons (instantiate-variables (first clause) a-list)
                 (instantiate-variables (rest clause) a-list)))))

(defun make-variables-unique (clause)
  "
  Purpose:	Avoids conflict resulting from having the same variable
		names appear in more than one rule.
  "
  (let* ((variables (list-variables clause))
	 (unique-variables
	   ;;Make substitution list:
	   (mapcar #'(lambda (variable)
		       ;;Generate a new name, based on first character of
		       ;;the given variable, plus a unique number:
		       (with-input-from-string
			 (input (format nil "~c-~a" 
					(aref (format nil "~a" variable) 0)
					(incf *name-counter*)))
			 (read input)))
		   variables)))
    (instantiate-variables
	  clause
	  ;;Make binding list:
	  (mapcar #'(lambda (v l) (list v (list '? l)))
		  variables
		  unique-variables))))

(defun list-variables (clause &optional names)
  "
  Purpose:	Creates a list of variables appearing in a clause.
  "
  (cond ((atom clause) names)
        ((eq '? (first clause))
         ;;Ignore anonymous variable:
         (if (or (eq '\_ (second clause))
                 (member (second clause) names))
             names
             (append names (rest clause))))
        (t (list-variables (rest clause)
                           (list-variables (first clause)
                                           names)))))






