import { useMemo } from 'react'; import { ready, ink, fit } from '../../lib/mrly.js'; import { mount, Page, Group, Row, Pick, Slider, Btn, Stats, Stat, Note } from '../../lib/app.jsx'; import { Sketch } from '../../lib/draw.jsx'; import { useQuery } from '../../lib/query.js'; const m = await ready(); const MENU = JSON.parse(m.radix_menu()); const KOCH = MENU.presets[0]; const PAD = 16; const TALL = 520; const PLACE = 'A design places its copies by hand today: the base is a whole number, the digits are cells of a base by base box, and every cell sits where its row and column put it. The place dial hands that job to a ring of the plane. Pick a ring, `Z[i]` on the square lattice or `Z[w]` on the hexagonal, pick a base `b` of norm `q = N(b)` inside it, and pick a digit `d` for some of the `q` residue classes modulo `b`. Each digit is a place map `phi_d(x) = (u_d x + d) / b`, a similarity of ratio `1 / sqrt(q)` turned by a unit `u_d`, and the design is the set those maps hold fixed. A word `d_1 ... d_level` lands on `sum_(j=1..level) (prod_(i { try { return { ...fn(), error: null }; } catch (error) { return { error }; } }; const symbol = (ring) => (ring === 'gaussian' ? 'i' : 'w'); const spell = (z, ring) => { if (z.c === 0) return `${z.a}`; const tail = Math.abs(z.c) === 1 ? symbol(ring) : `${Math.abs(z.c)}${symbol(ring)}`; if (z.a === 0) return z.c < 0 ? `-${tail}` : tail; return `${z.a} ${z.c < 0 ? '-' : '+'} ${tail}`; }; const write = (list) => [list.map((d) => `${d.a}:${d.c}`).join('_'), list.map((d) => d.u).join('_')]; const residues = (ring, a, c) => JSON.parse(m.radix_read(ring, a, c, '0:0', '0', 1)).residues; const full = (ring, a, c) => write(residues(ring, a, c).map((z) => ({ ...z, u: 0 }))); function App() { const [q, set] = useQuery({ ring: KOCH.ring, a: KOCH.a, c: KOCH.c, d: KOCH.digits, t: KOCH.twists, level: KOCH.level, line: KOCH.line }); const cap = useMemo(() => attempt(() => ({ cap: m.radix_cap(q.d) })), [q.d]); const level = Math.max(1, Math.min(cap.cap ?? 1, Math.round(q.level) || 1)); const read = useMemo(() => attempt(() => ({ card: JSON.parse(m.radix_read(q.ring, q.a, q.c, q.d, q.t, level)) })), [q.ring, q.a, q.c, q.d, q.t, level]); const drawn = useMemo(() => attempt(() => ({ points: m.radix_points(q.ring, q.a, q.c, q.d, q.t, level) })), [q.ring, q.a, q.c, q.d, q.t, level]); const card = read.card; const points = drawn.points; const ringCard = MENU.rings.find((row) => row.name === q.ring); const chosen = card ? card.digits.map((d, i) => ({ a: d.a, c: d.c, u: card.twists[i], cls: d.class })) : []; const lay = (list) => { const sorted = [...list].sort((one, two) => one.cls - two.cls); const [d, t] = write(sorted); set({ d, t, level: sorted.length ? Math.min(level, m.radix_cap(d)) : level }); }; const toggle = (index) => { if (chosen.some((d) => d.cls === index)) lay(chosen.filter((d) => d.cls !== index)); else lay([...chosen, { ...card.residues[index], u: 0, cls: index }]); }; const turn = (index, unit) => lay(chosen.map((d) => (d.cls === index ? { ...d, u: unit } : d))); const rebase = (ring, a, c) => { const [d, t] = full(ring, a, c); set({ ring, a, c, d, t, level: Math.min(level, m.radix_cap(d)) }); }; const preset = (name) => { const row = MENU.presets.find((one) => one.name === name); if (row) set({ ring: row.ring, a: row.a, c: row.c, d: row.digits, t: row.twists, level: row.level, line: row.line }); }; const draw = (canvas) => { const [ctx, w, h] = fit(canvas, TALL); ctx.clearRect(0, 0, w, h); if (!points || points.length < 2) return; let [lowX, lowY, highX, highY] = [Infinity, Infinity, -Infinity, -Infinity]; for (let i = 0; i < points.length; i += 2) { lowX = Math.min(lowX, points[i]); highX = Math.max(highX, points[i]); lowY = Math.min(lowY, points[i + 1]); highY = Math.max(highY, points[i + 1]); } const span = Math.max(highX - lowX, highY - lowY, 1e-9); const scale = Math.min(w - 2 * PAD, h - 2 * PAD) / span; const X = (x) => w / 2 + (x - (lowX + highX) / 2) * scale; const Y = (y) => h / 2 - (y - (lowY + highY) / 2) * scale; const count = points.length / 2; ctx.strokeStyle = ink.blue; ctx.fillStyle = ink.blue; if (q.line) { ctx.lineWidth = count > 4096 ? 0.6 : 1.2; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(X(points[0]), Y(points[1])); for (let i = 2; i < points.length; i += 2) ctx.lineTo(X(points[i]), Y(points[i + 1])); ctx.stroke(); return; } const dot = Math.max(1, Math.min(5, Math.round(0.6 * scale * span / Math.sqrt(count)))); for (let i = 0; i < points.length; i += 2) ctx.fillRect(X(points[i]) - dot / 2, Y(points[i + 1]) - dot / 2, dot, dot); }; const controls = ( <> [row.name, row.label])]} onChange={preset} /> [row.name, row.name === 'gaussian' ? 'Z[i], the square lattice' : 'Z[w], the hexagonal lattice'])} onChange={(name) => { const row = MENU.rings.find((one) => one.name === name); rebase(name, row.bases[0].a, row.bases[0].c); }} /> [`${b.a}:${b.c}`, `${spell(b, q.ring)}, norm ${b.norm}`])} onChange={(value) => { const [a, c] = value.split(':').map(Number); rebase(q.ring, a, c); }} /> set({ level: value })} /> set({ line: value === 'line' })} /> rebase(q.ring, q.a, q.c)}>Every residue lay(chosen.map((d) => ({ ...d, u: 0 })))}>Drop the twists ); return ( Every residue, count, dimension and point on this page comes from the crates through wasm; the page only draws. The level is capped so one drawing stays under 2^16 points. The preset names are the standard ones of the literature: what the crate pins is the arithmetic of each quintuple, never the naming. Nearby: the memory dial thins the words the same design accepts, the plane primes walks the same two rings, the tile is what the untwisted whole-number base does.}>

the design at level {level} {card ? `${card.fill} words, ${card.distinct} distinct points` : 'the place maps at work'}

{card ? ( {`${spell({ a: card.a, c: card.c }, card.ring)}, norm ${card.q}`} {card.size} {card.fill} {card.distinct} {card.dimension.toFixed(6)} {card.code} the fill is `card F ^ L` at every level, twists and all {card.glued ? the place maps glue: fewer points than words : no glue: one point a word} ) : null}

{PLACE}

the digits {card ? `${card.size} of the ${card.q} classes modulo the base` : 'one representative a class'}

{card ? (
{card.residues.map((z, index) => { const held = chosen.find((d) => d.cls === index); return ( toggle(index)} onKeyDown={(event) => { if (event.key === 'Enter') toggle(index); }}> {spell(held ?? z, card.ring)} ); })}
) : null} {card ? ( {chosen.map((d) => ( [String(index), spell(u, card.ring)])} onChange={(value) => turn(d.cls, Number(value))} /> ))} ) : null} {card ? ( {card.canonical ? 'every digit' : 'not every digit'} {card.canonical ? 'every digit is the least-norm representative of its class, so the code names this design' : 'a digit is off the canonical system, so the code names the classes and not this design'} ) : null}

{CODE}

the twists {card ? `${card.units.length} units on this lattice` : 'a rotation a digit'}

{TWIST}

{GLUE}

); } mount();