
Hello! I’m Peter!
Blog
-
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
- 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
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 adrawing_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
classThe
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 runningpip3 install Pillow
When an instance of the
NeedleDrawer
class is initialized, makes a “floor” and “tosses” 100 needles (by creating 100 instances of theRandomNeedle
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 theRandomNeedle
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.
-
Polytopes with Lattice Coordinates
Problems 21, 66, and 116 in my Open Problem Collection concern polytopes with lattice coordinates—that is, polygons, polyhedra, or higher-dimensional analogs with vertices the square or triangular grids. (In higher dimensions, I’m most interested in the \(n\)-dimensional integer lattice and the \(n\)-simplex honeycomb).
The \(A334581(4) = 84\) ways to place an equilateral triangle on the tetrahedral grid with four points per side. Illustration showing three of the \(\binom{7+2}{4} = 126\) equilateral triangles on a grid with seven points per side. This was largely inspired by one of my favorite mathematical facts: given a triangular grid with \(n\) points per side, you can find exactly \(\binom{n+2}{4}\) equilateral triangles with vertices on the grid. However, it turns out that there isn’t a similarly nice polynomial description of tetrahedra in a tetrahedron or of triangles in a tetrahedron. (Thanks to Anders Kaseorg for his Rust program that computed the number of triangles in all tetrahedra with 1000 or fewer points per side.)
The \(4\)-simplex (the \(4\)-dimensional analog of a triangle or tetrahedron) with \(n-1\) points per side, has a total of \(\binom{n+2}{4}\) points, so there is some correspondence between points in some \(4\)-dimensional polytope, and triangles in the triangular grid. This extends to other analogs of this problem: the number of squares in the square grid is the same as the number of points in a \(4\)-dimensional pyramid.
The \(\binom{n+2}{4}\) equilateral triangles
I put a Javascript applet on my webpage that illustrates a bijection between size-\(4\) subsets of \(n+2\) objects and triangles in the \(n\)-points-per-side grid. You can choose different subsets and see the resulting triangles. (The applet does not work on mobile.)
The solid blue triangle corresponding to the subset \(\{4,5,8,15\} \subseteq \{1,2,\dots,16\}\). The two smaller numbers in the subset give the size and orientation of the blue triangle, and the two larger numbers give the position. Polygons with vertices in \(\mathbb{Z}^n\)
This was also inspired by Mathologer video “What does this prove? Some of the most gorgeous visual ‘shrink’ proofs ever invented”, where Burkard Polster visually illustrates that the only regular polygons with vertices in \(\mathbb{Z}^n\) (and thus the \(n\)-simplex honeycomb) are equilateral triangles, squares, and regular hexagons.
Polyhedra with vertices in \(\mathbb{Z}^3\)
There are some surprising examples of polyhedra in the grid, including cubes with no faces parallel to the \(xy\)-, \(xz\)-, or \(yz\)-planes.
An example of a cube from Ionascu and Obando: the convex hull of \(\{(0,3,2),(1,1,4),(2,2,0),(2,5,3),(3,0,2),(3,3,5),(4,4,1),(5,2,3)\}\) While there are lots of polytopes that can be written with vertices in \(\mathbb{Z}^3\), Alaska resident and friend RavenclawPrefect cleverly uses Legendre’s three-square theorem to prove that there’s no way to write the uniform triangular prism this way! However, he provides a cute embedding in \(\mathbb{Z}^5\): the convex hull of $$\scriptsize{\{(0,0,1,0,0),(0,1,0,0,0),(1,0,0,0,0),(0,0,1,1,1),(0,1,0,1,1),(1,0,0,1,1)}\}.$$
Polygons on a “centered \(n\)-gon”
I asked a question on Math Stack Exchange, “When is it possible to find a regular \(k\)-gon in a centered \(n\)-gon“—where “centered \(n\)-gon” refers to the diagram that you get when illustrating central polygonal numbers. These diagrams are one of many possible generalizations of the triangular, square, and centered hexagonal grids. (Although it’s worth noting that the centered triangular grid is different from the ordinary triangular grid.)
If you have any ideas about this, let me know on Twitter or post an answer to the Stack Exchange question above.
A catalog of polytopes and grids
On my OEIS wiki page, I’ve created some tables that show different kinds of polytopes in different kinds of grids. There are quite a number of combinations of polygons/polyhedra and grids that either don’t have an OEIS sequence or that I have been unable to find.
Square Rectangular Centered Square Triangular Centered Hexagonal Equilateral Triangle – – – A000332 A008893 Square A002415 A130684 A006324 – – Regular Hexagon – – – A011779 A000537 Regular Polygon A002415 A130684 A006324 ? A339483* Triangle A045996 A334705 ? ? A241223 Rectangle A085582 A289832 ? – – Right Triangle A077435 ? ? ? A241225 OEIS sequences for polygons on 2-dimensional grids.
Sequences marked with “*” are ones that I’ve authored, cells marked with “—” have no polygons, and cells marked with “?” do not have a corresponding sequence that I know of.Cubic Tetrahedral Octahedral Equilateral Triangle A102698 A334581* A342353* Square A334881* A334891* ? Regular Hexagon A338322* ? ? Regular Polygon A338323* ? ? Triangle ? ? ? Rectangle ? ? ? Right Triangle ? ? ? Regular Tetrahedron A103158 A269747 ? Cube A098928 ? ? Octahedron A178797 ? ? Platonic Solid A338791 ? ? OEIS sequences for polytopes on 3-dimensional grids.
Sequences marked with “*” are ones that I’ve authored, and cells marked with “?” do not have a corresponding sequence that I know of.If you’re interested in working on filling in some of the gaps in this table, I’d love it if you let me now! And if you’d like to collaborate or could use help getting started, send me a message on Twitter!
-
Parity Bitmaps from the OEIS
My friend Alec Jones and I wrote a Python script that takes a two-dimensional sequence in the On-Line Encyclopedia of Integer Sequences and uses it to create a one-bit-per-pixel (1BPP) “parity bitmaps“. The program is simple: it colors a given pixel is black or white depending on whether the corresponding value is even or odd.
A048152 parity bitmap, rescaled through Lospec’s Pixel Art Scaler. A207409 parity bitmap. An Unexpected Fractal
We’ve now run the script on over a thousand sequences, but we still both agree on our favorite: the fractal generated by OEIS sequence A279212.
Fill an array by antidiagonals upwards; in the top left cell enter \(a(0)=1\); thereafter, in the \(n\)-th cell, enter the sum of the entries of those earlier cells that can be “seen” from that cell.
Notice that in the images below, increasing the rows and columns by a factor of \(2^n\) seems to increase the “resolution”, because the parity bitmap is self similar at 2x the scale. We still don’t have a good explanation for why we’d expect these images are fractals. If you know, please answer our question about it on Math Stack Exchange. (Alec and I have generated these images up to 16384 × 32768 resolution, roughly 536 megapixels.)
The Construction of the Sequence
The sequence is built up by “antidiagonals”, as shown in the GIF below. In the definition, “seen” means every direction a chess queen can move that already has numbers written down (i.e. north, west, northwest, or southwest). That is, look at all of the positions you can move to, add them all up, write that number in your square, move to the next square, and repeat. (The number in cell \(C\) also counts the number of paths a queen can make from \(C\) to the northwest corner using only N, NW, W, SW moves.)
(Interestingly, but only tangentially related: Code Golf Stack Exchange User flawr noticed that the number of north/west rook walks is related to the number of ways of partitioning a \(1 \times n\) grid into triangles.)
Parity Bitmaps for Other Sequences
It’s worth noting that many sequences are all black, consist of simple repeating patterns, or look like static. However, chess-type constructions, as illustrated by the GIF above, the one above yield images that look like the Sierpiński triangle. (See A132439 and A334017 below, and look at A334016 and A334745 via their OEIS entries.) Look below for a couple other sequences with interesting images too.
I ordered a poster-sized print of the A279212 fractal for Alec, and he framed it in his office.
Alec’s fractal, framed above his fountain pen ink and tasteful books. Some ideas for further exploration:
- See more images I’ve already generated, I have hundreds of images available for download on my Github page.
- Check out my Twitter bot @oeisTriangles that tweets related images.
- Download the Python script to run on other sequences.
- Share any ideas that this sparked for you—I’m @PeterKagey on Twitter!