Summer Internship at Naval Research Lab, Washington DC On Building PVS Interfaces for Abstraction Proofs Extending TAME for Refinements Sayan Mitra mitras@theory.lcs.mit.edu Myla Archer archer@itd.nrl.navy.mil Summer Internship at Naval Research Lab, Washington DC
1. Motivation Overheads for using Theorem Provers Users have to learn the language of specification and the commands for interacting with the prover Writing the specifications An Interface for a TP is an environment that customizes the TP for particular logics, proof systems… Strict rules for specification Fewer proof steps, partially automated proofs with strategies Possible to generate human readable proofs What is a TPI like ? Example: TAME (Timed Automaton Modeling Environment) Template for specifying Timed I/O Automata Strategies for Inductive Invariant Proofs.
Talk Outline Motivation Background Interface for Abstraction proofs Overview of PVS PVS strategies TAME Interface for Abstraction proofs The examples Strategy for proving Refinements Conclusions and Future work
Prototype Verification System (PVS) General purpose TP developed by SRI Applications: fault-tolerant flight control systems, hardware and real-time systems GNU Emacs interface for specification and prover Expressive specification language: Typed high order logic Basic types, uninterpreted types, abstract data types Predicate subtypes (may produce proof obligations) Parameterized theories Interactive prover: Primitive rules: propositional, quantifier, induction, rewriting, decision procedures for linear arithmetic Strategies or defined rules: grind Strategicals: Commands for constructing strategies
PVS Overview (continued) : Language Example : GCD Euclid’s algorithm: Replace the larger of the two numbers by their difference; repeat until same; GCD: THEORY BEGIN i, j : VAR posnat gcd(i,j) : RECURSIVE posnat : IF i = j THEN i ELSIF i > j THEN gcd(i-j,j) ELSE gcd(I, j – i) ENDIF MEASURE i+j Typchecking this would generate gcd_TCC1: FORALL (i,j): NOT i = j AND i > j i – j + j < i + j subtype_TCC2: FORALL (i, j ): NOT i = j AND i > j i – j > 0 Need to show that recursive definitions terminate, hence the MEASURE ; an expression That decreases
GCD Example Invoking the prover on divides_lemma divides?(i,j): bool = EXISTS (k:posnat) : i * k = j gcd_greatest: THEOREM FORALL (z:posnat) : divides?(z,i) AND divides?(z,j) divides(z,gcd(i,j)) gcd_divides: THEOREM divides?(gcd(i,j),i) AND divides?(gcd(i,j),j) divides_lemma: LEMMA divides?(z,i) & divides?(z,j) AND i>j divides?(z, i-j) Invoking the prover on divides_lemma divides_lemma: |----- {1} FORALL (i:posnat, j: posnat, z:posnat): divides?(z,i) AND divides?(z,j) AND i>j divides?(z, i-j) Rule? (skolem!)
PVS Prover: Proof for divides_lemma {-2} j!1 > 0 {-3} z!1 > 0 {-4} divides?(z!1, i!1) {-5} divides?(z!1 , j!1 ) {-6} i!1 > j!1 |----- {1} divides?(z!1, i!1- j!1) Rule? (expand “divides?”)
Proof for divides_lemma {-2} j!1 > 0 {-3} z!1 > 0 {-4} EXISTS (k:posnat) : z!1 * k = i!1 {-5} EXISTS (k:posnat) : z!1 * k = j!1 {-6} i!1 > j!1 |----- {1} EXISTS (k:posnat) : z!1* k = i!1- j!1 Rule? (skolem!) The EXISTS in the antecedents are skolemized, introduces k!1 k!2
Proof for divides_lemma {-1} k!1 > 0 {-2} k!2 > 0 {-3} i!1 > 0 {-4} j!1 > 0 {-5} z!1 > 0 {-6} z!1 * k!1 = i!1 {-7} z!1 * k!2 = j!1 {-8} i!1 > j!1 |----- {1} EXISTS (k:posnat) : z!1* k = i!1- j!1 Rule? (inst 1 “k!1 – k!2”) This yields 2 subgoals.
Proof for divides_lemma {-1} k!1 > 0 {-2} k!2 > 0 {-3} i!1 > 0 {-4} j!1 > 0 {-5} z!1 > 0 {-6} z!1 * k!1 = i!1 {-7} z!1 * k!2 = j!1 {-8} i!1 > j!1 |----- {1} z!1 * (k!1 – k!2) = i!1 – j!1 Rule? (assert) Propositional simplification finishes this branch.
Proof for divides_lemma … almost there divides_lemma.2 (TCC): {-1} k!1 > 0 {-2} k!2 > 0 {-3} i!1 > 0 {-4} j!1 > 0 {-5} z!1 > 0 {-6} z!1 * k!1 = i!1 {-7} z!1 * k!2 = j!1 {-8} i!1 > j!1 |----- {1} (k!1 – k!2) > 0 Rule? (lemma “pos_times_lt”) This requires a lemma from the prelude
Instantiating the lemma … divides_lemma.2 (TCC): {-1} FORALL (x: real, y:real) : 0 < x * y IFF ( 0 < x AND 0 < y) OR (x < 0 AND y < 0) {-2} k!1 > 0 {-3} k!2 > 0 {-4} i!1 > 0 {-5} j!1 > 0 {-6} z!1 > 0 {-7} z!1 * k!1 = i!1 {-8} z!1 * k!2 = j!1 {-9} i!1 > j!1 |----- {1} (k!1 – k!2) > 0 Rule? (inst -1 “k!2 – k!1” “z!1”) Instantiating the lemma …
Finally, propositional simplification completes the proof. divides_lemma.2 (TCC): {-1} 0 < (k!2 – k!1) * z!1 IFF ( 0 < k!2 – k!1 AND 0 < z!1 ) OR (k!2 – k!1 < 0 AND z!1 < 0) {-2} k!1 > 0 {-3} k!2 > 0 {-4} i!1 > 0 {-5} j!1 > 0 {-6} z!1 > 0 {-7} z!1 * k!1 = i!1 {-8} z!1 * k!2 = j!1 {-9} i!1 > j!1 |----- {1} (k!1 – k!2) > 0 Rule? (assert) Finally, propositional simplification completes the proof.
PVS Strategies PVS prover commands are either rules or strategies A rule executes as a single step, e.g. skolem, flatten, etc. A strategy is created using PVS strategy building commands (strategicals), e.g., then, branch, try, let, if, etc. Rule applications Other strategies Examples (TRY (THEN (LIFT-IF) (PROP) (ASSERT)) (FAIL) (SKIP)) (BRANCH (SPLIT) ((THEN (LIFT-IF) (PROP) (ASSERT)) (GRIND))
let, if strategicals allow the use of Lisp code in strategies Example (defstep suppose (x) (let ((supp_string (format nil “Suppose ~a” x) (nsupp_string (format nil “Suppose not [~a]” x)) (branch (with-labels) (case x) ((“Suppose”) (“Suppose not”))) ((comment supp_string) (comment nsupp_string)))) “For doing a simple case split” “First supposing ~a true then supposing it is false”) This creates a new defined rule suppose and also a “glass box” strategy suppose$.
Probing the Proof State in PVS Lisp is used to probe the current proof state The proof state is an instance of a CLOS object, with “slots” label, current-goal, etc. In the PVS Prover prompt try this: Rule? (lisp (describe *ps* )) The following slots have :instance allocation: label "weak_refinement_thm.1" current-goal {-1} "rel_mem_visible(rel_nu(rel_nu1_var!1))" {-2} "rel_mem_reachable(s1)“ {3} "rel_mem_enabled(rel_nu(rel_nu1_var!1), s1)" |------- {1} "mem_enabled_general(amap(rel_nu(rel_nu1_var!1)),r(s1))" Suppose we want to get the name of the name of the predicate for the general enabling condition…
2.3 Probing the Proof State in PVS We look inside the current goal of *ps* Rule? (lisp (describe (current-goal *ps* ))) The following slots have :instance allocation: s-forms (mem_enabled_general(amap(rel_nu(rel_nu1_var!1)), r(s1)) NOT rel_mem_visible(rel_nu(rel_nu1_var!1)) NOT rel_mem_reachable(s1) NOT rel_mem_enabled(rel_nu(rel_nu1_var!1), s1)) p-sforms (mem_enabled_general(amap(rel_nu(rel_nu1_var!1)), r(s1))) n-sforms (NOT rel_mem_visible(rel_nu(rel_nu1_var!1)) NOT rel_mem_reachable(s1) NOT rel_mem_enabled(rel_nu(rel_nu1_var!1), s1))
2.3 Probing the Proof State in PVS (lisp (describe (s-forms (current-goal *ps* )))) (mem_enabled_general(amap(rel_nu(rel_nu1_var!1)), r(s1)) NOT rel_mem_visible(rel_nu(rel_nu1_var!1)) NOT rel_mem_reachable(s1) NOT rel_mem_enabled(rel_nu(rel_nu1_var!1), s1)) is a cons. (lisp (describe (car (s-forms (current-goal *ps* ))))) newline-comment nil parens 0 type bool free-variables nil free-parameters unbound from-macro nil operator mem_enabled_general argument (amap(rel_nu(rel_nu1_var!1)), r(s1)) nil
Timed Automaton Modeling Environment PVS Interface for timed automata developed at NRL Specification: Template for specifying timed automata Reachable states Equivalence of states Strategies for proving invariants AUTO_INDUCT, TRY_SIMP, APPLY_PRECOND, etc. Prototype translator for generating human readable proofs from proof-scripts.
TAME Theory Tree TESLA_invariants TESLA_unique_aux TESLA_rewrite_1_aux state actions preconditions transitions start state TESLA_decls states machine time
Abstraction Relations: Refinements A refinement from A to B is a function r from states of A to states of B, such that: s є start(A) implies r(s) є start(B), and s aA s’ implies r(s)aBr(s’). A weak refinement from A to B is a function r from states of A to states of B, such that: for every reachable state s, and transition s aA s’, if a is a visible action then r(s)aBr(s’), otherwise r(s) = r(s’). Thm: (Weak) Refinement is sound. A ≤r B implies A ≤T B Thm: Invariance. A ≤r B and IB implies IA = {s|r(s) є IB}
Building a PVS Interface Polymorphic types are not available in PVS This is approximated by theories with parameters GROUP[G:TYPE, + : [G,G-> G] , 0: G, -: [G -> G]]: THEORY BEGIN …. END IMPORTING GROUP[int, +, 0, -] PVS v3.1 is supposed to support theory interpretations group:THEORY G: TYPE+ +:[G,G ->G] 0:G -: [G -> G] associative_ax:AXIOM FORALL (x,y,z:G): x + (y + z) = (x+y) +z END group group{{G:= nzreal, + := * , 0 := 1, - := LAMBDA (r:nzreal) : 1/r}}
Interface for Abstraction Proofs: Specification We define a generic automaton theory The generic refinement defines the relation between two automata automaton:THEORY BEGIN actions: TYPE+; visible:[actions->bool]; states: TYPE+ enabled:[actions,states -> bool]; trans:[actions, states ->states]; equivalent:[states, states ->bool]; reachable:[states->bool]; start:[states -> bool] END automaton refinement[ A, B : THEORY automaton, r:[A.states -> B.states] , amap: [A.actions -> B.actions]] : THEORY A common theory imports the specification of both the abstract and concrete automata and imports the refinement theory IMPORTING abstractions_lib@refinement[tip_decls, spec_decls, r, amap]
Problems This scheme requires Passing theories as parameters by name PVS theory interpretations We have merged the two _decls theories into a single theory which includes the invariants and abstraction relations. Naming issues doesn’t work with abstract data types !!?
Case Study : TIP Tree Identification Phase of IEEE 1394: leader election algorithm. Algorithm starts after a reset which clears all topology information except local information Leaf nodes send “parent request” msg to neighbors When a node receives PR from all but one of its neighbors then it sends a PR to the remaining neighbor. PRs are ACKed Tree grows from leaf to root; A node receiving an ACK terminates as a non-root. If a node receives PR from all its neighbors then it’s the root. Root contentions are broken by back-off and random retransmit.
TIP Specification: tip_decls tip_actions: DATATYPE nu(timeof:(fintime?)): nu? add_child(addE: Edges): add_child? children_known(childV: Vertices): children_known? ack(ackE: Edges): ack? resolve_contention(resE: Edges): resolve_contention? root(rootV: Vertices): root? noop:noop? tip_visible(a:tip_actions):bool tip_MMTstates: TYPE = [# init: [Vertices -> Bool], contention: [Vertices -> Bool], root: [Vertices->Bool], child: [Edges -> Bool], mq:[Edges -> BoolStar] #] IMPORTING states[tip_actions,tip_MMTstates,time,fintime?] tip_enabled_general (a:tip_actions, s:tip_states):Bool = true tip_enabled_specific (a:tip_actions, s:tip_states):Bool = nu(delta_t): delta_t > zero, add_child(e): init(target(e),s) & not(mq(e,s)=null), children_known(v): init(v,s) & (forall (e:tov(v))(f:tov(v)): child(e,s) or child(f,s) or e=f), ack(e): not(init(target(e),s)) & not(mq(e,s)=null), resolve_contention(e): contention(source(e),s) & contention(target(e),s), root(v): not(init(v,s)) & not(contention(v,s)) & not(root(v,s)) & (forall (e:tov(v)): child(e,s)), noop:true What is mq ? How does tip work ?
tip_decls tip_enabled (a:tip_actions, s:tip_states):Bool= tip_enabled_general(a,s) & tip_enabled_specific(a,s) IMPORTING machine[tip_states, tip_actions, tip_enabled, tip_trans, tip_start, tip_visible] tip_trans (a:tip_actions, s:tip_states):tip_states = nu(delta_t): s WITH [now := now(s) + delta_t], add_child(e): if not(mq(e,s)=null) then s WITH [basic:= basic(s) with [child := child(basic(s)) with [(e):=true], mq := mq(basic(s)) with [(e):= cdr(mq(e,s))]]] else s endif, children_known(v): …. ack(e):… resolve_contention(e):… root(v): s WITH [basic:= basic(s) with [root:= root(basic(s)) with [(v):= true]]], noop: s tip_start (s:tip_states):Bool = s = (# basic := basic(s) with ….
spec_decls spec_actions: DATATYPE spec_root(rootV: Vertices): spec_root? spec_nu(timeof:(fintime?)): spec_nu? spec_noop: spec_noop? spec_MMTstates: TYPE = [# done:bool #] IMPORTING states[spec_actions,spec_MMTstates,time,fintime?] spec_enabled_general (a:spec_actions, s:spec_states):bool = true; spec_enabled_specific (a:spec_actions, s:spec_states):bool = spec_nu(delta_t): delta_t > zero, spec_root(v): not(done(s)), spec_noop: true spec_trans (a:spec_actions, s:spec_states):spec_states = spec_nu(delta_t): s WITH [now := now(s) + delta_t], spec_root(v): s WITH [basic:= basic(s) with [done := true]], spec_noop : s spec_enabled (a:spec_actions, s:spec_states):bool = spec_enabled_general(a,s) & spec_enabled_specific(a,s) spec_start: [spec_states -> bool] = LAMBDA(s:spec_states):s = (# basic := basic(s) with [done:= false], now := zero, first := (LAMBDA (a:spec_actions): zero), last := (LAMBDA (a:spec_actions): infinity) #);
Action and State maps for Tip amap(aA: tip_actions): spec_actions = nu(t): spec_nu(t), add_child(e): spec_noop, children_known(c): spec_noop, ack(a): spec_noop, resolve_contention(r): spec_noop, root(v): spec_root(v), noop: spec_noop r(sA: tip_states): spec_states = (# basic := (# done := EXISTS (v:Vertices): root(v,sA) #), now := now(sA) #)
Tip Invariants and Refinement Theorem Invariant 15: There is at most one node for which all incoming links are child links refinement_base: bool = (FORALL (sA:tip_states): tip_start(sA) => spec_start(r(sA))); refinement_step: bool = (FORALL (sA:tip_states, aA:tip_actions): tip_reachable(sA) & tip_enabled(aA,sA) => IF tip_visible(aA) THEN (spec_enabled(amap(aA),r(sA)) & r(tip_trans(aA,sA))= spec_trans(amap(aA),r(sA))) ELSE (r(sA) = r(tip_trans(aA,sA))) ENDIF) weak_refinement : bool = refinement_base & weak_refinement_step weak_refinement_thm : THEOREM weak_refinement Check proof of theorem 200
5 Strategy for Refinement Proofs Structure of refinement proofs : (refinement_strat) Expands and splits theorem into base case and induction step Base case proved by skolemizing and expanding r and expanding tip_start and spec_start, followed by propositional simplification and rewriting. Induction step: Case split on the action name, this generates as many subgoals as there are actions For each visible action 2 new subgoals are generated Corresponding visible action is enabled Correspondence for the resulting post state For each invisible action prove either of the following Equality Try propositional simplification and rewriting to finish proof If this does not complete the proof for a particular action then invariants are required, and the user will be prompted to choose the appropriate invariant
Strategy results RPC-MEM Tip Proof completes without any user assistance Run time = 4.25 secs Real time 38.74 secs Can improve using rewrite instead of grind. Tip Used invariant 15 and invariant 13 manually for the branch which required maximum work in the full manual proof.
Future Work Strategies other structured proofs Abstraction Proofs Simulation Proofs Termination Proofs Liveness (?) Porting the system to new and enhanced PVS Interface for HIOA
Reliable Memory Identical to mem specs, except, does not have the mem_failure action. r and amap are identity functions. amap(aA: rel_mem_actions): mem_actions = rel_nu(t): nu(t), rel_read(p,l): read(p,l), rel_write(p, l, v): write(p,l,v), rel_return(p, ack): return(p,ack), rel_bad_arg(p): bad_arg(p), rel_get(p): get(p), rel_put(p): put(p), rel_noop: noop r(sA: rel_mem_states): mem_states = (# basic := (# pc := pc(sA), loc := loc(sA), val := val(sA), performed := performed(sA), memory := memory(sA), legal := legal(sA) #), now := now(sA), first := (LAMBDA(a:mem_actions): zero), last := (LAMBDA(a:mem_actions): infinity) #)
Example 2: Memory : The Spec find:[Locs, Memory -> MemVals] change:[Locs, Vals, Memory -> Memory] find_Ax: AXIOM FORALL(l,m): IF memloc(l) THEN find(l,m) = m(l) ELSE find(l,m) = InitVal ENDIF change_Ax: AXIOM FORALL(l,v,m): IF memloc(l) AND memval(v) THEN change(l,v,m) = m WITH [(l) := v] ELSE change(l,v,m) = m ENDIF mem_actions: DATATYPE nu(timeof:(fintime?)): nu? read(proc:Process, loc:Locs):read? write(proc:Process, loc:Locs, val: Vals):write? return(proc:Process, ack:Ack):return? bad_arg(proc:Process):bad_arg? mem_failure(proc:Process):mem_failure? get(proc:Process):get? put(proc:Process):put? noop:noop? mem_MMTstates: TYPE = [# pc:[Process->Mpc], loc:[Process->Locs], val:[Process->Vals], memory:Memory, performed:[Process->bool], legal:[Process->bool] #] mem_enabled_specific (a:mem_actions, s:mem_states):bool = nu(delta_t): delta_t > zero, read(p,l):true, write(p,l,v):true, return(p,ack):( Read?(pc(s,p)) OR Write?(pc(s,p)) ) & performed(s,p)& IF (Read?(pc(s,p))) THEN (memval_Ack?(ack) & mval(ack) = val(s,p)) ELSE writeOk?(ack) ENDIF, bad_arg(p): ( Read?(pc(s,p)) OR Write?(pc(s,p)) ) AND NOT legal(s,p), mem_failure(p):( Read?(pc(s,p)) OR Write?(pc(s,p)) ), get(p): Read?(pc(s,p)) AND legal(s,p) AND (NOT performed(s,p)), put(p): Write?(pc(s,p)) AND legal(s,p), noop:true