← Interactive tools

The making of the zeta process

A curated transcript of the conversation that built the zeta process.

A curated transcript of the conversation (with Claude Code) that produced the zeta process for tao-web — an interactive realization of the "zeta process" of §10.2 of "Primitive sets and von Mangoldt chains: Erdős problem #1196 and beyond" — from confirming the mathematics through the divisibility-chain bubbles, the fading-trail animation, and the two accumulating comparison panels.

How to read this. As with the spacetime, Gilbreath, and random-variable transcripts, the aim is to record the process:

The core was checked with headless Node unit tests throughout (42 checks: the geometric thresholding, the divisibility chain, ζ(s), the zeta distribution, and the νΛ hitting mass), and the UI wiring with a DOM-shim smoke test (31 checks). Nothing was published until each stage had been tested.

1. The idea — "do you understand the math first?"

Terence (opening, attaching the paper):

I'd like to start designing a new applet to visualize the "zeta process" described in Section 10.2 of the attached paper. We can mimic the animation style approach to random variable visualization in the RV app. Here, we have some source variables E_{p,k} for various primes p […] and then we have a "time" variable s>1 (which could be displayed as some sort of vertical slider in log scale proportional to log 1/(s-1)). For each s, there is a thresholding applied […] which produces the variables e_{p,s}, which then generate the prime factorization of random natural number Z_s, which also is distributed by a zeta distribution. The Z_s is piecewise constant in s and so each instantiation […] gives rise to some random divisibility chain that could somehow be displayed vertically next to the s timeline […]. This is a tricky and complex task; the first question is whether you understand the math behind the zeta process?

Rather than start coding, Claude wrote back a full derivation to be checked: each clock Ep,k is exponential with rate log p, so the exponent ep,s (the length of the leading run beating s) is geometric and Zs = ∏ pep,s has the zeta distribution by the Euler product. The one subtle point it flagged: for a fixed set of clocks, the jumps of the chain happen at running-minimum thresholds and multiply by a whole prime power pj — so the chain is exactly the von Mangoldt downward chain and can "jump over" numbers (straight from 1 to 4). And the probability that n ever lies on the chain is the invariant von Mangoldt weight νΛ(n) = ∫₁ log n /(ζ(s) ns) ds.

Terence confirmed the reading, and set aside one feature for later:

A later upgrade to the app could also try to visualize the hitting measure nu_Lambda and compare it against empirical hitting measure, but this can be deferred for now.

2. Design — one shared s-axis

Claude proposed a layout in which everything reads against a single vertical s-axis (log-scaled in 1/(s−1), s→1 at the top): the per-prime clock panels on the right, the threshold as a draggable line, and the divisibility chain as bubbles beside the axis — so "each node near its s" falls out for free. It sketched a staged build (tested core → static layout → animation + Zs histogram → the deferred νΛ view) and asked about category and defaults.

Terence:

Proposed layout looks good. I like the default ranges for p, s (one can later add options to adjust them). Number theory is a good category. Let's start Stage 0

3. Stage 0 — the tested core, and a precompute question

Claude wrote zeta.js: a seedable RNG and exponential sampler, the leading-run statistic, the chain builder (which groups running-minimum plateaus into single prime-power steps), ζ(s) by Euler–Maclaurin, and the νΛ integral. Forty-two Node checks confirmed the key facts — including the empirical hitting frequency of n converging to νΛ(n), which validates the chain builder and the integral together.

Terence asked a performance question:

Would it be helpful to precompute zeta (and potentially also nu) to reasonable accuracy if that would free up compute at run-time? (Of course one can only precompute a finite number of values, so would have to fall back to direct calculation if one is out of range of the precomputed tables)

Claude's answer changed the plan slightly: ζ(s) is ~20 float operations and is needed only once per frame, so a table would add interpolation error for no gain — keep it direct. But νΛ(n) is a genuine integral, so it was memoized: each n is integrated once on first use and cached, which is the runtime win without a stale fixed table.

4. Stage 1 — the static layout

Claude built the first version of the page: the eight clock panels (sampling from many more primes than are shown, so the chain stays essentially exact), the ticked s-axis with a draggable handle, and the chain to its left, with green markers showing which clocks still beat the threshold. A DOM-shim smoke test drove the real inline script headlessly. Terence found it "pretty good."

5. Bubbles, and a slow fading animation

Terence:

Right now the Z_s values are placed at the midpoint of their s-range, but I'd like them at the bottom, and enclosed in circles as in the paper, e.g. if the Z_s transition from 1 to 2 to 2·19 to 2²·19 then there should be bubbles around 1, 2, 2·19, and 2²·19 in the left line, with say the 2 bubble positioned at the value of s where it transitions from 1 to 2.

I'd like the ability to animate as in RV, though this time on a much slower time scape (e.g., 1/sec), with some faded version of previous instances (maybe by default only tracking the previous 3 or so with increasingly faded colors). Having the interface similar to the RV interface for consistency would be great.

Claude moved each chain node to a circled bubble at the exact s where Zs jumps to it, and added an RV-style toolbar — ▶ Run / ⏸ Pause / ⏭ Step, a speed slider defaulting to 1/s, and a trail slider defaulting to 3 — with the last few chains lingering as fading ghosts, so the distribution of chains builds up as it runs.

6. Stage 2 — the histogram, a tooltip, and a strip

For Stage 2, Claude added the lower panel: the empirical distribution of Zs (bars) against the exact 1/(ζ(s) ns) (a red curve that tracks s live as you drag), with a tail bucket for the heavy tail near s=1.

Terence (two refinements, mid-build):

One can make a tool tip when hovering over a bubble value of Z_s to reveal both the prime factorization and the decimal numeral value […]. If there are multiple E_{p,k}'s appearing in the p column, perhaps one can thicken the p-column to a strip to show the location of the first and last E_{p,k}'s displayed (maybe filled in with a shading).

Claude added a hover tooltip on the bubbles (factorization, plus the decimal for values that fit), and widened any prime column carrying two or more clocks into a shaded strip spanning its first to last clock.

7. The hitting measure νΛ

Terence:

My one suggestion is to put the current value of Z_s (and any faded ones in the trail) in the histogram as well. You can do that concurrently with Stage 3.

Claude marked the live Zs (and its faded trail) as carets over the histogram bins, and — the deferred Stage 3 — added a panel toggle to the νΛ hitting measure: empirical "fraction of chains passing through n" against the exact νΛ(n) curve. This is the payoff: the bars converge to the von Mangoldt weight behind the paper.

8. Pacing — starting empty, ticks, one draw per tick

Terence:

With an initial seed of 1,500 draws the histograms already converge so fast that there is no visual benefit to them being animated. Perhaps start them at 0 draws? Similarly to the Z_s distribution panel, for the hitting measure panel one can display all active values of Z_s (other than 1 which is permanently active) […] as ticks, and have older values in the trail as faded ticks (which would be overwritten by more recent ticks in case there is a collision […]).

Both panels now start empty, so watching them fill has a point; the νΛ panel ticks every value the current chain hits, with the trail as faded ticks that a newer tick overwrites on a collision.

Terence:

Adding 250 draws per tick also makes convergence way too rapid. Maybe just do one draw per tick?

Claude checked that the display range [1.1, 6] reproduces νΛ to about 0.003, which allowed the clean version: one draw per tick, in which the instantiation you watch is the sample counted — its bubble caret marks precisely the histogram bin that grows, and its chain ticks the νΛ bars that grow.

9. Audits and launch

Terence: "Looks great! Let's do the audits and then making of."

The audits confirmed the app is self-contained (no network, no CDN, strict CSP) and touches no page state outside its own box. The fixes: the canvas palette now follows the page's light/dark CSS variables each frame; aria labels were added; and the spacebar play/pause was scoped to when the pointer is over the plot, so it never hijacks page scrolling. The applet source is released under the MIT license (the mathematical content remains reserved).

Built with Claude Code (Opus 4.8). The underlying mathematics is from Alexeev, Barreto, Li, Lichtman, Price, Shah, Tang, and Tao, arXiv:2605.00301.