// Figures for the iterative-solver chapter: how the residual falls per // iteration, how the true error falls, and how both compare with a direct // solve of the same system. import Plot from '../../plot/Plot.tsx' import Legend from '../../plot/Legend.tsx' import { EndLabel, Line, Markers } from '../../plot/Curves.tsx' import { ink, marker, method, type MethodKey } from '../../plot/palette.ts' import type { Num } from '../../engine/types.ts' import { fmtErr } from '../onedim/panels.tsx' export interface IterOut { names: string[] N: number its: number[] /** squared relative residual and squared error: the MATLAB layer hands back * the squares, for the reason its driver explains */ R2: Num[][] E2: Num[][] T: Num[][] discErr: number directMs: number unknowns: number } const rootOf = (rows: Num[][]): Num[][] => rows.map((r) => r.map((v) => (v == null || !isFinite(v) || v < 0 ? null : Math.sqrt(v)))) // one palette slot per method, in the order the script lists them const SLOTS: MethodKey[] = ['fd2', 'fd4', 'fast', 'spectral', 'iterative'] function lowHigh(rows: Num[][], floor = 1e-14): [number, number] { let lo = Infinity let hi = -Infinity for (const r of rows) { for (const v of r) { if (v == null || !isFinite(v) || v <= 0) continue if (v < lo) lo = v if (v > hi) hi = v } } if (!isFinite(lo)) return [floor, 1] return [Math.max(lo / 3, floor), hi * 3] } function lastIndex(row: Num[]): number { for (let i = row.length - 1; i >= 0; i--) if (row[i] != null && isFinite(row[i] as number)) return i return 0 } export function IterPanels({ data }: { data: IterOut }) { const its = data.its const R = rootOf(data.R2) const E = rootOf(data.E2) const xd: [number, number] = [0, its[its.length - 1]] const rd = lowHigh(R) const ed = lowHigh([...E, [data.discErr]]) const td = lowHigh(data.T.map((r) => r.map((v) => (v != null && v > 0 ? v : null)))) td[0] = Math.max(td[0], 0.05) const legend = data.names.map((n, k) => ({ label: n, color: method[SLOTS[k]] })) return ( <>
relative residual against iteration count
{(f) => ( <> {R.map((row, k) => ( {row.length <= 40 && ( )} ))} )}
true error against iteration count; the dashed line is the discretisation error, which is where the exact solution of this linear system sits
( <> {E.map((row, k) => { const i = lastIndex(row) return ( ) })} )} > {(f) => ( <> {E.map((row, k) => ( ))} )}
true error against milliseconds of solver time; the marked point is the sine-transform direct solve of the same system
{(f) => ( <> {E.map((row, k) => ( (v == null || v <= 0 ? NaN : v))} y={row} f={f} color={method[SLOTS[k]]} /> ))} )} ) } export function iterSummary(d: IterOut) { const mg = d.E2[d.E2.length - 1] const i = lastIndex(mg) return [ { label: 'unknowns', value: String(d.unknowns) }, { label: 'discretisation error', value: fmtErr(d.discErr) }, { label: 'multigrid cycles used', value: String(d.its[i]) }, ] }