import Plot from '../plot/Plot.tsx' import Legend, { type LegendItem } from '../plot/Legend.tsx' import More from './More.tsx' import SeriesToggle from './SeriesToggle.tsx' import { dColor, series } from '../plot/palette.ts' import { linePath, type Frame } from '../plot/scales.ts' import type { ConvergeOut, FuncName, NodeKind, Num } from '../engine/types.ts' interface Props { out: ConvergeOut | null running: boolean stale: boolean f: FuncName nodes: NodeKind ds: number[] maxN: number showSpline: boolean showPoly: boolean onChange: (patch: { ds?: number[]; maxN?: number; showSpline?: boolean; showPoly?: boolean }) => void onRun: () => void } const D_CHOICES = [0, 1, 2, 3, 4, 5, 6, 8] const N_CHOICES = [80, 160, 320, 640] const fmt = (v: Num) => (v == null || !isFinite(v) ? '-' : v.toExponential(1)) const fmtOrd = (v: Num) => (v == null || !isFinite(v) ? '' : v.toFixed(1)) const F_LABEL: Record = { runge: '1 / (1 + x²)', sine: 'sin x', abs: '|x|', custom: 'the custom function', } export default function ConvergencePanel(props: Props) { const { out, running, stale, f, nodes, ds, maxN, showSpline, showPoly, onChange, onRun } = props const toggleD = (d: number) => { const next = ds.includes(d) ? ds.filter((v) => v !== d) : [...ds, d].sort((a, b) => a - b) if (next.length > 0 && next.length <= 6) onChange({ ds: next }) } return (

Refit with more and more nodes and record the largest error each time. On these log-log axes a power law is a straight line, and steeper means faster convergence. Currently fitting {F_LABEL[f]}{' '} on {nodes} nodes.

Theorem 2: for d ≥ 1 the error is O(hd+1) as h → 0, whatever the nodes look like, provided f is smooth enough. On these axes that is a line of slope −(d+1), and the slopes measured between consecutive n are printed in the table. With uniform nodes and Runge's function this reproduces the paper's Table 1; turn the spline on for Tables 3 and 4.

blend degrees d
{D_CHOICES.map((d) => ( ))}
largest n
{N_CHOICES.map((n) => ( ))}
compare with
onChange({ showSpline: on })} > cubic spline onChange({ showPoly: on })} > polynomial
{!out ? (

The study refits the interpolant at every n and every d, so it is the one thing on this page that does not run on its own. Press Run.

) : ( )}
) } function ConvergenceChart({ out, showSpline, showPoly, stale, }: { out: ConvergeOut showSpline: boolean showPoly: boolean stale: boolean }) { const all: Num[] = [ ...out.E.flat(), ...(showSpline ? (out.splineErr ?? []) : []), ...(showPoly ? (out.polyErr ?? []) : []), ] const finite = all.filter((v): v is number => v != null && isFinite(v) && v > 0) const lo = Math.min(...finite) const hi = Math.max(...finite) const yd: [number, number] = [Math.pow(10, Math.floor(Math.log10(lo)) - 0.3), Math.pow(10, Math.ceil(Math.log10(hi)) + 0.3)] const xd: [number, number] = [out.ns[0] * 0.85, out.ns[out.ns.length - 1] * 1.35] const legend: LegendItem[] = out.ds.map((d, i) => ({ label: `d = ${d}`, color: dColor(i, out.ds.length), })) if (showPoly && out.polyErr) legend.push({ label: 'polynomial (d = n)', color: series.poly }) if (showSpline && out.splineErr) legend.push({ label: 'cubic spline', color: series.spline }) const dots = (f: Frame, vals: Num[], color: string) => vals.map((v, j) => v == null || !isFinite(v) || v <= 0 ? null : ( ), ) /** the label goes at the right end of the curve, on its last finite point */ const endLabel = (f: Frame, vals: Num[], color: string, text: string) => { for (let j = vals.length - 1; j >= 0; j--) { const v = vals[j] if (v != null && isFinite(v) && v > 0) { return ( {text} ) } } return null } return ( <> {stale &&
Showing the previous study — the settings have changed since.
} String(Math.round(v))} > {(f) => ( <> {showPoly && out.polyErr && ( <> {dots(f, out.polyErr, series.poly)} {endLabel(f, out.polyErr, series.poly, 'poly')} )} {showSpline && out.splineErr && ( <> {dots(f, out.splineErr, series.spline)} {endLabel(f, out.splineErr, series.spline, 'spline')} )} {out.E.map((row, i) => { const c = dColor(i, out.ds.length) return ( {dots(f, row, c)} {endLabel(f, row, c, `d = ${out.ds[i]}`)} ) })} )}

The same numbers

{out.ds.map((d) => ( ))} {showSpline && out.splineErr && } {showPoly && out.polyErr && } , , ])} {showSpline && out.splineErr && [, ]} {showPoly && out.polyErr && } {out.ns.map((n, j) => ( {out.ds.map((d, i) => [ , , ])} {showSpline && out.splineErr && [ , , ]} {showPoly && out.polyErr && } ))}
n d = {d} cubic splinepolynomial
{out.ds.map((d) => [ errorordererrorordererror
{n}{fmt(out.E[i][j])} {fmtOrd(out.orders[i][j])} {fmt(out.splineErr[j])} {fmtOrd(out.splineOrders?.[j] ?? null)} {fmt(out.polyErr[j])}

The order column is the slope measured between consecutive rows; Theorem 2 predicts d + 1.

The order is log(eprev / e) / log(n / nprev). Where a row of errors stops falling, it has reached the point at which the weights themselves, which grow like h −d, cost more accuracy than the higher order buys.

) }