Monte Carlo simulation of PI, in Python - python

I know there are countless ways to do this that are better than my way, but I really want to figure out what I'm doing wrong here.
I'm trying to get to the value of PI doing a monte carlo simulation,but I don't get what I'm doing wrong
PI Monte Carlo
import random
import pylab
def MonteCarloPI(numtries):
circle = 0
for i in range (numtries):
a = (float(random.random())+1)**0.5
b = float(random.random())*a
if b <= 1:
circle += 1
rapportoAree = (circle/numtries)
return rapportoAree*4
print(MonteCarloPI(1000))
Edit
The point of this is to find the area of the circle without using x and y coordinates.
The idea is that if 'a' is a random number between 1 and sqrt2 and then 'b' is a random number between 0 and 'a' In theory all values of 'b' that are <1 should be inside the circle, so I should have the number of points in the circle and the number of total points, that way I can calculate the fraction that is proportional to PI
I hope this image can explain better than my words

The problem is that your distribution of (a, b)values is not equivalent to the uniform distribution of (x, y) values (Cartesian coordinates). Your implementation uses something akin to polar coordinates, with a uniform distribution of the radius (over the range of a square) and a quadratically skewed distribution of the reference angle.
As a result, you generate more points within the circle than is proper for the measurement you want. To fix this, you'll need to alter your formulas to account for the polar transformations you've introduced.
-- a --
Your posted formula:
a = (float(random.random())+1)**0.5
Does not yield a uniform distribution over the range (1.0, sqrt(2.0)). It's more dense the higher you go. For comparison, let's extend this to the range (1, 10) -- we'll choose a random integer 0-99.
a = (random.randint(0, 99) + 1)**0.5
All 100 integers are equally likely, but let's look at the resulting distribution. At the bottom, the results in the range [1.0, 2.0) will be hit with only integers 1 and 2. At the top, results in the range [9.0, 10.0) -- the same size -- will be hit with integers 80 through 99 (a total of 20 values). Thus, 9 <= a < 10 is ten times as likely as 1 <= a < 2.
Your formula suffers from a lesser version of this problem. You can correct this by generating numbers in a uniform distribution over [1.0, sqrt(2.0)].
-- b --
To illustrate the problem with b, draw some equally-spaced (according to the a values you generate) line segments. What is the total length of these segments? The "inside" component of each segment is 1.0, but note that the points represented are much more dense near the origin -- inside the circle -- than outside.
To calculate b, you need to undo the center bias of your polar coordinates. I will leave this derivation as an exercise for the student ... should you want to continue this line of attack.

Three different methods using Cartesian coordinates.
import random
import math
def MonteCarloPI(numtries):
inside = 0
for i in range (numtries):
x2 = random.random()**2
y2 = random.random()**2
if (math.sqrt(x2 + y2) < 1.0):
inside += 1
return (float(inside) / numtries) * 4
outputs:
>>> print(MonteCarloPI(1000))
3.14
and
>>> print(MonteCarloPI(100000))
3.14476
you can even clean up the function using the built in math.hypot which calculates euclidean norms:
import random
from math import hypot
def MonteCarloPI(numtries):
inside = 0
for i in range (numtries):
if hypot(random.random(), random.random()) < 1:
inside += 1
return (float(inside) / numtries) * 4
>>> print(MonteCarloPI(100000))
3.14364
or numpy one liner
import numpy as np
def MonteCarloPI(n):
return np.sum(np.random.rand(n)**2+np.random.rand(n)**2<1)/float(n)*4
outputs:
>>> print(MonteCarloPI(100000))
3.1462

Related

random numer generation except inside of the circle in python

I want to generate 750 random number in 2D data x (0,1) and y (0,1)
X1 = np.random.random((750,2))
However, I want to make sure I don't have any value in the circular region such as
I can remove the value, but I want to fix the number of the random number to be 750. What would be the best way to generate such list?
import random
points = []
radius = 1
num_points=1500
index = 0
max_coord = 750
while index < num_points:
x=random.random()*max_coord
y=random.random()*max_coord
if x**2 + y**2 > radius**2:
points = points + [[x,y]]
index = index + 1
print(points)
Change max_coor to obtain bigger numbers. This only gives you 1500 points outside the circle of the given radius=1. Each coordinate varies between 0 and 750
you can just apply pythagorean's theorem. Here is my code to do that
import numpy as np
import matplotlib.pyplot as plt
dist = 0.2
X1 = np.random.random((750,2))
X2 = [x for x in X1 if (x[0]-0.5)**2 + (x[1]-0.5)**2 > dist**2] # filter values that are to close to center
# for plotting
x = [x[0] for x in X2]
y = [y[1] for y in X2]
plt.scatter(x,y)
plt.show()
You can do this with a well-known technique called rejection sampling. Generate candidate values and see if they meet your constraints. If so, accept them, otherwise reject and repeat. With 2-d sampling, the probability of acceptance on a given trial is P{Accept} = Area(acceptance region) / Area(generating region). Repetitions due to rejection are independent, so the number of trials has a geometric distribution with parameter p = P{Accept}
and Expected # trials = 1 / P{Accept}. For example, if the rejection region is a circle with radius 1/2 centered in a unit square, P{Accept} = (4 - Pi)/4 andon average it will take 4/(4 - Pi) (approximately 4.66) attempts per value generated. Obviously this won't work if the radius is >= sqrt(2)/2, and gets expensive as you approach that limit.
Note that the rejection circle does not need to be centered in the square, you just reject any candidate that is less than radius away from the circle's center.
Here's the code:
import numpy as np
def generate_pt(exclusion_radius):
while True:
candidate = np.random.sample(2)
# The following assumes the rejection circle is centered at (0.5, 0.5).
# Adjust by subtracting individual x/y coordinates for the center of
# the circle if you don't want it centered. Acceptance/rejection
# distance is determined by Pythagorean theorem.
if np.sum(np.square(candidate - 0.5)) >= exclusion_radius * exclusion_radius:
return candidate
# Generate 750 points outside the centered circle of radius 0.3.
data = np.array([generate_pt(0.3) for _ in range(750)])

Fit a line segment to a set of points

I'm trying to fit a line segment to a set of points but I have trouble finding an algorithm for it. I have a 2D line segment L and a set of 2D points C. L can be represented in any suitable way (I don't care), like support and definition vector, two points, a linear equation with left and right bound, ... The only important thing is that the line has a beginning and an end, so it's not infinite.
I want to fit L in C, so that the sum of all distances of c to L (where c is a point in C) is minimized. This is a least squares problem but I (think) cannot use polynmoial fitting, because L is only a segment. My mathematical knowledge in that area is a bit lacking so any hints on further reading would be appreciated aswell.
Here is an illustration of my problem:
The orange line should be fitted to the blue points so that the sum of squares of distances of each point to the line is minimal. I don't mind if the solution is in a different language or not code at all, as long as I can extract an algorithm from it.
Since this is more of a mathematical question I'm not sure if it's ok for SO or should be moved to cross validated or math exchange.
This solution is relatively similar to one already posted here, but I think is slightly more efficient, elegant and understandable, which is why I posted it despite the similarity.
As was already written, the min(max(...)) formulation makes it hard to solve this problem analytically, which is why scipy.optimize fits well.
The solution is based on the mathematical formulation for distance between a point and a finite line segment outlined in https://math.stackexchange.com/questions/330269/the-distance-from-a-point-to-a-line-segment
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize, NonlinearConstraint
def calc_distance_from_point_set(v_):
#v_ is accepted as 1d array to make easier with scipy.optimize
#Reshape into two points
v = (v_[:2].reshape(2, 1), v_[2:].reshape(2, 1))
#Calculate t* for s(t*) = v_0 + t*(v_1-v_0), for the line segment w.r.t each point
t_star_matrix = np.minimum(np.maximum(np.matmul(P-v[0].T, v[1]-v[0]) / np.linalg.norm(v[1]-v[0])**2, 0), 1)
#Calculate s(t*)
s_t_star_matrix = v[0]+((t_star_matrix.ravel())*(v[1]-v[0]))
#Take distance between all points and respective point on segment
distance_from_every_point = np.linalg.norm(P.T -s_t_star_matrix, axis=0)
return np.sum(distance_from_every_point)
if __name__ == '__main__':
#Random points from bounding box
box_1 = np.random.uniform(-5, 5, 20)
box_2 = np.random.uniform(-5, 5, 20)
P = np.stack([box_1, box_2], axis=1)
segment_length = 3
segment_length_constraint = NonlinearConstraint(fun=lambda x: np.linalg.norm(np.array([x[0], x[1]]) - np.array([x[2] ,x[3]])), lb=[segment_length], ub=[segment_length])
point = minimize(calc_distance_from_point_set, (0.0,-.0,1.0,1.0), options={'maxiter': 100, 'disp': True},constraints=segment_length_constraint).x
plt.scatter(box_1, box_2)
plt.plot([point[0], point[2]], [point[1], point[3]])
Example result:
Here is a proposition in python. The distance between the points and the line is computed based on the approach proposed here: Fit a line segment to a set of points
The fact that the segment has a finite length, which impose the usage of min and max function, or if tests to see whether we have to use perpendicular distance or distance to one of the end points, makes really difficult (impossible?) to get an analytic solution.
The proposed solution will thus use optimization algorithm to approach the best solution. It uses scipy.optimize.minimize, see: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
Since the segment length is fixed, we have only three degrees of freedom. In the proposed solution I use x and y coordinate of the starting segment point and segment slope as free parameters. I use getCoordinates function to get starting and ending point of the segment from these 3 parameters and the length.
import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
import math as m
from scipy.spatial import distance
# Plot the points and the segment
def plotFunction(points,x1,x2):
'Plotting function for plane and iterations'
plt.plot(points[:,0],points[:,1],'ro')
plt.plot([x1[0],x2[0]],[x1[1],x2[1]])
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.show()
# Get the sum of the distance between all the points and the segment
# The segment is defined by guess and length were:
# guess[0]=x coordinate of the starting point
# guess[1]=y coordinate of the starting point
# guess[2]=slope
# Since distance is always >0 no need to use root mean square values
def getDist(guess,points,length):
start_pt=np.array([guess[0],guess[1]])
slope=guess[2]
[x1,x2]=getCoordinates(start_pt,slope,length)
total_dist=0
# Loop over each points to get the distance between the point and the segment
for pt in points:
total_dist+=minimum_distance(x1,x2,pt,length)
return(total_dist)
# Return minimum distance between line segment x1-x2 and point pt
# Adapted from https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
def minimum_distance(x1, x2, pt,length):
length2 = length**2 # i.e. |x1-x2|^2 - avoid a sqrt, we use length that we already know to avoid re-computation
if length2 == 0.0:
return distance.euclidean(p, v);
# Consider the line extending the segment, parameterized as x1 + t (x2 - x1).
# We find projection of point p onto the line.
# It falls where t = [(pt-x1) . (x2-x1)] / |x2-x1|^2
# We clamp t from [0,1] to handle points outside the segment vw.
t = max(0, min(1, np.dot(pt - x1, x2 - x1) / length2));
projection = x1 + t * (x2 - x1); # Projection falls on the segment
return distance.euclidean(pt, projection);
# Get coordinates of start and end point of the segment from start_pt,
# slope and length, obtained by solving slope=dy/dx, dx^2+dy^2=length
def getCoordinates(start_pt,slope,length):
x1=start_pt
dx=length/m.sqrt(slope**2+1)
dy=slope*dx
x2=start_pt+np.array([dx,dy])
return [x1,x2]
if __name__ == '__main__':
# Generate random points
num_points=20
points=np.random.rand(num_points,2)
# Starting position
length=0.5
start_pt=np.array([0.25,0.5])
slope=0
#Use scipy.optimize, minimize to find the best start_pt and slope combination
res = minimize(getDist, x0=[start_pt[0],start_pt[1],slope], args=(points,length), method="Nelder-Mead")
# Retreive best parameters
start_pt=np.array([res.x[0],res.x[1]])
slope=res.x[2]
[x1,x2]=getCoordinates(start_pt,slope,length)
print("\n** The best segment found is defined by:")
print("\t** start_pt:\t",x1)
print("\t** end_pt:\t",x2)
print("\t** slope:\t",slope)
print("** The total distance is:",getDist([x1[0],x2[1],slope],points,length),"\n")
# Plot results
plotFunction(points,x1,x2)

Python : Generate normal distribution in the order of the bell

I want to generate normal distribution in the order of the bell.
I used this code to generate the numbers:
import numpy as np
mu,sigma,n = 0.,1.,1000
def normal(x,mu,sigma):
return ( 2.*np.pi*sigma**2. )**-.5 * np.exp( -.5 * (x-mu)**2. / sigma**2. )
x = np.random.normal(mu,sigma,n) #generate random list of points from normal distribution
y = normal(x,mu,sigma) #evaluate the probability density at each point
x,y = x[np.argsort(y)],np.sort(y) #sort according to the probability density
which is a code proposed in : Generating normal distribution in order python, numpy
but the numbers are not following the bell form.
Any ideas?
Thank you very much
A couple of things you are confusing.
random.normal draws n numbers randomly from a bell curve
So you have a 1000 numbers, each distinct, all drawn from the curve. To recreate the curve, you need to apply some binning. The amount of points in each bin will recreate the curve (just a single point by itself can hardly represent a probability). Using some extensive binning on your x vector of only a 1000 points:
h,hx=np.histogram(x,bins=50)
and plotting h as a function of hx (so I group your thousand numbers into 50 bins, the y axis will show the amount of points in the bins:
Now we can see x was drawn from a bell distribution - the chance to fall in the center bin is determined by the Gaussian. This is a sampling, so each point may vary a bit of course - the more points you use and the finer binning and the better it will be (smoother).
y = normal(x,mu,sigma)
This just evaluates the Gaussian at any given x, so really, supply normal with any list of numbers around your mean (mu) and it will calculate the bell curve exactly (the exact probability). Plotting your y against x (Doesn't matter that your x is Gaussian itself, but it's a 1000 points around the mean, so it can recreate the functions):
See how smooth that is? That's because it's not a sampling, it's an exact calculation of the function. You could have used just any 1000 points around 0 and it would have looked just as good.
Your code works just fine.
import numpy as np
import matplotlib.pyplot as plt
mu,sigma,n = 0.,1.,1000
def normal(x,mu,sigma):
return ( 2.*np.pi*sigma**2. )**-.5 * np.exp( -.5 * (x-mu)**2. / sigma**2. )
x = np.random.normal(mu,sigma,n)
y = normal(x,mu,sigma)
plt.plot(x,y)

Generate random xy values within two-dimensional circular radius?

I have some points that are located in the same place, with WGS84 latlngs, and I want to 'jitter' them randomly so that they don't overlap.
Right now I'm using this crude method, which jitters them within a square:
r['latitude'] = float(r['latitude']) + random.uniform(-0.0005, 0.0005)
r['longitude'] = float(r['longitude']) + random.uniform(-0.0005, 0.0005)
How could I adapt this to jitter them randomly within a circle?
I guess I want a product x*y = 0.001 where x and y are random values. But I have absolutely no idea how to generate this!
(I realise that really I should use something like this to account for the curvature of the earth's surface, but in practice a simple circle is probably fine :) )
One simple way to generate random samples within a circle is to just generate square samples as you are, and then reject the ones that fall outside the circle.
The basic idea is, you generate a vector with x = radius of circle y = 0.
You then rotate the vector by a random angle between 0 and 360, or 0 to 2 pi radians.
You then apply this displacement vector and you have your random jitter in a circle.
An example from one of my scripts:
def get_randrad(pos, radius):
radius = random() * radius
angle = random() * 2 * pi
return (int(pos[0] + radius * cos(angle)),
int(pos[1] + radius * sin(angle)))
pos beeing the target location and radius beeing the "jitter" range.
As pjs pointed out, add
radius *= math.sqrt(random())
for uniform distribution
Merely culling results that fall outside your circle will be sufficient.
If you don't want to throw out some percentage of random results, you could choose a random angle and distance, to ensure all your values fall within the radius of your circle. It's important to note, with this solution, that the precision of the methods you use to extrapolate an angle into a vector will skew your distribution to be more concentrated in the center.
If you make a vector out of your x,y values, and then do something like randomize the length of said vector to fall within your circle, your distribution will no longer be uniform, so I would steer clear of that approach, if uniformity is your biggest concern.
The culling approach is the most evenly distributed, of the three I mentioned, although the random angle/length approach is usually fine, except in cases involving very fine precision and granularity.

Monte Carlo Method, Darts in overlapping area of two circles

I am trying to estimate the value of pi using a monte carlo simulation. I need to use two unit circles that are a user input distance from the origin. I understand how this problem works with a single circle, I just don't understand how I am meant to use two circles. Here is what I have got so far (this is the modified code I used for a previous problem the used one circle with radius 2.
import random
import math
import sys
def main():
numDarts=int(sys.argv[1])
distance=float(sys.argv[2])
print(montePi(numDarts,distance))
def montePi(numDarts,distance):
if distance>=1:
return(0)
inCircle=0
for I in range(numDarts):
x=(2*(random.random()))-2
y=random.random()
d=math.sqrt(x**2+y**2)
if d<=2 and d>=-2:
inCircle=inCircle+1
pi=inCircle/numDarts*4
return pi
main()
I need to change this code to work with 2 unit circles, but I do not understand how to use trigonometry to do this, or am I overthinking the problem? Either way help will be appreciated as I continue trying to figure this out.
What I do know is that I need to change the X coordinate, as well as the equation that determines "d" (d=math.sqrt(x*2+y*2)), im just not sure how.
These are my instructions-
Write a program called mcintersection.py that uses the Monte Carlo method to
estimate the area of this shape (and prints the result). Your program should take
two command-line parameters: distance and numDarts. The distance parameter
specifies how far away the circles are from the origin on the x-axis. So if distance
is 0, then both circles are centered on the origin, and completely overlap. If
distance is 0.5 then one circle is centered at (-0.5, 0) and the other at (0.5, 0). If
distance is 1 or greater, then the circles do not overlap at all! In that last case, your
program can simply output 0. The numDarts parameter should specify the number
of random points to pick in the Monte Carlo process.
In this case, the rectangle should be 2 units tall (with the top at y = 1 and the
bottom at y = -1). You could also safely make the rectangle 2 units wide, but this
will generally be much bigger than necessary. Instead, you should figure out
exactly how wide the shape is, based on the distance parameter. That way you can
use as skinny a rectangle as possible.
If I understand the problem correctly, you have two unit circles centered at (distance, 0) and (-distance, 0) (that is, one is slightly to the right of the origin and one is slightly to the left). You're trying to determine if a given point, (x, y) is within both circles.
The simplest approach might be to simply compute the distance between the point and the center of each of the circles. You've already done this in your previous code, just repeat the computation twice, once with the offset distance inverted, then use and to see if your point is in both circles.
But a more elegant solution would be to notice how your two circles intersect each other exactly on the y-axis. To the right of the axis, the left circle is completely contained within the right one. To the left of the y-axis, the right circle is entirely within the left circle. And since the shape is symmetrical, the two halves are of exactly equal size.
This means you can limit your darts to only hitting on one side of the axis, and then get away with just a single distance test:
def circle_intersection_area(num_darts, distance):
if distance >= 1:
return 0
in_circle = 0
width = 1-distance # this is enough to cover half of the target
for i in range(num_darts):
x = random.random()*width # random value from 0 to 1-distance
y = random.random()*2 - 1 # random value from -1 to 1
d = math.sqrt((x+distance)**2 + y**2) # distance from (-distance, 0)
if d <= 1:
in_circle += 1
sample_area = width * 2
target_area = sample_area * (in_circle / num_darts)
return target_area * 2 # double, since we were only testing half the target

Categories

Resources