Model.set_param updates the parameter but leaves any species initial condition that references it at its network-generation value. get_state() returns the stale amount, and reset() does not rebuild it either.
The dependency is already recorded and the invalidation flag already exists — NetworkModel.species_ic_param_refs names the (species, parameter) pair, and NetworkModel.ic_state_dirty stays False across the set_param call. So the machinery to invalidate appears to be in place and simply does not fire.
This is the network-path counterpart of #44, which fixed the same class of defect (a set_param that does not re-derive seed-species amounts) on the network-free path.
Reproducer
Self-contained, two species:
cat > minimal.net <<'EOF'
begin parameters
1 Stot 100
2 k 1
end parameters
begin species
1 A() Stot
2 B() 0
end species
begin reactions
1 1 2 k #A->B
end reactions
begin groups
1 Obs_A 1
end groups
EOF
import bngsim
m = bngsim.Model.from_net("minimal.net")
print(m._core.species_ic_param_refs) # [(0, 0)] species 0's IC references parameter 0
print(m._core.ic_state_dirty) # False
print(m.get_state()[0]) # 100.0
m.set_param("Stot", 1e6)
print(m.get_param("Stot")) # 1000000.0 <- parameter did move
print(m._core.ic_state_dirty) # False <- never marked dirty
print(m.get_state()[0]) # 100.0 <- expected 1000000.0
m.reset()
print(m.get_state()[0]) # 100.0 <- reset does not rebuild it either
|
expected |
actual |
get_param("Stot") |
1e6 |
1e6 ✓ |
get_state()[0] |
1e6 |
100.0 ✗ |
get_state()[0] after reset() |
1e6 |
100.0 ✗ |
ic_state_dirty after set_param |
True |
False ✗ |
On a published model
Same behaviour on a real network — scaffolded_mapk_cascade_kocieniewski2012 (85 species), where Stot is parameter 5 and species 3's initial condition (Scaff(map2k,map3k,mapk) Stot), and species_ic_param_refs reads [(3, 5)]:
m = bngsim.Model.from_net("B02_Kocieniewski_2012.net")
m.get_state()[3] # 100000.0
m.set_param("Stot", 1e6)
m.get_state()[3] # 100000.0 <- expected 1e6
Why it matters
A dose scan over a total amount — the common case, since a "total" is exactly the kind of parameter a species initial condition is written in terms of — silently returns identical results at every dose, with no error and no warning. Nothing in the API surface indicates the scan did not take effect: get_param confirms the new value.
It is also a cross-engine trap. AMICI, reading the same system through SBML initialAssignment, moves correctly, so a comparison scanning such a parameter disagrees for a reason that has nothing to do with the solver under test. Found while building a cross-engine steady-state dose scan, where it affected 3 of 6 published models (Stot, Rtot, APCtot) and had to be worked around by rewriting the parameter in the .net and reloading.
Version
bngsim 0.11.35 (build v0.11.35-72-g201836f), macOS 15 / darwin 24.6.0, Python 3.12.
Suggested fix
Have set_param / set_params consult species_ic_param_refs and set ic_state_dirty when the written parameter appears in it, so the existing rebuild path runs. Worth deciding explicitly whether the IC rebuild happens eagerly on set_param or lazily at the next get_state()/reset()/solve; the lazy form matches what the dirty flag implies. reset() in particular should rebuild from current parameter values rather than restore a snapshot taken at load time.
Model.set_paramupdates the parameter but leaves any species initial condition that references it at its network-generation value.get_state()returns the stale amount, andreset()does not rebuild it either.The dependency is already recorded and the invalidation flag already exists —
NetworkModel.species_ic_param_refsnames the (species, parameter) pair, andNetworkModel.ic_state_dirtystaysFalseacross theset_paramcall. So the machinery to invalidate appears to be in place and simply does not fire.This is the network-path counterpart of #44, which fixed the same class of defect (a
set_paramthat does not re-derive seed-species amounts) on the network-free path.Reproducer
Self-contained, two species:
get_param("Stot")get_state()[0]get_state()[0]afterreset()ic_state_dirtyafterset_paramTrueFalse✗On a published model
Same behaviour on a real network —
scaffolded_mapk_cascade_kocieniewski2012(85 species), whereStotis parameter 5 and species 3's initial condition (Scaff(map2k,map3k,mapk) Stot), andspecies_ic_param_refsreads[(3, 5)]:Why it matters
A dose scan over a total amount — the common case, since a "total" is exactly the kind of parameter a species initial condition is written in terms of — silently returns identical results at every dose, with no error and no warning. Nothing in the API surface indicates the scan did not take effect:
get_paramconfirms the new value.It is also a cross-engine trap. AMICI, reading the same system through SBML
initialAssignment, moves correctly, so a comparison scanning such a parameter disagrees for a reason that has nothing to do with the solver under test. Found while building a cross-engine steady-state dose scan, where it affected 3 of 6 published models (Stot,Rtot,APCtot) and had to be worked around by rewriting the parameter in the.netand reloading.Version
bngsim 0.11.35(buildv0.11.35-72-g201836f), macOS 15 / darwin 24.6.0, Python 3.12.Suggested fix
Have
set_param/set_paramsconsultspecies_ic_param_refsand setic_state_dirtywhen the written parameter appears in it, so the existing rebuild path runs. Worth deciding explicitly whether the IC rebuild happens eagerly onset_paramor lazily at the nextget_state()/reset()/solve; the lazy form matches what the dirty flag implies.reset()in particular should rebuild from current parameter values rather than restore a snapshot taken at load time.