Cases with custom rule in Eisbach - isabelle

I wrote this rule:
method rule_inversion uses P =
(cases rule: nt_network[OF P])
but Isabelle tells me
Rule has fewer premises than arguments given
The exact nt_network doesn't seem to matter, since this fails the same way:
method rule_inversion uses R P =
(cases rule: R[OF P])
How do I apply OF P to a rule used for cases inside Eisbach when P is a parameter of the tactic?

Related

Is there a way to name and refer to a assumption in Isabelle apply-scripts?

Is there any way in Isabelle (2021) to refer to assumptions in the old apply style proofs?
In particular, I am interested in using the assumptions as facts in the OF operator so that I can do (hypothetically):
apply(rule R[OF assm1 assm4])
, where assm1 and assm4 should refer to the 1st and 4th assumptions in the current proof state.
Often times, I can arrange assumptions of the current sugboal so that R[OF assm1 assm4] is the same as the subgoal. But then, I can't finish the proof because I don't know how to refer to assm1 assm4 etc. It seems that only global theorem names are allowed with OF.
I even tried to use the subgoal_tac method on the assumptions, but it does not seem to have an option of giving names to the fact.
In the end, I have to use an automatic script such as simp, which is somewhat opaque for something so obvious. By the way, this is for learning purposes. I tried setting up simp_trace, but still couldn't replicate the effect without using simp.
Moreover,
If there is no way to refer to assumptions, is this a limitation of the tactics or a fundamental limitation of natural deduction? (i.e. Is the rewriting style of R[OF assm1 assm4] not compatible with natural deduction?)
The whole point is Isar is that you can name assumptions...
The first low-level solution is to use drule (or frule to keep the assumptions).
Here is an example:
lemma
assumes ‹⋀x y. P x ⟹ Q y ⟹ R z› ‹P x› ‹Q y›
shows ‹R z›
using assms(2-) apply -
apply (drule assms(1))
apply assumption
apply assumption
done
Look at Chapter 5 for details on the destruction/elimination/intro rules.
The second solution is subgoal:
lemma
assumes ‹⋀x y. P x ⟹ Q y ⟹ R z› ‹P x› ‹Q y›
shows ‹R z›
using assms(2-) apply -
subgoal premises p
by (rule assms(1)[OF p])
done
but this creates hard-to-read proofs if you have very deep nesting.
The third and best solution is to use Isar proofs…
Here is a version that completely avoids using names:
lemma
assumes ‹⋀x y. P x ⟹ Q y ⟹ R z› ‹P x› ‹Q y›
shows ‹R z›
using assms apply -
apply (elim meta_allE[of _ x])
apply (elim meta_allE[of _ y])
apply (drule cut_rl)
apply assumption
apply (drule cut_rl)
apply assumption
apply assumption
done
You can see how ugly this is and why you should avoid that.

cases vs case_tac/induct vs induct_tac

I've been working with Isabelle/HOL for a few months now, but I've been unable to figure out the exact intention of the use of _tac.
Specifically, I'm talking about cases vs case_tac and induct vs indut_tac (although it would be nice to know the meaning of tac in general, since I'm also using other methods such as cut_tac).
I've noticed I can't use cases or induct using apply with ⋀-bound variables, but I can if it's an structured proof. Why?
An example of this:
lemma "¬(∀x. ¬(P x)) ⟹ ∃x. P x"
apply (rule ccontr)
apply (erule notE)
apply (rule allI)
apply (case_tac "P x")
apply (erule notE)
apply (erule exI)
apply assumption
done
On the other hand, another difference I've noticed between induct and induct_tac is that I can use double induction with the latter, but not with the former. Again, I'm clueless why.
Thanks in advance.
*_tac are built-in tactics used in apply-scripts. In particular, case_tac and induct_tac have been basically superseded by the cases and induction proof methods in Isabelle/Isar. As you mentioned, case_tac and induct_tac can handle ⋀-bound variables. However, this is quite fragile, since their names are often generated automatically and may change when Isabelle changes (of course, you could use rename_tac to choose fixed names). That's one of the reasons why nowadays structured proof methods are preferred to unstructured tactic scripts. Now, back to your example: In order to be able to use cases, you can introduce a structured block as follows:
lemma "¬(∀x. ¬(P x)) ⟹ ∃x. P x"
apply (rule ccontr)
apply (erule notE)
proof (intro allI)
fix x
assume "∄x. P x"
then show "¬ P x"
apply (cases "P x")
apply (erule notE)
apply (erule exI)
apply assumption
done
qed
As you can see, structured proofs are typically verbose but much more readable than linear apply-scripts.
If you're still curious about the "double-induction" issue, an example would be very welcome. Finally, if you want to learn more about structured proofs using the Isabelle/Isar language environment, I'd strongly suggest you read this tutorial on Isabelle/HOL and The Isabelle/Isar Reference Manual for more detailed information.

Fragile rule application in Isabelle

I was playing around with an example from the Isabelle/HOL tutorial to get a better understanding on the correspondence between Isar and Tactics proofs.
This is a version which works:
lemma rtrancl_converseD: "(x,y) ∈ (r ^-1 )^* ⟹ (y,x) ∈ r^* "
proof (induct y rule: rtrancl_induct)
case base
then show ?case ..
next case (step y z)
then have "(z, y) ∈ r" using rtrancl_converseD by simp
with `(y,x)∈ r^*` show "(z,x) ∈ r^*" using [[unify_trace_failure]]
apply (subgoal_tac "1=(1::nat)")
apply (rule converse_rtrancl_into_rtrancl)
apply simp_all
done
qed
I want to instantiate converse_rtrancl_into_rtrancl which proofs (?a, ?b) ∈ ?r ⟹ (?b, ?c) ∈ ?r^* ⟹ (?a, ?c) ∈ ?r^* .
But without the seemingly nonsensical apply (subgoal_tac "1=(1::nat)") line this errors with
Clash: r =/= Transitive_Closure.rtrancl
Failed to apply proof method⌂:
using this:
(y, x) ∈ r^*
(z, y) ∈ r
goal (1 subgoal):
1. (z, x) ∈ r^*
If I fully instantiate the rule apply (rule converse_rtrancl_into_rtrancl[of z y r x]) this becomes Clash: z__ =/= ya__.
This leaves me with three questions: Why does this specific case break? How can I fix it? And how can I figure out what went wrong in these cases since I can't really understand what the unify_trace_failure message wants to tell me.
rule-tactics are usually sensitive to the order of premises. The order of premises in converse_rtrancl_into_rtrancl and in your proof state don't match. Switching the order of premises in the proof state using rotate_tac will make them match the rule, so that you can directly apply fact like this:
... show "(z,x) ∈ r^*"
apply (rotate_tac)
apply (fact converse_rtrancl_into_rtrancl)
done
Or, if you want to include some kind of rule tactic, this would look like this:
apply (rotate_tac)
apply (erule converse_rtrancl_into_rtrancl)
apply (assumption)
(I personally don't use apply scripts ever in my everyday work. So apply-style gurus might know more elegant ways of handling this kind of situation. ;) )
Regarding your 1=(1::nat) / simp_all fix:
The whole goal can directly be solved by simp_all. So, attempts with adding stuff like 1=1 probably did not really tell you a lot about how much the other methods contributed to solving the proof.
However, the additional assumption seems to actually help Isabelle match converse_rtrancl_into_rtrancl correctly. (Don't ask me why!) So, one could indeed circumvent the problem by adding this spurious assumption and then eliminating it with refl again like:
apply (subgoal_tac "1=(1::nat)")
apply (erule converse_rtrancl_into_rtrancl)
apply (assumption)
apply (rule refl)
This does not look particularly elegant, of course.
The [[unify_trace_failure]] probably only really helps if one is familiar with the internal workings of Nipkow's higher-order unification algorithm. (I'm not.) I think the hint for the future here would really be that one must look closely at the order of premises for some tactics (rather than at the unifier debug output).
I found an explanation in the Isar reference 6.4.3 .
The with b1..bn command is equivalent to from b1..bn and this, i.e. it enters the proof chaining mode which adds them as (structured) assumptions to proof methods.
Basic proof methods (such as rule) expect multiple facts to be given
in their proper order, corresponding to a prefix of the premises of
the rule involved. Note that positions may be easily skipped using
something like from _ and a and b, for example. This involves the
trivial rule PROP ψ =⇒ PROP ψ, which is bound in Isabelle/Pure as “_”
(underscore).
Automated methods (such as simp or auto) just insert any given facts
before their usual operation. Depending on the kind of procedure
involved, the order of facts is less significant here.
Given the information about the 'with' translation and that rule expects chained facts in order, we could try to flip the chained facts. And indeed this works:
from this and `(y,x)∈ r^*` show "(z,x) ∈ r^*"
by (rule converse_rtrancl_into_rtrancl)
I think "6.4.3 Fundamental methods and attributes" is also relevant because it describes how the basic methods interact with incoming facts. Notably, the '-' noop which is sometimes used when starting proofs turns forward chaining into assumptions on the goal.
with `(y,x)∈ r^*` show "(z,x) ∈ r^*"
apply -
apply (rule converse_rtrancl_into_rtrancl; assumption)
done
This works because the first apply consumes all chained facts so the second apply is pure backwards chaining. This is also why the subgoal_tac or rotate_tac worked, but only if they are in seperate apply commands.

What rule does 'apply (rule)' or 'proof' use?

When I use apply (rule) in an apply-script, typically an appropriate rule is selected. The same holds for proof in structured proofs. Where can I learn/lookup the name of the rule that was used?
You can try using rule_trace as follows:
lemma "a ∧ b"
using [[rule_trace]]
apply rule
which will display in the output:
rules:
?P ⟹ ?Q ⟹ ?P ∧ ?Q
?P ⟹ ?Q ⟹ ?P ∧ ?Q
proof (prove): step 2
goal (2 subgoals):
1. a
2. b
If the names of the rules are needed, you can then try using find_theorems; I'm not sure if they can be directly determined.
Everything that is declared as Pure.intro/intro/iff (or one of its ? or ! variants) is considered as default introduction rule (i.e., if no current facts are chained in). Similarly, everything that is declared as Pure.elim/elim/iff is considered as default elimination rule (i.e., if current facts are chained in). Usually later declarations take precedence over earlier ones (at least if the same kind of declaration is used... mixing, e.g., Pure.intro? with intro, etc., might turn out differently).
However, this just answers what kind of rules are considered in principle. I don't know a way to directly find out which rule was applied. But it is relatively straight-forward to find the correct rule by find_theorems intro directly above the line you were wondering about. E.g.,
lemma "A & B"
find_theorems intro
proof
will show you all rules that could be applied as introduction rule to the goal A & B. One of them is the default rule applied by proof (you will recognize it by the subgoals you obtained).
For elimination rules you can use, e.g.,
lemma assumes "A | B" shows "P"
using assms
find_theorems elim
proof
The other answers already tell you how to determine which lemmas are applied by rule. Note that proof does not call the rule, but the method default. Most of the time, default does the same as rule, but e.g. to prove a locale predicate it calls unfold_locales.
I don't know of any method to see what actually happens there.

Drop a variable in a goal in apply style

Having recently learnt how to drop an unwanted premise in an apply-style proof, I now wonder how to drop an unneeded variable. That is, suppose I have the goal
1. !!x y z. A ⟹ B ⟹ C
where y does not appear in A, B or C. How can I transform it to the following?
1. !!x z. A ⟹ B ⟹ C
triv_forall_equality is indeed the Pure rule to strip redundant parameters. There is also prune_params_tac to do that as ML tactic, it operates on all subgoals. Note that the latter is not exposed as Isar proof method, since it is hardly ever required in practice: tools like simp and auto already include it by default.
Note that the approach via (simp only: triv_forall_equality) works in many situations, but there is also a snag: the only modifier in Isabelle/HOL does a bit more than "only" using the given simp rules. It includes things like arithmetic solvers, which might cause surprise or confusion some situations.
To imitate prune_params_tac precisely within the Isar method language, you could use (unfold triv_forall_equality) although there is a tiny conceptual snag: its use of arbitrary rewriting instead of just infolding equations c = t is just a historical accident.
A simple:
apply simp
will do the trick. If you don't want to perform any other transformations on the goal state, you can try:
apply (simp only: triv_forall_equality)
which will remove the unnecessary meta-quantifiers, but otherwise not modify the goal state.

Resources