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 />);