/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / src / figures / intro.tsx
280 lines · 10.3 KBCodeBlameHistory
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 1// Figures for the introduction: what the problem is, on what domains, and
2// what the spread in accuracy-against-cost looks like before any of it is
3// measured.
4import type { ReactNode } from 'react'
5import { Arrowheads, Figure, Label, Node, arrow } from './svg.tsx'
6import { ink, method, seqColor } from '../plot/palette.ts'
8/** A closed wobbly curve, so that "a region" does not read as "a disc". */
9function blob(cx: number, cy: number, rx: number, ry: number, phase: number): string {
10 const pts: string[] = []
11 for (let i = 0; i < 72; i++) {
12 const t = (2 * Math.PI * i) / 72
13 const r = 1 + 0.1 * Math.cos(3 * t + phase) + 0.045 * Math.sin(5 * t + 1.3)
14 pts.push(`${(cx + rx * r * Math.cos(t)).toFixed(1)} ${(cy + ry * r * Math.sin(t)).toFixed(1)}`)
15 }
16 return `M ${pts.join(' L ')} Z`
19/** Outward normal of the blob at parameter t, for the Neumann panel. */
20function blobPoint(cx: number, cy: number, rx: number, ry: number, phase: number, t: number) {
21 const r = 1 + 0.1 * Math.cos(3 * t + phase) + 0.045 * Math.sin(5 * t + 1.3)
22 const dr = -0.3 * Math.sin(3 * t + phase) + 0.225 * Math.cos(5 * t + 1.3)
23 const x = cx + rx * r * Math.cos(t)
24 const y = cy + ry * r * Math.sin(t)
25 const dx = rx * (dr * Math.cos(t) - r * Math.sin(t))
26 const dy = ry * (dr * Math.sin(t) + r * Math.cos(t))
27 const n = Math.hypot(dx, dy)
28 return { x, y, nx: dy / n, ny: -dx / n }
31const PANEL_W = 232
32const CY = 108
34function Panel({
35 x,
36 heading,
37 interior,
38 sub,
39 muted,
40 children,
41}: {
42 x: number
43 heading: string
44 interior: string
45 sub: string
46 muted?: boolean
47 children?: ReactNode
48}) {
49 return (
50 <g transform={`translate(${x} 0)`}>
51 <Label x={PANEL_W / 2} y={16} size={13} weight={600} fill={muted ? ink.muted : ink.primary}>
52 {heading}
53 </Label>
54 {children}
55 <Label x={PANEL_W / 2} y={CY} size={14} fill={muted ? ink.muted : ink.primary}>
56 {interior}
57 </Label>
58 <Label x={PANEL_W / 2} y={214} size={12} fill={ink.muted}>
59 {sub}
60 </Label>
61 </g>
62 )
65/** Laplace against Poisson, Dirichlet against Neumann, in one row. */
66export function DirichletProblem() {
67 const cx = PANEL_W / 2
68 const rx = 74
69 const ry = 54
70 const normals = [0.4, 1.5, 2.6, 3.7, 4.8, 5.9].map((t) => blobPoint(cx, CY, rx, ry, 0.7, t))
71 return (
72 <Figure
73 width={716}
74 height={232}
75 caption="The interior condition and the boundary condition are chosen independently. This tutorial takes the first two panels: Laplace and Poisson with data prescribed on the whole boundary."
76 >
77 <Arrowheads id="dp" colors={[ink.muted]} />
79 <Panel x={0} heading="Laplace, Dirichlet" interior="Δu = 0" sub="Chapters 5–10">
80 <path d={blob(cx, CY, rx, ry, 0.7)} fill="#20242c" stroke={method.fd2} strokeWidth={2.4} />
81 <Label x={cx} y={CY + 22} size={11.5} fill={ink.muted}>
82 no source
83 </Label>
84 <Label x={cx} y={CY + ry + 26} size={12} fill={method.fd2}>
85 u = g on ∂Ω
86 </Label>
87 </Panel>
89 <Panel x={242} heading="Poisson, Dirichlet" interior="−Δu = f" sub="Chapters 1–4">
90 <path d={blob(cx, CY, rx, ry, 0.7)} fill="#20242c" stroke={method.fd2} strokeWidth={2.4} />
91 <ellipse cx={cx - 44} cy={CY - 24} rx={17} ry={13} fill={seqColor(0.75)} opacity={0.5} />
92 <ellipse cx={cx + 46} cy={CY + 26} rx={12} ry={10} fill={seqColor(0.5)} opacity={0.45} />
93 <Label x={cx} y={CY + 22} size={11.5} fill={ink.muted}>
94 a source f inside
95 </Label>
96 <Label x={cx} y={CY + ry + 26} size={12} fill={method.fd2}>
97 u = g on ∂Ω
98 </Label>
99 </Panel>
101 <Panel x={484} heading="Neumann" interior="Δu = 0" sub="not treated here" muted>
102 <path d={blob(cx, CY, rx, ry, 0.7)} fill="#1e1e1c" stroke={ink.axis} strokeWidth={2.4} />
103 {normals.map((p, i) => (
104 <line
105 key={i}
106 x1={p.x}
107 y1={p.y}
108 x2={p.x + 15 * p.nx}
109 y2={p.y + 15 * p.ny}
110 stroke={ink.muted}
111 strokeWidth={1.3}
112 markerEnd={arrow('dp', 0)}
113 />
114 ))}
115 <Label x={cx} y={CY + ry + 26} size={12} fill={ink.muted}>
116 ∂u/∂n = g on ∂Ω
117 </Label>
118 </Panel>
119 </Figure>
120 )
123/** The four domains, in the order the tutorial meets them. */
124export function Domains() {
125 const c = ink.node
126 const box = (i: number) => 12 + i * 176
127 return (
128 <Figure
129 width={716}
130 height={196}
131 caption="The four domains. Each step to the right removes a structural assumption: the second dimension, then the convexity of the corner, then the straight edge itself."
132 >
133 {/* 1-D interval */}
134 <g transform={`translate(${box(0)} 0)`}>
135 <Label x={76} y={16} size={12.5} weight={600} fill={ink.primary}>
136 interval
137 </Label>
138 <line x1={10} y1={92} x2={142} y2={92} stroke={ink.axis} strokeWidth={1.5} />
139 {Array.from({ length: 9 }, (_, i) => (
140 <Node key={i} x={10 + (i * 132) / 8} y={92} r={i === 0 || i === 8 ? 4.5 : 3} fill={i === 0 || i === 8 ? method.fd2 : c} />
141 ))}
142 <Label x={10} y={112} size={11} fill={method.fd2}>
143 u(0)=α
144 </Label>
145 <Label x={142} y={112} size={11} fill={method.fd2}>
146 u(1)=β
147 </Label>
148 <Label x={76} y={168} size={11.5} fill={ink.muted}>
149 Chapters 1–4
150 </Label>
151 </g>
153 {/* unit square */}
154 <g transform={`translate(${box(1)} 0)`}>
155 <Label x={76} y={16} size={12.5} weight={600} fill={ink.primary}>
156 square
157 </Label>
158 <rect x={22} y={38} width={108} height={108} fill="#20242c" stroke={method.fd2} strokeWidth={2.2} />
159 {Array.from({ length: 5 }, (_, i) => (
160 <g key={i}>
161 <line x1={22 + (i + 1) * 18} y1={38} x2={22 + (i + 1) * 18} y2={146} stroke={ink.axis} strokeWidth={1} />
162 <line x1={22} y1={38 + (i + 1) * 18} x2={130} y2={38 + (i + 1) * 18} stroke={ink.axis} strokeWidth={1} />
163 </g>
164 ))}
165 <Label x={76} y={168} size={11.5} fill={ink.muted}>
166 Chapters 5–8
167 </Label>
168 </g>
170 {/* L-shape */}
171 <g transform={`translate(${box(2)} 0)`}>
172 <Label x={76} y={16} size={12.5} weight={600} fill={ink.primary}>
173 L-shape
174 </Label>
175 <path
176 d="M 22 38 H 130 V 92 H 76 V 146 H 22 Z"
177 fill="#20242c"
178 stroke={method.iterative}
179 strokeWidth={2.2}
180 strokeLinejoin="round"
181 />
182 <circle cx={76} cy={92} r={5} fill="none" stroke={method.mps} strokeWidth={2} />
183 <Label x={95} y={106} size={11} anchor="start" fill={method.mps}>
184 3π/2
185 </Label>
186 <Label x={76} y={168} size={11.5} fill={ink.muted}>
187 Chapter 9
188 </Label>
189 </g>
191 {/* smooth curve */}
192 <g transform={`translate(${box(3)} 0)`}>
193 <Label x={76} y={16} size={12.5} weight={600} fill={ink.primary}>
194 smooth curve
195 </Label>
196 <path
197 d={blob(76, 92, 56, 54, 2.1)}
198 fill="#20242c"
199 stroke={method.bie}
200 strokeWidth={2.2}
201 />
202 {Array.from({ length: 18 }, (_, i) => {
203 const p = blobPoint(76, 92, 56, 54, 2.1, (2 * Math.PI * i) / 18)
204 return <Node key={i} x={p.x} y={p.y} r={2.4} fill={ink.node} />
205 })}
206 <Label x={76} y={168} size={11.5} fill={ink.muted}>
207 Chapter 10
208 </Label>
209 </g>
210 </Figure>
211 )
214/** Schematic of the spread the tutorial measures. Not data. */
215export function AccuracyCost() {
216 const L = 66
217 const R = 470
218 const T = 26
219 const B = 176
220 // log10 time from -1 to 3.5 ms across; log10 error from 0 at the top to -16
221 const sx = (v: number) => L + ((v + 1) / 4.5) * (R - L)
222 const sy = (v: number) => T + (-v / 16) * (B - T)
223 const curve = (pts: [number, number][]) =>
224 'M ' + pts.map(([t, e]) => `${sx(t).toFixed(1)} ${sy(e).toFixed(1)}`).join(' L ')
226 const series: { key: keyof typeof method; label: string; pts: [number, number][] }[] = [
227 { key: 'fd2', label: 'second order', pts: [[-0.9, -1.0], [0.0, -2.6], [1.2, -4.4], [2.4, -6.0], [3.4, -7.3]] },
228 { key: 'fd4', label: 'sixth order', pts: [[-0.5, -1.4], [0.1, -5.0], [0.7, -9.0], [1.3, -13.0], [2.6, -13.4]] },
229 { key: 'spectral', label: 'spectral', pts: [[-0.2, -2.0], [0.15, -6.5], [0.5, -11.5], [0.9, -14.0], [2.0, -14.0]] },
230 { key: 'bie', label: 'boundary integral', pts: [[0.0, -3.0], [0.35, -9.0], [0.8, -15.4], [1.9, -15.6]] },
231 ]
233 return (
234 <Figure
235 width={716}
236 height={214}
237 caption="Schematic, not measured data: the shape of the comparison every chapter returns to. Higher order changes the slope, and structure changes which curve is available at all. The labs produce the real numbers."
238 >
239 <line x1={L} y1={T} x2={L} y2={B} stroke={ink.axis} strokeWidth={1.2} />
240 <line x1={L} y1={B} x2={R} y2={B} stroke={ink.axis} strokeWidth={1.2} />
241 {[0, -4, -8, -12, -16].map((e) => (
242 <g key={e}>
243 <line x1={L} y1={sy(e)} x2={R} y2={sy(e)} stroke={ink.grid} strokeWidth={1} />
244 <Label x={L - 8} y={sy(e)} size={10.5} anchor="end" fill={ink.muted} mono>
245 {e === 0 ? '1' : `1e${e}`}
246 </Label>
247 </g>
248 ))}
249 <g transform={`translate(${L - 46} ${(T + B) / 2}) rotate(-90)`}>
250 <Label x={0} y={0} size={11.5} fill={ink.muted}>
251 error
252 </Label>
253 </g>
254 <Label x={(L + R) / 2} y={B + 22} size={11.5} fill={ink.muted}>
255 time for one solve →
256 </Label>
258 {series.map((s, i) => {
259 const last = s.pts[s.pts.length - 1]
260 const ly = T + 16 + i * 22
261 return (
262 <g key={s.key}>
263 <path d={curve(s.pts)} fill="none" stroke={method[s.key]} strokeWidth={2.2} strokeLinecap="round" />
264 <Node x={sx(last[0])} y={sy(last[1])} r={3.4} fill={method[s.key]} />
265 <line x1={R + 26} y1={ly} x2={R + 40} y2={ly} stroke={method[s.key]} strokeWidth={2.4} />
266 <Label x={R + 48} y={ly} size={12} anchor="start" fill={method[s.key]}>
267 {s.label}
268 </Label>
269 </g>
270 )
271 })}
272 <Label x={R + 26} y={T + 16 + 4 * 22 + 6} size={11} anchor="start" fill={ink.muted}>
273 each curve stops at
274 </Label>
275 <Label x={R + 26} y={T + 16 + 4 * 22 + 22} size={11} anchor="start" fill={ink.muted}>
276 its own roundoff floor
277 </Label>
278 </Figure>
279 )
↑↓ move↵openescclose