SE 504
Negative-Positive Array Element Pair Counting: A Program Development Example Illustrating Heuristics for Obtaining Loop Invariants from Postconditions

Problem: Develop a program that, given a fixed array b[] of ints, counts the number of pairs of elements in which the first is non-positive and the other, appearing later, is non-negative. That is, develop a program that meets the following specification:


|[con b : array of int;
  var r : int;

  r := ?

  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0  ∧  b.j ≥ 0) }
]|

It is clear that the solution will require a loop. Hence, following the heuristic of "replacing a constant (in the postcondition) by a fresh variable", we replace #b by n to obtain the slightly stronger postcondition

Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0)   ∧   n = #b

to which we can apply the "remove a conjunct" heuristic to obtain as a candidate for loop invariant

I: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0)

The loop guard, B, should be the negation of the remaining conjunct, n ≠ #b. (By making such choices for I and B, we have [I ∧ ¬B  ⇒  Q'], which is item (iii) among our five proof obligations for a loop. In order to truthify I, we can initialize both n and r to zero. The obvious modification to make to n inside the loop is to increment it. Our program is thus

|[con b : array of int;
  var r : int;
  var n : int;
  n,r := 0,0;
  {loop invariant I: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0) }
  do n ≠ #b  --->
     r := E;
     { I(n:=n+1) }
     n := n+1;
  od
  {Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0)   ∧   n = #b }
  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0 ∧ b.j ≥ 0) }
]|

Our choice to increment n as the last command in the body of the loop (rather than simultaneously with the assignment to r) is somewhat arbitrary, but this is usually a good choice. Often, selection and/or loop commands must be involved in making updates (inside the body of a loop) to the variables other than the "loop control" variable, and so it is usually easier to think about the increment to the loop control variable (here, n) being made after all those other updates.

Notice the "intermediate assertion" in the middle of the loop body. This reflects the fact that the desired postcondition of r:=E is I(n:=n+1), which, by now, should be immediately apparent to you. In case it is not, here is the reasoning: In order to prove item (ii) of the loop checklist, we need {I ∧ B} r:=E; n:=n+1 {I}. According to the Hoare Triple Catenation Law, to prove this it suffices to find a predicate R such that both (a) {I∧B} r:=E {R} and (b) {R} n:=n+1 {I}. But here the choice for R is obvious: wp.(n:=n+1).I, which is I(n:=n+1). (See Hoare Triple Assignment Law and wp Assignment Law.) By making this choice, we get (b) "for free" and we get the weakest possible postcondition for (a).

Let's try to derive E by attempting to prove {I ∧ B} r:=E {I(n:=n+1)}. By the law relating Hoare Triples to wp, this is equivalent to

[I ∧ B  ==>  wp.(r:=E).(I(n:=n+1))]

We prove it by assuming the antecedant and showing the consequent:

Assume I and B.

    wp.(r:=E).I(n:=n+1)

 =    < wp assignment rule >

    (I(n:=n+1))(r:=E)

 =    < defn of I; textual sub. >

    E = (#i,j | 0≤i<j<n+1 : b.i ≤ 0  ∧  b.j ≥ 0) 

 =    < Split range (see Lemma 0 in the Appendix) >

    E = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0) +
        (#i,j | 0≤i<j=n : b.i ≤ 0  ∧  b.j ≥ 0)

 =    < simplifying 2nd quantification (see Lemma 1 in the Appendix) >

    E = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0) +
        (#i | 0≤i<n : b.i ≤ 0  ∧  b.n ≥ 0)

 =    < assumption I: r = (...) >

    E = r + (#i | 0≤i<n : b.i ≤ 0  ∧  b.n ≥ 0)

 =    < Notice that the 2nd conjunct of the body of the quantification does 
        not depend upon the value of dummy i.  Hence, there are two cases.
        If b.n ≥ 0 is true, (3.39) tells us that the quantification is
        equivalent to (#i | 0≤i<n : b.i ≤ 0).  However, if b.n ≥ 0 is 
        false, (3.40) tells us that the quantification is equivalent to 
        (#i | 0≤i<n : false), which has value zero.  Accordingly, we rewrite
        the quantification in terms of the "if" function: if(true,b,c) = b
        and if(false,b,c) = c.
        For a more complete account, see Lemma 2 in the Appendix.        >

    E = r +  if(b.n ≥ 0, (#i | 0≤i<n: b.i ≤ 0), 0)

 =    < strengthen invariant: s = (#i | 0≤i<n : b.i ≤ 0) >

    E = r +  if(b.n ≥ 0, s, 0)

We now have

|[con b : array of int;
  var r : int;
  var n : int;
  var s : int;
  n,r,s := 0,0,0;
  {loop invariant I: I0 ∧ I1, where
      I0: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0) and
      I1: s = (#i | 0≤i<n : b.i ≤ 0)
  }
  do n ≠ #b  --->
     r := r + if(b.n ≥ 0, s, 0);
     s := F;
     { I(n:=n+1) }
     n := n+1
  od
  {Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0)   ∧   n = #b }
  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0 ∧ b.j ≥ 0) }
]|

The proper initialization for s is obvious. Somewhat arbitrarily, we decided to place the assignment to s after that to r, because it seems unlikely that r will be needed inside F. We could have made the assignments simultaneous. However, it would not have been a good idea to place the assignment to s before the assignment to r, because we calculated the right hand side of the assignment to r based upon s having the same value then as it had at the beginning of the loop iteration!

Let us try to calculate F so that the relevant conjunct of I(n:=n+1) necessarily holds after the assignment s:=F:

    wp.(s:=F).(I1(n:=n+1))

 =    < wp assignment law >

    (I1(n:=n+1))(s:=F)

 =    < defn. of I1, textual sub. (twice) >

    F = (#i | 0≤i<n+1 : b.i ≤ 0)  

 =    < split range (8.16) >

    F = (#i | 0≤i<n : b.i ≤ 0)  +  (#i | i=n : b.i ≤ 0)

 =    < assumption s = (+i | 0≤i<n: b.i ≤ 0) >

    F = s + (#i | i=n : b.i ≤ 0)

 =    < defn of # quantifier >

    F = s + (+i | i=n ∧ b.i ≤ 0 : 1)

 =    < Substitution (Gries 3.84a) >

    F = s + (+i | i=n ∧ b.n ≤ 0 : 1)

 =    < See theorem at end of Lemma 2 in the Appendix >

    F = s + if(b.n ≤ 0, (+i | i=n  : 1), 0)

 =    < one-point rule (Gries 8.14); text. sub. >

    F = s + if(b.n ≤ 0, 1, 0)

This yields the program

|[con b : array of int;
  var r : int;
  var n : int;
  var s : int;
  n,r,s := 0,0,0;
  {loop invariant I: I0 ∧ I1, where
      I0: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0) and
      I1: s = (#i | 0≤i<n : b.i ≤ 0)
  }
  do n ≠ #b  --->
     r := r + if(b.n ≥ 0, s, 0);
     s := s + if(b.n ≤ 0, 1, 0);
     { I(n:=n+1) }
     n := n+1
  od
  {Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0)   ∧   n = #b }
  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0 ∧ b.j ≥ 0) }
]|

The usual definition of Dijkstra's Guarded Command Language doesn't include the "if" construct as used above. But it is clear how to translate it into our usual notation:

|[con b : array of int;
  var r : int;
  var n : int;
  var s : int;
  n,r,s := 0,0,0;
  {loop invariant I: I0 ∧ I1, where 
      I0: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0) and
      I1: s = (#i | 0≤i<n : b.i ≤ 0)
  }
  do n ≠ #b  --->
     if b.n ≥ 0  -->  r := r+s;
     [] b.n < 0  -->  r := r+0;
     fi
     if b.n ≤ 0  -->  s := s+1;
     [] b.n > 0  -->  s := s+0;
     fi
     { I(n:=n+1) }
     n := n+1
  od
  {Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0)   ∧   n = #b }
  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0 ∧ b.j ≥ 0) }
]|

By examining the guards of the two selection commands, it is clear that, if we separate the guards into b.n > 0, b.n = 0, and b.n < 0, we can combine the two commands as follows:

     if b.n > 0  -->  r, s := r+s, s+0;
     [] b.n = 0  -->  r, s := r+s, s+1;
     [] b.n < 0  -->  r, s := r+0, s+1;
     fi

Of course, assignments such as s:=s+0 have no effect and can be omitted. Doing so, the selection command can be rewritten as

     if b.n > 0  -->  r    := r+s;
     [] b.n = 0  -->  r, s := r+s, s+1;
     [] b.n < 0  -->  s    := s+1;
     fi

Finally, it is obvious that the program will terminate, as n is initialized to 0, is incremented during each loop iteration, and the loop terminates upon n reaching value #b. (The obvious bound function is #b - n.) To prove items (iv) and (v) rigorously, we will need to include in the loop invarant the extra conjunct 0≤n≤#b. We are left with the program

|[con b : array of int;
  var r : int;
  var n : int;
  var s : int;
  n,r,s := 0,0,0;
  {loop invariant I: I0 ∧ I1 ∧ I2, where 
      I0: r = (#i,j | 0≤i<j<n : b.i ≤ 0 ∧ b.j ≥ 0),
      I1: s = (#i | 0≤i<n : b.i ≤ 0), and
      I2: 0≤n≤#b 
  }
  {bound function t: #b - n}
  do n ≠ #b  --->
     if b.n > 0  -->  r    := r+s;
     [] b.n = 0  -->  r, s := r+s, s+1;
     [] b.n < 0  -->  s    := s+1;
     fi
     { I(n:=n+1) }
     n := n+1
  od
  {Q': r = (#i,j | 0≤i<j<n : b.i ≤ 0  ∧  b.j ≥ 0)   ∧   n = #b }
  {Q: r = (#i,j | 0≤i<j<#b : b.i ≤ 0 ∧ b.j ≥ 0) }
]|

Depending upon your stylistic preferences, you may prefer to rewrite the loop as follows (thereby avoiding the nesting of a selection command inside the loop body):

  do n ≠ #b ∧ b.n > 0  ⟶  r,n := r+s,n+1;
  [] n ≠ #b ∧ b.n = 0  ⟶  r,s,n := r+s,s+1,n+1;
  [] n ≠ #b ∧ b.n < 0  ⟶  s,n := s+1,n+1;
  od


APPENDIX

Lemma 0: Assuming that n ≥ 0,

     (#i,j | 0≤i<j<n+1 : R) = (#i,j | 0≤i<j<n : R) + (#i,j | 0≤i<j=n : R)

Here is a proof, which transforms the left-hand side to the right-hand side:

     (#i,j | 0≤i<j<n+1 : R)  

  =    < defn of # quantifier >

     (+i,j | 0≤i<j<n+1 ∧ R : 1)  

  =    < rewrite range; note that j=0 is allowed by the 2nd conjunct in range
         below but not by the 1st, so no harm occurs by using 0≤j rather than 0<j  >

     (+i,j | 0≤i<j ∧ 0≤j<n+1 ∧ R : 1)  

  =    < (Gries 8.20) Nesting >

     (+j | 0≤j<n+1  : (+i | 0≤i<j ∧ R : 1))

  =    < (Gries 8.23) Split off term;
         note that assumption n≥0 guarantees that range is not empty >

     (+j | 0≤j<n : (+i | 0≤i<j ∧ R : 1))  +  (+j | j=n : (+i | 0≤i<j ∧ R : 1)) 

  =    < (Gries 8.20) Nesting, twice >

     (+i,j | 0≤j<n ∧ 0≤i<j ∧ R : 1) + (+i,j | j=n ∧ 0≤i<j ∧ R : 1)

  =    < rewrite ranges >

     (+i,j | 0≤i<j<n ∧ R : 1) + (+i,j | 0≤i<j=n ∧ R : 1) 

  =    < defn of # quantifier, twice >

     (#i,j | 0≤i<j<n : R) + (#i,j | 0≤i<j=n : R)


Lemma 1:

[(#i,j | 0≤i<j=n : b.i ≤ 0 ∧ b.j ≥ 0)  =  (#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0)]

Here we justify that claim by transforming the left-hand side into the right-hand side:

    (#i,j | 0≤i<j=n : b.i ≤ 0 ∧ b.j ≥ 0)

 =    < defn of # quantifier >

    (+i,j | 0≤i<j=n ∧ b.i ≤ 0 ∧ b.j ≥ 0 : 1)

 =    < rewrite 1st conjunct of range >

    (+i,j | 0≤i<j ∧ j=n ∧ b.i ≤ 0 ∧ b.j ≥ 0 : 1)

 =    < (3.84a) >

    (+i,j | 0≤i<n ∧ j=n ∧ b.i ≤ 0 ∧ b.n ≥ 0 : 1)

 =    < Nesting (8.20) > 

    (+i | 0≤i<n ∧ b.i ≤ 0 ∧ b.n ≥ 0 : (+j | j=n : 1))

 =    < (8.14) One-point rule, applied to nested quantification >

    (+i | 0≤i<n ∧ b.i ≤ 0 ∧ b.n ≥ 0 : 1)

 =    < defn of # quantifier >

    (#i |: 0≤i<n ∧ b.i ≤ 0 ∧ b.n ≥ 0)

 =    < Trading: (#x |: S∧T) = (#x | S : T) >

    (#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0)


Lemma 2:

[(#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0) = if(b.n ≥ 0, (#i | 0≤i<n : b.i ≤ 0), 0)]

Letting z be the left-hand side of the equation and a, b, and c be the three arguments of if, respectively, we find that this equation is of the form z = if(a, b, c). We manipulate this to arrive at something that suggests a proof strategy:

    z = if(a,b,c)

 =    < (Gries 3.73) (true ==> p) = p, with p := z=if(a,b,c) >

    true  ==>  z = if(a,b,c)

 =    < (Gries 3.28) excluded middle >

    a ∨ ¬a  ==>  z = if(a,b,c)

 =    < Gries 3.78) >

    (a  ==>  z = if(a,b,c)) ∧ (¬a  ==>  z = if(a,b,c))

 =    < replace by true (Gries 3.85a) >

    (a  ==>  z = if(true,b,c)) ∧ (¬a  ==>  z = if(a,b,c))

 =    < defn of if says if(true,b,c) = b >

    (a  ==>  z = b) ∧ (¬a  ==>  z = if(a,b,c))

 =    < defn of implication (Gries 3.59); double negation (Gries 3.12) >

    (a  ==>  z = b) ∧ (a ∨ (z = if(a,b,c)))

 =    < replace by false (Gries 3.88) >

    (a  ==>  z = b) ∧ (a ∨ (z = if(false,b,c)))

 =    < defn of if says if(false,b,c) = c >

    (a  ==>  z = b) ∧ (a ∨ (z = c)))

 =    < defn of implication (Gries 3.59); double negation (Gries 3.12) >

    (a  ==>  z = b) ∧ (¬a  ==>  z = c)

Thus, to prove the original equation, it suffices to prove each of the two conjuncts (instantiated appropriately) immediately above. That is, it suffices to prove both

(a) b.n ≥ 0   ==>   (#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0) = (#i | 0≤i<n : b.i ≤ 0) and

(b) ¬(b.n ≥ 0)   ==>   (#i | 0≤i<n : b.i ≤ 0∧b.n ≥ 0) = 0

Proof of (a):

  Assume the antecedant b.n ≥ 0.

    (#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0)

 =    < assumption b.n ≥ 0 >

    (#i | 0≤i<n : b.i ≤ 0 ∧ true)

 =    < Identity of ∧ (Gries 3.39) >

    (#i | 0≤i<n : b.i ≤ 0) 

Proof of (b):

  Assume the antecedant ¬(b.n ≥ 0).

    (#i | 0≤i<n : b.i ≤ 0 ∧ b.n ≥ 0)

 =    < assumption ¬(b.n ≥ 0) >

    (#i | 0≤i<n : b.i ≤ 0 ∧ false)

 =    < Zero of ∧ (Gries 3.40) >

    (#i | 0≤i<n : false)

 =    < defn of # quantifier >

    (+i | 0≤i<n ∧ false : 1)

 =    < Zero of ∧ (Gries 3.40) >

    (+i | false : 1)

 =    < Empty range (Gries 8.13), 0 is identity of + >

    0

Note that what we proved is nothing more than a special case of

Theorem: Provided that there are no free occurrences of x in R,

     [(*x | Q ∧ R : B)  =  if(R, (*x | Q : B), e)]
where e is the identity of *.