Chapter 2
Can You Knock Down The Last Bowling Pin?
Magritte the bowler is back! This time, he is competing head-to-head against fellow bowler Fosse. However, rather than knocking down pins arranged in a triangular formation, they are trying to knock down pins (where is some very, very large number) arranged in a rhombus, as shown below:
When Magritte rolls, he always knocks down the topmost pin. Then, if any pin gets knocked down, it has a percent chance of knocking down either of the two pins directly behind it, independently of each other. (If there is only one pin directly behind it, then it too has a percent chance of being knocked over.)
Fosse is a stronger bowler than Magritte. Like Magritte, she always knocks down the topmost pin. But each pin that’s knocked down then has a percent chance (rather than Magritte’s percent) of knocking down any of the pins directly behind it.
What are Magritte’s and Fosse’s respective probabilities of knocking down the bottommost pin in the rhombus formation?
Solution
This is a directed bond percolation problem on the rhombic lattice, and the percolation probability for it has no known closed form. Each knocked pin wets each pin directly behind it with probability (the bond is “open”), and the question is whether the wet cluster from the top pin reaches the lone bottom pin.
What governs the outcome is the directed-percolation threshold of this lattice, around . Magritte plays at , comfortably below threshold: the wet front dies out after finitely many rows, so for a very large rhombus the bottom pin is essentially never reached, a probability that tends to . Fosse plays at , above threshold: the wet front survives with positive probability and can span the whole rhombus, but even then the single bottom pin is reached only sometimes. A large-lattice Monte Carlo estimate gives (Fosse’s value is a finite-size simulation estimate of an unsolved quantity, not an exact figure.)
The computation
Propagate the wet front row by row down the rhombus, vectorised over many parallel rolls. Expanding rows (top half) send each wet pin to its two children through independent open bonds; contracting rows (bottom half) collect each child from its two parents. The fraction of rolls whose front reaches the final single pin is the answer.
import numpy as np
def reach_bottom(p, n, runs, seed=0):
rng = np.random.default_rng(seed)
sizes = list(range(1, n+1)) + list(range(n-1, 0, -1)) # rhombus row sizes
wet = np.ones((runs, 1), bool) # topmost pin always knocked down
for r in range(len(sizes) - 1):
s, s2 = sizes[r], sizes[r+1]
if s2 > s: # expanding: parent j -> children j, j+1
nxt = np.zeros((runs, s2), bool)
nxt[:, :s] |= wet & (rng.random((runs, s)) < p)
nxt[:, 1:] |= wet & (rng.random((runs, s)) < p)
else: # contracting: child m <- parents m, m+1
nxt = (wet[:, :s2] & (rng.random((runs, s2)) < p)) | \
(wet[:, 1:] & (rng.random((runs, s2)) < p))
wet = nxt
return wet[:, 0].mean()
print(round(reach_bottom(0.7, 500, 5000), 3)) # Fosse ~0.58
print(round(reach_bottom(0.5, 500, 5000), 4)) # Magritte ~0