Summary
The bngsim SBML backend warms its cached engine template with a sensitivity Simulator (#543, _warm_engine_template in pybnf/bngsim_sbml_model.py). Every per-action clone then inherits that compiled sensitivity RHS, because the action's own Simulator does not ask for codegen=True. If an action then writes a derived parameter on the clone, one whose value bngsim computes from other parameters (an SBML parameter set by an initialAssignment, for instance), the inherited sensitivity RHS keeps the chain rule it was compiled with, and bngsim does not check (lanl/bngsim#708). The gradient would then be wrong, with no error.
This is not measured here; it needs checking. #937 measured and fixed exactly this on the .net backend: on k2 = 2*k1 with k2 pinned by a condition, inheriting gave max|dA/dk1| = 1.43 against a closed form of 0. There, every sensitivity construction now passes codegen=True. #937 listed the SBML backend under "Not examined".
The path
The warm's docstring reports tensors "bit-identical either way" on the Smith and yeast models. Those checks did not include a condition or fitted value that overrides a derived parameter, which is the only case #708 affects.
How to check
A positive control in the shape of #937's test_a_sensitivity_run_is_not_served_a_stale_chain_rule: an SBML model with k2 set by an initialAssignment 2*k1, driving A -> B. Add a condition (or fitted value) that pins k2, then compare the gradient d(A)/d(k1) under that condition against finite differences of an independent integration (scipy), with and without the warm.
One complication: #865 drops a condition that sets an initialAssignment target whenever a free parameter feeds an initialAssignment, which may mask this in some configurations. The check should cover a configuration #865 does not drop, or run after #865 is fixed.
If affected
Do what #937 did: pass codegen=True on every sensitivity construction, accepting bngsim's structural cache-key cost per action (lanl/bngsim#820 would make that cheap). Alternatively, wait for lanl/bngsim#708's fix (recompute and compare the key on artifact reuse). The plain-run warm is unaffected either way: a plain RHS reads parameter values at run time.
Summary
The bngsim SBML backend warms its cached engine template with a sensitivity
Simulator(#543,_warm_engine_templateinpybnf/bngsim_sbml_model.py). Every per-action clone then inherits that compiled sensitivity RHS, because the action's ownSimulatordoes not ask forcodegen=True. If an action then writes a derived parameter on the clone, one whose value bngsim computes from other parameters (an SBML parameter set by aninitialAssignment, for instance), the inherited sensitivity RHS keeps the chain rule it was compiled with, and bngsim does not check (lanl/bngsim#708). The gradient would then be wrong, with no error.This is not measured here; it needs checking. #937 measured and fixed exactly this on the
.netbackend: onk2 = 2*k1withk2pinned by a condition, inheriting gavemax|dA/dk1| = 1.43against a closed form of 0. There, every sensitivity construction now passescodegen=True. #937 listed the SBML backend under "Not examined".The path
_warm_engine_templatebuildsbngsim.Simulator(template, method='ode', **sensitivity_kwargs)on the process-cached template once, so the template carries a sensitivity artifact (_template_is_warm).Simulatorwithoutcodegen=True, so bngsim reuses the inherited artifact (sensitivity artifacts are reusable across sensitivity runs; only a plain one is refused, A plain ODE Simulator() derives, emits and compiles the ∂f/∂p sensitivity RHS it never calls: +17.5s and a 14.6x larger .so on BIOMD0000000496 bngsim#209)._set_engine_value_if_presentcallsengine_model.set_param(name, value)on the clone for every global parameter a fitted value or a condition targets, derived ones included.The warm's docstring reports tensors "bit-identical either way" on the Smith and yeast models. Those checks did not include a condition or fitted value that overrides a derived parameter, which is the only case #708 affects.
How to check
A positive control in the shape of #937's
test_a_sensitivity_run_is_not_served_a_stale_chain_rule: an SBML model withk2set by aninitialAssignment2*k1, drivingA -> B. Add a condition (or fitted value) that pinsk2, then compare the gradient d(A)/d(k1) under that condition against finite differences of an independent integration (scipy), with and without the warm.One complication: #865 drops a condition that sets an
initialAssignmenttarget whenever a free parameter feeds an initialAssignment, which may mask this in some configurations. The check should cover a configuration #865 does not drop, or run after #865 is fixed.If affected
Do what #937 did: pass
codegen=Trueon every sensitivity construction, accepting bngsim's structural cache-key cost per action (lanl/bngsim#820 would make that cheap). Alternatively, wait for lanl/bngsim#708's fix (recompute and compare the key on artifact reuse). The plain-run warm is unaffected either way: a plain RHS reads parameter values at run time.