Buffon's needle simulation in python

后端 未结 6 1482
臣服心动
臣服心动 2021-01-27 18:47
import numpy as np
import matplotlib.pylab as plt

class Buffon_needle_problem:

    def __init__(self,x,y,n,m):
        self.x = x #width of the needle
        self.y =         


        
相关标签:
6条回答
  • 2021-01-27 18:55

    I would say that the problem is that you are defining the alignment of the needle by a simple linear function, when in fact the effective length of the needle from its centre is defined by a sinusoidal function.

    You want to calculate the effective length of the needle (at 90° to the lines) by using a function that will calculate it from its angle.

    Something like:

    self.z.append(np.cos(np.random.uniform(-np.pi/2, np.pi/2))*self.x)
    

    This will give the cosine of a random angle between -90° and +90°, times the length of the needle.

    For reference, cos(+/-90) = 0 and cos(0) = 1, so at 90°, the needle with have effectively zero length, and at 0°, its full length.

    I have neither mathplotlib or numpy installed on this machine, so I can't see if this fixes it, but it's definitely necessary.

    0 讨论(0)
  • 2021-01-27 18:58

    Looks like you were committing a simple rounding error. The code below works, though the results are not very close to pi...

    import numpy as np
    import matplotlib.pylab as plt
    
    class Buffon_needle_problem:
    def __init__(self,x,y,n,m):
        self.x = x #width of the needle
        self.y = y #witdh of the space
        self.r = []#coordinated of the centre of the needle
        self.z = []#measure of the alingment of the needle
        self.n = n#no of throws
        self.m = m#no of simulations
        self.pi_approx = []
    
    def samples(self):
        # throwing the needles
        for i in range(self.n):
            self.r.append(np.random.uniform(0,self.y))
            self.z.append(np.random.uniform(0,self.x/2.0))
        return [self.r,self.z]
    
    def simulation(self):
        #self.samples()
        # m simulations
        for j in range(self.m):
            self.r=[]
            self.z=[]
            for i in range(self.n):
                self.r.append(np.random.uniform(0,self.y))
                self.z.append(np.random.uniform(0,self.x/2.0))
            # n throws
            hits = 0 # setting the succes to 0
            for i in range(self.n):
                # condition for a hit
                if self.r[i]+self.z[i]>=self.y or self.r[i]-self.z[i] <= 0.0:
                    hits += 1
                else:
                    continue
            hits = 2.0*(float(self.x)/self.y)*float(self.n)/float(hits)
            self.pi_approx.append(hits)
    
        return self.pi_approx
    
    y = Buffon_needle_problem(1,2,40000,5)
    print (y.simulation())
    

    Also note that you were using the same sample for all simulations!

    0 讨论(0)
  • 2021-01-27 19:08

    Buffon's needle work accurately only when the distance between the two lines is double the length of needle. Make sure to cross check it.

    I have seen many baffon's online simulation which are doing this mistake. They just take the distance between two adjacent lines to be equal to the needle's length. That's their main logical errors.

    0 讨论(0)
  • 2021-01-27 19:11

    I used Python turtle to approximate the value of Pi:

    from turtle import *
    from random import *
    
    setworldcoordinates(-100, -200, 200, 200)
    ht(); speed(0); color('blue')
    
    drops = 20  # increase number of drops for better approximation
    hits = 0    # hits counter
    
    # draw parallel lines with distance 20 between adjacent lines
    for i in range(0, 120, 20): 
        pu(); setpos(0, i); pd()
        fd(100) # length of line
    
    # throw needles
    color('red')
    for j in range(drops):
        pu()
        goto(randrange(10, 90), randrange(0,100))
        y1 = ycor() # keep ycor of start point
        seth(360*random())
        pd(); fd(20)    # draw needle of length 20
        y2 = ycor() # keep ycor of end point
    
        if y1//20 != y2//20:    # decisive test: if it is a hit then ...
            hits += 1       # increase the hits counter by 1
    
    print(2 * drops / hits)
    
    Output samples
    With 50 drops   3.225806451612903
    with 200 drops  3.3057851239669422
    with 1000 drops 3.1645569620253164
    
    0 讨论(0)
  • 2021-01-27 19:17

    The sampling of the needle's alignment should be a uniform cosine. See the following link for the method: http://pdg.lbl.gov/2012/reviews/rpp2012-rev-monte-carlo-techniques.pdf

    Also, there were a few logical problems with the program. Here is a working version.

    #!/bin/python
    import numpy as np
    
    def sample_cosine():
      rr=2.
      while rr > 1.:
        u1=np.random.uniform(0,1.)
        u2=np.random.uniform(0,1.)
        v1=2*u1-1.
        rr=v1*v1+u2*u2
      cc=(v1*v1-u2*u2)/rr
      return cc
    
    class Buffon_needle_problem:
    
         def __init__(self,x,y,n,m):
            self.x = float(x)  #width of the needle
            self.y = float(y)  #witdh of the space
            self.r = [] #coordinated of the centre of the needle
            self.z = [] #measure of the alignment of the needle
            self.n = n  #no of throws
            self.m = m  #no of simulations
            self.p = self.x/self.y
            self.pi_approx = []
    
        def samples(self):
            # throwing the needles
            for i in range(self.n):
                self.r.append(np.random.uniform(0,self.y))
                C=sample_cosine()
                self.z.append(C*self.x/2.)
            return [self.r,self.z]
    
        def simulation(self):
            # m simulation
            for j in range(self.m):
                self.r=[]
                self.z=[]
                self.samples()
                # n throw
                hits = 0 #setting the success to 0
                for i in range(self.n):
                    #condition for a hit
                    if self.r[i]+self.z[i]>=self.y or self.r[i]-self.z[i]<0.:
                        hits += 1
                    else:
                        continue
                est =self.p*float(self.n)/float(hits)
                self.pi_approx.append(est)
            return self.pi_approx
    
    y = Buffon_needle_problem(1,2,80000,5)
    
    print (y.simulation())
    
    0 讨论(0)
  • 2021-01-27 19:21

    NOT answer to original question, if you just want the pi estimate, here's some simple code from I did in a computational revision exercise yesterday at Uni Sydney (Aust), against my early inclinations, to reduce complexity, we only modelled for a random point between zero and distance between lines and a random angle from zero to 90 degrees.

    import random 
    from numpy import pi, sin
    
    def buffon(L, D, N):
        '''
        BUFFON takes L (needle length), 
        D = distance between lines and N = number of drops, 
        returns probability of hitting line
        generate random number 'd' between 0 and D 
        generate theta between 0 and pi/2
        hit when L*sin(theta)) - d is great than D
    
        '''
        hit = 0; 
        for loop in range(N) :
            theta = pi*random.random()/2
            if L * sin(theta) > D - D*random.random(): # d = random*D
                hit += 1
        return hit/N
    
    #% Prob_hit = 2*L/(D*pi) hence:  Pi_est = 2*L / (P_hit*D); 
    L = 1
    D = 4
    N = int(1e8) 
    Pi_est = 2*L / (buffon(L,D,N)*D)
    

    It was in MatLab, I wanted to try it in Python, see if I could use any comprehension lists, any ideas to speed this up WELCOME.

    0 讨论(0)
提交回复
热议问题