斯卡拉Pi的蒙特卡罗计算

时间:2014-09-03 14:48:52

标签: scala montecarlo

假设我想用蒙特卡罗模拟计算Pi作为练习。

我正在编写一个函数,它随机选取一个正方形(0, 1), (1, 0)中的一个点并测试该点是否在圆内。

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

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

然后我正在编写一个函数,它将测试函数和试验次数作为参数,并返回测试结果为真的试验部分。

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

......我可以计算Pi

monteCarlo(100000, circleTest) * 4

现在我想知道monteCarlo功能是否可以改进。你如何写monteCarlo有效且可读?

例如,由于试用次数很多,因此值得使用viewiterator代替Range(1, trials)reduce代替map sum

6 个答案:

答案 0 :(得分:8)

值得注意的是Random.nextDouble是副作用 - 当你调用它时它会改变随机数生成器的状态。这对你来说可能不是一个问题,但由于这里已有五个答案,我认为添加一个纯粹功能性的东西不会有任何损害。

首先,您需要一个随机数生成monad实现。幸运的是,NICTA提供了与Scalaz集成的a really nice one。您可以像这样使用它:

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)

然后:

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

还没有计算出来 - 我们刚刚建立了一个计算,它会在执行时随机生成我们的值。为方便起见,让我们在IO monad中的现场执行:

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

这不是最高性能的解决方案,但它足够好,对许多应用程序来说可能不是问题,参考透明度可能是值得的。

答案 1 :(得分:3)

基于流的版本,另一种选择。我认为这很清楚。

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

sum并非专门针对流,但实施(在TraversableOnce中)只调用foldLeft专用的&#34;允许GC沿途收集。&# 34;因此.sum不会强制对流进行评估,因此不会立即将所有试验保留在内存中)

答案 2 :(得分:2)

我发现以下递归版本没有问题:

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)
}

答案 3 :(得分:2)

还有一个foldLeft版本:

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

这比问题中的map版本更有效。

答案 4 :(得分:1)

使用尾递归可能是一个想法:

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
}

答案 5 :(得分:1)

在并行集合上使用aggregate,如下所示,

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
}

其中部分结果与其他测试计算并行计算。