Monte Carlo Methods: Computation of Pi

Basics: variables, values, printing


In [1]:
Ntotal = 100
print(Ntotal)


100

In [51]:
Nin = 55.0
print(Ntotal/Nin)


1.8181818181818181

Random Numbers: Uniformly Distributed Random Numbers


In [52]:
import random
random.uniform(0.0,1.0)


Out[52]:
0.35946385256831836

In [53]:
random.uniform(0.0,1.0)


Out[53]:
0.4254536023197447

In [54]:
random.uniform(0.0,1.0)


Out[54]:
0.45041014466321794

In [55]:
random.uniform(0.0,1.0)


Out[55]:
0.1067779404137692

Generating "dots" - random x, random y, and r


In [56]:
import math
import random
Ntotal = 100
Nin = 0

x = random.uniform(0.0,1.0)
y = random.uniform(0.0,1.0)
r = math.sqrt(x**2 + y**2)

print(x)
print(y)
print(r)


0.05974571703564047
0.5787235838766186
0.5817993960456649

The range() sequence type

If you want to generate a series of numbers in a sequence, e.g. 1,2,3,4..., then you want the range() immutable sequence type (or its equivalent in some other Python library). See the example below. range() doesn't directly create a list of numbers; it is its own type in Python. To get a list from it, see below.


In [57]:
list_of_numbers=list(range(1,5))
print(list_of_numbers)

# Note that the list EXCLUDES the endpoint. If you want to get a list of 5 numbers, from 1-5, 
# then you need to extend the endpoint by 1:

list_of_numbers=list(range(1,6))
print(list_of_numbers)

# or this

list_of_numbers=list(range(0,5))
print(list_of_numbers)


[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[0, 1, 2, 3, 4]

Putting things together: "looping" 100 times and printing r each time

Let's put things together now. We can create a variable that stores the number of iterations ("loops") of the calculation we want to do. Let's call that Ntotal. Let's then loop 100 times and each time make a random x, random y, and from that compute $r=\sqrt{x^2+y^2}$.

Note that Python uses indentation to indicate a related block of code acting as a "subroutine" - a program within the program.


In [58]:
import math
Ntotal = 100
Nin = 0
for i in range(0,Ntotal):
    x = random.uniform(0.0,1.0)
    y = random.uniform(0.0,1.0)
    r = math.sqrt(x**2 + y**2)
    print("x=%f, y=%f, r=%f" % (x,y,r))


x=0.791567, y=0.333009, r=0.858762
x=0.992927, y=0.005934, r=0.992945
x=0.469626, y=0.819610, r=0.944622
x=0.416850, y=0.183726, r=0.455543
x=0.318550, y=0.766839, r=0.830371
x=0.432118, y=0.918300, r=1.014889
x=0.234147, y=0.911066, r=0.940674
x=0.470924, y=0.567224, r=0.737233
x=0.319811, y=0.416551, r=0.525160
x=0.308308, y=0.480111, r=0.570579
x=0.641958, y=0.908198, r=1.112175
x=0.423121, y=0.074430, r=0.429617
x=0.521205, y=0.304230, r=0.603498
x=0.896660, y=0.036615, r=0.897407
x=0.001064, y=0.598737, r=0.598738
x=0.949464, y=0.406963, r=1.033006
x=0.233593, y=0.115538, r=0.260605
x=0.603962, y=0.683370, r=0.912011
x=0.776504, y=0.248119, r=0.815182
x=0.865313, y=0.990933, r=1.315566
x=0.967289, y=0.298944, r=1.012431
x=0.645350, y=0.135341, r=0.659389
x=0.582214, y=0.480999, r=0.755204
x=0.645292, y=0.135795, r=0.659426
x=0.928328, y=0.835170, r=1.248720
x=0.660645, y=0.573594, r=0.874907
x=0.676551, y=0.663061, r=0.947297
x=0.061292, y=0.422757, r=0.427177
x=0.836637, y=0.356069, r=0.909256
x=0.654316, y=0.686324, r=0.948245
x=0.073818, y=0.470031, r=0.475792
x=0.165287, y=0.323111, r=0.362933
x=0.217669, y=0.913619, r=0.939191
x=0.658174, y=0.233332, r=0.698310
x=0.690408, y=0.036371, r=0.691366
x=0.462053, y=0.854387, r=0.971324
x=0.497483, y=0.218318, r=0.543279
x=0.180186, y=0.544419, r=0.573462
x=0.957969, y=0.429008, r=1.049643
x=0.795510, y=0.025465, r=0.795917
x=0.551435, y=0.714036, r=0.902180
x=0.446069, y=0.098077, r=0.456724
x=0.132402, y=0.473013, r=0.491194
x=0.870270, y=0.143476, r=0.882018
x=0.388756, y=0.320096, r=0.503580
x=0.733615, y=0.508464, r=0.892595
x=0.841823, y=0.076221, r=0.845266
x=0.386002, y=0.439506, r=0.584947
x=0.290895, y=0.795213, r=0.846749
x=0.517129, y=0.487043, r=0.710375
x=0.808254, y=0.586816, r=0.998813
x=0.848211, y=0.522392, r=0.996170
x=0.011347, y=0.313687, r=0.313892
x=0.541277, y=0.448020, r=0.702640
x=0.727471, y=0.259864, r=0.772492
x=0.882927, y=0.814090, r=1.200959
x=0.974732, y=0.991959, r=1.390714
x=0.544426, y=0.656826, r=0.853124
x=0.871165, y=0.948307, r=1.287717
x=0.767980, y=0.280870, r=0.817729
x=0.843898, y=0.351519, r=0.914183
x=0.382619, y=0.320762, r=0.499285
x=0.998235, y=0.901319, r=1.344934
x=0.120513, y=0.638654, r=0.649925
x=0.485058, y=0.160885, r=0.511043
x=0.636300, y=0.351582, r=0.726971
x=0.277267, y=0.120914, r=0.302485
x=0.756297, y=0.444439, r=0.877218
x=0.549390, y=0.381078, r=0.668617
x=0.283071, y=0.301148, r=0.413303
x=0.038511, y=0.878613, r=0.879456
x=0.244637, y=0.873252, r=0.906872
x=0.488953, y=0.834838, r=0.967486
x=0.006460, y=0.770381, r=0.770408
x=0.285199, y=0.792716, r=0.842459
x=0.989241, y=0.004285, r=0.989250
x=0.317365, y=0.098933, r=0.332428
x=0.969571, y=0.261713, r=1.004272
x=0.668673, y=0.123241, r=0.679935
x=0.737083, y=0.363940, r=0.822036
x=0.947875, y=0.811524, r=1.247813
x=0.521742, y=0.923659, r=1.060830
x=0.801828, y=0.648326, r=1.031142
x=0.137876, y=0.732300, r=0.745167
x=0.506840, y=0.600653, r=0.785921
x=0.385898, y=0.111785, r=0.401763
x=0.061165, y=0.949166, r=0.951134
x=0.459561, y=0.136102, r=0.479291
x=0.307478, y=0.729010, r=0.791201
x=0.097166, y=0.586099, r=0.594099
x=0.892814, y=0.470591, r=1.009243
x=0.611127, y=0.298190, r=0.679995
x=0.444214, y=0.136910, r=0.464833
x=0.170535, y=0.223613, r=0.281221
x=0.774163, y=0.218649, r=0.804447
x=0.966349, y=0.972007, r=1.370630
x=0.665899, y=0.711009, r=0.974143
x=0.873525, y=0.477837, r=0.995678
x=0.713081, y=0.650659, r=0.965320
x=0.520243, y=0.580109, r=0.779217

Monte Carlo - Accept/Reject

Now that we can make random "dots" in x,y and compute the radius (relative to 0,0) of each dot, let's employ "Accept/Reject" to see if something is in/on the circle or outside the circle. All we have to do is, for each $r$, test whether $r \le R$ or $r > R$. If the former, we have a dot in the circle - a hit! If the latter, then we have a dot out of the circle - a miss! We accept the hits and reject the misses. The total number of points define all the moves in the game, and the ratio of hits to the total will tell us about the area of the circle, and thus get us closer to $\pi$.


In [59]:
Ntotal = 100
Nin = 0
R = 1.0
for i in range(0,Ntotal):
    x = random.uniform(0.0,1.0)
    y = random.uniform(0.0,1.0)
    r = math.sqrt(x**2 + y**2)

    if r <= R:
        Nin = Nin + 1
        # alternatively, Nin += 1 (auto-increment by 1)

print("Number of dots: %d" % Ntotal)
print("Number of hits: %d" % Nin)
print("Number of misses: %d" % (Ntotal-Nin))
        
my_pi = 4.0*float(Nin)/float(Ntotal)
print("pi = %f" % (my_pi))


Number of dots: 100
Number of hits: 80
Number of misses: 20
pi = 3.200000

Precision in Monte Carlo Simulation

The number above is probably close to what you know as $\pi$, but likely not very precise. After all, we only threw 100 dots. We can increase precision by increasing the number of dots. In the code block below, feel free to play with Ntotal, trying different values. Observe how the computed value of $\pi$ changes. Would you say it's "closing in" on the value you know, or not? Try Ntotal at 1000, 5000, 10000, 50000, and 100000.

In the code block below, I have also added a computation of statistical error. The error should be a binomial error. Binomial errors occur when you have a bunch of things and you can classify them in two ways: as $A$ and $\bar{A}$ ("A" and "Not A"). In our case, they are hits or "not hits" (misses). So binomial errors apply. A binomial error is computed below.


In [60]:
Ntotal = 100
Nin = 0
R = 1.0
for i in range(0,Ntotal):
    x = random.uniform(0.0,1.0)
    y = random.uniform(0.0,1.0)
    r = math.sqrt(x**2 + y**2)

    if r <= R:
        Nin = Nin + 1
        # alternatively, Nin += 1 (auto-increment by 1)

my_pi = 4.0*float(Nin)/float(Ntotal)
my_pi_uncertainty = my_pi * math.sqrt(1.0/float(Nin) + 1.0/float(Ntotal))
print("pi = %.6f +/- %.6f (percent error= %.2f%%)" % (my_pi, my_pi_uncertainty, 100.0*my_pi_uncertainty/my_pi))


pi = 3.000000 +/- 0.458258 (percent error= 15.28%)