import { createElement, Fragment } from 'react' import type { Chapter, Control } from '../../ui/types.ts' import driver from './driver.m?raw' import script from './lshape.m?raw' import text from './text.md?raw' import { Fields2D, type Single2DOut } from '../twodim/panels.tsx' import { Convergence, WorkPrecision, fmtErr, type SweepOut } from '../onedim/panels.tsx' import type { MethodKey } from '../../plot/palette.ts' const SLOTS: MethodKey[] = ['fd2', 'mps'] const KIND: Control = { kind: 'select', key: 'kind', label: 'ground truth', options: [ { value: 'both', label: 'singular + smooth (generic)' }, { value: 'sing', label: 'singular only: r^(2/3) sin(2θ/3)' }, { value: 'smooth', label: 'smooth only: log|z − z₀|' }, ], } export const ch09: Chapter = { id: 'corner', nav: 'A corner, and what it costs', part: 'Beyond the square', title: 'A corner, and what it costs', blurb: 'The L-shaped domain, the loss of second order, and a basis that contains the singularity.', text, labs: [ { id: 'field', title: 'The solution and where the error lives', caption: 'Compare the error picture on the generic case with the one on the smooth case.', driver, script, manual: true, controls: [ KIND, { kind: 'select', key: 'mid', label: 'method', options: [ { value: 1, label: '5-point + CG' }, { value: 2, label: 'harmonic basis' }, ], }, { kind: 'slider', key: 'n', label: 'resolution', values: [32, 64, 96, 128], min: 32, max: 128 }, ], defaults: { kind: 'both', mode: 'field', mid: 1, n: 64 }, derive: (p) => ({ ...p, n: Number(p.mid) === 2 ? Math.max(8, Math.round(Number(p.n) / 4)) : p.n }), render: (d: Single2DOut) => createElement(Fields2D, { data: d, xDomain: [-1, 1], yDomain: [-1, 1] }), summary: (d: Single2DOut, p) => [ { label: 'resolution', value: Number(p.mid) === 2 ? `${Math.max(8, Math.round(Number(p.n) / 4))} basis terms` : `${Number(p.n)} per unit`, }, { label: 'max error', value: fmtErr(d.maxerr) }, ], }, { id: 'sweep', title: 'A grid against a basis', caption: 'The two methods have unrelated resolution parameters, so the first plot is only meaningful curve by curve; the second, against time, compares them directly.', driver, script, manual: true, editorOpen: true, controls: [KIND], defaults: { kind: 'both', mode: 'sweep', nsm: [ [16, 32, 64, 128], [8, 16, 24, 40], ], }, render: (d: SweepOut) => createElement( Fragment, null, createElement(Convergence, { key: 'c', data: d, style: { slots: SLOTS, refs: [{ p: -4 / 3, label: 'slope −4/3' }] }, height: 320, xLabel: 'grid intervals per unit, or basis terms', }), createElement(WorkPrecision, { key: 'w', data: d, style: { slots: SLOTS }, height: 320 }), ), summary: (d: SweepOut) => [ { label: 'best difference', value: fmtErr(Math.min(...(d.E[0].filter((v) => v != null) as number[]))) }, { label: 'best basis', value: fmtErr(Math.min(...(d.E[1].filter((v) => v != null) as number[]))) }, ], }, ], }