Monday, July 7, 2014
Where have I been?
I underwent a peculiar transformation since I moved from Zürich to Toronto. I used to think that you can study a software system purely from a logical point of view and, when you proved all you needed to prove, all is left to do is run the software. I have done it for some non-trivial software system but I realize that, for a bigger piece of software, the amount of work to cover the whole thing would be excessive both in the sense of "exceeding necessity" and "exceeding ability to do so". I'm reworking my vision of software design as an activity that includes proving theorem but which also comprises other techniques of validation. For example, random testing as done by QuickCheck in Haskell is often a good compromise. The key is to choose which technique applies best.
For instance, I started developing a program one year ago and it has now become relatively big (considering that I'm the only person developing it) and, to my surprise, it works well even though I didn't prove a single property about it. In the back of my mind however, I keep pulling some parts of the design aside and figuring out the logic that would make it possible to prove that it is correct. One of these days, I'll prove the correctness of some crucial parts of my programs and I think that will be both really interesting and really useful -- especially considering that the program is a formal verifier.
On the other hand, I am also discovering the interplay between mastering the theory and having good software tools for designing software. I used to think that if you know the theory well, you have no need for tools. I'm realizing that this is a pretty silly point of view: the program that I mentioned above is specialized in writing proofs for simple conjectures. It very incredibly fast. In one of the small systems that I use that tool for, there are around 500 conjectures that need to be proved. Most of them are incredibly easy to prove but they still need to be proved. My tool takes about 30s to prove 499 of them leaving me with the truly difficult theorem. That's incredibly helpful because now, I can focus my attention on the hardest of the hard design problems. The best part is, compared to an interactive prover, I do not need to manually invoke the prover on each proof obligation. They will all be attempted automatically except for those with a proof provided by the user.
The tool is designed to allow me to verify Unit-B models. I don't think I would be able to use Unit-B as confidently as I do and as efficiently as I do without it. Even better, when using Unit-B to develop the very first examples, every time I was faced with a problem, I could improvise a solution and think really hard and figure out that it was valid. This is very inconvenient to teach the method because I hardly have a list of all the tricks that I use. By implementing a verifier, I need to identify the truly useful techniques and make sure that they can be used with confidence and have a clear explanation to disallow whatever I choose to exclude. In other words, I'm now forced to formulate the rules of the method independently of any one example.
So much for simplistic views of the world.
Tuesday, July 5, 2011
On Function Purity and Simple Framing
Here is the latest blog post by Bertrand Meyer.
http://bertrandmeyer.com/2011/07/04/if-im-not-pure-at-least-my-functions-are/It talks about the problem of function purity and broaches the subject of framing. In short, the frame of the specification of a routine is the set of variables (in the broad meaning of the term) that the execution of the routine can modify. In object oriented programming, it is especially tricky because of the sets of dynamically allocated objects of which it might be necessary to alter the state in a given routine. Indeed such set can be taken to be the set of objects reachable from a given root. In a limited solution, such as shown by Meyer in his latest post, you only name variables and expressions that can change. In more elaborate solutions, such as the dynamic frames of Kassios (as quoted by Meyer), it is possible to provide a set of objects and the attributes that can be expected to change. It is especially handy for specifying dynamic data structures like linked lists.
Such a solution seems to present the disadvantage of disallowing the extension of frames through inheritance, which Meyer preserves. I think a compromise can be reached between the two but it is a bit too involved for a post only intended for sharing another blog post.
Monday, October 18, 2010
The Segment of Maximum Sum
First, here is the postcondition that we want to implement:
P0: r = 〈↑ p, q: 0 ≤ p ≤ q ≤ N: 〈∑ i: p ≤ i < q: X.i〉〉where ↑ denotes the maximum of two operands. Like the conjunction and the disjunction, it is idempotent, associative and symmetric so we can use it in the same was as the universal and existential quantification. The first thing to do is to introduce a name for the summation part so that we can manipulate it independently of the overall specification.
(0) S.p.q = 〈∑ i: p ≤ i < q: X.i〉And the specification becomes:
P1: r = 〈↑ p, q: 0 ≤ p ≤ q ≤ N: S.p.q〉The traditional way to get an invariant from this is to replace N by a variable, let's call it n, and use n = N as an exit condition:
J0: r = 〈↑ p, q: 0 ≤ p ≤ q ≤ n: S.p.q〉 C0: n = NBy symmetry, we can assume that 0 is a proper initial value for n which makes S.0.0 a good one for r.
I0: n = 0 I1: r = 0Last formality: having n increase by one at each iteration will guarantee termination provided 0 ≤ N.
S0: n' = n + 1Let's now start calculating with J0 to see what adjustments we have to make to r to preserve it. Two distinct heuristics get us to start calculating with the right-hand side of the equality: it is much more complicated than the left-hand side so we can assume that most of our moves will be eliminations and all the program variable involved in the right-hand side don't have a matching assignment so we would basically be stuck right at the start. For a given loop with invariant J and body S, the invariance of J is assured by:
J' ⇐ J ∧ Swhere J' is J with all its program variables primed. It means we can use whatever is part of the body or the invariant to prove J's invariance, to do so, we can invent new statements for the body as we go.
〈↑ p, q: 0 ≤ p ≤ q ≤ n': S.p.q〉
= { S0 }
〈↑ p, q: 0 ≤ p ≤ q ≤ n+1: S.p.q〉
= { Split range }
〈↑ p, q: 0 ≤ p ≤ q ≤ n: S.p.q〉
↑ 〈↑ p: 0 ≤ p ≤ n+1: S.p.(n+1)〉
= { Use final value of a new variable using
Q0: s' = 〈↑ p: 0 ≤ p ≤ n+1: S.p.(n+1)〉 and J0 }
r ↑ s'
= { New statement: S1: r' = r ↑ s' to conclude
the proof of J0's invariance }
r'
Q0: s' = 〈↑ p: 0 ≤ p ≤ n+1: S.p.(n+1)〉
S1: r' = r ↑ s'
Q0 links unprimed variables with primed variables in a way that cannot be evaluated. Let's turn it into an invariant to fix this.
〈↑ p: 0 ≤ p ≤ n+1: S.p.(n+1)〉
= { S0 }
〈↑ p: 0 ≤ p ≤ n': S.p.n'〉
= { J1' }
s'
where
J1: s = 〈↑ p: 0 ≤ p ≤ n: S.p.n〉We now have to make sure that J1 is also invariant and use the same heuristic as for J0 to do so.
〈↑ p: 0 ≤ p ≤ n': S.p.n'〉
= { S0 and split }
〈↑ p: 0 ≤ p ≤ n: S.p.(n+1)〉↑ S.(n+1).(n+1)
= { (1), see below ; S.n.n = 0 }
〈↑ p: 0 ≤ p ≤ n: S.p.n + X.n〉↑ 0
= { + over ↑ }
(〈↑ p: 0 ≤ p ≤ n: S.p.n〉+ X.n) ↑ 0
= { J1 }
(s + X.n) ↑ 0
= { New statement: S2: s' = (s + X.n) ↑ 0 }
s'
S2: s' = (s + X.n) ↑ 0
(1) S.p.(n+1) = S.p.n + X.n ⇐ p ≤ n
(1) is a theorem that we calculated because we had a term S.p.(n+1) that we needed to replace by one formulated in terms of S.p.n to make it possible to use J1 to eliminate the quantified maximum.
Proof of (1):
S.p.(n+1)
= { (0) }
〈∑ i: p ≤ i < n+1: X.i〉
= { Split range using p ≤ n }
〈∑ i: p ≤ i < n: X.i〉+ X.n
= { (0) }
S.p.n + X.n
end of proof
All that is missing now to have a complete program is to add an initialization for s. An examination of J1 tells us that for n = 0, S.0.0 is an appropriate value for s. We now get:
n = 0 ∧ r = 0 ∧ s = 0
; while n ≠ N do
n' = n+1 ∧ r' = r↑s' ∧ s' = (s + X.n) ↑ 0
od
This would be a simple enough program to execute except for S1 which has primed variables on both side of the equality. Let's see if we can find a sequence of assignments (rather than a conjunction of equalities) that will do the same as S0 ∧ S1 ∧ S2.
The main law that we use for calculating assignments is
(2) s := E ; P ≡ P [s \ E]where P [s \ E] is the same as P except for the occurrences of s which are replaced by E. The way in which we will make assignments appear is by replacing terms of the initial specification by specifications of the form x' = x so that we can eventually replace the whole boolean specification by a skip and keep only the assignments.
n' = n+1 ∧ r' = r↑s' ∧ s' = (s + X.n) ↑ 0
= { Let's deal with s' to introduce s' = s
and solve the assignment to r }
s := (s + X.n) ↑ 0
; (n' = n+1 ∧ r' = r↑s' ∧ s' = s)
= { Leibniz }
s := (s + X.n) ↑ 0
; (n' = n+1 ∧ r' = r↑s ∧ s' = s)
= { Assignment (either of r or n will do. Let's pick
them in inverse order of introduction }
s := (s + X.n) ↑ 0
; r := r↑s
; (n' = n+1 ∧ r' = r ∧ s' = s)
= { One last time, assignment }
s := (s + X.n) ↑ 0
; r := r↑s
; n := n+1
; (n' = n ∧ r' = r ∧ s' = s)
⇐ { Introduce skip }
s := (s + X.n) ↑ 0
; r := r↑s
; n := n+1
; skip
= { Identity of ; }
s := (s + X.n) ↑ 0
; r := r↑s
; n := n+1
It is a bit disappointing to see that the order between the assignment to r and that to n is irrelevant and we could have used a multiple simultaneous assignment for them but, then, we would have noticed that the choice of grouping n with r or grouping it with s is also irrelevant. One way or another, an irrelevant choice has to be made.
By interpreting the initialization in a similar way, we get the following program:
n, r, s := 0, 0, 0
{ invariant J0 ∧ J1 }
; while n ≠ N do
s := (s + X.n) ↑ 0
; r := r ↑ s
; n := n + 1
od
Thursday, August 5, 2010
The Development of a Solution to the Problem of the Cubes
Before I begin, let me point out that the publication of this blog entry is made possible because I haven't used Blogger's interface to edit it (or at least, I used it as little as possible). Instead, I wrote it in a plain text editor using a markup language I am developping. Since I don't have developed an HTML generator yet, I had to convert it myself to HTML. Blogger did not make it easy but I think the result is much better than what I got previously by just fighting with the web interface. I would certainly appreciate any feedback concerning the resulting layout.
(end of note)
The first statement of this problem that I have seen is in Wim Feijen's note WF114 [0]. I really like the structure of his presentation but, whenever I have reproduced it, either for myself of to present the problem and a method to find a solution to somebody else, I have made on slight change: instead of using predicate transformers semantics, I use a relational semantics and I believe the switch to be quite significant from a practical standpoint. I still use the invariant based loop development techniques developped by Dijkstra & cie instead of the recursion / precondition technique of Hehner and I think it is very characteristic for my style. It shows that I prefer to manipulate one small part of the program at a time.
DEVELOPMENT ===========
The problem at hand consists in producing an array with the N first cubes without resorting to multiplication operations. Formally, the postcondition we are trying to satisfy is:
P0: (A i: 0 ≤ i < N: f.i = i^3)with f the output variable (a function). About notation '.' is the function application operator and (A i: R: T) is a universal quantification over the range R and with the term T. Here, we use the property that ≤ and < are mutually conjunctional which means that their combined application can be unfolded into
a ≤ b < c ≡ a ≤ b ∧ b < cThey are also mutually conjunctional with equality (=) whereas logical equivalence (≡) is not conjunctinal at all because it is associative and the two features of the operator would interfere. (end of note) We can now proceed to find a suitable invariant for our loop. A good technique to do so is to replace some constants in the postcondition by variables. Since it is a quantification, it seems reasonable to focus our search in the range. We find 0 and N. We could choose either or both but if we use 0 as our initial value and N as the final one, we get the first cube for free. We will therefore opt for replacing only N with a variable:
E0: k = N J0: (A i: 0 ≤ i < k: f.i = i^3)If we start k off at 0, the range of J0 is becomes false and the invariant is therefore trivially true. From there, it is not a big step to say that increasing k regularly will lead us to E0 (our variant is then N-k).
I0: k = 0 S0: k' = k + 1It is now time to see how we will maintain J0 (in the loop) while applying S0 to converge. Note on proof obligations If we use the technique of the invariant to build our loop, we have to fulfill a certain certain proof obligation. Assuming that J is the invariant, B is the body of the loop, E is the exit condition, IN is the initialization of the loop and P is the postcondition:
(0) J' ⇐ J ∧ B ∧ ¬ E "B (and E) preserve(¬s) J" (1) J ⇐ IN "IN establishes J" (2) P ⇐ J ∧ E "J and E lead to P"In the preceding definitions, primed predicates are predicates for which all unprimed program variables are replaced with their primed version. Unprimed variables designate their initial values (before the execution of a given statement or program) and the primed variables designate their final values.
(3) (B and E preserve J)
∧ (IN establishes J)
∧ (J and E lead to P)
≡
{ IN } until E do B od { P }
The last line is to be interpreted as a Hoare triple with assertions between brackets. It reads "started in a state satisficing IN, the program [here the while loop], finishes in a state satisficing P."
(end of note)
(A i: 0 ≤ i < k': f'.i = i^3)
= { S0 }
(A i: 0 ≤ i < k + 1: f'.i = i^3)
= { Split off one term }
(A i: 0 ≤ i < k: f'.i = i^3) ∧ f'.k = k^3
= { Modify only f.k; S1: (A i: 0 ≤ i < k: f'.i = f.i) }
J0 ∧ f'.k = k^3
S1: (A i: 0 ≤ i < k: f'.i = f.i)The last line of the provious proof makes a nice assignment. The only problem is that it requires some multiplications, we will continue out calculations rather than adopt it as our next assignment.
f'.k = k^3
= { Let's introduce a new variable to hold k^3 }
{ at all time. J1: a = k^3 }
f'.k = a
S2: f'.k = a J1: a = k^3If we added the symmetric complement of S1, that is that everything beyond k in the values of f stays unchanged but it is not required yet. We add it afterwards to complement S1 and S2 as array assignments but it is not urgent.
(4) (J0' ≡ J0 ∧ S2) ⇐ J1 ∧ S0 ∧ S1Since the left argument of ⇐ is monotonic, we can weaken (4) by weakening its left-hand side and replacing the equivalence by a consequence (⇐). After applying the shunting rule (see (A) in the appendix), we have exactly the shape of the proof obligation for maintaining an invariant... except that invariant used to maintain J0 (i.e. J0 ∧ J1) is much stronger than J0. We can prove that we can maintain it too, though.
(5) J0' ⇐ J0 ∧ J1 ∧ S0 ∧ S1 ∧ S2To maintain J1, we will split it in two, manipulate the left-hand side of the equality since we know a lot about k and hope it will lead to operations on a.
k'^3
= { S0 }
(k + 1)^3
= { Unfold _ ^3; ∙ over + }
k^3 + 3∙k^2 + 3∙k + 1
= { J1 }
a + 3∙k^2 + 3∙k + 1
= { We need a new variable to hold the term }
{ which contains a product J2: b = 3∙k^2 + 3∙k + 1 }
a + b
= { We want to fall back on J1'. S3: a' = a + b is }
{ good candidate. }
a'
J2: b = 3∙k^2 + 3∙k + 1 S3: a' = a + b
(6) J1' ⇐ J1 ∧ J2 ∧ S0 ∧ S3It seems like we have just created some more work for ourselves but we could be reasured by the sight of the decreasing order of the equations in our new invariants.
3∙k'^2 + 3∙k' + 1
= { S0 }
3∙(k + 1)^2 + 3∙(k + 1) + 1
= { ∙ over + }
3∙k^2 + 6∙k + 3 + 3∙k + 3 + 1
= { J2 }
b + 6∙k + 6
= { You know the drill! J3: c = 6∙k + 6 }
b + c
= { S4: b' = b + c }
b'
J3: c = 6∙k + 6 S4: b' = b + c
(7) J2' ⇐ J2 ∧ J3 ∧ S0 ∧ S4Now for the last touch on the invariant:
6∙k' + 6
= { S0 }
6∙k + 6 + 6
= { J3 }
c + 6
= { S5: c' = c + 6 }
c'
S5: c' = c + 6
(8) J3' ⇐ J3 ∧ S0 ∧ S5We can now happily conclude
(9) S6 preserves J4with
S6: S0 ∧ S1 ∧ S2 ∧ S3 ∧ S4 ∧ S5 J4: J0 ∧ J1 ∧ J2 ∧ J3We started off knowing that J0 ∧ E0 ⇒ P0 so all that is left for a correct loop is to find the initialization. We already have I0 and, from there, we can easily calculate the initial values of the auxiliary variables. Let's ignore the calculations and see the initial values right away.
I1: a = 0 I2: b = 1 I3: c = 6We can see that there is no value specified for f and, as a matter of fact, any value will do. We now have the final lemma for the correctness of this loop.
(10) I0 ∧ I1 ∧ I2 ∧ I3 establishes J0 ∧ J1 ∧ J2 ∧ J3CONCLUSION ========== Looking at the end result, we can see that we ended up with a series of assignments (SX) but we haven't specified an order in which they have to be executed. It is because it is both simple and tedious to come up with an order and the order so arbitrary that it is all noise. For that reason, we can trust a compiler to do it for us. When we have to deal with a programming languages that doesn't support multiple simultanous assignments, we have to take the time to introduce the noise that is the order ourselves. The key here is that we must not overwrite a variable before we are done with its initial value. We have a bit of a problem when we run into an assignment where the use and the writing of variables are cyclicly dependant like in the following:
S6: x' = y ∧ y' = xTo solve the problem, we can add an auxiliary variable (say, t) and initialize it so that it can be substituted for every use of a certain variable.
I4: t = x
x' = y ∧ y' = x
= { I4 }
x' = y ∧ y' = t
= { Assignment law (B) }
(x := y) ; (x' = x ∧ y' = t)
= { Assignment law (B) }
(x := y) ; (y := t) ; (x' = x ∧ y' = y)
⇐ { Skip introduction (C) }
(x := y) ; (y := t) ; skip
= { Identity of ; (D) }
(x := y) ; (y := t)
And, of course, it is also simple to implement I4 and prepend it to the program we just derived.
Note:
Since we are interested in a program that ends up with I4, we are going to use primed variables.
(end of note)
t' = x ∧ x' = x ∧ y' = y
= { Assignment law (B) }
(t := x) ; (t' = t ∧ x' = x ∧ y' = y)
⇐ { Skip introduction (C) }
(t := x) ; skip
= { Identity of ; (D) }
t := x
In short, this is why I don't bother to do it with the body of my loop.
Finally, the main interest of this programming exercise does not lie in the resulting program. Quite frankly, it accomplishes quite a banal task. However, the technique which led us to it is quite systematic. We used very little knowledge to find it and, unlike Feijen's version, we did not introduce assignments of '?' to mark the necessity to create a new assignments to then 'guess' what should be the expression. To be honest, it is not a very hard guess but it is still outside of the scope of a calculation. The fact that I integrated that choice in a calculation makes it easier to justify.
Regards,
Simon Hudon
ETH Zürich
APPENDIX
========
Unusual laws of propositional calculus
(A) A ∧ B ⇒ C ≡ A ⇒ (B ⇒ C) { "shunting" }
Hehner's programming laws [1]
(B) (x := E) ; P ≡ P [x \ E]
{ "assignment law" P [x \ E] is the }
{ variable substitution notation, }
{ P is a predicate, x a program }
{ variable and E an expression }
(C) x' = x ⇐ skip
{ For any program variable x }
(D) P ; skip ≡ P
{ For any program predicate P }
REFERENCES
==========
[0] Wim Feijen, WF114 - The problem of the table of cubes, http://www.mathmeth.com/wf/files/wf1xx/wf114.pdf
[1] Eric Hehner, A Practical Theory of Programming, http://www.cs.toronto.edu/~hehner/aPToP/
Saturday, November 28, 2009
Back in the 60s
Wednesday, November 4, 2009
Bad Separation of Concerns
S = x ++ z
z = [ y ] ++ wand scanning one character was encoded as:
x, z := x ++ [ y ], w
Pattern Matching is a power way of associating properties or code with the shape of the data and we need to be able to express it. Since we can use it with code, it would be important to have programming techniques to make the executable, that is, techniques usable by a programmer to transform a specification using pattern matching into executable code implementing it. If the methods are especially effective and simple, we could have it implemented in our compiler but this is far from being a design consideration for the construct.
Sunday, October 11, 2009
Modeling vs Programming vs Coding
Friday, September 18, 2009
Hehner's Program Calculus
if b -> x := E [] c -> x := F fi
(b /\ x' = E) \/ (c /\ x' = F)
(b ==> (x := E).p) /\ (c ==> (x := F).p) /\ (b \/ c)
(0) x max y >= x
(1) x max y >= x
(2) x max y = x \/ x max y = y
(3) x max y = y max x
(4) x = x max y == x >= y
(5) y = x max y == y >= x
(6) x >= y \/ y >= x
P: z = x max y
Q: z := x
R: z := y
Q.P
== { Subtitution }
x = x max y
== { (4) }
x >= y
R.P == y >= x
if x >= y -> z := x
[] y >= x -> z := y
fi
(x >= y) \/ (y >= x)
== { (6) }
true
P: z := x max y
Q: z' = x max y
z' = x max y
== { Identity of /\ }
z' = x max y /\ true
== { (2) }
z' = x max y /\ (x = x max y \/ y = x max y)
== { /\ distributes over \/ }
(z' = x max y /\ x = x max y) \/ (z' = x max y /\ y = x max y)
== { Leibniz, twice }
(z' = x /\ x = x max y) \/ (z' = y /\ y = x max y)
== { (4) and (5) }
(z' = x /\ x >= y) \/ (z' = y /\ y >= x)
if x >= y -> z := x
[] y >= x -> z := y
fi