Arguing for program correctness and writing correct programs Saying things about states, programs Program state s1: x=4, y=-1.5, A={ me, you, he Assertions about program states x=3 False in s1 (y=x) x>=0 True in s1 Programs connect pairs of states e.g. x := x+4 leads from s1 to a state where x is now 9 Assertions about programs {Q code {P FOR ALL STATES s,s : if Q was true in starting state s, and code finishes in state s, then R mus be true in ending state s {x>3 x := x+4 {x>6 {x>3 x := x+4 {x>10 true false (e.g.,start state when x=4) ABorgida 1 ABorgida 2 Instead of Easier notation in my lecture notes {x=3 x := x+4 {x>6 we will write // x = 3 x := x + 4 // x > 6 We use x := x+4 for the Java assignment statement x = x+4; because otherwise equality = in logical formulas is confused with = in assignment statements. Reasoning about programs in general //PREcondition: what you assume in order for the program to work correctly //POSTcondition: what you want your program to ensure/ accomplish ABorgida 3 ABorgida 4
Reasoning about assignment statements BACKWARDS! The weakest condition that holds before an assignment x := e which guarantees condition P afterwards is { P with x replaced by e // (y + x) z > (y+x)/2 y := y + x; // y z > y/2 (A condition p is weaker than q, if q implies p. e.g. y>1 implies y>0 so y>0 is weaker than y>1 ) Proof for a Hoare assertion about assignment Therefore, to prove {Q x := e {P step #1 {P with x replaced by e x := e {P step #2 >>> show Q implies {P with x replaced by e using algebra,logic //PRE y > 0 >>>> show y> 0 implies y+4=1 // y + 4 > 1 x:= 4; // y + x > 1 y := y + x; // y > 1 ABorgida 5 ABorgida 6 (Aside, only for those who care: why the rule {P x :=e {P with x replaced by e is wrong ) Try reasoning forward from PRE to POST here //PRE: y = 4 y := y + 3; // y + 3 = 4 >>> y = 4-3 // POST: y= 1 Initial state: y will be 4 in memory This would be the result of applying the forward rule But in the final state the above program y will be 7, not 1!!! (Aside, only for those who care: why Epp s way of using x old and x new does not work in general ) Try reasoning forward from PRE to POST here //PRE: y = 4 // y old = 4 y := y + 1; // y new = y old + 1 y := y + 1; // y new = y old + 1 which leaves y new = 5, not 6 as desired. One would have to go on to use ( y new ) new etc. ABorgida 7 ABorgida 8
Simple example Reasoning about Loops with Invariants //PRE: true //POST: sum = 1+...+25 Unfortunately, there is no weakest precondition for loops, which you can drive back, as for assignment. ABorgida 9 ABorgida 10 What is a loop invariant? A loop invariant INV is an assertion about relationship between program variables. It is used like P(n) in induction, but n is counting the number of times thru the loop, so it doesn t appear in our formulas. //PRE: What is assumed for the program to work <Set-up> // need INV true here [Prove Basis P(0)] while ( TEST CONDITION C) { // have INV here [assume I.H. P(k)] <Loop body> // need INV here [Prove P(k+1)] // therefore have INV here [Have proven by induction Forall n P(n) //POST: What is desired at the end (Between the top and bottom of the loop, headway is being made towards reaching the loop's goal. This might disturb (make false) the invariant. The point of Loop Invariants is the promise that the invariant will be What is a loop invariant? Augment the preceding with knowledge about how WHILE loop works by testing the loop condition //PRE: What is assumed for the program to work <Set-up> // need INV here while ( TEST CONDITION C) { // have INV here & C <Loop body> // need INV here // therefore have INV here & ( C) //POST: What is desired at the end restored before repeating the loop body each time.) ABorgida 12 ABorgida 11
Loop invariant in pictures A formal statement INV about the relationship between variables in your program. needs to be proven true just before the loop is ever run (establishing the invariant) [Induction basis] assume/have it true at the top of loop, when you enter it [Induction Hypothesis] needs to be proven true again at the bottom of the loop, after the loop body is executed (maintaining the invariant). infer at the exit from the loop it is still true! (by induction on # of times thru loop) enter loop body body body exit INV INV INV loop INV & C & C & C & C because we got into the body because we exited ABorgida 14 ABorgida 13 Simple example (cont d) //PRE: //POST: sum = 1+2+...+25 1. Think about why program works? Trace it 2. Find an INV 3. Then show post condition is implied by INV 4. Then show the loop body maintain INV (it is an invariant) 5. Then show the set-up establishes INV. Simple example guessing an invariant by tracing //PRE: //POST: sum = 1+...+25 k sum 0 0 1 1 = 0 + 1 2 3 = 0 +1 + 2 3 6 = 0 +1 + 2 + 3 Where INV belongs in this program //have PRE : << just before loop begins //need INV: sum = 0 +... + k (prove) << just after loop entry //have INV&: (sum=0+... +k)&(k 25) (assume) INV: sum = 0 +... + k << at end of loop body //need INV: sum = 0 +... + k (prove) << just after loop exit //have INV+: sum = 0+... +k) & k=25 (proven) //need POST: sum = 1+...+25 (prove) ABorgida 15 ABorgida 16
Proof obligations //PRE: true // INV: sum = 0 +... + k (prove true here) #A // INV&: sum = 0 +... + k & k 25 (assume here) #B // INV: sum = 0 +... + k (prove true here) #C // INV+: sum = 0 +... + k & k=25 (proven by now) #D //need POST: sum = 1+...+25 We will not be able to go top-down, because the assignment := rule is backwards/bottom-up. So, we will have to show #D implies POST push #C backwards through the body, and then show #B implies it push #A backwards through the initialization, and show PRE implies it ABorgida 17 i) Prove POST implied //PRE: true #1 // INV: sum = 0 +... + k (prove) #4 // INV : sum = 0 +... + k &(k 25) (assume) #5 // INV: sum = 0 +... + k (prove) #8 // INV : sum = 0 +... + k & (k=25) (have) #9 >>> show #9! #10: just substitute 25 for k, & drop 0 //POST: sum = 1+2+...+25 (prove) #10 ABorgida 18 ii) Push backward invariant in loop body using assignment //PRE: true // INV: sum = 0 +... + k (prove) // INV : sum = 0 +... + k &(k 25) (assume) ii) Push backward invariant (cont d) //PRE: true // INV: sum = 0 +... + k (prove) // INV : sum = 0 +... + k &(k 25) (assume) // sum + k := 0 +... + k // INV: sum = 0 +... + k (prove) // INV : sum = 0 +... + k & (k=25) >>> #9! #10 //need POST: sum = 1+2+...+25 // sum + (k+1) = 0 +... + (k+1) // sum + k := 0 +... + k // INV: sum = 0 +... + k (prove) // INV : sum = 0 +... +k & (k = 25) have >>> #9! #10 //need POST: sum = 1+2+...+25 ABorgida 19 ABorgida 20
iii) Prove #5 implies #6 //PRE: true #1 iv) Push INV at #4 back //PRE: true #1 // INV: sum = 0 +... + k (prove) #4 // INV : sum = 0 +... + k &(k 25) (assume) #5 >>>show #5! #6: subtract (k+1) from both sides of #6 // sum + (k+1) = 0 +... + (k+1) #6 // sum + k := 0 +... + k #7 // INV: sum = 0 +... + k (prove) #8 // INV : sum = 0 +... + k & (k=25) (have) #9 >>> #9! #10: //POST: sum = 1+2+...+25 (prove) #10 ABorgida 21 // k = 0 +... + k #3 // INV: sum = 0 +... + k (prove) #4 // INV&: sum = 0 +... + k &(k 25) (assume) #5 >>> #5! #6 // sum + (k+1) = 0 +... + (k+1) #6 // sum + k := 0 +... + k #7 // INV: sum = 0 +... + k (prove) #8 // INV+: sum = 0 +... + k & (k=25) (have) #9 >>> #9! #10: //POST: sum = 1+2+...+25 (prove) #10 ABorgida 22 iv) Push INV at #4 back (cont d) //PRE: true #1 // 0 = 0 +... + 0 #2 // k = 0 +... + k #3 // INV: sum = 0 +... + k (prove) #4 // INV&: sum = 0 +... + k &(k 25) (assume) #5 >>> #5! #6 // sum + (k+1) = 0 +... + (k+1) #6 // sum + k := 0 +... + k #7 // INV: sum = 0 +... + k (prove) #8 // INV+: sum = 0 +... + k & (k=25) (have) #9 >>> #9! #10: //POST: sum = 1+2+...+25 (prove) #10 ABorgida 23 v) Show #2 is implied by #1 //PRE: true #1 >>> show #1! #2: obviously true // 0 = 0 +... + 0 #2 // k = 0 +... + k #3 // INV: sum = 0 +... + k (prove) #4 // INV&: sum = 0 +... + k &(k 25) (assume) #5 >>> #5! #6 // sum + (k+1) = 0 +... + (k+1) #6 // sum + k := 0 +... + k #7 // INV: sum = 0 +... + k (prove) #8 // INV+: sum = 0 +... + k & (k=25) (have) #9 >>> #9! #10: //POST: sum = 1+2+...+25 (prove) #10 ABorgida 24
DONE! ABorgida 25 Identical proof for 25 replaced by N //PRE: true #1 >>>#1! #2: obviously true // 0 = 0 +... + 0 #2 // k := 0 +... + k #3 // INV: sum = 0 +... + k (prove) #4 while (k!= N) { // INV&: sum = 0 +... + k &(k N) (assume) #5 >>> #5! #6: subtract (k+1) from both sides of #6 // sum + (k+1) = 0 +... + (k+1) #6 // sum + k := 0 +... + k #7 // INV: sum = 0 +... + k (prove) #8 // INV+: sum = 0 +... + k & (k=n) (have) #9 >>> #9! #10: substitute N for k //POST: sum = 1+2+...+N (prove) #10 ABorgida 26 Can we find mistakes using invariants? Lets swap the two lines inside the loop: //PRE: //POST: sum = 1+...+25 k sum 0 0 1 0 2 1 = 0 +1 3 3 = 0 +1 + 2 4 6 = 0 + 1 + 2 + 3 INV : sum = 0 +... + (k-1) Does INV (k=25) imply POST? INV (k=25) is sum = 0+...+(25-1) which does not imply POST!! Bad program. (Proof gives you a hint on how to fix it: (k!=26) ABorgida 27 Multiplication program 1 //PRE: true // Strategy: 1. Find a reasonable INV 2. Check it guarantees POST, once the loop stops 3. Then show the loop body maintains INV 4. And then show the set-up establishes INV (Notation: on assignments, etc we will use function( int m, int n) to indicate that m and n are inputs, and will not change these.) ABorgida 29
// PRE: true y:= y + n; x:= x + 1; // Multiplication program 1 There are infinitely many (useless) loop invariants! look for something that when the loop ends guarantees the needed condition afterwards; understand how code works. (Repeated addition Trace the loop to see what values variables take, and so what conditions relate them.also ask yourself why should this program work x y n m 0 0 1 n * Conjecture: INV is (y=x*n). 2 2n 3 3n ABorgida 30 Multiplication program 1 proof pattern After guessing an invariant INV, we will fill the following pattern: >>> prove PRE implies H H) is INV established before loop? G) F) need INV E) have INV & (x!= m) >>> prove E implies D D) C) B) need INV A) have INV & (x=m) >>> prove A implies POST is INV maintained by the loop body? is POST established by INV& C? ABorgida 31 Multiplication program 1:fill in proof pattern >>> prove PRE implies H H) G) >>> prove E implies D D) C) Multiplication program 1:fill in proof pattern >>> prove PRE -> H H) G) >>> prove E implies D D) C) need y=(x+1)*n >>> prove A implies POST y=x*n & x=m y=m*n >>> A implies POST ABorgida 33 ABorgida 34
Multiplication program 1:fill in proof pattern >>> prove PRE -> H H) G) >>> prove E implies D D) need (y+n)=(x+1)*n C) need y=(x+1)*n >>> A implies POST Multiplication program 1:fill in proof pattern >>> prove PRE -> H H) G) >>> prove E implies D D) need (y+n)=(x+1)*n C) need y=(x+1)*n >>> A implies POST D is y+n=x*n+n; subtract n from both sides, leaving y=x*n E implies this. QED ABorgida 35 ABorgida 36 Multiplication program 1:fill in proof pattern >>> prove PRE -> H H) G) need y=0*n >>> E implies D D) need (y+n)=(x+1)*n C) need y=(x+1)*n >>> A implies POST Multiplication program 1:fill in proof pattern >>> prove PRE -> H H) need 0=0*n G) need y=0*n >>> E implies D D) need (y+n)=(x+1)*n C) need y=(x+1)*n >>> A implies POST ABorgida 37 ABorgida 38
Multiplication program 1:fill in proof pattern >>> prove PRE implies H H) need 0=0*n G) need y=0*n >>> E implies D D) need (y+n)=(x+1)*n C) need y=(x+1)*n >>> A implies POST True 0=0 QED Summary of approach 1. Guess at an invariant INV such that INV& Cond! POST 2. Leave blank lines between code text lines 3. Work your way up filling in the blanks using Hoare or regular logic //have PRE: >>> algebra/logic proof <intialization code> //need INV: while (Cond) { // have INV & Cond: >>> algebra/logic proof <loop body> // need INV: // have INV & Cond >>> algebra/logic proof // need POST: ABorgida 39 ABorgida 40 Summary of proof for multiplication 1 have PRE: True >>> Prove True! 0=0*n [algebra] need 0 = 0*n need y=0*n need INV: y=x*n have INV & C: y=x*n & x!=m >>> prove (y=x*n & x!=m)! y+n=(x+1)*n [algebra] need y+n = (x+1)*n need y=(x+1)*n need INV: y=x*n have INV & ~C == y=x*n & ~(x!=m) >>> prove previous line! POST [algebra] Another multiplication program 2 PRE: 0 <= m while (x < m) { trace the loop to see what values variables take, and so what conditions relate them x y 0 0 1 n 2 2n Conjecture: INV is still (y=x*n). Verify that INV /\ ~(x < m) implies y=m*n -- see next page ABorgida 41 ABorgida 42
multiplication program 2 Finding the right invariant that implies POST Conjecture: INV is (y=n*x). Verify that INV & (x < m) (y=n*x) & (x>=m) implies y=m*n OUCH! It does not! So we need to strengthen the invariant so it makes x=m. Can do so by adding (x <= m) to INV, because this together with (x>=m) yields x=m. Let INV in this case be (y=n*x) & (x <= m) multiplication program 2 proof outline PRE:??? >>> show PRE -> A A) B) C) need INV: y=x*n & x<=m while (x < m) { D) have INV & C: y=x*n & x<=m & x<m >>> show D -> D D ) E) & x<=m G)have INV & ~C: y=x*n & x<=m & ~(x < m) >>> show G -> POST (done) Then INV & (x>=m) does imply x=m, and hence y=m*n, POST ABorgida 44 ABorgida 43 multiplication program 2 push back invariant PRE:??? need y=x*n & x<=m while (x < m) { //D: have y=x*n & x<=m & x<m //>>> show D! D using algebra // D : need (y+n)=(x+1)*n & (x+1) <= m // E: need y=(x+1)*n & (x+1) <= m // F: need y=x*n & x<=m have y=x*n & x<=m & ~(x < m) multiplication program 2 push back invariant PRE:??? need y=x*n & x<=m while (x < m) { //D: have y=x*n & x<=m & x<m //>>> show D! D using algebra // D : need (y+n)=(x+1)*n & (x+1) <= m // E: need y=(x+1)*n & (x+1) <= m // F: need y=x*n & x<=m have y=x*n & x<=m & ~(x < m) y=x*n implies y+n=x*n+n; if x and m are integers, then x<m implies x+1<=m QED ABorgida 45 ABorgida 46
mult program 2 - establish INV before loop PRE: //??? >>> prove PRE -> A A // 0=0*n & 0<=m // y=0*n & 0<=m // INV:need y=x*n & x<=m while (x < m) { // now y=x*n & x<=m & x<m // need (y+n)=(x+1)*n & (x+1) <= m // need y=(x+1)*n & (x+1) <= m // need y=x*n & x<=m // now y=x*n & x<=m & ~(x < m) // So PRE needs to ensure (0<=m) ABorgida 47 PRE: true while (x <= m) { multiplication program 3 trace the loop to see what values variables take, and so what conditions relate them x y Conjecture: 0 0 INV3 is INV2: (y=n*x) & (x<=m) 1 n Verify that INV2 /\ ~(x <= m) imply y=m*n. 2 2n (y=n*x) & (x<=m) & x>m false which implies anything. But sign of something amiss ABorgida 48 PRE:??? multiplication program 3 while (x <= m) { // have INV2 & (x<=m): (y=x*n & x<=m) & x<=m // need INV: y=x*n & x<=m here INV2 & ~(x<=m): (y=x*n & x<=m) & ~(x <= m) Try to prove that INV2 is in fact loop invariant PRE:??? multiplication program 3 OOPS: Logic & algebra do not give P4->P3!!! INV2 is not a loop invariant! while (x <= m) { // INV : have y=x*n & x<=m & x<=m >>>> prove that P4 P3 using algebra // P3: need (y+n)=(x+1)*n & (x+1) <= m // P2: need y=(x+1)*n & (x+1) <= m // INV: need y=x*n & x<=m here y=x*n & x<=m & ~(x <= m) ABorgida 49 ABorgida 50
PRE:??? multiplication program 3 OOPS: Logic & algebra do not give P4->P3!!! INV2 is not a loop invariant! while (x <= m) { // INV : have y=x*n & x<=m & x<=m >>>> prove that P4 P3 using algebra // P3: need (y+n)=(x+1)*n & (x+1) <= m // P2: need y=(x+1)*n & (x+1) <= m // INV: need y=x*n & x<=m here y=x*n & x<=m & ~(x <= m) What does this program compute? y=(m+1)*n. So there should be no loop invariant to show that it computes y=m*n Programs with counter pattern k := <init> while (k!= m) {... k := k + 1 Such programs tend to make an invariant hold for a range of values from <init> to k, so that at the end of the loop, when k=m, it holds for an entire range. ABorgida 51 ABorgida 52