index.jsx
9.2 kB · jsx · 162 lines
1import { useMemo } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { mount, Page, Group, Pick, Slider, Text, Btn, Stats, Stat, Note } from '../../lib/app.jsx';4import { Grid } from '../../lib/draw.jsx';5import { Terms } from '../../lib/series.jsx';6import { useQuery } from '../../lib/query.js';78const m = await ready();910const FIRST = { d: 1, k: 2, code: '7', level: 9 };11const SPAN = m.memory_span();12const CAPS = { 1: m.memory_cap(1), 2: m.memory_cap(2) };1314const PRESETS = [15 ['7/1/2', 'the golden rule'],16 ['23/1/3', 'the supergolden rule'],17 ['54/1/3', 'the plastic rule'],18 ['127/1/3', 'the tribonacci rule'],19 ['3/1/1', 'the full shift'],20 ['0/1/2', 'the empty rule'],21 ['7/2/1', 'the plane design 7'],22 ['31710/2/2', 'no digit twice'],23];2425const DIAL =26 'A design word is a string of digits, one digit a level, and the digit is a corner of the cube: at dim 1 the two ends of an interval, at dim 2 the four corners of a square, read as the corner integer `c = sum_i d_i 2^i` with `x` bit `0` and `y` bit `1`. Today every word is allowed and the digits are independent, so the design is the same tile folded into itself. The memory dial breaks that independence: a rule names which windows of `k` consecutive digits may stand side by side, and a word survives only when every one of its windows is allowed. At `k = 1` the window is one digit, the rule is a plain digit set, and the accepted words are the cells of `bang dim <dim>, code <code>` at that level, cell for cell. Turn `k` past one and the design remembers what it just wrote.';2728const CODE =29 'The alphabet is the `2^dim` corner digits. A window is `k` of them, `(c_1, ..., c_k)`, read as the integer `w = sum_j c_j 2^(dim (k - j))` with the first digit most significant, and bit `w` of the code says whether that window may stand. So a rule of width `k` at dim `dim` is one number below `2^(2^(k dim))`, and the span `k dim <= ' + SPAN + '` keeps it inside a 64-bit code. Click a window below to forbid it or let it back in.';3031const GROWTH =32 'Accepted words of length `level` are the walks of length `level - k + 1` on the transfer matrix `A`, whose states are the `2^((k - 1) dim)` windows one digit short, so `N_W(level) = 1^T A^(level - k + 1) 1` and for `level < k` every word counts, `2^(dim level)` of them. The count grows like the Perron root `rho` of `A`, and the growth exponent is `log_2 rho`. The memory number `kappa = log_2(card W) / k - log_2 rho`, for `card W` the allowed windows, is the bits per digit a rule spends on memory, zero on every memoryless design. A word of length `m k` cuts into `m` windows that never meet, so `rho^k <= card W` and `kappa >= 0`; a rule whose windows are a digit set repeated `k` times reaches that bound and spends nothing, and the golden rule spends about a tenth of a bit a digit.';3334const attempt = (fn) => {35 try {36 return { ...fn(), error: null };37 } catch (error) {38 return { error };39 }40};4142const memoryless = (card) => Number.isFinite(card.kappa) && Math.abs(card.kappa) < 1e-9;4344const digits = (w, d, k) => Array.from({ length: k }, (_, j) => (w >> (d * (k - 1 - j))) & ((1 << d) - 1));4546const stack = (sheet, level) => {47 const src = sheet.types;48 const band = Math.max(1, Math.round(sheet.width / (2 * level)));49 const types = new Uint8Array(sheet.width * level * band);50 for (let row = 0; row < level * band; row++) {51 const from = Math.floor(row / band) * sheet.width;52 types.set(src.subarray(from, from + sheet.width), row * sheet.width);53 }54 return { width: sheet.width, height: level * band, types };55};5657function App() {58 const [pick, set] = useQuery(FIRST);5960 const d = pick.d === 2 ? 2 : 1;61 const k = Math.max(1, Math.min(Math.floor(SPAN / d), Math.round(pick.k) || 1));62 const cap = CAPS[d];63 const level = Math.max(1, Math.min(cap, Math.round(pick.level) || 1));64 const code = /^\d+$/.test(pick.code) ? pick.code : '0';6566 const read = useMemo(() => attempt(() => ({ read: JSON.parse(m.memory_read(d, k, code, level)) })), [d, k, code, level]);67 const drawn = useMemo(() => attempt(() => ({ sheet: m.memory_sheet(d, k, code, level) })), [d, k, code, level]);6869 const card = read.read;70 const toggle = (w) => set({ code: (BigInt(code) ^ (1n << BigInt(w))).toString() });71 const preset = (value) => {72 const [c, dim, width] = value.split('/');73 set({ code: c, d: +dim, k: +width, level: Math.min(CAPS[+dim], level) });74 };7576 const controls = (77 <>78 <Group name="The rule">79 <Pick label="preset" value="" options={[['', 'pick one'], ...PRESETS]} onChange={preset} />80 <Pick label="dimension" value={String(d)} options={[['1', '1, the interval'], ['2', '2, the square']]}81 onChange={(value) => set({ d: +value, code: '0', k: 1, level: Math.min(CAPS[+value], level) })} />82 <Slider label="width k" value={k} min={1} max={Math.floor(SPAN / d)} onChange={(value) => set({ k: value, code: '0' })} />83 </Group>84 <Group name="The code">85 <Text label="code" wide value={code} onChange={(value) => set({ code: value.replace(/\D/g, '') || '0' })} />86 <Btn onClick={() => set({ code: card ? (BigInt(card.codes) - 1n).toString() : '0' })}>Allow all</Btn>87 <Btn onClick={() => set({ code: '0' })}>Forbid all</Btn>88 </Group>89 <Group name="The depth">90 <Slider label={`level 1 to ${cap}`} value={level} min={1} max={cap} onChange={(value) => set({ level: value })} />91 </Group>92 </>93 );9495 const sheet = drawn.sheet;9697 return (98 <Page crumb="memory" title="The memory dial"99 sub="A design accepts every word of digits it can write. Give it a memory of the last k digits and it accepts fewer: the cells thin out, the count stops doubling, and the growth exponent slides off the dimension. At width one the dial is off and the picture is the ordinary design of the same code."100 controls={controls}101 foot={<>Every count, root and exponent on this page comes from the crates through wasm; the page only draws. The sheet is capped at 2^16 sites, so the level runs to {CAPS[1]} in one dimension and to {CAPS[2]} in two. Nearby: <a href="../universe">the universe</a> is the gallery of the codes this dial reads at width one, <a href="../words">words</a> changes the rule level by level instead of digit by digit, <a href="../wolfram">the rules</a> reads the same byte as a cellular automaton.</>}>102103 <div className="panel">104 <h2>the accepted words <span>{d === 1 ? 'one row a level, the intervals a word still reaches' : `the ${1 << level} by ${1 << level} grid of level ${level}`}</span></h2>105 <Note error={drawn.error ?? read.error} />106 {sheet ? (107 <Grid grid={d === 1 ? stack(sheet, level) : sheet} on={ink.yellow}108 style={d === 1 ? { height: 'clamp(200px, 34vw, 420px)' } : undefined}109 role="img" aria-label={`the words of width ${k} rule ${code} at dimension ${d}, level ${level}`} />110 ) : null}111 {card ? (112 <Stats>113 <Stat label="rule">{`dim ${card.dimension}, k ${card.width}, code ${card.code}`}</Stat>114 <Stat label="level">{level}</Stat>115 <Stat label="accepted words">{card.counts[level - 1]}</Stat>116 <Stat label="every word">{card.letters ** level}</Stat>117 {k === 1 ? <span className="chip verified">width one, so this sheet is the design bang dim {card.dimension}, code {card.code} at level {level}, cell for cell</span> : null}118 </Stats>119 ) : null}120 <p className="sub">{DIAL}</p>121 </div>122123 <div className="arena">124 <div className="panel">125 <h2>the count a level <span>N_W(level) for level one to {level}</span></h2>126 {card ? <Terms terms={card.counts} start={1} /> : null}127 {card ? (128 <Stats>129 <Stat label="Perron root">{card.perron.toFixed(9)}</Stat>130 <Stat label="log_2 rho">{Number.isFinite(card.exponent) ? card.exponent.toFixed(9) : 'none'}</Stat>131 <Stat label="kappa">{Number.isFinite(card.kappa) ? card.kappa.toFixed(9) : 'all of it'}</Stat>132 <Stat label="allowed windows">{card.window_count}</Stat>133 <Stat label="states">{card.states}</Stat>134 <Stat label="alphabet">{card.alphabet.length ? card.alphabet.join(', ') : 'empty'}</Stat>135 <span className={`chip ${memoryless(card) ? 'verified' : 'proved'}`}>136 {memoryless(card) ? 'a memoryless rule, no bits spent on memory' : 'the windows spend this rule bits'}137 </span>138 </Stats>139 ) : null}140 <p className="sub">{GROWTH}</p>141 </div>142143 <div className="panel">144 <h2>the windows <span>{card ? `${card.windows} of them, ${card.windows} bits of code below ${card.codes}` : 'the dial itself'}</span></h2>145 {card ? (146 <div className="ribbon tight">147 {card.allowed.map((on, w) => (148 <span key={w} role="button" tabIndex={0} className={on ? 'yellow' : undefined}149 onClick={() => toggle(w)} onKeyDown={(event) => { if (event.key === 'Enter') toggle(w); }}>150 <b>{digits(w, d, k).join('·')}</b>151 </span>152 ))}153 </div>154 ) : null}155 <p className="sub">{CODE}</p>156 </div>157 </div>158 </Page>159 );160}161162mount(<App />);