HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMajor

Monte Carlo pi calculation

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
montecalculationcarlo

Problem

In order to learn the basics of Monte Carlo I calculated pi with it.
I also wrote an explanation of the reasoning behind the code.

Down here you can see the circle with random points that I simulated in my code.

"""
        This programme calculates pi with Monte Carlo

Given a square and a circle inside it.
We have

Area_of_the_square = LENGTH ** 2
Area_of_the_circle = radius ** 2 * pi => (LENGTH ** 2) / 4 * pi

The circle is obviously smaller than the square.
We have the equation:

Area_of_the_square * number_less_than_one == Area_of_the_circle

This programme is going to put a big number of points inside the square
(I suggest TIMES_TO_REPEAT = 10**5).

It will then count how many of them are inside the circle,

number_less_than_one = points_inside_the_circle / total_points

After doing some simple math with this formula:

Area_of_the_square * number_less_than_one == Area_of_the_circle

we get that

pi = number_less_than_one * 4

NOTE: This method is deadly slow and quite complicated,
I made this programme just in order to learn.

"""
import random

TIMES_TO_REPEAT = 10**5
LENGTH = 10**5
CENTER = [LENGTH/2,LENGTH/2]

def in_circle(point):
    x = point[0]
    y = point[1]
    center_x = CENTER[0]
    center_y = CENTER[1]
    radius = LENGTH/2
    return (x - center_x)**2 + (y - center_y)**2 < radius**2

count = inside_count = 0
for i in range(TIMES_TO_REPEAT):
    point = random.randint(1,LENGTH),random.randint(1,LENGTH)
    if in_circle(point):
        inside_count += 1
    count += 1

pi = (inside_count / count) * 4

print(pi)

Solution

Your code seems to be working. Here are a few things to make it better.

Tuple unpacking

x = point[0]
y = point[1]


can be written :

x, y = point


Remove what is not required

You maintain a count variable but you already know the count : it is TIMES_TO_REPEAT.

Variable name

TIMES_TO_REPEAT does not seem to be such a good name. NB_POINTS would probably be better.

Removing the counting logic

Using iterators and sum, you can remove the logic to count points :

inside_count = sum(1 for i in range(NB_POINTS) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))


Underscore for throwaway values

In your iteration, you don't really care about the value of i. It is quite a common thing to call it _ (more about this).

Move things to a function

It makes things easier to test

def compute_pi(nb_it):
    inside_count = sum(1 for _ in range(nb_it) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))
    return (inside_count / nb_it) * 4


Add an if main guard

It is quite common to define functions/values/classes/whatever from your python file but to use them only behind an if main guard to make things easier to reuse via module imports for instance.

Oops

Currently, changing the value of LENGTH seems to be changing the result. This is probably wrong.

At this point, my code looks like :

import random

NB_POINTS = 10**5
LENGTH = 10**5
CENTER = [LENGTH/2,LENGTH/2]

def in_circle(point):
    x,y = point
    center_x, center_y = CENTER
    radius = LENGTH/2
    return (x - center_x)**2 + (y - center_y)**2 < radius**2

def compute_pi(nb_it):
    inside_count = sum(1 for _ in range(nb_it) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))
    return (inside_count / nb_it) * 4

if __name__ == "__main__":
    print(compute_pi(NB_POINTS))


Using the right type and the right values

In order to solve the problem above and make things easier, you could change a bit the referential you are using.

Instead of using a circle of radius R (equal to length/2) and with center (R, R) and picking integer values in square [1, R] [1, R] maybe you could consider a circle of radius R and with center 0 and pick floating values in [-R, +R]. Even better, you could focus on the corner of the circle by considering points in [0, R][0, R]. Finally, R is not really important here, you could pick 1.

Then the code becomes :

import random

NB_POINTS = 10**5

def in_circle(x, y):
    return x**2 + y**2 < 1

def compute_pi(nb_it):
    inside_count = sum(1 for _ in range(nb_it) if in_circle(random.random(),random.random()))
    return (inside_count / nb_it) * 4

if __name__ == "__main__":
    print(compute_pi(NB_POINTS))


And then, this can be simplified.

import random

NB_POINTS = 10**5

def compute_pi(nb_it):
    return 4 * sum(1 for _ in range(nb_it) if random.random()**2 + random.random()**2 < 1) / nb_it

if __name__ == "__main__":
    print(compute_pi(NB_POINTS))

Code Snippets

x = point[0]
y = point[1]
x, y = point
inside_count = sum(1 for i in range(NB_POINTS) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))
def compute_pi(nb_it):
    inside_count = sum(1 for _ in range(nb_it) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))
    return (inside_count / nb_it) * 4
import random

NB_POINTS = 10**5
LENGTH = 10**5
CENTER = [LENGTH/2,LENGTH/2]

def in_circle(point):
    x,y = point
    center_x, center_y = CENTER
    radius = LENGTH/2
    return (x - center_x)**2 + (y - center_y)**2 < radius**2

def compute_pi(nb_it):
    inside_count = sum(1 for _ in range(nb_it) if in_circle((random.randint(1,LENGTH),random.randint(1,LENGTH))))
    return (inside_count / nb_it) * 4

if __name__ == "__main__":
    print(compute_pi(NB_POINTS))

Context

StackExchange Code Review Q#69370, answer score: 30

Revisions (0)

No revisions yet.