/ concept-collection / barycentric-rational
Sign in
concept-collection / barycentric-rational
barycentric-rational / CLAUDE.md
131 lines · 6.3 KBPreviewCodeBlameHistoryRaw
1# CLAUDE.md
3Notes for future agents working in this repo. See [README.md](README.md) first —
4it explains the paper and what each tab shows.
6## The shape of the thing
8A standalone Vite + React app that embeds [numbl](https://numbl.org) as a library
9(`numbl/browser`, `createNumblSession`). This is the seqlab / numbl-image-filter
10pattern, not the numbl-project.json + site-viewer pattern that
11hitandrun-interactive uses: there is no `numbl-project.json` here and the deploy
12is a plain `vite build` to GitHub Pages.
14The MATLAB layer is the interesting part, and it is worth understanding before
15changing anything.
17## How a run works
191. The editor holds a **method script**: function definitions only, no statements.
202. `src/engine/files.ts` composes `main.m` = `src/matlab/driver.m` + `"\n"` + that
21 script. The script's functions therefore become **local functions of the
22 driver**, which is the only way the driver can call them.
233. `src/matlab/lib/*.m` go into the session as ordinary function files.
244. Parameters are written to `params.json` in the session VFS; the driver reads it
25 with `jsondecode(fileread(...))` and writes `out.json`, which the app reads back
26 with `session.readFile`.
28One session per script. Changing a parameter only rewrites `params.json` and
29re-runs — a full boot is only needed when the script text changes, because the
30script is compiled into `main.m` at boot.
32### Gotcha: invoke the script with `run('main.m')`, not `main;`
34numbl 0.4.18 mis-binds the arguments of a script's **local functions** when the
35script is invoked by name from the REPL, which is what `session.execute` gives us:
36the callee sees its parameters as undefined (`Undefined function or variable 'p'`).
37Reproduce it outside the browser with
39```
40npx tsx $NUMBL/src/cli.ts eval "t;" # fails
41npx tsx $NUMBL/src/cli.ts eval "run('t.m');" # works
42npx tsx $NUMBL/src/cli.ts run t.m # works
43```
45on any script with a local function that takes an argument. `runner.ts` goes
46through `run('main.m')` for this reason. If numbl fixes it, the workaround is
47harmless either way.
49### Gotcha: `jsonencode` flattens single-row matrices
51`jsonencode([1 2 3])` is `[1,2,3]`, not `[[1,2,3]]`, so a matrix with one row
52arrives on the JavaScript side with the wrong nesting. `drv_rows` in the driver
53converts matrices to a cell array of rows before encoding. Use it for anything
54matrix-shaped (`E`, `orders`, `P`, `L`).
56NaN and ±Inf all cross as `null`, which is why the TypeScript side uses
57`Num = number | null` everywhere and every plot helper skips nulls.
59## The contract with the method script
61```matlab
62w = bary_weights(x, d) % required
63r = bary_eval(x, y, w, t) % required
64[P, L] = local_blend(x, y, d, t) % optional; without it the Blending tab is empty
65```
67The driver wraps `local_blend` in try/catch and reports the message, so a script
68that omits it is not an error. Everything else the tabs show — the denominator,
69its roots, the real poles, the polynomial and spline overlays — is derived in the
70driver from `x`, `y` and `w`, which is deliberate: it means the Poles tab tells the
71truth about *whatever* weights the user supplies, and is why `equal.m` and
72`random.m` light it up.
74## Numerical points that took some getting right
76- **Vectorise `bary_eval`.** The scalar double loop takes 6.8 s for the
77 convergence study; `D = t(:) - x(:).'` then a matvec takes 0.19 s, with
78 identical results.
79- **The denominator's leading coefficients.** `q(t) = Σ_k w_k Π_{j≠k}(t − x_j)`
80 has degree n−d for the Floater-Hormann weights: the top *d* coefficients vanish
81 identically, because the first *d* moments of the weights are zero (that is what
82 "r reproduces polynomials of degree d" means). They come out of the sum as
83 roundoff rather than as zero, and left in place they produce spurious roots,
84 some of them **real** — which would be a lie in a picture whose whole point is
85 that there are none. `drv_denom_coeffs` returns a per-coefficient rounding-error
86 floor (`eps · (n+1) · Σ|w| · binomial(n, m)`, in the rescaled variable) and the
87 leading coefficients are stripped against it.
88- **Parity.** The degree of the denominator is n−d when n−d is **even** and n−d−1
89 when it is odd, because the leading coefficient is ±Σ_{i=0}^{n−d} (−1)^i. That
90 is the same parity that splits Theorem 2 into two cases. `scripts/matlab-test.mjs`
91 asserts the root count, so get this right or the tests fail.
92- **Rescale to [−1, 1]** before forming products of n factors, or they over- or
93 underflow. It multiplies the denominator by a positive constant, so signs and
94 roots are untouched.
95- **Drawing the denominator.** It spans many orders of magnitude. The plot shows
96 `sign(q)·(|q|/max|q|)^(1/n)`, which preserves every sign and every zero.
98## Tests
100```
101npm run test:matlab # ~2 min: the .m layer vs the paper, through the numbl CLI
102npm run test:browser # ~1 min: the built app in headless Chrome
103```
105`test:matlab` needs a numbl clone (`NUMBL_DIR`, default `~/src/numbl`) and runs
106`main.m` directly with the CLI, so it does **not** exercise the REPL path — the
107browser test is what covers that. Both check real numbers from the paper rather
108than snapshots; if you change the MATLAB layer, they will tell you.
110`test:browser` builds nothing itself — run `npm run build` first. It spawns
111`vite preview` detached and kills the process group, because signalling `npx`
112alone leaves the server holding the port.
114Visual judgement is not something either suite does. Ask a human to look at the
115page; the headless run is for console errors and behavioural assertions only.
117## Plotting
119`src/plot/` is a small hand-rolled SVG layer, no chart library. `palette.ts`
120documents which colour does which job (identity, polarity, ordinal, status) and
121records the colour-vision validator results; if you add a series, re-validate
122rather than picking a hex that looks nice. Curves that run off to infinity are
123clamped to a band just outside the frame and clipped, so a pole leaves the frame
124in the right direction instead of producing unusable path data.
126## Ideas not done
128- Berrut and Trefethen's approach to evaluating derivatives of r via the
129 Schneider-Werner formulas, which the paper mentions in Section 4.
130- The Lebesgue constant of the interpolation operator, as a function of d.
131- Table 2's "best d for each n" as a small search.
moveopenescclose