index.jsx
7.2 kB · jsx · 120 lines
1import { useMemo } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { mount, Page, Group, Pick, Slider, Stats, Stat, Note } from '../../lib/app.jsx';4import { Grid, Pixels } from '../../lib/draw.jsx';5import { useQuery } from '../../lib/query.js';6import { useSeeds, seeded, Picker, Ramp } from '../../lib/select.jsx';78const m = await ready();9const SIDES = [2, 3, 5];10const DEEPEST = 5;11const BUDGET = 65536;1213const cap = (side) => Math.min(DEEPEST, m.level_cap(side, 2, BUDGET));14const wrap = (value, span) => ((value % span) + span) % span;1516function App() {17 const s = useSeeds();18 const [pick, set] = useQuery({19 code: seeded(s, 2, 3, '495'), side: 3, base: 3, level: 4,20 t1: 0, t2: 0, bar: 10, ramp: 'heat', levels: 32, invert: false,21 });2223 const code = pick.code.trim();24 const level = Math.max(1, Math.min(pick.level, cap(pick.side)));25 const span = pick.side ** level;26 const t1 = wrap(pick.t1, span);27 const t2 = wrap(pick.t2, span);2829 const built = useMemo(() => {30 try {31 return {32 grid: m.two_grid(code, pick.side, level, 0, pick.base),33 field: m.modes_field(code, pick.side, level, pick.base),34 digits: m.modes_digits(code, pick.side, pick.base),35 name: m.name_of(code, 2, pick.base),36 error: null,37 };38 } catch (error) {39 return { grid: null, field: null, digits: 0, name: '', error };40 }41 }, [code, pick.side, level, pick.base]);4243 const read = useMemo(() => built.field && Float32Array.from(built.field, (v) => v ** (1 / level)), [built.field, level]);44 const heat = useMemo(() => read && m.paint_span(read, span, 0, 1, pick.ramp, pick.levels, pick.invert), [read, pick.ramp, pick.levels, pick.invert]);45 const wave = useMemo(() => {46 if (built.error) return null;47 return m.paint_span(m.modes_pattern(code, pick.side, level, pick.base, t1, t2), span, -1, 1, 'diverge', pick.levels, false);48 }, [built.field, t1, t2, pick.levels]);49 const value = useMemo(() => (built.error ? null : m.modes_value(code, pick.side, level, pick.base, t1, t2)), [built.field, t1, t2]);50 const large = useMemo(() => (built.error ? 0 : m.modes_large(code, pick.side, level, pick.base, pick.bar / 100)), [built.field, pick.bar]);5152 const mass = built.digits ** level;53 const seek = (event) => {54 const box = event.currentTarget.getBoundingClientRect();55 const col = Math.floor(((event.clientX - box.left) / box.width) * span);56 const row = Math.floor(((event.clientY - box.top) / box.height) * span);57 set({ t1: Math.max(0, Math.min(span - 1, row)), t2: Math.max(0, Math.min(span - 1, col)) });58 };5960 const controls = (61 <>62 <Group name="Design">63 <Picker dimension={2} bases={[3, 2]} code={pick.code} base={pick.base} seeds={s} onChange={set} />64 <Pick label="side" value={pick.side} options={SIDES.map((v) => [v, v])} onChange={(v) => set({ side: +v, level: Math.min(level, cap(+v)) })} />65 <Slider label="level" value={level} min={1} max={cap(pick.side)} show={`${level}, ${span} by ${span}`} onChange={(v) => set({ level: v, t1: wrap(t1, pick.side ** v), t2: wrap(t2, pick.side ** v) })} />66 </Group>67 <Group name="Frequency">68 <Slider label="t1" value={t1} min={0} max={span - 1} onChange={(v) => set({ t1: v })} />69 <Slider label="t2" value={t2} min={0} max={span - 1} onChange={(v) => set({ t2: v })} />70 </Group>71 <Group name="Large values">72 <Slider label="threshold" value={pick.bar} min={0} max={100} show={`${pick.bar}% of fill^level, ${large} frequencies`} onChange={(v) => set({ bar: v })} />73 </Group>74 <Group name="Colour">75 <Ramp value={pick} onChange={set} />76 </Group>77 </>78 );7980 return (81 <Page crumb="modes" title="The modes of a design mask"82 sub={<>Lay a design's stencil at the level over every point of a <code>side^level</code> by <code>side^level</code> torus and add up what it covers. That operator has one family of modes, the waves <code>e(<t, x> / side^level)</code>, and each one is stretched by a single number. Pick a frequency and watch its wave; the middle panel is the whole field of those numbers at once.</>}83 controls={controls}84 foot={<>The design is the picker's plane code at its side and its residue base; the filled cells of its level-one tile are the digit set <code>F</code>, and the stencil at the level is every sum of <code>level</code> of them scaled by the powers of the side. The eigenvalue is a product over the digits, so the field <code>|lambda|</code> is <code>level</code> rescaled copies of one small transform multiplied together, which is why it repeats at every scale like the design itself. The middle panel reads the field at its per-level root, <code>|lambda|^(1/level) / fill</code>, the average size of one factor; the printed numbers are the raw ones. The same design stacked over its own scales is <a href="../moire">moire</a>, turned on itself <a href="../radial">radial</a>, and joined into a network whose Laplacian has its own spectrum on <a href="../spectra">spectra</a>; the same stencil run as a neighbourhood is <a href="../mrlylife">mrlylife</a>. Every eigenvalue, every count and every wave comes out of the crates through wasm; the page only draws.</>}>85 <p><span className="chip proved">Proved</span> On the torus <code>(Z/side^level)^2</code> the mask operator <code>(A x)(u) = sum over s in S_level of x(u + s)</code> holds every character <code>e(<t, x> / side^level)</code> fixed in direction, with eigenvalue <code>lambda(t) = prod over j < level of hat F(side^j t / side^level)</code> where <code>hat F(y) = sum over v in F of e(<v, y>)</code>, so <code>lambda(0) = fill^level</code>.</p>86 <div className="arena">87 <div className="panel">88 <h2>The mask <span>{`level ${level}, ${mass} cells of ${span * span}`}</span></h2>89 {built.grid && <Grid grid={built.grid} on={ink.yellow} role="img" aria-label="The design mask" />}90 </div>91 <div className="panel">92 <h2>The eigenvalue field <span>{`|lambda|^(1/${level}) / fill, click to pick t`}</span></h2>93 {heat && <Pixels data={heat} onPointerDown={seek} role="img" aria-label="The eigenvalue field on the frequency torus" />}94 </div>95 <div className="panel">96 <h2>The mode <span>{`t = (${t1}, ${t2})`}</span></h2>97 {wave && <Pixels data={wave} role="img" aria-label="The real mode of the chosen frequency" />}98 </div>99 </div>100 <Stats>101 <Stat label="design">{built.name}</Stat>102 <Stat label="side">{pick.side}</Stat>103 <Stat label="base">{pick.base}</Stat>104 <Stat label="digits">{built.digits}</Stat>105 <Stat label="mass fill^level">{mass}</Stat>106 <Stat label="torus">{`${span} by ${span}`}</Stat>107 </Stats>108 <Stats>109 <Stat label="lambda re">{value ? value[0].toFixed(6) : ''}</Stat>110 <Stat label="lambda im">{value ? value[1].toFixed(6) : ''}</Stat>111 <Stat label="|lambda| / fill^level">{value ? value[2].toFixed(6) : ''}</Stat>112 <Stat label="large values">{`${large} at ${pick.bar}% of fill^level`}</Stat>113 <Stat label="share">{`${((large / (span * span)) * 100).toFixed(3)}%`}</Stat>114 </Stats>115 <Note error={built.error} />116 </Page>117 );118}119120mount(<App />);