// Static figures for Chapters 5 to 8.
import type { ReactNode } from 'react'
import { Arrowheads, Figure, Label, Node, arrow } from './svg.tsx'
import { ink, method, seqColor } from '../plot/palette.ts'
/** The grid, the five-point stencil, and the sparsity it produces. */
export function FivePointStencil() {
const n = 8
const s = 20
const X0 = 42
const Y0 = 34
const gx = (i: number) => X0 + i * s
const gy = (j: number) => Y0 + (n - j) * s
const ci = 3
const cj = 5
const nb: [number, number][] = [
[ci - 1, cj],
[ci + 1, cj],
[ci, cj - 1],
[ci, cj + 1],
]
return (
{/* grid lines */}
{Array.from({ length: n + 1 }, (_, i) => (
))}
{/* the stencil arms, drawn under the nodes */}
{nb.map(([i, j], k) => (
))}
{/* nodes */}
{Array.from({ length: n + 1 }, (_, i) =>
Array.from({ length: n + 1 }, (_, j) => {
const bnd = i === 0 || j === 0 || i === n || j === n
const inStencil = (i === ci && j === cj) || nb.some(([a, b]) => a === i && b === j)
return (
)
}),
)}
{nb.map(([i, j], k) => (
))}
{/* the sparsity pattern of A = I⊗T + T⊗I */}
{(() => {
const m = 5
const K = m * m
const px = 168 / K
const cells: ReactNode[] = []
for (let r = 0; r < K; r++) {
for (let c = 0; c < K; c++) {
const d = Math.abs(r - c)
if (d !== 0 && d !== 1 && d !== m) continue
if (d === 1 && Math.floor(r / m) !== Math.floor(c / m)) continue
cells.push(
,
)
}
}
return cells
})()}
)
}
/** The sine-transform solve, as three steps. */
export function FastPoissonSteps() {
const boxes = [
{ x: 10, w: 160, t: 'F', s: 'right-hand side on the grid', c: ink.axis },
{ x: 216, w: 176, t: 'F̃ = S F S', s: '2-D sine transform, O(N² log N)', c: method.fast },
{ x: 438, w: 176, t: 'Ũ = F̃ / (λₖ + λₗ)', s: 'a division, one per unknown', c: method.fast },
]
return (
{boxes.map((b, i) => (
{i < boxes.length - 1 ? (
) : null}
))}
)
}
/** Five-point against the compact nine-point. */
export function NinePointStencil() {
const s = 44
const draw = (ox: number, oy: number, w: (di: number, dj: number) => string | null, colour: string) => {
const cells: { x: number; y: number; v: string }[] = []
for (const dj of [-1, 0, 1]) {
for (const di of [-1, 0, 1]) {
const v = w(di, dj)
if (v !== null) cells.push({ x: ox + di * s, y: oy - dj * s, v })
}
}
return (
{cells.map((c, i) =>
c.x === ox && c.y === oy ? null : (
),
)}
{cells.map((c, i) => (
))}
)
}
return (
{draw(168, 116, (di, dj) => (di === 0 && dj === 0 ? '−4' : di === 0 || dj === 0 ? '1' : null), method.fd2)}
{draw(548, 116, (di, dj) => (di === 0 && dj === 0 ? '−20' : di === 0 || dj === 0 ? '4' : '1'), method.fd4)}
)
}
/** The multigrid V-cycle. */
export function VCycle() {
const levels = [
{ label: 'h', w: 300 },
{ label: '2h', w: 210 },
{ label: '4h', w: 140 },
{ label: '8h', w: 84 },
]
const y = (k: number) => 34 + k * 42
const XC = 358
const xl = (k: number) => XC - levels[k].w / 2
const xr = (k: number) => XC + levels[k].w / 2
return (
{levels.map((L, k) => (
{(() => {
const m = Math.max(3, 9 - 2 * k)
return Array.from({ length: m }, (_, i) => (
))
})()}
))}
{/* descending and ascending legs */}
{[0, 1, 2].map((k) => (
))}
{[2, 1, 0].map((k) => (
))}
)
}
/** What smoothing does to the error, as a picture rather than a claim. */
export function SmoothingIdea() {
const L = 40
const R = 320
const mid = 62
const wig = (amp: number, hf: number, phase: number) => {
const pts: string[] = []
for (let i = 0; i <= 140; i++) {
const t = i / 140
const v =
amp * Math.sin(Math.PI * t) * 0.75 +
hf * (Math.sin(19 * Math.PI * t + phase) * 0.5 + Math.sin(31 * Math.PI * t) * 0.35)
pts.push(`${(L + t * (R - L)).toFixed(1)} ${(mid - v).toFixed(1)}`)
}
return 'M ' + pts.join(' L ')
}
return (
{
const pts: string[] = []
for (let i = 0; i <= 140; i++) {
const t = i / 140
pts.push(`${(8 + t * (R - L)).toFixed(1)} ${(mid - 26 * Math.sin(Math.PI * t) * 0.75).toFixed(1)}`)
}
return 'M ' + pts.join(' L ')
})()}
fill="none"
stroke={method.iterative}
strokeWidth={1.7}
/>
{Array.from({ length: 8 }, (_, i) => (
))}
)
}