index.jsx

7.5 kB · jsx · 103 lines

1import { useEffect, useMemo, useState } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { useQuery } from '../../lib/query.js';4import { mount, Page, Row, Slider, Pick, Stats, Stat, Note } from '../../lib/app.jsx';5import { Sketch } from '../../lib/draw.jsx';6import { board, line, bars, axis, tag } from '../../lib/chart.js';7import { meterChart, LOW, HIGH, PER_OCTAVE, ZEROS, CURVE } from './widget.jsx';89const m = await ready();10const FIRST = { k: 1, window: 'smooth', j: HIGH };11const FLOOR = 1e-8;1213const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;1415function caption(pick, meter, miss, count) {16  if (!meter) return `Sieving the totients to ${(2 ** (HIGH + 1)).toLocaleString('en')} and finding the first ${ZEROS} zeros.`;17  const k = pick.k;18  if (pick.window === 'sharp') {19    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';20    return `Cut the window sharply and the error divided by y is a cloud of prime jumps that no wave fits: ${tail}.`;21  }22  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.`;23  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.`;24  if (k < ZEROS) return `${k} zeros sum to a wave that misses the dots by ${miss.toExponential(1)} of their peak.`;25  return `${k} zeros fit every dot to ${miss.toExponential(1)} of the peak: the error is the zeros' waves and nothing else.`;26}2728function App() {29  const [pick, set] = useQuery(FIRST);30  const [meter, setMeter] = useState(null);31  const [error, setError] = useState(null);32  useEffect(() => {33    try {34      setMeter(new m.Novelty(HIGH, PER_OCTAVE, ZEROS));35    } catch (fault) {36      setError(fault);37    }38  }, []);39  const sharp = pick.window === 'sharp';40  const heights = useMemo(() => (meter ? meter.heights() : new Float64Array()), [meter]);41  const dots = useMemo(() => (meter ? meter.dots(sharp) : new Float64Array()), [meter, sharp]);42  const gammas = useMemo(() => (meter ? meter.gammas() : new Float64Array()), [meter]);43  const amplitudes = useMemo(() => (meter ? meter.amplitudes() : new Float64Array()), [meter]);44  const curve = useMemo(() => Float64Array.from({ length: CURVE + 1 }, (_, i) => LOW + (pick.j - LOW) * i / CURVE), [pick.j]);45  const wave = useMemo(() => (meter ? meter.wave(pick.k, sharp, curve) : new Float64Array()), [meter, pick.k, sharp, curve]);46  const miss = useMemo(() => (meter ? meter.miss(pick.k) : 1), [meter, pick.k]);47  const count = useMemo(() => heights.filter((j) => j <= pick.j).length, [heights, pick.j]);4849  const chart = meterChart({ heights, dots, curve, wave, j: pick.j, k: pick.k, sharp, height: 320 });5051  const ladder = (canvas) => {52    const b = board(canvas, 150, { top: 24, bottom: 20 });53    if (!meter) return;54    const top = Math.log(amplitudes[0] * 1.5);55    const bottom = Math.log(FLOOR);56    const values = Array.from(amplitudes, (a) => Math.max(0, (Math.log(Math.max(a, FLOOR)) - bottom) / (top - bottom)));57    bars(b, values, { peak: 1, color: (i) => (i < pick.k ? ink.orange : ink.line), inset: 0.5 });58    axis(b, [[0, 'zero 1'], [1, `zero ${ZEROS}, height ${gammas[ZEROS - 1].toFixed(0)}`]]);59    tag(b, `|c| from ${amplitudes[0].toFixed(2)} down to ${amplitudes[ZEROS - 1].toExponential(0)}, log scale`, ink.dim);60  };6162  const controls = (63    <Row>64      <Slider label="zeros in the wave" value={pick.k} min={0} max={ZEROS} onChange={(v) => set({ k: v })} />65      <Pick label="window" value={pick.window} options={[['smooth', 'smooth bump'], ['sharp', 'sharp cutoff']]} onChange={(v) => set({ window: v })} />66      <Slider label="y down to 2^-j, j" value={pick.j} min={LOW + 1} max={HIGH} onChange={(v) => set({ j: v })} />67    </Row>68  );6970  const last = meter && pick.k ? gammas[pick.k - 1] : null;7172  return (73    <Page crumb="novelty" title="The stack hears the zeros"74      sub={<>Stack a grid of <code>n</code> cells on the unit interval for every scale <code>n</code>: scale <code>n</code> lights <code>phi(n)</code> nodes no smaller scale drew. Count that novelty through a smooth window around the scale <code>1/y</code>, 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 <code>log y</code>. 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.</>}75      foot={<>The window is the bump <code>f(u) = exp(4 - 1/((u - 1)(2 - u)))</code> on <code>[1, 2]</code>, and the meter is <code>E_f(y) = y^2 sum_n phi(n) f(n y) - (6/pi^2) F(2)</code>, <code>F</code> the Mellin transform of <code>f</code>, the totients sieved to <code>2^{HIGH + 1}</code> so every window fits. Under the Riemann hypothesis <code>E_f(y) = sum_rho F(rho) zeta(rho - 1)/zeta'(rho) y^(2 - rho)</code> up to a smaller remainder, so <code>E_f(y)/y^(3/2)</code> is <code>2 Re sum c_rho y^(-i gamma)</code>, a cosine in <code>log y</code> per zero at the zero's height. The curve is that sum over the first <code>K</code> zeros with nothing fitted: the zeros come from the critical line walked on <a href="../zeta/">the zeta page</a>, <code>zeta(rho - 1)</code> and <code>zeta'(rho)</code> from the same Euler-Maclaurin sum off the line, <code>F(rho)</code> from a 4096-node rule. The sharp window is the indicator of <code>[1, 2]</code>; its error divided by <code>y</code> stays of unit size because the totient sum jumps by about <code>0.39 p</code> at every prime <code>p</code>, louder than the waves, which live at <code>y^(3/2)</code>. The exponent of the smooth meter, <code>3/2</code> in <code>y</code> for every smooth window, is equivalent to the Riemann hypothesis; the paper <a href="/papers/novelty-meter/">The stack hears the zeros</a> assembles that equivalence and <a href="/research/stack/">the stack page</a> reads the meter to <code>2^-23.5</code>. Every number here is one crate call; the page only draws.</>}76      controls={controls}>77      <div className="arena">78        <div className="panel">79          <h2>The meter <span>{sharp ? 'the sharp window, E(y) / y' : 'the smooth window, E(y) / y^(3/2)'}</span></h2>80          <Sketch draw={chart} deps={[meter, dots, wave, pick.j, pick.k, sharp]} className="bars" role="img" aria-label="The novelty error at every height as dots, with the wave of the first zeros as a curve" />81          <p className="sub">{caption(pick, meter, miss, count)}</p>82        </div>83      </div>84      <div className="arena">85        <div className="panel">86          <h2>The waves <span>one bar per zero, its amplitude on a log scale, the first {pick.k} in the sum</span></h2>87          <Sketch draw={ladder} deps={[meter, pick.k]} className="bars" role="img" aria-label="The amplitude of every zero's wave, the first so many lit" />88        </div>89      </div>90      <Stats>91        <Stat label="readings">{count}</Stat>92        <Stat label="sieve">{meter ? meter.sieve().toLocaleString('en') : ''}</Stat>93        <Stat label="zeros">{`${pick.k} of ${ZEROS}`}</Stat>94        <Stat label="last height">{last === null ? 'none' : last.toFixed(4)}</Stat>95        <Stat label="its amplitude">{last === null ? 'none' : amplitudes[pick.k - 1].toExponential(3)}</Stat>96        <Stat label="fit">{meter ? miss.toExponential(1) : ''}</Stat>97      </Stats>98      <Note error={error} />99    </Page>100  );101}102103mount(<App />);