// 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) => ( ))}
) }