Zulip Chat Archive

Stream: new members

Topic: Writing a Fintype instance


Sam Collins (Nov 26 2024 at 15:56):

Hello, I'm new to Lean and new to Zulip so apologies if I'm doing something wrong.

I have a type:

def BVec (n : Nat) : Fin n -> Bool

I wish to be able to use fin_cases on a specific BVec n when n is known. For example, fin_cases a where a : BVec 1 should give me two cases ![false] and ![true] to work with. I believe to be able to do this, I must write an instance of Fintype for BVec n (not sure on the terminology here):

instance Fintype (BVec n) where
    elems := sorry
    complete := sorry

However I am having great trouble filling in elems (let alone complete). I'm aware that fin_cases works on Fin n -> Bool but it gives me very generic cases using multisets or something, which isn't very useful for me. In my case I can represent each case with e.g. ![false, true].

I would appreciate any help on this, thanks.

Quang Dao (Nov 26 2024 at 16:00):

You can express BVec in terms of existing mathlib definitions, namely docs#finFunctionFinEquiv and docs#finTwoEquiv. That should give you Fintype for free

Sam Collins (Nov 26 2024 at 16:05):

@Quang Dao Interesting, thanks. I'll look into that and possibly use those definitions for BVec. Would you recommend something similar for BFun n? It's currently defined as:

def BFun (n : Nat) := BVec n -> Bool

Out of interest, if I were to stick with my definition of BVec n, how would one go about writing an instance of Fintype for it? Or is this a case where that's very difficult or impossible, and there's really no reason to define Boolean vectors like that?

For a wider scope on what I'm trying to accomplish, I'm trying to prove Post's Theorem of Functional Completeness in the field of Boolean functions using Lean4.

Sam Collins (Nov 26 2024 at 16:19):

To be honest I don't yet entirely know what it means to "express BVec in terms of existing mathlib definitions" :sweat_smile:

Etienne Marion (Nov 26 2024 at 16:38):

This works:

import Mathlib

def BVec (n : Nat) := Fin n  Bool

instance (n : Nat) : Fintype (BVec n) := by
  unfold BVec
  infer_instance

Lean knows that Fin n and Bool are fintypes and that the arrow between two fintypes gives a fintype, so once you unfold the definition you can just type infer_instance which will automatically close the goal by typeclass inference.

Here is a shorter version which does the same:

import Mathlib

def BVec (n : Nat) := Fin n  Bool

instance (n : Nat) : Fintype (BVec n) := inferInstanceAs (Fintype (Fin n  Bool))

Sam Collins (Nov 26 2024 at 16:46):

@Etienne Marion Thanks. When I call fin_cases on a BVec 1 it produces something that uses Multiset.Pi.cons 0 0 true and Multiset.Pi.empty fun a ↦ Bool. simp doesn't do anything with these, whereas before I split into cases of ![true] and ![false] (with a sorry to show this is possible) and after rwing these into the goal,simp proved it.

Sam Collins (Nov 26 2024 at 16:52):

Here is the full code I had, which worked (but contains sorry and isn't as neat as fin_cases a <;> simp):

def narrow (a : BVec (n + 1)) : BVec n :=
    fun i, h => a i, Nat.lt_succ_of_lt h

def xor_all (n : Nat) (a : BVec n) (b : BVec n) : Bool :=
    match n with
    | 0 => false
    | k + 1 => xor_all k (narrow a) (narrow b) ^^ (a k && b k)

def linear_fun (c : BVec (n + 1)) : BFun n :=
    fun a => c n ^^ xor_all n a (narrow c)

def linear (f : BFunAll) :=  c : BVec (f.fst + 1), linear_fun c = f.snd

def L : Set BFunAll := {f | linear f}

theorem bvec_1_is_true_or_false (a : BVec 1) : a = ![true]  a = ![false] := sorry

/-- example proof that all 1-ary Boolean functions are linear -/
example (f : BFun 1) : 1, f  L := by
    show linear 1, f
    unfold linear
    use ![f ![false] ^^ f ![true], f ![false]]
    unfold linear_fun
    repeat unfold xor_all
    repeat unfold narrow
    funext a
    cases bvec_1_is_true_or_false a with
    | inl h₀ =>
        rw [h₀]
        simp
    | inr h₁ =>
        rw [h₁]
        simp

Etienne Marion (Nov 26 2024 at 16:57):

There is some code missing it does not work in the online editor

Sam Collins (Nov 26 2024 at 16:58):

Prepend the following:

-- import Mathlib is probably also fine
import Mathlib.Tactic
-- probably not necessary
variable {n : }

def BVec (n : ) := Fin n -> Bool

def BFun (n : ) := BVec n -> Bool

def BFunAll := Σ n : , BFun n

My apologies

Sam Collins (Nov 26 2024 at 16:59):

(I think import Mathlib is also fine but my peer added import Mathlib.Tactic instead for some reason)

Etienne Marion (Nov 26 2024 at 17:09):

I am not familiar with fin_cases but I guess that the issue here is that the way the fintype instance is provided for an arrow type uses multisets, but it does not give the cases you like. This makes me think that if you want to prove this using fin_cases you'll have to fill in the instance explicitely, but that would mean proving what you want to avoid so it's not a real solution.

Etienne Marion (Nov 26 2024 at 17:09):

So I'm not sure how to proceed here.

Sam Collins (Nov 26 2024 at 17:16):

@Etienne Marion Using fin_cases isn't a necessity, it's just that writing an instance of Fintype for BVec n that behaves nicely would allow me to avoid writing individual theorems for splitting a BVec n into cases. I don't even know how to prove such theorems (hence the sorry) and I shouldn't need them at all because clearly BVec n is a finite type. Do you have any idea how to complete the proof where it says sorry? Or do you have any idea how to use the equivalences suggested by @Quang Dao above to write an instance of Fintype that behaves nicely? I've tried the following:

instance (n : Nat) : Fintype (BVec n) := by
    unfold BVec
    have h : Fintype (Fin (2 ^ n)) := sorry
    rw [finFunctionFinEquiv.invFun] at h
    rw [finTwoEquiv] at h
    exact h

but it doesn't work.

Thanks for the help!

Etienne Marion (Nov 26 2024 at 17:31):

This works:

theorem bvec_1_is_true_or_false (a : BVec 1) : a = ![true]  a = ![false] := by
  unfold BVec
  obtain h | h := Bool.eq_false_or_eq_true (a 0)
  · left
    ext x
    rw [Subsingleton.elim x 0, h]
    rfl
  · right
    ext x
    rw [Subsingleton.elim x 0, h]
    rfl

Though it's probably not the best solution.

Sam Collins (Nov 26 2024 at 17:45):

Thanks. It's taking me a while to wrap my head around why that works, but I'm sure I'll get there :sweat_smile:

Etienne Marion (Nov 26 2024 at 17:49):

An element of BVec 1 is just a function form Fin 1 to Bool. So I'm saying: either a 0 = true or a 0 = false. Then the ext tactic says that to prove two functions are equal it suffices to prove that they coincide everywhere, so we taxe x : Fin 1 and want to prove a x = true or a x = false depending on the case. But as all elements in Fin 1 are equal (it is a docs#Subsingleton), we have x = 0.

Sam Collins (Nov 26 2024 at 18:26):

Managed to write an equivalence relation between BVec n and Fin (2 ^ n) (turned out to be very easy):

def bvec_n_equiv_fin_2_n : BVec n  Fin (2 ^ n) where
  toFun i :=
    let f : Fin n -> Fin 2 :=
      fun x => finTwoEquiv.invFun (i x)
    finFunctionFinEquiv.toFun f
  invFun b :=
    let f : Fin n -> Fin 2 :=
      finFunctionFinEquiv.invFun b
    fun x => finTwoEquiv.toFun (f x)
  left_inv i := by simp
  right_inv b := by simp

Not sure whether I can use this to write an instance of Fintype (BVec n) that behaves nicely?

Sam Collins (Nov 26 2024 at 21:49):

Think I got it working:

@[simp]
def bvec_n_equiv_fin_2_n : BVec n  Fin (2 ^ n) where
  toFun a :=
    let f := fun i => finTwoEquiv.invFun (a i)
    finFunctionFinEquiv.toFun f
  invFun b :=
    let f := finFunctionFinEquiv.invFun b
    fun i => finTwoEquiv.toFun (f i)
  left_inv a := by simp
  right_inv b := by simp

attribute [simp] finTwoEquiv finFunctionFinEquiv

instance : Fintype (BVec n) :=
  Fintype.ofEquiv (Fin (2 ^ n)) bvec_n_equiv_fin_2_n.symm

def narrow (a : BVec (n + 1)) : BVec n :=
  fun i, h => a i, Nat.lt_succ_of_lt h

def xor_all (n : Nat) (a : BVec n) (b : BVec n) : Bool :=
  match n with
  | 0 => false
  | k + 1 => xor_all k (narrow a) (narrow b) ^^ (a k && b k)

def linear_fun (c : BVec (n + 1)) : BFun n :=
  fun a => c n ^^ xor_all n a (narrow c)

def linear (f : BFunAll) :=  c : BVec (f.fst + 1), linear_fun c = f.snd

def L : Set BFunAll := {f | linear f}

@[simp]
theorem vec_1_is_fun_1 (x : Bool) : ![x] = fun _ => x := by funext i; fin_cases i; simp

/-- example proof that all 1-ary Boolean functions are linear -/
example (f : BFun 1) : 1, f  L := by
  show linear 1, f
  unfold linear
  use ![f ![false] ^^ f ![true], f ![false]]
  unfold linear_fun
  repeat unfold xor_all
  repeat unfold narrow
  funext a
  fin_cases a <;> simp

Matt Diamond (Nov 27 2024 at 02:05):

FYI we have docs#BitVec if you want to take advantage of existing library code (like docs#BitVec.xor, docs#BitVec.truncate, etc.)

totally understand if you want to roll your own for learning purposes, though

Matt Diamond (Nov 27 2024 at 02:15):

weirdly there's no Fintype instance for BitVec even though it's just a wrapper around Fin (2 ^ n)...

I'm curious if there's an easier way, but you can just do this:

import Mathlib

def finTwoPowEquivBitVec : Fin (2 ^ n)  BitVec n :=
BitVec.ofFin, BitVec.toFin, fun _ => rfl, fun _ => rfl

instance : Fintype (BitVec n) := Fintype.ofEquiv _ finTwoPowEquivBitVec

Sam Collins (Nov 27 2024 at 10:51):

@Matt Diamond We (myself and my peers) were aware of BitVec, but thought that because indexing it to retrieve an individual Bool requires bit fiddling (?), it might be harder to use it in proofs that need to reason about individual bits that are defined in terms of something else. Not sure whether this makes sense, we're all new to Lean and are basically guessing at this point, but I'm fairly convinced that we should try using BitVecs and see whether we encounter any issues, rather than writing them off from the start, since multiple different sources have suggested using them, including yourself (although I'm not sure whether any of these sources have used them to prove results in Boolean functions).

Matt Diamond (Nov 27 2024 at 21:56):

@Sam Collins Yes, it's certainly not as direct as just having a function from Fin n to Bool, but there's an API in place which handles bit extraction for you (i.e. docs#BitVec.getLsb' and docs#BitVec.getMsb', along with a docs#GetElem instance which allows you to simply write b[i]), so the implementation details should hopefully not be too much of an inconvenience.


Last updated: May 02 2025 at 03:31 UTC