NICTA / rng

Pure-functional random value generation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support for other distribution, e.g. Normal, Poisson

beloglazov opened this issue · comments

It's just an enhancement suggestion: it would be nice to have support for other distributions in addition to the uniform distribution, such as the normal and Poisson distributions.

Thanks for you great work!

I'm trying to implement Knuth's algorithm for generating Poisson samples (http://en.wikipedia.org/wiki/Poisson_distribution#Generating_Poisson-distributed_random_variables) and came up with the following:

    def poissonSample(lambda: Double): Rng[Int] = {
      val L = math.exp(-lambda)

      @annotation.tailrec
      def loop(p: Double, k: Int): Rng[Int] = {          
        for {                                         
          u <- choosedouble(0, 1) 
        } yield {
          if (p > L) loop(p * u, k + 1)   
          else k 
        }         
      }                                             

      loop(1.0, 0).map(_ - 1)                        
    }

This doesn't compile, as the internal call to loop is expected to return Int instead of Rng[Int]. I'm really a beginner in functional programming, and can't figure out at the moment how to solve this. I'd be happy to get any suggestions. Thanks!

Try:

def poissonSample(lambda: Double): Rng[Int] = {
  val L = math.exp(-lambda)

  @annotation.tailrec
  def loop(p: Double, k: Int): Rng[Int] = {          
    choosedouble(0, 1).flatMap { u =>                                         
      if (p > L) loop(p * u, k + 1)   
      else       Rng.constant(k) 
    }         
  }                                             

  loop(1.0, 0).map(_ - 1)                        
}

Thanks! That fixed the type issue. However, loop is not tail-recursive:

[error] could not optimize @tailrec annotated method loop: it contains a recursive call not in tail position
[error]       choosedouble(0, 1).flatMap(u =>

Is there any way to make it tail-recursive?

If you are interested, here is an implementation of the Box-Muller method for generating normally distributed numbers:

def normalSample(mean: Double, variance: Double): Rng[Double] = {
  // Box–Muller method
  choosedouble(0, 1).fill(2) map { u =>
    val z = math.sqrt(-2 * math.log(u(0))) * math.cos(2 * math.Pi * u(1))
    mean + math.sqrt(variance) * z
  }
}

@beloglazov I don't think it needs to be tail recursive. Rng will take care of that for you internally.

@markhibberd Great, thanks for letting me know! I need to dig more into the Rng implementation to understand that.