index.jsx
15.6 kB · jsx · 303 lines
1import { useEffect, useMemo, useRef, useState } from 'react';2import { ready, ink, fit } from '../../lib/mrly.js';3import { useQuery } from '../../lib/query.js';4import { mount, Page, Row, Btn, Stats, Stat, Note } from '../../lib/app.jsx';5import { Sketch } from '../../lib/draw.jsx';6import { useSeeds, roll } from '../../lib/select.jsx';7import { board, bars, axis, tag } from '../../lib/chart.js';8import { Terms, mix } from '../../lib/series.jsx';910const m = await ready();11const COLS = 40;12const LINES = 25;13const PAGE = 25;14const BUDGET = 14;15const WIN = JSON.parse(m.census_window());16const CEILING = Number(WIN.ceiling);17const START = JSON.parse(m.census_walk(WIN.tiers[0].keys));1819function shade(count, peak) {20 if (count === 0) return ink.orange;21 if (count === 1) return ink.dim;22 return mix(ink.blue, ink.yellow, Math.log(count) / peak);23}2425function census() {26 return { counts: m.census_counts(), report: JSON.parse(m.census_report()) };27}2829function frame() {30 return new Promise((resolve) => requestAnimationFrame(resolve));31}3233function App() {34 const s = useSeeds();35 const [pick, set] = useQuery({ value: 16 });36 const value = Math.min(CEILING, Math.max(1, pick.value || 16));37 const [typing, setTyping] = useState(String(value));38 const [page, setPage] = useState(0);39 const [walking, setWalking] = useState(true);40 const [state, setState] = useState(START);41 const [look, setLook] = useState(census);42 const [error, setError] = useState(null);43 const live = useRef(state);44 live.current = state;4546 const choose = (v, typed = false) => {47 const next = Math.min(CEILING, Math.max(1, v || 1));48 setPage(0);49 if (!typed || next !== v) setTyping(String(next));50 set({ value: next });51 };5253 const pass = async () => {54 setWalking(true);55 setError(null);56 let span = 4;57 let ticks = 0;58 let broken = false;59 let now = live.current;60 do {61 await frame();62 const opened = performance.now();63 while (performance.now() - opened < BUDGET) {64 const clock = performance.now();65 try {66 now = JSON.parse(m.census_walk(span));67 } catch (error) {68 setError(error);69 broken = true;70 break;71 }72 const spent = Math.max(performance.now() - clock, 0.5);73 span = Math.max(1, Math.min(span * 2, 4000, Math.round((span * BUDGET) / spent)));74 if (now.done >= now.total || spent >= BUDGET) break;75 }76 live.current = now;77 setState(now);78 if (++ticks % 8 === 0) setLook(census());79 } while (!broken && now.done < now.total);80 setWalking(false);81 setLook(census());82 };8384 useEffect(() => { pass(); }, []);8586 const found = useMemo(() => JSON.parse(m.census_writers(value, page, PAGE)), [value, page, look]);87 const champs = useMemo(() => JSON.parse(m.census_champions(20)), [look]);88 const misses = useMemo(() => JSON.parse(m.census_misses(30)), [look]);8990 const field = (canvas) => {91 const side = canvas.clientWidth / COLS;92 const [ctx, w, h] = fit(canvas, Math.round(side * LINES));93 ctx.clearRect(0, 0, w, h);94 const peak = Math.log(Math.max(2, ...look.counts));95 for (let i = 0; i < look.counts.length; i++) {96 ctx.fillStyle = shade(look.counts[i], peak);97 ctx.fillRect((i % COLS) * side, Math.floor(i / COLS) * side, Math.max(1, side - 1), Math.max(1, side - 1));98 }99 const at = value - 1;100 ctx.strokeStyle = ink.fg;101 ctx.lineWidth = 2;102 ctx.strokeRect((at % COLS) * side - 1.5, Math.floor(at / COLS) * side - 1.5, side + 2, side + 2);103 };104105 const split = (canvas) => {106 const b = board(canvas, 62, { top: 20, bottom: 24 });107 const parts = [108 ['missed', look.report.never, ink.orange],109 ['written once', look.report.once, ink.dim],110 ['written by many', look.report.multiple, ink.blue],111 ];112 let at = 0;113 let label = b.x(0);114 for (const [name, count, color] of parts) {115 b.ctx.fillStyle = color;116 b.ctx.fillRect(b.x(at / CEILING), b.roof, Math.max(1, (b.wide * count) / CEILING - 1), b.floor - b.roof);117 label = tag(b, `${name} ${count}`, color, 'left', label, b.h - 8) + 14;118 at += count;119 }120 tag(b, `1 to ${CEILING} at ${state.depth} rendered terms`, ink.dim);121 };122123 const champions = (canvas) => {124 const b = board(canvas, 210);125 bars(b, champs.map((row) => row.rows), { color: (i) => (champs[i].value === value ? ink.yellow : ink.blue) });126 axis(b, champs.map((row, i) => [(i + 0.5) / champs.length, row.value]));127 tag(b, 'rows writing the integer, the twenty heaviest', ink.dim);128 tag(b, `leader ${champs[0].value} at ${champs[0].rows} rows`, ink.fg, 'right');129 };130131 const onField = (event) => {132 const box = event.currentTarget.getBoundingClientRect();133 const side = box.width / COLS;134 const column = Math.floor((event.clientX - box.left) / side);135 const row = Math.floor((event.clientY - box.top) / side);136 if (column >= 0 && column < COLS && row >= 0 && row < LINES) choose(row * COLS + column + 1);137 };138139 const onChampions = (event) => {140 const box = event.currentTarget.getBoundingClientRect();141 const at = Math.floor(((event.clientX - box.left - 14) / (box.width - 28)) * champs.length);142 if (champs[at]) choose(champs[at].value);143 };144145 const random = () => {146 const [at] = roll(s.next(), [[1, CEILING]]);147 choose(at);148 };149150 const miss = () => {151 const all = JSON.parse(m.census_misses(CEILING));152 if (!all.length) return;153 const [at] = roll(s.next(), [[0, all.length - 1]]);154 choose(all[at]);155 };156157 const champion = () => {158 const rows = JSON.parse(m.census_champions(20));159 const [at] = roll(s.next(), [[0, rows.length - 1]]);160 choose(rows[at].value);161 };162163 const tierOf = (name) => found.tiers.find((tier) => tier.tier === name)?.rows ?? '';164165 const controls = (166 <Row>167 <label>integer <input type="number" min={1} max={CEILING} value={typing} onChange={(e) => { setTyping(e.target.value); if (e.target.value !== '') choose(+e.target.value, true); }} /></label>168 <Btn onClick={random}>Randomize</Btn>169 <Btn onClick={miss}>A miss</Btn>170 <Btn onClick={champion}>A champion</Btn>171 </Row>172 );173174 return (175 <Page crumb="integers" title="The integers this machine writes, and the ones it misses"176 sub="Every design, every measure, every axis is a row of the ledger, and every row writes a run of integers. Take the union over the whole registry and most small integers are written many times over, a few are written once, and some are written by nothing at all. Type an integer or click the field: you get every row that writes it, the design, the measure and the closed form, or the verdict that it is missed."177 foot={<>The census is only as good as its window, so the window is pinned and printed rather than assumed. A row is one design, one measure and one axis, taken over the four cost tiers of the <a href="../sequences">ledger</a>. A row's rendered window is its first <b>min(48, B)</b> terms, <b>B</b> the leading terms whose footprint fits 100000 cells: one cell for a closed measure, <code>number^dimension + level * span</code> for a convolved one, <code>number^(dimension * level)</code> for a grid. A row whose rendered terms are strictly increasing stops at the first term above the ceiling, which on this page is 1000. A row writes <b>n</b> when <b>n</b> is a term inside that window and <b>n</b> is in range, and multiplicity counts rows and not places, so a row writing the same integer twice counts once. Terms at or below zero - the Euler characteristics, the voids of a solid - are counted apart and never folded in. The page opens at the ledger's own eight-term heads and deepens on request, because more than half of the written set arrives past the head; the last pass is the pinned 48-term window, and the depth table above is the honest measure of how much the window itself decides. That a missed integer is written by no row at any depth is a conjecture and not a result: the rows the cap still cuts have deeper terms nobody has rendered, and the counter says how many rows those are. The same census runs to a ceiling of 100000 in the research tree, where the miss density climbs decade by decade; whatever the ceiling, a fixed registry renders at most 48 terms a row, so the written set is finite and far enough out the census is almost all miss. Every number here is computed in Rust and walked live through wasm; the page only draws. The census behind it, misses and all, is the <a href="/research/integers/">integers note</a>.</>}178 controls={controls}>179 <div className="panel">180 <h2>The pinned window <span>{walking ? `walking ${state.done} of ${state.total} rows at ${state.depth} terms` : state.complete ? `complete at the pinned ${WIN.cap}-term window` : `${state.pending} rows are cut by the ${state.depth}-term cap`}</span></h2>181 <div className="stats">182 <span>registry <b>{WIN.registry}</b> rows</span>183 <span>read <b>{`${state.rows} of ${WIN.registry} rows`}</b></span>184 <span>rendered terms <b>{state.complete ? `${state.depth}, the pinned cap` : state.depth}</b></span>185 <span>ceiling <b>{CEILING}</b></span>186 <span>cells a term <b>{WIN.cells}</b></span>187 <span><button hidden={walking || state.complete} onClick={() => { if (!walking && !state.complete) pass(); }}>{`deepen to ${state.next} terms · ${state.pending} rows`}</button></span>188 </div>189 <div className="meter"><div style={{ width: `${(100 * state.done) / Math.max(1, state.total)}%`, background: walking ? ink.blue : ink.green }} /></div>190 <Note error={error} />191 </div>192 <div className="arena">193 <div className="panel">194 <h2>The field <span>{`1 to ${CEILING}, one cell an integer, click to read one`}</span></h2>195 <Sketch draw={field} deps={[look, value]} aria-label="The field, one cell an integer, click to read one" onPointerDown={onField} />196 <div className="stats">197 <span><span className="swatch" style={{ background: ink.orange }} /> missed <b>{look.report.never}</b></span>198 <span><span className="swatch" style={{ background: ink.dim }} /> written once <b>{look.report.once}</b></span>199 <span><span className="swatch" style={{ background: ink.blue }} /> written by many <b>{look.report.multiple}</b></span>200 <span>share written <b>{look.report.share.toFixed(4)}</b></span>201 </div>202 <Sketch className="bars" style={{ height: 62 }} draw={split} deps={[look, state.depth]} role="img" aria-label="The split of the window into missed, written once and written by many" />203 </div>204 <div className="panel">205 <h2>The verdict <span>{`${state.rows} rows read at ${state.depth} rendered terms`}</span></h2>206 <p className="banner" style={{ color: found.rows === 0 ? ink.orange : found.rows === 1 ? ink.yellow : ink.fg }}>207 {found.rows ? `${value} is written by ${found.rows === 1 ? 'exactly one row' : `${found.rows} rows`}` : `${value} is missed: no row of the ${WIN.registry} writes it inside the window`}208 </p>209 <Stats>210 <Stat label="rows writing it">{found.rows}</Stat>211 <Stat label="closed">{tierOf('closed')}</Stat>212 <Stat label="convolved">{tierOf('convolved')}</Stat>213 <Stat label="side grid">{tierOf('side')}</Stat>214 <Stat label="level grid">{tierOf('level')}</Stat>215 <Stat label="page">{found.rows ? `${page + 1} of ${Math.ceil(found.rows / PAGE)}` : '0'}</Stat>216 <span>217 <button disabled={page === 0} onClick={() => setPage(page - 1)}>prev</button>{' '}218 <button disabled={(page + 1) * PAGE >= found.rows} onClick={() => setPage(page + 1)}>next</button>219 </span>220 </Stats>221 <div className="scroll">222 <table>223 <thead><tr><th>row</th><th>measure</th><th>closed form</th><th>writes it at</th><th>first terms</th></tr></thead>224 <tbody>225 {found.shown.length ? found.shown.map((row, i) => (226 <tr key={`${i}:${row.name}`}>227 <td className="mono"><a href={`../sequences?q=${row.name}`}>{row.name}</a></td>228 <td className="mono">{row.measure} · {row.axis}</td>229 <td className="mono">{row.closed || 'none known'}</td>230 <td className="num">term {row.index + 1}, {row.axis === 'level' ? `level ${row.term}` : `side ${row.side}`}</td>231 <td><Terms terms={row.head} tight /></td>232 </tr>233 )) : <tr><td className="dim">no row of the registry writes it</td><td colSpan={4}></td></tr>}234 </tbody>235 </table>236 </div>237 </div>238 </div>239 <div className="arena">240 <div className="panel">241 <h2>The champions <span>the integers the most rows write</span></h2>242 <Sketch className="bars" style={{ height: 210 }} draw={champions} deps={[champs, value]} aria-label="The champions, the integers the most rows write" onPointerDown={onChampions} />243 <h2>The first misses <span>click one to read it</span></h2>244 <Terms terms={misses} marks={misses.map((n) => n === value)} onPick={(n) => choose(Number(n))} empty="no integer of the window is missed" tight />245 </div>246 <div className="panel">247 <h2>The depth of the window <span>{state.complete ? 'the last row is the pinned window' : `${state.pending} rows are still cut by the cap`}</span></h2>248 <div className="scroll">249 <table>250 <thead><tr><th>rendered terms</th><th>written</th><th>missed</th><th>once</th><th>first miss</th><th>rows still cut</th></tr></thead>251 <tbody>252 {look.report.depths.length ? look.report.depths.map((row) => (253 <tr key={row.depth}>254 <td className="num">{row.depth}</td>255 <td className="num">{row.written}</td>256 <td className="num">{row.never}</td>257 <td className="num">{row.once}</td>258 <td className="num">{row.first_miss}</td>259 <td className="num">{row.deepenable}</td>260 </tr>261 )) : <tr><td className="dim">{`the ${WIN.depths[0]}-term pass is still walking`}</td><td colSpan={5}></td></tr>}262 </tbody>263 </table>264 </div>265 <h2>The miss density by decade</h2>266 <div className="scroll">267 <table>268 <thead><tr><th>decade</th><th>width</th><th>missed</th><th>miss density</th></tr></thead>269 <tbody>270 {look.report.bands.map((band) => (271 <tr key={band.first}>272 <td className="num">{band.first} to {band.last}</td>273 <td className="num">{band.width}</td>274 <td className="num">{band.missed}</td>275 <td className="num">{band.density.toFixed(6)}</td>276 </tr>277 ))}278 </tbody>279 </table>280 </div>281 <h2>The tiers</h2>282 <div className="scroll">283 <table>284 <thead><tr><th>tier</th><th>rows</th><th>written</th><th>written by this tier alone</th></tr></thead>285 <tbody>286 {look.report.tiers.map((tier) => (287 <tr key={tier.tier}>288 <td>{tier.tier}</td>289 <td className="num">{tier.rows}</td>290 <td className="num">{tier.written}</td>291 <td className="num">{tier.alone}</td>292 </tr>293 ))}294 </tbody>295 </table>296 </div>297 </div>298 </div>299 </Page>300 );301}302303mount(<App />);