import { createElement, Fragment } from 'react' import type { Chapter, Control } from '../../ui/types.ts' import driver from './driver.m?raw' import script from './bie.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' import type { Num } from '../../engine/types.ts' interface BieOut extends SweepOut { Enear: Num[][] cx: number[] cy: number[] } const SLOTS: MethodKey[] = ['bie', 'bie', 'mps', 'mps'] const DASHES = [undefined, '5 4', undefined, '5 4'] /** Interleave the interior and near-boundary errors into one four-series set. */ function split(d: BieOut): SweepOut { return { names: [ 'Nyström, interior', 'Nyström, at 98% radius', 'fundamental solutions, interior', 'fundamental solutions, at 98% radius', ], ns: d.ns, E: [d.E[0], d.Enear[0], d.E[1], d.Enear[1]], T: [d.T[0], d.T[0], d.T[1], d.T[1]], orders: [d.orders[0], d.orders[0], d.orders[1], d.orders[1]], } } const GEOM: Control[] = [ { kind: 'slider', key: 'a', label: 'lobe amplitude a', min: 0, max: 0.35, step: 0.05, format: (v) => v.toFixed(2) }, { kind: 'slider', key: 'nlobe', label: 'number of lobes', min: 2, max: 8, step: 1, format: (v) => String(v) }, ] export const ch10: Chapter = { id: 'boundary-integral', nav: 'Boundary integral equations', part: 'Beyond the square', title: 'Boundary integral equations', blurb: 'A curved domain, an unknown that lives only on the boundary, and full double precision from a few hundred points.', text, labs: [ { id: 'field', title: 'The solution inside the curve', caption: 'The error picture is on a logarithmic colour scale covering whatever range the data spans. Note where it is worst.', driver, script, manual: true, controls: [ ...GEOM, { kind: 'select', key: 'mid', label: 'method', options: [ { value: 1, label: 'double-layer Nyström' }, { value: 2, label: 'fundamental solutions' }, ], }, { kind: 'slider', key: 'n', label: 'boundary unknowns', values: [16, 32, 64, 128, 256], min: 16, max: 256 }, { kind: 'slider', key: 'par', label: 'MFS source radius', min: 1.6, max: 4, step: 0.2, format: (v) => v.toFixed(1) }, ], defaults: { a: 0.3, nlobe: 5, mode: 'field', mid: 1, n: 128, par: 2.2 }, render: (d: Single2DOut & { cx: number[]; cy: number[] }) => createElement(Fields2D, { data: d, xDomain: [d.x[0], d.x[d.x.length - 1]], yDomain: [d.y[0], d.y[d.y.length - 1]], curves: [{ x: d.cx, y: d.cy }], }), summary: (d: Single2DOut) => [ { label: 'unknowns', value: String(d.unknowns) }, { label: 'max error', value: fmtErr(d.maxerr) }, ], }, { id: 'sweep', title: 'Geometric convergence, and the close-evaluation wall', caption: 'Solid curves are errors at interior points; dashed curves the same solution evaluated at 98 per cent of the radius.', driver, script, manual: true, editorOpen: true, controls: [ ...GEOM, { kind: 'slider', key: 'par', label: 'MFS source radius', min: 1.6, max: 4, step: 0.2, format: (v) => v.toFixed(1) }, ], defaults: { a: 0.3, nlobe: 5, mode: 'sweep', par: 2.2, ns: [32, 64, 128, 256, 384, 512, 768] }, render: (d: BieOut) => createElement( Fragment, null, createElement(Convergence, { key: 'c', data: split(d), style: { slots: SLOTS, dashes: DASHES }, height: 340, xLabel: 'boundary unknowns n', }), createElement(WorkPrecision, { key: 'w', data: split(d), style: { slots: SLOTS, dashes: DASHES }, height: 340, }), ), summary: (d: BieOut) => [ { label: 'best Nyström', value: fmtErr(Math.min(...(d.E[0].filter((v) => v != null) as number[]))) }, { label: 'best MFS', value: fmtErr(Math.min(...(d.E[1].filter((v) => v != null) as number[]))) }, ], }, ], }