; Correctness of an Even/Odd Program
; Problem: Define an M1 program to compute 1 if n
; is even (i.e., n mod2 is 0) and 0 if n is odd.
; Prove it correct.
; Design Plan: Use an auxiliary variable, bit, as
; a flag (valued 0 or 1, where 1 means true).
; We'll count n down by 1 and flip the bit each
; time. So if the bit starts at 1 and we flip it
; an even number of times it will end at 1. I.e.,
; if we return the bit, we are answering the
; question "is n even?".
; (0) Start ACL2
; (include-book "m1-lemmas")
(in-package "M1")
; (1) Write your specification, i.e., define the
; expected inputs and the desired output, theta.
(defun ok-inputs (n)
(natp n))
(defun theta (n)
(if (equal (mod n 2) 0)
1
0))
; (2) Write your algorithm. This will consist of
; a tail-recursive helper function and a wrapper,
; fn.
(defun helper (n bit)
(if (zp n)
bit
(helper (- n 1)
(if (equal bit 0) 1 0))))
(defun fn (n) (helper n 1))
; (3) Prove that the algorithm satisfies the spec,
; by proving first that the helper is
; appropriately related to theta and then that fn
; is theta on ok inputs.
(defthm helper-is-theta
(implies (and (ok-inputs n)
(or (equal bit 0)
(equal bit 1)))
(equal (helper n bit)
(if (equal (theta n) 1)
bit
(if (equal bit 0) 1 0)))))
(defthm fn-is-theta
(implies (ok-inputs n)
(equal (fn n) ; = (helper n 1)
(theta n))))
; Disable these two lemmas because they confuse
; the theorem prover when it is dealing with the
; code versus fn.
(in-theory (disable helper-is-theta fn-is-theta))
; (4) Write your M1 program with the intention of
; implementing your algorithm.
(defconst *pi*
'((ICONST 1) ; 0
(ISTORE 1) ; 1 bit = 1;
; loop:
(ILOAD 0) ; 2
(IFEQ 12) ; 3 if n=0, goto
(ILOAD 0) ; 4
(ICONST 1) ; 5
(ISUB) ; 6
(ISTORE 0) ; 7 n = n-1;
(ILOAD 1) ; 8
(IFEQ 3) ; 9 if bit=0, goto
(ICONST 0) ;10
(GOTO 2) ;11
; put-1:
(ICONST 1) ;12
(ISTORE 1) ;13 bit = (flip bit);
(GOTO -12) ;14 goto loop
; end:
(ILOAD 1) ;15
(HALT))) ;16 ``return'' bit;
; (5) Define the ACL2 function that schedules your
; program, starting with the loop schedule and
; then using it to schedule the whole program.
; The schedule should take the program from pc 0
; to a HALT statement. (Sometimes your schedules
; will require multiple inputs or other locals,
; but our example only requires the first local.)
(defun loop-sched (n bit)
(if (zp n)
(repeat 'TICK 3)
(if (equal bit 0)
(ap (repeat 'TICK 11)
(loop-sched (- n 1)
(if (equal bit 0) 1 0)))
(ap (repeat 'TICK 12)
(loop-sched (- n 1)
(if (equal bit 0) 1 0))))))
(defun sched (n)
(ap (repeat 'TICK 2)
(loop-sched n 1)))
(defun test (n)
(let ((sf (run (sched n)
(make-state 0 (list n) nil *pi*))))
(list (list :pc (pc sf) '--> (next-inst sf))
(list :locals (locals sf))
(list :stack (stack sf)))))
; (6) Prove that the code implements your
; algorithm, starting with the lemma that the loop
; implements the helper. Each time you prove a
; lemma relating code to algorithm, disable the
; corresponding schedule function so the theorem
; prover doesn't look any deeper into subsequent
; code.
; Important Note: Your lemma about the loop must
; consider the general case. For example, if the
; loop uses the locals n, m, and a, you must
; characterize its behavior for arbitrary, legal
; n, m, and a, not just a special case (e.g.,
; where a is 0).
(defthm loop-is-helper
(implies (and (ok-inputs n)
(or (equal bit 0) (equal bit 1)))
(equal (run (loop-sched n bit)
(make-state 2
(list n bit)
nil
*pi*))
(make-state 16
(list 0 (helper n bit))
(push (helper n bit) nil)
*pi*))))
(in-theory (disable loop-sched))
(defthm program-is-fn
(implies (ok-inputs n)
(equal (run (sched n)
(make-state 0
(list n)
nil
*pi*))
(make-state 16
(list 0 (fn n))
(push (fn n) nil)
*pi*))))
(in-theory (disable sched))
; (7) Put the two steps together to get
; correctness.
(in-theory (enable fn-is-theta))
(defthm program-correct
(implies (ok-inputs n)
(equal (run (sched n)
(make-state 0
(list n)
nil
*pi*))
(make-state 16
(list 0 (theta n))
(push (theta n)
nil)
*pi*))))
; This corollary just shows we did what we set out
; to do:
(defthm total-correctness
(implies (and (natp n)
(equal sf (run (sched n)
(make-state 0
(list n)
nil
*pi*))))
(and (equal (next-inst sf) '(HALT))
(equal (top (stack sf))
(if (equal (mod n 2) 0)
1
0))))
:rule-classes nil)
; Think of the above theorem as saying: for all
; natural numbers n, there exists a schedule (for
; example, the one constructed by (sched n)) such
; that running *pi* with (list n) as input
; produces a state, sf, that is halted and which
; contains 1 or 0 on top of the stack depending on
; whether n is even or odd. Note that the
; algorithm used by *pi* is not specified or
; derivable from this formula.