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.
Related
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.
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.
I have the following:
lemma
assumes p: "P"
assumes pimpq: "P⟶Q"
shows "P∧Q"
proof -
from pimpq p have q: "Q" by (rule impE)
from p q show ?thesis by (rule conjI)
qed
I have thought that this is down to basic inference rules but reading the documentation for rule in section 9.4.3 Structured Methods of the Isar Reference Manual it turned out that it uses the Classical Reasoner with various rules. And, replacing the by clauses by .. also solves the goals, so mentioning implication elimination and conjunction introduction is not essential.
Is it possible to write a strict formalization here in Isar, i.e. not to use any automation and extra rules that are not explicit in the program text? Something like a forward proof in HOL4.
You could use Pure.rule if you don't want to use the classical module.
lemma
assumes p: "P"
assumes pimpq: "P⟶Q"
shows "P∧Q"
proof -
from pimpq p have q: "Q" by (Pure.rule impE)
from p q show ?thesis by (Pure.rule conjI)
qed
If you write .. Isabelle will automatically select a rule based on lemmas that are marked with the [intro] or [elim] attribute.
Maybe you can also share your HOL4 proof that you want to reproduce in Isabelle, so that we can suggest the equivalent in Isabelle/HOL.
I have been playing around with basic examples of proofs in Isabelle.
Consider the following simple proof:
lemma
fixes n::nat
shows "n*(n+1) = n^2 + n"
by simp
It seems to me that a powerful proof assistant like Isabelle should be able to prove this lemma without much guidance.
However, I was surprised to find out that Isabelle actually fails at applying the rule simp here (I also tried other "generic" rules like simp_all, auto, force, blast but the result is the same).
If I replace the last line by the following, then it works out:
by (simp add: power2_eq_square)
My concern is that I feel like I shouldn't have had to tell the system about the specific rule power2_eq_square to complete this proof.
Playing around with similar trivial examples, I found that simp is able to prove
n*(n+2)=n*n+n*2
but fails with
n*(n+3)=n*n+n*3
The last example is proven
by (simp add: distrib_left)
It is a complete mystery to me why I need to specify distrib_left in that second example, but not in the first (why is that?).
I have given these examples not for their own sake, but mainly to illustrate my main question:
Is there a way to automate the verification of routine algebraic identities such as the above in Isabelle? If there isn't, then why not? What are the technical obstacles?
Daily proof work indeed often stumbles over »routine algebraic identities«; but after some practical experience one usually develops some intuition how to solve such problems effectively. A pattern I have developed over the years, by example:
context semidom
begin
lemma "a * (b ^ 2 + c) + 2 = a * b * b + c * a + 2"
A typical explorative proof starts with
apply auto
Then associativity and commutative are considered also
apply (auto simp add: ac_simps)
Then more algebaic normalizing rules are applied
apply (auto simp add: algebra_simps)
The last gap is then easily filled by sledgehammer
apply (simp add: power2_eq_square)
After that, the proof can be compactified
by (simp add: algebra_simps power2_eq_square)
The lemma
lemma power2_eq_square: "a^2 = a * a"
is not a good rewrite rule in general, as it will easily blow up the size of terms. So it is expected that a term rewriting based automation like simp will not apply this without you telling it to.
What you want is some sort of proof search, and Isabelle provides that: After writing your lemma, you can invoke the sledgehammer tool, and it will readily and quickly find the proof for you:
Sledgehammering...
Proof found...
"z3": Try this: by (simp add: power2_eq_square) (1 ms)
"cvc4": Try this: by (simp add: power2_eq_square) (5 ms)
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.