# A π-estimating Twitter bot: Part I

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

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 An example of dropping 100 needles in Buffon’s needle problem. Needles that cross a vertical line are colored red.

### 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. 63/100 needles cross a vertical line; approximates $$\pi \approx 200/63 \approx 3.174$$. 61/100 needles cross a vertical line; approximates $$\pi \approx 200/61 \approx 3.279$$. 68/100 needles cross a vertical line; approximates $$\pi \approx 200/68 \approx 2.941$$. 66/100 needles cross a vertical line; approximates $$\pi \approx 200/66 \approx 3.030$$.

### 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: This estimates $$\pi\approx 2\left(\frac{10000}{63681}\right) \approx 3.1407$$ an error of 0.03%.

## 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.