Introduction to PVS for CSE3341

PVS is a formal specification language and prover. It is used in real groups and projects, e.g., the NASA Langley group.

On the Prism computers you can start PVS with: /cs/home/lai/pvs-4.2/pvs

Workflow

Create new file: PVS menu → Files and Theores → new-pvs-file, then enter name (without the .pvs suffix).

Load file: PVS menu → Files and Theores → find-pvs-file, then enter name (without the .pvs suffix). Keyboard: ctrl-c ctrl-f

Save file: PVS menu → Files and Theores → save-pvs-file. Keyboard: ctrl-x ctrl-s. Also the save button in the toolbar.

Parse, typecheck: PVS menu → Parsing and Typechecking → etc. If you ask for typechecking, parsing will also be done. Keyboard: ctrl-c ctrl-t for typechecking.

Prove (one statement): PVS menu → Prover Invocation → prove. This will parse and typecheck first, then start a proof session for the statement near the cursor. If it has been proved previously, you will be asked several questions about whether you want to re-use the previous proof or write a new proof. Keyboard: ctrl-c p

If the prover goes into an infinite loop, press ctrl-c ctrl-c, then you will get a strange message and prompt, then enter :reset, that will bring it back to normal.

Exit: ctrl-x ctrl-c, or close the window.

Proofs are saved in files with the extension .prf, e.g., if your PVS file is X.pvs, proofs are saved in X.prf.

Emacs tidbits:

Syntax

Sample file. (Updated May 12.)

Keywords and operators are case-insensitive, e.g., THEORY=theory, FORALL=forall. Names are case-sensitive.

Theory

name : THEORY BEGIN stuff END name
demo : THEORY
BEGIN
stuff
END demo

A theory is like a module. The above example defines a theory and calls it "demo". Put all your stuff inside a theory. You can have several theories in the same file (but no nesting).

Comments

A comment begins with % and extends to the end of line.

Statements to be proved

name : THEOREM statement
imp : THEOREM forall (x:int): exists (x1:int): not (x=0 & x1>=x)

This assigns the name "imp" to a statement to be proved. Statements to be proved are marked by CHALLENGE, CLAIM, CONJECTURE, COROLLARY, FACT, FORMULA, LAW, LEMMA, PROPOSITION, SUBLEMMA, THEOREM. (Same meaning. Lower case also works because these are keywords.)

Constants and Functions

name : type = value
max_size : nat = 10

name(params and types) : result type = body
f(n:nat, b:bool) : nat = if b then n+1 else n*2 endif

name(params and types) : RECURSIVE result type = body MEASURE expression
zero(n:nat) : RECURSIVE nat = if n>0 then zero(n-1) else 0 endif MEASURE n

These define constants and functions. Recursive functions require special treatment: The idea is that you have to tell PVS why the recursion terminates, and you do this by pointing out an expression that decreases across recursive calls and is non-negative.

You can also specify constants and functions without values if the values do not matter:

name : type
N : nat
F : [nat -> nat]

Expressions and Operators

Variable Declarations

Writing variables with their types everywhere can become tedious, especially when you define a function and then state a lot of properties of that function, e.g.,

f(n:nat) = n+10
T0 : THEOREM forall (n:nat): n>=0
T1 : THEOREM forall (n:nat): n>=1
T2 : THEOREM forall (n:nat): n>=2

To avoid writing n:nat repeatedly, you can do this:

n : VAR nat
f(n) = n+10
T0 : THEOREM forall n: n>=0
T1 : THEOREM forall n: n>=1
T2 : THEOREM forall n: n>=2

Beware that it does not create a "global variable" in the programming sense! All it means is: whenever you define a function with parameter n or write a quantified statement with bound variable n, you can just write n and PVS expands it to n:nat. It just enables a shorthand.

Types

Basic types: bool, nat, int, rat, real, number_field

Function type: [type -> type]

arrays and lists: finite_sequence[item type] e.g., finite_sequence[bool] is the type of arrays of booleans.

Subtypes (restricting a type to a condition): You do not have to say your variable has type nat; you can actually say your variable has type being those natural numbers strictly less than 10, or in general take any existing type and impose an extra condition. The long form is:

{ v : type | condition on v }
{ n:nat | n < 10 }

Beware that v is just for the sake of specifying the condition inside and does not "leak" outside. For example n in the example has nothing to do with any occurrence of n elsewhere.

Here are some examples in various contexts:
N:{n:nat|n<10} declares a constant natural less than 10.
F:[{n:nat|n<10} -> {n:nat|n<5}] declares a function mapping naturals less than 10 to naturals less than 5.
f(n : {n:nat|n>=1}) : {m:nat|m>=2} = n+1 defines a function and is very specific about its domain and co-domain.
silly : THEOREM forall (x : {x:int|x>=-10}): x+1>=-9 For all integers x at least -10, x+1 is at least -9.

In function parameters and quantified variables, the notation (v:{v:type|cond}) is tedious and redundant; therefore a shorthand is allowed: (v:type|cond). Examples:
f(n:nat|n>=1) : {m:nat|m>=2} = n+1 (There is no shorthand for result types.)
silly : THEOREM forall (x:int|x>=-10): x+1>=-9

The most interesting thing happens when a parameter's type depends on previous parameters. Here is a motivation: define a recursive function for the sum of an array from one index to another:
sum(L:finite_sequence[real], i:?, j:?) : RECURSIVE real = ...
What should be the types of i and j? It is not very nice to just make them nat's, since arbitrary indexes may be invalid. Here is a solution: you can say that i is at most L`length, and j is between i and L`length:
sum(L:finite_sequence[real], i:nat|i<=L`length, j:nat|i<=j & j<=L`length) : RECURSIVE real = ...
And so the type of i depends on the value of L, and the type of j depends on the values of both i and L.

Because types in PVS are so flexible, the typechecking phase creates extra statements to be proved. Example: if you mention L`seq(n), PVS wants you to prove 0≤n<L`length. Recursive definitions also create extra statements to be proved (your measure really decreases and is at least 0). To see them, use PVS menu → Viewing TCCs → show-tccs. Most of them can be proved automatically: use PVS menu → Parsing and Typechecking → typecheck-prove. Most people do it after major changes to their files but skip it for minor changes.

Prover Commands

Interrupting a Proof Session

(quit)
quit the whole session
(undo)
go back one step
(postpone)
(when there are several subproofs to do) switch to another subproof

Powerful Commands

(grind)
Logic, guess witnesses for existential statements, rewriting, simplification, linear arithmetic. Some useful options:
(induct-and-simplify "n")
Induction on variable n, then similar to (grind :if-match best). (Most options for (grind) are applicable, too.)
(use "law0" "law1")
Apply the said laws/theorems, with automatic guessing of instantiation like :if-match best.

Basic Commands

This is not a comprehensive list. Just the ones I use most often and adequate.

(flatten)
Decompose OR,IMPLIES into lists of assumptions and conclusions.
(split)
Break AND,IF,IFF into subproofs.
(prop)
Repeatedly (flatten) and (split).
(case "x>0")
Introduce two cases.
(lift-if)
Distribute IF.
(skosimp*)
Strip universal quantifiers, then (flatten). May add this option:
(inst 1 "0")
Use witness 0 for existential quantifier in formula #1.
(induct "n")
Induction on n.
(expand* "f" "g" "h")
Expand the definitions of f,g,h.
(lemma "law0" ("x" "0" "y" "2*k"))
Add law0 to assumptions, instantiating x to 0, y to 2*k.
(forward-chain "law0") or (forward-chain -1)
Use law0 or assumption -1 for deduction among assumptions.
(rewrite "law0")
Use law0 to rewrite.
(assert)
Simplify, linear arithmetic, store assumptions as true and conclusions as false in hope of seeing a contradiction in later steps.