; Correctness of Fib
; Problem: Define an M1 program to compute the Fibonacci function on its
; natural number input. Prove your program correct.
; Fib(0) = 0
; Fib(1) = 1
; Fib(2) = Fib(1)+Fib(0)
; Fib(3) = Fib(2)+Fib(1)
; ...
; However, M1 does not have recursion or method call, so you have to do this
; iteratively. When you verify your helper function you will have to think of
; the generalization that characterizes its output for all legal inputs, not
; just the initial case. It also means that when you verify your loop code
; you'll have to specify the final values of ALL of the locals you're using. I
; suggest you define the function fib-locals to return the list of the final
; values of all of the locals, rather than worry about the closed form
; expression of them.
; Design Plan: Think of the fib sequence 0, 1, 1, 2, 3, 5, 8, ... and arrange
; two auxiliary variables, j and k, to hold the first two values, 0 and 1,
; respectively. This is a ``window'' into the sequence and it is possible to
; slide the window to the right by shifting k into j and adding k to the old
; value of j to get the new value of k. So fib can be computed by sliding the
; window up n times. I control the slide by counting n down to 0 by 1. I will
; express this algorithm more precisely (and formally) when I verify it!
; (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 fib (n)
(if (zp n)
0
(if (equal n 1)
1
(+ (fib (- n 1))
(fib (- n 2))))))
(defun theta (n)
(fib n))
; (2) Write your algorithm. This will consist of a tail-recursive helper
; function and a wrapper, fn.
(defun helper (n j k)
(if (zp n)
j
(if (equal n 1)
k
(helper (- n 1) k (+ j k)))))
(defun fn (n) (helper n 0 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.
; Important Note: When you verify your helper function, you must consider the
; most general case. For example, if helper is defined with formal parameters
; n, j, and k and fn calls helper initializing j and k to 0 and 1, your helper
; theorem must be about (helper n j k), not just about the special case (helper
; n 0 1).
; It takes some creativity to figure out what the helper function does for
; arbitrary j and k (instead of the initial 0 and 1). I figured it out by
; drawing a little table for unknown j and k but n=7:
; n j k
; 7 j k <--- unknown initial values
; 6 k j+k
; 5 j+k j+2k
; 4 j+2k 2j+3k
; 3 2j+3k 3j+5k
; 2 3j+5k 5j+8k
; 1 5j+8k 8j+13k
; Do you recognize the coefficients on j and k in the final line?
(defthm helper-is-theta
(implies (and (ok-inputs n)
(natp j)
(natp k)
(<= 1 n))
(equal (helper n j k)
(+ (* (fib (- n 1)) j)
(* (fib n) k)))))
(defthm fn-is-theta
(implies (ok-inputs n)
(equal (fn n)
(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 0) ; 0
(istore 1) ; 1 j = 0
(iconst 1) ; 2
(istore 2) ; 3 k = 1
(iload 0) ; 4 loop:
(ifeq 16) ; 5 if n=0, goto exitj
(iload 0) ; 6
(iconst 1) ; 7
(isub) ; 8
(ifeq 14) ; 9 if n=1, goto exitk
(iload 0) ; 10
(iconst 1) ; 11
(isub) ; 12
(istore 0) ; 13 n=n-1
(iload 2) ; 14 save k on stack
(iload 1) ; 15
(iload 2) ; 16
(iadd) ; 17
(istore 2) ; 18 k=j+k
(istore 1) ; 19 j= saved k
(goto -16) ; 20 goto loop
(iload 1) ; 21 exitj: return j
(halt) ; 22
(iload 2) ; 23 exitk: return k
(halt)) ; 24
)
; (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)
(if (zp n)
(repeat 'TICK 3)
(if (equal n 1)
(repeat 'TICK 7)
(ap (repeat 'TICK 17)
(loop-sched (- n 1))))))
(defun sched (n)
(ap (repeat 'TICK 4)
(loop-sched n)))
; (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.
; Hint: Define the function fib-locals to return the list of the final values
; of all of the locals. We could struggle to come up with closed-form expressions
; for the final values of j and k but there's no need. We're not interested --
; but to verify the loop we have to specify what they are, somehow.
(defun fib-locals (n j k)
(if (zp n)
(list n j k)
(if (equal n 1)
(list n j k)
(fib-locals (- n 1) k (+ j k)))))
; Important Note: Your lemma about the loop must consider the general case.
; For example, if the loop uses the locals n, j, and k, you must characterize
; its behavior for arbitrary, legal n, j, and k, not just a special case (e.g.,
; where j and k are 0 and 1 respectively).
(defthm loop-is-helper
(implies (and (ok-inputs n)
(natp j)
(natp k))
(equal (run (loop-sched n)
(make-state 4
(list n j k)
nil
*pi*))
(make-state (if (equal n 0) 22 24)
(fib-locals n j k)
(push (helper n j k) 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 (if (equal n 0) 22 24)
(fib-locals n 0 1)
(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 (if (equal n 0) 22 24)
(fib-locals n 0 1)
(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))
(fib n))))
: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 (fib n) on top of the stack. Note that the algorithm used by *pi*
; is not specified or derivable from this formula.