/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / src / chapters / ch09 / chapter.ts
103 lines · 3.4 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 './lshape.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'
10const SLOTS: MethodKey[] = ['fd2', 'mps']
12const KIND: Control = {
13 kind: 'select',
14 key: 'kind',
15 label: 'ground truth',
16 options: [
17 { value: 'both', label: 'singular + smooth (generic)' },
18 { value: 'sing', label: 'singular only: r^(2/3) sin(2θ/3)' },
19 { value: 'smooth', label: 'smooth only: log|z − z₀|' },
20 ],
23export const ch09: Chapter = {
24 id: 'corner',
25 nav: 'A corner, and what it costs',
26 part: 'Beyond the square',
27 title: 'A corner, and what it costs',
28 blurb: 'The L-shaped domain, the loss of second order, and a basis that contains the singularity.',
29 text,
30 labs: [
31 {
32 id: 'field',
33 title: 'The solution and where the error lives',
34 caption: 'Compare the error picture on the generic case with the one on the smooth case.',
35 driver,
36 script,
37 manual: true,
38 controls: [
39 KIND,
40 {
41 kind: 'select',
42 key: 'mid',
43 label: 'method',
44 options: [
45 { value: 1, label: '5-point + CG' },
46 { value: 2, label: 'harmonic basis' },
47 ],
48 },
49 { kind: 'slider', key: 'n', label: 'resolution', values: [32, 64, 96, 128], min: 32, max: 128 },
50 ],
51 defaults: { kind: 'both', mode: 'field', mid: 1, n: 64 },
52 derive: (p) => ({ ...p, n: Number(p.mid) === 2 ? Math.max(8, Math.round(Number(p.n) / 4)) : p.n }),
53 render: (d: Single2DOut) =>
54 createElement(Fields2D, { data: d, xDomain: [-1, 1], yDomain: [-1, 1] }),
55 summary: (d: Single2DOut, p) => [
56 {
57 label: 'resolution',
58 value:
59 Number(p.mid) === 2
60 ? `${Math.max(8, Math.round(Number(p.n) / 4))} basis terms`
61 : `${Number(p.n)} per unit`,
62 },
63 { label: 'max error', value: fmtErr(d.maxerr) },
64 ],
65 },
66 {
67 id: 'sweep',
68 title: 'A grid against a basis',
69 caption:
70 'The two methods have unrelated resolution parameters, so the first plot is only meaningful curve by curve; the second, against time, compares them directly.',
71 driver,
72 script,
73 manual: true,
74 editorOpen: true,
75 controls: [KIND],
76 defaults: {
77 kind: 'both',
78 mode: 'sweep',
79 nsm: [
80 [16, 32, 64, 128],
81 [8, 16, 24, 40],
82 ],
83 },
84 render: (d: SweepOut) =>
85 createElement(
86 Fragment,
87 null,
88 createElement(Convergence, {
89 key: 'c',
90 data: d,
91 style: { slots: SLOTS, refs: [{ p: -4 / 3, label: 'slope −4/3' }] },
92 height: 320,
93 xLabel: 'grid intervals per unit, or basis terms',
94 }),
95 createElement(WorkPrecision, { key: 'w', data: d, style: { slots: SLOTS }, height: 320 }),
96 ),
97 summary: (d: SweepOut) => [
98 { label: 'best difference', value: fmtErr(Math.min(...(d.E[0].filter((v) => v != null) as number[]))) },
99 { label: 'best basis', value: fmtErr(Math.min(...(d.E[1].filter((v) => v != null) as number[]))) },
100 ],
101 },
102 ],
↑↓ move↵openescclose