Zulip Chat Archive

Stream: lean4

Topic: Working with ```cast``` and ```HEq```


Sophie Morel (Dec 22 2023 at 20:19):

Okay, so I'm not sure that I'm approaching this the right way... The original problem that I'm trying to solve is proving the following lemma:

import Mathlib.Data.Set.Finite

example (n : ) :
    Set.Finite {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
    p.1 < n  p.2.1 < n} := by sorry

I was able to write a proof, here it is:

import Mathlib.Data.Set.Finite

open Set in
example (n : ) :
    Set.Finite {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
    p.1 < n  p.2.1 < n} := by
  set s := {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
      p.1 < n  p.2.1 < n}
  rw [Set.finite_def]
  apply Nonempty.intro
  set g : s  (k : Finset.range n) × (l : Finset.range n) ×
        {s : Finset (Fin (k + l)) | s.card = l} := (fun p =>
          ⟨⟨p.1.1, Finset.mem_range.mpr p.2.1⟩, p.1.2.1, Finset.mem_range.mpr p.2.2⟩, p.1.2.2⟩)
  set h : (k : Finset.range n) × (l : Finset.range n) ×
        {s : Finset (Fin (k + l)) | s.card = l}  s :=
        fun p => by refine ⟨⟨p.1, p.2.1, p.2.2⟩, ?_
                    simp only [coe_setOf, mem_setOf_eq]
                    exact Finset.mem_range.mp p.1.property, Finset.mem_range.mp p.2.1.property
  have h1 :  x, h (g x) = x := by
        intro p, hp
        simp only [coe_setOf, mem_setOf_eq, Sigma.eta]
  have h2 :  x, g (h x) = x := by
        intro k, l, t
        simp only [coe_setOf, mem_setOf_eq, id_eq]
  set e : s  (k : Finset.range n) × (l : Finset.range n) ×
        {s : Finset (Fin (k + l)) | s.card = l} :=
        {toFun := g
         invFun := h
         left_inv := h1
         right_inv := h2}
  apply Fintype.ofEquiv _ e.symm

However, it a bit clunky, and I have several similar results to prove (but not similar enough to have an obvious common generalizations: the difference is that there are more or less factors in the product).

So I though that the best would be to have a result saying "if I have a map with a finite image and finite fibers, then its domain is finite", or something slightly more general. This was easy, here it is:

import Mathlib.Data.Set.Finite

theorem Set.Finite.of_finite_image_of_finite_fibers {α : Type*} {β : Type*} {s : Set α}
    {f : α  β} (hfin1 : Set.Finite (f '' s)) (hfin2 :  y  f '' s, Set.Finite (f ⁻¹' {y})) :
    Set.Finite s :=
  Set.Finite.subset (Set.Finite.biUnion hfin1 hfin2) (fun x hx  Set.mem_biUnion
  (Set.mem_image_of_mem f hx) (by rw [Set.mem_preimage, Set.mem_singleton_iff]))

But I ran into trouble when trying to apply it to my example. First I defined the function, the obvious idea is projection on the first two factors, here it is:

def f : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l}   ×  := fun p  p.1, p.2.1

Okay, now I need to prove that the fibers of f are finie. So the most obvious is to define an injective map g from the fiber at (k,l) into Finset (Fin (k + l)), because that last thing is certainly finite. Here is the map g, already I am starting to struggle:

def g (k l : ) : f ⁻¹' {⟨k, l⟩}  Finset (Fin (k + l)) :=
          fun x  by have h := Prod.eq_iff_fst_eq_snd_eq.mp
                       (Set.mem_singleton_iff.mp (Set.mem_preimage.mp x.2))
                     simp only at h
                     refine cast ?_ x.1.2.2.1
                     simp_rw [ h.1,  h.2, f]

Now I just need to prove that g is injective, except I can't seem to do it. I am battling expressions full of cast and HEq and I don't really know what to do. I can show you how I started, but very quickly I start going round in circles:

example (k l : ) : Function.Injective (g k l) := by
  intro x y
  have hx := Prod.eq_iff_fst_eq_snd_eq.mp (Set.mem_singleton_iff.mp (Set.mem_preimage.mp x.2))
  have hy := Prod.eq_iff_fst_eq_snd_eq.mp (Set.mem_singleton_iff.mp (Set.mem_preimage.mp y.2))
  unfold f at hx hy
  simp only at hx hy
  intro h
  ext
  · rw [hx.1, hy.1]
  · apply heq_of_cast_eq

And so on...

Sophie Morel (Dec 22 2023 at 20:25):

I understand that of the problems is that, if even if, say, I know that n = m, then this does not mean that Fin n = Fin m and I need to use cast in order to move elements between these types. But that doesn't tell me how to use this strange cast function...

Yaël Dillies (Dec 22 2023 at 20:27):

Certainly m = n implies Fin m = Fin n! What you mean is that the typechecker doesn't know they are equal unless you actually substitute the m = n equality.

Yaël Dillies (Dec 22 2023 at 20:27):

Do you know about tactic#subst ?

Sophie Morel (Dec 22 2023 at 20:34):

I certainly don't know about that tactic. Let me check it out, thanks !

Sophie Morel (Dec 22 2023 at 20:34):

(Equality is complicated.)

Kyle Miller (Dec 22 2023 at 20:35):

Going back to the beginning, I think this would be easiest rewriting it as a product involving set products, and it's a product of finite sets.

Kyle Miller (Dec 22 2023 at 20:36):

Oh, oops, missed the dependence when reading it quickly.

Sophie Morel (Dec 22 2023 at 20:37):

Yeah, I'm in a noob but I'm not that much of a noob. :sweat_smile:

Sophie Morel (Dec 22 2023 at 20:38):

Okay, I can't really make subst work yet but also I have to go to bed because I have to catch a train super early tomorrow, so I'll think more about it and be back later.

Kyle Miller (Dec 22 2023 at 20:40):

I have to go, but this is what I'd use:

theorem foo {n : } :
    {p : (q : ( × )) × {s : Finset (Fin (q.1 + q.2)) // s.card = q.2} |
      p.1.1 < n  p.1.2 < n} =
      Finset.sigma (Finset.range n ×ˢ Finset.range n) (fun q =>
        (Finset.univ : Finset ({s : Finset (Fin (q.1 + q.2)) // s.card = q.2}))) := by
    ext k, l
    simp

Kyle Miller (Dec 22 2023 at 20:40):

That establishes Set.Finite for a related set, and then your theorem then is about reassociating that dependent product.

Kyle Miller (Dec 22 2023 at 20:44):

(@Yaël Dillies would you know if there's at least an equivalence to reassociate this dependent product?

Yaël Dillies (Dec 22 2023 at 20:44):

To get the dependent type outside of the subtype?

Sophie Morel (Dec 22 2023 at 21:11):

Kyle Miller said:

I have to go, but this is what I'd use:

theorem foo {n : } :
    {p : (q : ( × )) × {s : Finset (Fin (q.1 + q.2)) // s.card = q.2} |
      p.1.1 < n  p.1.2 < n} =
      Finset.sigma (Finset.range n ×ˢ Finset.range n) (fun q =>
        (Finset.univ : Finset ({s : Finset (Fin (q.1 + q.2)) // s.card = q.2}))) := by
    ext k, l
    simp

(Yes, I'm back. Only for a few minutes :fingers_crossed:, I wanted to check something in my code.)
Interesting, thanks ! I've been wondering about reassociating the dependent product myself, it seemed that it would make my life easier.

I wanted to check what I did in the original proof: I construct an equivalence with (k : Finset.range n) × (l : Finset.range n) × {s : Finset (Fin (k + l)) | s.card = l}, and then poof, Lean knows that that one is finite ! (I know its PSigma.fintype working its magic in the background, but still, magic.) So maybe another way to go would be to make the construction of the equivalence more efficient... Okay, I'm really going to bed now, I swear.

Kyle Miller (Dec 22 2023 at 21:50):

@Sophie Morel I realized right when I had to go that there's a more direct way to do this. It's still the same idea, write down a Finset expression that calculates the correct thing:

example (n : ) :
    Set.Finite {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
    p.1 < n  p.2.1 < n} := by
  convert_to Set.Finite ((Finset.range n).sigma fun k =>
    (Finset.range n).sigma fun l =>
      (Finset.univ : Finset ({s : Finset (Fin (k + l)) // s.card = l}))).toSet
  · ext k, l, s
    simp
  apply Finset.finite_toSet

Kyle Miller (Dec 22 2023 at 21:50):

I used convert_to rather than write a separate lemma that gives an equality of the sets.

Terence Tao (Dec 23 2023 at 01:39):

Perhaps it might be easier to work with an equivalent set such as {(k,l,s) : ℕ × ℕ × (Finset ℕ) | s ⊆ Finset.range (k+l) ∧ s.card = l ∧ k ∈ Finset.range n ∧ l ∈ Finset.range n} throughout, to avoid dependent types entirely? Note that this (up to coercions) is a subset of (Finset.range n) × (Finset.range n) × Finset (Finset.range (2*n)) so is manifestly finite.

Sophie Morel (Dec 23 2023 at 06:55):

Terence Tao said:

Perhaps it might be easier to work with an equivalent set such as {(k,l,s) : ℕ × ℕ × (Finset ℕ) | s ⊆ Finset.range (k+l) ∧ s.card = l ∧ k ∈ Finset.range n ∧ l ∈ Finset.range n} throughout, to avoid dependent types entirely? Note that this (up to coercions) is a subset of (Finset.range n) × (Finset.range n) × Finset (Finset.range (2*n)) so is manifestly finite.

The thing is that I can't choose what I am working with unless I do some rewriting of already existing mathlib code, or unless I duplicate some work. These are from the calculation of the power series of f(x + y) from that of f(x), I am proving that the new power series converges without any completeness assumptions if the original power series was finite. If I'm trying to minimize the amount of pain for myself, not rewriting all the technical lemmas there seems like a good option !

Sophie Morel (Dec 23 2023 at 06:59):

Kyle Miller said:

Sophie Morel I realized right when I had to go that there's a more direct way to do this. It's still the same idea, write down a Finset expression that calculates the correct thing:

example (n : ) :
    Set.Finite {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
    p.1 < n  p.2.1 < n} := by
  convert_to Set.Finite ((Finset.range n).sigma fun k =>
    (Finset.range n).sigma fun l =>
      (Finset.univ : Finset ({s : Finset (Fin (k + l)) // s.card = l}))).toSet
  · ext k, l, s
    simp
  apply Finset.finite_toSet

Great, thank you !

Is there a list of super-cool secret Lean4 tactics somewhere, with their documentation ? It seems that every time I ask a question, somebody answers "oh, you don't know the frobnicate tactic, that's exactly the thing for this situation". Meanwhile I was struggling the other day to find the documentation of rewrite. :sweat_smile:
(ETA: I did figure it out, I just need to hover over the tactic name in vscode and a handy documentation pop ups. And here I was trying to find it in the mathlib documentation...)

Yaël Dillies (Dec 23 2023 at 07:06):

Yes, mathlib documentation for tactics has disappeared in the port. This is something we're aware of but nobody fixed it yet.

Sophie Morel (Dec 23 2023 at 07:08):

Well, I've been examining Kyle's proof and it's pretty cool. I definitely have to learn more about convert and convert_to, because it seems like something I could have used in the past. (I've finally learned how to use calc instead of piling 13 levels of le_trans and lt_of_lt_and_le etc, and I'm cursing myself for not taking the time to learn earlier.)

Sophie Morel (Dec 23 2023 at 07:10):

Yaël Dillies said:

Yes, mathlib documentation for tactics has disappeared in the port. This is something we're aware of but nobody fixed it yet.

Ah, that explains why I have a much harder time finding it now, it's not there ! :laughing: It's reassuring in a way, because I felt like I had become very stupid all of a sudden.

Riccardo Brasca (Dec 23 2023 at 08:48):

Sophie Morel said:

Kyle Miller said:

Sophie Morel I realized right when I had to go that there's a more direct way to do this. It's still the same idea, write down a Finset expression that calculates the correct thing:

example (n : ) :
    Set.Finite {p : (k : ) × (l : ) × {s : Finset (Fin (k + l)) // s.card = l} |
    p.1 < n  p.2.1 < n} := by
  convert_to Set.Finite ((Finset.range n).sigma fun k =>
    (Finset.range n).sigma fun l =>
      (Finset.univ : Finset ({s : Finset (Fin (k + l)) // s.card = l}))).toSet
  · ext k, l, s
    simp
  apply Finset.finite_toSet

Great, thank you !

Is there a list of super-cool secret Lean4 tactics somewhere, with their documentation ? It seems that every time I ask a question, somebody answers "oh, you don't know the frobnicate tactic, that's exactly the thing for this situation". Meanwhile I was struggling the other day to find the documentation of rewrite. :sweat_smile:
(ETA: I did figure it out, I just need to hover over the tactic name in vscode and a handy documentation pop ups. And here I was trying to find it in the mathlib documentation...)

You can have a look here. It is just a copy paste of the official documentation, but at least it is a list of tactics.

Sophie Morel (Dec 23 2023 at 09:10):

Thank you!
Hahaha, I've been playing with convert and this thing is freaking dark magic!

Kyle Miller (Dec 23 2023 at 16:17):

@Sophie Morel One of the things you can do with it is use to handle "wrong" Fintype and DecidableEq instances. If exact fails just because of differences in such instances, convert should succeed. (If it doesn't, ping me!)

Other related tactics are:

  • congr! if the goal is an iff, eq, heq, or any other reflexive relation, and you want to reduce to the case of showing that corresponding sub-expressions are equal. (convert/convert_to are implemented using it)
  • congrm is similar, but you can provide a pattern with placeholders, where the placeholders are in positions where you want a new sub-goal
  • gcongr is for "generalized congruence," which can handle relations like inequalities, vs equalities/iffs in the previous tactics. It is more opinionated about doing what makes sense in context.
  • apply_fun can handle applying a function at an equality hypothesis or, if it's injective, at the goal. It's not super related, but it's also got something to do with applying congruence lemmas so I'll include it.

Sophie Morel (Dec 25 2023 at 11:53):

Thank you for the list of useful tactics ! Even for the ones I already knew, you have just taught me about new ways to use them.


Last updated: May 02 2025 at 03:31 UTC