// Figures for the introduction: what the problem is, on what domains, and
// what the spread in accuracy-against-cost looks like before any of it is
// measured.
import type { ReactNode } from 'react'
import { Arrowheads, Figure, Label, Node, arrow } from './svg.tsx'
import { ink, method, seqColor } from '../plot/palette.ts'
/** A closed wobbly curve, so that "a region" does not read as "a disc". */
function blob(cx: number, cy: number, rx: number, ry: number, phase: number): string {
const pts: string[] = []
for (let i = 0; i < 72; i++) {
const t = (2 * Math.PI * i) / 72
const r = 1 + 0.1 * Math.cos(3 * t + phase) + 0.045 * Math.sin(5 * t + 1.3)
pts.push(`${(cx + rx * r * Math.cos(t)).toFixed(1)} ${(cy + ry * r * Math.sin(t)).toFixed(1)}`)
}
return `M ${pts.join(' L ')} Z`
}
/** Outward normal of the blob at parameter t, for the Neumann panel. */
function blobPoint(cx: number, cy: number, rx: number, ry: number, phase: number, t: number) {
const r = 1 + 0.1 * Math.cos(3 * t + phase) + 0.045 * Math.sin(5 * t + 1.3)
const dr = -0.3 * Math.sin(3 * t + phase) + 0.225 * Math.cos(5 * t + 1.3)
const x = cx + rx * r * Math.cos(t)
const y = cy + ry * r * Math.sin(t)
const dx = rx * (dr * Math.cos(t) - r * Math.sin(t))
const dy = ry * (dr * Math.sin(t) + r * Math.cos(t))
const n = Math.hypot(dx, dy)
return { x, y, nx: dy / n, ny: -dx / n }
}
const PANEL_W = 232
const CY = 108
function Panel({
x,
heading,
interior,
sub,
muted,
children,
}: {
x: number
heading: string
interior: string
sub: string
muted?: boolean
children?: ReactNode
}) {
return (
{children}
)
}
/** Laplace against Poisson, Dirichlet against Neumann, in one row. */
export function DirichletProblem() {
const cx = PANEL_W / 2
const rx = 74
const ry = 54
const normals = [0.4, 1.5, 2.6, 3.7, 4.8, 5.9].map((t) => blobPoint(cx, CY, rx, ry, 0.7, t))
return (
{normals.map((p, i) => (
))}
)
}
/** The four domains, in the order the tutorial meets them. */
export function Domains() {
const c = ink.node
const box = (i: number) => 12 + i * 176
return (
{/* 1-D interval */}
{Array.from({ length: 9 }, (_, i) => (
))}
{/* unit square */}
{Array.from({ length: 5 }, (_, i) => (
))}
{/* L-shape */}
{/* smooth curve */}
{Array.from({ length: 18 }, (_, i) => {
const p = blobPoint(76, 92, 56, 54, 2.1, (2 * Math.PI * i) / 18)
return
})}
)
}
/** Schematic of the spread the tutorial measures. Not data. */
export function AccuracyCost() {
const L = 66
const R = 470
const T = 26
const B = 176
// log10 time from -1 to 3.5 ms across; log10 error from 0 at the top to -16
const sx = (v: number) => L + ((v + 1) / 4.5) * (R - L)
const sy = (v: number) => T + (-v / 16) * (B - T)
const curve = (pts: [number, number][]) =>
'M ' + pts.map(([t, e]) => `${sx(t).toFixed(1)} ${sy(e).toFixed(1)}`).join(' L ')
const series: { key: keyof typeof method; label: string; pts: [number, number][] }[] = [
{ 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]] },
{ 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]] },
{ 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]] },
{ key: 'bie', label: 'boundary integral', pts: [[0.0, -3.0], [0.35, -9.0], [0.8, -15.4], [1.9, -15.6]] },
]
return (
{[0, -4, -8, -12, -16].map((e) => (
))}
{series.map((s, i) => {
const last = s.pts[s.pts.length - 1]
const ly = T + 16 + i * 22
return (
)
})}
)
}