(in-package "PACO") ; Section: THE WATERFALL ; The waterfall is a simple finite state machine (whose individual ; state transitions are very complicated). Abstractly, each state ; contains a "processor" and transitions to one of two neighbor ; states, the "hit" state and the "miss" state. Roughly speaking, ; when we are in a state we apply its processor to the input clause ; and obtain either a "hit" signal (and some new clauses) or "miss" ; signal. We then transit to the appropriate state and continue. ; However, the "hit" state for every state is the top of the falls. ; +<---------------------------------------+ ; | | ; apply-hints-clause ---------------------->| ; | | ; simplify-clause ------------------------->| ; | | ; settled-down-clause---------------------->| ; | | ; ... | ; | | ; induct-clause --------------------------->+ ; | ; UNPROVED ; We therefore represent a state s of the waterfall as a pair whose car ; is the processor for s and whose cdr is the miss state for s. The hit ; state for every state is the constant state below, which includes, by ; successive cdrs, every state below it in the falls. ; Because the word "STATE" has a very different meaning in ACL2 than we have ; been using thus far in this discussion, we refer to the "states" of the ; waterfall as "ledges" and basically name them by the processors on each. ; Observe that the cdr of the 'simplify-clause ledge, for example, is the ; 'settled-down-clause ledge, etc. That is, each ledge contains the ; ones below it. ; Note: To add a new processor to the waterfall you must add the ; appropriate entry to the *waterfall* and redefine ; apply-waterfall-process below. ; The waterfall builds a proof attempt, represented by (defrec proof-attempt (process depth-to-go clause-id input-clause input-hist input-pspv output-signal . children)) ; A proof attempt is a tree of proot-attempt nodes. The process is ; one of the waterfall processes or else INDUCT. The depth-to-go ; records how much recursion depth is left in the waterfall -- there ; is no reason to believe that repeated simplification will ultimately ; terminate. Input-clause, input-hist and input-pspv record the input ; to the processor. The world is always constant and is not part of ; the proof-attempt. Output-signal records the output signal produced ; by the processor. Finally, children is the recursively obtained ; list of proof-attempts for each child produced by the processor. ; The legal signals are HIT, MISS, ABORT or anything else. The last ; indicates an error and is actually handled like an ABORT: further ; processing of the children is not pursued, but ongoing computations for the ; other higher- and peer-subgoals continues uninterrupted. ; About Hints: The pspv contains :hint-settings, which is an alist ; pairing clause ids with hint information. To apply hints, we first ; determine whether there is a pair for the given clause id. If there is, ; we may change the clause (i.e., adding a hypothesis as per a :USE ; hint). We may change the :rewrite-constant in the pspv (i.e., to ; install a new enabled structure). We also change the :hint-settings ; in the pspv, removing the hint just used. We also change the hist, ; adding an APPLY-HINTS-CLAUSE entry. ; Here is a generic hint-settings ; ((id . ((:USE . (term1 term2 ... termk)) ; a list of terms to add as hyps ; (:ENS . ens) ; a new enabled structure ; (:INDUCT . term) ; a term to suggest an induction ; )) ; ...) (defun apply-hints-clause1 (alist cl pspv) ; Here, alist is the alist pairing :keys to values. That alist was ; paired in the hint-settings with the id of clause cl. We apply that ; alist to cl and pspv, obtaining (mv cl' pspv'). (cond ((endp alist) (mv cl pspv)) (t (let ((key (car (car alist))) (val (cdr (car alist))) (rcnst (access prove-spec-var pspv :rewrite-constant))) (case key (:USE (apply-hints-clause1 (cdr alist) (disjoin-clauses (dumb-negate-lit-lst val) cl) pspv)) (:IN-THEORY (apply-hints-clause1 (cdr alist) cl (change prove-spec-var pspv :rewrite-constant (change rewrite-constant rcnst :ens val)))) (:INDUCT (apply-hints-clause1 (cdr alist) cl (change prove-spec-var pspv :induct-hint-val val))) (:HANDS-OFF (apply-hints-clause1 (cdr alist) cl (change prove-spec-var pspv :rewrite-constant (change rewrite-constant rcnst :fns-to-be-ignored val)))) (:EXPAND (apply-hints-clause1 (cdr alist) cl (change prove-spec-var pspv :rewrite-constant (change rewrite-constant rcnst :expand-lst val)))) (:DO-NOT (apply-hints-clause1 (cdr alist) cl (change prove-spec-var pspv :do-not-processes val))) (otherwise (apply-hints-clause1 (cdr alist) cl pspv))))))) (defun apply-hints-clause (id cl hist pspv wrld) ; This is a standard waterfall processor. In fact, it is the first ; processor in the waterfall. As for all processors, the results are ; (mv signal cl-set hist-obj new-pspv). ; The idea here is to recognize whether id has an associated hint in ; the pspv and if so to apply the hint. Typically a hint might change ; the clause and the pspv, e.g., adding hyps to one and deleting the ; hint from the other after modifying the :rewrite-constant. (declare (ignore hist wrld)) (let ((temp (assoc-equal id (access prove-spec-var pspv :hint-settings)))) (cond (temp (mv-let (new-cl new-pspv) (apply-hints-clause1 (cdr temp) cl (change prove-spec-var pspv :hint-settings (delete1-equal temp (access prove-spec-var pspv :hint-settings)))) (mv 'HIT (list new-cl) nil new-pspv))) (t (mv 'MISS nil nil nil))))) ; Here is the function that applies a waterfall processor. Note that ; if the pspv contains an :induct-hint-val other than :DO-NOT-INDUCT, ; then every processor except induct-clause is a no-op. If the hint ; is :DO-NOT-INDUCT we just take the natural course through the ; waterfall and fail when (if) we get to induction. If an induction ; hint of T or a term was provided, we skip other processes and go ; straight to induction. ; This is another identity function used for nume tracking. But it is ; not cut from the standard mold because it also prints a message ; about the just-completed waterfall process. (defun apply-waterfall-process (process id cl hist pspv wrld) (cond ((member-eq process (access prove-spec-var pspv :do-not-processes)) (mv 'miss nil nil nil)) ((and (access prove-spec-var pspv :induct-hint-val) (not (equal (access prove-spec-var pspv :induct-hint-val) :do-not-induct)) (not (equal process 'induct-clause))) (mv 'miss nil nil nil)) (t ; This is a weird call of my macro. Push a new empty frame. If the ; result is HIT, then print the stuff indicated and pop the frame with flg t ; else don't print and pop the frame with flg nil. ; The problem is that I don't have any assurance that the "printing" code ; prints instead of doing some arbitrary unsound raw lisp thing. ( (case process (apply-hints-clause (apply-hints-clause id cl hist pspv wrld)) (simplify-clause (simplify-clause id cl hist pspv wrld)) (settled-down-clause (settled-down-clause id cl hist pspv wrld)) (eliminate-destructors-clause (eliminate-destructors-clause id cl hist pspv wrld)) (induct-clause (induct-clause id cl hist pspv wrld)) (otherwise (mv 'abort nil (list (cons :UNKNOWN-PROCESS process)) pspv))))))) (defconst *waterfall* '(apply-hints-clause simplify-clause settled-down-clause eliminate-destructors-clause induct-clause )) ; Guide to the Waterfall Code: The top-level function is waterfall. ; It trickles down the ledges, trying each process until it comes to ; one that HITs. When that happens, it calls waterfall-lst on the ; children. That function computes the new clause id for each child ; and calls waterfall on each one. The lex4 measure is used, but only ; the first 3 components are relevant. The last is always 0; I didn't ; have lex3. (acl2::set-well-founded-relation e0-ord-<) (mutual-recursion (defun waterfall (ledge id cl hist pspv wrld nnn) (declare (xargs :measure (lex4 (nfix nnn) (acl2-count ledge) 0 0) :hints (("Goal" :in-theory (disable apply-waterfall-process))))) (cond ((or (endp ledge) (zp nnn)) (make proof-attempt :process 'UNPROVED :depth-to-go nnn :clause-id id :input-clause cl :input-hist hist :input-pspv pspv :output-signal (if (zp nnn) 'TIMEOUT 'UNPROVED) :children nil)) (t (mv-let (signal cl-set hist-alist new-pspv) (apply-waterfall-process (car ledge) id cl hist pspv wrld) (case signal (HIT (let ((new-hist (cons (make history-entry :processor (car ledge) :clause cl :alist hist-alist) hist)) ; Some hints are not inherited from their parents. This is the place ; we reset the un-inherited hints. At the moment, the only such hint ; is the :do-not one. If that were inherited and the user, say, ; turned off simplify-clause for a given goal, it would stay off for ; all children until turned back on. (new-pspv (change prove-spec-var new-pspv :do-not-processes nil))) (make proof-attempt :process (car ledge) :depth-to-go nnn :clause-id id :input-clause cl :input-hist hist :input-pspv pspv :output-signal 'HIT :children (waterfall-lst id cl-set new-hist new-pspv wrld (- nnn 1))))) (MISS (waterfall (cdr ledge) id cl hist pspv wrld nnn)) (otherwise ; Typically the only other signal is ABORT, which means the proof has ; failed and the input-clause is an unproved subgoal. But the signal ; might be an arbitrary "error message". (let ((new-hist (cons (make history-entry :processor (car ledge) :clause cl :alist hist-alist) hist))) (make proof-attempt :process (car ledge) :depth-to-go nnn :clause-id id :input-clause cl :input-hist new-hist :input-pspv pspv :output-signal signal :children nil)))))))) (defun waterfall-lst (id0 clauses hist pspv wrld nnn) (declare (xargs :measure (lex4 (nfix nnn) (acl2-count *waterfall*) (acl2-count clauses) 0))) (cond ((endp clauses) nil) (t (cons (waterfall *waterfall* (append id0 (list (len clauses))) (car clauses) hist pspv wrld nnn) (waterfall-lst id0 (cdr clauses) hist pspv wrld nnn))))) ) (mutual-recursion (defun scan-proof-attempt1 (p errors subgoals) ; We explore a proof-attempt p and collect into errors every error signal ; or other anomaly and collect into subgoals every unproved ; subgoal. Each new element is collected in a keyword pair: ; (:SUBGOAL id clause-as-formula) ; (:ERROR signal) ; We return (mv errors subgoals). (cond ((endp p) (mv (cons '(:ERROR ILL-FORMED) errors) subgoals)) ((eq (access proof-attempt p :process) 'UNPROVED) (mv errors (cons (list :SUBGOAL (access proof-attempt p :clause-id) (prettyify-clause (access proof-attempt p :input-clause))) subgoals))) ((member (access proof-attempt p :process) *waterfall*) (cond ((eq (access proof-attempt p :output-signal) 'HIT) (scan-proof-attempt1-lst (access proof-attempt p :children) errors subgoals)) (t (mv (cons (list :ERROR 'UDS (access proof-attempt p :output-signal)) errors) subgoals)))) (t (mv (cons (list :ERROR 'UDF (access proof-attempt p :process)) errors) subgoals)))) (defun scan-proof-attempt1-lst (plst errors subgoals) (cond ((endp plst) (mv errors subgoals)) (t (mv-let (errors subgoals) (scan-proof-attempt1 (car plst) errors subgoals) (scan-proof-attempt1-lst (cdr plst) errors subgoals))))) ) (defun scan-proof-attempt (p) (mv-let (errors subgoals) (scan-proof-attempt1 p nil nil) (cond ((and (null errors) (null subgoals)) :QED) (t (list :FAILURE (cons :ERRORS errors) (cons :SUBGOALS subgoals)))))) (mutual-recursion (defun condense-proof-attempt1 (p) ; We assume there are no errors in the scanned proof attempt. (cond ((consp p) (list* (access proof-attempt p :process) (list :SUBGOAL (access proof-attempt p :clause-id) (prettyify-clause (access proof-attempt p :input-clause))) (condense-proof-attempt1-lst (access proof-attempt p :children)))) (t nil))) (defun condense-proof-attempt1-lst (plst) (cond ((endp plst) nil) (t (cons (condense-proof-attempt1 (car plst)) (condense-proof-attempt1-lst (cdr plst)))))) ) (defun condense-proof-attempt (p) (let ((temp (scan-proof-attempt p))) (cond ((eq temp :QED) (condense-proof-attempt1 p)) ((and (consp temp) (null (cdr (assoc-eq :ERRORS (cdr temp))))) (condense-proof-attempt1 p)) (t temp)))) (defun describe-proof-attempt (p d-level) (case d-level (0 (scan-proof-attempt p)) (1 (condense-proof-attempt p)) (otherwise p))) ; THE PROVER (defun prove1 (term pspv wrld waterfall-depth) (waterfall *waterfall* nil (list term) nil pspv wrld waterfall-depth)) (defun make-initial-pspv (ens hint-settings) (make prove-spec-var :rewrite-constant (make rewrite-constant :expand-lst nil :terms-to-be-ignored-by-rewrite nil :top-clause nil :current-clause nil :ens ens :current-literal nil) :induct-hint-val nil :induction-hyp-terms nil :induction-concl-terms nil :do-not-processes nil :hint-settings hint-settings :global-ens ens)) ; The following function is still the identity function. But it is ; not cut from the standard mold. After doing the standard nume ; tracking and evaluating body, it prints all the numes in the ; top-most frame of the nume stack. It first converts them to runes. (defun prove (term ens wrld hint-settings waterfall-depth) ( (prove1 term (make-initial-pspv ens hint-settings) wrld waterfall-depth)))