membership proof - isabelle

I need to prove the following:
lemma "m = min_list(x#xs) ⟹ m ∈ set (x#xs)"
In plain English, I need to prove that the return value from "min_list (x#xs)" is always a member of (x#xs)
I tried:
apply(induct xs)
apply(auto)
I also tried to reuse existing lemmas for the min_list by using:
find_theorems min_list
The sub-goal at this point is so long that I do not know how to proceed.
I am not looking for a full answer just hints on how to approach this lemma. Moreover, is this proof an easy one or significantly difficult one for someone just learning Isabelle?

Spoiler: it is possible to use the standard list induction and auto to prove the theorem, i.e. something similar to by (induct xs ...) (auto simp: ...). I deliberately left out sections in the proof for you to fill in on your own. You will need to think about if any variables (i.e. m or x) need to be specified as arbitrary and also understand what information the simplifier may need (look for clues in the specification of min_list in the theory List).
With regard to your question about the difficulty of the problem, I believe, that difficulty is a function of experience. Most certainly, when I started learning Isabelle, I was finding it difficult to formalise proofs similar to the one in your question. After a certain time spent coding in Isabelle (by the time of answering this question, I must have accrued an equivalent of 4-5 months of full-time coding in Isabelle), such problems no longer seem to present a significant challenge for me. Of course, there are other factors that need to be taken into account, e.g. previous training in mathematics or logic and previous coding experience.
General advice from someone who is learning Isabelle on his own (the advice may not be consistent with the approach that is normally recommended by professional instructors)
I believe, when proving similar results, it is important to understand that Isabelle is, primarily, a tool for formalisation of 'pen-and-paper' proofs. Therefore, it is important to have the 'pen-and-paper' proof at hand before trying to formalise it. I would suggest the following general approach when attacking similar problems:
Write the proof on paper.
Formalise the proof using Isar, providing as many details as possible and not caring too much about the length of the proof. Also, try not to rely on the tools for automated reasoning (i.e. auto, blast, meson, metis, fastforce) and use direct methods like rule and intro as much as you can.
Once your Isar proof is complete, apply tools for automated reasoning (e.g. auto, blast) to your Isar proof to simplify your proof as much as possible.
Of course, eventually, it will become increasingly easy to omit 1 and 2 as you make progress in learning Isabelle.
I can provide further details, e.g. the complete short proof and the long Isar version of the proof.
UPDATE
As per your request in the comments, I provide an informal proof.
Lemma. m = min_list (x # xs) ⟹ m ∈ set (x # xs).
Remarks. For completeness, I also provide the definition of min_list and some comments about the const set. The definition of min_list can be found in the theory List:
fun min_list :: "'a::ord list ⇒ 'a" where
"min_list (x # xs) = (case xs of [] ⇒ x | _ ⇒ min x (min_list xs))"
The const set is defined implicitly and constitutes a part of the datatype infrastructure for list (see the document "Defining (Co)datatypes and Primitively (Co)recursive Functions in Isabelle/HOL" in the standard documentation if Isabelle). In particular, it is called the 'set function' of the datatype. Many basic properties of the const set can be found by inspection/search, e.g. find_theorems list.set. I believe that the theorem thm list.set is representative of the main properties of the const set (I took the liberty to rename the schematic variables in the theorem):
set [] = {}
set (?x # ?xs) = insert ?x (set ?xs)
Proof. The proof is by structural induction on the list xs. The induction principle is stated as an unnamed lemma at the beginning of the theory List. For completeness, I restate the induction principle below:
"P [] ⟹ (⋀a list. P list ⟹ P (a # list)) ⟹ P list"
Base case: assume xs = [], show m = min_list (x # xs) ⟹ m ∈ set (x # xs) for all x. From the definition of min_list, it is trivial to see that min_list (x # []) = x. Similarly, set (x # []) = {x} can be shown directly from the properties of the const set. Substituting into the predicate above, it remains to show that m = x ⟹ m ∈ {x} for all x. This follows from basic set theory.
Inductive step: assume ⋀x. m = min_list (x # xs) ⟹ m ∈ set (x # xs), show m = min_list (a # x # xs) ⟹ m ∈ set (a # x # xs) for all a, x and xs. Fix a, x and xs. Assume m = min_list (a # x # xs). Then it remains to show that m ∈ set (a # x # xs). Given m = min_list (a # x # xs), from the definition of min_list, it is easy to infer that either m = a or m = min_list (x # xs). Consider these cases explicitly:
Case I: m = a. a ∈ set (a # x # xs) follows from the definitions. Then, m ∈ set (a # x # xs) by substitution.
Case II: m = min_list (x # xs). Then, from the assumption ⋀x. m = min_list (x # xs) ⟹ m ∈ set (x # xs) it follows that m ∈ set (x # xs). Thus, m ∈ set (a # x # xs) follows from the properties of set.
In all possible cases m ∈ set (a # x # xs), which is what was required to prove.
Thus, the proof is concluded.
Concluding thoughts. Try converting this informal proof to an Isar proof. Also, please note that the proof may not be ideal - I might make edits to the proof later.

Related

How to prove that a recursive function has some value

Here is a trivial function and a lemma:
fun count_from where
"count_from y 0 = []"
| "count_from y (Suc x) = y # count_from (Suc y) x"
lemma "count_from 3 5 = [3,4,5,6,7]"
It's just an example. The real function is more complicated.
Could you please suggest how to prove such a lemmas?
I redefined the function using tail-recursion and proved the lemma as follows:
fun count_from2 where
"count_from2 y 0 ys = ys"
| "count_from2 y (Suc x) ys = count_from2 (Suc y) x (ys # [y])"
lemma "count_from2 3 5 [] = xs ⟹ xs = [3,4,5,6,7]"
apply (erule count_from2.elims)
apply simp
apply (drule_tac s="xs" in sym)
apply (erule count_from2.elims)
apply simp
apply (drule_tac s="xs" in sym)
apply (erule count_from2.elims)
apply simp
apply (drule_tac s="xs" in sym)
apply (erule count_from2.elims)
by auto
For sure it's not an adequate solution.
I have a several questions:
Is it preferred to define functions using tail-recursion? Does it usually simplifies theorem proving?
Why function simplification rules (count_from.simps or count_from2.simps) can't be applied?
Should I define an introduction rules to prove the first lemma?
Is it possible to apply a function induction rule to prove such a lemmas?
Your question might be better phrased as ‘How do I evaluate a recursively-defined function and get that evaluation as a theorem?’
The answer is that usually the simplifier should do a decent job at evaluating it. The problem here is that numerals like 1, 2, 3 use a binary representation of the natural numbers, whereas the function is defined by pattern matching on 0 and Suc. This is the reason why your simps cannot be applied: they only match on terms of the form count_from ?y 0 or count_from ?y (Suc ?x) and count_from 3 5 is neither.
What you can do to move things along is to use the theorem collection eval_nat_numeral, which simply rewrites numerals like 1, 2, 3 into successor notation:
lemma "count_from 3 5 = [3,4,5,6,7]"
by (simp add: eval_nat_numeral)
Another possibility are the code_simp and eval proof methods which try to prove a statement that is ‘executable’ in some sense by evaluating it and checking that you get True. They both work fine here:
lemma "count_from 3 5 = [3,4,5,6,7]"
by code_simp
The difference between the two is that code_simp uses the simplifier, which gives you a ‘proper’ proof that goes through the Isabelle kernel (but this can be very slow for bigger examples), whereas eval uses the code generator as a trusted oracle and is thus potentially less trustworthy (although I have never seen a problem in practice) but much faster.
As for your other questions: No, I don't see how induction applies here. I don't know what you mean by defining introduction rules (what would they look like?). And tail-recursion does not really have any advantages for proving things – in fact, if you ‘artificially’ make function definitions tail-recursive as you have done for count_from2 you actually make things slightly more difficult, since any properties you want to prove then require additional generalisation before you can prove them by induction. A classic example is normal vs tail-recursive list reversal (I think you can find that in the ‘Programming and Proving’ tutorial).
Also note that a function very much like your count_from already exists in the library: It is called upt :: nat ⇒ nat ⇒ nat list and has custom syntax of the form [a..<b]. It gives you the list of natural numbers between a and b-1 in ascending order. It is a good idea to look at the ‘What's in Main’ document to get an idea for what.

Introducing fixed representation for a quotient type in Isabelle

This question is better explained with an example. Suppose I want to prove the following lemma:
lemma int_inv: "(n::int) - (n::int) = (0::int)"
How I'd informally prove this is something along these lines:
Lemma: n - n = 0, for any integer n and 0 = abs_int(0,0).
Proof:
Let abs_int(a,b) = n for some fixed natural numbers a and b.
--- some complex and mind blowing argument here ---
That means it suffices to prove that a+b+0 = a+b+0, which is true by reflexivity.
QED.
However, I'm having trouble with the first step "Let abs_int(a,b) = n". The let statement doesn't seem to be made for this, as it only allows one term on the left side, so I'm lost at how I could introduce the variables a and b in an arbitrary representation for n.
How may I introduce a fixed reprensentation for a quotient type so I may use the variables in it?
Note: I know the statement above can be proved by auto, and the problem may be sidestepped by rewriting the lemma as "lemma int_inv: "Abs_integ(a,b) - Abs_integ(a,b) = (0::int)". However, I'm looking specifically for a way to prove by introducing an arbitrary representation in the proof.
You can introduce a concrete representation with the theorem int.abs_induct. However, you almost never want to do that manually.
The general method of proving statements about quotients is to first state an equivalent theorem about the underlying relation, and then use the transfer tool. It would've helped if your example wasn't automatically discharged by automation... in fact, let's create our own little int type so that it isn't:
theory Scratch
imports Main
begin
quotient_type int = "nat × nat" / "intrel"
morphisms Rep_Integ Abs_Integ
proof (rule equivpI)
show "reflp intrel" by (auto simp: reflp_def)
show "symp intrel" by (auto simp: symp_def)
show "transp intrel" by (auto simp: transp_def)
qed
lift_definition sub :: "int ⇒ int ⇒ int"
is "λ(x, y) (u, v). (x + v, y + u)"
by auto
lift_definition zero :: "int" is "(0, 0)".
Now, we have
lemma int_inv: "sub n n = zero"
apply transfer
proof (prove)
goal (1 subgoal):
1. ⋀n. intrel ((case n of (x, y) ⇒ λ(u, v). (x + v, y + u)) n) (0, 0)
So, the version we want to prove is
lemma int_inv': "intrel ((case n of (x, y) ⇒ λ(u, v). (x + v, y + u)) n) (0, 0)"
by (induct n) simp
Now we can transfer it with
lemma int_inv: "sub n n = zero"
by transfer (fact int_inv')
Note that the transfer proof method is backtracking — this means that it will try many possible transfers until one of them succeeds. Note however, that this backtracking doesn't apply across separate apply commands. Thus you will always want to write a transfer proof as by transfer something_simple, instead of, say proof transfer.
You can see the many possible versions with
apply transfer
back back back back back
Note also, that if your theorem mentions constants about int which weren't defined with lift_definition, you will need to prove a transfer rule for them separately. There are some examples of that here.
In general, after defining a quotient you will want to "forget" about its underlying construction as soon as possible, proving enough properties by transfer so that the rest can be proven without peeking into your type's construction.

Reindexing sums in Isabelle

I'm trying to translate the argument I gave in this answer into Isabelle and I managed to prove it almost completely. However, I still need to prove:
"(∑k | k ∈ {1..n} ∧ d dvd k. f (k/n)) =
(∑q | q ∈ {1..n/d}. f (q/(n/d)))" for d :: nat
My idea was to use this theorem:
sum.reindex_bij_witness
however, I cannot instantiate the transformations i,j that relate the sets S,T of the theorem. In principle, the setting should be:
S = {k. k ∈ {1..n} ∧ d dvd k}
T = {q. q ∈ {1..n/d}}
i k = k/d
j q = q d
I believe there is a typing error. Perhaps I should be using div?
First of all, note that instead of gcd a b = 1, you should write coprime a b. That is equivalent (at least for all types that have a GCD), but it is more convenient to use.
Second, I would not write assumptions like ⋀n. F n = …. It makes more sense to write that as a defines, i.e.
lemma
fixes F :: "nat ⇒ complex"
defines "F ≡ (λn. …)"
Third, {q. q ∈ {1..n/d}} is exactly the same as {1..n/d}, so I suggest you write it that way.
To answer your actual question: If what you have written in your question is how you wrote it in Isabelle and n and d are of type nat, you should be aware that {q. q ∈ {1..n/d}} actually means {1..real n / real d}. If n / d > 1, this is actually an infinite set of real numbers and probably not what you want.
What you actually want is probably the set {1..n div d} where div denotes division on natural numbers. This is then a finite set of natural numbers.
Then you can prove the following fairly easily:
lemma
fixes f :: "real ⇒ complex" and n d :: nat
assumes "d > 0" "d dvd n"
shows "(∑k | k ∈ {1..n} ∧ d dvd k. f (k/n)) =
(∑q∈{1..n div d}. f (q/(n/d)))"
by (rule sum.reindex_bij_witness[of _ "λk. k * d" "λk. k div d"])
(use assms in ‹force simp: div_le_mono›)+
A note on div
div and / denote the same function, namely Rings.divide.divide. However, / for historic reasons (and perhaps in fond memory of Pascal), / additionally imposes the type class restriction inverse, i.e. it only works on types that have an inverse function.
In most practical cases, this means that div is a general kind of division operation on rings, whereas / only works in fields (or division rings, or things that are ‘almost’ fields like formal power series).
If you write a / b for natural numbers a and b, this is therefore a type error. The coercion system of Isabelle then infers that you probably meant to write real a / real b and that's what you get.
It's a good idea to look at the output in such cases to ensure that the inferred coercions match what you intended.
Debugging non-matching rules
If you apply some rule (e.g. with apply (rule …)) and it fails and you don't understand why, there is a little trick to find out. If you add a using [[unify_trace_failure]] before the apply, you get an error message that indicates where exactly the unification failed. In this case, the message is
The following types do not unify:
(nat ⇒ complex) ⇒ nat set ⇒ complex
(real ⇒ complex) ⇒ real set ⇒ complex
This indicates that there is a summation over a set of reals somewhere that should be a summation over a set of naturals.

How to prove basic facts about datatypes and codatatypes?

I would like to prove some basic facts about a datatype_new and a codatatype: the first does not have an infinite element, and that the latter does have one.
theory Co
imports BNF
begin
datatype_new natural = Zero | Successor natural
lemma "¬ (∃ x. x = Successor x)"
oops
codatatype conat = CoZero | CoSucc conat
lemma "∃ x. x = CoSucc x"
oops
The problem was that I could not come up with a pen-and-paper proof, let alone a proof script.
An idea for the first was to use the size function, which has a theorem
size (Successor ?natural) = size ?natural + Suc 0
and somehow using that size is a function, applying it to the two sides of the original equation one cannot have a natural number equal to its successor. But I do not see how I could formalise this.
For the latter I did not even have an idea how to derive this theorem from the facts that the codatatype package proves.
How can I prove these?
Personally, I don't know the first thing about codatatypes. But let me try to help you nevertheless.
The first lemma you posted can be proven automatically by sledgehammer. It finds a proof using the size function, effectively reducing the problem on natural to the same problem on nat:
by (metis Scratch.natural.size(2) n_not_Suc_n nat.size(4) size_nat)
If you want a very basic, step-by-step version of this proof, you could write it like this:
lemma "¬(∃x. x = Successor x)"
proof clarify
fix x assume "x = Successor x"
hence "size x = size (Successor x)" by (rule subst) (rule refl)
also have "... = size x + Suc 0" by (rule natural.size)
finally have "0 = Suc 0" by (subst (asm) add_0_iff) (rule sym)
moreover have "0 ≠ Suc 0" by (rule nat.distinct(1))
ultimately show False by contradiction
qed
If you want a more “elementary” proof, without the use of HOL natural numbers, you can do a proof by contradiction using induction on your natural:
lemma "¬(∃x. x = Successor x)"
proof clarify
fix x assume "x = Successor x"
thus False by (induction x) simp_all
qed
You basically get the two cases in the induction:
Zero = Successor Zero ⟹ False
⋀x. (x = Successor x ⟹ False) ⟹
Successor x = Successor (Successor x) ⟹ False
The first subgoal is a direct consequence of natural.distinct(1), the second one can be reduced to the induction hypothesis using natural.inject. Since these rules are in the simpset, simp_all can solve it automatically.
As for the second lemma, the only solution I can think of is to explicitly construct the infinite element using primcorec:
primcorec infinity :: conat where
"infinity = CoSucc infinity"
Then you can prove your second lemma simply by unfolding the definition:
lemma "∃x. x = CoSucc x"
proof
show "infinity = CoSucc infinity" by (rule infinity.ctr)
qed
Caveat: these proofs work, but I am not sure whether they are the easiest and/or most elegant solution to this problem. I have virtually no knowledge of codatatypes or the new datatype package.

How to extract the instantiated variable in Isabelle?

I am trying to prove the following in Isabelle:
theorem map_fold: "∃h b. (map f xs) = foldr h xs b"
apply (induction xs)
apply auto
done
How can I get the instantiated value of h and b?
An approach that sometimes works for this purpose is to state a schematic lemma:
schematic_lemma "map f xs = foldr ?h xs ?b"
apply (induct xs)
apply simp
...
Methods like simp or rule can instantiate schematic variables during the proof (a result of unification). If you are able to complete the proof, then you can just look at the resulting lemma to see what the final instantiations were.
Beware that schematic variables can be a bit tricky: sometimes simp will instantiate a schematic variable in a way that makes the current goal trivially provable, but simultaneously makes other subgoals unsolvable.
In this specific case, Isabelle is able to instantiate ?b with no problem, but it can't determine ?h by unification. In general, schematic variables with function types are much trickier to handle.
In the end, I did something like what Manuel suggested: First, state a lemma with ordinary variables (lemma "map f xs = foldr h xs b"). Then see where the proof by induction gets stuck, and incrementally refine the statement until it is provable.
One way is to use SOME:
h := SOME h. ∃b. map f xs = foldr h xs b
b := SOME b. map f xs = foldr h xs b
Using your map_fold theorem and some fiddling around with someI_ex, you could prove that with these definitions, map f xs = foldr h xs b does indeed hold.
However, while this logically gives you values of h and b, I expect you will not be very satisfied with them, because you don't actualls see what h and b are; and there is no way (logically) to do that either.
In some cases, you can also formulate a theorem stating “There are f, xs such that no h, b exist with map f xs = foldr h xs b” and get nitpick to find a counterexample for that statement, but this case is too complicated for nitpick, as it would have to find a function on an infinite domain that depends on another function on an infinite domain.
I do not think there is a way for you to actually get the existential witnesses h and b out of the theorem you proved as concrete values. You will just have to find them yourself by inspection of the induction cases and find that they are h = λx xs. f x # xs and b = [].
This is by far the easiest solution.
Update: Proof extraction
Upon re-reading this thread today, I actually remembered that proof extraction does exist in Isabelle. It requires explicit proof terms to be computed for all theorems, so you need to start Isabelle with isabelle jedit -l HOL-Proofs. Then you can do this:
theorem map_fold: "∃h b. (map f xs) = foldr h xs b"
by (induction xs) auto
extract map_fold
This defines you a constant map_fold of type ('a ⇒ 'b) ⇒ 'a list ⇒ ('a ⇒ 'b list ⇒ 'b list) × 'b list, i.e. given a mapping function and a list, it gives you the function and the initial state you have to put into the foldr in order to get the same result. You can look at the definition using thm map_fold_def. Simplifying it a bit, it looks like this:
map_fold f xs =
rec_list (λx xa. default, []) (λx xa H. (λa b. f a # map f xa, default)) xs
This is a bit difficult to read, but you can see the [] and the f a # map f xa.
Unfortunately, proof terms get pretty big, so I doubt this will be of much use for anything more than toy examples.

Resources