index.jsx
9.1 kB · jsx · 160 lines
1import { useEffect, useMemo, useRef } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { mount, Page, Row, Pick, Slider, Btn, Stats, Stat, Note } from '../../lib/app.jsx';4import { Grid, Markup, Sketch } from '../../lib/draw.jsx';5import { useQuery } from '../../lib/query.js';6import { useSeeds, seeded, Picker } from '../../lib/select.jsx';7import { board, line, axis, tag } from '../../lib/chart.js';89const m = await ready();10const SERVE = 1100;11const OBJECTS = [['triangle', 'Sierpinski triangle'], ['code', 'flat code'], ['carpet', 'carpet slice'], ['solid', 'solid slice']];12const FIRST = { object: 'triangle', code: '7', number: 3, side: 5, level: 4, operator: 1, window: 10 };1314const first = (seeds) => (seeds.get() ? { ...FIRST, code: seeded(seeds, 2, 2, '7'), object: 'code' } : FIRST);1516function plan(look, number) {17 if (look.object === 'solid') return { kind: 'slice', code: '255', number, cap: 1 };18 if (look.object === 'carpet') return { kind: 'slice', code: '23', number: 3, cap: 2 };19 if (look.object === 'triangle') return { kind: 'flat', code: '7', number: 2, cap: m.fill_cap('7', 2, 2, 2, SERVE) };20 const code = look.code.trim();21 return { kind: 'flat', code, number: look.number, cap: m.fill_cap(code, look.number, 2, 2, SERVE) };22}2324function Lock({ off, children }) {25 return <fieldset className="set" disabled={off}>{children}</fieldset>;26}2728function App() {29 const s = useSeeds();30 const [look, set] = useQuery(first(s));31 const kept = useRef({ spec: null, level: look.level, number: 0, data: null, grid: null, svg: null, what: '' });3233 const view = useMemo(() => {34 try {35 const number = JSON.parse(m.slice_series('255', look.side))[look.side - 1].n;36 const spec = plan(look, number);37 const level = Math.min(Math.max(1, look.level), spec.cap);38 const data = JSON.parse(m.spectrum(spec.kind, spec.code, spec.number, level, look.operator === 1, look.window / 100));39 const slice = spec.kind === 'slice';40 const svg = slice ? m.hex_svg(spec.code, spec.number, level, 2, 'cut', Math.max(2, Math.round(300 / spec.number ** level))) : null;41 const grid = slice ? null : m.two_grid(spec.code, spec.number, level, 0, 2);42 const what = `${m.name_of(spec.code, slice ? 3 : 2, 2)}, level ${level}`;43 kept.current = { spec, level, number, data, grid, svg, what };44 return { ...kept.current, error: null };45 } catch (error) {46 return { ...kept.current, error };47 }48 }, [look.object, look.code, look.number, look.side, look.level, look.operator, look.window]);4950 useEffect(() => {51 if (view.level !== look.level) set({ level: view.level });52 }, [view.level]);5354 const chart = (canvas) => {55 const data = view.data;56 if (!data) return;57 const b = board(canvas, 300, { left: 52, right: 14, top: 14, bottom: 26 });58 const steps = data.stair;59 if (steps.length < 2) return;60 const xs = steps.map((p) => Math.log(p[0]));61 const ys = steps.map((p) => Math.log(p[1]));62 const x0 = xs[0], x1 = xs.at(-1), y0 = ys[0];63 const fx = (x) => (x1 === x0 ? 0.5 : (x - x0) / (x1 - x0));64 const fy = (y) => (y0 === 0 ? 0.5 : (y - y0) / -y0);65 if (data.fitted) {66 b.ctx.fillStyle = ink.panel;67 b.ctx.fillRect(b.x(0), b.roof, b.x(fx(xs[data.fitted - 1])) - b.x(0), b.tall);68 }69 axis(b, [[0, steps[0][0].toExponential(2)], [1, steps.at(-1)[0].toFixed(4)]], { wall: true });70 const stair = [[0, fy(ys[0])]];71 for (let i = 1; i < steps.length; i++) stair.push([fx(xs[i]), fy(ys[i - 1])], [fx(xs[i]), fy(ys[i])]);72 line(b, stair, ink.blue, { width: 1.6 });73 if (data.fit) {74 const [intercept, slope] = data.fit;75 const seg = (a, c) => [[fx(a), fy(intercept + slope * a)], [fx(c), fy(intercept + slope * c)]];76 line(b, seg(x0, x1), ink.yellow, { width: 1.2, dash: [3, 4] });77 line(b, seg(x0, xs[data.fitted - 1]), ink.yellow, { width: 2.2 });78 }79 tag(b, '1', ink.dim, 'right', b.x(0) - 6, b.y(fy(0)) + 4);80 tag(b, steps[0][1].toFixed(4), ink.dim, 'right', b.x(0) - 6, b.y(fy(y0)) + 4);81 };8283 const data = view.data, cap = view.spec ? view.spec.cap : 1, level = view.level;84 const legend = !view.error && data;8586 const controls = (87 <>88 <section>89 <h3>The object</h3>90 <Row>91 <Pick label="object" value={look.object} options={OBJECTS} onChange={(v) => set({ object: v })} />92 <Lock off={look.object !== 'code'}>93 <Picker dimension={2} code={look.code} seeds={s} button={false} onChange={(patch) => set({ ...patch, object: 'code' })} />94 </Lock>95 <Btn onClick={() => set({ code: m.random_code(2, 2, s.next()), object: 'code' })}>Randomize</Btn>96 <Lock off={look.object !== 'code'}>97 <Pick label="tile" value={look.number} options={[[2, 2], [3, 3]]} onChange={(v) => set({ number: +v })} />98 </Lock>99 <Lock off={look.object !== 'solid'}>100 <Slider label="side" value={look.side} min={2} max={7} show={`n ${view.number}`} onChange={(v) => set({ side: v })} />101 </Lock>102 <Lock off={look.object === 'solid'}>103 <Slider label="level" value={level} min={1} max={cap} show={`${level} of ${cap}`} onChange={(v) => set({ level: v })} />104 </Lock>105 </Row>106 </section>107 <section>108 <h3>The reading</h3>109 <Row>110 <Pick label="operator" value={look.operator} options={[[1, 'normalised'], [0, 'combinatorial']]} onChange={(v) => set({ operator: +v })} />111 <Slider label="window" value={look.window} min={1} max={20} show={`${look.window}%`} onChange={(v) => set({ window: v })} />112 </Row>113 </section>114 </>115 );116117 return (118 <Page crumb="spectra" title="A third of the triangle's spectrum sits on a single number"119 sub={<>Build the graph on the filled cells of a design, take its normalised Laplacian <code>I - D^-1/2 A D^-1/2</code>, and diagonalise. On the Sierpinski triangle the spectrum is extraordinarily degenerate: the eigenvalue 1 alone carries a third of it, and a cascade of families sits beneath, the first at <code>1 -/+ sqrt(30)/6</code>. The same operator on the diagonal section of a cube answers a different question - the low end of its integrated density of states has a slope, and twice that slope is the random-walk spectral dimension <code>d_s</code>. One staircase, two readings: the jumps are the degeneracy, the low corner is the dimension.</>}120 controls={controls}121 foot={<>The graph, the Laplacian, the eigenvalues, the clustering and the fit all come out of the crates: a dense real symmetric eigensolver written from scratch - Householder tridiagonalisation then implicit QL with Wilkinson shifts - returns the spectrum ascending, the clusters split it at consecutive gaps above <code>1e-9</code>, and the low-window least squares in log-log returns an intercept and a slope the page only draws. The slice reading is taken on the giant piece; the whole section's piece count is reported beside it. Anything past the export's node cap is refused rather than served slowly. How much of a design's complexity the geometry can see, and the two designs that draw alike and differ everywhere else, are in <a href="/research/complexity/">the complexity note</a>.</>}>122 <div className="arena">123 <div className="panel">124 <h2>Integrated density of states <span>{`log rank fraction against log eigenvalue, ${look.operator === 1 ? 'normalised' : 'combinatorial'}`}</span></h2>125 <Sketch draw={chart} deps={[view]} className="bars" role="img" aria-label="Integrated density of states" />126 <Stats>127 {legend && (128 <>129 <span>staircase <b style={{ color: ink.blue }}>{data.distinct} distinct</b></span>130 <span>shaded low window <b>{look.window}%</b></span>131 <span>fitted slope <b style={{ color: ink.yellow }}>{data.fit ? data.fit[1].toFixed(4) : 'none'}</b></span>132 <span>d_s = 2 x slope <b>{data.exponent === null ? 'none' : data.exponent.toFixed(3)}</b></span>133 </>134 )}135 </Stats>136 </div>137 <div className="panel">138 <h2>The object <span>{view.what}</span></h2>139 {view.grid ? <Grid grid={view.grid} on={ink.blue} role="img" aria-label="The object" /> : <Markup svg={view.svg ?? ''} role="img" aria-label="The object" />}140 <h2>The eight largest clusters</h2>141 <pre>{legend ? data.top.map(([value, size]) => `${value.toFixed(10).padStart(14)} x${size}`).join('\n') : ''}</pre>142 </div>143 </div>144 <Stats>145 <Stat label="nodes">{data?.nodes}</Stat>146 <Stat label="edges">{data?.edges}</Stat>147 <Stat label="pieces">{data?.components}</Stat>148 <Stat label="distinct">{data?.distinct}</Stat>149 <Stat label="degenerate classes">{data?.classes}</Stat>150 <Stat label="repeated fraction">{data?.repeated}</Stat>151 <Stat label="mult of 1">{data?.one}</Stat>152 <Stat label="mult of the pair">{data?.pair.join(' and ')}</Stat>153 <Stat label="d_s">{data && (data.exponent === null ? 'none' : data.exponent.toFixed(4))}</Stat>154 </Stats>155 <Note error={view.error} />156 </Page>157 );158}159160mount(<App />);