index.jsx

15.3 kB · jsx · 278 lines

1import { useMemo } from 'react';2import { ready, ink } from '../../../lib/mrly.js';3import { mount, Page, Row, Slider, Pick, Btn, Stats, Stat, Note } from '../../../lib/app.jsx';4import { Sketch } from '../../../lib/draw.jsx';5import { board, line, rules, axis, tag } from '../../../lib/chart.js';6import { useQuery } from '../../../lib/query.js';78const m = await ready();9const CHAIN = m.dissection_chain();10const FIRST = 3;11const ROOF = 0.75;12const FRIENDS = [[10, 7], [10, 5], [10, 0], [3, 1], [17, 8], [33, 16]];13const NAMES = ['A', 'B', 'C1', 'C2'];14const LANES = [3, 2, 1, 0];15const hues = () => [ink.blue, ink.dim, ink.orange, ink.yellow];1617const fixed = (value, places) => (Number.isFinite(value) ? value.toFixed(places) : 'none');18const clamp = (value, low, high) => Math.min(Math.max(value, low), high);1920function logTicks(b, read) {21  const first = read.logx[0], last = read.logx[read.logx.length - 1];22  const wide = Math.max(last - first, 1e-9);23  const ticks = [];24  for (let j = 1; j <= read.level; j += 1) {25    const at = (j * Math.log(read.base) - first) / wide;26    if (at >= 0 && at <= 1.0001) ticks.push([Math.min(at, 1), `${read.base}^${j}`]);27  }28  axis(b, ticks, { wall: true });29  return (i) => (read.logx[i] - first) / wide;30}3132function Meter({ tally, by }) {33  const draw = (canvas) => {34    const b = board(canvas, 240, { left: 44 });35    const series = by === 'mass' ? tally.mass : tally.root;36    const reach = by === 'mass' ? 1 : Math.max(1.25, tally.read.peak * 1.1);37    const at = logTicks(b, tally);38    const lift = (v) => 0.5 + v / (2 * reach);39    line(b, [[0, lift(1)], [1, lift(1)]], ink.line, { width: 1, dash: [3, 4] });40    line(b, [[0, lift(-1)], [1, lift(-1)]], ink.line, { width: 1, dash: [3, 4] });41    line(b, [[0, 0.5], [1, 0.5]], ink.line, { width: 1 });42    line(b, Array.from(series, (v, i) => [at(i), lift(v)]), ink.yellow, { width: 1.4 });43    b.ctx.fillStyle = ink.dim;44    b.ctx.textAlign = 'right';45    b.ctx.fillText(fixed(reach, 2), b.left - 6, b.y(1) + 4);46    b.ctx.fillText(fixed(-reach, 2), b.left - 6, b.y(0) + 4);47    b.ctx.textAlign = 'left';48    tag(b, by === 'mass' ? 'M_F(x) / A_F(x)' : 'M_F(x) / A_F(x)^(1/2)', ink.yellow);49    tag(b, 'dashed: plus and minus one', ink.dim, 'right');50  };51  return <Sketch className="bars" draw={draw} deps={[tally, by]} role="img" aria-label="The meter of the set over its yardstick against log x" />;52}5354function Primes({ tally }) {55  const draw = (canvas) => {56    const b = board(canvas, 200, { left: 44 });57    let high = 1.5;58    for (const v of tally.primes) if (v > high) high = v;59    const at = logTicks(b, tally);60    const lift = (v) => v / high;61    line(b, [[0, lift(1)], [1, lift(1)]], ink.line, { width: 1, dash: [3, 4] });62    line(b, Array.from(tally.primes, (v, i) => [at(i), lift(v)]), ink.green, { width: 1.4 });63    b.ctx.fillStyle = ink.dim;64    b.ctx.textAlign = 'right';65    b.ctx.fillText(fixed(high, 2), b.left - 6, b.y(1) + 4);66    b.ctx.fillText('1', b.left - 6, b.y(lift(1)) + 4);67    b.ctx.fillText('0', b.left - 6, b.y(0) + 4);68    b.ctx.textAlign = 'left';69    tag(b, 'psi_F(x) / (kappa_F A_F(x))', ink.green);70  };71  return <Sketch className="bars" draw={draw} deps={[tally]} role="img" aria-label="The prime count of the set over its main term against log x" />;72}7374function Lanes({ grid }) {75  const draw = (canvas) => {76    const b = board(canvas, 250, { left: 34 });77    const inks = hues();78    const { region, weight } = grid;79    const y = region.length;80    const lane = b.tall / 4;81    const decades = 4;82    const tall = (w) => Math.max(1.5, (lane - 6) * clamp(1 + Math.log10(Math.max(w, 1e-12)) / decades, 0, 1));83    const cols = Math.max(1, Math.floor(b.wide));84    const sparse = y * 3 <= cols;85    const peak = LANES.map(() => new Float32Array(sparse ? y : cols).fill(-1));86    for (let a = 0; a < y; a += 1) {87      const slot = sparse ? a : Math.min(cols - 1, Math.floor((a / y) * cols));88      const row = peak[region[a]];89      if (weight[a] > row[slot]) row[slot] = weight[a];90    }91    LANES.forEach((r, k) => {92      const floor = b.roof + lane * (k + 1);93      b.ctx.fillStyle = ink.line;94      b.ctx.fillRect(b.left, floor - 0.5, b.wide, 1);95      b.ctx.fillStyle = inks[r];96      const row = peak[r];97      const step = b.wide / row.length;98      for (let s = 0; s < row.length; s += 1) {99        if (row[s] < 0) continue;100        const h = tall(row[s]);101        const x = b.left + s * step;102        b.ctx.fillRect(x, floor - h, sparse ? Math.max(1, step - 1) : Math.max(1, step), h);103      }104      b.ctx.fillStyle = inks[r];105      b.ctx.textAlign = 'right';106      b.ctx.fillText(NAMES[r], b.left - 8, floor - lane / 2 + 4);107      b.ctx.textAlign = 'left';108    });109    axis(b, [[0, '0'], [0.25, '1/4'], [0.5, '1/2'], [0.75, '3/4'], [1, '1']]);110    tag(b, `a / ${grid.read.base}^${grid.read.level}, bar height |hat F(a/y)| / fill^level on four decades`, ink.dim);111  };112  return <Sketch className="bars" draw={draw} deps={[grid]} role="img" aria-label="The frequency grid cut into the regions A, B, C1 and C2, each frequency weighed by the digit transform" />;113}114115function Wall({ read }) {116  const draw = (canvas) => {117    const b = board(canvas, 260, { left: 44 });118    const low = Math.log(FIRST), high = Math.log(FIRST + CHAIN.length - 1);119    const across = (q) => (Math.log(q) - low) / (high - low);120    const lift = (v) => clamp(v / ROOF, 0, 1);121    const [barA, barB] = read.bars;122    const walls = [[read.walls.digit, 'digit'], [read.walls.window, 'window'], [read.walls.chain, 'chain']];123    rules(b, walls.map(([q]) => across(q)), { color: ink.line, dash: [2, 4] });124    line(b, [[0, lift(barA)], [1, lift(barA)]], ink.pink, { width: 1.2 });125    line(b, [[0, lift(barB)], [1, lift(barB)]], ink.pink, { width: 1, dash: [4, 4] });126    const curve = [];127    for (let i = 0; i < CHAIN.length; i += 1) curve.push([across(FIRST + i), lift(CHAIN[i])]);128    line(b, curve, ink.blue, { width: 1.6 });129    line(b, [[across(read.base), lift(read.chain)]], ink.blue, { dots: 5 });130    line(b, [[across(read.base), lift(read.reading)]], ink.yellow, { dots: 5 });131    axis(b, [[0, String(FIRST)], [across(10), '10'], [across(100), '100'], [across(1000), '1000'], [1, String(FIRST + CHAIN.length - 1)]], { wall: true });132    b.ctx.fillStyle = ink.dim;133    b.ctx.textAlign = 'right';134    for (const v of [barA, barB, 0.5, ROOF]) b.ctx.fillText(fixed(v, 2), b.left - 6, b.y(lift(v)) + 4);135    b.ctx.textAlign = 'center';136    for (const [q, name] of walls) b.ctx.fillText(`${name} ${q}`, b.x(across(q)), b.roof - 4 + (name === 'window' ? -10 : 0));137    b.ctx.textAlign = 'left';138    tag(b, 'the chain alpha_1 by base', ink.blue, 'left', b.x(0), b.roof + 14);139  };140  return <Sketch className="bars" draw={draw} deps={[read]} role="img" aria-label="The chain certificate exponent against the base, the bars one fifth and one quarter, and the walls" />;141}142143function reachLine(read) {144  const { walls, base, digit } = read;145  const where = {146    proof: `Base ${base} missing ${digit} is reached by proof: the chain clears 1/5 at every base from ${walls.chain}.`,147    certificate: `Base ${base} missing ${digit} is reached by certificate: a verified shifted-grid bound below 1/5 holds for this set, short of the chain's proof from ${walls.chain}.`,148    none: `Base ${base} missing ${digit} is not reached: no certificate below 1/5 is known for this set, so the theorem says nothing here and the curves above are readings.`,149  }[read.reach];150  return `${where} The chain proves every base from ${walls.chain}; window certificates cover ${walls.window} to ${walls.chain - 1} and per-digit certificates every set from ${walls.digit}, and base ${walls.first} missing 0 is the first set certified below 1/5.`;151}152153function App() {154  const [pick, set] = useQuery({ base: 10, digit: 7, depth: 6, level: 3, z: 8, by: 'mass' });155  const digit = Math.min(pick.digit, pick.base - 1);156157  let error = null;158  const read = useMemo(() => {159    try {160      return JSON.parse(m.dissection_read(pick.base, digit));161    } catch (fault) {162      return { fault };163    }164  }, [pick.base, digit]);165  if (read.fault) error = read.fault;166  const depth = read.depths ? clamp(pick.depth, read.depths[0], read.depths[1]) : pick.depth;167  const level = read.grids ? clamp(pick.level, read.grids[0], read.grids[1]) : pick.level;168169  const tally = useMemo(() => {170    if (read.fault) return null;171    try {172      const got = m.dissection_tally(pick.base, digit, depth);173      return { logx: got.logx, mass: got.mass, root: got.root, primes: got.primes, read: JSON.parse(got.read), base: pick.base, level: depth };174    } catch (fault) {175      return { fault };176    }177  }, [pick.base, digit, depth, read]);178  const grid = useMemo(() => {179    if (read.fault) return null;180    try {181      const got = m.dissection_grid(pick.base, digit, level, pick.z);182      return { region: got.region, weight: got.weight, read: { ...JSON.parse(got.read), base: pick.base, level } };183    } catch (fault) {184      return { fault };185    }186  }, [pick.base, digit, level, pick.z, read]);187  error = error ?? tally?.fault ?? grid?.fault ?? null;188  const good = !error;189190  const controls = (191    <>192      <section>193        <h3>The set</h3>194        <Row>195          <Slider label="base" value={pick.base} min={3} max={128} onChange={(v) => set({ base: v, digit: Math.min(digit, v - 1) })} />196          <Slider label="missing" value={digit} min={0} max={pick.base - 1} onChange={(v) => set({ digit: v })} />197        </Row>198        <Row>199          {FRIENDS.map(([q, e]) => (200            <Btn key={`${q}-${e}`} on={pick.base === q && digit === e} onClick={() => set({ base: q, digit: e })}>{`${q} less ${e}`}</Btn>201          ))}202        </Row>203      </section>204      <section>205        <h3>The meter</h3>206        <Row>207          <Slider label="digits of x" value={depth} min={read.depths?.[0] ?? 3} max={read.depths?.[1] ?? 3} onChange={(v) => set({ depth: v })} />208          <Pick label="against" value={pick.by} options={[['mass', 'A_F(x)'], ['root', 'A_F(x)^(1/2)']]} onChange={(v) => set({ by: v })} />209        </Row>210      </section>211      <section>212        <h3>The grid</h3>213        <Row>214          <Slider label="level" value={level} min={read.grids?.[0] ?? 1} max={read.grids?.[1] ?? 1} onChange={(v) => set({ level: v })} />215          <Slider label="Z" value={pick.z} min={2} max={64} onChange={(v) => set({ z: v })} />216        </Row>217      </section>218    </>219  );220221  const t = tally?.read;222  const g = grid?.read;223  return (224    <Page crumb="dissection" title="The dissection and its wall"225      sub="Keep the positive integers whose digits in base q avoid one digit. Their Mobius sum M_F(x) is o(A_F(x)), with no hypothesis, once the digit transform's l^1 exponent alpha_1 sits below 1/5, and the prime count follows its main term kappa_F A_F(x). That happens from base 584 by proof. Pick a small base, where everything fits in a browser, and see what the theorem is about and whether it reaches the set you picked."226      controls={controls}227      foot={<>Every number here is computed in Rust by <code>mrlyrs::num::dissection</code> and the page only draws. The set <code>S_F</code> holds the integers whose base-<code>q</code> digits all lie in <code>F</code>, the base less one digit; <code>A_F(x)</code> counts it up to <code>x</code>, <code>M_F(x)</code> sums <code>mu</code> over it and <code>psi_F(x)</code> sums the von Mangoldt weight <code>log p</code> over its prime powers, against <code>kappa_F = (q/phi(q)) #&#123;f in F : gcd(f, q) = 1&#125;/fill</code>. The grid is the <code>y = q^level</code> frequencies <code>a/y</code>, each given its last continued-fraction convergent <code>l/d</code> with <code>d &lt;= Q = y^(3/5)</code> and its height <code>h = |ad - ly|</code>: region A is <code>d &gt;= y^(2/5)</code>, the minor arcs; C1 and C2 have <code>d &lt; Z</code> and <code>h &lt; Z</code>, C2 when <code>d</code> divides a power of <code>q</code>; B is the rest. The cut never sees the digits; the bar heights, <code>|hat F_level(a/y)|</code>, are what each region is paid against. Region A pays the whole <code>l^1</code> mass against the minor-arc bound <code>x^(4/5)</code>, which is why the bar is <code>1/5</code>; region B asks only <code>1/4</code>. The blue curve is the chain certificate, one number per base good at every missing digit; the yellow dot is the reading <code>log_q(c_j/(fill c_(j-1)))</code> of the unshifted masses at the deepest level inside <code>2^21</code> frequencies, a reading and never a bound. The mathematics is <a href="/research/mobius/">the Mobius page</a>, section The unconditional dissection, and <a href="/papers/unconditional-mertens-at-large-base/">the paper</a>.</>}>228      <div className="panel">229        <h2>The meter <span>{pick.by === 'mass' ? (read.reach === 'none' ? 'M_F(x) over its mass A_F(x): a reading, the theorem does not reach this set' : 'M_F(x) over its mass A_F(x): the theorem sends this to zero') : 'M_F(x) over A_F(x)^(1/2): square-root size, the open conjecture'}</span></h2>230        {good && tally && <Meter tally={tally} by={pick.by} />}231        <Stats>232          <Stat label="digits">{read.digits && `${read.fill} of ${read.base}, ${digit} missing`}</Stat>233          <Stat label="A_F(x)">{t?.count}</Stat>234          <Stat label="M_F(x)">{t?.meter}</Stat>235          <Stat label="M_F / A_F">{t && fixed(t.mass, 6)}</Stat>236          <Stat label="M_F / A_F^(1/2)">{t && fixed(t.root, 4)}</Stat>237          <Stat label="max |M_F| / A_F^(1/2)">{t && fixed(t.peak, 4)}</Stat>238          <Stat label="x up to">{t && `${pick.base}^${depth}`}</Stat>239        </Stats>240      </div>241      <div className="panel" style={{ marginTop: 22 }}>242        <h2>The prime count <span>psi_F(x) over its main term kappa_F A_F(x)</span></h2>243        {good && tally && <Primes tally={tally} />}244        <Stats>245          <Stat label="kappa_F">{read.kappa && `${read.kappa[0]}/${read.kappa[1]}`}</Stat>246          <Stat label="psi_F(x)">{t && fixed(t.psi, 2)}</Stat>247          <Stat label="psi_F / (kappa_F A_F)">{t && fixed(t.primes, 6)}</Stat>248          <Stat label="two consecutive digits">{read.digits && (read.consecutive ? 'yes' : 'no, so no main term')}</Stat>249        </Stats>250      </div>251      <div className="panel" style={{ marginTop: 22 }}>252        <h2>The grid <span>the frequencies a/y cut into four regions by their Dirichlet fraction</span></h2>253        {good && grid && <Lanes grid={grid} />}254        <Stats>255          <Stat label="y">{g?.y}</Stat>256          <Stat label="Q">{g?.cap}</Stat>257          <Stat label="y^(2/5)">{g && fixed(g.low, 2)}</Stat>258          {NAMES.map((name, r) => (259            <Stat key={name} label={name}>{g && `${g.counts[r]}, ${fixed(100 * g.shares[r], 1)}% of l^1`}</Stat>260          ))}261        </Stats>262      </div>263      <div className="panel" style={{ marginTop: 22 }}>264        <h2>The wall <span>alpha_1 against 1/5 and 1/4, blue the chain by base, yellow this set's reading</span></h2>265        {good && read.chain !== undefined && <Wall read={read} />}266        <Stats>267          <Stat label="chain alpha_1">{read.chain !== undefined && fixed(read.chain, 6)}</Stat>268          <Stat label="reading alpha_1">{read.reading !== undefined && `${fixed(read.reading, 6)} at level ${read.level}`}</Stat>269          <Stat label="bars">{read.bars && `${read.bars[0]} and ${read.bars[1]}`}</Stat>270        </Stats>271        <p style={{ marginTop: 10, fontSize: 13 }}>{good && read.walls && reachLine(read)}</p>272      </div>273      <Note error={error}>{good && g && `Z = ${g.z}; the regions partition the grid once Z sits below y^(2/5), and at level ${level} that is ${fixed(g.low, 2)}.`}</Note>274    </Page>275  );276}277278mount(<App />);