Zulip Chat Archive

Stream: new members

Topic: Generate random Fin n


Jesse Slater (Oct 21 2023 at 20:33):

I haven't worked with randomness in a functional language before, and I am having a bit of trouble. I am trying to apply a series of random functions to a number.
Here is the code I have:

def apply_random_n
  {i : Nat}
  (h : 0 < i)
  (start n : Nat)
  (v : Fin i  Nat  Nat)
  : Nat :=
  match n with
  | 0 => start
  | n' + 1 =>
    let r : Fin i := 0, h
    --r should be randomly generated each step
    v r (apply_random_n h start n' v)

What do I need to change to generate r randomly each step? I see the Random.randFin function in mathlib, but I cannot figure out how to use it for the life of me.

Yaël Dillies (Oct 21 2023 at 20:42):

Here's a file I wrote for my computer projects last year:

Copyright (c) 2023 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies
-/
import Mathlib.Control.Random
import Catam.Mathlib.Data.Enum
import Catam.Mathlib.Data.Rat.Defs

/-!
# Additional randomness operations
-/

namespace Random
variable {g : Type} {α β : Type _} [RandomGen g] {n : ℕ} [NeZero n]

def randFin' : RandG g (Fin n) :=
  λ ⟨g⟩ ↦ (randNat g 0 n).map (λ a ↦ Fin.ofNat' a $ NeZero.pos n) ULift.up

instance : Random (Fin n) where random := randFin'

/-- Return `true` with probability `p`, `false` with probability `1 - p`. If `p ≤ 0`, it always
returns false. If `1 ≤ p`, it always returns true. -/
def bernoulli (p : ℚ) : RandG g Bool := return (← rand (Fin p.den)) < p.num

/-- Return a bipartite graph on `n + n` vertices where each edge is present independently with
probability `p`. -/
def bipartiteGraph (n : ℕ) (p : ℚ) : RandG g (Fin n → Fin n → Bool) :=
Monad.pi $ λ _ ↦ Monad.pi $ λ _ ↦ bernoulli p

/-- Return an element of an enumerable type `α` uniformly at random. -/
def uniform [Enum α] [Nonempty α] : RandG g α := Enum.enum.get <$> randFin'

end Random

Alex J. Best (Oct 21 2023 at 20:42):

Functions in function languages are pure, they cannot depend on anything other than their inputs. So you'll need to work in some sort of monad for randomness docs#IO.rand or docs#RandG that Yael mentions, or pass in a random state and generator each time docs#randNat. What you want to do precisely

Yaël Dillies (Oct 21 2023 at 20:42):

along with

Copyright (c) 2023 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies
-/
import Mathlib.Data.Fin.Basic
import Mathlib.Data.List.Range
import Catam.Mathlib.Data.Vector.Basic

/-!
# Enumeration of types
-/

class Enum (α : Type _) where
  enum : List α
  enum_nodup : enum.Nodup
  index : α → Fin enum.length
  enum_index : ∀ a, enum.get (index a) = a

instance {n : ℕ} : Enum (Fin n) :=
{ enum := List.finRange _,
  enum_nodup := List.nodup_finRange _,
  index := λ n ↦ ⟨n, by rw [List.length_finRange]; exact n.2⟩,
  enum_index := λ a ↦ by simp }

instance {α : Type _} {β : α → Type _} [Enum α] [∀ a, ToString (β a)] : ToString (∀ a, β a) :=
⟨λ f ↦ toString $ Enum.enum.map $ λ a ↦ toString $ f a⟩

instance [Enum α] [Nonempty α] : NeZero (Enum.enum : List α).length :=
⟨λ h ↦ by
  haveI := ‹Nonempty α›.map Enum.index
  rw [h] at this
  exact not_isEmpty_of_nonempty (Fin 0) (inferInstance : IsEmpty (Fin 0))⟩

namespace Monad
universe u v
variable {α : Type u} {β : Type (max u v)} {m : Type (max u v) → Type _}

-- TODO: Generalise to dependent types
/-- Return a random function from random results for each input. -/
def pi [Enum α] [Monad m] (r : α → m β) : m (α → β) :=
(λ l a ↦ l.toList.get ⟨Enum.index a, by simp⟩) <$> Vector.mapM r ⟨Enum.enum, rfl⟩

end Monad

Yaël Dillies (Oct 21 2023 at 20:44):

Sorry, I can't make the repo public because of fairness concerns, but I can certainly dump those prereqs into LeanCamCombi or something for me to avoid copying everything over here.

Jesse Slater (Oct 21 2023 at 20:55):

@Alex J. Best
I don't need anything fancy, just need to generate a series of random Fin n. This is not any sort of mission critical code, and I won't be writing any proofs about it, so I just want to simplest solution.

Jesse Slater (Oct 21 2023 at 20:59):

All I need to do with the result is look at it

Jesse Slater (Oct 21 2023 at 21:04):

@Yaël Dillies I am sure this would solve my problem, but I apologize, I have no idea how to use it.

Alex J. Best (Oct 21 2023 at 21:09):

Here is an example

import Std


def apply_random_n
  {i : Nat}
  (h : 0 < i)
  (start n : Nat)
  (v : Fin i  Nat  Nat)
  (g : StdGen)
  : Nat × StdGen :=
  match n with
  | 0 => (start, g)
  | n' + 1 =>
    let rs := (randNat g 0 (i - 1))
    let r : Fin i := Fin.ofNat' rs.1 h
    --r should be randomly generated each step
    Prod.map (v r) id (apply_random_n h start n' v rs.2)

-- some code to demo how it works, you don't need to understand all of this, just that we have to update g every time
open Lean Meta
#eval show MetaM Unit from do
  let mut g := mkStdGen
  for _ in List.range 10 do
    let a := apply_random_n (sorry : 0 < 10) 0 1 (fun j i => i + j.val) g
    dbg_trace a.1
    g := a.2 -- if you comment out this line there is no randomness

Jesse Slater (Oct 21 2023 at 21:20):

Thank you, this is perfect!

Eric Wieser (Oct 22 2023 at 08:01):

Don't we have a Fin instance for docs#Random ?

Eric Wieser (Oct 22 2023 at 08:01):

docs#Random.instRandomFinSucc

Eric Wieser (Oct 22 2023 at 08:01):

I guess we should change that to NeZero as in Yaël's example


Last updated: Dec 20 2023 at 11:08 UTC