3 min read

Calculate pi by throwing needles

Suppose that you drop a short needle on ruled paper, what is then the probability that the needle comes to lie in a position where it crosses one of the lines? - Georges Buffon, 1777

When the needle is the right length, the answer is pi!

Here is a quick example in R of approximating the value of Pi by digitally tossing a box of matches onto a sheet of lined paper.

library(retistruct)

Make a needle

match_stick_length <- 20
number_of_matches_in_a_box <- 10000
distance_between_surface_lines <- 40

point_1 <- c(-(match_stick_length/2),(match_stick_length/2))
point_2 <- c(0,0)
single_match <-cbind(point_1, point_2)
rownames(single_match)<- c("point_1","point_2")
colnames(single_match)<- c("x","y")
single_match
##           x y
## point_1 -10 0
## point_2  10 0
## Warning: package 'rgl' was built under R version 3.4.4
## Warning: package 'shiny' was built under R version 3.4.4

Throw the match

First rotate around the origin

  angle <- runif(1, min=1, max=360)*pi/180
  xy <- as.matrix(single_match)
  
  # Rotation
  cos.angle <- cos(angle)
  sin.angle <- sin(angle)
  after_rotation <- xy %*% t(matrix(c(cos.angle, sin.angle, -sin.angle, 
        cos.angle), 2, 2))
  
after_rotation
##               [,1]      [,2]
## point_1  0.1190636 -9.999291
## point_2 -0.1190636  9.999291

Then translate that line onto the table

  # Translation
  translation.x <- runif(1, min=10, max=90)
  translation.y <- runif(1, min=10, max=90)
  translation <- matrix(c(translation.x,translation.x,translation.y,translation.y), 2, 2)
  new_coord <- after_rotation + translation
  colnames(new_coord) <- c("x","y")
  rownames(new_coord) <- c("Point_1","Point_2")
new_coord <- after_rotation + matrix(c(50,50,50,50),2,2)

Repeat for an entire box of matches

Throwing_matches <- function(){
  single_match <-cbind(c(-5,5),c(0,0))
  angle <- runif(1, min=1, max=360)*pi/180
  xy <- as.matrix(single_match)
  
  # Rotation
  cos.angle <- cos(angle)
  sin.angle <- sin(angle)
  after_rotation <- xy %*% matrix(c(cos.angle, sin.angle, -sin.angle, 
        cos.angle), 2, 2, byrow=TRUE )
  
  # Translation
  translation.x <- runif(1, min=10, max=90)
  translation.y <- runif(1, min=10, max=90)
  translation <- matrix(c(translation.x,translation.x,translation.y,translation.y), 2, 2)
  new_coord <- after_rotation + translation
  colnames(new_coord) <- c("x","y")
  rownames(new_coord) <- c("Point_1","Point_2")
  
  return(new_coord)   
}

Throwing_matches()
##                x        y
## Point_1 50.61685 36.22574
## Point_2 41.00933 38.99984
for(i in 1:1000){
Throwing_matches()
}

surface_line <- function(line_height = 0){matrix(c(0,100,line_height,line_height), 2, 2, byrow=FALSE)}
##      [,1] [,2]
## [1,]    0   20
## [2,]  100   20

one_match <- Throwing_matches()
one_surface_line <- surface_line(30)
line.line.intersection(one_match[1,], one_match[2,], one_surface_line[1,], one_surface_line[2,], interior.only = TRUE)
## [1] NA NA

Make a list of surface lines and a list of matches

list_of_matches <- array(NA,dim = c(2,2,number_of_matches_in_a_box))
list_of_surface_lines <- array(NA,dim = c(2,2,6))

for(i in 1:number_of_matches_in_a_box){
  list_of_matches[,,i] <- Throwing_matches()
}

line_placement <- seq(0,100, by=20)
for(i in 1:6){
  list_of_surface_lines[,,i] <- surface_line(line_placement[i])
}

crosses <- matrix(NA, 1, 10, byrow=TRUE)

for(i in 1:dim(list_of_matches)[3]){
for(j in 1:dim(list_of_surface_lines)[3]){
  crosses <- rbind(crosses, c(
    line.line.intersection(list_of_matches[1,,i], list_of_matches[2,,i], list_of_surface_lines[1,,j],     
    list_of_surface_lines[2,,j], interior.only = TRUE), 
    
    list_of_matches[1,,i], list_of_matches[2,,i], list_of_surface_lines[1,,j], list_of_surface_lines[2,,j])
    )
  }
}

crosses <- crosses[which(is.na(crosses[,1]) == FALSE),]
colnames(crosses) <- c("x.cross", "y.cross", "x.p1", "y.p1", "x.p2", 
                    "y.p2", "x.surface.p1", "y.surface.p1", "x.surface.p2", 
                    "y.surface.p2")
head(crosses)
##       x.cross y.cross     x.p1     y.p1      x.p2     y.p2 x.surface.p1
## [1,] 12.31511      20 14.36624 22.62863  8.214425 14.74477            0
## [2,] 45.79014      80 44.77192 82.43566 48.628934 73.20942            0
## [3,] 70.15395      20 74.24544 28.20362 69.782313 19.25485            0
## [4,] 49.66294      60 49.75337 59.89791 43.122492 67.38333            0
## [5,] 59.91925      20 60.98953 22.25686 56.704609 13.22141            0
## [6,] 74.89057      60 72.66679 55.32310 76.960902 64.35419            0
##      y.surface.p1 x.surface.p2 y.surface.p2
## [1,]           20          100           20
## [2,]           80          100           80
## [3,]           20          100           20
## [4,]           60          100           60
## [5,]           20          100           20
## [6,]           60          100           60

pi_estimate <- (2* match_stick_length * number_of_matches_in_a_box)/(distance_between_surface_lines * length(crosses[,1]))
## [1] 3.194888