GPT today: Buffon’s Needle in Python with plotting (and some jokes)

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.


Buffon's needles, by Werner Krauth, copyright 2006

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.


Buffon's needles by ChatGPT4 May 23, 2024 (version 1)

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.


Buffon's needles by ChatGPT May 23, 2024 (version 2)

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