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)) #{f in F : gcd(f, q) = 1}/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 <= Q = y^(3/5)</code> and its height <code>h = |ad - ly|</code>: region A is <code>d >= y^(2/5)</code>, the minor arcs; C1 and C2 have <code>d < Z</code> and <code>h < 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 />);