import { useEffect, useMemo, useState } from 'react'; import { ready, ink } from '../../lib/mrly.js'; import { useQuery } from '../../lib/query.js'; import { mount, Page, Row, Slider, Pick, Stats, Stat, Note } from '../../lib/app.jsx'; import { Sketch } from '../../lib/draw.jsx'; import { board, line, bars, axis, tag } from '../../lib/chart.js'; import { meterChart, LOW, HIGH, PER_OCTAVE, ZEROS, CURVE } from './widget.jsx'; const m = await ready(); const FIRST = { k: 1, window: 'smooth', j: HIGH }; const FLOOR = 1e-8; const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`; function caption(pick, meter, miss, count) { if (!meter) return `Sieving the totients to ${(2 ** (HIGH + 1)).toLocaleString('en')} and finding the first ${ZEROS} zeros.`; const k = pick.k; if (pick.window === 'sharp') { const tail = k ? `the ${plural(k, 'zero')} you added, drawn to this scale, shrink by the root of y and vanish under it` : 'the waves of the zeros would sit a factor of the root of y below it'; return `Cut the window sharply and the error divided by y is a cloud of prime jumps that no wave fits: ${tail}.`; } if (k === 0) return `The smoothed error at ${count} heights, each divided by y^(3/2): a signal of unit size waiting for its waves.`; if (k === 1) return `The first zero alone, at height ${meter.gammas()[0].toFixed(2)}, is one cosine in log y and already hugs the dots, missing them by ${miss.toFixed(2)} of their peak.`; if (k < ZEROS) return `${k} zeros sum to a wave that misses the dots by ${miss.toExponential(1)} of their peak.`; return `${k} zeros fit every dot to ${miss.toExponential(1)} of the peak: the error is the zeros' waves and nothing else.`; } function App() { const [pick, set] = useQuery(FIRST); const [meter, setMeter] = useState(null); const [error, setError] = useState(null); useEffect(() => { try { setMeter(new m.Novelty(HIGH, PER_OCTAVE, ZEROS)); } catch (fault) { setError(fault); } }, []); const sharp = pick.window === 'sharp'; const heights = useMemo(() => (meter ? meter.heights() : new Float64Array()), [meter]); const dots = useMemo(() => (meter ? meter.dots(sharp) : new Float64Array()), [meter, sharp]); const gammas = useMemo(() => (meter ? meter.gammas() : new Float64Array()), [meter]); const amplitudes = useMemo(() => (meter ? meter.amplitudes() : new Float64Array()), [meter]); const curve = useMemo(() => Float64Array.from({ length: CURVE + 1 }, (_, i) => LOW + (pick.j - LOW) * i / CURVE), [pick.j]); const wave = useMemo(() => (meter ? meter.wave(pick.k, sharp, curve) : new Float64Array()), [meter, pick.k, sharp, curve]); const miss = useMemo(() => (meter ? meter.miss(pick.k) : 1), [meter, pick.k]); const count = useMemo(() => heights.filter((j) => j <= pick.j).length, [heights, pick.j]); const chart = meterChart({ heights, dots, curve, wave, j: pick.j, k: pick.k, sharp, height: 320 }); const ladder = (canvas) => { const b = board(canvas, 150, { top: 24, bottom: 20 }); if (!meter) return; const top = Math.log(amplitudes[0] * 1.5); const bottom = Math.log(FLOOR); const values = Array.from(amplitudes, (a) => Math.max(0, (Math.log(Math.max(a, FLOOR)) - bottom) / (top - bottom))); bars(b, values, { peak: 1, color: (i) => (i < pick.k ? ink.orange : ink.line), inset: 0.5 }); axis(b, [[0, 'zero 1'], [1, `zero ${ZEROS}, height ${gammas[ZEROS - 1].toFixed(0)}`]]); tag(b, `|c| from ${amplitudes[0].toFixed(2)} down to ${amplitudes[ZEROS - 1].toExponential(0)}, log scale`, ink.dim); }; const controls = ( set({ k: v })} /> set({ window: v })} /> set({ j: v })} /> ); const last = meter && pick.k ? gammas[pick.k - 1] : null; return ( Stack a grid of n cells on the unit interval for every scale n: scale n lights phi(n) nodes no smaller scale drew. Count that novelty through a smooth window around the scale 1/y, take away the main term, and what is left is a sum of waves, one per zero of zeta, each with the zero's height as its frequency in log y. Add the zeros one at a time and watch the wave settle on the dots; cut the window sharply and the primes shout over it.} foot={<>The window is the bump f(u) = exp(4 - 1/((u - 1)(2 - u))) on [1, 2], and the meter is E_f(y) = y^2 sum_n phi(n) f(n y) - (6/pi^2) F(2), F the Mellin transform of f, the totients sieved to 2^{HIGH + 1} so every window fits. Under the Riemann hypothesis E_f(y) = sum_rho F(rho) zeta(rho - 1)/zeta'(rho) y^(2 - rho) up to a smaller remainder, so E_f(y)/y^(3/2) is 2 Re sum c_rho y^(-i gamma), a cosine in log y per zero at the zero's height. The curve is that sum over the first K zeros with nothing fitted: the zeros come from the critical line walked on the zeta page, zeta(rho - 1) and zeta'(rho) from the same Euler-Maclaurin sum off the line, F(rho) from a 4096-node rule. The sharp window is the indicator of [1, 2]; its error divided by y stays of unit size because the totient sum jumps by about 0.39 p at every prime p, louder than the waves, which live at y^(3/2). The exponent of the smooth meter, 3/2 in y for every smooth window, is equivalent to the Riemann hypothesis; the paper The stack hears the zeros assembles that equivalence and the stack page reads the meter to 2^-23.5. Every number here is one crate call; the page only draws.} controls={controls}>

The meter {sharp ? 'the sharp window, E(y) / y' : 'the smooth window, E(y) / y^(3/2)'}

{caption(pick, meter, miss, count)}

The waves one bar per zero, its amplitude on a log scale, the first {pick.k} in the sum

{count} {meter ? meter.sieve().toLocaleString('en') : ''} {`${pick.k} of ${ZEROS}`} {last === null ? 'none' : last.toFixed(4)} {last === null ? 'none' : amplitudes[pick.k - 1].toExponential(3)} {meter ? miss.toExponential(1) : ''}
); } mount();