Monte Carlo Pi
Mix.install([
{:vega_lite, "~> 0.1.6"},
{:kino_vega_lite, "~> 0.1.10"}
])
Introduction
This notebook uses Monte Carlo simulation to approximate π.
By placing 2d points randomly witnin -1 to 1 on each axis and looking at the fraction that land within the unit circle, one can approximate π. The more points, the better the approximation.
Given $A{sqr}$ being the area of the square and $A{circ}$ being the area of the unit circle, we have that
$ \frac{A{circ}}{A{sqr}} = \frac{\pi r^2}{4r^2} = \frac{\pi}{4} $
or
$ \pi = 4 \cdot \frac{A{circ}}{A{sqr}} $
A longer description can be found here.
Note: The outcome is determined by chance and the quality of the employed random number generator.
Configuration
step_count = 2_000
Simulation
Produce coordinates:
samples =
1..step_count
|> Enum.map(fn _ -> %{x: 2 * :rand.uniform_real() - 1, y: 2 * :rand.uniform_real() - 1} end)
Map coordinates to whether they are inside of the unit circle:
outcomes =
samples
|> Enum.map(fn sample -> :math.sqrt(sample.x * sample.x + sample.y * sample.y) < 1 end)
Calculate the series of approximations:
approximation =
outcomes
|> List.foldl([], fn outcome, acc ->
{count, inside, outside} =
case acc do
[last | _] -> {Map.get(last, "count"), Map.get(last, "inside"), Map.get(last, "outside")}
_ -> {0, 0, 0}
end
{inside, outside} =
case outcome do
true -> {inside + 1, outside}
false -> {inside, outside + 1}
end
entry = %{
"count" => count + 1,
"inside" => inside,
"outside" => outside,
"pi" => inside / (count + 1) * 4,
"legend" => "simulated"
}
[entry | acc]
end)
|> Enum.reverse()
Result
The final approximation is:
approximation
|> Enum.at(-1)
|> (fn entry -> Map.get(entry, "pi") end).()
Simulation Set
defmodule Canvas do
@dim 320
@unit 256
@point_r 2
@gap 8
@gridcolor "#999999"
def render(samples) do
grid_lines = grid()
circle_lines = circle()
point_lines = point(samples)
"""
#{grid_lines}
#{circle_lines}
#{point_lines}
"""
|> Kino.Image.new(:svg)
end
defp grid() do
dashes = "stroke-dasharray=\"3\""
"""
1.0
0.5
0.0
-0.5
-1.0
-1.0
-0.5
0.0
0.5
1.0
"""
end
defp circle() do
"""
"""
end
defp point(samples) do
samples
|> Enum.map(fn sample ->
color =
if :math.sqrt(sample.x * sample.x + sample.y * sample.y) > 1 do
"#ff0000"
else
"#0000ff"
end
"""
"""
end)
|> Enum.join("\n")
end
end
Canvas.render(samples)
Path of Approximation
alias VegaLite, as: Vl
correct = [
%{"legend" => "correct", "pi" => :math.pi(), "count" => 1},
%{"legend" => "correct", "pi" => :math.pi(), "count" => step_count}
]
Vl.new(width: 400, height: 300)
|> Vl.data_from_values(correct ++ approximation)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "count", type: :quantitative, title: "Step count")
|> Vl.encode_field(:y, "pi", type: :quantitative, title: "Value")
|> Vl.encode_field(:color, "legend", type: :nominal, title: "Legend")
Final Words
In this example, the full unit square is used as the space the randomly positioned points are placed in. We could have restricted this further to the first quadrant.