Summary
The gradient optimizers (#386) refuse an unsupported fit with a clean pre-flight PybnfError that points the user at a metaheuristic fit_type. Today that happens in three places (pybnf/algorithms/optimizers/gradient_base.py), plus a per-evaluation objective gate:
- edition —
_require_edition_2 (before any model is built);
- backend —
_require_sensitivity_backend (after model build: the model must expose bngsim's forward-sensitivity hooks);
- capability — deferred to
_setup_gradient_path's apply_routing (the bngsim build must have the output_sensitivities feature);
- objective — a per-evaluation
GradientNotSupported → PybnfError for an objective the assembly cannot differentiate.
#386's deliverable list called for one more — "Capability / gradient-availability / events gate … Refuse to start … when the model has discrete events (BNGsim raises on sensitivity requests there)" — and that one was never implemented. This issue tracks it (it is the discrete-events half of #386 deliverable 5, the remaining unchecked gate).
The gap
A model with discrete events (a state-dependent discrete change in the dynamics) has no smooth forward sensitivity, so bngsim raises when sensitivities are requested for it. Because there is no pre-flight gate, such a model does not get the clean "use a metaheuristic fit_type" refusal the other gates give — instead it fails mid-run, at _setup_gradient_path / the first sensitivity-bearing simulate(), with a backend-level error. The requirement is documented (docs/gradient_fitting.rst: "requires a deterministic ODE simulation of a reaction network … run with method=>\"ode\"") but it is only documented, not enforced up front.
What to build
A new gate in GradientOptimizer (gradient_base.py) — e.g. _require_differentiable_dynamics — fired as early as it can be (after model build, next to _require_sensitivity_backend), that detects a discrete-event model and raises a PybnfError carrying the shared _FALLBACK_HINT. Mirror the existing gate pattern exactly:
def _require_sensitivity_backend(self):
for model in self.model_list:
if not hasattr(model, 'enable_output_sensitivities'):
raise PybnfError("Gradient-based fitting (fit_type = %s) requires the bngsim "
"backend's forward sensitivities, but model '%s' ..." % (...),
_FALLBACK_HINT)
Detection mechanism — TBD / needs confirmation. I did not pin down how a discrete event is represented at the PyBNF/bngsim model layer (an events block? a model attribute exposed on the BngsimModel? something only the bngsim engine knows?). The implementer should confirm where the signal lives; the gate may need a small bngsim-side hook (a model.has_discrete_events / capability query) so the refusal can be made before the run rather than caught from a backend exception. If a pre-flight signal genuinely isn't available, the fallback is to catch the bngsim sensitivity-request exception at _setup_gradient_path and re-raise it as the same clean PybnfError + fallback hint (a late but still actionable gate).
Adjacent (decide in scope)
Non-ODE simulation methods — SSA (stochastic) and NFsim (network-free) — are likewise non-differentiable, and there is no runtime gate for them on the gradient path either (only the documented method=>\"ode\" requirement). Decide whether this gate should be the unified "smooth deterministic ODE" check covering both the events case and the non-ODE-method case, or events-only with the method case tracked separately.
Done criteria
References
Summary
The gradient optimizers (#386) refuse an unsupported fit with a clean pre-flight
PybnfErrorthat points the user at a metaheuristicfit_type. Today that happens in three places (pybnf/algorithms/optimizers/gradient_base.py), plus a per-evaluation objective gate:_require_edition_2(before any model is built);_require_sensitivity_backend(after model build: the model must expose bngsim's forward-sensitivity hooks);_setup_gradient_path'sapply_routing(the bngsim build must have theoutput_sensitivitiesfeature);GradientNotSupported→PybnfErrorfor an objective the assembly cannot differentiate.#386's deliverable list called for one more — "Capability / gradient-availability / events gate … Refuse to start … when the model has discrete events (BNGsim raises on sensitivity requests there)" — and that one was never implemented. This issue tracks it (it is the discrete-events half of #386 deliverable 5, the remaining unchecked gate).
The gap
A model with discrete events (a state-dependent discrete change in the dynamics) has no smooth forward sensitivity, so bngsim raises when sensitivities are requested for it. Because there is no pre-flight gate, such a model does not get the clean "use a metaheuristic fit_type" refusal the other gates give — instead it fails mid-run, at
_setup_gradient_path/ the first sensitivity-bearingsimulate(), with a backend-level error. The requirement is documented (docs/gradient_fitting.rst: "requires a deterministic ODE simulation of a reaction network … run withmethod=>\"ode\"") but it is only documented, not enforced up front.What to build
A new gate in
GradientOptimizer(gradient_base.py) — e.g._require_differentiable_dynamics— fired as early as it can be (after model build, next to_require_sensitivity_backend), that detects a discrete-event model and raises aPybnfErrorcarrying the shared_FALLBACK_HINT. Mirror the existing gate pattern exactly:Detection mechanism — TBD / needs confirmation. I did not pin down how a discrete event is represented at the PyBNF/bngsim model layer (an events block? a model attribute exposed on the
BngsimModel? something only the bngsim engine knows?). The implementer should confirm where the signal lives; the gate may need a small bngsim-side hook (amodel.has_discrete_events/ capability query) so the refusal can be made before the run rather than caught from a backend exception. If a pre-flight signal genuinely isn't available, the fallback is to catch the bngsim sensitivity-request exception at_setup_gradient_pathand re-raise it as the same cleanPybnfError+ fallback hint (a late but still actionable gate).Adjacent (decide in scope)
Non-ODE simulation methods — SSA (stochastic) and NFsim (network-free) — are likewise non-differentiable, and there is no runtime gate for them on the gradient path either (only the documented
method=>\"ode\"requirement). Decide whether this gate should be the unified "smooth deterministic ODE" check covering both the events case and the non-ODE-method case, or events-only with the method case tracked separately.Done criteria
fit_type = trf/lbfgsis refused before the run (or at worst at path activation) with an actionablePybnfErrorpointing at a metaheuristicfit_type— never a raw backend traceback.test_trf_refuses_when_bngsim_lacks_output_sensitivities/test_trf_refuses_non_least_squares_objective_pointing_at_lbfgs) covering the events refusal.gradient_base.py's gate comment block (currently lists three gates) updated to four;docs/gradient_fitting.rstnotes the ODE/no-events requirement is enforced, not just documented.References
pybnf/algorithms/optimizers/gradient_base.py(the# --- gates ---block).docs/gradient_fitting.rst(the edition-2 / deterministic-ODE note).