/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / src / figures / beyond.tsx
227 lines · 9.0 KBCodeBlameHistory
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 1// Static figures for Chapters 9 and 10.
2import { Arrowheads, Figure, Label, Node, arrow } from './svg.tsx'
3import { ink, method, seqColor } from '../plot/palette.ts'
5/** The L-shaped domain, its re-entrant corner, and the profile of r^(2/3). */
6export function ReentrantCorner() {
7 const S = 62 // half-width of the square, in px
8 const CX = 150
9 const CY = 112
10 return (
11 <Figure
12 width={716}
13 height={236}
14 caption="The re-entrant corner and what it does to the solution. A convex corner of angle π/2 gives r², which is harmless; the 3π/2 corner gives r²ᐟ³, whose gradient is infinite at the origin. No difference scheme recovers second order from that."
15 >
16 <Arrowheads id="rc" colors={[method.mps, ink.muted]} />
18 {/* the domain */}
19 <path
20 d={`M ${CX - S} ${CY - S} H ${CX + S} V ${CY} H ${CX} V ${CY + S} H ${CX - S} Z`}
21 fill="#20242c"
22 stroke={method.iterative}
23 strokeWidth={2.2}
24 strokeLinejoin="round"
25 />
26 <Label x={CX - 30} y={CY - 34} size={13} fill={ink.secondary}>
27 Ω
28 </Label>
29 <Label x={CX} y={18} size={12.5} weight={600} fill={ink.primary}>
30 the L-shaped domain
31 </Label>
33 {/* the corner angle */}
34 <path
35 d={`M ${CX + 26} ${CY} A 26 26 0 1 0 ${CX} ${CY + 26}`}
36 fill="none"
37 stroke={method.mps}
38 strokeWidth={1.6}
39 markerEnd={arrow('rc', 0)}
40 />
41 <Label x={CX - 32} y={CY + 54} size={12} anchor="middle" fill={method.mps}>
42 α = 3π/2
43 </Label>
44 <Node x={CX} y={CY} r={3.6} fill={method.mps} />
46 <Label x={CX} y={CY + S + 26} size={11.5} fill={ink.muted}>
47 π/2 at every other corner
48 </Label>
50 {/* the profile */}
51 <g transform="translate(354 0)">
52 <Label x={160} y={18} size={12.5} weight={600} fill={ink.primary}>
53 radial profile at the corner
54 </Label>
55 <line x1={20} y1={186} x2={300} y2={186} stroke={ink.axis} strokeWidth={1.2} />
56 <line x1={20} y1={186} x2={20} y2={38} stroke={ink.axis} strokeWidth={1.2} />
57 <Label x={160} y={206} size={11.5} fill={ink.muted} italic>
58 distance r from the corner
59 </Label>
60 {(
61 [
62 { p: 2 / 3, c: method.mps, l: 'r²ᐟ³ at the re-entrant corner' },
63 { p: 2, c: ink.muted, l: 'r² at a convex corner' },
64 ] as const
65 ).map((s, k) => {
66 const pts: string[] = []
67 for (let i = 0; i <= 100; i++) {
68 const t = i / 100
69 pts.push(`${(20 + t * 280).toFixed(1)} ${(186 - 140 * Math.pow(t, s.p)).toFixed(1)}`)
70 }
71 return (
72 <g key={k}>
73 <path d={'M ' + pts.join(' L ')} fill="none" stroke={s.c} strokeWidth={2.2} />
74 <line x1={34} y1={52 + k * 19} x2={52} y2={52 + k * 19} stroke={s.c} strokeWidth={2.2} />
75 <Label x={60} y={52 + k * 19} size={11.5} anchor="start" fill={s.c}>
76 {s.l}
77 </Label>
78 </g>
79 )
80 })}
82 </g>
83 </Figure>
84 )
87/** Discretising the area against discretising the boundary. */
88export function AreaAgainstBoundary() {
89 const blobPts = (cx: number, cy: number, rr: number, m: number) =>
90 Array.from({ length: m }, (_, i) => {
91 const t = (2 * Math.PI * i) / m
92 const r = rr * (1 + 0.18 * Math.cos(3 * t))
93 return { x: cx + r * Math.cos(t), y: cy + r * Math.sin(t) * 0.92, t }
94 })
95 const path = (cx: number, cy: number, rr: number) =>
96 'M ' + blobPts(cx, cy, rr, 160).map((p) => `${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' L ') + ' Z'
98 const cx1 = 168
99 const cx2 = 548
100 const cy = 118
101 const rr = 78
103 return (
104 <Figure
105 width={716}
106 height={258}
107 caption="Two ways to spend unknowns on the same problem. Filling the area needs a grid the curve does not fit; putting the unknown on the curve itself needs a few hundred points, and the equation is then satisfied exactly in the interior by construction."
108 >
109 {/* area discretisation */}
110 <Label x={cx1} y={18} size={12.5} weight={600} fill={ink.primary}>
111 unknowns in the area
112 </Label>
113 <clipPath id="ab-clip">
114 <path d={path(cx1, cy, rr)} />
115 </clipPath>
116 <path d={path(cx1, cy, rr)} fill="#20242c" stroke={ink.axis} strokeWidth={1.6} />
117 <g clipPath="url(#ab-clip)">
118 {Array.from({ length: 19 }, (_, i) => (
119 <line key={`v${i}`} x1={cx1 - 96 + i * 11} y1={cy - 92} x2={cx1 - 96 + i * 11} y2={cy + 96} stroke={ink.axis} strokeWidth={1} />
120 ))}
121 {Array.from({ length: 18 }, (_, j) => (
122 <line key={`h${j}`} x1={cx1 - 100} y1={cy - 88 + j * 11} x2={cx1 + 100} y2={cy - 88 + j * 11} stroke={ink.axis} strokeWidth={1} />
123 ))}
124 {Array.from({ length: 19 }, (_, i) =>
125 Array.from({ length: 17 }, (_, j) => (
126 <Node key={`${i}-${j}`} x={cx1 - 96 + i * 11} y={cy - 88 + j * 11} r={1.9} fill={seqColor(0.55)} />
127 )),
128 )}
129 </g>
130 <Label x={cx1} y={cy + rr + 44} size={11.5} fill={ink.muted}>
131 O(N²) unknowns, and the boundary falls between nodes
132 </Label>
134 {/* boundary discretisation */}
135 <Label x={cx2} y={18} size={12.5} weight={600} fill={ink.primary}>
136 unknowns on the boundary
137 </Label>
138 <path d={path(cx2, cy, rr)} fill="#1e1e1c" stroke={method.bie} strokeWidth={2.2} />
139 {blobPts(cx2, cy, rr, 36).map((p, i) => (
140 <Node key={i} x={p.x} y={p.y} r={3} fill={method.bie} />
141 ))}
142 <Label x={cx2} y={cy} size={12} fill={ink.secondary}>
143 Δu = 0 exactly
144 </Label>
145 <Label x={cx2} y={cy + rr + 44} size={11.5} fill={ink.muted}>
146 O(n) unknowns, placed where the data is
147 </Label>
148 </Figure>
149 )
152/** Why evaluation near the curve is the hard part. */
153export function CloseEvaluation() {
154 const cx = 200
155 const cy = 116
156 const rr = 86
157 const pts = (m: number) =>
158 Array.from({ length: m }, (_, i) => {
159 const t = (2 * Math.PI * i) / m
160 const r = rr * (1 + 0.16 * Math.cos(3 * t))
161 return { x: cx + r * Math.cos(t), y: cy + r * Math.sin(t) * 0.94 }
162 })
163 const d = 'M ' + pts(180).map((p) => `${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' L ') + ' Z'
164 const far = { x: cx - 12, y: cy + 6 }
165 const near = { x: cx + 74, y: cy - 26 }
167 // the integrand along the curve, seen from each target
168 const K = (spread: number, amp: number) => {
169 const out: string[] = []
170 for (let i = 0; i <= 200; i++) {
171 const t = -1 + (2 * i) / 200
172 const v = amp / (1 + (t / spread) ** 2)
173 out.push(`${(420 + (t + 1) * 140).toFixed(1)} ${(186 - v).toFixed(1)}`)
174 }
175 return 'M ' + out.join(' L ')
176 }
178 return (
179 <Figure
180 width={716}
181 height={256}
182 caption="Close evaluation. The density on the curve is accurate in both cases; what fails is the quadrature used to evaluate the potential. Seen from a target well inside, the integrand is a gentle periodic function; seen from a target one grid spacing away, it is a spike the trapezoid rule cannot resolve."
183 >
184 <Arrowheads id="ce" colors={[ink.muted]} />
185 <path d={d} fill="#20242c" stroke={method.bie} strokeWidth={2.2} />
186 {pts(40).map((p, i) => (
187 <Node key={i} x={p.x} y={p.y} r={2.6} fill={method.bie} />
188 ))}
189 <Node x={far.x} y={far.y} r={4.4} fill={method.spectral} />
190 <Label x={far.x} y={far.y + 20} size={11.5} fill={method.spectral}>
191 target well inside
192 </Label>
193 <Node x={near.x} y={near.y} r={4.4} fill={method.iterative} />
194 <line x1={near.x + 6} y1={near.y - 4} x2={near.x + 40} y2={near.y - 28} stroke={method.iterative} strokeWidth={1} />
195 <Label x={near.x + 44} y={near.y - 32} size={11.5} anchor="start" fill={method.iterative}>
196 target one grid
197 </Label>
198 <Label x={near.x + 44} y={near.y - 17} size={11.5} anchor="start" fill={method.iterative}>
199 spacing away
200 </Label>
202 <line x1={332} y1={130} x2={382} y2={130} stroke={ink.muted} strokeWidth={1.3} markerEnd={arrow('ce', 0)} />
203 <Label x={357} y={114} size={11} fill={ink.muted}>
204 integrand
205 </Label>
207 <line x1={420} y1={186} x2={700} y2={186} stroke={ink.axis} strokeWidth={1.2} />
208 <Label x={560} y={206} size={11.5} fill={ink.muted} italic>
209 arclength around the curve
210 </Label>
211 <path d={K(0.55, 42)} fill="none" stroke={method.spectral} strokeWidth={2.2} />
212 <path d={K(0.035, 138)} fill="none" stroke={method.iterative} strokeWidth={2.2} />
213 {Array.from({ length: 21 }, (_, i) => (
214 <Node key={i} x={420 + i * 14} y={186} r={2.2} fill={ink.family} />
215 ))}
216 <line x1={424} y1={228} x2={442} y2={228} stroke={method.spectral} strokeWidth={2.2} />
217 <Label x={450} y={228} size={11.5} anchor="start" fill={method.spectral}>
218 from the far target: smooth
219 </Label>
220 <line x1={424} y1={246} x2={442} y2={246} stroke={method.iterative} strokeWidth={2.2} />
221 <Label x={450} y={246} size={11.5} anchor="start" fill={method.iterative}>
222 from the near target: a spike
223 </Label>
225 </Figure>
226 )
↑↓ move↵openescclose