π / 4 = 1 - (1/3) + (1/5) - (1/7) + (1/9) - (1/11) + ...There is another novel approach to calculate π. Imagine that you have a dart board that is 2 units square. (Suppose that a were 2 in the figure above.) The square inscribes a circle of radius 1. The center of the circle coincides with the center of the square. Now imagine that you throw darts at that dart board randomly. If the throws are truly random, the ratio of the number of darts that fall within the circle to the total number of darts thrown is the same as the ratio of the area of the circle to the area of the square dart board. The area of a circle with unit radius is just π square units. The area of the dart board is 4 square units. Thus, the ratio of the area of the circle to the area of the square is π / 4. From that it's easy to estimate π; just multiply by 4.
That's the computation you'll be doing in this assignment, which has two main parts.
Hint: a point (a, b) is inside a Circle if the distance from (a, b) to the circle center is less than the radius. You can calculate the distance between points (x1, y1) and (x2, y2) with the distance function, which is provided below. I'm leaving it to you to figure out how to decide whether a point is inside a square.
import math import random def distance( x1, y1, x2, y2 ): return math.sqrt( (x1 - x2)**2 + (y1 - y2)**2 ) class Circle: """Defines a circle object. It's centered at (x, y) and has radius rad.""" def __init__(self, rad, x, y): """Define the Circle object.""" def __str__(self): """Return a string describing the object.""" def getRadius(self): """Return the radius.""" def getCenterX(self): """Return the x component of the center point.""" def getCenterY(self): """Return the y component of the center point.""" def getArea(self): """Return the area of the circle.""" def pointInCircle(self, a, b): """Return a boolean indicating whether point (a, b) is inside the circle.""" class Square: """Construct a square with given side length, aligned with the axes and top left corner at point (x, y).""" def __init__(self, side, x, y): """Define the Square object.""" def __str__(self): """Return a string describing the object.""" def getSide(self): """Return the side length of the Square.""" def getULX(self): """Return the x component of the upper left corner.""" def getULY(self): """Return the y component of the upper left corner.""" def getArea(self): """Return the area of the square.""" def pointInSquare(self, a, b): """Return a boolean indicating whether point (a, b) is inside the square."""Be sure to test your classes before you proceed to Part 2. Below is some behavior you might see in testing these classes:
>>> from CalculatePi import * >>> c = Circle( 1, 3, 9 ) # define a Circle object >>> print(c) Circle of radius 1 centered at point (3, 9) >>> c.getRadius() 1 >>> c.getCenterX() 3 >>> c.getCenterY() 9 >>> c.pointInCircle( 1, 2 ) False >>> c.pointInCircle( 3.2, 8.4 ) True >>> c.getArea() # notice the area 3.141592653589793 >>> s = Square( 2, -2, -4 ) # define a square >>> print(s) Square of side 2 with upper left point at (-2, -4) >>> s.getSide() 2 >>> s.getULX() -2 >>> s.getULY() -4 >>> s.getArea() 4 >>> s.pointInSquare( 0, 0 ) False >>> s.pointInSquare( 0, -4 ) False >>> s.pointInSquare( -1, -5 ) True >>>
Then, we'll use this to estimate π by imagining that the square is the dartboard with inscribed circle as described above. We throw darts randomly at the board and calculate how many hit the circle, out of how many total throws. This ratio should approach π / 4. Notice that a dart hits the board if its x and y coordinates are both between -1 and 1. To see whether it hits the circle use the pointInCircle method.
To simulate the throwing of darts we will use a random number generator. In the random module is the function random.random() which returns a random float in the range 0.0 to 1.0. To generate a random value in range -1.0 to 1.0, you can do the following:
r = (random.random() * 2) - 1Think about why that works.
You should write two functions: estimatePi( c, n ) and main(). Function estimatePi( c, n ) does the following. It takes two parameters: a Circle c of radius 1 and centered at (0, 0) and a count n. Generate n points (x, y), where both x and y are in the range (-1.0..1.0). For each, see if the point is inside the Circle. Keep track of the number of hits (those in the circle). Return an estimate of π as (hits / n) * 4.
In the main() function, do the following: Define a Circle object of radius 1, centered at (0, 0) and a Square object of side 2 with upper left corner at (-1, 1). In a loop, run your estimatePi( c, n ) function on that Circle and with n set to powers of 10 between 100 and 10,000,000. You will print the results in a nice table as shown below:
n = 100 Calculated PI = 3.320000 Difference = +0.178407 n = 1000 Calculated PI = 3.080000 Difference = -0.061593 n = 10000 Calculated PI = 3.120400 Difference = -0.021193 n = 100000 Calculated PI = 3.144720 Difference = +0.003127 n = 1000000 Calculated PI = 3.142588 Difference = +0.000995 n = 10000000 Calculated PI = 3.141796 Difference = +0.000204Notice that the Calculated PI value is the value you computed printed with 6 places of precision after the decimal point. The Difference field in the output is your calculated value of PI minus math.pi. Note that this might be positive or negative. You can make a value print with a sign by adding a "+" to your format, e.g., format(diff, "+0.6f").
Your output must be in the above format. The number of throws must be left justified. The calculated value of π and the difference must be expressed to six places of precision. There should be plus or minus sign on the difference. Notice that your values for estimated π and the difference from math.pi will differ from those shown above, and will differ each time you run the program. Ideally, the difference approaches 0 as n increases.
It must also contain a header with the following format:
# Assignment: HW7 # File: CalculatePi.py # Student: # UT EID: # Course Name: CS303E # # Date Created: # Description of Program:
But, in effect, that's still what's happening in Python. It's just that they hide it under a nicer syntax. For example, when you write "x + y" in Python, that's really a short way of calling: "x.__add__(y)", the __add__ method on the class of x. (Magic methods like __add__ and __str__ are explained in Slideset 7.)
>>> i1 = 10 >>> i2 = 20 >>> i1.__add__(i2) 30 >>> i1 + i2 30Note that for some reason you can't call __add__ directly on integer literals; "10.__add__(20)" isn't allowed.
When you define a convenient notation for a more cumbersome underlying syntax, that's often called syntactic sugar. Luckily, the syntactic sugar means that you can use the familiar syntax of arithmetic almost all the time when you're doing arithmetic in Python. But Python also allows you to "overload" that familiar syntax in some very unfamiliar contexts. You've already seen that you can add two numbers; you can also "add" (concatenate) two strings. That's just because the __add__ method is defined in the string class to be concatenation.
If you wanted to define a way to "add" two circles or two squares using the syntax "d1 + d2" you could do it in Python and give it whatever meaning you wanted simply by defining "__add__" on the class. That wouldn't make much sense for this assignment. It might if, for example, you often needed the sum of the areas of several figures. You could even re-define the meaning of "+" on integers, but you'd almost certainly regret that!