Werner Krauth is visiting NYU’s Simons Center for Physics from ENS in Paris. He’s a physicist and the author of the 2006 book Statistical Mechanics: Algorithms and Computations. And he’s stopping by our reading group Fridays, where I learned about his most peculiar approach to plotting. He coded it in Postscript (!!! see below) and ran simulations on the printer. The result is quite beautiful. Here’s Figure 1.10 from the text.

I wondered how well GPT would do at this task. It’s pretty complicated, but then Buffon’s well represented on the internet. Let’s give it a shot. Here’s my prompt:
BC: From a book on molecular dynamics, “We want to follow in Count Buffon’s footsteps and and perform our own needle-throwing experiments. One of these, with 2000 samples, is shown in Fig. 1.10.” Write a Python program to generate those 2000 samples. Then take the 2000 samples and write code to plot them. Each needle should be displayed as a line connected to a needle eye with a small ball in the middle. There should be 17 “cracks” in the experiment and the ball should be solid if a needle overlaps a crack. The field overall is sized 20 high x 16 wide and has 17 vertical cracks. Each needle is unit length.
At which point, GPT gave me three blocks of code (see below), and when I ran it, this was the result.

This is actually better than I expected. You can see it got the distinction right of white circles versus red circles, but not quite the right visualization. So I asked it to try again with this prompt:
BC: That’s great, but there are several improvements I’d like to see.
1. Black and white rendering only.
2. Thinner lines and smaller circles and disks.
3. Plot the vertical cracks with thin lines.
4. Put a needle like shape (like an American football) on one end of each needle to give it an orientation.I just need the final Plotting the Needles code for this.
Et voila.

Well, almost. It forgot to distinguish the ones that overlapped with circles rather than black disks.
This took a total of 2 minutes (plus 10 minutes to get into the blog, because it’s totally manual).
Here’s the code snippets it produced.
import numpy as np
def generate_needles(num_samples, field_width, field_height):
# Generate random midpoints
x = np.random.uniform(0, field_width, num_samples)
y = np.random.uniform(0, field_height, num_samples)
# Generate random angles from 0 to 2*pi
theta = np.random.uniform(0, 2*np.pi, num_samples)
return x, y, theta
# Constants
num_samples = 2000
field_width = 16
field_height = 20
x, y, theta = generate_needles(num_samples, field_width, field_height)
def check_overlaps(x, y, theta, field_width, num_cracks):
crack_positions = np.linspace(0, field_width, num_cracks + 1)
overlaps = np.zeros(len(x), dtype=bool)
for i in range(len(x)):
# Calculate endpoints
x1 = x[i] + 0.5 * np.cos(theta[i])
y1 = y[i] + 0.5 * np.sin(theta[i])
x2 = x[i] - 0.5 * np.cos(theta[i])
y2 = y[i] - 0.5 * np.sin(theta[i])
# Check for overlap
min_x = min(x1, x2)
max_x = max(x1, x2)
overlaps[i] = np.any((crack_positions > min_x) & (crack_positions < max_x))
return overlaps
overlaps = check_overlaps(x, y, theta, field_width, 17)
import matplotlib.pyplot as plt
def plot_needles(x, y, theta, overlaps):
fig, ax = plt.subplots(figsize=(8, 6))
for i in range(len(x)):
# Endpoints
x1 = x[i] + 0.5 * np.cos(theta[i])
y1 = y[i] + 0.5 * np.sin(theta[i])
x2 = x[i] - 0.5 * np.cos(theta[i])
y2 = y[i] - 0.5 * np.sin(theta[i])
# Plot needle
ax.plot([x1, x2], [y1, y2], 'k-')
# Plot needle eye
ax.plot(x[i], y[i], 'ro' if overlaps[i] else 'wo')
ax.set_xlim(0, field_width)
ax.set_ylim(0, field_height)
ax.set_aspect('equal')
plt.show()
plot_needles(x, y, theta, overlaps)
This is very neat and well organized code! Here's the followup code.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
def plot_needles(x, y, theta, overlaps):
fig, ax = plt.subplots(figsize=(8, 6))
# Plot vertical cracks
field_width = 16
num_cracks = 17
crack_positions = np.linspace(0, field_width, num_cracks + 1)
for cp in crack_positions:
ax.axvline(x=cp, color='gray', linestyle='-', linewidth=0.5)
# Settings for needles and circles
line_width = 0.5
circle_radius = 0.05 # radius for midpoint marker
needle_length = 1
football_length = 0.2 # length of the "football" shape
for i in range(len(x)):
# Calculate endpoints
x1 = x[i] + 0.5 * np.cos(theta[i])
y1 = y[i] + 0.5 * np.sin(theta[i])
x2 = x[i] - 0.5 * np.cos(theta[i])
y2 = y[i] - 0.5 * np.sin(theta[i])
# Draw needle
ax.plot([x1, x2], [y1, y2], 'k-', lw=line_width)
# Draw "American football" shape
football_x = x[i] + 0.5 * np.cos(theta[i]) - football_length * np.cos(theta[i])
football_y = y[i] + 0.5 * np.sin(theta[i]) - football_length * np.sin(theta[i])
ellipse = patches.Ellipse((football_x, football_y), football_length, circle_radius,
angle=np.degrees(theta[i]), color='black')
ax.add_patch(ellipse)
# Draw needle eye
color = 'black' if overlaps[i] else 'white'
ax.plot(x[i], y[i], marker='o', markersize=circle_radius*30, markeredgecolor='black', markerfacecolor=color)
ax.set_xlim(0, field_width)
ax.set_ylim(0, field_height)
ax.set_aspect('equal')
ax.axis('off') # Turn off the axis
plt.show()
plot_needles(x, y, theta, overlaps)
What's really amazing here is that it recognizes "American football shape" and can convert that into Matplotlib code.
Getting better at humor
GPT's getting funnier. This is from Mitzi.
MM: please give me a joke with the punchline "all your base are belong to us"
GPT4: Why did the computer take over the entire internet?
Because it finally decoded the message: "All your base are belong to us."
Thank you sir, may I have another?
MM: that's pretty good. please give me another one.
ChatGPT: Why did the grammar teacher fail the evil overlord's essay?
Because it ended with "All your base are belong to us."
A good comic has a lot of material.
MM: another one?
GPT: Why did the spaceship's navigation system malfunction?
Because the aliens reprogrammed it to say, "All your base are belong to us."
And because it's that time of year.
MM: please make a baseball joke about this
GPT: Why did the opposing baseball team start panicking in the middle of the game?
Because the scoreboard flashed a message: "All your base are belong to us."
Mitzi says "home team" would have been funnier.
The Postscript postscript
Werner shared his postscript code for generating his plot. It runs as part of the postscript rendering. The relevant part is at the bottom starting at "4000".
%!PS-Adobe-2.0 EPSF-2.0
%%Title: IN_buffon_exp_new.eps
%%BoundingBox: 45 131 430 625
%% Postscript figure from
% Statistical Mechanics: Algorithms and Computations
% Werner Krauth
% Oxford University Press (2006)
%
/cm { 0.0351 div} def
/thicklinewidth 1.5 def
/normallinewidth 1.0 def % 0.35377 mm or 1/72 inch
/thinlinewidth 0.5 def
/white 1.0 def % 0 % gray = white
/black .0 def % 100% gray= black
/tinyradius 0.03 cm def %
%
/dot { % usage: x y radius dot (draws a dot (filled circle) of given radius in current grayvalue)
gsave
0 360 arc
gsave
fill
grestore
gsave
black setgray
stroke
grestore
grestore
} def
/circle { % usage: x y radius circle (draws an unfilled circle of given radius)
% the linewidth is not specified
gsave
0 360 arc
black setgray
stroke
grestore
} def
/nran { % usage: N nran (picks random number between 1 and N)
rand exch mod 1 add } def
/ran01 { % usage ran01 (picks random number between 0 and 1)
/xnorm 10000 def
rand xnorm mod cvr xnorm div } def
%%%%%
%%%%% end common area of all Smac postscript figures.
%%%%%
/sc {0.75 mul} def
100 srand
/needle { % usage: angle x y needle, draws a needle, which for angle = 0 extends from
% -0.5 sc cm to + 0.5 sc cm
% allows different gray values. Version 04-JUN-03
gsave
translate
rotate
/l 0.7 sc cm def % factor setting lateral size of hole
/x .2 sc cm def % 2 x: length of hole
/y .6 sc cm def % length of shaft
/radius l dup mul x dup mul add sqrt def
/alpha x l atan def
%
% make the shaft
%
thinlinewidth setlinewidth
1 setlinejoin
1 setlinecap
gsave
y 2 div x -1 mul add 0 translate
y -1 mul 0 cm moveto
0 sc cm 0 cm lineto
gsave
black setgray
stroke
grestore
%
% make the hole
%
newpath
x l -1 mul radius 90 alpha add 90 alpha sub arcn
x l radius 270 alpha add 270 alpha sub arcn
% fill
gsave
black setgray
stroke
grestore
grestore
0 0 tinyradius 1.3 mul dot
grestore
} def
2 cm 5 cm translate
/ymax 22 sc cm def
/xmax 18. cm def
/delx 1. sc cm def
/ncracks 18 def
thinlinewidth setlinewidth
gsave
200 thinlinewidth setlinewidth
ncracks {0 sc cm -.5 sc cm moveto 0 sc cm ymax .5 sc cm add lineto stroke delx 0 translate} repeat
stroke
grestore
white setgray
thinlinewidth setlinewidth
4000 {
/angle {360 nran} def
/xcenter {delx .5 mul ran01 mul} def
/ycenter {ymax ran01 mul} def
/xc xcenter def
/an angle def
xc an cos .5 sc cm mul abs sub 0 lt {black setgray}{white setgray} ifelse
2 nran 1 gt {/xc delx xc sub def } if
/xc xc ncracks 1 sub nran 1 sub delx mul add def
an xc ycenter needle
} repeat
showpage
Ah, working directly with Postscript – I remember it well! In the later 1980s, when Postscript printers were fairly new, our department got one. My colleague had written a versatile program to make good quality 2-D graphs. It was a command line program and could write to various backends. He wrote one to output Postscript and presto, we could make good graphs.
Except that I wanted to be able to label the graphs beyond his simple axis labels. I wrote a GUI program (in TurboPascal) that parsed his Postscript subset, extracted the data and displayed it, and could add labels, orient them, and move them around. Then it could turn that output into Postscript and merge it back into the original output.
Now we could make make publication-quality labeled graphs. Fun times!
Cool beans. I had a friend from high school whose dad ran an ironworks company (cutting, rigging, welding etc). After my first year in college as a math major I came back in the summer and my friend said his dad needed to figure out the shape to cut pipes when making one pipe intersect with another pipe, with the assumption being that the “base” pipe had at least the same diameter, usually larger, and the smaller pipe was coming in at an arbitrary angle with the smaller pipe axis offset from the base pipe axis an arbitrary amount. I remember I worked out a bunch of equations that would give the “height” of the cut as a function of the polar angle around the smaller pipe, and then we plotted it on screen on some ancient VGA garbage monitor and traced it in pencil onto tracing paper… Eventually I think we worked out how to do the calculation in postscript so we could print it out, cut out the template and wrap it around the pipe. Then you could use a soapstone to trace the curve onto the pipe and cut it with a cutting torch.
I don’t actually think they ever used it. It wasn’t convenient in the field (ie. no-one had a postscript printer out in a ironworks yard). But it was probably my first foray into “applied mathematics” as an 18 year old. Good times!
> GPT’s getting funnier.
That is rather questionable.
Anyways, here are two more:
>>please give me a joke with the punchline “AI has become Sauron’s Ring”
ChatGPT:
Why did the programmer refuse to use artificial intelligence anymore?
Because AI has become Sauron’s Ring: it’s powerful, tempting, and if you’re not careful, it’ll rule over everything!
>>that’s pretty good. please give me another one.
ChatGPT:
Why did the wizard switch from using magic to artificial intelligence?
Because he realized that AI has become Sauron’s Ring: it promises great power, but if you’re not careful, it’ll control you and your destiny!