1import { createElement, Fragment } from 'react'
2import type { Chapter } from '../../ui/types.ts'
3import driver from '../onedim/driver.m?raw'
4import script from './spectral.m?raw'
5import text from './text.md?raw'
6import { Convergence, Solution1D, WorkPrecision, fmtErr, type SingleOut, type SweepOut } from '../onedim/panels.tsx'
7import { KIND_CONTROL } from '../shared.ts'
8import type { MethodKey } from '../../plot/palette.ts'
10const NS = [8, 16, 32, 64, 128, 256, 1024, 4096, 16384]
11const SLOTS: MethodKey[] = ['fd2', 'fd4', 'spectral']
13export const ch04: Chapter = {
14 id: 'spectral',
15 nav: 'Chebyshev collocation',
16 part: 'One dimension',
17 title: 'Chebyshev collocation',
18 blurb: 'A dense matrix, geometric convergence, and the conditions under which that is worth it.',
19 text,
20 labs: [
21 {
22 id: 'single',
23 title: 'One spectral solve',
24 caption:
25 'Note where the nodes are. The error is plotted at the nodes, so at N = 32 there are 33 of them and the answer is already near machine precision.',
26 driver,
27 script,
28 controls: [
29 KIND_CONTROL,
30 {
31 kind: 'select',
32 key: 'mid',
33 label: 'method',
34 options: [
35 { value: 3, label: 'Chebyshev collocation' },
36 { value: 2, label: 'compact 4th order' },
37 { value: 1, label: '3-point differences' },
38 ],
39 },
40 { kind: 'slider', key: 'N', label: 'N', values: [4, 8, 12, 16, 24, 32, 48, 64, 96, 128], min: 4, max: 128 },
41 ],
42 defaults: { kind: 'smooth', mode: 'single', mid: 3, N: 24, ngrid: 700 },
43 editorOpen: true,
44 render: (d: SingleOut, p) =>
45 createElement(Solution1D, { data: d, slot: SLOTS[Number(p.mid) - 1] ?? 'spectral' }),
46 summary: (d: SingleOut) => [
47 { label: 'unknowns', value: String(d.n - 1) },
48 { label: 'max error', value: fmtErr(d.maxerr) },
49 ],
50 },
51 {
52 id: 'sweep',
53 title: 'All three, by resolution and by cost',
54 caption: 'Chebyshev is capped at N = 256, where the dense solve starts to dominate the timing.',
55 driver,
56 script,
57 controls: [KIND_CONTROL],
58 defaults: { kind: 'smooth', mode: 'sweep', ns: NS, nmaxs: [16384, 4096, 256], timed: 1 },
59 render: (d: SweepOut) =>
60 createElement(
61 Fragment,
62 null,
63 createElement(Convergence, {
64 key: 'c',
65 data: d,
66 style: {
67 slots: SLOTS,
68 refs: [
69 { p: -2, label: 'slope −2' },
70 { p: -4, label: 'slope −4' },
71 ],
72 },
73 height: 340,
74 }),
75 createElement(WorkPrecision, { key: 'w', data: d, style: { slots: SLOTS }, height: 340 }),
76 ),
77 summary: (d: SweepOut) => [
78 { label: 'best spectral', value: fmtErr(Math.min(...(d.E[2].filter((v) => v != null) as number[]))) },
79 { label: 'best 3-point', value: fmtErr(Math.min(...(d.E[0].filter((v) => v != null) as number[]))) },
80 ],
81 },
82 ],
83}