/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / src / chapters / ch10 / chapter.ts
123 lines · 4.3 KBCodeBlameHistory
088be4cAn interactive tutorial on the Dirichlet problem, accuracy against costJeremy Magland 1import { createElement, Fragment } from 'react'
2import type { Chapter, Control } from '../../ui/types.ts'
3import driver from './driver.m?raw'
4import script from './bie.m?raw'
5import text from './text.md?raw'
6import { Fields2D, type Single2DOut } from '../twodim/panels.tsx'
7import { Convergence, WorkPrecision, fmtErr, type SweepOut } from '../onedim/panels.tsx'
8import type { MethodKey } from '../../plot/palette.ts'
9import type { Num } from '../../engine/types.ts'
11interface BieOut extends SweepOut {
12 Enear: Num[][]
13 cx: number[]
14 cy: number[]
17const SLOTS: MethodKey[] = ['bie', 'bie', 'mps', 'mps']
18const DASHES = [undefined, '5 4', undefined, '5 4']
20/** Interleave the interior and near-boundary errors into one four-series set. */
21function split(d: BieOut): SweepOut {
22 return {
23 names: [
24 'Nyström, interior',
25 'Nyström, at 98% radius',
26 'fundamental solutions, interior',
27 'fundamental solutions, at 98% radius',
28 ],
29 ns: d.ns,
30 E: [d.E[0], d.Enear[0], d.E[1], d.Enear[1]],
31 T: [d.T[0], d.T[0], d.T[1], d.T[1]],
32 orders: [d.orders[0], d.orders[0], d.orders[1], d.orders[1]],
33 }
36const GEOM: Control[] = [
37 { kind: 'slider', key: 'a', label: 'lobe amplitude a', min: 0, max: 0.35, step: 0.05, format: (v) => v.toFixed(2) },
38 { kind: 'slider', key: 'nlobe', label: 'number of lobes', min: 2, max: 8, step: 1, format: (v) => String(v) },
41export const ch10: Chapter = {
42 id: 'boundary-integral',
43 nav: 'Boundary integral equations',
44 part: 'Beyond the square',
45 title: 'Boundary integral equations',
46 blurb:
47 'A curved domain, an unknown that lives only on the boundary, and full double precision from a few hundred points.',
48 text,
49 labs: [
50 {
51 id: 'field',
52 title: 'The solution inside the curve',
53 caption:
54 'The error picture is on a logarithmic colour scale covering whatever range the data spans. Note where it is worst.',
55 driver,
56 script,
57 manual: true,
58 controls: [
59 ...GEOM,
60 {
61 kind: 'select',
62 key: 'mid',
63 label: 'method',
64 options: [
65 { value: 1, label: 'double-layer Nyström' },
66 { value: 2, label: 'fundamental solutions' },
67 ],
68 },
69 { kind: 'slider', key: 'n', label: 'boundary unknowns', values: [16, 32, 64, 128, 256], min: 16, max: 256 },
70 { kind: 'slider', key: 'par', label: 'MFS source radius', min: 1.6, max: 4, step: 0.2, format: (v) => v.toFixed(1) },
71 ],
72 defaults: { a: 0.3, nlobe: 5, mode: 'field', mid: 1, n: 128, par: 2.2 },
73 render: (d: Single2DOut & { cx: number[]; cy: number[] }) =>
74 createElement(Fields2D, {
75 data: d,
76 xDomain: [d.x[0], d.x[d.x.length - 1]],
77 yDomain: [d.y[0], d.y[d.y.length - 1]],
78 curves: [{ x: d.cx, y: d.cy }],
79 }),
80 summary: (d: Single2DOut) => [
81 { label: 'unknowns', value: String(d.unknowns) },
82 { label: 'max error', value: fmtErr(d.maxerr) },
83 ],
84 },
85 {
86 id: 'sweep',
87 title: 'Geometric convergence, and the close-evaluation wall',
88 caption:
89 'Solid curves are errors at interior points; dashed curves the same solution evaluated at 98 per cent of the radius.',
90 driver,
91 script,
92 manual: true,
93 editorOpen: true,
94 controls: [
95 ...GEOM,
96 { kind: 'slider', key: 'par', label: 'MFS source radius', min: 1.6, max: 4, step: 0.2, format: (v) => v.toFixed(1) },
97 ],
98 defaults: { a: 0.3, nlobe: 5, mode: 'sweep', par: 2.2, ns: [32, 64, 128, 256, 384, 512, 768] },
99 render: (d: BieOut) =>
100 createElement(
101 Fragment,
102 null,
103 createElement(Convergence, {
104 key: 'c',
105 data: split(d),
106 style: { slots: SLOTS, dashes: DASHES },
107 height: 340,
108 xLabel: 'boundary unknowns n',
109 }),
110 createElement(WorkPrecision, {
111 key: 'w',
112 data: split(d),
113 style: { slots: SLOTS, dashes: DASHES },
114 height: 340,
115 }),
116 ),
117 summary: (d: BieOut) => [
118 { label: 'best Nyström', value: fmtErr(Math.min(...(d.E[0].filter((v) => v != null) as number[]))) },
119 { label: 'best MFS', value: fmtErr(Math.min(...(d.E[1].filter((v) => v != null) as number[]))) },
120 ],
121 },
122 ],
↑↓ move↵openescclose