Skip to content

New-era experiment: parameter_scan — design the simulation endpoint-time surface (ADR-0028) #426

@wshlavacek

Description

@wshlavacek

Summary

The new-era experiment: / data: surface (ADR-0028, Chunk 3) currently supports time-course experiments only. A parameter-scan experiment is parsed but rejected with a clear error, because the scan's simulation endpoint time has no home in the experiment grammar. This issue tracks designing that surface.

This is a child of #423 (ADR-0028) and is recorded under that ADR's "Open / deferred".

Why it's deferred (the actual gap)

For a time course, everything the simulation needs comes from the data: the data's time column is the output grid, and its max is the end time. ADR-0028's whole premise — "PyBNF emits simulation commands that output at exactly the data's points" — falls out for free.

A parameter scan breaks that symmetry. The data supplies the swept values (the dose column, col 0), but it does not supply the endpoint time — how long to integrate each dose before reading the observable. A dose-response .exp is (dose, obs1, obs2, …) with no time anywhere. So the data-derived convention has nothing to derive the endpoint from.

The backend plumbing is already in place (ADR-0028 Chunk 3a): ParamScan accepts explicit scan values and BNGLModel.add_action emits par_scan_vals=>[…]; the only missing piece is the authoring surface for the endpoint time.

Current behavior (Chunk 3c)

experiment: dose, type: parameter_scan, data: dose.exp
  → PybnfError: parameter_scan experiments are not yet supported via the new
    'experiment:' surface: the scan's simulation endpoint time has no home in the
    experiment grammar yet (ADR-0028, Open/deferred). Use a legacy 'param_scan' action.

Type is also inferred (a non-time independent variable ⇒ parameter scan), so an experiment whose data's col-0 header is a model parameter hits the same error.

How should we set the endpoint time? (the design question)

Four options, roughly in order of preference:

Option A — default to steady state (recommended), with an optional fixed time

Most dose-response measurements are at steady state, and this is exactly PEtab v2's time = inf convention — so it needs no new endpoint field at all and keeps the data-driven spirit (the data still drives the doses; "when" is just "at equilibrium").

experiment: dose, type: parameter_scan, data: dose.exp          # steady state (default)
experiment: dose, type: parameter_scan, t_end: 500, data: …     # fixed endpoint (opt-in)
  • Maps to parameter_scan({…, steady_state=>1}) on BNG2.pl, the steady-state scan path on bngsim (bngsim_model/scan.py already has _SS_METHOD_PARITY / _SS_METHOD_NEWTON), and a per-dose steadyState() on RoadRunner.
  • Caveat: not every model reaches a steady state, and not every dose-response is measured there — hence the explicit t_end: escape hatch. Steady-state detection robustness differs across backends and would need verification (esp. oscillatory / non-converging models).

Option B — an explicit endpoint field only

Add t_end: (or reuse time:) as a parameter-scan-only experiment field; no implicit default, error if absent. Simplest and unambiguous, matches the legacy param_scan's time: key, but adds a field that's meaningless for a time course and offers no steady-state convenience.

Option C — carry the time in the data (PEtab-aligned)

PEtab puts a time column on every measurement row (with inf ⇒ steady state). The PyBNF-native analog would let a dose-response .exp carry a (constant, or inf) time column alongside the dose column. Most faithful to PEtab and makes export 1:1, but changes the .exp shape for scans and needs a multi-independent-variable reader.

Option D — status quo

Keep parameter scans on the legacy param_scan action; the new-era surface stays time-course-only. Cheapest, but leaves dose-response out of the modern surface and blocks the exporter's dose-response path (#422 / ADR-0027).

Recommendation: Option A (steady-state default + optional t_end:), because it needs no endpoint in the data, matches PEtab's time = inf, and covers the common case; Option B's explicit t_end: is the escape hatch within it. Confirm steady-state scan support + robustness on all three backends as part of the work.

Acceptance

  • A design decision on the endpoint-time surface (the four options above).
  • _load_experiments synthesizes a ParamScan (suffix = experiment name, par_scan_vals = the data's swept values) instead of raising.
  • _load_t_length handles the synthesized scan (mirrors the time-course merge).
  • Recovery-tier coverage for a dose-response fit through the real backend (mirrors test_de_recovers_via_experiment_surface).
  • ADR-0028 "Open / deferred" updated.

Backend note

The endpoint-time choice interacts with backend support for steady-state scans (Option A). Separately, the broader new-era explicit-output-points mechanism (sample_times) is honored by BNG2.pl (all methods) and RoadRunner (cvode + gillespie), but bngsim does not honor sample_times for NFsim (it warns and drops them); bngsim also does not support method=>pla at all. ODE and SSA — the common cases — are fully compatible on all three backends.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions