This document records the parallel execution plan for implementing the remaining Phase A features: 1-D line-plot mode, stochastic Heun integration, and numerical safety guards.
Three independent features need to land. Sequential implementation would take
~3× the wall-clock time. All three touch disjoint regions of widget.js:
| Feature | Code Region | Conflict Risk |
|---|---|---|
| 1-D line plot | renderPhasePlane, computeNullclines, computeVectorField, CSS |
Low |
| Stochastic Heun | heun(), makeProjectedRHS (noise-free), UI noise controls |
Low |
| Safety guards | safeRK4(), safeHeun(), budget wrappers around numerical functions |
Low |
# Create feature branches from main
git branch p1-1d-mode
git branch p2-heun-noise
git branch p3-safety-guards
# Create isolated worktrees (each agent gets its own checkout)
git worktree add ../trajecturtle-1d p1-1d-mode
git worktree add ../trajecturtle-heun p2-heun-noise
git worktree add ../trajecturtle-safe p3-safety-guardsBranch: p1-1d-mode
Worktree: ../trajecturtle-1d
When display.length === 1, the phase plane renders a 1-D line plot:
- Horizontal axis: state variable
x(or whichever is displayed) - Vertical axis:
dx/dt(derivative of the displayed variable) - Vertical range:
[-2, 2](arbitrary but fixed for all 1-D plots) - Nullcline: zero-crossings of
f(x)plotted as points on the x-axis - Vector field: 1-D arrows along the x-axis
- Fixed points: where
f(x) = 0, shown as colored dots - Trajectory: time-series of
x(t)(same as time-series panel, not shown) - No limit-cycle detection in
detectRegimefor 1-D mode
Files to edit:
src/phase_plane_widget/static/widget.jsrenderPhasePlane(): addis1DbranchcomputeNullclines(): handleis1D(only one nullcline)computeVectorField(): handleis1D(arrows on x-axis)detectRegime(): already updated in main, verify 1-D branch works
src/phase_plane_widget/static/widget.css: add.ppw-1d-modestyles
Estimated: ~80 lines of JS + 20 lines of CSS.
Success criteria:
display = [0]on Wilson-Cowan renders a line plot- Clicking on the x-axis sets initial condition
- Fixed points appear as colored dots at zero-crossings
Branch: p2-heun-noise
Worktree: ../trajecturtle-heun
Add Stratonovich Heun integration with per-variable noise:
function heun(modelName, state0, t0, tMax, dt, params, noiseSigma) {
// noiseSigma: array of length n, one entry per state variable
// Wiener increments: dW ~ N(0, sqrt(dt))
// Stratonovich: predictor-corrector with noise evaluated at both steps
}Key constraints:
- Nullclines and vector field are noise-free (deterministic)
makeProjectedRHS()should accept adeterministicflag (default true)- Trajectories use Heun when noise is enabled, RK4 when disabled
- Per-variable noise strength sliders (0–1 range)
- Noise toggle checkbox
Files to edit:
src/phase_plane_widget/static/widget.js- Add
heun()function - Modify
makeProjectedRHS()to acceptdeterministicflag - Modify
computeAll(): use Heun when noise enabled - Add noise UI controls (checkbox + per-var sliders)
- Add
src/phase_plane_widget/widget.py: addnoise_enable,noise_sigmatraits
Estimated: ~120 lines of JS + 20 lines of Python.
Success criteria:
- Heun with σ=0 matches RK4 within tolerance
- Noise increases trajectory dispersion
- UI toggles between deterministic and stochastic modes
- Nullclines unchanged when noise is on/off
Branch: p3-safety-guards
Worktree: ../trajecturtle-safe
Wrap all numerical entry points with safety guards:
- NaN/Inf bailout: Any NaN or Inf in state terminates integration early
- Computation budget: max steps = 50,000 for integration
- Exponential clamp:
Math.exp(x)→Math.exp(Math.max(-709, Math.min(x, 709))) - Newton-Raphson budget: max 625 iterations (25×25 grid)
- Nullcline budget: max 3,600 evaluations (60×60 grid)
- Vector field budget: max 144 evaluations (12×12 grid)
function safeRK4(modelName, state0, t0, tMax, dt, params, budget = 50000) {
// Wrap RK4 with NaN/Inf detection and step counter
}
function hasBudget(counter, limit) {
if (counter > limit) return false;
return true;
}Files to edit:
src/phase_plane_widget/static/widget.js- Wrap
rk4()withsafeRK4() - Add budget checks to
findFixedPoints(),computeNullclines(),computeVectorField() - Add
expClamp()helper used everywhere - Return empty/null on failure +
console.warn()
- Wrap
Estimated: ~60 lines of JS.
Success criteria:
- MPR model at unstable regime (where NaN could occur) degrades gracefully
- Console warning on budget exhaustion, not crash
- All three built-in models still work normally
After all three branches pass review and tests, merge in this order:
- P3 first — safety guards are transparent API wrappers, no functional change
- P2 second — Heun builds on stable (guarded) integrator base
- P1 last — rendering is independent but depends on stable base
cd /home/duke/src/phase-plane-widget
# Review each branch
for b in p3-safety-guards p2-heun-noise p1-1d-mode; do
git log --oneline $b..origin/main
done
# Merge in order
git merge p3-safety-guards
git merge p2-heun-noise
git merge p1-1d-mode
# Clean up worktrees
git worktree remove ../trajecturtle-1d
git worktree remove ../trajecturtle-heun
git worktree remove ../trajecturtle-safe
git branch -d p1-1d-mode p2-heun-noise p3-safety-guards
# Build & deploy
uv run --extra docs python scripts/generate_demos.py
uv run --extra docs mkdocs build --strict
git commit -am "feat: merge parallel branches (1D mode, Heun, safety guards)"
git pushRun these three in parallel (each takes ~20–40 min):
# Agent 1: 1D mode
cd ../trajecturtle-1d
pi -p "Implement 1D line-plot phase plane mode in widget.js. When display.length===1, renderPhasePlane should draw dx/dt vs x (xlim on horizontal, derivative on vertical [-2,2]). computeNullclines and computeVectorField must handle 1D: computeNullclines returns nullcline_y as zero-crossings of f(x), computeVectorField returns 1D arrows on the x-axis. Remove limit cycle detection from detectRegime when is1D. Update CSS for 1D mode. Test by checking that Wilson-Cowan with display=[0] renders a line plot. Write a quick test script to verify." --no-sandbox --model ollama/deepseek-v4-flash
# Agent 2: Heun noise
cd ../trajecturtle-heun
pi -p "Implement stochastic Heun integration in widget.js. Add a heun() function that supports per-variable noise with noise_shape=[nvar]. Add 'noise_enable' checkbox and per-variable noise strength sliders (0–1 range). The projected RHS for nullclines/vector field must be noise-free (deterministic). Trajectories use Heun when noise is enabled, RK4 when disabled. Add Wiener increments with dt^(1/2) scaling. Stratonovich interpretation. Fix: makeProjectedRHS should use a deterministic flag. Add a test that Heun with sigma=0 agrees with RK4. Write a test script." --no-sandbox --model ollama/deepseek-v4-flash
# Agent 3: Safety guards
cd ../trajecturtle-safe
pi -p "Add numerical safety guards to widget.js. Wrap RK4/Heun with safe integrators that: (1) detect NaN/Inf and short-circuit, (2) enforce a computation budget (max steps 50000), (3) clamp exponential arguments to [-709,709], (4) add hasBudget() checks to findFixedPoints (max 625 newton iterations), computeNullclines (max 3600 evals), computeVectorField (max 144 evals). Return empty/null on failure with console warning rather than crashing. Create a test with the MPR model at an unstable regime where NaN could occur, verifying graceful degradation." --no-sandbox --model ollama/deepseek-v4-flash| Risk | Likelihood | Mitigation |
|---|---|---|
| Agent crashes / stalls | Medium | Set timeouts; poll every 5 min; kill and retry |
| Merge conflicts | Low | Branches touch disjoint code regions (render vs integrator vs guards) |
| Tests timeout on slow machine | Medium | Reduce t_max in demos; use smaller grids |
| Heun ≠ RK4 at σ=0 | Medium | Increase tolerance; check floating-point differences |
- Branches created
- Worktrees created
- Agent 1 (1D) launched
- Agent 2 (Heun) launched
- Agent 3 (Safety) launched
- Agent 1 completed
- Agent 2 completed
- Agent 3 completed
- P3 merged into main
- P2 merged into main
- P1 merged into main
- Tests pass
- Docs built
- Pushed to GitHub
Generated: 2026-04-24