"What do the zeros actually have to do with the primes?"
The short version
Everything. The connection is not an analogy, it is an identity. Chebyshev's prime-counting function psi(x) = sum_{p^k <= x} log p — a staircase that jumps by log p at every prime power — can be written exactly as a smooth main term x minus a sum of waves, one wave per non-trivial zero of zeta:
psi_0(x) = x - sum over rho x^rho / rho - log(2 pi) - (1/2) log(1 - x^-2)Each zero rho = beta + i gamma contributes a pure oscillation in the variable u = log x, of frequency gamma and amplitude 2 x^beta / |rho|. The primes are the interference pattern of these waves; the zeros are the Fourier spectrum of the primes. Run the sum one way and the zeros rebuild the primes; run it the other way and a bare sum of cosines over the zeros develops spikes at exactly u = log p^k, and at no other point. This is why the Riemann Hypothesis is not an aesthetic preference: beta is the growth exponent of the error in every prime count. RH says every beta equals 1/2 — that the primes are as regular as they could possibly be. A single zero with beta > 1/2 would be a permanent anomaly in the distribution of the primes of size x^beta, and no amount of averaging would ever wash it out.
1. The bridge: -zeta'/zeta
The Euler product (see docs/01-sums-integrals-and-continuation.md) is zeta(s) = prod_p (1 - p^-s)^{-1} for Re(s) > 1. Take the logarithm to turn the product into a sum, then differentiate:
log zeta(s) = - sum_p log(1 - p^-s) = sum_p sum_{k>=1} p^(-ks) / k
-zeta'(s)/zeta(s) = sum_p sum_{k>=1} (log p) p^(-ks) = sum_{n>=2} Lambda(n) n^-swhere the von Mangoldt function Lambda(n) is log p if n = p^k for some k >= 1, and 0 otherwise. This is the whole reason Lambda exists: it is the arithmetic function whose Dirichlet series is the logarithmic derivative of zeta. Weighting p^k by log p instead of counting primes by 1 is not cosmetic — it is what makes the analysis clean, and pi(x) is recovered from psi(x) afterwards by Möbius inversion (§8).
Sanity check at s = 2 + 0.3i (mpmath, series truncated at n = 3·10^5):
-zeta'/zeta(s) = 0.489626756937 - 0.240714012303 i
sum Lambda(n) n^-s = 0.489628660161 - 0.240716580265 iThe residual is the truncation tail, as it should be.
Why this is the bridge. The left side knows about the zeros — a logarithmic derivative has a simple pole with residue equal to the multiplicity at every zero of zeta, and a simple pole with residue -1 at the pole s = 1. The right side knows about the primes. One function, two faces — exactly the geometry/spectrum split you already met in docs/02-theta-heat-and-modularity.md, where the windings of heat around a circle and the eigenvalues of the Laplacian were two faces of theta. Here the windings become prime powers and the eigenvalues become zeros.
2. Perron: extracting a staircase from a Dirichlet series
To get from sum Lambda(n) n^-s to sum_{n <= x} Lambda(n) you need a device that turns a Dirichlet series into a partial sum. That device is Perron's formula, and it rests on one integral:
(1/2 pi i) integral over Re(s)=c of y^s / s ds = 1 if y > 1, 0 if y < 1, 1/2 if y = 1(c > 0; the integral is a principal value.) It is a sharp cutoff written analytically: close the contour to the left when y > 1 and pick up the pole at s = 0; close to the right when y < 1 and pick up nothing. Put y = x/n and sum against Lambda(n):
psi_0(x) = (1/2 pi i) integral over Re(s)=c of (-zeta'(s)/zeta(s)) · x^s / s ds , c > 1The subscript on psi_0 is the price of that 1/2 in the cutoff: at a prime power the contour integral returns the midpoint of the jump, psi_0(x) = psi(x) - Lambda(x)/2. Away from prime powers psi_0 = psi. This matters when you test the formula at x = 8 or x = 121; the code handles it (explicit.convergence_table reports both psi_true and psi0_true).
3. Hadamard: where the poles are
Now push the contour left. Everything the answer contains is a residue, so the question becomes: where are the poles of (-zeta'/zeta)(s) · x^s/s? To answer that globally — not just where the Euler product converges — you need the factorisation of the completed function xi(s) = (1/2) s(s-1) pi^(-s/2) Gamma(s/2) zeta(s) from docs/03-functional-equation.md.
THEOREM (Hadamard, 1893). xi is entire of order 1, and therefore factors over its zeros:
xi(s) = e^(A + B s) prod over rho (1 - s/rho) e^(s/rho)the product running over the non-trivial zeros of zeta, which are exactly the zeros of xi. The e^(s/rho) factors are the genus-1 convergence factors, needed because sum 1/|rho| diverges while sum 1/|rho|^2 converges.
Take the logarithmic derivative of xi in both of its forms — the Hadamard product, and the definition in terms of zeta and Gamma — and solve for zeta'/zeta. Out comes a global partial fraction expansion whose content, stripped of the constants, is:
-zeta'(s)/zeta(s) has a simple pole, residue +1, at s = 1 (the pole of zeta)
a simple pole, residue -1, at each s = rho (non-trivial zeros)
a simple pole, residue -1, at each s = -2n (trivial zeros)That is the entire input. The rest is bookkeeping.
4. Collecting the residues
Sweep the contour from Re(s) = c > 1 off to Re(s) = -infinity. The integrand is (-zeta'/zeta)(s) · x^s/s, so a pole of -zeta'/zeta at s_0 with residue r contributes r · x^(s_0)/s_0, and the pole of the factor 1/s at s = 0 contributes (-zeta'/zeta)(0):
| pole | contributes | value |
|---|---|---|
s = 1 (pole of zeta) | + x^1/1 | x — the main term, i.e. the PNT |
s = rho | - x^rho/rho | the oscillation, one term per zero |
s = 0 (from 1/s) | (-zeta'/zeta)(0) | -log(2 pi) |
s = -2n | - x^(-2n)/(-2n) | sums to -(1/2) log(1 - x^-2) |
Two of these deserve a check rather than a nod:
zeta'(0)/zeta(0) = log(2 pi). Verified to 25 digits: both equal1.837877066409345483560659….sum_{n>=1} x^(-2n)/(2n) = -(1/2) log(1 - x^-2), by the Mercator series forlog(1-z)atz = x^-2. This term is genuinely tiny:5.03e-3atx = 10,5.00e-5atx = 100. It is the trivial zeros — and they contribute almost nothing, which is exactly why nobody cares about them.
THEOREM (von Mangoldt, 1895). For x > 1,
psi_0(x) = x - sum over rho x^rho/rho - log(2 pi) - (1/2) log(1 - x^-2)the rho-sum taken as lim_{T -> inf} sum over |Im rho| < T, i.e. in conjugate pairs. That symmetric pairing is not optional: the series is only conditionally convergent, and reordering it would change the answer.
5. Each zero is a wave
Pair rho = beta + i gamma with its conjugate. Since x^rho = x^beta · e^(i gamma log x) and 1/rho = e^(-i arg rho)/|rho|,
x^rho/rho + x^rhobar/rhobar = 2 Re(x^rho/rho)
= 2 x^beta cos(gamma log x - arg rho) / |rho|On RH, beta = 1/2, so |rho| = sqrt(1/4 + gamma^2) and arg rho = arctan(2 gamma), and the working form the code evaluates (explicit.psi_curve) is
psi_0(x) ≈ x - 2 sqrt(x) · sum over gamma > 0 of cos(gamma log x - arg rho)/|rho|
- log(2 pi) - (1/2) log(1 - x^-2)Read that as music. In the variable u = log x each zero is a pure tone:
- frequency
gamma— the first zero,gamma_1 = 14.134725141…, has wavelength2 pi/gamma_1 = 0.4445inlog x, i.e. one full cycle every timexis multiplied bye^(2 pi/gamma_1) = 1.5597; - amplitude
2 x^beta/|rho|— decaying with the zero's height, so the low zeros dominate; - phase
arg rho.
Concretely at x = 100: the smooth part alone gives x - log 2pi - (1/2)log(1 - x^-2) = 98.1622, against psi(100) = 94.0453 — an overshoot of 4.1169. The single lowest zero contributes a wave worth 1.0579 there, cutting the error to 3.06. Adding more zeros:
N zeros gamma_N psi estimate error vs psi(100) = 94.0453
0 — 98.1622 +4.1169
1 14.135 97.1042 +3.0589
10 49.774 95.0403 +0.9950
50 143.112 94.4488 +0.4034
100 236.524 93.6700 -0.3753
500 811.184 93.9906 -0.0548Reproduce it exactly:
from zeta.explicit import convergence_table
for r in convergence_table(100.0, 500, counts=(0, 1, 10, 50, 100, 500)):
print(r["n_zeros"], round(r["gamma_max"], 3), round(r["psi_est"], 4), round(r["error"], 4))Note that the error is not monotone — 100 zeros does worse than 50. That is the signature of a conditionally convergent Fourier series being truncated: you are watching Gibbs-type ringing, not a bug. The truncated formula does have a rigorous error term; the standard statement (Davenport, Multiplicative Number Theory, §17) has the shape O(x log^2(xT)/T + log x) when the sum is cut at height T — I am quoting the shape, so check the exact statement before using it quantitatively. The practical moral is already visible in the table: to resolve psi near x you need zeros up to height of order x.
6. Why RH is exactly an error-term statement
Look at the amplitude once more: 2 x^beta / |rho|. The real part of a zero is an exponent on x. That one observation is the entire significance of RH.
THEOREM. Let Theta = sup { Re(rho) } over the non-trivial zeros. Then, for every eps > 0,
psi(x) - x = O(x^(Theta + eps)) and psi(x) - x = Omega(x^(Theta - eps))So the size of the prime-counting error is precisely x^Theta — no more, and no less. Hardy proved infinitely many zeros lie on the critical line, so Theta >= 1/2, and
RH <=> Theta = 1/2 <=> psi(x) = x + O(sqrt(x) log^2 x)
<=> pi(x) = li(x) + O(sqrt(x) log x)The last equivalence is von Koch's theorem (1901). Under RH the constants can even be made explicit: Schoenfeld (1976) proved |psi(x) - x| < (1/(8 pi)) sqrt(x) log^2 x for x >= 73.2, and |pi(x) - li(x)| < (1/(8 pi)) sqrt(x) log x for x >= 2657. (I am confident in the constant 1/(8 pi) and the shape; verify the thresholds against the paper before quoting them.)
Nor could the bound be improved much. THEOREM (Littlewood, 1914). psi(x) - x = Omega_±(sqrt(x) log log log x). The sqrt(x) is genuinely there; only logarithms are negotiable. So RH is the statement that the error is as small as the zeros already force it to be — "the primes are as regular as they could possibly be."
What one rogue zero would do. Suppose — counterfactually — a single zero sat at beta = 0.6, gamma = 100. Its wave has amplitude 2 x^0.6/100, against an RH-permitted total error of order sqrt(x). The ratio is x^0.1/50, which passes 1 at x = 50^10 ≈ 9.77e16. Beyond that point this one zero out-shouts every other effect in the theory, forever, with a clean periodic signature of wavelength 2 pi/100 in log x. It would not be a correction; it would be a permanent, ever-growing, in-principle-detectable bias in how the primes are laid out. That is what "off the line" costs.
7. The dual direction: the spectrum of the primes
Differentiate the explicit formula with respect to u = log x. The staircase psi_0(e^u) becomes a comb of delta spikes, one at each u = log n, with weight Lambda(n):
sum_{n>=2} Lambda(n) delta(u - log n) = e^u - 2 e^(u/2) sum_{gamma>0} cos(gamma u) - 1/(e^(2u) - 1)Divide by e^(u/2) and discard the smooth pieces. The purely oscillatory object
D(u) = -2 sum over gamma > 0 of w(gamma) cos(gamma u)— a bare sum of cosines over the zeros, containing no arithmetic input whatsoever — is a spike train with a peak at every u = log p^k and nowhere else; and after normalising by the window w, the peak at u = log n has height Lambda(n)/sqrt(n). That is zeta.explicit.prime_spectrum:
import numpy as np
from zeta.explicit import prime_spectrum, spectrum_peaks
u = np.linspace(0.2, 3.2, 4000)
sp = prime_spectrum(1000, u, window="gauss") # 1000 zeros, Gaussian taper
for p in spectrum_peaks(u, sp, n_peaks=10):
print(round(p["u"], 4), round(p["x"], 3), p["nearest_n"],
round(p["height"], 4), round(p["lambda_over_sqrt_n"], 4))Output, run in this repository (rows re-sorted by u for readability — spectrum_peaks returns them in order of decreasing height; the first call computes the zeros and takes a minute, after which they are cached to data/):
u x = e^u n peak height Lambda(n)/sqrt(n)
0.6931 2.000 2 0.4792 0.4901
1.0986 3.000 3 0.6258 0.6343
1.6094 5.000 5 0.7086 0.7198
1.9459 7.000 7 0.7194 0.7355
2.1972 9.000 9 0.3511 0.3662 <- the prime power 3^2
2.3979 11.000 11 0.7078 0.7230
2.5650 13.000 13 0.6792 0.7114and between the spikes the signal is flat: D(log 6) = -0.009, D(log 10) = -0.013, D(log 2.5) = -0.005. Nothing goes into this computation but a list of zero ordinates, and out come the primes with their correct multiplicities. Note 9 appearing at roughly half the height of a prime (log 3 / 3 versus log 3 / sqrt 3): the spectrum sees prime powers, properly weighted, because Lambda does.
The rigorous version of this duality is the Riemann–Weil explicit formula: for a suitable test function f with Fourier transform fhat, a sum of f(gamma) over the zeros equals a sum of fhat(log p^k) · Lambda(p^k)/sqrt(p^k) over prime powers, plus archimedean terms. Reading peak heights off a plot the way we just did is the practical, HEURISTIC form of that theorem; the theorem itself is a statement about smoothed sums, which is precisely why the code applies a window.
8. pi(x), Riemann's R, and the li overestimate
Riemann's own 1859 formula was for pi, not psi. Set J(y) = sum_{k>=1} pi(y^(1/k))/k; then log zeta(s) = s · integral J(y) y^(-s-1) dy, and the same contour argument gives
J_0(y) = li(y) - sum over rho li(y^rho) - log 2 + integral from y to inf of dt/(t(t^2-1) log t)Möbius inversion — a finite sum, since J(y) = 0 for y < 2 — returns pi(x) = sum_n mu(n)/n · J(x^(1/n)), whose headline form is
pi(x) ≈ R(x) - sum over rho R(x^rho), R(x) = sum_{n>=1} mu(n)/n · li(x^(1/n))R is the Riemann R function (explicit.R, evaluated via Gram's rapidly convergent series R(x) = 1 + sum_{k>=1} (log x)^k / (k · k! · zeta(k+1))). It is a strikingly better approximation to pi(x) than li(x) is, because its -(1/2) li(sqrt x) term subtracts off the systematic overcount coming from the squares of primes:
x pi(x) li(x) - pi(x) R(x) - pi(x)
10^6 78498 129.5 29.4
10^8 5761455 754.4 96.9
10^10 455052511 3103.6 -1827.7
10^12 37607912018 38262.8 -1475.8Driving the formula from the zeros works too: pi_from_zeros(100, 500) returns 24.9899, against pi(100) = 25.
Notice that li(x) - pi(x) is positive in every row — as it is in every row anyone has ever computed. It is very tempting to conjecture that pi(x) < li(x) always. That conjecture is false.
THEOREM (Littlewood, 1914). pi(x) - li(x) changes sign infinitely often.
The mechanism is visible in the formula above. The -(1/2) li(sqrt x) bias is what the zero-waves must overcome, and Littlewood showed they eventually do — but only through a delicate near-alignment of very many waves at once, which is why it takes so absurdly long. No crossing point is known explicitly. Skewes (1933) gave the first upper bound, e^(e^(e^79)), assuming RH, and in 1955 an unconditional bound usually quoted as e^(e^(e^(e^7.705))). Modern work has located a region where a crossing provably occurs, commonly cited as just under 1.4 × 10^316 (Bays and Hudson, 2000, exhibited a crossing region near 1.398 × 10^316; later authors refined the leading digits slightly downward). Direct computation has confirmed pi(x) < li(x) out beyond 10^19 with no crossing. Hedge: I am confident in the order of magnitude 10^316 and in the Littlewood and Skewes attributions; treat the leading digits, and the exact extent of the verified range, as "commonly cited" rather than checked here.
Where to go next
zeta/explicit.py— every formula above, implemented and documented:psi_from_zeros,psi_curve,psi_staircase,convergence_table,J_from_zeros,pi_from_zeros,R,prime_spectrum,spectrum_peaks.tests/test_explicit.pyis the regression suite; running it (.venv/bin/python -m pytest tests/test_explicit.py) is the fastest way to convince yourself that none of this is hand-waving.- The one plot to make. Overlay
explicit.psi_staircase(the true jagged staircase) withexplicit.psi_curveforN = 1, 10, 100, 1000zeros on2 < x < 100. Watch a smooth line grow corners, then risers, then land on the steps. Nothing makes the duality as vivid. docs/03-functional-equation.md— the symmetryxi(s) = xi(1-s)that makes the zeros come in pairsrho, 1 - rhoand puts the mirror atRe(s) = 1/2. This document explains why that mirror matters; that one explains why it exists.zeta/zeros.py— the supply side:first_n_zeros,zeros_from_scratch, andverify_rh_up_to, which proves — by Turing's method, not by statistics — that every zero below a given height is simple and on the line.zeta/statistics.py— one level deeper. §5 treats the zeros as a bag of independent frequencies; they are not. Their spacings follow the GUE pair-correlation law, which is a statement about how the waves inpsiconspire with each other.docs/05-de-bruijn-newman.mdandzeta/heatflow.py— the frontier: deformXiby heat flow and ask when the zeros become real.