This is a visual explanation of how Monte Carlo methods can be used to estimate the value of Pi. It is a circle inscribed within a square. Click the button to simulate the dropping of 1000 needles randomly across the square. An estimate of Pi is calculated based on the proportion of points that fall within the circle. Repeating this simulation many times and averaging the results will yield a fairly accurate approximation of the true value of Pi.
xxxxxxxxxx
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
form {
position: absolute;
top: 400px;
left: 1em;
}
table {
position: absolute;
top: 1em;
left: 550px;
}
td, th {
padding: 1px 4px;
}
path {
fill: none;
stroke: #333;
stroke-width: 1px;
}
rect {
fill: none;
stroke: #333;
stroke-width: 1px;
}
</style>
<body>
<div id="piSim"></div>
<table>
<thead>
<tr>
<th>Simulation</th>
<th>Estimate</th>
</tr>
</thead>
<tbody id="results">
</tbody>
<tfoot>
<td>Avg.</td>
<td id="average"></td>
</tfoot>
</table>
<div>
<form>
<input type="button" name="run" id="start-sim" value="Run Simulation" onclick="runSim()">
</form>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 20, right: 10, bottom: 30, left: 10};
var numSamples = 0,
numSamplesMax = 1000,
estimates = [];
var simCounter = 0;
var width = 350,
height = width,
radius = width/2;
var sample = uniformRandomSampler(width, height, numSamplesMax);
var unitCircle = d3.svg.arc()
.innerRadius(0)
.outerRadius(radius)
.startAngle(0)
.endAngle(2* Math.PI);
var svg = d3.select("#piSim").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("rect")
.attr("x", -width / 2)
.attr("y", -height / 2)
.attr("width", width)
.attr("height", height);
svg.append("path")
.attr("d", unitCircle);
var needles = svg.append("g");
var tbody = d3.select("#results");
function removeNeedles() {
d3.selectAll("circle").remove();
numSamples = 0;
}
function dropNeedles() {
var inCircle = 0;
d3.timer(function () {
for (var i = 0; i < 25; ++i) {
var s = sample();
if (!s) {
postResult(4 * (inCircle / numSamplesMax));
return true;
}
var length = computeLength(s);
if (length >= radius) {
s[2] = "#d7191c";
} else {
s[2] = "#2c7bb6";
inCircle++;
}
needles.append("circle")
.attr("cx", s[0])
.attr("cy", s[1])
.attr("r", 0)
.attr("fill", s[2])
.transition()
.attr("r", 2);
}
});
}
function computeLength(s) {
return Math.sqrt(Math.pow(s[0], 2) + Math.pow(s[1], 2));
}
function postResult(result) {
var formatter = d3.format(".5n");
estimates.push(result);
var average = estimates.reduce(function (a, b) {
return a + b;
}) / estimates.length;
var row = tbody.append("tr");
row.append("td")
.text(simCounter);
row.append("td")
.text(formatter(result));
d3.select("#average").text(formatter(average));
}
function runSim() {
removeNeedles();
simCounter++;
dropNeedles();
}
function randomCoordinate(min, max) {
return Math.random() * (max - min) + min;
}
// based on @mbostock: https://bl.ocks.org/mbostock/fe3f75700e70416e37cd
function uniformRandomSampler(width, height, numSamplesMax) {
return function () {
if (++numSamples > numSamplesMax) return;
return [randomCoordinate(-width/2, width/2), randomCoordinate(-height/2, height/2)];
};
}
</script>
</body>
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js