Summary
On a gradient-based fit (fit_type = trf / lbfgs), PyBNF enables a sensitivity request on the whole model, and any action that cannot carry forward sensitivities aborts the entire fit — even when that action's output is not a scored gradient target.
Triggers (both raised during model.execute())
- Carried-state scan (
simulate() → parameter_scan() with no reset between them) → PybnfError at pybnf/bngsim_model/net_model.py:1178 (_scan_carried_state).
- Non-ODE-method action (
ssa / psa / nfsim) → PybnfError at pybnf/bngsim_model/net_model.py:309-316 (_sensitivity_request_kwargs).
Both guards fire whenever self._sensitivity_request is not None — i.e. on every gradient fit — regardless of whether the offending action's output is actually scored against data.
Why this is a bug
A model whose fit target is a differentiable ODE time course, but which also carries an incidental action (a stochastic diagnostic simulate, or a pre-equilibrated simulate→parameter_scan block) that is never scored, cannot be gradient-fit at all. The unscored action needs no sensitivities, yet it aborts the run.
This also contradicts the module's own documented intent in pybnf/algorithms/optimizers/gradient_base.py:320-328:
a non-ODE simulation method (SSA / NFsim) is likewise non-differentiable, but the method is an action-level property (a model can mix actions) rather than a model-structure one, so it is not hoisted here; it keeps its existing clean per-evaluation refusal
The intent is that only sensitivity-bearing (scored) actions should require differentiability. The implementation over-reaches by forcing a sensitivity request onto every action in the model.
Proposed fix
Gate sensitivity-enabling per action on whether that action's output is a scored gradient target. The scoring↔action mapping is already available — gradient_base.py:447-452 intersects res.simdata with exp_data during gradient assembly. Run incidental / unscored non-differentiable actions on the ordinary path with sensitivities off, so they neither compute wasted sensitivity tensors nor abort the fit.
Reproduction sketch
Edition-2 gradient fit (fit_type = trf or lbfgs). Model has:
- one scored ODE
simulate action mapped to time-course data, plus
- one unscored action that is either non-ODE (
nfsim / ssa) or a carried-state simulate→parameter_scan block.
The fit aborts at the first evaluation with the PybnfError from one of the guards above, even though the scored objective is fully differentiable.
Summary
On a gradient-based fit (
fit_type = trf/lbfgs), PyBNF enables a sensitivity request on the whole model, and any action that cannot carry forward sensitivities aborts the entire fit — even when that action's output is not a scored gradient target.Triggers (both raised during
model.execute())simulate()→parameter_scan()with no reset between them) →PybnfErroratpybnf/bngsim_model/net_model.py:1178(_scan_carried_state).ssa/psa/nfsim) →PybnfErroratpybnf/bngsim_model/net_model.py:309-316(_sensitivity_request_kwargs).Both guards fire whenever
self._sensitivity_request is not None— i.e. on every gradient fit — regardless of whether the offending action's output is actually scored against data.Why this is a bug
A model whose fit target is a differentiable ODE time course, but which also carries an incidental action (a stochastic diagnostic
simulate, or a pre-equilibratedsimulate→parameter_scanblock) that is never scored, cannot be gradient-fit at all. The unscored action needs no sensitivities, yet it aborts the run.This also contradicts the module's own documented intent in
pybnf/algorithms/optimizers/gradient_base.py:320-328:The intent is that only sensitivity-bearing (scored) actions should require differentiability. The implementation over-reaches by forcing a sensitivity request onto every action in the model.
Proposed fix
Gate sensitivity-enabling per action on whether that action's output is a scored gradient target. The scoring↔action mapping is already available —
gradient_base.py:447-452intersectsres.simdatawithexp_dataduring gradient assembly. Run incidental / unscored non-differentiable actions on the ordinary path with sensitivities off, so they neither compute wasted sensitivity tensors nor abort the fit.Reproduction sketch
Edition-2 gradient fit (
fit_type = trforlbfgs). Model has:simulateaction mapped to time-course data, plusnfsim/ssa) or a carried-statesimulate→parameter_scanblock.The fit aborts at the first evaluation with the
PybnfErrorfrom one of the guards above, even though the scored objective is fully differentiable.