Correctness Example, CPSC 331, Fall 2010

home page -  news -  syllabus -  schedule -  assignments -  tutorials -  tests -  java -  references -  Mike Jacobson


 Proving the Correctness of a Simple Program

An Example Problem

Recall that the lecture notes on proving correctness of algorithms included the exercise of proving the correctness of the following algorithm.

Precondition: n is a positive integer
Postcondition: n is unchanged and sum is the sum of the first n positive integers.
Algorithm: i := 1
sum := 1
while (i < n) do
  i := i+1
  sum := sum + i
end while

The process that one can follow to develop this proof, and the assertions that should be used as intermediate assertions (including loop invariants), and the loop variant for the loop in this algorithm, are described below.

Establishing Partial Correctness

  1. Getting Started

    Notice, first, that there is only one rule that can be applied — this is a sequence of two (or more) statements

    S1; S2

    You can actually apply this rule in numerous ways, because the program is a sequence of (at least) three statements. For example, one might choose to consider S1 to be just the first statement, or you (taking the other extreme) one might choose S1 to include everything but the last statement.

    We will try to use the organization of the program itself to make a decision about this: Notice that it includes a pair of statements that initialize variables used later on, followed by a loop that does most of the work. We will consider S1 to be the “initialization” stage (again, consisting of the first two statements), so that S2 is everything else (that is, the loop).

    Applying the Rule

    At this point, along with our program, we have the given precondition

    p:   n is a positive integer

    and the given postcondition, which is essentially as follows.

    q:    n is unchanged, and sum is the sum of the first n positive integers

    In order to proceed we must discover an “intermediate assertion,” r, and then establish each of the following:

    1. {p} S1 {r}
    2. {r} S2 {q}

    Now, if you are dealing with somebody else’s program (and you don’t know why it works) and the program is poorly documented (as is the case — deliberately — for this example) then it will not necessarily be clear what this intermediate assertion is! Of course, it is really not very reasonable to expect you (some unfortunate person who has been given the algorithm) to be able to “prove it to be correct” anyway!

    This is all supposed to be applied in one of the following two other situations:

    1. You already know why the algorithms works — and you are using the tools we are discussing to write a proof down (in a somewhat standard, and somewhat formal way). In this case, you should know what “r” is.

    2. You are trying to discover the reasons why the algorithm works, but the algorithm you have been given is properly documented (or a proof of its correctness has also been supplied in some other form). In this case the assertion”r” is written down somewhere (it should be part of the inline documentation) — unless your program is only one or two lines long, and it is obvious what the assertion should be.

    Now, as mentioned above, this example program is not sufficiently documented! A few of the intermediate assertions needed to establish correctness should be included as documentation here, and are not. Subsequent examples, presented after the first exercise (or two) about correctness, will generally be better documented than this algorithm is.

    On the other hand, this particular assertion would probably not be included in the better documented version of the algorithm anyway, because somebody who is a bit more familiar with all this (one hopes, like most students in this course after another week or two of study and practice) will be able to figure out what the needed assertion is, on her or his own!

    The assertion that we will need to use, to continue, is as follows.

    r:   n is a positive integer (whose value has not been changed), i is equal to 1, and sum is also equal to 1 (so that sum is equal to the sum of the first i positive integers).

    This is more “verbose” than the assertions we will generally use — one probably wouldn’t include the parenthetical remark at the end, in practice.

  2. Establishing Partial Correctness of the First Subprogram

    At this point, we have the same precondition as before,

    p:   n is a positive integer

    The postcondition that we should use, when considering the first condition, is the assertion that we identified as our “intermediate assertion” during the previous step:

    r:   n is a positive integer (whose value has not been changed), i is equal to 1, and sum is also equal to 1 (so that sum is equal to the sum of the first i positive integers).

    Once again, our program is a sequence of two programs, so we can consider it as

    S1, 1; S1, 2

    In this case there is no choice about what to call S1, 1, because our whole (sub)program only includes two statements — S1 1 is the first of these statements, and S1, 2 is the second.

    Establishing the partial correctness of this program is simple enough for it to be left as an exercise.

  3. Establishing Partial Correctness of the Second Subprogram

    At this point we should using the “intermediate assertion” from the first step as our precondition:

    r:   n is a positive integer (whose value has not been changed), i is equal to 1, and sum is also equal to 1 (so that sum is equal to the sum of the first i positive integers).

    Since this subprogram is the last part of the entire program, we must use the postcondition that we were given as the postcondition for this subprogram as well:

    q:    n is unchanged, and sum is the sum of the first n positive integers

    This time our program has a different structure — it is a loop with the form

    while (c) do S2 end while

    where c is the test “i < n” and S2 is a sequence of two statements.

    As the lecture notes indicate we must continue by finding a “loop invariant” — a statement that holds the beginning of every execution of the loop body.

    This is an appropriate place to repeat something that has already stated above:

    This program is not sufficiently documented.

    This is deliberate — it has been done in hopes that it help students to see why documentation is important — but the following should also be pointed out:

    Students will be required to document their code on assignments!

    In particular: Loop invariants (and loop variants) will be required as in-line documentation for all loops included in code written for the first assignment!

    This standard might be relaxed — somewhat — for later assignments. However, all assertions that are not likely to be obvious to others, that are needed to prove correctness, should be included in documentation later on, as well. This will frequently include loop invariants and loop variants.

    For this example, the following can be used.

    Loop Invariant:

    1. n is a positive integer (whose value has not been changed by this algorithm)
    2. i is a positive integer such that 1 ≤ i < n
    3. sum is the sum of the first i positive integers

    We will call this loop invariant “sbefore” in the rest of this discussion.

    We will also need to identify a condition that is satisfied at the end of each execution of the loop body. (This does not necessarily need to be included in documentation, but you might choose to do so if you think that it woud be helpful.) In this case, the following should be used.

    Satisfied at the End of Each Execution of the Loop Body:

    1. n is a positive integer (whose value has not been changed by this algorithm)
    2. i is a positive integer such that 1 ≤ i ≤ n
    3. sum is the sum of the first i positive integers

    Please read this carefully and note that it is not the same as the loop invariant! The relationship between i and n, given in the second condition, has changed slightly.

    We will call this condition “safter” in the rest of this example.

    Consulting the lecture notes, we see that it is now necessary to do each of the following things in order to prove that sbefore really is a loop invariant:

    1. Prove that

      (r ∧ (c)) ⇒ sbefore

      — which establishes that the loop invariant holds the first time the loop body is executed, if it is ever executed at all

    2. Prove that

      { sbefore } S2 { safter }

      — that is, establish the partial correctness of the loop body for the assertions that have now been identified

    3. Prove that

      (safter ∧ (c)) ⇒ sbefore

      — which establishes that if the expected conditions holds at the end of the kth execution of the loop body, for a positive integer k, and the loop test is satisfied, then the precondition holds once again just before the loop is executed for the k+1st time

    Now, since c is the test “i < n, the statements that one needs to prove in steps (i) and (iii) above are (or, at least, should be) obvious. The second is another “partial correctness” problem which will be considered shortly.

    Consulting our notes once again, we see that we need to do three things to establish partial correctness of this loop:

    1. Prove that

      (r ∧ ¬(c)) ⇒ q

      — which establishes that the postcondition is satisfied at the end, if the loop body never gets executed at all

    2. Prove that sbefore is a loop invariant for this loop — which we’ve just finished doing, above

    3. Prove that

      (safter ∧ ¬(c)) ⇒ q

      — which establishes that if the loop is executed for a kth time, for a positive number k, and the loop test is not satisfied (so that the loop halts after that), then the postcondition is satisfied at this point, as well.

    Notice that since n is a positive integer, n ≥ 1. Since c is the test “i < n,” it should be clear that n is equal to one if the loop body never gets executed at all and, more in all other cases, that i = n immediately after the final execution of the loop body. A comparison of spost to q should be sufficient for you to see that the statements one needs to verify in steps 1 and 3, above, are true (and fairly obvious).

  4. Establishing Partial Correctness of the Loop Body

    Once again, the loop body is a sequence of two statements.

    The following “intermediate assertion” might not be obvious to students who are seeing all this material for the first time — but it is a good exercise (to make sure that you understand the proof rule for partial correctness of an assignment statement) to confirm that it can be used.

    Satisfied after Execution of the First Statement in the Loop Body:

    1. n is a positive integer (whose value has not been changed by this algorithm)
    2. i is a positive integer such that 1 ≤ i ≤ n
    3. sum is the sum of the first i−1 positive integers
    We'll refer to this assesrtion as Smid below.

    Exercise: Complete the proof of partial correctness of the loop body using the information provided above.

Establishing Termination

  1. Getting Started

    To establish termination one begins in much the same way as we did when considering partial correctness: Break the program down into two pieces.

    Consulting the lecture notes we see that we need to prove each of the following, where p and r are the assertions that were considered during partial correctness.

    1. Prove that if p is satisfied and S1 is executed then this algorithm terminates.
    2. Prove that if r is satisfied and S2 is executed then this program terminates.

    Each of these are considered, below.

  2. Proving Termination of the First Subprogram

    Notice that this is a sequence of programs, once again, so that the same proof rule must be applied. After that, notice that there is virtually nothing more that one must do — except to recall (and mention) that assignment statements (that do not call methods that haven’t been verified themselves, yet) always terminate.

  3. Proving Termination of the Second Subprogram

    Consulting the lecture notes, one sees that it is necessary to establish a loop variant for this loop. We mentioned and discarded a few examples before considerig the function

    fL(n, i) = ni

    Three properties were to be established. These are mentioned, and verified, below.

    1. This is an integer-valued function of the program’s inputs and other variables. This should be obvious.

    2. Each execution of the loop body decreases the value of this function by at least one. This is easy to show: Notice that the value of the variable n is not modified, while i is incremented — so that, in fact, the value of this function is decreased by exactly one every time the loop body is executed.

    3. If the value of the function is zero or negative then the loop test is not satisfied. This is pretty clear, too, since the loop test is “i < n

    As the notes state, we may now conclude that the loop halts — and, furthermore that the initial value of the loop variant, n−1, is an upper bound on the number of times that the loop body is executed when the loop is.

Putting Everything Together: The Proof

Here, we extract all the details from the explanation above and present the proof. This proof is roughly what would be expected on written assignment questions and tests, but it assumes that the various assertions used have been defined as above.
  1. Partial Correctness: note throughout that the variable n is never modified in the code, so the clause about n not being modified can be assumed in all assertions.

    1. {p} S1 {r}. After S1 is executed, i and sum have both been assigned the value 1, so {r} holds. (Could also break this down and prove each statement separately, but his is not really necessary.)
    2. {r} S2 {q}. We prove the following
      • (r &and ¬(c)) &rArr q. In the context of this algorithm, ¬(c) means that i=n, which combined with r implies that sum is the sum of the first n positive integers as required.
      • Sbefore is a loop invariant:
        • (r &and c) &rArr Sbefore. Because r states that i=1, and since c is true, we have that i < n; thus 1 &le i < n. Also, r states that sum=1, which is equal to the sum of the first i=1 positive integers.
        • {Sbefore} S2 {Safter}. We first prove that {Sbefore} i = i+1 {Smid}. By the substitution rule, we need to prove Sbefore &rArr {i is a positive integer such that 1 &le i+1 &le n; sum is the sum of the first integers}. Note that Sbefore states that 1 &le i < n, and this implies 1 &le i+1 &le n. The remaining clauses are the same in both assertions.

          Next, we prove that {Smid} sum = sum +i {Safter}. Again, by the substitution rule we can prove Smid &rArr {sum+i is the sum of the first i positive integers} (note that we exclude the other two clauses of Smid and Safter because the are the same in both assertions). Smid states that sum is the sum of the first i-1 positive integers; thus sum+i is the sum of the first i positive integers as required.

        • {Safter &and (c) &rArr Sbefore. Because Safter states that 1 &le i &le n, and c states that i < n, we have 1 &le i < n as required.
      • (Safter &and ¬(c) &rArr q. As before, ¬(c) means that i=n, and when combined with Safter we have that sum is the sum of the first n positive integers as required.
  2. Termination: We need to prove that fL(n,i) = n-i is a loop variant for the while loop, assuming that the preconditions {r} of the loop hold. We argue as follows:

    • {r} states that both n and i are integers, so fL(n,i) is an integer-valued function.
    • The loop does not modify n and the value of i increases by 1 after every iteration. Thus, the value of the function decreases by 1 after every iteration.
    • If fL(n,i) = n-i &le 0, then we have i &ge n and the loop test of i < n will fail.
    Thus, fL(n,i) is a loop variant by definition, and the loop terminates.
Thus, the program is partially correct and terminates, and is therefore correct.
Last updated:
http://www.cpsc.ucalgary.ca/~jacobs/cpsc331/F10/handouts/lecture03-example.html