Skip to content

feat(app): add a Bootstrap tab and use the Ward rotavirus data as the example - #26

Merged
seanthimons merged 7 commits into
mainfrom
feat/app-bootstrap
Oct 23, 2026
Merged

seanthimons merged 7 commits into
mainfrom
feat/app-bootstrap

Conversation

@seanthimons

Copy link
Copy Markdown
Owner

Stacked on #24 (base is feat/app-explore; GitHub retargets to main once #24 merges).

Summary

Adds a Bootstrap tab that runs singlehit's bootstrap in the background and feeds the result into the Plots and Explore tabs as confidence bands. Also switches the example button to the Ward rotavirus data.

  • Background runs: one async job per fitted model on a shared mirai worker pool. The pool starts lazily on the first run (capped at 8 workers, options(singlehitapp.workers = n) overrides) and stops with the app. Starting it ourselves on every run means no job "owns" the pool, so finishing one cannot shut it down under another session.
  • Progress and cancel: progress is polled without blocking, with one bar per model (the exact model is far slower, so one overall bar would stall). Cancel stops the workers; a run is also cancelled when the analysis changes or the session ends. A cancelled run is never collected.
  • Gentle defaults: 1,000 replicates; 200 for the exact model. Limits are 100 to 5,000 (1,000 for exact). Optional seed, with the same offsets as analyze_dose_response() so a run is reproducible.
  • Results: percentile intervals, how many replicates converged, and parameter-uncertainty plots. A bootstrap with fewer than two converged replicates is reported and not attached (it would break the Plots tab).
  • Bands: attached only to the analysis they were computed from. Plots says when a view has no bands yet, and that the overlay never draws them.
  • Ward example: 8 dose groups. The exponential model fits badly, beta-Poisson is adequate, and the exact model is recommended, which makes a good demo of the comparison.

Why the 5,000 ceiling

Launching a 10,000-replicate job blocks the app's single R process for about 48 seconds (1,000 takes 1.7 s). The cause is in singlehit, not here: the worker closure in start_mirai_bootstrap() captures its frame, so every batch carries the whole task list and dispatch cost is quadratic (4,000 replicates: 5.4 s with the frame captured, 0.02 s without). That is being fixed in a separate singlehit PR. Until then the app caps runs at 5,000 and shows a "Preparing bootstrap replicates" message while a run starts. The cap can be revisited after that fix lands.

Requires singlehit from main

add_bootstrap() and bootstrap_progress() are on main but not in the released 0.2.0, so singlehit (>= 0.2.0) in DESCRIPTION is not enough on its own; Remotes: seanthimons/singlehit covers GitHub installs. The floor should be raised once singlehit's version is bumped (a root change, out of scope here). The app README says so.

Testing

  • App suite via app/dev/run_tests.R: 218 pass, 0 fail. Module tests run real jobs on a two-worker pool (100 replicates is the smallest allowed run).
  • Driven in a browser, launched with source("dev/run_dev.R"): Ward example, then Fit (with and without the exact model), then Bootstrap. Bands appear on Plots, the progress bars track each model, and after Cancel every worker drops to 0% CPU. Two layout bugs found this way (clipped cards, squashed plots) are fixed.

Not in this PR

The one-click "confidence bands" preset for Explore, pooling, and export.

… example

Run singlehit's bootstrap in the background from the app and feed the finished
bootstraps into the Plots and Explore tabs as confidence bands.

- one async job per fitted model on a shared, lazily started mirai pool (capped
  at 8 workers), so no job owns the pool and a session finishing cannot shut it
  down under another; stopped with the app via onStop
- progress polled without blocking, one bar per model, Cancel, and cancellation
  when the analysis changes or the session ends; a cancelled run is never
  collected
- gentle defaults: 1,000 replicates, 200 for the much slower exact model; limits
  of 100 to 5,000 (1,000 for exact); optional seed using the same offsets as
  analyze_dose_response() so a run can be reproduced
- results: percentile intervals, replicates that converged, parameter plots; a
  bootstrap with fewer than two converged replicates is reported, not attached
- bands are attached only to the analysis they were computed from; Plots says
  when a view has no bands yet
- the example button loads the Ward rotavirus data
- tests, including real-mirai runs on a two-worker pool

Requires singlehit from main: add_bootstrap() and bootstrap_progress() are not in
the released 0.2.0.
- Allow up to 10,000 bootstrap replicates and add a benchmark script
- Show busy indicators and task button status during analysis and bootstrap setup
The busy-indicator test searched for a string shiny::useBusyIndicators() never
emits, so it failed. The tag works by setting two attributes on the page
element; assert those instead.
…hit 0.3.0

After a bootstrap the Plots tab stayed on the model overlay, which never draws
confidence bands, so the bands looked like they had not been applied; the only
clue was a small grey hint. Measured in the real app by counting coloured pixels:
overlay 1.1%, one panel per model 16.4%, single model 18.8%.

- when bands first arrive while the overlay is selected, switch to the panel view
  and say why; the choice is left alone on the panel and single views
- the hint is now a visible callout that names the views that show bands
- tests for the view helper and, with the radio update and notice mocked, for
  the switch itself

Raise the app's minimums to singlehit 0.3.0 (add_bootstrap() and
bootstrap_progress() were added in that release) and testthat 3.1.7
(local_mocked_bindings), and update the README note accordingly.
Draw confidence bands switches to the fitted curves, fills esquisse's slots
with a ribbon of lower/upper by level, faceted by model, and sets a log10
dose axis. It drives esquisse's own inputs through their public update
functions, waiting for esquisse to react between steps, and only appears when
the curves actually carry bands. Requires esquisse 2.1.0 or later.
@seanthimons
seanthimons changed the base branch from feat/app-explore to main October 23, 2026 16:57
@seanthimons
seanthimons marked this pull request as ready for review October 23, 2026 17:34
@seanthimons
seanthimons merged commit bf4723f into main Oct 23, 2026
10 checks passed
@seanthimons
seanthimons deleted the feat/app-bootstrap branch October 27, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant