Why is this simple proof with real numbers not proven by auto (or even sledgehammer) - isabelle

What am I doing wrong/ forgetting that this can't be automatically proven by isabelle:
lemma "sqrt 5 = (1 + sqrt 5) / 2 - (1 - sqrt 5) / 2"
apply auto
sorry
This is what I actually want to prove:
fun fib :: "nat ⇒ nat" where
"fib (Suc 0) = Suc 0"
| "fib (Suc (Suc 0)) = Suc 0"
| "fib (Suc n) = (fib (n - 1)) + (fib (n - 2))"
lemma "(n::nat) > 0 ⟹ fib n = (1 / sqrt 5) * (
(((1 + sqrt 5) / 2)^n) -
(((1 - sqrt 5) / 2)^n)
)"
proof(induction n)
case 0
then show ?case
by auto
next
case (Suc n)
then show ?case
proof(induction n)
case 0
then show ?case
apply(auto)
sorry
next
case (Suc n)
then show ?case
apply auto
sorry
qed
qed

Sledgehammer usually isn't good at doing heavy arithmetic rewriting, which is required here. Proof methods that use the simplifier, like auto and simp, are good at this. But you have to give them the right rules.
There are a number of theorem collections for this:
algebra_simps, which normalises a term w.r.t. associativity, commutativity, and distributivity (essentially sorting everything in ascending order and multiplying out)
field_simps, which additionally cross-multiplies fractions. But caution: the denominator must be provably non-zero. If the simplifier cannot show that, it will not cross-multiply. If the denominator is a product of several factors, it may multiply out the product first and then the denominator is so ugly that it cannot show non-zeroness anymore.
divide_simps is like field_simps except that it does not use distributivity, and it always cross-multiplies by introducing a case distinction for the case that the denominator is zero. (In Isabelle/HOL, x / 0 = 0 holds by definition.)
Another rule you will need here is power2_eq_square to rewrite the square into a multiplication. Then you get:
lemma "sqrt 5 = (1 + sqrt 5) / 2 - ((1 - sqrt 5) / 2)^2"
apply (auto simp: field_simps power2_eq_square)
proof (prove)
goal (1 subgoal):
1. False
So something is not quite right here. As NieDzejkob pointed out in the comment, the left-hand side should be sqrt 5 - 1. With that, the proof goes through.
By the way, in case you're not aware, Fibonacci numbers are in the Isabelle/HOL standard library: https://isabelle.in.tum.de/library/HOL/HOL-Number_Theory/Fib.html

Related

Using `defines` with induction

Consider following lemma which should be easily provable:
lemma
fixes n m::nat
defines "m ≡ n - 1"
shows "m ≤ n"
proof(induction n)
case 0
then show ?case unfolding m_def
(* Why does «n» appear here? *)
next
case (Suc n)
then show ?case sorry
qed
However after unfolding m, the goal becomes n - 1 ≤ 0 instead of 0 - 1 ≤ 0 rendering the goal unprovable since n = 2 is a counterexample.
Is this a bug in Isabelle? How can I unfold the definition correctly?
I think a useful explanation could be the following: Recall the definition of nat.induct, namely
?P 0 ⟹ (⋀n. ?P n ⟹ ?P (Suc n)) ⟹ ?P ?n
and note that ?n means that n is implicitly universally quantified, that is, the previous definition is equivalent to
⋀n. ?P 0 ⟹ (⋀n. ?P n ⟹ ?P (Suc n)) ⟹ ?P n
Now, when applying nat.induct to your example, clearly the first subgoal to prove is ?P 0, i.e., m ≤ 0. However, in that context, n is still an arbitrary but fixed nat, in particular it does not hold that n = 0, and that is the reason why after unfolding the definition of m you get n - 1 ≤ 0 as the new subgoal. With respect to your specific question, the problem is that you cannot prove your result by induction on n (but you can easily prove it using unfolding m_def by simp).
As Javier pointed out, the n defined in the lemma head is different from the n created by induction. In other words, any facts from "outside" that reference n are not directly usable within the proof (induction n) environment.
However, Isabelle does offer a way to "inject" such facts, by piping them into induction:
lemma
fixes n m::nat
defines "m ≡ n - 1"
shows "m ≤ n"
using m_def (* this allows induction to use this fact *)
proof(induction n)
case 0
then show ?case by simp
next
case (Suc n)
then show ?case by simp
qed
using assms will work just as well in this case.
Note that direcly referring to m_def is no longer necessary, since a version of it is included for each case (in 0.hyps and Suc.hyps; use print_cases inside the proof for more information).

Why can't we do case analysis on inductively defined predicates *directly* when doing rule inversion?

There seems to be something about inductive predicates I don't understand since I keep getting issues with them. My most recent struggle is to understand case analysis with inductively defined predicates on ev from chapter 5 of the concrete semantics books.
Assume I am proving
lemma
shows "ev n ⟹ ev (n - 2)"
I've tried to start the proof immediately in Isabelle/HOL but it complains when I try or gives weird goals:
lemma
shows "ev n ⟹ ev (n - 2)"
proof (cases)
which shows:
proof (state)
goal (2 subgoals):
1. ⟦ev n; ?P⟧ ⟹ ev (n - 2)
2. ⟦ev n; ¬ ?P⟧ ⟹ ev (n - 2)
which is not what I expected.
When I pass n to cases we instead induct on the definition of natural numbers (note other times it does the induction on ev correctly, see later example):
lemma
shows "ev n ⟹ ev (n - 2)"
proof (cases n)
gives:
proof (state)
goal (2 subgoals):
1. ⟦ev n; n = 0⟧ ⟹ ev (n - 2)
2. ⋀nat. ⟦ev n; n = Suc nat⟧ ⟹ ev (n - 2)
which is not what I expected. Note however that the following DOES work (i.e. it inducts on ev not on natural numbers) even with n as a parameter:
lemma
shows "ev n ⟹ ev (n - 2)"
proof -
assume 0: "ev n"
from this show "ev (n - 2)"
proof (cases n)
I realize that there must be some magic about assuming ev n first and then stating to show ev (n-2) otherwise the error wouldn't occur.
I understand the idea of rule inversion (of arriving at a given fact to be proven in reverse, by analysis the cases that could have lead to it). For the "even" predicate the rule inversion is:
ev n ==> n = 0 ∨ (∃k. n = Suc (Suc k) ∧ ev k)
which makes sense based on the inductively defined predicate:
inductive ev :: "nat ⇒ bool" where
ev0: "ev 0" |
evSS: "ev n ⟹ ev (Suc (Suc n))"
but I don't understand. Why wouldn't directly doing the cases work? or why is this syntax invalid:
proof (cases ev)
or this:
proof (cases ev.case)
etc.
I think the crux is that at the heart I don't know when dealing with inductively define predicates if this induction is applied to the goal or the assumption, but from the writing on of the textbook:
The name rule inversion emphasizes that we are reasoning backwards: by which rules could some given fact have been proved?
I'd assume it's applying rule inversion to the goals since it says "by which rules could some given fact have been proved".
In addition, this example ev ==> ev (n-2) froom the book does not help because both the premise and conclusion have ev involved.
How is case analysis with rule inversion really working and why do we need to assume things first for Isabelle to give sensible goal for case analysis?
Not sure I understand the entire question but this:
lemma
shows "ev n ⟹ ev (n - 2)"
proof (cases)
Gives you:
proof (state)
goal (2 subgoals):
1. ⟦ev n; ?P⟧ ⟹ ev (n - 2)
2. ⟦ev n; ¬ ?P⟧ ⟹ ev (n - 2)
because Isabelle is splitting on the truth of ev n, i.e. either true or false. I believe the syntax you are looking for is:
proof (cases rule: ev.cases)
Which is how you tell Isabelle explicitly what rule it should use for a proof by cases.
The way to do it is as the answer Ben Sheffield said:
proof (cases rule: ev.cases)
I also noticed that:
apply (rule ev.cases)
works, but I think it would be helpful to go through a small example to see the cases explicitly outlined:
Consider:
lemma "ev n ⟹ ev (n - 2)"
first inspect it's cases theorem:
thm ev.cases
⟦ev ?a; ?a = 0 ⟹ ?P; ⋀n. ⟦?a = Suc (Suc n); ev n⟧ ⟹ ?P⟧ ⟹ ?P
then it unifies the goal and introduces the new goals with the original assumptions and all the assumption for the cases. That is why there is a ev n in all of them.
apply (rule ev.cases)
has goals:
proof (prove)
goal (3 subgoals):
1. ev n ⟹ ev ?a
2. ⟦ev n; ?a = 0⟧ ⟹ ev (n - 2)
3. ⋀na. ⟦ev n; ?a = Suc (Suc na); ev na⟧ ⟹ ev (n - 2)
and you can do simp and proceed the proof as normal.

Revisiting Gauss sum with Isabelle

I'm interested in a variation of the argument that stablishes:
"(∑ i=1..k . i) = k*(k+1) div 2"
We know this follows from a simple induction but the intution is a bit different. A way to see this formula is that if you sum the extremes of the sequence of numbers 1..k you get
1+k = 2 + (k-1) = ...
and then you just multiply the right number of times to get the complete sum.
I would like to reproduce this argument to show the following inequality:
"(∑n = 1..k - 1. cmod (f (int n))) ≤ 2 * (∑n ≤ k div 2. cmod (f (int n)))"
Here I know that cmod (f (int k - n)) = cmod (cnj (f n)) for every n.
Do you see an elegant way of proving this in Isabelle?
The trick to do this proof in an elegant way is to realise that ∑i=1..k. i is the same as ∑i=1..k. k + 1 - i and then adding that to the original sum so that the i cancels. This is a simple re-indexing argument:
lemma "(∑i=1..k. i :: nat) = k * (k + 1) div 2"
proof -
have "(∑i=1..k. i) = (∑i=1..k. k + 1 - i)"
by (rule sum.reindex_bij_witness[of _ "λi. k + 1 - i" "λi. k + 1 - i"]) auto
hence "2 * (∑i=1..k. i) = (∑i=1..k. i) + (∑i=1..k. k + 1 - i)"
by simp
also have "… = k * (k + 1)"
by (simp add: sum.distrib [symmetric])
finally show ?thesis by simp
qed
For the other thing you mentioned, I think that the best way to do this is to first split the sum into the elements less that k div 2 and the rest. Then you can reindex the second sum similarly to the one above. Then the inequality part comes in because you might have one extra leftover element ‘in the middle’ if k is odd and you have to throw that away.
Brief sketch of the important part of the proof:
lemma
assumes "⋀i. f i ≥ 0"
shows "(∑i=1..<k. f (i::nat) :: real) = T"
proof -
(* Separate summation domain into two disjoint parts *)
have "(∑i=1..<k. f i) = (∑i∈{1..k div 2}∪{k div 2<..<k}. f i)"
by (intro sum.cong) auto
(* Pull sum apart *)
also have "… = (∑i∈{1..k div 2}. f i) + (∑i∈{k div 2<..<k}. f i)"
by (subst sum.union_disjoint) auto
(* Reindex the second sum *)
also have "(∑i∈{k div 2<..<k}. f i) = (∑i∈{1..<k - k div 2}. f (k - i))"
by (rule sum.reindex_bij_witness[of _ "λi. k - i" "λi. k - i"]) auto
(* Throw away the element in the middle if k is odd *)
also have "… ≤ (∑i∈{1..k div 2}. f (k - i))"
using assms by (intro sum_mono2) auto
finally have "(∑i=1..<k. f i) ≤ (∑i=1..k div 2. f i + f (k - i))"
by (simp add: sum.distrib)
Figuring out how to do these sum manipulations idiomatically in Isabelle takes some experience. sum.reindex_bij_witness is a very useful rule (as you can see). Things like sum.mono_neutral_left/right can also help a lot.

In isabelle, why is this simplification lemma not being substituted?

I'm working through the Isabelle "Programming and Proving" tutorial, and am coming to Ex2.10, where you have to arrive at an equation discribing the number of nodes in an "exploded" tree.
The approach I've taken to this is to create separate expressions for the internal and leaf nodes in the tree, and am working on a proof for the number of internal nodes in the tree, as such:
lemma dddq: " a>0 ⟶ (nodes_noleaf (explode a b) = (ptser (a - 1) (2::nat)) + ((2 ^ a) * (nodes_noleaf b)))"
apply(induction a)
apply(simp)
apply(simp add:eeei eeed eeej eeek )
and this leaves the proof state as the following:
goal (1 subgoal):
1. ⋀a. 0 < a ⟶ nodes_noleaf (explode a b) = ptser (a - Suc 0) 2 + 2 ^ a * nodes_noleaf b ⟹
Suc (2 * nodes_noleaf (explode a b)) = ptser a 2 + 2 * 2 ^ a * nodes_noleaf b
Now, I also created (and successfully proved) a lemma that should replace the ptser a 2 + 2 * 2 ^ a * nodes_noleaf b with (Suc (2 * ((ptser (a - Suc 0) 2) + 2 ^ a * nodes_noleaf b)))), as such:
lemma eeek: "∀ a b . a>0 ⟶ (((ptser a 2) + 2 * 2 ^ a * nodes_noleaf b) = (Suc (2 * ((ptser (a - Suc 0) 2) + 2 ^ a * nodes_noleaf b))))"
apply(auto)
apply(simp add: ddddd)
done
However, adding this to the list of simplifications for the dddq does nothing, and I don't see the reason why.
Additional definitions..
fun nodes_noleaf:: "tree0 ⇒ nat" where
"nodes_noleaf Tip = 0"|
"nodes_noleaf (Node a b) = (add 1 (add (nodes_noleaf a) (nodes_noleaf b)))"
fun explode:: "nat ⇒ tree0 ⇒ tree0" where
"explode 0 t = t" |
"explode (Suc n) t = explode n (Node t t)"
fun ptser:: "nat ⇒ nat ⇒ nat" where
"ptser 0 b = b^0" |
"ptser a b = b^a + (ptser (a - 1) b)"
Your lemma eeek is a conditional rewrite rule, because it can only be applied when the simplifier can prove that a > 0 holds. In your goal state, however, you do not have the assumption a > 0. The 0 < a is a precondition to the induction hypothesis (--> binds stronger than ==>), which is why simp does not apply the induction hypothesis either.
Since the question does not contain all the definitions of your goal, it is hard to pinpoint the exact reason. Nevertheless, I suggest to drop the assumption a > 0 from dddq and prove a stronger statement.
A comment on style: Try to use the connectives !! and ==> of the natural deduction framework rather than explicit universal quantifiers and -->. The simplifier knows how to convert them back into !! and ==>, but other proof methods do not do this automatically. Thus, using !! and ==> will save you boilerplate proof steps later on.

Isabelle: Predecessor function

I am not sure, but I think sometimes my proofs would be easier if I had a predecessor function, e.g., in case a variable is known not to be zero.
I don't know a good example, but perhaps here: { fix n have "(n::nat) > 0 ⟹ (∑i<n. f i) = Predecessor n" sorry }
Possibly because it is not a good idea, there is no predecessor function in the library.
Is there a way to simulate a predecessor function or similar?
I have thought of this example:
theorem dummy:
shows "1=1" (* dummy *)
proof-
(* Predecessor function *)
def pred == "λnum::nat. (∑i∈{ i . Suc i = num}. i)"
{fix n :: nat
from pred_def have "n>0 ⟹ Suc (pred n) = n"
apply(induct n)
by simp_all
}
show ?thesis sorry
qed
Your definition is unnecessarily complicated. Why do you not just write
def pred ≡ "λn::nat. n - 1"
Then you can have
have [simp]: "⋀n. n > 0 ⟹ Suc (pred n) = n" by (simp add: pred_def)
In the case of 0, the pred function then simply returns 0 and Suc (pred 0) = 0 obviously doesn't hold. You could also define pred ≡ "λn. THE n'. Suc n' = n". That would return the unique natural number whose successor is n if such a number exists (i.e. if n > 0) and undefined (i.e. some natural number you know nothing about) otherwise. However, I would argue that in this case, it is much easier and sensible to just do pred ≡ λn::nat. n - 1.
I would suspect that in most cases, you can simply forgo the pred function and write n - 1; however, I do know that it is sometimes good to have the - 1 “protected” by a definition. In these cases, I usually def a variable n' as n - 1 and prove Suc n' = n – basically the same thing. In my opinion, seeing as proving this takes only one line, it does not really merit a definition of its own, such as this pred function, but one could make a reasonable case for it, I guess.
Another thing: I've noticed you use lemma "1 = 1" as some kind of dummy environment to do Isar proofs in. I would like to point out the existence of notepad, which exists precisely for that use case and that can be used as follows:
notepad
begin
have "some fact" by something
end

Resources