Tute 10 Liam O'Connor May 23, 2017 proc Search(value g : G, value s : V g, value k : K, result v : T, result f : B) Where a graph g : G is dened as a 4-tuple (V, Γ, κ, λ) containing a set of vertices V, a successor function Γ : V P(V), a key function κ : V K and a payload function λ : V T, all much the same as the assignment. Like the assignment, we will use subscripts (e.g. Γ g ) to refer to a particular component of a graph. As we plan to do DFS using an unbounded stack on a graph, we do not need either of the preconditions from the assignment. Our postconditions, however, look quite similar, with the added assertion that a ne will be found i it is reachable from the start ne { a trivial assertion for a tree, but not for a graph. f, v : True, (1) For our purposes, we will extend the toy language with sets and set operations, and operations on abstract stacks, which we will later eliminate via data renement. S(τ) ::= (empty stack) x, t where x τ and t S(τ) Each of the stack operations is given a renement rule below: initialise t : True, t = t := pop t, x : t = h, r, x = h t = r x := pop(t) push t : True, t = x, t 0 push(t, x) We will also allow comparisons to the empty stack (t = ) in guards with the obvious semantics. 1
To begin deriving the implementation, let us rst intruce our stack and our visited set, which will contain all nes that we have already seen. This set is needed in a graph search, but not in a tree search as trees are guaranteed to be acyclic. (1) i-loc,... var t : S(V g ) := var S : P(V g ) := f, v, t, S : t = S =, (2) Now, we know we need a loop, and we have a pretty go idea of what the guard should be: We will exit the loop when we either nd what we're looking for (i.e. f is true), or when we have nowhere else to look (the stack t is empty). (2) sc,... f, v, t, S : t = S =, ϕ (3) f, v, t, S : ϕ, ϕ (f t = ) (4) (f n V f, v, t, S : ϕ (f t = ), g. κ g (n) = k λ g (n) = v n Γg(s)) (5) Here ϕ stands for our loop invariant, which, like the postcondition, is made of two components. ( f ( x S. x Γ ϕ = g(s) κ g (x) k Γ g (x) S t ) ( x t. x Γg(s)) s S t ) In the successful case, when f is true, we have found the ne we are looking for. Note that, because the loop guard tells us that f starts false, this case in the loop invariant only becomes true in the nal iteration of the loop. If we have found the ne we're looking for, then we no longer care about the state of the stack or the visited set, and we can just include the successful postcondition directly in the invariant. If f is false, however, we must describe in the invariant all the relevant properties of our stack t and visited set S. We use the notation t to refer to the set of all elements of a stack t: = x, s = {x} s At the end of the loop, if f is still false, then we know the stack is empty at the end of our loop from our guard. In such a situation, our invariant can be simplied to: ϕ f t = ( x S. x Γ g(s) κ g (x) k Γ g (x) S) s S We must ensure two main conditions: 2
1. Everything we have visited is not the ne we're looking for ( n S.κ g (n) k) 2. We have visited every reachable ne from the start ne (Γ g(s) S) With those two facts, we can conclude that no reachable ne from the start ne is the one we're looking for, which is what the postcondition requires ( n V g. n Γ g(s) κ g (n) k). The rst condition trivially follows from the invariant here, because we explicitly state that the visited set does not contain the key. The second condition can be rephrased as x Γ g(s). x S. We can show this from our invariant above using induction on the length of the path from s to x. In the base case, where x = s, we know from the nal conjunct in the invariant that s S. In the inductive case, where x Γ g (k), we have the inductive hypothesis that k S. Because our simplied invariant says that every successor of every ne in S must also be in S, we can conclude that x S as required, as x is a successor of k S. Thus, because in the case where f is true our invariant includes our postcondition directly, and in the case where f is false it directly implies the postcondition, we can just rene the third statement directly to skip: (5)..., skip skip We must also derive ce to establish our invariant initially. We'll do that by setting f to False, and pushing the start ne s onto the stack: Now we can intruce the while loop: (4) while (3) sc,... f := False; push(t, s) while (f t = ) do f, v, t, S : ϕ f t, ϕ (6) Rening the loop by is mostly tedious, but we begin by rst popping a ne o the stack. We know this is safe as our guard guarantees the stack is non-empty: (6) sc, pop, c-frame,... var n := pop(t); f, v, t, S : ϕ n,t / t f, ϕ (7) 3
Then, we check to see of the ne we just popped is the one we're looking for, in which case we can set f to true, thus making it very easy to re-establish the invariant by simply setting v to be the payload. (7) if, sc, assign,... if κ g (n) = k then else fi f := True; v := λ g (n); f, v, t, S : ϕ n,t / t f κ g (n) k, ϕ (8) In the else case, because we know the key is not the one we're looking for, we may be inclined to add n to the set S, however to do that we must ensure that n satises all the other conditions imposed on elements of S by the invariant. Firstly, there must be a path from the start ne s to n, which we know is true from our precondition, which states that all elements of the stack n, t (which clearly includes n) are reachable from s. Secondly, we must ensure that all successors of the ne n are in either the stack or the set S. To accomplish this we will simply loop through the set of unvisited successors and push each one to the stack. (8)... var m := Γ g (n) \ S while m do var c : m push(t, c) m := m \ {c} S := S {n} 4
Now, gathering all the ce together, we get: proc Search(value g : G, value s : V g, value k : K, result v : T, result f : B) var t : S(V g ) := ; var S : P(V g ) := ; f := False; push(t, s); while (f t = ) do var n := pop(t); if κ g (n) = k then f := True; v := λ g (n); else var m := Γ g (n) \ S while m do var c : m push(t, c) m := m \ {c} S := S {n} fi 5