This is the first part of a three part series about making the Twitter bot @BotfonsNeedles. In this part, I will write a Python 3 program that

- uses a Monte Carlo method to approximate \(\pi\) with Buffon’s needle problem, and
- produces an image with the Python library Pillow

In the second part, I’ll explain how to use the Twitter API to

- post the images to Twitter via the Python library Tweepy, and
- keep track of all of the Tweets to get an increasingly accurate estimate of \(\pi\).

In the third part, I’ll explain how to

- Package all of this code up into a Docker container
- Push the Docker image to Amazon Web Services (AWS)
- Set up a function on AWS Lambda to run the code on a timer

### Buffon’s needle problem

Buffon’s needle problem is a surprising way of computing \(\pi\). It says that if you throw \(n\) needles of length \(\ell\) randomly onto a floor that has parallel lines that are a distance of \(\ell\) apart, then the expected number of needles that cross a line is \(\frac{2n}\pi\). Therefore one way to approximate (\pi) is to divide \(2n\) by the number of needles that cross a line.

I had my computer simulate 400 needle tosses, and 258 of them crossed a line. Thus this experiment approximates \(\pi \approx 2\!\left(\frac{400}{258}\right) \approx 3.101\), about a 1.3% error from the true value.

### Modeling in Python

Our goal is to write a Python program that can simulate tossing needles on the floor both numerically (e.g. “258 of 400 needles crossed a line”) and graphically (i.e. creates the PNG images like in the above example).

#### The `RandomNeedle`

class.

We’ll start by defining a `RandomNeedle`

class which takes

- a
`canvas_width`

, \(w\); - a
`canvas_height`

, \(h\); - and a
`line_spacing,`

\(\ell\).

It then initializes by choosing a random angle (\theta \in [0,\pi]) and random placement for the center of the needle in \[(x,y) \in \left[\frac{\ell}{2}, w -\,\frac{\ell}{2}\right] \times \left[\frac{\ell}{2}, h -\,\frac{\ell}{2}\right]\] in order to avoid issues with boundary conditions.

Next, it uses the angle and some plane geometry to compute the endpoints of the needle: \[\begin{bmatrix}x\\y\end{bmatrix} \pm \frac{\ell}{2}\begin{bmatrix}\cos(\theta)\\ \sin(\theta)\end{bmatrix}.\]

The class’s first method is `crosses_line`

, which checks to see that the \(x\)-values at either end of the needle are in different “sections”. Since we know that the parallel lines occur at all multiples of \(\ell\), we can just check that \[\left\lfloor\frac{x_\text{start}}{\ell}\right\rfloor \neq \left\lfloor\frac{x_\text{end}}{\ell}\right\rfloor.\]

The class’s second method is `draw`

which takes a `drawing_context`

via Pillow and simply draws a line.

```
import math
import random
class RandomNeedle:
def __init__(self, canvas_width, canvas_height, line_spacing):
theta = random.random()*math.pi
half_needle = line_spacing//2
self.x = random.randint(half_needle, canvas_width-half_needle)
self.y = random.randint(half_needle, canvas_height-half_needle)
self.del_x = half_needle * math.cos(theta)
self.del_y = half_needle * math.sin(theta)
self.spacing = line_spacing
def crosses_line(self):
initial_sector = (self.x - self.del_x)//self.spacing
terminal_sector = (self.x + self.del_x)//self.spacing
return abs(initial_sector - terminal_sector) == 1
def draw(self, drawing_context):
color = "red" if self.crosses_line() else "grey"
initial_point = (self.x-self.del_x, self.y-self.del_y)
terminal_point = (self.x+self.del_x, self.y+self.del_y)
drawing_context.line([initial_point, terminal_point], color, 10)
```

By generating \(100\,000\) instances of the `RandomNeedle`

class, and keeping a running estimation of (\pi) based on what percentage of the needles cross the line, you get a plot like the following:

## The `NeedleDrawer`

class

The `NeedleDrawer`

class is all about running these simulations and drawing pictures of them. In order to draw the images, we use the Python library Pillow which I installed by running

`pip3 install Pillow`

When an instance of the `NeedleDrawer`

class is initialized, makes a “floor” and “tosses” 100 needles (by creating 100 instances of the `RandomNeedle`

class).

The main function in this class is `draw_image`

, which makes a \(4096 \times 2048\) pixel canvas, draws the vertical lines, then draws each of the `RandomNeedle`

instances.

(It saves the files to the `/tmp`

directory in root because that’s the only place we can write file to our Docker instance on AWS Lambda, which will be a step in part 2 of this series.)

```
from PIL import Image, ImageDraw
from random_needle import RandomNeedle
class NeedleDrawer:
def __init__(self):
self.width = 4096
self.height = 2048
self.spacing = 256
self.random_needles = self.toss_needles(100)
def draw_vertical_lines(self):
for x in range(self.spacing, self.width, self.spacing):
self.drawing_context.line([(x,0),(x,self.height)],width=10, fill="black")
def toss_needles(self, count):
return [RandomNeedle(self.width, self.height, self.spacing) for _ in range(count)]
def draw_needles(self):
for needle in self.random_needles:
needle.draw(self.drawing_context)
def count_needles(self):
cross_count = sum(1 for n in self.random_needles if n.crosses_line())
return (cross_count, len(self.random_needles))
def draw_image(self):
img = Image.new("RGB", (self.width, self.height), (255,255,255))
self.drawing_context = ImageDraw.Draw(img)
self.draw_vertical_lines()
self.draw_needles()
del self.drawing_context
img.save("/tmp/needle_drop.png")
return self.count_needles()
```

## Next Steps

In the next part of this series, we’re going to add a new class that uses the Twitter API to post needle-drop experiments to Twitter. In the final part of the series, we’ll wire this up to AWS Lambda to post to Twitter on a timer.

## 1 Comment