index.jsx
9.9 kB · jsx · 165 lines
1import { useMemo } from 'react';2import { ready, ink } from '../../lib/mrly.js';3import { useQuery } from '../../lib/query.js';4import { mount, Page, Row, Pick, Slider, Stats, Stat, Note } from '../../lib/app.jsx';5import { Sketch } from '../../lib/draw.jsx';6import { Pins, Ratios, Terms, mix } from '../../lib/series.jsx';7import { board, line, axis, tag } from '../../lib/chart.js';89const m = await ready();10const BASES = [3, 5];11const CAPS = new Map(BASES.map((base) => [base, m.carry_cap(base)]));12const STRIP = Math.max(...CAPS.values());13const LANES = [[3, 'three'], [5, 'five']];14const SIGNS = JSON.parse(m.carry_signs(STRIP));15const CHAR = 6.7;1617function shown(value) {18 return value === null || value === undefined ? 'none' : Number(value).toFixed(6);19}2021function App() {22 const [pick, set] = useQuery({ base: 3, dimension: 3, levels: 8 });2324 const cap = CAPS.get(pick.base) ?? 2;25 const dimension = Math.min(cap, Math.max(2, pick.dimension));2627 const read = useMemo(() => {28 try {29 return { row: JSON.parse(m.carry_block(pick.base, dimension, pick.levels)), error: null };30 } catch (error) {31 return { row: null, error };32 }33 }, [pick.base, dimension, pick.levels]);3435 const row = read.row;3637 const block = (canvas) => {38 if (!row) return;39 const grid = row.block;40 const n = grid.length;41 const b = board(canvas, 62 + n * 34, { top: 26, bottom: 12 });42 const size = Math.min(b.wide / n, (b.floor - b.roof) / n);43 const left = b.x(0) + (b.wide - size * n) / 2;44 const peak = Math.max(...grid.flat(), 1);45 grid.forEach((cells, r) => cells.forEach((value, c) => {46 const x = left + c * size;47 const y = b.roof + r * size;48 b.ctx.fillStyle = mix(ink.line, ink.yellow, value / peak);49 b.ctx.fillRect(x, y, size - 2, size - 2);50 const text = String(value);51 if (size >= text.length * CHAR + 8) tag(b, text, ink.deep, 'center', x + size / 2 - 1, y + size / 2 + 3);52 }));53 tag(b, `the reflection-even block, ${n} by ${n}, rows the carry out`, ink.dim);54 };5556 const parity = (canvas) => {57 const b = board(canvas, 210, { top: 30, bottom: 24 });58 const n = SIGNS.length;59 const step = b.wide / n;60 const mid = (b.roof + b.floor) / 2;61 const tall = (b.floor - b.roof) / 2 / LANES.length - 3;62 SIGNS.forEach((each, i) => {63 const x = b.x(i / n) + 1;64 const wide = Math.max(1, step - 2);65 const odd = each.dimension % 2 === 1;66 LANES.forEach(([base, key], lane) => {67 const sign = each[key]?.sign;68 const y = odd ? mid - (lane + 1) * (tall + 3) : mid + 3 + lane * (tall + 3);69 b.ctx.fillStyle = sign === undefined ? ink.line : sign > 0 ? ink.orange : ink.blue;70 b.ctx.fillRect(x, y, wide, tall);71 if (base === pick.base && each.dimension === dimension) {72 b.ctx.strokeStyle = ink.fg;73 b.ctx.lineWidth = 1.5;74 b.ctx.strokeRect(x - 0.5, y - 0.5, wide + 1, tall + 1);75 } else if (each.open) {76 b.ctx.strokeStyle = ink.yellow;77 b.ctx.lineWidth = 1;78 b.ctx.strokeRect(x + 0.5, y + 0.5, wide - 1, tall - 1);79 }80 });81 });82 line(b, [[0, 0.5], [1, 0.5]], ink.dim, { width: 1, dash: [4, 4] });83 axis(b, SIGNS.map((each, i) => [(i + 0.5) / n, each.dimension]));84 const next = tag(b, 'odd D above, the slice exponent over the codimension', ink.orange);85 tag(b, 'even D below, under it', ink.blue, 'left', next + 16);86 tag(b, 'yellow: the open class D = 1 mod 3', ink.yellow, 'right');87 };8889 const controls = (90 <Row>91 <Pick label="base" value={pick.base} options={BASES} onChange={(value) => set({ base: +value, dimension: Math.min(CAPS.get(+value) ?? 2, dimension) })} />92 <Slider label="dimension" value={dimension} min={2} max={cap} onChange={(value) => set({ dimension: value })} show={`D ${dimension} of ${cap}`} />93 <Slider label="levels" value={pick.levels} min={1} max={24} onChange={(value) => set({ levels: value })} />94 </Row>95 );9697 return (98 <Page crumb="carry" title="One carry decides how many past terms a diagonal slice needs"99 sub={<>Keep the cells of the base whose digit vector has at most one middle digit and you have a sponge in every dimension. Cut it through the centre, square to the main diagonal, and count the cells the cut meets at each level. Refining by one level adds one digit per coordinate, and all the cut remembers is a carry - a small whole number that a contraction and a reflection squeeze into <code>ceil(dim/2)</code> states. That is the whole rule: <code>dim</code> axes, <code>ceil(dim/2)</code> past terms, and a growth exponent that misses the generic value by a hair, above it at odd <code>dim</code> and below it at even <code>dim</code>.</>}100 controls={controls}101 foot={<>The integers here are the crate's exact arithmetic; the root, the logs, the gap and the spectral ratio are its floating-point readings. The digit polynomial is the level-one census by digit sum, <code>A(t)^(dim-1)(A(t) + dim t^m)</code> with <code>A</code> the sum of every digit power but the middle one; at base three it factors as <code>(1 + t^2)^(dim-1)(1 + dim t + t^2)</code>. The carry map <code>c -> (c + m dim - s)/base</code> contracts onto <code>|c| <= (dim-1)/2</code>, the polynomial's palindromic symmetry folds that window in half, and the even block left over is the matrix drawn above: its characteristic polynomial is the recurrence, its Perron root the growth, and the order the counts exhibit is read back independently by the recurrence hunter that <a href="../plot">the plot</a> uses. The sign is a comparison of whole numbers, not a rounding: the characteristic polynomial is evaluated at <code>f_dim/base</code> with the denominators cleared, so a hundred and twenty-eight bits set the dimensions the strip can reach. At dim 3 the ladder is 1, 6, 42, 306, 2250 - the same count the sponge's diagonal profile gives on <a href="../slices">the slices</a> and <a href="../spectrometer">the spectrometer</a>, reached there by building the cut and here without one. The research page is <a href="/research/slices/">slices</a>; the order theorem and the odd half of the sign law at base 3, away from the open class, are the paper <a href="/papers/slice-recurrence-order/">slice-recurrence-order</a>, and the even half, at base three and base five, is <a href="/papers/slice-sign-even-half/">slice-sign-even-half</a>. The odd class <code>dim = 1 mod 3</code> is still open.</>}>102 <div className="arena">103 <div className="panel">104 <h2>The carry block <span>{row ? `order ${row.order}, ceil(D/2)` : 'nothing read'}</span></h2>105 <Sketch draw={block} deps={[row]} className="bars" role="img" aria-label="The carry block" />106 {row && <Terms terms={row.digits.map(String)} tight label="P(t)" />}107 {row && (108 <Stats>109 <Stat label="characteristic">{row.polynomial}</Stat>110 <Stat label="trace">{row.trace}</Stat>111 <Stat label="determinant">{row.determinant}</Stat>112 <Stat label="Perron root">{shown(row.read.root)}</Stat>113 <Stat label="fill">{row.fill}</Stat>114 <Stat label="spectral ratio">{shown(row.spectral)}</Stat>115 </Stats>116 )}117 </div>118 <div className="panel">119 <h2>The exponent <span>{row ? `sign ${row.read.sign > 0 ? 'plus one' : 'minus one'}` : 'nothing read'}</span></h2>120 {row && (121 <Stats>122 <Stat label={`log_${pick.base} of the root`}>{shown(row.read.log_root)}</Stat>123 <Stat label={`log_${pick.base} of the fill less one`}>{shown(row.read.log_fill)}</Stat>124 <Stat label="root against fill over base">{`${shown(row.read.root)} against ${row.fill} / ${pick.base}`}</Stat>125 <Stat label="gap">{shown(row.read.gap)}</Stat>126 <Stat label="sign read">{row.read.sign}</Stat>127 <Stat label="the law">{row.law}</Stat>128 <Stat label="agree">{row.read.sign === row.law ? 'yes' : 'no'}</Stat>129 <Stat label="class">{row.open ? 'the open odd class 1 mod 3' : dimension % 2 === 0 ? 'even half, proved on the shelf at bases 3 and 5' : pick.base === 3 ? 'odd half, proved on the shelf at base 3' : 'odd half at base 5, verified on this range and proved nowhere'}</Stat>130 </Stats>131 )}132 <h2>The order <span>{row ? (row.fits ? 'the terms agree' : 'not enough terms') : ''}</span></h2>133 {row && (134 <Stats>135 <Stat label="proved order">{row.order}</Stat>136 <Stat label="order the terms exhibit">{row.found ?? (row.fits ? 'none fits these terms' : 'too few terms to read')}</Stat>137 <Stat label="free bound">2D + 1</Stat>138 <Stat label="terms in hand">{row.terms.length}</Stat>139 <Stat label="held to">{row.capped ? 'the exact integers' : 'the levels asked for'}</Stat>140 </Stats>141 )}142 </div>143 </div>144 <div className="panel">145 <h2>The ladder <span>{row ? `a_${dimension}(L), base ${pick.base}` : ''}</span></h2>146 {row && <Pins terms={row.terms} start={0} label={`level from 0, base ${pick.base}, dimension ${dimension}`} />}147 {row && <Terms terms={row.terms} start={0} capped={row.capped} />}148 {row && <Ratios values={row.ratios} start={1} label={`each count against the one before, falling to the Perron root ${shown(row.read.root)}`} />}149 </div>150 <div className="panel">151 <h2>The sign law <span>{`dimensions 2 to ${STRIP}, both bases`}</span></h2>152 <Sketch draw={parity} deps={[pick.base, dimension]} className="bars" role="img" aria-label="The sign law" />153 <Stats>154 <Stat label="lanes">base 3 nearest the line, base 5 outside it</Stat>155 <Stat label="grey">past that base's exact cap</Stat>156 <Stat label="base 3 cap">{CAPS.get(3)}</Stat>157 <Stat label="base 5 cap">{CAPS.get(5)}</Stat>158 </Stats>159 </div>160 <Note error={read.error} />161 </Page>162 );163}164165mount(<App />);