Estimating Pi With Incanter and Monte Carlo Methods
Of all the things John von Neumann invented (including the Minimax theorem behind my Tic Tac Toe AI, and the architecture of the computer it runs on), Monte Carlo simulation is one of my favorites. Unlike some of his other breakthroughs, the idea behind Monte Carlo simulation is simple: use probability and computation to estimate solutions to hard problems.
Think of the craziest integral you’ve ever solved, and the sheaves of notebook paper spent working out the area under that ridiculous curve. The Monte Carlo approach is a clever hack: tack a graph of the function to a dartboard, and start throwing darts at random. As more and more darts hit the board, the ratio of darts that land under the curve to total darts thrown will approximate the proportion of the dartboard under the curve. Multiply this ratio by the area of the dartboard, and you’ve computed the integral. As a student whose favorite fourth grade problem solving strategy was “guess and check,” and whose favorite tenth grade problem solving strategy was “plug it into your TI-83,” the idea has a lot of appeal.
Wikipedia has a great example of estimating the value of Pi with a Monte Carlo simulation. I wrote a similar simulation in Python a couple months ago, but wanted to check out Clojure’s Incanter library for statistical computing and try my hand at a TDD solution.
To start, you’ll want the speclj
test framework for Clojure, which uses Rspec-like syntax and provides
a helpful autorunner. Create a new Leiningen project and add it as a dev
dependency and plugin in project.clj
. Make sure you set up your spec
directory as described in the
documentation. You’ll also want to add Incanter to your project
dependencies. Here’s my final project.clj
:
1 2 3 4 5 6 7 |
|
To start, we’ll need a way to tell whether a given point is inside the circle. Here’s a simple test:
1 2 3 4 5 6 7 8 9 10 |
|
Fire up the testrunner with lein spec -a
, and you’ll see an
exception, since the function doesn’t exist. Time to add it to
core.clj
:
1 2 3 4 5 6 7 8 9 10 |
|
The let
binding pulls x and y coordinates out of a vector
representing a point. If x squared plus y squared are less
than 1, the point is inside the circle. Save and watch the
autorunner turn green.
Next, we need a way to generate points at random, with x and y values between 0 and 1. Here’s a test:
1 2 3 4 5 6 7 8 |
|
And a dead-simple implementation (clojure.core/rand
conveniently
generates random floats between 0 and 1).:
1 2 |
|
We need a lot of random points, so we’d better add a function to generate them. Here’s a test and a function that passes:
1 2 3 |
|
1 2 |
|
Once we’ve generated lots of points, we’ll want to know how many lie inside the circle. This test is a little more complicated, since it requires some mock points to test against:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
But a passing solution is as easy as using in-circle?
as a filter:
1 2 |
|
In order to plot the points, we’ll need to sort them into groups. A
map should do the trick. Let’s also take this chance to
pull inside
, outside
, and points
out of the let
binding and define them as vars so we can reuse them.
Here’s the result after a quick refactor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
This code passes, but it’s not as clear as it could be:
1 2 3 |
|
I’d like to rename the inline function outside-circle?
. So let’s add
a test:
1 2 3 |
|
Write the function:
1 2 |
|
And refactor sort-points
once speclj gives us the green light:
1 2 3 |
|
It shouldn’t be hard to convert sorted points into a ratio. Here’s a test and solution:
1 2 3 |
|
Make sure you return a float instead of a rational:
1 2 3 |
|
And now we’re just a step away from estimating Pi. We’re considering a quarter circle inside a unit square. The area of the square is 1, and the quarter circle 1/4 * Pi. So multiplying by four gives us our estimate:
1 2 3 4 5 6 7 8 |
|
The second part of this test is a little tricky. Our estimate is probabilistic, but with enough points it should almost always generate an estimate within range. Passing the test is easy:
1 2 |
|
All that’s left is to create an Incanter chart. We’ll start by plotting the circle with Incanter. First, we need to write a function. Here’s a test for points on 1 = x2 + y2:
1 2 3 4 5 6 7 |
|
To write the function, make sure you refer incanter.core/sqrt
to
your namespace:
1 2 3 4 |
|
At this point, writing tests gets a little hairy. The JFreeChart
object on which Incanter plots are based doesn’t
offer much
in the way of public fields to test against. But we
can at least check that the function returns a chart:
1 2 3 4 |
|
Refer incanter.charts/function-plot
to plot the function:
1 2 3 4 5 6 |
|
Running (view (draw-circle))
from the REPL should produce a chart
like this:
The last step is to add the points and some annotations to the Incanter plot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
|
Here’s a plot and estimate with 100 points:
With 10000:
And with 100000:
A gist with all the code from this post is available here.