Accumulating results while using them in Isabelle/Isar - isabelle

Sometimes in a proof I find myself needing to accumulate results, but also needing use the last result, so I end up using "also then" for that purpose:
proof
have ...
also then have ...
also then have ...
ultimately show ...
qed
I feel like there are more idiomatic ways to this that I don't know about. On the other hand, this might be the standard way to do it and encouraged by the community.
So in light of that, I have two questions:
Is using "also then" discouraged?
If so, what alternatives can I use to accumulate results while using them?

I will start by providing some background. You have breached the subject known as calculational reasoning in Isabelle. Calculation reasoning is described in subsection 1.2 of the document The Isabelle/Isar Reference Manual.
Two of the most common patterns for calculational reasoning are
have "a R b" sorry
also have "b R c" sorry
also have "c R d" sorry
finally have "a R d" by assumption
(where R is a transitive relation, such as =, written using the infix notation) and
have P sorry
moreover have Q sorry
moreover have R sorry
ultimately have S by (rule assms(1))
The commands like also and moreover use an additional fact calculation to store additional information. For example, as the calculation in the first example above proceeds, the fact calculation changes in the following manner
have "a R b" sorry
also have "b R c" (*calculation: a R b*) sorry
also have "c R d" (*calculation: a R c*) sorry
finally have "a R d" by assumption
In this case, the transitivity rule of R is used for chaining the predicates. Thus, the final goal can be discharged by assumption. The situation is different for the moreover ... ultimately pattern:
have P sorry
moreover have Q (*calculation: P*) sorry
moreover have R (*calculation: P, Q*) sorry
ultimately have S (*P ⟹ Q ⟹ R ⟹ S*) by (rule assms(1))
In this case, the fact calculation merely accumulates all previous results.
The implementation of the calculational reasoning is explained in subsection 6.3 of the document The Isabelle/Isar Reference Manual. However, I omit the details in this post.
I will now make an attempt to answer your questions in the context of what was stated above.
Is using "also then" discouraged?
I believe that this is not, necessarily, discouraged and there are some instances of the use of this pattern in the AFP. However, I can imagine that for this specific pattern this would be a reasonably uncommon use case.
If so, what alternatives can I use to accumulate results while using
them?
I believe that if you, indeed, need to merely accumulate results (while, possibly, using them in the intermittent steps), the best pattern to use would be moreover ... ultimately. However, of course, this depends on what exactly is meant by the "accumulation of results".
Remark 1
I hope that from the discussion above it is apparent that the use of also in conjunction with ultimately is very unconventional. In most cases, it makes little sense to use such a pattern.
Remark 2
The pattern also ... finally is often used in conjunction with the abbreviation ...:
have "a R b" sorry
also have "... R c" sorry
also have "... R d" sorry
finally have "a R d" by assumption
Of course, the benefits can only become apparent if b and c are sufficiently long subterms.

Related

How to have full control over substitution in Isabelle

In Isabelle I find myself often using
apply(subst xx)
apply(rule subst)
apply(subst_tac xx)
and similar command but often it is a hit or miss. Is there any resources on how to guide the
term unification and how to precisely specify the terms that should be substituted for?
For example if there are multiple ways to perform unification, how can I disambiguate?
If I have multiple equalities among the premises, how can I tell Isabelle which one of them to use? I spend way too much time wrestling with such seemingly simple problems.
This book https://www21.in.tum.de/~nipkow/LNCS2283/ has a chapter dedicated to substitution but it's far too short, only covers erule ssubst and doesn't really answer my questions.
To give some examples, this is ssubst
lemma ssubst: "t = s ⟹ P s ⟹ P t"
by (drule sym) (erule subst)
but what about
lemma arg_cong: "x = y ⟹ f x = f y"
by (iprover intro: refl elim: subst)
How can I do erule_tac arg_cong and specify exactly the desired f, x and y? Anything I tried resulted in Failed to apply proof method which is not a particularly enlightening error message.
As I recall, a more elaborate substitution method is available, in particular for restricting substitution to certain contexts. But to answer your question properly it's essential to know what sort of assertions you are trying to prove. If you are working with short expressions (up to a couple of lines long), then it's much better to guide the series of transformations using equational reasoning, via also and finally. (See Programming and Proving in Isabelle/HOL, 4.2.2 Chains of (In)Equations.) Then at the cost of writing out these expressions, you'll often find that you can prove each step automatically, without detailed substitutions, and you can follow your reasoning.
Also note that if your expressions are long because they contain large, repeated subexpressions, you can introduce abbreviations using define. You will then not only have shorter and clearer proofs, but you will find that automation will perform much better.
The other situation is when you are working with verification conditions dozens of lines long, or even longer. In that case it is worth looking for more advanced substitution packages.

How to manage all the various proof methods

Is there a "generic" informal algorithm that users of Isabelle follow, when they are trying to prove something that isn't proved immediately by auto or sledgehammer? A kind of general way of figuring out, if auto needs additional lemmas, formulated by the user, to succeed or if better some other proof method is used.
A related question is: Is there maybe a table to be found somewhere with all the proof methods together with the context in which to apply them? When I'm reading through the Programming and Proving tutorial, the description of various methods (respectively variants of some methods, such as the many variant of auto) are scattered through the text, which constantly makes me go back and for between text and Isabelle code (which also leads to forgetting what exactly is used for what) and which results in a very inefficient workflow.
No, there's no "generic" informal way. You can use try0 which tries all standard proof methods (like auto, blast, fastforce, …) and/or sledgehammer which is more advanced.
After that, the fun part starts.
Can this theorem be shown with simpler helper lemmas? You can use the command "sorry" for assuming that a lemma is true.
How would I prove this on a piece of paper? And then try to do this proof in Isabelle.
Ask for help :) Lots of people on stack overflow, #isabelle on freenode and the Isabelle mailing list are waiting for your questions.
For your second question: No, there's no such overview. Maybe someone should write one, but as mentioned before you can simply use try0.
ammbauer's answer already covers lots of important stuff, but here are some more things that may help you:
When the automation gets stuck at a certain point, look at the available premises and the goal at that point. What kind of simplification did you expect the system to do at that point? Why didn't it do it? Perhaps the corresponding rule is just not in the simp set (add it with simp add:) or some preconditions of the rule could not be proved (in that case, add enough facts so that they can be proved, or do it yourself in an additional step)
Isar proofs are good. If you have some complicated goal, try breaking it down into smaller steps in Isar. If you have bigger auxiliary facts that may even be of more general interest, try pulling them out as auxiliary lemmas. Perhaps you can even generalise them a bit. Sometimes that even simplifies the proof.
In the same vein: Too much information can confuse both you and Isabelle. You can introduce local definitions in Isar with define x where "x = …" and unfold them with x_def. This makes your goals smaller and cleaner and decreases the probability of the automation going down useless paths in its proof search.
Isabelle does not automatically unfold definitions, so if you have a definition, and you want to unfold it for a proof, you have to do that yourself by using unfolding foo_def or simp add: foo_def.
The defining equations of functions defined with fun or primrec are unfolding by anything using the simplifier (simp, simp_all, force, auto) unless the equations (foo.simps) have manually been deleted from the simp set. (by lemmas [simp del] = foo.simps or declare [simp del] foo.simps)
Different proof methods are good at different things, and it takes some experience to know what method to use in what case. As a general rule, anything that requires only rewriting/simplification should be done with simp or simp_all. Anything related to classical reasoning (i.e. first-order logic or sets) calls for blast. If you need both rewriting and classical reasoning, try auto or force. Think of auto as a combination of simp and blast, and force is like an ‘all-or-nothing’ variant of auto that fails if it cannot solve the goal entirely. It also tries a little harder than auto.
Most proof methods can take options. You probably already know add: and del: for simp and simp_all, and the equivalent simp:/simp del: for auto. However, the classical reasoners (auto, blast, force, etc.) also accept intro:, dest:, elim: and the corresponding del: options. These are for declaring introduction, destruction, and elimination rules.
Some more information on the classical reasoner:
An introduction rule is a rule of the form P ⟹ Q ⟹ R that should be used whenever the goal has the form R, to replace it with P and Q
A destruction rule is a rule of the form P ⟹ Q ⟹ R that should be used whenever a fact of the form P is in the premises to replace to goal G with the new goals Q and R ⟹ G.
An elimination rule is something like thm exE (elimination of the existential quantifier). These are like a generalisation of destruction rules that also allow introducing new variables. These rules often appear in this like case distinctions.
The classical reasoner used by auto, blast, force etc. will use the rules in the claset (i.e. that have been declared intro/dest/elim) automatically whenever appropriate. If doing that does not lead to a proof, the automation will backtrack at some point and try other rules. You can disable backtracking for specific rules by using intro!: instead of intro: (and analogously for the others). Then the automation will apply that rule whenever possible without ever looking back.
The basic proof methods rule, drule, erule correspond to applying a single intro/dest/elim rule and are good for single step reasoning, e.g. in order to find out why automatic methods fail to make progress at a certain point. intro is like rule but applies the set of rules it is given iteratively until it is no longer possible.
safe and clarify are occasionally useful. The former essentially strips away quantifiers and logical connectives (try it on a goal like ∀x. P x ∧ Q x ⟶ R x) and the latter similarly tries to ‘clean up’ the goal. (I forgot what it does exactly, I just use it occasionally when I think it might be useful)

Apply simplifier to arbitrary term

I have a term in mind, say "foo 1 2 a b", and I'd like to know if Isabelle can simplify it for me. I'd like to write something like
simplify "foo 1 2 a b"
and have the simplified term printed in the output buffer. Is this possible?
My current 'workaround' is:
lemma "foo 1 2 a b = blah"
apply simp
which works fine but looks a bit hacky.
What doesn't work (in my case) is:
value "foo 1 2 a b"
because a and b are unbound variables, and because my foo involves infinite sets and other fancy stuff, which the code generator chokes on.
There is no built-in feature AFAIK, but there are several ways to achieve this. You have already discovered one of them, namely state the term as a lemma and then invoke the simplifier. The drawback is that this cannot be used in all contexts, for example, not inside an apply proof script.
Alternatively, you can invoke the simplifier via the attribute [simplified]. This works in all contexts via the thm command and produces the output in the output buffer. First, the term must be injected into a theorem, then you can apply simplify to the theorem and display the result with thm. Here is the preparatory stuff that can go into your theory of miscellaneous stuff.
definition simp :: "'a ⇒ bool" where "simp _ = True"
notation (output) simp ("_")
lemma simp: "simp x" by(simp add: simp_def)
Then, you can write
thm simp[of "foo 1 2 a b", simplified]
and see the simplified term in the output window.
The evaluation mechanism is probably not what you want, because evaluation uses a different set of rewrite rules (namely the code equations) than the simplifier normally uses (the simpset). Therefore, this is likely to evaluate to a different term than by applying the simplifier. To see the difference, apply code_simp instead of simp in your approach with lemma "foo 1 2 a b = blah". The proof method code_simp uses the code equations just like value [simp] used to.
When using the value command, the evaluation of the argument is conducted by registered evaluators (see the Reference Manual of Isabelle2013-2).
It used to be possible to explicitly choose an evaluator in previous versions of Isabelle (e.g., Isabelle2013-2) by giving an extra argument to the value command. E.g.,
value [simp] "foo 1 2 a b"
It seems that in Isabelle2014 this parameter was dropped and according to the Reference Manual of Isabelle2014, the strategy is now fixed to first use ML code generation and in case this fails, normalization by evaluation.
From the NEWS file in the development version (e82c72f3b227) of Isabelle it seems as if this parameter will be enabled again in the upcoming Isabelle release.
UPDATE: As Andreas pointed out, value [simp] does not use the same set of simplification rules as apply simp. So even if available, the solution I described above will most likely not yield the result you want.

Isabelle: Sledgehammer finds a proof but it fails

Often times I have the problem that sledgehammer finds a proof, but when I insert it, it doesn't terminate. I guess sledgehammer is one of the most important parts of Isabelle, but then it gets annoying if a proof fails.
In the Sledgehammer tutorial,
there is a small chapter on "Why does Metis fail to reconstruct the proof?".
It lists:
Try the isar_proofs option to obtain a step-by-step Isar proof where
each step is justified by metis. Since the steps are fairly small,
metis is more likely to be able to replay them.
Try the smt proof method instead of metis. It is usually stronger,
but you need to either have Z3 available to replay the proofs, trust
the SMT solver, or use certificates.
Try the blast or auto proof methods, passing the necessary facts
via unfolding, using, intro:, elim:, dest:, or simp:, as
appropriate.
The problem is that the first option makes the proof more verbose and also it involves manual intervention.
The second option rarely works.
So what about the third option. Are there any easy to follow heuristics that I can apply?
What's the difference between unfolding and using? Also are there any best practices on how to use intro:, elim:, and dest: from a failed metis proof?
Partial EXAMPLE
proof-
have "(det (?lm)) = (det (transpose ?lm))" by (smt det_transpose)
then have "(det (?lm)) = [...][not shown]"
unfolding det_transpose transpose_mat_factor_col by auto
then show ?thesis [...][not shown]
qed
I would like to get rid of the first line of the proof, as the line seems trivial. If I remove the first line, sledgehammer will still find a proof, but this found proof fails (doesn't terminate).
Concerning your statement sledgehammer is one of the most important parts of Isabelle:
You never need sledgehammer to succeed with a proof. But of course sledgehammer is very convenient and can save a lot of tedious reasoning. Thus it is definitely a very important part for making Isabelle more usable for people who did not spend many years using it (and even for those sledgehammer makes everyday proving more productive).
Coming to your question
Try the blast or auto proof methods, passing the necessary facts via unfolding,
using, intro:, elim:, dest:, or simp:, as appropriate.
[...]
So what about [this] option. Are there any easy to follow heuristics that I can apply?
Indeed there are:
unfolding: This (recursively) unfolds equations, i.e., it is very similar to apply (simp only: ...). The heuristic is, when you do not get the expected result with simp: ... try unfolding ... instead (it might be the case that other equations are interfering).
using: This is used to add additional assumptions to the current subgoal. The heuristic is, whenever a fact does not fit one of the patterns below, try using instead.
intro:: This is used for introduction rules, i.e., of the form that whenever certain assumptions are satisfied some connective (or more generally constant) may be introduced.
Example: A ==> B ==> A & B (where the introduced constant is (&)).
elim:: This is used for elimination rules, i.e., of the form that from the presence of a certain connective (or more generally constant) some facts may be concluded as additional assumptions.
Example: A & B ==> (A ==> B ==> P) ==> P (where the constant (&) is eliminated in favor of explicitly having A and B as assumptions). Note the general form of the conclusion (which is not related to the major premise A & B), this is important to not loose provability (see also dest:).
dest:: This is used for destruction rules, i.e., of the form that from the presence of a certain constant some facts may be concluded directly.
Example: A & B ==> B (Note that the information that A holds is lost in the conclusion, unlike in the elim: example.)
simp:: This is used for simplification rules, i.e., (conditional) equations, which are always applied from left to right (thus it is sometimes useful to add [symmetric] to a fact, in order to apply it from right to left, but beware of nontermination, for it is easy to introduce looping derivations in this way).
Having said this, often it is just experience that lets you decide in which way best to employ a given fact inside a proof. What I usually do when I got a proof by sledgehammer which is too slow in Isar is to inspect the facts that where used by the found proof. Then categorize them as above, invoke auto appropriately and if that did not completely solve the goal, apply sledgehammer once more (hopefully delivering an "easier" proof this time).
You ask a number of questions, but I'll take your title and the second paragraph as the essence of your main complaint, where I end up giving a long-winded answer which can be summarized with,
Sledgehammer is part of a three-pronged arsenal,
you becoming more experienced, with never ending experimentation, along with trial and error is the heuristic,
not using many of the proofs which Sledgehammer returns is a big part of using Sledgehammer, and
the minimize and preplay_timeout options can save you some time and frustration by automatically playing the proofs back, which gives you timing information, and sometimes shows that a found proof will fail.
Starting with your second paragraph, you say:
Often times I have the problem that Sledgehammer finds a proof. But then I try it, but the proof doesn't terminate. I guess Sledgehammer is one of most important parts of Isabelle,...
Sledgehammer is important, but I consider it part of a three-pronged arsenal, where the three parts would be:
Detailed proof steps using natural deduction.
Automatic proof methods, such as auto, simp, rule, etc. A big part of this would be creating your own simp rewrite rules, and learning to use theorems with rule and the myriad of other automatic proof methods.
Sledgehammer calling automatic theorem provers (ATPs). Using steps 1 and 2, with experience, are used to set up Sledgehammer. Experience counts for a lot. You might use auto to simplify things so that Sledgehammer succeeds, but you might not use auto because it will expand formulas to where Sledgehammer has no chance of succeeding.
...but then it gets annoying if a proof fails.
So here, your expectations and my expectations for Sledgehammer diverge. These days, if I get annoyed, I get annoyed that I will have to work more than 30 seconds to prove a theorem. If I'm hugely disappointed that a particular Sledgehammer proof fails, it's because I've been trying to prove a theorem for hours or days without success.
Using Sledgehammer not to find proofs, but to find good proofs
Automation can sometimes alleviate frustration. Clicking on a Sledgehammer proof, only to find out that it fails, would be frustrating. Here is the way I currently use Sledgehammer, unless I start becoming desperate for a proof:
sledgehammer_params[minimize=smart,preplay_timeout=10,timeout=60,verbose=true,
max_relevant=smart,provers="
remote_vampire metis remote_satallax z3_tptp remote_e
remote_e_tofof spass remote_e_sine e z3 yices
"]
The options minimize=smart and preplay_timeout=10 are related to Sledgehammer playing back proofs, after it finds them. Not using many of the proofs that Sledgehammer finds is a big part of using Sledgehammer, and proof playback is a big part of culling out proofs.
Myself, I don't deal much with Sledgehammer proofs that don't terminate, but that's probably because I'm selective to begin with.
My first criteria for a Sledgehammer proof is that it be reasonably fast, and so when Sledgehammer reports that it's found a proof that's greater than 3 seconds long, I don't even try using it, unless I'm desperate to find out whether a theorem can be proved.
The use of Sledgehammer for me usually goes like this:
State a theorem and see if I get lucky with Sledgehammer.
If Sledgehammer gives me a proof that's 30 milliseconds or less, then I consider that good proof, but I still experiment with try and the automated proof methods of section 9.4.4, page 208, of isar-ref.pdf. Many times I can get a proof down to 5ms or less.
A metis proof of total time over 100ms, I'm willing to work 30 minutes or more to try and get a faster proof.
A metis proof of 200ms to 500ms, I'll resort to everything I know to try and get it down to below 100ms, which many times means converting to a detailed proof.
A smt or metis proof of greater than 1 second I only consider good as a temporary proof.
A proof in the output panel that Sledgehammer reports as being greater than 3 seconds, I usually don't even try, because even if it ends up working, I'm going to have to work to find another proof anyway, so I'd rather spend my time up front trying to find a good proof.
The option 3 heuristic
You say,
So what about the third option. Are there any easy to follow heuristics that I can apply?
The heuristic is:
"as appropriate",
which is to say that the heuristic is "use Sledgehammer as part of a three-pronged arsenal".
The heuristic is also "read lots of tutorials and documentation so that you have lots of other things to use with Sledgehammer". Sledgehammer is powerful, but it's not infinitely powerful, and for some theorems, you can use your own simp rules to prove in 0ms with apply(simp) or apply(auto) what Sledgehammer will never prove.
For myself, I'm up to about 150 to 200 theorems, so the "as appropriate" has much more meaning to me that it used to have. Basically, you try and set up Sledgehammer the way it needs to be set up.
The way Sledgehammer needs to be set up will sometimes mean running auto or simp first, but sometimes not, because many times running auto or simp will doom Sledgehammer to failure.
But sometimes, you don't even want a metis proof from Sledgehammer, except as a preliminary proof until you can find a better proof, which, for me, generally means a faster proof using the automatic proof methods.
I'm no authority on Sledgehammer, but it seems Sledgehammer is good at matching up hypotheses and conclusions from old theorems, with hypotheses and conclusions being used for a new theorem. What it's not good at is proving formulas which I've greatly expanded by using simp and auto.
I continue with the long-winded heuristic that is Sledgehammer centric:
Use Sledgehammer to jump-start the proof process, by proving some theorems with Sledgehammer that you otherwise don't know how to prove.
Turn your theorems which are equivalencies into simp rewrite rules for use with automatic proof methods like simp, auto, fastforce, etc., as described in chapter 9 of tutorial.pdf.
Use some of your theorems for conditional rewrite rules for use with intro and rule.
The last two steps are used to completely solve a proof step or used to set up Sledgehammer "as appropriate". Sledgehammer never ceases to be useful, no matter how much you know, and it's extremely useful when you don't know much, but Sledgehammer alone is not the road to success.
If Sledgehammer can't prove a theorem, then resort to a detailed proof, starting with a bare-bones detailed proof. Sometimes, breaking up an if-and-only-if into two conditionals allows Sledgehammer to easily prove the two conditionals, when it couldn't prove the if-and-only-if.
After you've proved lots of stuff, go back and optimize your proofs. Sometimes, with all the rewrite rules you've created, simp and auto will magically prove things, and you will get rid of some metis proofs that Sledgehammer found for you. Sometimes, you'll use Sledgehammer to find a metis proof that's even faster.
Use this command to optimize timing:
ML_command "Toplevel.timing := true"
There's another SO post giving more detail about it.
I can answer your subquestion "What's the difference between unfolding and using?". Roughly speaking, it works like this.
Suppose lemma foo is of the form x = a+b+c. If you write
unfolding foo
in your proof, then all occurrences of x will be replaced with a+b+c. On the other hand, if you write
using foo
then x=a+b+c will be added to your list of assumptions.

Logic question (universal and existential quantifications)

I have a logical statement that says "If everyone plays the game, we will have fun".
In formal logic we can write this as:
Let D mean the people playing.
Let G be the predicate for play the game.
Let F be the predicate for having fun.
Thus [VxeD, G(x)] -> [VyeD, F(y)]
V is the computer science symbol for universal quantification. E below is the existential quantifier.
I'm looking for a way to write a similar statement using only existential quantifiers. My best guess would be that we simply need to find a way to find the counter-example where it doesn't happen, thus negate the above.
The problem is negating it doesn't make sense. We get:
[VxeD, G(x)] ^ [EyeD, !L(y)]
It's not a proper statement since the universal is still in there though it is also equivalent. Thus I need to re-fabricate my statement to something like: VxeD, VyeD, G(x) ^ F(y) I would get ExeD, EyeD, !G(x) v !F(y) which would mean "There exists someone who doesn't learn or someone else who doesn't have fun" which doesn't seem correct to me.
Some guidance or clarification would be fantastic :-)
Thanks!
I don't understand your ^ symbol, but I believe you are looking for the contrapositive. In your example, if the original statement is:
[VxeD, G(x)] -> [VyeD, F(y)]
then the contrapositive is
[ExeD, !F(x)] -> [EyeD, !G(y)]
meaning "if there is someone who is not having fun, then there exists someone not playing the game." Note that this is different than the statement in your comment above: it may well be the case that everyone is having fun, but not everyone is playing.
In general, p -> q is equivalent to !q -> !p.
(Of course I may not have understood your notation correctly.)
I'm having trouble reading your notation. I'll use A for the universal quantifier, E for the existence quantifier, F for the predicate 'having fun', G for the predicate 'playingng learned the game', then
AxL(x) -> AxF(x)
Now, you can just apply the usual gymnastics:
<==> !AxL(x) <- !AxF(x)
<==> Ex!G(x) <- Ex!F(x)
<==> Ex!F(x) -> Ex!G(x)
so, indeed, when someone's not having fun, it means not everybody played the game.

Resources