A prototype checker that uses foundational proof certificates (FPCs) to define small outline proofs of lemmas that require simple inductions and previously proved lemmas. These definitions are given as intuitionistic specifications written as logic programs using least fixed point definitions. A focused sequent calculus is used to structure the search used in the proof checker.
Dependencies:
- Bedwyr 1.4-beta1 or newer (will be bumped up presently).
The checker is meant to be instantiated and loaded on top of the Bedwyr runtime, and thus requires no dedicated installation procedure.
Documentation and references coming shortly. The basic workflow is as follows.
From scratch:
- Define or choose an FPC template.
- Write predicates and theorems.
- Assemble the system and possibly write certificates for the theorems.
- Load instantiated checker on Bedwyr.
To check everything:
bedwyr -t -I my-system-harness.thm
Or interactively just:
bedwyr my-system-harness.thm
More realistically, use a translator to bypass steps 1-3 as shown below.
As Bedwyr has primitive support for modules and namespaces, be mindful to avoid, and remember to check for, the usual silly errors: name clashes, order of declarations and includes, etc.
Together with the Abella interactive theorem prover and the Bedwyr logic programming environment, FPCcheck conforms the system we call ACheck. With it, we can use FPCs to independently verify results formulated, and possibly proved in, Abella.
The following tutorial sections illustrate the use of this system, which itself relies on FPC templates and examples included in this distribution. To use the system, you will need the code in this repository, as well as a working installation of both Abella and Bedwyr (see dependency notes for current versions).
For more information you can refer to the following working paper:
[Proof outlines as proof certificates: a system description] (http://www.lix.polytechnique.fr/Labo/Dale.Miller/papers/acheck.pdf) by Roberto Blanco and Dale Miller in Proceedings of the First International Workshop on Focusing (2015)
The script acheck.pl
automates a simple workflow given an Abella session file
and full paths to both Abella and Bedwyr. As with the other scripts, it is quick
and dirty with no advanced security built in, and best (one could argue
peremptorily) used in a controlled environment.
Here we use the checker to certify results from the Abella proof assistant by means of simple FPCs. A basic familiarity with its workflow is assumed, but can easily be gained if needed by referring to the first sections of this primer:
[Abella: a system for reasoning about relational specifications] (http://jfr.unibo.it/article/view/4650) by David Baelde et al. in Journal of Formalized Reasoning, vol. 7, no. 2, pp. 1-89 (2014)
Dependencies:
- Abella 2.0.3-fpc
- OCaml 4.02.0 or newer
The first thing you need is a file with the contents of an Abella session: declarations, definitions and theorems. Proofs are currently ignored and can be elided: to trust the correctness of the theorem (and its proof) we will put our faith no longer in Abella, but in the checker; not in the proof script, but in the certificate.
Run the session file through a translator prepared to export proof obligations
to the checker. Currently, this is an instrumented fork of Abella that can be
built and run normally and is available from
here (branch fpc-translation
).
Interactive use is possible but somewhat more sensitive to errors. A (mostly)
error-free session is assumed. For batch processing, use:
abella my-session-file.thm
The integrated translator in the background generates a collection of files in the working directory, currently named as follows:
fpc-decl.thm
: declarations and definitions encoded for the checker.fpc-sign.thm
: signature based onfpc-decl.thm
for internal use.fpc-thms.thm
: for each theorem, an encoding for the checker and a verifier that takes a certificate and uses it to try to prove the theorem.fpc-test.thm
: instantiation of the checker and proof assertions for all verifiers.
If the working directory is a direct subfolder of the checker, all its
components will be found and assembled. If necessary, correct the paths at the
top of fpc-test.thm
to point to the actual location of the checker (i.e., all
those outside the set of files just mentioned).
The harness file fpc-test.thm
furnishes default certificates that should be
able to prove many relatively simple results. To run the checker, use:
bedwyr -t -I fpc-test.thm
This will try to prove each theorem in turn using its verifier and the certificate given in the corresponding assertion, terminating upon failure. This failure could arise from a certificate that does not admit a proof of the theorem, or possibly (if it was proved by Abella) from a bug in Abella itself: the so called theorem may not be such!
For our current, tutorial purposes, let us assume the latter will not be the case. :-)
After the definition of the original Abella file, the main user task would be the production and refinement of certificates for each failing assertion until all theorems are checked by their respective certificates. The translation makes use of a simple FPC template that admits the following main types of certificates (cf. the FPC documentation for further information).
- Simple certificates: proofs as bounded-depth search.
(induction B A S A S)
: induct greedily once, and attempt to find a proof with a maximum depth ofB
bipoles, plus initial rules at the end. Each bipole may containA
instances of the unfold inference rule during each asynchronous phase, andS
occurrences during each synchronous phase.(apply B A S A S)
: attempt to find a proof with a maximum depth ofB
bipoles, plus initial rules at the end. Each bipole may containA
instances of the unfold inference rule during each asynchronous phase, andS
occurrences during each synchronous phase.
- Nested certificates: proofs as trees of operations.
(induction? C)
: induct greedily and continue using certificateC
. Currently, it can only be used as the root of a tree.(case? A C1 C2)
: perform asynchronous case analysis greedily (left or) after at mostA
(asynchronous) unfoldings. Continue along each branch using certificatesC1
andC2
, respectively.(apply? A S I C)
: close the current bipole unfolding at mostA
times during the remainder of the asynchronous phase andS
times during the synchronous phase. Use indexI
to arbitrate the transition between both phases making either a local or global decision, i.e., a lemma. Continue using certificateC
after closing the bipole.search
: try to finish the proof by application of the initial rules.
The translation generates verifiers accepting certificates of these shapes. In any given proof, all previous theorems may be used as lemmas. Under the previous certificate schemes of the simple FPC, indexing is given as:
(idx "local")
: make a local, internal decision, therefore private to the checker.(idx "my-theorem-name")
: decide globally usingmy-theorem-name
, declared earlier in the session.
Detailed (i.e., nested) certificates with tight bounds should always work well in principle. On the other hand, being overly generous with unfoldings may, in some cases, lead Bedwyr to unification problems it cannot solve, terminating the proof instead of backtracking to safety.
To illustrate usage of the full system, suppose we have the following simple
Abella session, plus.thm
:
Kind nat type.
Type z nat.
Type s nat -> nat.
Define nat : nat -> prop by
nat z ;
nat (s N) := nat N.
Define plus : nat -> nat -> nat -> prop by
plus z N N ;
plus (s N) M (s P) := plus N M P.
Theorem plustotal :
forall N, nat N -> forall M, nat M -> exists S, plus N M S.
induction on 1. intros. case H1.
search.
apply IH to H3. apply H4 to H2. search.
Ignoring for now the presence of proof scripts, we can now run the file through Abella:
abella plus.thm
This will generate a bunch of .thm
files (as described above). Chief in
importance for us is fpc-test.thm
, which opens with the following include
declarations:
#include "../kernel/logic.thm".
#include "../kernel/cert-sig.thm".
#include "../fpc/simple-fpc.thm".
#include "fpc-sign.thm".
#include "../kernel/kernel.thm".
#include "fpc-thms.thm".
If necessary, we adjust the ..
"placeholders" to point to the absolute or
relative path to the main checker directory. Once done, we turn our attention to
certificate design.
Considering the structure of the Abella proof of plustotal
, there is the
common induction pattern:
induction on #. intros. case H#.
We can achieve a similar result by application of the obvious induction, which
includes the case analysis that derives as many goals as the predicate has
clauses: two in the case of plus
, the zero case and the successor case.
(If necessary, to accommodate the greedy nature of induction certificates, we
would reorder the hypotheses so that H#
becomes H1
.) In certificate terms,
this translates into:
(induction?
(case? 0
...
...
)
)
Where the two missing pieces are the sub-certificates for each case. Now, we consider each resulting goal in turn.
For the zero case, we have:
Variables: M
IH : forall N, nat N * -> (forall M, nat M -> (exists S, plus N M S))
H2 : nat M
============================
exists S, plus z M S
All the script does is apply the search
tactic. It is easy to see this
involves focusing on the right for the positive phase, unfolding once on the
right and applying initial rules. So following our certificate syntax, we get:
(apply? 0 1 (idx "local") search)
For the successor case, we have:
Variables: M N1
IH : forall N, nat N * -> (forall M, nat M -> (exists S, plus N M S))
H2 : nat M
H3 : nat N1 *
============================
exists S, plus (s N1) M S
Whereas in Abella we need to appeal to the induction hypothesis explicitly, it becomes a natural consequence of the asynchronous phase, and we need not deal with it explicitly in the certificate. After doing this, we arrive at this state:
Variables: M N1
IH : forall N, nat N * -> (forall M, nat M -> (exists S, plus N M S))
H2 : nat M
H3 : nat N1 *
H4 : forall M, nat M -> (exists S, plus N1 M S)
============================
exists S, plus (s N1) M S
Note that at this point we have a collection of "atoms" and negative formulas
on the left, and a positive goal on the right: if nothing is done to the atoms,
they will be frozen, never to be touched again, and we will have reached the end
of the negative phase. The proof script instructs to operate on H4
using other
hypotheses for assumptions. Thus, as explained, fixed points will be frozen and
the bipole will transition to the positive phase. Thus, we have one bipole:
(apply? 0 0 (idx "local") ...)
After release, we will begin a new bipole:
Variables: M N1 S
IH : forall N, nat N * -> (forall M, nat M -> (exists S, plus N M S))
H2 : nat M
H3 : nat N1 *
H4 : forall M, nat M -> (exists S, plus N1 M S)
H5 : plus N1 M S
============================
exists S, plus (s N1) M S
As before, the search
tactic concludes the goal, and with it the proof:
(apply? 0 1 (idx "local") search)
And so we arrive at the final certificate:
(induction?
(case? 0
(apply? 0 1 (idx "local") search)
(apply? 0 0 (idx "local") (apply? 0 1 (idx "local") search))
)
)
We could include this information in the Abella file making use of the ship
tactic, with the only proviso that double quotes should be encoded as single
quotes within the .thm
file. The theorem would be stated thus:
Theorem plustotal :
forall N, nat N -> forall M, nat M -> exists S, plus N M S.
ship "
(induction?
(case? 0
(apply? 0 1 (idx 'local') search)
(apply? 0 0 (idx 'local') (apply? 0 1 (idx 'local') search))
)
)
".
The resulting harness file fpc-test.thm
will contain proof harnesses for each
theorem with a shipped certificate. Those proved or skipped within Abella will
present commented placeholders, but the translation will assume that trust in
their correctness lies elsewhere. In this way, we can verify all shipped
theorems without further action on our part:
bedwyr -t -I fpc-test.thm
Note that full trust in the certificate requires that a certificate be supplied and verified for every single theorem. Otherwise, correctness on the whole would be proved subject to the correctness of the elided proofs.
Certificates can be written or rewritten directly on the fpc-test.thm
file. In
this case, double quotes are written normally. Furthermore, there is no need to
fix everything prior to execution. Rather, we can load the system interactively:
bedwyr fpc-test.thm
Within the session, we can attempt to prove a theorem invoking its proof
predicate (the theorem name concatenated with the suffix __proof__
) and
supplying a certificate. For example:
plustotal__proof__ (induction 1 0 1 0 1).
Let us now extend our session file with a proof of the commutativity of addition, which employs lemmas for the sub-proofs of the zero case and the successor case that will result from a proof by induction of the main result:
Theorem plus0com : forall N, nat N -> plus N z N.
induction on 1. intros. case H1.
search.
apply IH to H2. search.
Theorem plusscom : forall M, nat M -> forall N, nat N ->
forall P, plus M N P -> plus M (s N) (s P).
induction on 1. intros. case H1.
case H3. search.
case H3. apply IH to H4. apply H6 to H2. apply H7 to H5. search.
Theorem pluscom : forall N, nat N -> forall M, nat M ->
forall S, plus N M S -> plus M N S.
induction on 1. intros. case H1.
case H3. apply plus0com to H2. search.
case H3. apply IH to H4. apply H6 to H2. apply H7 to H5.
apply plusscom to H2. apply H9 to H4. apply H10 to H8. search.
We will focus on the main result, pluscom
; the two lemmas will make for easy
practice after the explanation.
First of all, an important technical point that has some bearing on the transition from Abella proofs to certificates: if you step throughout the proof in Abella, you will notice that the successor case opens with the following sequent:
Variables: M S N1
IH : forall N, nat N * ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H2 : nat M
H3 : plus (s N1) M S
H4 : nat N1 *
============================
plus M (s N1) S
Here, the annotations match N1
in H4
with the natural number N
upon which
we are inducing. The successor case shows up in H3
, which is the subject of
the first step in the case: a natural, determinate unfolding. Critically, later
on we will appeal to H4
directly to move forth with the proof, but the
induction rule of our sequent calculus consumes the fixed point it is
applied to: it appears unfolded, but not directly, in the resulting sequent. If
the proof relies on its presence, minor adjustments may be needed, as will be
seen shortly.
For now, let us duplicate the inductive assumption so that it will be present in both systems (improvements in the translation procedure will enable automation of this process easily). Thus, we will prove the following variant of the theorem instead:
Theorem pluscom : forall N, nat N -> nat N -> forall M, nat M ->
forall S, plus N M S -> plus M N S.
induction on 1. intros. case H1.
case H4. apply plus0com to H3. search.
case H2. case H4. apply IH to H5 H6. apply H8 to H3. apply H9 to H7.
apply plusscom to H3. apply H11 to H5. apply H12 to H10. search.
After the usual induction scheme and associated certificate, we commence with the zero case for the first of two missing branches:
Variables: M S
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H2 : nat z
H3 : nat M
H4 : plus z M S
============================
plus M z S
Still in the asynchronous phase, we detect a clear unfolding candidate in H4
,
and add it to the resources our certificate will need. Then we get to:
Variables: S
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H2 : nat z
H3 : nat S
============================
plus S z S
Among our hypotheses, we have everything we need to invoke the previous lemma
plus0com
to derive the desired conclusion, so everything is frozen and a
"global decision" is made to move to the synchronous phase. At the end, we will
have the conclusion among our premises. Therefore, the partial certificate is:
(apply? 1 0 (idx "plus0com") ...)
And the resulting sequent:
Variables: S
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H2 : nat z
H3 : nat S
H5 : plus S z S
============================
plus S z S
So we simply close with search
(also in the certificate) and move on to the
successor case:
Variables: M S N1
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H2 : nat (s N1)
H3 : nat M
H4 : plus (s N1) M S
H5 : nat N1 *
============================
plus M (s N1) S
Here we see two obvious candidates for deterministic case analysis, i.e.,
asynchronous unfolding. Interestingly, one of those is the still unfolded form
of the inductive hypothesis, and the point of junction with our aforementioned
adjustments. We also let the proof proceed with the appeal to IH
, as explained
before, and arrive at:
Variables: M N1 P
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H3 : nat M
H5 : nat N1 *
H6 : nat N1
H7 : plus N1 M P
H8 : forall M, nat M -> (forall S, plus N1 M S -> plus M N1 S)
============================
plus M (s N1) (s P)
At this point, the synchronous phase must follow. The proof script instructs to
select the implication H8
(a local decide) picking one of the hypotheses to
satisfy the left branch, giving value to the free variable in the process and
producing H9
:
Variables: M N1 P
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H3 : nat M
H5 : nat N1 *
H6 : nat N1
H7 : plus N1 M P
H8 : forall M, nat M -> (forall S, plus N1 M S -> plus M N1 S)
H9 : forall S, plus N1 M S -> plus M N1 S
============================
plus M (s N1) (s P)
Still in the synchronous phase and focused on the resulting formula H9
, we
find another implication, which we treat analogously yielding:
Variables: M N1 P
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H3 : nat M
H5 : nat N1 *
H6 : nat N1
H7 : plus N1 M P
H8 : forall M, nat M -> (forall S, plus N1 M S -> plus M N1 S)
H9 : forall S, plus N1 M S -> plus M N1 S
H10 : plus M N1 P
============================
plus M (s N1) (s P)
The product is a least fixed point, which by virtue of its polarity ends the synchronous phase. So we ended the bipole with a local decision and two asynchronous unfoldings, which betokens the certificate:
(apply? 2 0 (idx "local") ...)
Now we can apply the successor lemma. This constitutes a shallow bipole: simply decide on the lemma, choose the right hypotheses, and release with the new conclusion hypothesis:
Variables: M N1 P
IH : forall N, nat N * -> nat N ->
(forall M, nat M -> (forall S, plus N M S -> plus M N S))
H3 : nat M
H5 : nat N1 *
H6 : nat N1
H7 : plus N1 M P
H8 : forall M, nat M -> (forall S, plus N1 M S -> plus M N1 S)
H9 : forall S, plus N1 M S -> plus M N1 S
H10 : plus M N1 P
H11 : forall N, nat N -> (forall P, plus M N P -> plus M (s N) (s P))
============================
plus M (s N1) (s P)
The certificate is likewise simple, filling part of the gap left by the previous bipole and leaving room for a bit more of the proof:
(apply? 0 0 (idx "plusscom") ...)
The rest of the proof is as the first bipole in the branch: there is nothing to
do except make a decision on the conclusion of the lemma, picking hypotheses for
its assumptions during the synchronous phase. At the end, we obtain a negative
formula that closes the bipole and coincides with the desired conclusion, so
search
concludes both branch and proof.
(apply? 0 0 (idx "local") search)
Here is the certificate for the full proof:
(induction?
(case? 0
(apply? 1 0 (idx "plus0com") search)
(apply? 2 0 (idx "local") (apply? 0 0 (idx "plusscom") (apply? 0 0 (idx "local") search)))
)
)
Simple certificates can be easier to arrive at and are certainly shorter. The trade-off is less information and longer verification times spent in search of the critical pieces of information left out of the proof outline.
Suppose now that by whichever dark arts we concoct the following block
certificate for pluscom
:
(induction 3 2 0 2 0)
From our previous efforts we can easily infer that this expresses a sufficiently wide search space to prove the theorem, provided that the necessary lemmas are available. But we do not need this to be the case, and it is often a reasonable guess that a good library of lemmas will allow many results to be proven by such basic certificates.
A more interesting question is how to go from a general, blind certificate to a specific, guided one, closer to the proof reconstruction than the proof search end of the spectrum, and in most cases substantially faster to check. In the certificate constructors considered so far, there are two important kinds of information: bounds and decisions. The latter both constrain the former and are in general far more difficult to fiddle with. They constitute the skeleton of a proof, and we will focus on them in this part.
The simple FPC template defines two additional families of constructors of interest.
(induction# B A S A S D)
and(apply# B A S A S D)
: decorated counterparts of the block certificates with an additional parameterD
representing the tree of decisions that are made throughout the proof.(pair# C1 C2)
: a certificate pair that takes two certificatesC1
andC2
and uses them in tandem during checking.
One important idea behind certificate pairs is that they can be used to implement certificate elaboration: checking one, fully defined certificate, and using the reconstructed proof to produce a second, refined certificate, with additional or different information.
In particular, we would like to retrieve the tree of decisions from the block
certificate (induction 3 2 0 2 0)
in order to speed up subsequent checking of
the theorem. To this end, we can pair it with its decorated counterpart, where
the decision tree is unknown:
(pair# (induction 3 2 0 2 0) (induction# 3 2 0 2 0 D))
If we attempt to check this certificate, and in the absence of contradicting information, the first half must lead to the same proof as before. The second half, proceeding in lockstep, will collect the tree of decisions and output it upon success. We can use this to build a more detailed certificate like the one we obtained constructively from the Abella proof script above. Slightly more sophisticated marshalling will let the checker move from the decision tree to the nested certificate by itself.
It should be noted that Bedwyr as runtime is not optimized for speed, and for deep proofs detailed certificates currently have a very clear advantage over simple ones.
Drop a line or get hacking!
How to know if you've broken something? Bedwyr support for debugging and unit
testing is rudimentary, so things can get interesting. The file debug.thm
implements the conventional debugging predicates and (generous) traces can be
obtained uncommenting lines ended with the %DEBUG
marker in .thm
files. To
toggle tracing, debug.pl
can be used (this script rewrites files, play with it
with the usual care).
The current best shot at a test battery is the collection of harness files and
Abella sessions available in the examples
folder. These can be thought of (and
run as) integration tests for the system. To run the whole test battery, use
test.pl
.