import { useMemo } from 'react'; import { ready, ink } from '../../lib/mrly.js'; import { board, line, axis, rules, tag } from '../../lib/chart.js'; import { mount, Page, Group, Pick, Slider, Check, Stats, Stat, Note } from '../../lib/app.jsx'; import { Signs, Sketch } from '../../lib/draw.jsx'; import { useQuery } from '../../lib/query.js'; import { useSeeds, seeded, Picker } from '../../lib/select.jsx'; const m = await ready(); const SIDES = [2, 3, 5]; const DEEPEST = 6; const CELLS = 729; const SAMPLES = 200; const SWEEP = 2001; const PAD = 14; const band = () => ({ plus: ink.fg, minus: ink.blue, empty: ink.deep }); const cap = (side) => Math.min(DEEPEST, m.level_cap(side, 1, CELLS)); function App() { const s = useSeeds(); const [pick, set] = useQuery({ code: seeded(s, 2, 3, '495'), side: 3, base: 3, level: 5, eps: 7, closed: true, }); const code = pick.code.trim(); const level = Math.max(1, Math.min(pick.level, cap(pick.side))); const span = pick.side ** level; const reach = Math.max(1, span / pick.side); const eps = Math.max(1, Math.min(pick.eps, reach)); const built = useMemo(() => { try { return { dist: m.tube_distance(code, pick.side, level, pick.base), pairs: m.tube_profile(code, pick.side, level, pick.base, SAMPLES), digits: m.modes_digits(code, pick.side, pick.base), d: m.dimension(code, pick.side, 2, pick.base), inside: m.tube_class(code, pick.side, pick.base), name: m.name_of(code, 2, pick.base), error: null, }; } catch (error) { return { dist: null, pairs: null, digits: 0, d: 0, inside: false, name: '', error }; } }, [code, pick.side, level, pick.base]); const limit = useMemo(() => { if (!built.inside) return null; const gap = 1 - 1 / pick.side; let low = Infinity, high = -Infinity; for (let i = 0; i < SWEEP; i += 1) { const g = m.tube_closed(pick.side, built.digits, 1 / pick.side + (gap * i) / (SWEEP - 1)); low = Math.min(low, g); high = Math.max(high, g); } return { low, high, swing: (100 * (high - low)) / low }; }, [built.inside, built.digits, pick.side]); const cells = useMemo(() => { if (!built.dist) return null; return { width: span, height: span, types: Uint8Array.from(built.dist, (v) => (v === 0 ? 0 : v <= eps ? 1 : 2)) }; }, [built.dist, eps]); const volume = useMemo( () => (built.dist ? m.tube_volume(code, pick.side, level, pick.base, eps) : null), [built.dist, eps], ); const yellow = useMemo(() => { if (!limit || !built.pairs || !pick.closed) return null; const out = []; for (let k = 0; k < built.pairs.length; k += 2) out.push(m.tube_closed(pick.side, built.digits, Math.exp(-built.pairs[k]))); return out; }, [limit, built.pairs, pick.closed]); const profile = (canvas) => { const pairs = built.pairs; if (!pairs || pairs.length < 4) return; const b = board(canvas, 340, { pad: PAD, top: 20, bottom: 22 }); const us = [], ms = []; for (let k = 0; k < pairs.length; k += 2) { us.push(pairs[k]); ms.push(pairs[k + 1]); } const [u0, u1] = [us[0], us.at(-1)]; const seen = yellow ? ms.concat(yellow) : ms; const floor = Math.min(...seen), roof = Math.max(...seen); const pad = (roof - floor) * 0.08 || 0.05; const fx = (u) => (u - u0) / (u1 - u0); const fy = (v) => (v - floor + pad) / (roof - floor + 2 * pad); const step = Math.log(pick.side); const marks = []; for (let n = Math.ceil(u0 / step); n * step <= u1; n += 1) marks.push(fx(n * step)); rules(b, marks, { dash: [2, 4] }); rules(b, [fx(Math.log(span / eps))], { color: ink.pink }); if (yellow) { line(b, [[0, fy(limit.low)], [1, fy(limit.low)]], ink.dim, { width: 1, dash: [3, 5] }); line(b, [[0, fy(limit.high)], [1, fy(limit.high)]], ink.dim, { width: 1, dash: [3, 5] }); line(b, us.map((u, i) => [fx(u), fy(yellow[i])]), ink.yellow, { width: 1.6 }); } line(b, us.map((u, i) => [fx(u), fy(ms[i])]), ink.blue, { width: 1.6 }); axis(b, [[0, `ln 1/eps ${u0.toFixed(2)}`], [1, u1.toFixed(2)]], { wall: true }); const edge = tag(b, 'M measured', ink.blue); if (yellow) tag(b, `G limit, swing ${limit.swing.toFixed(5)}%`, ink.yellow, 'left', edge + 12); }; const controls = ( <> [v, v])} onChange={(v) => set({ side: +v, level: Math.min(level, cap(+v)) })} /> set({ level: v })} /> set({ eps: v })} /> set({ closed: v })} /> ); return ( Fatten a design by eps and measure the area it swallows: that is the inner tube V(eps). Divide by the power the dimension asks for and you get the Minkowski content reading M(eps), which should settle down if the design has a length in its own dimension. Drag the radius and watch it not settle: it circles the same profile forever, once per factor of the base.} controls={controls} foot={<>The left panel is an exact Euclidean distance transform of the grid at the level in Rust, the two-pass lower envelope of parabolas, so every cell carries its true distance in cell widths to the nearest filled cell; the band is that field thresholded at the radius, and the tube area is the field read again, each cell carrying the share of itself the radius reaches. No hole lemma enters, so every design the picker offers gets a tube and a profile. The yellow curve is the other route and applies only to designs whose holes are isolated interior squares with their boundaries in the set: there the complement splits level by level into fill^(m-1) open squares of side base^(-m), the inner parallel area of a square of side s is 4 eps s - 4 eps^2 until 2 eps passes s and s^2 after, and the two geometric tails close in the form the page draws. The measured curve sits below the limit by about (4/5) eps^(log(fill)/log(base) - 1), the cost of a finite grid, and climbs onto it as the radius shrinks. The dashed rules are the powers of the base, one period of the profile apart. The same design counted inside a shape rather than fattened is the crop, and its mask laid over the torus is the modes. Every distance, area and profile value comes out of the crates through wasm; the page only draws.}>

Proved The Sierpinski carpet is not Minkowski measurable: its tube is the exact hole sum, M(eps) runs onto a log-periodic G(t) with G(1/3) = G(1) = 379/280, and the swing between its maximum and its minimum is 0.36625%, above zero, so no limit exists. The proof, the class it opens and the sponge it does not reach are on the dimensions page.

The design and its tube {`level ${level}, radius ${eps} of ${span} cells`}

{cells && }

The Minkowski profile {built.pairs && built.pairs.length ? `M against ln 1/eps, ${SAMPLES} radii` : 'the grid is too coarse to resolve a range of radii'}

{built.name} {pick.base} {pick.side} {built.digits} {built.d ? built.d.toFixed(9) : ''} {`${span} by ${span}`} {`${eps} cells, ${(eps / span).toFixed(9)}`} {volume === null ? '' : volume.toFixed(3)} {volume === null ? '' : (volume / (span * span)).toFixed(9)} {built.inside ? 'the design is in the class' : 'outside the class, measured only'} {limit ? `${limit.low.toFixed(9)} to ${limit.high.toFixed(9)}` : ''} {limit ? `${limit.swing.toFixed(6)}%` : ''}
); } mount();