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