How can I efficiently prove existential propositions with multiple variables in Isabelle/Isar? - isabelle

Say I want to prove the lemma ∃ n m k . [n, m, k] = [2, 3, 5] in Isabelle/Isar. If I go ahead as suggested in the Isabelle/HOL tutorial on page 45, my proof looks as follows:
lemma "∃ n m k . [n, m, k] = [2, 3, 5]"
proof
show "∃ m k . [2, m, k] = [2, 3, 5]"
proof
show "∃ k . [2, 3, k] = [2, 3, 5]"
proof
show "[2, 3, 5] = [2, 3, 5]" by simp
qed
qed
qed
Of course, this is way too verbose. How can I prove propositions like the above one such that the proofs are concise and readable?

Multiple existential quantifiers can be introduced in a single step by applying the single-quantifier introduction rule several times. For example, the proof method (rule exI)+ introduces all outermost existential quantifiers.
lemma "∃n m k. [n, m, k] = [2, 3, 5]"
proof(rule exI)+
show "[2, 3, 5] = [2, 3, 5]" by simp
qed
Alternatively, you can first state the instantiated property and then use an automatic proof method to do the instantiations. Usually blast works well here because it does not invoke the simplfier. In your example, you will have to add type annotations because numbers are overloaded.
lemma "∃n m k :: nat. [n, m, k] = [2, 3, 5]"
proof -
have "[2, 3, 5 :: nat] = [2, 3, 5]" by simp
then show ?thesis by blast
qed

Related

Einops rearrange function basic functionallity

I'm trying to grok the einops syntax for tensor reordering, but am somehow missing the point
If I have the following matrix:
mat = torch.randint(1, 10, (8,4))
I understand what the following command does:
rearrange(mat, '(h n) w -> (n h) w', n = 2)
But can't really wrap my head around the following ones:
rearrange(mat, '(n h) w -> (h n) w', n = 2)
rearrange(mat, '(n h) w -> (h n) w', n = 4)
Any help would be appreciated
rearrange(mat, '(h n) w -> (n h) w', n = 2)
and
rearrange(mat, '(n h) w -> (h n) w', n = 2)
are inversions of each other. If you can imagine what one does, second makes reverse transform
As for the latter, mat is 8x4
rearrange(mat, '(n h) w -> (h n) w', n = 4)
So you first split first dimension in 4x2 (below I ignore w dimension, because nothing special happens with it)
[0, 1, 2, 3, 4, 5, 6, 7]
to
[0, 1,
2, 3,
4, 5,
6, 7]
then you change order of axes to 2x4 (transpose)
[0, 2, 4, 6,
1, 3, 5, 7]
then merge two dimensions into one
[0, 2, 4, 5, 1, 3, 5, 7]
If you still don't feel how that works, take simpler examples like
rearrange(np.arange(50), '(h n) -> h n', h=5)
rearrange(np.arange(50), '(h n) -> h n', h=10)
rearrange(np.arange(50), '(h n) -> n h', h=10)
etc. So that you could track movement of each element in the matrix

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?

How to simplify an inductive predicate by evaluation?

I defined a very simple object-oriented model. The model defines a set of classes and a set of associations.
nonterminal fmaplets and fmaplet
syntax
"_fmaplet" :: "['a, 'a] ⇒ fmaplet" ("_ /↦⇩f/ _")
"_fmaplets" :: "['a, 'a] ⇒ fmaplet" ("_ /[↦⇩f]/ _")
"" :: "fmaplet ⇒ fmaplets" ("_")
"_FMaplets" :: "[fmaplet, fmaplets] ⇒ fmaplets" ("_,/ _")
"_FMapUpd" :: "['a ⇀ 'b, fmaplets] ⇒ 'a ⇀ 'b" ("_/'(_')" [900, 0] 900)
"_FMap" :: "fmaplets ⇒ 'a ⇀ 'b" ("(1[_])")
syntax (ASCII)
"_fmaplet" :: "['a, 'a] ⇒ fmaplet" ("_ /|->f/ _")
"_fmaplets" :: "['a, 'a] ⇒ fmaplet" ("_ /[|->f]/ _")
translations
"_FMapUpd m (_FMaplets xy ms)" ⇌ "_FMapUpd (_FMapUpd m xy) ms"
"_FMapUpd m (_fmaplet x y)" ⇌ "CONST fmupd x y m"
"_FMap ms" ⇌ "_FMapUpd (CONST fmempty) ms"
"_FMap (_FMaplets ms1 ms2)" ↽ "_FMapUpd (_FMap ms1) ms2"
"_FMaplets ms1 (_FMaplets ms2 ms3)" ↽ "_FMaplets (_FMaplets ms1 ms2) ms3"
datatype classes1 =
Object | Person | Employee | Customer | Project | Task | Sprint
abbreviation "associations ≡ [
STR ''ProjectManager'' ↦⇩f [
STR ''projects'' ↦⇩f (Project, 0::nat, 100::nat),
STR ''manager'' ↦⇩f (Employee, 1, 1)],
STR ''ProjectMember'' ↦⇩f [
STR ''member_of'' ↦⇩f (Project, 0, 100),
STR ''members'' ↦⇩f (Employee, 1, 20)],
STR ''ManagerEmployee'' ↦⇩f [
STR ''line_manager'' ↦⇩f (Employee, 0, 1),
STR ''project_manager'' ↦⇩f (Employee, 0, 100),
STR ''employees'' ↦⇩f (Employee, 3, 7)],
STR ''ProjectCustomer'' ↦⇩f [
STR ''projects'' ↦⇩f (Project, 0, 100),
STR ''customer'' ↦⇩f (Customer, 1, 1)],
STR ''ProjectTask'' ↦⇩f [
STR ''project'' ↦⇩f (Project, 1, 1),
STR ''tasks'' ↦⇩f (Task, 0, 100)],
STR ''SprintTaskAssignee'' ↦⇩f [
STR ''sprint'' ↦⇩f (Sprint, 0, 10),
STR ''tasks'' ↦⇩f (Task, 0, 5),
STR ''assignee'' ↦⇩f (Employee, 0, 1)]]"
I defined also a class_roles predicate which relates a class to a set of association ends navigable from this class:
lemma fmember_code_predI [code_pred_intro]:
"x |∈| xs" if "Predicate_Compile.contains (fset xs) x"
using that by (simp add: Predicate_Compile.contains_def fmember.rep_eq)
code_pred fmember
by (simp add: Predicate_Compile.contains_def fmember.rep_eq)
definition "assoc_end_class ≡ fst"
inductive assoc_refer_class where
"role |∈| fmdom ends ⟹
fmlookup ends role = Some end ⟹
assoc_end_class end = 𝒞 ⟹
assoc_refer_class ends 𝒞 role"
code_pred [show_modes] assoc_refer_class .
inductive class_roles where
"assoc |∈| fmdom assocs ⟹
fmlookup assocs assoc = Some ends ⟹
assoc_refer_class ends 𝒞 from ⟹
role |∈| fmdom ends ⟹
fmlookup ends role = Some end ⟹
role ≠ from ⟹
class_roles assocs 𝒞 assoc from role"
code_pred [show_modes] class_roles .
values "{(x, y, z, a). class_roles associations x y z a}"
This predicate can be evaluated very fast (please see the last line above).
I need to prove that all association ends are unique for each class. For simplicity I'm trying to prove it for the Employee class:
lemma fmupd_to_rhs:
"fmupd k x xm = y ⟷ y = fmupd k x xm"
by auto
lemma class_roles_unique:
"class_roles associations Employee assoc1 from role ⟹
class_roles associations Employee assoc2 from role ⟹ assoc1 = assoc2"
apply (erule class_roles.cases; erule class_roles.cases;
erule assoc_refer_class.cases; erule assoc_refer_class.cases)
unfolding fmupd_to_rhs
apply (simp)
apply (elim disjE)
apply auto[1]
apply auto[1]
apply auto[1]
(* And so on... Proving of each case is very slow *)
The problem is that it's very slow. Is it possible to simplify a class_roles predicate using lemmas generated by code_pred? Or could you suggest a better approach to prove this lemma?
The code_pred command generates equations for class_roles, one for each inferred mode, and values uses them. The theorem class_roles.equation shows them all. If you want to use them to prove your lemma, you must first transform goal or the lemma statement such that one of the generated class_role_... constants appears. Doing this manually is pretty cumbersome.
You get much better automation if you let the predicate compiler do this transformation for you. Since the lemma contains universally quantified variables (assoc1, assoc2, from, and role), I recommend that you define the negation of the lemma statement as an inductive predicate, as the negation turns the universal quantifier into an existential, which is modelled by a free variable in the assumptions. Then, you can use the proof method eval to do the heavy work:
inductive foo where
"foo" if
"class_roles associations Employee assoc1 from role"
"class_roles associations Employee assoc2 from role"
"assoc1 ≠ assoc2"
code_pred foo .
lemma class_roles_unique:
assumes "class_roles associations Employee assoc1 from role"
and "class_roles associations Employee assoc2 from role"
shows "assoc1 = assoc2"
proof -
have "¬ foo" by eval
with assms show ?thesis by(simp add: foo.simps)
qed
Note that eval uses code generation and evaluation in PolyML, so it computes the result rather than proves it. That is, evaluation is not checked by Isabelle's kernel. The related proof method code_simp goes through the kernel, but it does not work out of the box in this example because the code setup for String.asciis_of_literals is missing in Isabelle2018.
The following lemmas provide the missing code equations for literal strings, but code_simp is very slow with literal strings (normalization is a bit faster, but not checked by Isabelle's kernel either).
definition dup_bit :: "bool ⇒ integer ⇒ integer" where
"dup_bit b i = i + i + (if b then 1 else 0)"
lemma dup_bit_code [code]:
"dup_bit True 0 = 1"
"dup_bit False 0 = 0"
"dup_bit True (Code_Numeral.Pos n) = Code_Numeral.Pos (num.Bit1 n)"
"dup_bit False (Code_Numeral.Pos n) = Code_Numeral.Pos (num.Bit0 n)"
"dup_bit True (Code_Numeral.Neg n) = - Code_Numeral.sub (num.Bit0 n) Num.One"
"dup_bit False (Code_Numeral.Neg n) = Code_Numeral.Neg (num.Bit0 n)"
by(simp_all add: dup_bit_def Code_Numeral.sub_def nat_of_num_add num_eq_iff)
(metis diff_numeral_special(1) numeral_Bit0 numeral_plus_numeral sub_num_simps(2))
fun integer_of_bits :: "bool list ⇒ integer" where
"integer_of_bits [] = 0"
| "integer_of_bits (b # bs) = dup_bit b (integer_of_bits bs)"
lemma asciis_of_literal_code [code]:
"String.asciis_of_literal (STR '''') = []"
"String.asciis_of_literal (String.Literal b0 b1 b2 b3 b4 b5 b6 s) =
integer_of_bits [b0, b1, b2, b3, b4, b5, b6] # String.asciis_of_literal s"
including literal.lifting by(transfer; simp add: dup_bit_def; fail)+

Proving the cardinality of a more involved set

Supposing I have a set involving three conjunctions {k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2}.
How can I prove in Isabelle that the cardinality of this set is 1 ? (Namely only k=6 has gcd 3 6 = 2.) I.e., how can I prove lemma a_set : "card {k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2} = 1" ?
Using sledgehammer (or try) again doesn't yield results - I find it very difficult to find what exactly I need to give the proof methods to make them able to to the proof. (Even removing, e.g. gcd 3 k = 2, doesn't make it amenable to auto or sledgehammer.)
Your proposition is incorrect. The set you described is actually empty, as gcd 3 6 = 3. Sledgehammer can prove that the cardinality is zero without problems, although the resulting proof is again a bit ugly, as is often the case with Sledgehammer proofs:
lemma "card {k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2} = 0"
by (metis (mono_tags, lifting) card.empty coprime_Suc_nat
empty_Collect_eq eval_nat_numeral(3) gcd_nat.left_idem
numeral_One numeral_eq_iff semiring_norm(85))
Let's do it by hand, just to illustrate how to do it. These sorts of proofs do tend to get a little ugly, especially when you don't know the system well.
lemma "{k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2} = {}"
proof safe
fix x :: nat
assume "x > 2" "x ≤ 7" "gcd 3 x = 2"
from ‹x > 2› and ‹x ≤ 7› have "x = 3 ∨ x = 4 ∨ x = 5 ∨ x = 6 ∨ x = 7" by auto
with ‹gcd 3 x = 2› show "x ∈ {}" by (auto simp: gcd_non_0_nat)
qed
Another, much simpler way (but also perhaps more dubious one) would be to use eval. This uses the code generator as an oracle, i.e. it compiles the expression to ML code, compiles it, runs it, looks if the result is True, and then accepts this as a theorem without going through the Isabelle kernel like for normal proofs. One should think twice before using this, in my opinion, but for toy examples, it is perfectly all right:
lemma "card {k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2} = 0"
proof -
have "{k::nat. 2<k ∧ k ≤ 7 ∧ gcd 3 k = 2} = Set.filter (λk. gcd 3 k = 2) {2<..7}"
by (simp add: Set.filter_def)
also have "card … = 0" by eval
finally show ?thesis .
qed
Note that I had to massage the set a bit first (use Set.filter instead of the set comprehension) in order for eval to accept it. (Code generation can be a bit tricky)
UPDATE:
For the other statement from the comments, the proof has to look like this:
lemma "{k::nat. 0<k ∧ k ≤ 5 ∧ gcd 5 k = 1} = {1,2,3,4}"
proof (intro equalityI subsetI)
fix x :: nat
assume x: "x ∈ {k. 0 < k ∧ k ≤ 5 ∧ coprime 5 k}"
from x have "x = 1 ∨ x = 2 ∨ x = 3 ∨ x = 4 ∨ x = 5" by auto
with x show "x ∈ {1,2,3,4}" by (auto simp: gcd_non_0_nat)
qed (auto simp: gcd_non_0_nat)
The reason why this looks so different is because the right-hand side of the goal is no longer simply {}, so safe behaves differently and generates a pretty complicated mess of subgoals (just look at the proof state after the proof safe). With intro equalityI subsetI, we essentially just say that we want to prove that A = B by proving a ∈ A ⟹ a ∈ B and the other way round for arbitrary a. This is probably more robust than safe.

Isabelle list lifter and compression

From two sets in Isabelle a third list needs to be created with element of the form (a, b) where a is from the first set and b is in the second set. in addition the elements in the last set must be filtered by some condition.
The code:
theory Scratch
imports Main Nat
begin
value "let a = {(1::int), 2, 3, 4} in (let b = {(6::int),7,8,9} in
((1::int), 6) ∈ set (filter (λ el . (snd el) < 8) [(n,m). n ∈ a ∧ m ∈ b]))"
end
The result I expected was True or False. the results was:
"(1, 6)
∈ set [u←if (1 = n ∨ 2 = n ∨ 3 = n ∨ 4 = n) ∧
(6 = m ∨ 7 = m ∨ 8 = m ∨ 9 = m)
then [(n, m)] else [] . snd u < 8]"
:: "bool"
Why does the result not evaluate to a True/False value?
Is it possible to write code where the filter functions is evaluated on a set and not list?
First of all, you cannot convert sets to lists. Lists have a specific order of elements; sets do not.
Question 1
This is because you have free variables in there: n and m. The expression [(n,m). n ∈ a ∧ m ∈ b] basically means if n ∈ a ∧ m ∈ b then [(n,m)] else []. This is not what you want.
If a and b were lists, you could use the list comprehension syntax [(n,m). n ← a, m ← b]. However, since a and b are sets, this cannot possibly work, since the result would be a list with a specific order, and that order has to come from somewhere – but a and b, as sets, have no such order.
Question 2
In formalisation, the best approach is to first define things abstractly, without using data structures that are too concrete. If you don't need to maintain a specific ordering of your values, use a set, not a list. You can then later refine this from sets to lists in order to obtain executable (and efficient) code.
There is a section on refinement in the Isabelle code generation manual. I recommend you have a look at it.
That said, there is some limited support for code generation with sets. Sets are then internally represented as lists and most basic operations work, but code generation may sometimes fail – not all operations on sets are computable in general. There is the function Set.filter, which is executable and basically does the same on sets as the regular filter function does for lists.
However, the following will fail due to a wellsortedness error:
value "let a = {(1::int), 2, 3, 4} in (let b = {(6::int),7,8,9} in
((1::int), (6 :: int)) ∈ Set.filter (λ el . (snd el) < 8) {x. fst x ∈ a ∧ snd x ∈ b})"
This is because set comprehensions (i.e. {x. … }) are, in general, not computable. You have to replace this {x. fst x ∈ a ∧ snd x ∈ b} with something the code generator can generate code for. In this case, it's easy, because this operation is just the cartesian product. You can write:
value "let a = {(1::int), 2, 3, 4} in (let b = {(6::int),7,8,9} in
((1::int), (6 :: int)) ∈ Set.filter (λ el . (snd el) < 8) (a × b))"
And you get the result you'd expect.

Resources