How to fix "Illegal schematic variable(s)" in mutually recursive rule induction? - recursion

In Isabelle, I'm trying to do rule induction on mutually recursive inductive definitions. Here's the simplest example I was able to create:
theory complex_exprs
imports Main
begin
datatype A = NumA int
| AB B
and B = NumB int
| BA A
inductive eval_a :: "A ⇒ int ⇒ bool" and eval_b :: "B ⇒ int ⇒ bool" where
eval_num_a: "eval_a (NumA i) i" |
eval_a_b: "eval_b b i ⟹ eval_a (AB b) i" |
eval_num_b: "eval_b (NumB i) i" |
eval_b_a: "eval_a a i ⟹ eval_b (BA a) i"
lemma foo:
assumes "eval_a a result"
shows "True"
using assms
proof (induction a)
case (NumA x)
show ?case by auto
case (AB x)
At this point, Isabelle stops with 'Illegal schematic variable(s) in case "AB"'. Indeed the current goal is ⋀x. ?P2.2 x ⟹ eval_a (AB x) result ⟹ True which contains the assumption ?P2.2 x. Is that the 'schematic variable' Isabelle is talking about? Where does it come from, and how can I get rid of it?
I get the same problem if I try to do the induction on the rules:
proof (induction)
case (eval_num_a i)
show ?case by auto
case (eval_a_b b i)
Again, the goal is ⋀b i. eval_b b i ⟹ ?P2.0 b i ⟹ True with the unknown ?P2.0 b i, and I can't continue.
As a related question: I tried to do the induction using
proof (induction rule: eval_a_eval_b.induct)
but Isabelle doesn't accept this, saying 'Failed to apply initial proof method'.
How do I make this induction go through? (In my actual application, I do actually need induction because the goal is more complex than True.)

Proofs about mutually recursive definitions, be they datatypes, functions or inductive predicates, must be mutually recursive themselves. However, in your lemma, you only state the inductive property for eval_a, but not for eval_b. In the case for AB, you obviously want to use the induction hypothesis for eval_b, but as the lemma does not state the inductive property for eval_b, Isabelle does not know what it is. So it leaves it as a schematic variable ?P2.0.
So, you have to state two goals, say
lemma
shows "eval_a a result ==> True"
and "eval_b b result ==> True"
Then, the method induction a b will figure out that the first statement corresponds to A and the second to B.
The induction rule for the inductive predicates fails because this rule eliminates the inductive predicate (induction over datatypes only "eliminates" the type information, but this is not a HOL formula) and it cannot find the assumption for the second inductive predicate.
More examples on induction over mutually recursive objects can be found in src/HOL/Induct/Common_Patterns.thy.

Related

Generalize a claim in a structural induction proof to be able to use the induction hypothesis

I want to prove the following
lemma
fixes pi :: "'a path" and T :: "'a ts"
shows "valid_path T pi s ⟹ ∀ op ∈ set pi. valid_operator T op"
by induction on pi where
fun valid_path :: "'a ts ⇒ 'a path ⇒ 'a state ⇒ bool" where
"valid_path T [] s = True" |
"valid_path T (op#ops) s = (valid_operator T op ∧ valid_path T ops (effect op s))
and path is just a type synonym for an operator list.
The other definitions should not play a role for the proof.
The base case works fine.
The problem is that, informally, for the inductive step where pi = (x # xs) I'm assuming that
if valid_path T xs s
then ∀ op ∈ set xs. valid_operator T op
and I must show that this implies
if valid_path T (x#xs) s
then ∀ op ∈ set (x#xs). valid_operator T op
I can use the definition of valid_path here, so this last expression is equivalent to
if valid_path T (xs) (effect x s)
then ∀ op ∈ set (x#xs). valid_operator T op
If I could be able to use the induction hypothesis on valid_path T (xs) (effect x s) I would be done.
I can't since the hypothesis only holds for valid_path T (xs) s instead of valid_path T xs (effect x s).
But this does not really matter since the predicate of that if statement does not depend on s at all!
But Isabelle does not know that so it complains.
How can I make it such that I can apply the inductive hypothesis on valid_path T (xs) (effect x s)?
I have a feeling that I have to make the claim more general, so that I can use the hypothesis on the proof, but I don't know how.
It is very common that you have to generalize some terms in an induction. Use the keyword arbitrary in the induct method.
proof (induct pi arbitrary: s)
This is explained in Chapter 2.4 of Programming and Proving in Isabelle/HOL.

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 fix "partially applied constant on left hand side of code equation"?

I'm trying to define the code equation:
datatype t = A | B | C
inductive less_t :: "t ⇒ t ⇒ bool" where
"less_t A B"
| "less_t B C"
code_pred [show_modes] less_t .
fun less_t_fun :: "t ⇒ t ⇒ bool" where
"less_t_fun A A = False"
| "less_t_fun A B = True"
| "less_t_fun A C = True"
| "less_t_fun B C = True"
| "less_t_fun B _ = False"
| "less_t_fun C _ = False"
lemma tancl_less_t_code [code]:
"less_t⇧+⇧+ x y ⟷ less_t_fun x y"
apply (rule iffI)
apply (erule tranclp_trans_induct)
apply (erule less_t.cases; simp)
apply (metis less_t_fun.elims(2) less_t_fun.simps(3) t.simps(4))
apply (induct rule: less_t_fun.induct; simp)
using less_t.intros apply auto
done
value "less_t A B"
value "less_t_fun A C"
value "less_t⇧+⇧+ A C"
And get the following warning:
Partially applied constant "less_t" on left hand side of equation, in theorem:
less_t⇧+⇧+ ?x ?y ≡ less_t_fun ?x ?y
This question is unrelated to transitive closures. I already received such a warning for different theorems:
Partially applied constant on left hand side of code equation
How to use different code lemmas for different modes of inductive predicate?
I just need to understand the meaning of this warning and how to fix it. Maybe I should define a different lemma?
The problem is that the structure of your lemma tancl_less_t_code is indeed not suitable as code-equations. Note that the outermost constant in the left-hand side of the equations is the transitive closure predicate tranclp. So, this tells the code-generator to use the lemma in order to implement tranclp. However, using your lemma one only knows how to implement tranclp for one specific predicate, namely less_t. Therefore, you get the complaint from Isabelle that your implementation is too specific.
There are at least two workarounds.
First, instead of the declaration [code], you can use [code unfold]. Then
every occurrence of tranclp less_t x y will be replaced by less_t_fun during the code generation. To make this rule even more applicable, I would then reformulate the lemma to tranclp less = less_t_fun, so that even if
tranclp less_t is not fully applied, the unfolding can happen.
Second, you can take the symmetric version of your lemma and declare it as
[simp]. Then in your implementation you just invoke less_t_fun instead of
tranclp less_t and in the proofs the simplifier will switch to the latter one.
For more information on [code] and [code_unfold] have a look into the
documentation of the code generator.

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