Monte Carlo calculation of Pi in Scala

给你一囗甜甜゛ 提交于 2019-11-27 22:14:54

问题


Suppose I would like to calculate Pi with Monte Carlo simulation as an exercise.

I am writing a function, which picks a point in a square (0, 1), (1, 0) at random and tests if the point is inside the circle.

import scala.math._
import scala.util.Random

def circleTest() = {
  val (x, y) = (Random.nextDouble, Random.nextDouble)
  sqrt(x*x + y*y) <= 1
}

Then I am writing a function, which takes as arguments the test function and the number of trials and returns the fraction of the trials in which the test was found to be true.

def monteCarlo(trials: Int, test: () => Boolean) =
  (1 to trials).map(_ => if (test()) 1 else 0).sum * 1.0 / trials

... and I can calculate Pi

monteCarlo(100000, circleTest) * 4

Now I wonder if monteCarlo function can be improved. How would you write monteCarlo efficient and readable ?

For example, since the number of trials is large is it worth using a view or iterator instead of Range(1, trials) and reduce instead of map and sum ?


回答1:


Stream based version, for another alternative. I think this is quite clear.

def monteCarlo(trials: Int, test: () => Boolean) =
    Stream
      .continually(if (test()) 1.0 else 0.0)
      .take(trials)
      .sum / trials

(the sum isn't specialised for streams but the implementation (in TraversableOnce) just calls foldLeft that is specialised and "allows GC to collect along the way." So the .sum won't force the stream to be evaluated and so won't keep all the trials in memory at once)




回答2:


It's worth noting that Random.nextDouble is side-effecting—when you call it it changes the state of the random number generator. This may not be a concern to you, but since there are already five answers here I figure it won't hurt anything to add one that's purely functional.

First you'll need a random number generation monad implementation. Luckily NICTA provides a really nice one that's integrated with Scalaz. You can use it like this:

import com.nicta.rng._, scalaz._, Scalaz._

val pointInUnitSquare = Rng.choosedouble(0.0, 1.0) zip Rng.choosedouble(0.0, 1.0)

val insideCircle = pointInUnitSquare.map { case (x, y) => x * x + y * y <= 1 }

def mcPi(trials: Int): Rng[Double] =
  EphemeralStream.range(0, trials).foldLeftM(0) {
    case (acc, _) => insideCircle.map(_.fold(1, 0) + acc)
  }.map(_ / trials.toDouble * 4)

And then:

scala> val choosePi = mcPi(10000000)
choosePi: com.nicta.rng.Rng[Double] = com.nicta.rng.Rng$$anon$3@16dd554f

Nothing's been computed yet—we've just built up a computation that will generate our value randomly when executed. Let's just execute it on the spot in the IO monad for the sake of convenience:

scala> choosePi.run.unsafePerformIO
res0: Double = 3.1415628

This won't be the most performant solution, but it's good enough that it may not be a problem for many applications, and the referential transparency may be worth it.




回答3:


I see no problem with the following recursive version:

def monteCarlo(trials: Int, test: () => Boolean) = {
  def bool2double(b: Boolean) = if (b) 1.0d else 0.0d
  @scala.annotation.tailrec
  def recurse(n: Int, sum: Double): Double = 
    if (n <= 0) sum / trials
    else recurse(n - 1, sum + bool2double(test()))
  recurse(trials, 0.0d)
}



回答4:


And a foldLeft version, too:

def monteCarloFold(trials: Int, test: () => Boolean) = 
  (1 to trials).foldLeft(0.0d)((s,i) => s + (if (test()) 1.0d else 0.0d)) / trials

This is more memory efficient than the map version in the question.




回答5:


Using tail recursion might be an idea:

def recMonteCarlo(trials: Int, currentSum: Double, test:() => Boolean):Double = trials match {
  case 0 => currentSum
  case x => 
    val nextSum = currentSum + (if (test()) 1.0 else 0.0)
    recMonteCarlo(trials-1, nextSum, test)

def monteCarlo(trials: Int, test:() => Boolean) = {
  val monteSum = recMonteCarlo(trials, 0, test)
  monteSum / trials
}



回答6:


Using aggregate on a parallel collection, like this,

def monteCarlo(trials: Int, test: () => Boolean) = {
  val pr = (1 to trials).par
  val s = pr.aggregate(0)( (a,_) => a + (if (test()) 1 else 0), _ + _) 
  s * 4.0 / trials
}

where partial results are summed up in parallel with other test calculations.



来源:https://stackoverflow.com/questions/25647407/monte-carlo-calculation-of-pi-in-scala

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!