/ tutorials / laplace-dirichlet
Sign in
tutorials / laplace-dirichlet
laplace-dirichlet / CLAUDE.md
180 lines · 8.8 KBCodeBlameHistory
3Notes for anyone, human or agent, working in this repository. Read
4[README.md](README.md) first for what the tutorial contains.
6## The shape of the thing
8A Vite + React app that embeds [numbl](https://numbl.org) as a library
9(`numbl/browser`, `createNumblSession`). This is the seqlab / barycentric-rational
10pattern, not the `numbl-project.json` + site-viewer pattern: there is no project
11file and the deploy is a plain `vite build`.
13```
14src/
15 App.tsx sidebar, hash routing, chapter switching
16 ui/ Lab (one runnable section), ChapterView, Controls, Markdown
17 engine/ the numbl session wrapper
18 plot/ a small hand-rolled SVG layer, plus a canvas field plot
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 19 figures/ static diagrams, one .tsx per part, keyed in figures/index.ts
088be4cAn interactive tutorial on the Dirichlet problem, accuracy against costJeremy Magland 20 matlab/ the shared .m library, shown to the reader on its own page
21 chapters/
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 22 intro/ the introduction: front matter, no labs
088be4cAn interactive tutorial on the Dirichlet problem, accuracy against costJeremy Magland 23 onedim/driver.m the driver for chapters 1-4, plus their shared panels
24 twodim/driver.m the driver for chapters 5-7, plus their shared panels
25 chNN/ per-chapter text.md, editable .m script, chapter.ts
26```
28A **chapter** is prose in one `text.md` with `<!--lab:id-->` markers where its labs
29go. A **lab** is a driver (fixed, hidden), an editable script (the method), a list of
30declarative controls, and a render function. Each lab owns one numbl session for as
31long as it is on screen: moving a control rewrites `params.json` and re-runs, while
32editing the script reboots the session, because the script is compiled into `main.m`
33at boot.
35`main.m` is `driver.m` + `"\n"` + the editable script, so the script's functions
36become **local functions of the driver** and the driver can call them. That is the
37whole of the contract; each driver's header comment states which functions its
38script must define.
42**This tutorial needs numbl 0.4.19 or later.** It runs correctly on 0.4.18 and
43is drastically slower there: the L-shaped sweep in Chapter 9 takes about three
44minutes instead of six seconds, and Chapter 8 about six seconds instead of a
45fifth of one.
47The reason is worth recording. Most of what this section used to say was a list
48of ways to fall off numbl's JIT fast path, found by profiling this tutorial: a
49`switch` anywhere in a call chain, a struct parameter, `A(k,:) = row` with a
50loop index, a bare `tic;`, and the expensive one: the rule that nothing nested
51under a loop the JIT had declined was compiled either, so a single unsupported
52line could put a whole solver on the interpreter. They were fixed
53upstream in 0.4.19, and the drivers here were rewritten back into ordinary
54MATLAB once they were: the sweeps are plain `for` loops again. If you find a
55comment describing a recursion-instead-of-a-loop workaround, it is stale; the
56git history has the old shape.
58What is left is worth knowing.
60### Keep the *timed* loop type-stable
62numbl specialises a loop on the types flowing through it. A loop whose iterate
63is assigned from several different functions, or whose body indexes a cell
64array, is not something it can specialise, and it runs interpreted. That is
65usually harmless, since the expensive callee inside is still compiled, but when
66the loop being measured *is* the thing under test the overhead lands in the
67number on screen. Two consequences visible in `ch08/iterative.m`:
69- each iterative method has its own loop rather than one loop with a switch
70 inside it, and multigrid keeps its grid hierarchy in one flat vector rather
71 than a cell array;
72- residual and error histories accumulate as squares and are square-rooted
73 once at the end. A `sqrt` inside the loop costs about 60 per cent on the
74 sweep, because sqrt may return a complex value and the history array is then
75 no longer a plain real array.
77Neither is a correctness matter. Check with `%!numbl:assert_jit` inside a loop
78body (it throws if that unit ran interpreted) and `NUMBL_JIT_LOG=1`, which
79prints every JIT decline with its reason.
81### `jsondecode` of a JSON array of arrays gives a cell of column vectors
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 83MATLAB gives a matrix when the rows are the same length. The L-shaped driver
84unpacks `p.nsm` by hand.
86### `jsonencode` refuses complex values, and flattens single-row matrices
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 88`jsonencode([1 2 3])` is `[1,2,3]`, not `[[1,2,3]]`, so anything matrix-shaped
89goes through `lp_rows`, which converts it to a cell array of rows first. NaN and
90Inf cross as `null`, which is why the TypeScript side uses `Num = number | null`
91everywhere and every plot helper skips nulls. A value whose imaginary part is
92entirely zero counts as real and encodes fine.
94### Backslash densifies sparse matrices
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 96`A \ b` converts a sparse `A` to dense before calling LAPACK, so there is no
97sparse direct solver here. That is why Chapter 5 stops at $N = 48$ and why
98Chapters 6 to 10 are about methods that do not need one. The text says so where
99it matters.
101### Scalar loops are often faster than vectorised slices
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 103A hand-written double loop over a stencil beats the equivalent
104`A(2:end-1,...)` expression by about a factor of three, because the vectorised
105form allocates several temporaries per sweep. The kernels in
106`ch08/iterative.m` are written as loops for that reason, which also happens to
107make them readable.
109### The browser is slower than the CLI
111The CLI uses a native LAPACK addon; the browser falls back to a TypeScript
112implementation, and it announces which one on `console.error`. Dense solves are
e5fc8c6An introduction and static figures, and the MATLAB written plainly againJeremy Magland 113noticeably slower in the page than in `npm run test:matlab`. Nothing in the
114tutorial depends on the absolute numbers, but do not quote a CLI timing as if it
115were what a reader will see.
117## Measuring time honestly
119Timings come from `tic`/`toc` **inside** the MATLAB layer, around the solve only, so
120they exclude session boot, JSON encoding and rendering. Each method gets a warm-up
121call before its first timed solve, because the first call also pays for compilation,
122and any solve under 5 ms is repeated a few times with the minimum kept. Below about
1230.1 ms the measurement is dominated by fixed overhead and the work-precision curves
124flatten out on the left; that is the clock, not the method.
126## Testing
128```
129npm run test:matlab # ~2 min: the .m layer through the numbl CLI
130npm run build && npm run test:browser [--shots]
131```
133Every assertion in `test:matlab` is a number stated in the prose: an observed order,
134an error at a given resolution, a ratio between two methods. If one fails, either the
135code or the text is wrong, and the text is not allowed to win by default.
137`test:browser` opens every chapter, presses run on the labs that wait to be asked,
138and checks that nothing throws and that the figures contain data. It cannot judge
139whether a picture is any good; `--shots` writes full-page screenshots into `tmp/` so
140that a human, or an agent with vision, can look.
144`src/figures/` holds the diagrams that are drawn rather than computed: the problem
145statement, the stencils, the roundoff-floor V, the V-cycle, the re-entrant corner,
146the close-evaluation spike. They are addressed from the prose by `<!--fig:id-->`, the
147same way labs are addressed by `<!--lab:id-->`, and resolved through the registry in
148`figures/index.ts`. They exist because several chapters, Chapters 8 to 10 in
149particular, show nothing at all until the reader presses Run.
151They are plain inline SVG with no data behind them, written against a 716-unit-wide
152viewBox and `width="100%"`, using `plot/palette.ts` for colour so that a curve
153labelled `fd2` is the same blue everywhere. Two habits are worth keeping. Text is
154placed by hand, so a caption or a legend that fits at one size may collide at
155another: render and look. And the introduction's accuracy-against-cost picture is a
156schematic, which its caption says out loud; do not let it drift into looking like
157measured data.
159To look at them, point a headless browser at each chapter and screenshot
160`figure.static-figure`; `npm run test:browser --shots` writes whole pages, which is
161coarser but needs no extra script.
165`src/plot/` is a small SVG layer with no chart library. `palette.ts` records which
166colour does which job and the validator run that justifies the set; if you add a
167series, re-run the validator rather than picking a hex that looks nice. Fields are
168drawn into a canvas at one pixel per grid cell and scaled, so a 512-by-512 grid costs
169no more to draw than a 16-by-16 one, and points outside the domain are passed as
170`null` and left as background.
172## Things deliberately not done
174- A sparse direct solver, for the reason above.
175- Graded meshes in Chapter 9. The chapter names local refinement and singular
176 enrichment as the standard remedies and then demonstrates a third, because the
177 third is the one that is short enough to read.
178- Close-evaluation quadrature in Chapter 10. The failure is shown and quantified; the
179 fix is a chapter of its own.
180- Neumann and mixed boundary conditions, anywhere.
↑↓ move↵openescclose