Skip to content

ResearchReplicationVolume 1, Issue 2, Winter 2025

Measuring a Universal Constant on a Laptop: The Site Percolation Threshold

A square grid of randomly filled cells suddenly gains an edge-to-edge path at one particular density. We ran 114,520 union-find sweeps in nine minutes on a laptop and measured that density as 0.5928171 plus or minus 0.0001110, which is 0.64 of our own error bars from the accepted 0.59274605.

Written jointly by the Science Journaling Club

Study
Computational replication
Review
Peer-edited by the club review board
Length
12 sectionsabout 25 min

Abstract

Fill the cells of a square grid at random. Each cell is occupied with probability pp, independently. Below a certain density the cells form scattered islands. Above it, one cluster reaches from top edge to bottom. That density, on an infinite grid, is the site percolation threshold. The square lattice has no closed form for it, so the constant has to be measured. We measured it. Our experiment is a computation, and nothing was observed in the physical world: a club program ran 114,520 Newman-Ziff union-find sweeps on lattices from L=16L = 16 to L=1024L = 1024, in 526.5 seconds of wall clock, seeded from 20251215. Extrapolate the half-crossing point p∗(L)p^*(L) to infinite size with the exact two-dimensional exponent 1/ν=3/41/\nu = 3/4, and the intercept comes out at pc=0.5928171±0.0001110p_c = 0.5928171 \pm 0.0001110, the error bar bootstrapped from the sweeps themselves. The accepted value is 0.59274605. We sit +0.0000710 above it. Call that +0.64 of our own error bars. The disagreement first appears in the fifth decimal place, and three checks stand behind the number. The union-find matched a separate flood fill on 11,400 random configurations, with zero mismatches, and the pipeline reproduced the exact crossing polynomial of the 3×3 and 4×4 lattices to within 0.5 standard errors at every point tested. And the crossing probability at the accepted threshold settled onto a size-independent 1/2, as conformal field theory says it must, moving from 6.60 standard errors high at L=16L = 16 to 0.08 at L=256L = 256. The fractal dimension of the largest cluster came out at 1.8823 against the exact 91/48 = 1.8958, a shortfall we keep and explain rather than fit away. Including our smallest lattice moves the answer to 0.5930250, 2.92 error bars out, and that single modelling choice is the largest one in the study.

A Constant With No Formula

Some constants can be derived: the bond percolation threshold of the square lattice is exactly one half. Kesten proved it in 1980 by a self-duality argument, complete and beautiful [1]. Site percolation on the same lattice has no such argument, and nobody has found a closed form. Nobody has proved that none exists either, so the value is known only because people keep measuring it, over and over, with better and better computers.

Newman and Ziff reported 0.59274621(13) in 2000 [2]. Lee reported 0.59274598(4) in 2008, after an extended study of which pseudo-random generators can be trusted at that precision [3], and in 2014 Jacobsen obtained 0.59274605(3) by a graph-polynomial method, not Monte Carlo at all [4]. We took the last of those: newest, most precise, and reached by a route that shares none of our machinery.

Our question was narrower than theirs. More interesting for a school club, we think. Not what pcp_c is; eight places settle that. How close can a laptop get in one evening, and can we say honestly how close we got?

0.5928171club estimate of p_c
±0.0001110bootstrap standard error
+0.64 σdistance from accepted value
526.5 stotal runtime, one laptop

The second number is the one we care about. An estimate without an honest error bar is a guess with decimals attached. Getting 0.5928 is easy. The work is knowing we get four decimal places, and that the fourth is shaky.

What the Lattice Is, and What It Is Not

The club has no laboratory. No porous rock here, no filter paper, no forest and no epidemic. We have a room with computers in it, so our research is computation, and the computation is the experiment. Every number below came out of a program we wrote, running on an array in memory, so when this article says "measured", it means measured from our own simulated lattices. Flip the coin ten thousand times. Reasoning about coins is a different activity.

The model is site percolation, the thing Broadbent and Hammersley coined the word for in 1957, while thinking about fluid spreading through a random medium [5]. Ours is the plainest version of it:

  • An L×LL \times L square grid. Each cell is occupied with probability pp, independently of every other cell.
  • Two occupied cells connect if they share an edge. Diagonal neighbours do not.
  • Boundaries are free. Nothing wraps around.
  • The lattice spans when a single cluster touches both end rows. Left-to-right crossings do not count.

Each of those four choices is a modelling decision. Each one changes the numbers: counting diagonal neighbours as connected would produce a different constant entirely. Periodic boundaries would converge toward the same limit faster, and counting a crossing in either direction would raise the crossing probability at every finite LL. We come back to the damage in §9.

The model leaves out most of what makes real spreading interesting. Sites here are independent. No clustering, no correlated disorder. The model holds no time in it: the lattice is static, and the only question is connectivity. Nothing flows along the path once it exists, so this says nothing about conductivity or flux, and real porous media are three-dimensional anyway, where the exponents differ and no exact values are known. We have a clean mathematical object that some real systems resemble, and the resemblance is a claim about universality classes, not a claim that our grid is a rock.

The Sweep

The naive way to measure a crossing probability. Pick a pp, generate a lattice, flood-fill it, record the verdict, repeat. Forty values of pp cost forty times the work. Newman and Ziff found the better way [2][7], and the whole feasibility of this study rests on it.

Take a uniformly random permutation of all N=L2N = L^2 sites and add them one at a time. After each addition, union the new site with any neighbours already present. The structure is a disjoint-set forest with union by size and path compression, the one Tarjan analysed in 1975 [8] and the ancestor of the cluster-labelling method Hoshen and Kopelman published a year later [9]. Each root carries two bits: top row touched, bottom row touched. Every union ORs the bits together. The first root to carry both marks the moment of spanning.

Record nspann_{\mathrm{span}}, the number of occupied sites at that instant. Spanning is monotone. Adding sites can never destroy a crossing, so one sweep hands you the whole microcanonical curve

RL(n)  =  P(spans∣exactly n sites occupied)  =  P(nspan≤n).R_L(n) \;=\; P(\text{spans} \mid \text{exactly } n \text{ sites occupied}) \;=\; P(n_{\mathrm{span}} \le n).

Converting that to the fixed-pp curve anybody actually wants is an identity, not an approximation. If sites are independent with probability pp, the number occupied is Binomial(N,p)(N, p), so

RL(p)  =  ∑n=0N(Nn)pn(1−p)N−n RL(n).R_L(p) \;=\; \sum_{n=0}^{N} \binom{N}{n} p^{n}(1-p)^{N-n}\, R_L(n).

One sweep therefore contributes to every point of the whole curve, hence 320 sweeps at L=1024L = 1024, less absurd than it sounds. The same fact makes the sweeps our sample size, since points along a curve are not independent of one another. They are the same sweeps viewed through different binomial weights.

0.000.250.500.751.000.400.450.500.550.600.650.700.750.80occupation probability pcrossing probability R(p)p_c = 0.5927CROSSING PROBABILITY, SEVEN LATTICE SIZES
  • L = 16
  • L = 32
  • L = 64
  • L = 128
  • L = 256
  • L = 512
  • L = 1024
Figure 1. The transition sharpening. Crossing probability RL(p)R_L(p) for the seven lattice sizes, each curve obtained by exact binomial convolution of that size's microcanonical sweep data. The dashed vertical line is the accepted threshold. At L=16L = 16 the curve climbs from 1% to 99% across a span of 0.296 in pp; by L=1024L = 1024 that span has shrunk to 0.0147, a factor of 20. In the infinite limit it becomes a step. The sharpening is what makes the constant well defined, and its rate is what lets us extrapolate.

The convolution window in nn holds essentially all the binomial mass. The narrowest carried 0.999999999939 of it, at L=512L = 512. We print that number in the output for a reason: a silently truncated convolution would bias every curve the same way, and would be close to impossible to spot afterwards.

Checking the Code Against Itself

Union-find code is short and fast. Path compression makes it shorter still, and easy to break in ways nobody sees. The failure mode that frightened us is not a crash. The dangerous version merges clusters correctly 99.99% of the time and shifts the threshold estimate by two parts in ten thousand, which is exactly the size of the effect we were trying to measure. So the code checks itself four ways before reporting anything, and the run halts if the first two fail.

Against an independent flood fill. 11,400 random configurations, six lattice sizes, pp drawn uniformly from 0.30 to 0.80. Both the spanning verdict and the size of the largest cluster were computed twice: once by the union-find, once by a breadth-first search written separately, sharing no code with it. Mismatches: zero. Both quantities, every time.

Against the sweep's own claim. The sweep asserts that the lattice first spans at site nspann_{\mathrm{span}}. So rebuild from the first nspann_{\mathrm{span}} sites of the permutation. Ask the flood fill whether it spans. It must. Remove the last site and ask again. It must not. Over 850 sweeps at three sizes, both conditions held every time.

Against exact enumeration. A 3×3 lattice has 29=5122^9 = 512 configurations and a 4×4 has 216=65,5362^{16} = 65{,}536, so brute force enumerated all of them, which gives the crossing probability as an exact polynomial in pp. Then the whole pipeline ran on those tiny lattices, 200,000 sweeps each. At p=0.59274605p = 0.59274605 the exact 3×3 answer is 0.566703307. We got 0.566421794. A gap of 0.25 standard errors. Worst of the ten comparisons: 0.50 standard errors. This check tests everything at once. A bug anywhere in the chain surfaces here.

Against conformal field theory. Cardy's 1992 formula gives the probability of a crossing at the critical point in any conformally mapped shape [10], and for a square of aspect ratio one that probability is exactly 1/2, independent of size. Smirnov later proved it rigorously for triangular-lattice site percolation [11]. So RL(pc)R_L(p_c) should converge to 1/2 as LL grows. Panel A of Figure 3 shows what we got: at L=16L = 16 we read 0.5165, or 6.60 standard errors above a half. Not a bug. The finite-size correction does precisely what theory says it should. By L=256L = 256: 0.5005, or 0.08 standard errors out.

The Arithmetic

The calculation, with nothing hidden in it.

Seven lattice sizes: 16, 32, 64, 128, 256, 512, 1024. Sweep counts in the same order: 40,000, 40,000, 20,000, 8,000, 5,000, 1,200 and 320. Total 114,520 sweeps. Run every sweep to the last site: 1,241,989,120 site placements. The campaign took 399.9 seconds. Validation and the convergence study took the rest of the 526.5.

For each size, bisection finds the pp where RL(p)=1/2R_L(p) = 1/2. Call it p∗(L)p^*(L). Finite-size scaling says

p∗(L)  =  pc  +  a L−1/ν,ν=43 exactly in two dimensions,p^*(L) \;=\; p_c \;+\; a\,L^{-1/\nu}, \qquad \nu = \tfrac{4}{3}\ \text{exactly in two dimensions,}

so L−1/ν=L−3/4L^{-1/\nu} = L^{-3/4}. Plot p∗(L)p^*(L) against L−3/4L^{-3/4}. The intercept is pcp_c. We did not fit the exponent ν=4/3\nu = 4/3. It descends from den Nijs's 1979 conjecture for the thermal exponent of the qq-state Potts model [12], confirmed by Nienhuis's Coulomb-gas work [13] and made rigorous for the triangular lattice by Smirnov and Werner [14].

Six values of L−3/4L^{-3/4}, 0.074325 down to 0.005524. Six matching values of p∗p^*. Weighted least squares, weights 1/σ21/\sigma^2. Intercept 0.5928171. Slope a=−0.007456a = -0.007456. Chi square 2.41 on 4 degrees of freedom, probability 0.66. The line fits about as well as lines ever fit six points.

The error bar comes from the sweeps and nothing else. Four hundred bootstrap replicates. Resample the sweeps at every size, independently and with replacement. Redo the convolution and the bisection at all six sizes. Redo the fit. The standard deviation of those 400 intercepts is 0.0001110. Subtract: 0.5928171−0.59274605=+0.00007100.5928171 - 0.59274605 = +0.0000710. Divide by the error bar: +0.64+0.64.

0.59000.59100.59200.59300.000.020.040.060.080.100.121632641282565121024club 0.5928171 ± 0.0001110accepted 0.59274605excluded from fitL raised to the power −3/4p*(L), the p where R = 1/2FINITE-SIZE SCALING: THE INTERCEPT IS THE ANSWER
Figure 2. The measurement, in one picture. Each filled circle is one lattice size's p∗(L)p^*(L) with its bootstrap error bar, plotted against L−3/4L^{-3/4}. The open circle at the far right is L=16L = 16, excluded from the headline fit. The solid line is the weighted fit through the six filled points, continued as a dashed line to show where L=16L = 16 would have to sit for a single power law to hold there. The square on the vertical axis is the intercept, which is the club's estimate of pcp_c; the dashed horizontal line is the accepted value. The whole study is the vertical distance between those two marks.
Lsweepsp*(L)boot s.e.L−3/4p*(L) − pcmean nspan/NRL(pc)mean SmaxSmax/L91/48
1640,0000.59015360.00029210.125000−0.00259250.5913670.51648490.060.469578
3240,0000.59225970.00020090.074325−0.00048640.5924510.505076319.890.448216
6420,0000.59259530.00017070.044194−0.00015070.5926140.5026371,167.030.439406
1288,0000.59241740.00016610.026278−0.00032860.5925240.5096434,309.310.436002
2565,0000.59273500.00013010.015625−0.00001110.5928790.50054015,812.270.429905
5121,2000.59271040.00017110.009291−0.00003570.5926840.50286458,769.000.429362
10243200.59290420.00020650.005524+0.00015820.5927970.479735216,149.460.424353

The two shaded rows are weak, for opposite reasons. L=16L = 16 has plenty of sweeps and a badly broken scaling law. L=1024L = 1024 has an excellent scaling law and 320 sweeps. Hence its RL(pc)R_L(p_c) of 0.4798. Furthest from a half, still only 0.73 standard errors out.

Working Notes From the Club Table

Meeting 1
First version: flood fill, count, repeat. One lattice per value of pp. Forty values of pp, five thousand lattices each, at L=256L = 256. Somebody estimated four hours for that one lattice size. We read the Newman-Ziff paper instead.

Meeting 1, later
Rewrote it as a sweep. Same L=256L = 256 point, all forty values of pp at once, 95 seconds. The speed-up is not a constant factor. A change of algorithm. The whole study exists because of it.

Meeting 2
Pure-Python union-find. Lattice held in a list with a one-cell border. The four neighbours are always s±1s\pm1 and s±Ws\pm W. No bounds tests. The border trick saved about 18% of the runtime. Numpy does the permutation and the convolution, nothing in the inner loop. That loop is pointer-chasing, and numpy is no help there.

Meeting 2
Timing is not linear in NN the way the paper promises. 18,922 sweeps per second at L=16L = 16. 2.5 at L=1024L = 1024. That is 7,500 times slower for 4,096 times the sites. The laptop drops its clock under sustained load. We report the numbers we got, not the numbers the algorithm deserves.

Meeting 3
Long argument about where to spend sweeps. The bootstrap error on p∗(L)p^*(L) falls as 1/m1/\sqrt{m}. But p∗(L)p^*(L) itself sits closer to pcp_c as LL grows, so sweeps spent on big lattices buy accuracy while sweeps spent on small ones buy precision about a point you then have to correct anyway. We settled on a constant wall-clock budget per size. Not optimal, but defensible.

Meeting 3
First full run had L=16L = 16 in the fit. Chi square came out 18.04 on 5 degrees of freedom, probability 0.0029. The fit was telling us the model was wrong. Correctly: one power law does not reach L=16L = 16. Dropped the point. Reported both ways.

Meeting 4
Seed fixed at 20251215. Nothing in the code changed after that. Every number here comes from one run of the file as it stands.

The Answer, With Its Error Bar

0.5928171, plus or minus 0.0001110.

The accepted value is 0.59274605, and the difference is +7.105×10−5+7.105\times10^{-5}, or 0.64 of our own standard error. Half the time, a one-sigma interval lands about that far out, which is neither suspicious nor impressive. A difference of 0.02 sigma would worry us more than one of 2.0.

The relative precision is 1.87×10−41.87\times10^{-4}, or 187 parts per million, and our one-sigma interval runs from 0.5927061 to 0.5929281. It holds both 0.5927 and 0.5928, so the fourth decimal is not safe. Every digit after that is noise we have no right to. Newman and Ziff got 0.59274621(13) in 2000, using a C implementation of the same algorithm and several orders of magnitude more sweeps [2], and our estimate differs from theirs by 7.1×10−57.1\times10^{-5}, which is 0.64 of our error bar and 546 of theirs. When two measurements disagree, the honestly large error bar is not the wrong one.

0.440.470.500.530.5616642561024Cardy: 1/2crossing probability at p_cA. R(p_c) against Llattice side L0.59250.59270.59290.5931acceptedclubrefits per binB. 400 bootstrap refitsbootstrap replicate of p_c
Figure 3. Two views of the same claim. A. The crossing probability evaluated at the accepted threshold, against lattice size, with the value 1/2 that Cardy's formula requires in the continuum limit. The L=16L = 16 point sits 6.60 standard errors high; by L=256L = 256 the gap is 0.08 standard errors. The error bars grow to the right because the sweep counts shrink. B. The 400 bootstrap refits that produced our error bar. The distribution is close to symmetric, its 95% interval runs from 0.5925836 to 0.5930203, and the accepted value sits in the body of it rather than out in a tail.

Panel B matters more than it looks. A bootstrap that came back skewed, or bimodal, or with a suspiciously small spread, would mean the bisection was landing on a flat part of the curve, or the resampling was failing to capture the real variability. It came back looking like a well-behaved error bar, and the 95% interval [0.5925836, 0.5930203] holds the accepted value with room on both sides.

Watching It Settle

An estimate right only at the end is poor comfort. So we reran the pipeline on the first mm sweeps only, for ten values of mm from 50 up to 40,000. Each reduced set bootstrapped 100 times.

0.59200.59250.59300.59350.59400.59451001,00010,000accepted 0.59274605sweeps used per lattice size, mextrapolated p_c from m sweepsTHE ESTIMATE SETTLING, WITH ITS OWN ERROR BAND
Figure 4. Convergence. The full extrapolation redone using only the first mm sweeps at every lattice size, with the shaded band showing plus and minus one bootstrap standard error. At m=50m = 50 the estimate is 0.5939574 and the band is wide enough to be useless. From m=1000m = 1000 onward the central value never leaves a window one error bar wide. The band narrows roughly as 1/m1/\sqrt{m} at first and then stops narrowing, because past m=320m = 320 the larger lattices have no more sweeps to give and only the small ones keep improving.

That curve is the honest story of the run. The first fifty sweeps produced 0.5939574, standard error 0.0006601, an estimate 1.8 standard errors high and essentially worthless. At m=100m = 100 the central value landed at 0.5927641. Closer to the accepted value than our final answer, by 0.0000181. Call that luck. The error bar of 0.0004677 says so. From m=1000m = 1000 onward the estimate stays within 0.0001 of where it finally lands, while the error bar shrinks by only a factor of 1.8 across a fortyfold increase in mm, because L=512L = 512 and L=1024L = 1024 have 1,200 and 320 sweeps between them and no amount of extra work at L=32L = 32 can fix that.

That flattening is the real limit of this study. We are not short of Monte Carlo samples. We are short of large lattices.

The Strongest Objection We Can Make

Now the case against our own number.

The scaling form p∗(L)=pc+aL−1/νp^*(L) = p_c + aL^{-1/\nu} is the leading term of an expansion rather than a law, and the next term decays faster, with an exponent Ziff and Newman measured at about 0.90 for one class of estimators [15], which we simply dropped. Our own table shows the consequence. The L=16L = 16 point sits 0.0026 below pcp_c while the single power law fitted to the larger sizes predicts about 0.0018 below, and including it drags the intercept up to 0.5930250 with a claimed error of 0.0000955, which is 2.92 standard errors from the accepted value. Without the chi square to warn us we would have published a number wrong by three of its own error bars, and the error bar would have been the smaller of the two, because adding a point always tightens a least-squares intercept. That is the trap, and it deserves saying out loud: a tighter error bar from a wrong model is worse than a wider one from a right model.

Second objection. We fixed 1/ν=3/41/\nu = 3/4 rather than fitting it. Let it float and the chi square is minimised at 1/ν=0.3001/\nu = 0.300, an absurd value implying ν=3.33\nu = 3.33, where the chi square is 2.065 against 2.408 at the correct exponent. A difference of 0.34 in chi square across that whole range means our data carry essentially no information about ν\nu, and they constrain pcp_c only because pcp_c is the intercept and the points sit close to it. So we import a theoretical result to get our headline number, and a reader who does not accept ν=4/3\nu = 4/3 should not accept our pcp_c either.

How much does the import cost? Refit at 1/ν=0.601/\nu = 0.60: 0.5928692. At 1/ν=1.001/\nu = 1.00: 0.5927658. That range spans 0.0001034, or 0.93 of our error bar. Notice the direction. The wrong exponent 1.00 lands closer than the correct 0.75. We call that coincidence, and we will not adopt it.

Third objection, and the one we think is largest. Free boundaries. Most published codes use periodic boundaries in the transverse direction, which removes the edge effect that dominates our small lattices, and we chose free boundaries only because they are the easiest thing to define and the easiest thing to explain. That same choice forced us to drop L=16L = 16. A different boundary condition, with everything else identical, would have given a different set of p∗(L)p^*(L), a different slope, probably a usable L=16L = 16 point, and a different final digit. We do not know what that digit would have been, because we did not run it. Saying so beats guessing.

Fourth. Our criterion is RL(p∗)=1/2R_L(p^*) = 1/2. Use the mean occupancy at first spanning instead, a different estimator of the same limit, and the same arithmetic yields 0.5928214, a difference of +0.0000754+0.0000754 from the accepted value rather than +0.0000710+0.0000710. The two routes agree to within a twentieth of an error bar, reassuring though not independent: both come from the same sweeps.

Where We Fall Short

The threshold came out well. The fractal dimension did not. We would redo that part first.

At the critical point the largest cluster is a fractal. Its mass scales as Smax⁡∼LDS_{\max} \sim L^{D} with D=91/48=1.8958333D = 91/48 = 1.8958333, a value that follows from the same Coulomb-gas machinery that gives ν=4/3\nu = 4/3 [13] and is rigorous on the triangular lattice [14]. We measured Smax⁡S_{\max} at fixed occupancy n=round(pcN)n = \mathrm{round}(p_c N), all seven sizes. Then a straight line through the log-log plot.

Over all seven sizes we get D=1.86068D = 1.86068, 0.035 low, with chi square 160.4 on 5 degrees of freedom. That chi square is a confession. A fit that bad means the model is not a pure power law over that range, and forcing one through anyway produces a slope that means nothing. Drop L=16L = 16 and L=32L = 32: D=1.88231D = 1.88231, chi square 0.8 on 3. Still 0.0135 below the exact value. But now the fit describes the points.

0.4200.4300.4400.4500.4600.4700.4801632641282565121024lattice side Lmean S_max divided by L^(91/48)COMPENSATED PLOT: FLAT WOULD MEAN NO CORRECTIONS
  • measured
  • club fit, D = 1.8823
Figure 5. Where the shortfall lives. Mean Smax⁡S_{\max} divided by L91/48L^{91/48}. If the pure power law held with the exact exponent, this would be a horizontal line. Instead it falls from 0.4696 at L=16L = 16 to 0.4244 at L=1024L = 1024, a drift of 9.6% across a factor of 64 in LL. The dashed line is what our fitted exponent 1.8823 implies. The curvature at the left is the correction to scaling; that the points still tilt at the right is why our exponent comes out low.

So our fractal dimension disagrees with the exact value. A real disagreement, not a statistical fluctuation. The reason is not mysterious. The compensated plot has not flattened even at L=1024L = 1024, so corrections to scaling are still contributing at our largest size, and a two-parameter power-law fit over a range that short will always come out low. Fixing it needs either lattices several times larger, which the laptop cannot reach in a night, or a fit that includes a correction term, which seven points cannot constrain. So we report 1.8823 and the 0.0135 shortfall, rather than restricting the fit quietly until the number looked right.

The contrast with the threshold is useful. pcp_c is an intercept, and intercepts forgive: the corrections we ignored push p∗(L)p^*(L) around, but they vanish as LL grows and the extrapolation absorbs most of what is left. DD is a slope, and slopes do not forgive, because a slow drift in the amplitude looks exactly like a change in the exponent, which is why one answer here is good to four decimals and the other to two. Same data and same code, one evening of computing.

Reproducing This

Four decimal places of a constant that has no formula. Under nine minutes, on a machine a student already owns. Getting the same numbers takes two lines.

$ cd scijournal
$ python analysis/percolation-threshold.py > analysis/percolation-threshold-output.txt

Python 3.12.3 and numpy 2.4.2. The script sets the BLAS thread-count variables to 1 before importing numpy. Then it runs eight worker processes of its own. Expect about nine minutes. Ours printed total runtime: 526.5 s on the last line. A machine that does not throttle will be faster, and one with fewer than eight cores slower, roughly in proportion.

The master seed is 20251215, and every worker stream is a numpy.random.SeedSequence with entropy [20251215, L, chunk_index]. So the output does not depend on chunk scheduling, or on the number of workers. If your output differs from ours in any digit, either numpy changed its generator or something is wrong, and we would like to hear about it.

The last section of the output file is a machine-readable block: lines beginning CURVE, SIZE, CONV, FIT, BOOTHIST and NSPAN, and every figure in this article and every number in the table came from those lines and from nothing else.

Three things we would change next time. Periodic boundaries in the transverse direction, for the reasons in §9. A cell-to-cell threshold estimator, which converges without needing the correction exponent [15]. And an inner loop in a compiled language, because our bottleneck is not the algorithm but CPython's pointer-chasing, and a factor of fifty there would put L=4096L = 4096 within reach and produce a fractal dimension worth printing. The rest we would keep. Especially the four validations, which cost about a fifth of the runtime and are the only reason we are willing to put a number in a headline.

One last thought about why this matters outside a physics course. Percolation is the standard model for spreading on a spatial network, and Grassberger showed in 1983 that the general epidemic process maps onto exactly this transition [16]. The threshold is what makes such models interesting. Below it nothing spreads. Above it everything does. The crossover is as sharp as our Figure 1 says. Jacobsen's value is 3,700 times more precise than ours [4]. Ours took nine minutes and a laptop, and we can tell you exactly how much to trust it.

References

Reproduce these numbers

From the analysis folder, run:

python percolation-threshold.py > percolation-threshold-output.txt
Seed
20251215
Analysis code
percolation-threshold.py39 kB