I want to create a function F in Isabelle that is given a formula
formula = pr int | neg formula | imp formula formula
and yields True if the formula is tautological and False otherwise.
For example:
F( φ ⇒ φ ) = True
F( φ ⇒ (ψ ⇒ φ) ) = True
F( ψ ⇒ φ ) = False
Can anyone help me? I find it really difficult to understand Isabelle's documentation and I cannot find such function (which I think that it should already exist).
In any case, if you want to talk about tautology of formulae (or about any semantic property of formulae), you first need to define semantics for your formulae, i.e. a function eval :: formula ⇒ (int ⇒ bool) ⇒ bool (assuming that the pr constructor represents free variables) that takes a formula and a variable assignment and returns whether the formula holds for that assignment or not.
You can define such a function by recursion over the formula using the primrec or fun command. There are many examples for those i the ‘Programming and Proving‘ tutorial on the Isabelle website.
Related
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.
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.
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.
In Isabelle, I have defined a function f:'a -> nat where 'a is some algebraic structure that extends a monoid (i.e. a group, semiring, ring, integral domain, field, ...).
I would like to use the output of this function as "coefficients" for my type 'a in other constructs. That is, if x:'a and n:nat, I would like to be able to use some operation ·:'a -> nat -> 'a that allows me to tell Isabelle that n·x = x + x + ... + x.
By searching a bit, I found the "Power.thy" theory and, in a sense, it does what I want. However, it does it for the "multiplicative version" of my problem. This is an issue if I want to change 'a for e.g. the integers. Using it would mean that instead of computing n·x, Isabelle would do x^n. Is there an analogous version to "Power.thy" that does what I want? Or are there any other ways to circumvent this problem?
I do not know of any predefined constant that implements such an operation, but it can easily be implemented by iterating addition, e.g., using comppow on nat:
definition scale :: "nat => 'a => 'a" where
"scale a n = ((plus a) ^^ n) 0"
where plus refers to the addition operation of your structure and 0 is the neutral element. If you are using the arithmetic type classes from Isabelle/HOL, you should add the sort constraint 'a :: monoid to scale's type.
There is also a type class operation scaleR in Complex_Main that implements such a coefficient scaling operation, but it allows real numbers, not only nats, so your structure might not satisfy all the required axioms (type class real_vector).
A quite idiomatic way to express this is multiplication and »of_nat«:
context semiring_1
begin
definition scale :: "nat ⇒ 'a ⇒ 'a"
where "scale n = times (of_nat n)"
lemma [simp]:
"scale 0 a = 0"
"scale (Suc n) a = a + scale n a"
by (simp_all add: scale_def algebra_simps)
lemma
"((plus a) ^^ n) 0 = scale n a"
by (induct n) (simp_all)
end
How do i show in an example with numbers that addition is primitve recursive.
I understand why it is primitive recursive through the proof but I just can't imagine how it works primitive recursively with numbers.
To show that a function φ is primitive recursive, it suffices to provide a finite sequence of primitive recursive functions beginning with the constant, successor and projection functions and terminating with φ such that each function is constructed from prior functions by composition and primitive recursion. The primitive recursive addition function is defined
add(0,x) = φ(x)
add(n + 1,x) = ψ(n,x,add(n,x))
where φ = P[1/1]
ψ = S ∘ P[3/3]
where P[m/n] is the m-ary projection function returning its nth argument for n >= 1 and n <= m. To demonstrate that add is primitive recursive, we must construct φ and ψ from the basic functions:
1. P[1/1] [Axiom]
2. P[3/3] [Axiom]
3. S [Axiom]
4. S ∘ P[3/3] [1,3 Composition]
6. PR(P[1/1],S ∘ P[3/3]) [1,4 Primitive Recursion]
The function φ is provided by the axioms of primitive recursive functions. The function ψ is constructed by composition from the primitive recursive functions S and P[3/3] in step (4). Finally, the function add is constructed from φ and ψ in step (6) by primitive recursion. To see how a value is computed by a primitive recursive function such as add, it suffices to systematically substitute the right-hand sides of function definitions where appropriate, then simplify. I've collapsed substitution and simplification of composition in the following example:
add(2,3) = S(P[3/3](1,3,add(1,3))) [Def. ψ]
= S(P[3/3](1,3,S(P[3/3](0,3,add(0,3))))) [Def. ψ]
= S(P[3/3](1,3,S(P[3/3](0,3,P[1/1](3))))) [Def. φ]
= S(P[3/3](1,3,S(P[3/3](0,3,3)))) [Def. P[1/1]]
= S(P[3/3](1,3,S(3))) [Def. P[3/3]]
= S(P[3/3](1,3,4)) [Def. S]
= S(4) [Def. P[3/3]]
= 5 [Def. S]
It's unclear precisely what you're asking, so I gave a general overview of the primitive recursive definition of addition, the proof that addition is primitive recursive, and provided an example computation. If you're still unclear, it might be helpful to perform computations on small values of primitive recursive functions.