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

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

  1. 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!

  2. > 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!

Leave a Reply

Your email address will not be published. Required fields are marked *