CLAUDE.md#
Tips for future agents. Patterned after the sibling hitandrun-interactive
project (same numbl + uihtml two-way bridge, 2D canvas figure).
Architecture#
app/— React single-file widget (npm run build→app/dist/index.html).*.mat root —walnuts_demo.m(driver) andwalnuts_sampler.m(figure plumbing).helpers/—walnuts.m(the sampler) and the targetlog_density.m/grad_log_density.m, put on the path by the driver viaaddpath('helpers').numbl-project.json— project metadata;entryis the landing page.
addpath('helpers') must be the first statement in the driver (numbl
resolves the search path before running, and addpath can't live inside a
function file). Run walnuts_demo.m, not the sampler directly.
Data flow (uihtml bridge)#
- MATLAB → figure:
uihtml(..., 'Data', struct)/sendEventToHTMLSource(src, name, data). - Figure → MATLAB:
sendToMATLAB(name, value)(seeapp/src/bridge.ts), received viaHTMLEventReceivedFcn.
The script sends the density grid + samples once, then handles two requests
(walnuts_sampler.m on_event):
resample{n, dt, maxError}→ fresh chain →samplesevent. (Target is fixed, so the density is not re-sent.)movie{dt, maxError}→walnuts(..., record=true)for a few transitions →movieevent carrying their orbit trajectories.
The figure's controls (samples / Δt / max error sliders, Resample) drive
resample; the ▶ Movie button drives movie and animates the returned
orbits (App.tsx + DensityView.tsx).
Trajectory recording#
walnuts.m records the orbit path only when called with record=true, via
globals (WREC_*): macro_step appends each committed macro-step's leapfrog
path (recomputed by leapfrog_capture), tagged with a segment id so the figure
breaks the polyline at orbit direction flips. Plain sampling (record=false)
takes the fast path with no recording overhead.
The algorithm#
helpers/walnuts.m is a direct port of Brian Ward's algorithms/WALNUTS.js
from chi-feng/mcmc-demo (based on Bob Carpenter's C++,
flatironinstitute/walnuts; paper arXiv:2506.18746). Spans carry both orbit
endpoints; macro_step does the within-orbit step halving; build_span
recurses (NUTS doubling); selection is Barker within sub-orbits, Metropolis at
the top. See README for full credits.
Performance#
Unlike the hitandrun kernel, WALNUTS uses recursion + structs, so it runs in
the numbl interpreter (it does not JS-JIT like a flat numeric loop). It's
still fine — banana orbits are shallow, ~1–2 ms/transition — so keep N modest
(the demo uses 1000 + 200 burn-in). Don't add a %!numbl:assert_jit guard here;
it would (correctly) fail.
Key files#
| File | Purpose |
|---|---|
app/src/App.tsx |
Reads data, hosts the info panel |
app/src/render/DensityView.tsx |
Canvas: density heatmap + sample scatter |
app/src/bridge.ts |
onData / onHostEvent / sendToMATLAB (generic) |
helpers/walnuts.m |
The WALNUTS sampler |
helpers/log_density.m / grad_log_density.m |
Banana target |
walnuts_sampler.m |
Runs the chain, builds the density grid, opens the figure |
walnuts_demo.m |
Driver: addpath + seed + call the sampler |
Local iteration#
cd app && npm install && npm run build # → app/dist/index.html
cd ..
npx tsx $NUMBL/src/cli.ts run walnuts_demo.m --plot # quick loop
# or preview the GitHub Pages bundle:
( cd $NUMBL && npm run build:site-viewer ) # one-time
npx tsx $NUMBL/src/cli.ts build-site . --out _site --base /
( cd _site && python3 -m http.server 8080 )
$NUMBL is a local clone of flatironinstitute/numbl. The real target is GitHub
Pages, where the deploy builds numbl from main.