Should I use universal quantification in lemma formulation? - isabelle

For
datatype natural = Zero | Succ natural
primrec add :: "natural ⇒ natural ⇒ natural"
where
"add Zero m = m"
| "add (Succ n) m = Succ (add n m)"
I prove
lemma add_succ_right: "⋀ m n. add m (Succ n) = Succ (add m n)"
For being mathematical, it is important to have universal quantification. However, for using this fact in the simplifier, it is better to do it without:
lemma add_succ_right_rewrite: "add m (Succ n) = Succ (add m n)"
What is the common wisdom about these versions, which one should I prefer in what circumstances?

Isabelle/HOL has three ways to universally quantify over variables in lemma statements:
lemma 1: "⋀m n. add m (Succ n) = Succ (add m n)"
lemma 2:
fixes m n
shows "add m (Succ n) = Succ (add m n)"
lemma 3: "∀m n. add m (Succ n) = Succ (add m n)"
Additionally, free variables in lemma statements become automatically quantified:
lemma 4: "add m (Succ n) = Succ (add m n)"
Lemmas 1, 2, and 4 yield the same theorem, which can be used in identical ways later on. Lemma 3 uses the HOL universal quantifier instead of the quantification from the meta-logic. Therefore, extra work is needed to instantiate the quantifier in lemma 3. Thus, this version should only be used in special circumstances.
The version in lemma 1 dates back to when the Isar language was not in its current state and is thus somewhat out-dated. Therefore, I would suggest to prefer version 2 (if you want to explicitly mention the quantified variables), or 4 (if not).

Related

How to proceed in Isabelle when the goal has implications and existentials?

I'm trying to write a proof in the Isabelle "structured style" and I'm not sure how to specify the value of existential variables. Specifically, I'm trying to expand the sorrys in this proof:
lemma division_theorem: "lt Zero n ⟹ ∃ q r. lt r n ∧ m = add (mul q n) r"
proof (induct m)
case Zero
then show ?case
by (metis add_zero_right mul.simps(1))
next
case (Suc m)
then show ?case
proof (cases "Suc r = n")
case True
then show ?thesis sorry
next
case False
then show ?thesis sorry
qed
qed
Zero, add, and mul are defined on a nat-like class that I made just for the purposes of writing simple number theory proofs, hopefully that is intuitive. I have done this in the "apply" style, so I'm familiar with how the proof is supposed to go, I'm just not understanding how to turn it into "structured" style.
So the goals generated by these cases are:
1. (lt Zero n ⟹ ∃q r. lt r n ∧ m = add (mul q n) r) ⟹
lt Zero n ⟹ cnat.Suc r = n ⟹ ∃q r. lt r n ∧ cnat.Suc m = add (mul q n) r
2. (lt Zero n ⟹ ∃q r. lt r n ∧ m = add (mul q n) r) ⟹
lt Zero n ⟹ cnat.Suc r ≠ n ⟹ ∃q r. lt r n ∧ cnat.Suc m = add (mul q n) r
At a high level, for that first goal, I want to grab the q and r from the first existential, specify q' = Suc q and r' = Zero for the second existential, and let sledgehammer bash out precisely what mix of arithmetic lemmas to use to prove that it works. And then do that same for q' = q and r' = Suc r for the second case.
How can I do this? I have tried various mixes of obtain, rule exI, but I feel like I'm not understanding some basic mechanism here. Using the apply style this works when I apply subgoal_tac but it seems like that is unlikely to be the ideal method of solution here.
As you can see in the two goals generated by the command cases "Suc r = n", the occurrences of variable r in both the expressions cnat.Suc r = n and cnat.Suc r ≠ n are actually free and thus not related to the existentially quantified formula whatsoever. In order to "grab" the q and r from the induction hypothesis you need to use the obtain command. As a side remark, I suggest to use the induction method instead of the induct method so you can refer to the induction hypothesis as Suc.IH instead of Suc.hyps. Once you "grab" q and r from the induction hypothesis, you just need to prove that
lt r' n, and that
Suc m = add (mul q' n) r'
with q' and r' as defined for each of your two cases. Here is a (slightly incomplete) proof of your division theorem:
lemma division_theorem: "lt Zero n ⟹ ∃ q r. lt r n ∧ m = add (mul q n) r"
proof (induction m)
case Zero
then show ?case
by (metis add_zero_right mul.simps(1))
next
case (Suc m)
(* "Grab" q and r from IH *)
from ‹lt Zero n› and Suc.IH obtain q and r where "lt r n ∧ m = add (mul q n) r"
by blast
show ?case
proof (cases "Suc r = n")
case True
(* In this case, we use q' = Suc q and r' = Zero as witnesses *)
from ‹Suc r = n› and ‹lt r n ∧ m = add (mul q n) r› have "Suc m = add (mul (Suc q) n) Zero"
using add_comm by auto
with ‹lt Zero n› show ?thesis
by blast
next
case False
(* In this case, we use q' = q and r' = Suc r as witnesses *)
from ‹lt r n ∧ m = add (mul q n) r› have "Suc m = add (mul q n) (Suc r)"
by simp
moreover have "lt (Suc r) n"
sorry (* left as exercise :) *)
ultimately show ?thesis
by blast
qed
qed

Simplifying if-then-else in summations or products

While doing some basic algebra, I frequently arrive at a subgoal of the following type (sometimes with a finite sum, sometimes with a finite product).
lemma foo:
fixes N :: nat
fixes a :: "nat ⇒ nat"
shows "(a 0) = (∑x = 0..N. (if x = 0 then 1 else 0) * (a x))"
This seems pretty obvious to me, but neither auto nor auto cong: sum.cong split: if_splits can handle this. What's more, sledgehammer also surrenders when called on this lemma. How can one efficiently work with finite sums and products containing if-then-else in general, and how to approach this case in particular?
My favourite way to do these things (because it is very general) is to use the rules sum.mono_neutral_left and sum.mono_neutral_cong_left and the corresponding right versions (and analogously for products). The rule sum.mono_neutral_right lets you drop arbitrarily many summands if they are all zero:
finite T ⟹ S ⊆ T ⟹ ∀i∈T - S. g i = 0
⟹ sum g T = sum g S
The cong rule additionally allows you to modify the summation function on the now smaller set:
finite T ⟹ S ⊆ T ⟹ ∀i∈T - S. g i = 0 ⟹ (⋀x. x ∈ S ⟹ g x = h x)
⟹ sum g T = sum h S
With those, it looks like this:
lemma foo:
fixes N :: nat and a :: "nat ⇒ nat"
shows "a 0 = (∑x = 0..N. (if x = 0 then 1 else 0) * a x)"
proof -
have "(∑x = 0..N. (if x = 0 then 1 else 0) * a x) = (∑x ∈ {0}. a x)"
by (intro sum.mono_neutral_cong_right) auto
also have "… = a 0"
by simp
finally show ?thesis ..
qed
Assuming the left-hand side could use an arbitrary value between 0 and N, what about adding a more general lemma
lemma bar:
fixes N :: nat
fixes a :: "nat ⇒ nat"
assumes
"M ≤ N"
shows "a M = (∑x = 0..N. (if x = M then 1 else 0) * (a x))"
using assms by (induction N) force+
and solving the original one with using bar by blast?

Associativity proof on Nats vs. Lists

I am comparing the associativity proofs for Nats and Lists.
The proof on Lists goes by induction
lemma append_assoc [simp]: "(xs # ys) # zs = xs # (ys # zs)"
by (induct xs) auto
But, the proof on Nats is
lemma nat_add_assoc: "(m + n) + k = m + ((n + k)::nat)"
by (rule add_assoc)
Why do I not need induction on the nat_add_assoc proof? Is it because of some automation happening on natural numbers?
The associativity proof on nat is also done by induction.
In Nat.thy you can find
instantiation nat :: comm_monoid_diff
which is the Isabelle way of saying nat has type class comm_monoid_diff. The following definitions and lemmas then show that the natural numbers are a commutative monoid under addition and that there's also subtraction.
In this block you find the proof:
instance proof
fix n m q :: nat
show "(n + m) + q = n + (m + q)" by (induct n) simp_all
The instantiation then gives us the lemma add_assoc on nat.

How can I prove irreflexivity of an inductively defined relation in Isabelle?

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).

Free type variables in proof by induction

While trying to prove lemmas about functions in continuation-passing style by induction I have come across a problem with free type variables. In my induction hypothesis, the continuation is a schematic variable but its type involves a free type variable. As a result Isabelle is not able to unify the type variable with a concrete type when I try to apply the i.h. I have cooked up this minimal example:
fun add_k :: "nat ⇒ nat ⇒ (nat ⇒ 'a) ⇒ 'a" where
"add_k 0 m k = k m" |
"add_k (Suc n) m k = add_k n m (λn'. k (Suc n'))"
lemma add_k_cps: "∀k. add_k n m k = k (add_k n m id)"
proof(rule, induction n)
case 0 show ?case by simp
next
case (Suc n)
have "add_k (Suc n) m k = add_k n m (λn'. k (Suc n'))" by simp
also have "… = k (Suc (add_k n m id))"
using Suc[where k="(λn'. k (Suc n'))"] by metis
also have "… = k (add_k n m (λn'. Suc n'))"
using Suc[where k="(λn'. Suc n')"] sorry (* Type unification failed *)
also have "… = k (add_k (Suc n) m id)" by simp
finally show ?case .
qed
In the "sorry step", the explicit instantiation of the schematic variable ?k fails with
Type unification failed
Failed to meet type constraint:
Term: Suc :: nat ⇒ nat
Type: nat ⇒ 'a
since 'a is free and not schematic. Without the instantiation the simplifier fails anyway and I couldn't find other methods that would work.
Since I cannot quantify over types, I don't see any way how to make 'a schematic inside the proof. When a term variable becomes schematic locally inside a proof, why isn't this the case with variables in its type too? After the lemma has been proved, they become schematic at the theory level anyway. This seems quite limiting. Could an option to do this be implemented in the future or is there some inherent limitation? Alternatively, is there an approach to avoid this problem and still keeping the continuation schematically polymorphic in the proven lemma?
Free type variables become schematic in a theorem when the theorem is exported from the block in which the type variables have been fixed. In particular, you cannot quantify over type variables in a block and then instantiate the type variable within the block, as you are trying to do in your induction. Arbitrary quantification over types leads to inconsistencies in HOL, so there is little hope that this could be changed.
Fortunately, there is a way to prove your lemma in CPS style without type quantification. The problem is that your statement is not general enough, because it contains id. If you generalise it, then the proof works:
lemma add_k_cps: "add_k n m (k ∘ f) = k (add_k n m f)"
proof(induction n arbitrary: f)
case 0 show ?case by simp
next
case (Suc n)
have "add_k (Suc n) m (k ∘ f) = add_k n m (k ∘ (λn'. f (Suc n')))" by(simp add: o_def)
also have "… = k (add_k n m (λn'. f (Suc n')))"
using Suc.IH[where f="(λn'. f (Suc n'))"] by metis
also have "… = k (add_k (Suc n) m f)" by simp
finally show ?case .
qed
You get your original theorem back, if you choose f = id.
This is an inherent limitation how induction works in HOL. Induction is a rule in HOL, so it is not possible to generalize any types in the induction hypothesis.
A specialized solution for your problem is to first prove
lemma add_k_cps_nat: "add_k n m k = k (n + m)"
by (induction n arbitrary: m k) auto
and then prove add_k_cps.
A general approach is: prove instances for fixed types first, for which the induction works. In the example case is is an induction by nat. And then derive a proof generalized in the type itself.

Resources