I am struggling to understand why each of the examples below either works or doesn't work and more abstractly how induction interacts with tactics vs Isar. I'm working on 4.3 in Programming and Proving in Isabelle/HOL (Dec 2016) on Windows 10 with the latest Isabelle/HOL (2016-1)
There are 8 cases: the lemma is either long (includes explicit name) or short, structured (using assumes and shows) or unstructred (using the arrows) and the proof is either structured (Isar) or unstructured (tactical).
theory Confusing_Induction
imports Main
begin
(* 4.3 *)
inductive ev :: " nat ⇒ bool " where
ev0: " ev 0 " |
evSS: " ev n ⟹ ev (n + 2) "
fun evn :: " nat ⇒ bool " where
" evn 0 = True " |
" evn (Suc 0) = False " |
" evn (Suc (Suc n)) = evn n "
1. Structured short lemma & structured proof
(* Hangs/gets stuck/loops on the following *)
(*
lemma assumes a: " ev (Suc(Suc m)) " shows" ev m "
proof(induction "Suc (Suc m)" arbitrary: " m " rule: ev.induct)
*)
2. Unstructured short lemma & structured proof
(* And this one ends up having issues with
"Illegal application of proof command in state mode" *)
(*
lemma " ev (Suc (Suc m)) ⟹ ev m "
proof(induction " Suc (Suc m) " arbitrary: " m " rule: ev.induct)
case ev0
then show ?case sorry
next
case (evSS n)
then show ?case sorry
qed
*)
3. Structured long lemma & structured proof
(* And neither of these can apply the induction *)
(*
lemma assumes a1: " ev n " and a2: " n = (Suc (Suc m)) " shows " ev m "
proof (induction " n " arbitrary: " m " rule: ev.induct)
lemma assumes a1: " n = (Suc (Suc m)) " and a2: "ev n " shows " ev m "
proof (induction " n " arbitrary: " m " rule: ev.induct)
*)
(* But this one can ?! *)
(*
lemma assumes a1: " ev n " and a2: " n = (Suc (Suc m)) " shows " ev m "
proof -
from a1 and a2 show " ev m "
proof (induction " n " arbitrary: " m " rule: ev.induct)
case ev0
then show ?case by simp
next
case (evSS n) thus ?case by simp
qed
qed
*)
4. Unstructured long lemma & structured proof
(* And this is the manually expanded form of the Advanced Rule Induciton from 4.4.6 *)
lemma tmp: " ev n ⟹ n = (Suc (Suc m)) ⟹ ev m "
proof (induction " n " arbitrary: " m " rule: ev.induct)
case ev0 thus ?case by simp
next
case (evSS n) thus ?case by simp
qed
lemma " ev (Suc (Suc m)) ⟹ ev m "
using tmp by blast
**5. Structured short lemma & unstructured proof*
(* Also gets stuck/hangs *)
(*
lemma assumes a: " ev (Suc (Suc m)) " shows " ev m "
apply(induction "Suc (Suc m)" arbitrary: " m " rule: ev.induct)
*)
6. Unstructured short lemma & unstructured proof
(* This goes through no problem (``arbitrary: " m "`` seems to be optional, why?) *)
lemma " ev (Suc(Suc m)) ⟹ ev m "
apply(induction "Suc (Suc m)" arbitrary: " m " rule: ev.induct)
apply(auto)
done
7. Structured long lemma & unstructured proof
(* But both of these "fail to apply the proof method" *)
(*
lemma assumes a1: " n = (Suc (Suc m)) " and a2: " ev n" shows " ev m "
apply(induction " n " arbitrary: " m " rule: ev.induct)
lemma assumes a1: " ev n " and a2: " n = (Suc (Suc m)) " shows " ev m "
apply(induction " n " arbitrary: " m " rule: ev.induct)
*)
8. Unstructured long lemma & unstructured proof
lemma " ev n ⟹ n = (Suc (Suc m)) ⟹ ev m "
apply(induction " n " arbitrary: " m " rule: ev.induct)
apply(auto)
done
end
I posted this to the cl-isabelle-users#lists.cam.ac.uk and received the following response from Larry Paulson. I've reproduced it below.
Thanks for your query. Some of your problems have to do with supplying premises to the induction in the correct way, but there are at least two big problems here.
(* 1. Structured short lemma & structured proof *)
(* Hangs/gets stuck/loops on the following *)
lemma assumes a: "ev (Suc(Suc m))” shows "ev m"
proof(induction "Suc (Suc m)" rule: ev.induct)
Doing it this way, your assumption is not visible, the goal is simply “ev m”, so induction isn’t applicable. But it’s absolutely bad that this mistake causes the induction method to loop.
(* 2. Unstructured short lemma & structured proof *)
(* And this one ends up having issues with
"Illegal application of proof command in state mode" *)
lemma "ev (Suc (Suc m)) ⟹ ev m"
proof(induction "Suc (Suc m)" rule: ev.induct)
case ev0
then show ?case
sorry
next
case (evSS n)
then show ?case sorry
qed
Here you are getting the error "Failed to refine any pending goal”, which breaks the rest of the proof. The reason you are getting this error message is that for some reason there is a mismatch between the goals you have and the goals that the induction methods thinks you have. In fact this proof can be written straightforwardly, but its shape is quite unexpected. This is also very bad.
lemma "ev (Suc (Suc m)) ⟹ ev m"
proof(induction "Suc (Suc m)" rule: ev.induct)
show "⋀n. ev n ⟹
(n = Suc (Suc m) ⟹ ev m) ⟹
n + 2 = Suc (Suc m) ⟹ ev m"
by simp
qed
Your (3,7,8) is the same problem as your (1) except that the induction method (correctly) fails. Clearly the argument "Suc (Suc m)” is causing the induction method to loop for some reason, as in your (5).
(* 6. Unstructured short lemma & unstructured proof *)
(* This goes through no problem (``arbitrary: " m "`` seems to be optional, why?) *)
It is simply that only some proofs need “arbitrary”, namely when the induction hypothesis involves variables that need to be generalised.
Your (7) can be written like this:
lemma assumes "ev n" and " n = (Suc (Suc m)) " shows " ev m "
using assms
proof(induction " n " arbitrary: " m " rule: ev.induct)
case ev0
then show ?case
sorry
next
case (evSS n)
then show ?case
sorry
qed
That is, you can supply the assumptions to the proof as shown (“using”). We even get the right cases doing it this way.
We are in a new release phase, but I hope that the problems involving the induction method and non-atomic terms can be fixed promptly.
Larry Paulson
Related
I was trying to recreate a simplified version of the natural numbers, for learning purposes (as it involves inductive definitions, recursive functions, etc...). In that process however, I got stuck in something that I thought would be very trivial.
Basically, I have a definition for natural numbers 'natt' and a definition for the '<' relation:
datatype natt = Zero | Succ natt
primrec natt_less :: "natt ⇒ natt ⇒ bool" (infixl "<" 75) where
"natt_less n Zero = False"
| "natt_less n (Succ m') = (case n of Zero ⇒ True | Succ n' ⇒ natt_less n' m')"
and from these, I tried to prove 3 basic properties of the < relation:
Non-reflexivity: ~ (a < a)
Non-symmetry: a < b ⟹ ~ (b < a)
Transitivity: a < b ⟹ b < c ⟹ a < c
I was able to prove the first, but not the others. What took me even more by surprise, is that there are some sub-lemmas that would aid on these, such as Succ a < b ⟹ a < b, a < b ⟹ a < Succ b or a < b ∨ a = b ∨ b < a, which seem even more trivial, but nonetheless I was also not able to prove, even after many attempts. It seems like only one of these (including 2. and 3.) is enough to prove the rest, but I wasn't able to prove any of them.
I'm mostly trying to use induction. Together with the fact that I've made the definitions myself, there are two possibilities - Either my definitions are wrong, and do not have the desired properties, or I'm missing some method/argument. So, I have two questions:
Is my definition wrong (i.e. it does not accurately represent < and lacks the desired properties)? If so, how may I fix it?
If not, how can I prove these seemingly trivial properties?
For context, my current attempts are by induction, which I can prove the base case, but always get stuck in the induction case, not really knowing where to go with the assumptions, such as in this example:
lemma less_Succ_1: "Succ a < b ⟹ a < b"
proof (induction b)
case Zero
assume "Succ a < Zero"
then have "False" by simp
then show ?case by simp
next
case (Succ b)
assume "(Succ a < b ⟹ a < b)" "Succ a < Succ b"
then show "a < Succ b" oops
After some tips from user9716869, it's clear that my main problem was the lack of knowledge about the arbitrary option in induction. Using (induction _ arbitrary: _) and (cases _) (see the reference manual for details), the proofs are quite straight forward.
Since these are made for educational purposes, the following proofs are not meant to be concise, but to make every step very clear. Most of these could be vastly reduced if more automation is desired, and some can be done in one line (which I left as a comment below the lemma).
Note: In these proofs, we are using an implicit lemma about inductive types, their injectivity (which implies (Succ a = Succ b) ≡ (a = b) and Zero ≠ Succ a). Furthermore, (Succ a < Succ b) ≡ (a < b) by definition.
First, we prove 2 useful lemmas:
a < b ⟹ b ≠ Zero
b ≠ Zero ⟷ (∃ b'. b = Succ b')
lemma greater_not_Zero [simp]: "a < b ⟹ b ≠ Zero"
(*by clarsimp*)
proof
assume "a < b" "b = Zero"
then have "a < Zero" by simp
then show "False" by simp
qed
lemma not_Zero_is_Succ: "b ≠ Zero ⟷ (∃ b'. b = Succ b')"
(*by (standard, cases b) auto*)
proof
show "b ≠ Zero ⟹ ∃ b'. b = Succ b'"
proof (cases b)
case Zero
assume ‹b ≠ Zero›
moreover note ‹b = Zero›
ultimately show "∃b'. b = Succ b'" by contradiction
next
case (Succ b')
assume ‹b ≠ Zero›
note ‹b = Succ b'›
then show "∃b'. b = Succ b'" by simp
qed
next
assume "∃ b'. b = Succ b'"
then obtain b'::natt where "b = Succ b'" by clarsimp
then show "b ≠ Zero" by simp
qed
Armed with these, we can prove the 3 main statements:
Non-reflexivity: ~ (a < a)
Non-symmetry: a < b ⟹ ~ (b < a)
Transitivity: a < b ⟹ b < c ⟹ a < c
lemma less_not_refl [simp]: "¬ a < a"
(*by (induction a) auto*)
proof (induction a)
case Zero
show "¬ Zero < Zero" by simp
next
case (Succ a)
note IH = ‹¬ a < a›
show "¬ Succ a < Succ a"
proof
assume "Succ a < Succ a"
then have "a < a" by simp
then show "False" using IH by contradiction
qed
qed
lemma less_not_sym: "a < b ⟹ ¬ b < a"
proof (induction a arbitrary: b)
case Zero
then show "¬ b < Zero" by simp
next
case (Succ a)
note IH = ‹⋀b. a < b ⟹ ¬ b < a›
and IH_prems = ‹Succ a < b›
show "¬ b < Succ a"
proof
assume asm:"b < Succ a"
have "b ≠ Zero" using IH_prems by simp
then obtain b'::natt where eq: "b = Succ (b')"
using not_Zero_is_Succ by clarsimp
then have "b' < a" using asm by simp
then have "¬ a < b'" using IH by clarsimp
moreover have "a < b'" using IH_prems eq by simp
ultimately show "False" by contradiction
qed
qed
lemma less_trans [trans]: "a < b ⟹ b < c ⟹ a < c"
proof (induction c arbitrary: a b)
case Zero
note ‹b < Zero›
then have "False" by simp
then show ?case by simp
next
case (Succ c)
note IH = ‹⋀a b. a < b ⟹ b < c ⟹ a < c›
and IH_prems = ‹a < b› ‹b < Succ c›
show "a < Succ c"
proof (cases a)
case Zero
note ‹a = Zero›
then show "a < Succ c" by simp
next
case (Succ a')
note cs_prem = ‹a = Succ a'›
have "b ≠ Zero" using IH_prems by simp
then obtain b' where b_eq: "b = Succ b'"
using not_Zero_is_Succ by clarsimp
then have "a' < b'" using IH_prems cs_prem b_eq by simp
moreover have "b' < c" using IH_prems b_eq by simp
ultimately have "a' < c" using IH by simp
then show "a < Succ c" using cs_prem by simp
qed
qed
Having worked with Coq before, I'm used to its system of "focusing" and "unfocusing" goals, so you can work with one goal at a time.
Does a similar system exists in Isabelle?
As an example, this code:
theory Scratch
imports Main
begin
theorem add_0: "n+0 = (n::nat)"
apply(induction n)
Generates a proof state with 2 subgoals:
proof (prove)
goal (2 subgoals):
1. 0 + 0 = 0
2. ⋀n. n + 0 = n ⟹
Suc n + 0 = Suc n
If I use apply(auto), both of them are solved. Let's suppose however that I want to work only on goal 1, is it possible to "focus" on it? If not, how may I apply auto to only one (or some) of the subgoals?
If you don't want to use Isar (which probably is better for readability), you can use subgoal to focus on the goal:
theorem add_0: "n+0 = (n::nat)"
apply(induction n)
subgoal by auto
subgoal by auto
or the brackets:
apply auto[]
to focus auto on the first goal only.
The main difference is that subgoal makes it impossible to instantiate schematic variables.
Mathias and Manuel mentioned the Isar style as the preferred way to focus on subgoals. Here is an example of how that could look like:
theorem add_0: "n+0 = (n::nat)"
proof (induction n)
case 0 ― ‹Focus on induction base subgoal here›
show "0 + 0 = (0::nat)"
by (rule plus_nat.add_0)
next
case (Suc n) ― ‹Focus on induction step subgoal here›
show "Suc n + 0 = Suc n"
proof (subst plus_nat.add_Suc)
show "Suc (n + 0) = Suc n"
by (subst Suc.IH) (rule refl)
qed
qed
Or implicitly, without naming cases:
theorem add_0: "n+0 = (n::nat)"
proof (induction n)
show "0 + 0 = (0::nat)"
by (rule plus_nat.add_0)
next
fix n :: nat
assume IH: "n + 0 = n"
show "Suc n + 0 = Suc n"
proof (subst plus_nat.add_Suc)
show "Suc (n + 0) = Suc n"
by (subst IH) (rule refl)
qed
qed
Consider as an example the following definition of inequality of natural numbers in Isabelle:
inductive unequal :: "nat ⇒ nat ⇒ bool" where
zero_suc: "unequal 0 (Suc _)" |
suc_zero: "unequal (Suc _) 0" |
suc_suc: "unequal n m ⟹ unequal (Suc n) (Suc m)"
I want to prove irreflexivity of unequal, that is, ¬ unequal n n. For illustration purposes let me first prove the contrived lemma ¬ unequal (n + m) (n + m):
lemma "¬ unequal (n + m) (n + m)"
proof
assume "unequal (n + m) (n + m)"
then show False
proof (induction "n + m" "n + m" arbitrary: n m)
case zero_suc
then show False by simp
next
case suc_zero
then show False by simp
next
case suc_suc
then show False by presburger
qed
qed
In the first two cases, False must be deduced from the assumptions 0 = n + m and Suc _ = n + m, which is trivial.
I would expect that the proof of ¬ unequal n n can be done in an analogous way, that is, according to the following pattern:
lemma "¬ unequal n n"
proof
assume "unequal n n"
then show False
proof (induction n n arbitrary: n)
case zero_suc
then show False sorry
next
case suc_zero
then show False sorry
next
case suc_suc
then show False sorry
qed
qed
In particular, I would expect that in the first two cases, I get the assumptions 0 = n and Suc _ = n. However, I get no assumptions at all, meaning that I am asked to prove False from nothing. Why is this and how can I conduct the proof of inequality?
You are inducting over unequal. Instead, you should induct over n, like this:
lemma "¬ (unequal n n)"
proof (induct n)
case 0
then show ?case sorry
next
case (Suc n)
then show ?case sorry
qed
Then we can use Sledgehammer on each of the subgoals marked with sorry. Sledgehammer (with CVC4) recommends us to complete the proof as follows:
lemma "¬ (unequal n n)"
proof (induct n)
case 0
then show ?case using unequal.cases by blast
next
case (Suc n)
then show ?case using unequal.cases by blast
qed
The induction method handles variable instantiations and non-variable instantiations differently. A non-variable instantiation t is a shorthand for x ≡ t where x is a fresh variable. As a result, induction is done on x, and the context additionally contains the definition x ≡ t.
Therefore, (induction "n + m" "n + m" arbitrary: n m) in the first proof is equivalent to (induction k ≡ "n + m" l ≡ "n + m" arbitrary: n m) with the effect described above. To get this effect for the second proof, you have to replace (induction n n arbitrary: n) with (induction k ≡ n l ≡ n arbitrary: n). The assumptions will actually become so simple that the pre-simplifier, which is run by the induction method, can derive False from them. As a result, there will be no cases left to prove, and you can replace the whole inner proof–qed block with by (induction k ≡ n l ≡ n arbitrary: n).
I am trying to prove something, that I thought would be relatively simple.
(k≥10 ⟹ 2^(k::nat) > (k::nat)^3)
However, I am getting stuck and do not know how to progress.
lemma "(k≥10 ⟹ 2^(k::nat) > (k::nat)^3)"
proof (induction k)
case 0
then show ?case by simp
next
case (Suc k)
assume "10 ≤ k ⟹ k ^ 3 < 2 ^ k"
assume "10 ≤ Suc k"
show "(Suc k) ^ 3 < 2 ^( Suc k)"
apply (simp add: algebra_simps)
then show ?case sorry
qed
A good induction rule can save a lot of hassle. You want to start induction with 10, not 0, so maybe there is a better induction rule? Using
find_theorems name:ind name:Nat
I find this one
Nat.dec_induct: ?i ≤ ?j ⟹ ?P ?i ⟹ (⋀n. ?i ≤ n ⟹ n < ?j ⟹ ?P n ⟹ ?P (Suc n)) ⟹ ?P ?j
From now on it is simple equational reasoning, and I was able to follow the proof in this math stackexchange answer step by step:
lemma
assumes "k≥10"
shows "2^(k::nat) > (k::nat)^3"
using assms
proof(induction rule: Nat.dec_induct)
case base show ?case by simp
next
case (step n)
note power2_eq_square[simp] power3_eq_cube[simp] ring_distribs[simp]
have "Suc n ^ 3 = 1 + 3*n + 3*n^2 + n^3" by simp
also have "… < 51*n + 3*n + 3*n^2 + n^3" using `n ≥ 10` by simp
also have "… = 54*n + 3*n^2 + n^3" by simp
also have "… < 6*n^2 + 3*n^2 + n^3" using `n ≥ 10` by simp
also have "… = 9*n^2 + n^3" by simp
also have "… < n^3 + n^3" using `n ≥ 10` by simp
also have "… = 2 * n^3" by simp
also have "… < 2 * 2^n" using `n ^ 3 < 2 ^ n` by simp
also have "… = 2^(Suc n)" by simp
finally show ?case.
qed
For algebraic proofs, the trick is to exactly identify the simplification steps and then find_theorems to search the relevant rules. Algebraic simp set doesn't contain aggressive simplifaction rules, probably because oversimplification might leave the goals unprovable. (Someone can add more reasons)
In this particular case, you have to assume 10 ≤ k for induction hypothesis to work. You can easily do that by case_tac "10 ≤ k", and then rewriting by monoid_mult_class.power3_eq_cube and le_Suc_ex. For ¬ 10 ≤ k, you can prove the goal using not_less_eq_eq.
I want to be able to prove a statement by induction on n (of type nat). It consists of a conditional whose antecedent is only true for n >= 2. A conditional whose antecedent is false is always true. So I'd like to prove the cases n=0, n=1 and n=2 all separately from the main inductive step. Is it possible to do a proof by induction with three base cases like the following:
lemma "P (n::nat) --> Q"
proof (induct n)
case 0
show ?case sorry
next
case 1
show ?case sorry
next
case 2
show ?case sorry
next
case (Suc n)
show ?case sorry
qed
As it stands, this doesn't seem to work. I could prove "P (n+2) --> Q" by induction instead, but it wouldn't be as strong a statement. I'm considering a case split into "n=0","n=1" and "n>=2", and proving only the last case by induction.
The cleanest way is probably to prove a custom induction rule for the kind of induction that you want, like this:
lemma nat_0_1_2_induct [case_names 0 1 2 step]:
assumes "P 0" "P 1" "P 2" "⋀n. n ≥ 2 ⟹ P n ⟹ P (Suc n)"
shows "P n"
proof (induction n rule: less_induct)
case (less n)
show ?case using assms(4)[OF _ less.IH[of "n - 1"]]
by (cases "n ≤ 2") (insert assms(1-3), auto simp: eval_nat_numeral le_Suc_eq)
qed
lemma "P (n::nat) ⟶ Q"
proof (induction n rule: nat_0_1_2_induct)
In theory, the induction_schema method is also very useful to prove such custom induction rules, but in this case, it doesn't help a lot:
lemma nat_0_1_2_induct [case_names 0 1 2 step]:
"P 0 ⟹ P 1 ⟹ P 2 ⟹ (⋀n. n ≥ 2 ⟹ P n ⟹ P (Suc n)) ⟹ P n"
proof (induction_schema, goal_cases complete wf terminate)
case (complete P n)
thus ?case by (cases n) force+
next
show "wf (Wellfounded.measure id)" by (rule wf_measure)
qed simp_all
You could also use less_induct directly and then do a case distinction within the induction step for the base cases.