Rate kernel returns a propensity of 0.0 for an unresolvable rate parameter, silently killing the reaction (extends #580 to src/model.cpp)

Open Beginner friendly
#589 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
84/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
cpp
Domain
backend

Research direction

Start in src/model.cpp at the Elementary/Functional and Michaelis-Menten/sQSSA guards in compute_reaction_rate(), then review the existing exception-translation boundary and ModelBuilder usage. Construct malformed rate-parameter state through ModelBuilder and verify that each invalid index raises an exception carrying the reaction and parameter indexes instead of returning zero.

Written by the indexing model from the issue text.

Description

correctness concern latent bug low priority

Summary

compute_reaction_rate() in src/model.cpp — the innermost propensity kernel, called for every reaction at every step of every ODE and SSA run — responds to an unresolvable rate parameter by returning a rate of 0.0.

src/model.cpp:2876-2877, the Elementary/Functional path:

int k_idx = rxn.rate_param_idx0; // pre-computed 0-based index
if (k_idx < 0 || k_idx >= static_cast<int>(params.size()))
    return 0.0;

src/model.cpp:2936-2941, the Michaelis-Menten / sQSSA path:

if (rxn.rate_law_param_indices.size() < 2)
    return 0.0;
int kcat_idx = rxn.rate_law_param_indices[0] - 1;
int km_idx = rxn.rate_law_param_indices[1] - 1;
if (kcat_idx < 0 || km_idx < 0)
    return 0.0;

A propensity of zero is not an error signal — it is a perfectly ordinary value meaning "this reaction does not fire right now". So if either guard ever fires, the model integrates with that reaction permanently dead, the run completes, the trajectory is well-formed, and nothing anywhere reports it.

Severity and honest scoping

These are guard clauses on paths that well-formed input should not reach — a reaction whose rate-parameter index is out of range indicates the model was built wrong upstream, not a user error. I have not demonstrated a reachable input that triggers either one, and they may well be unreachable today.

That is exactly why this is filed as an improvement rather than as a bug, and why it is low priority relative to the reachable defects in #553–#578. The argument for fixing it anyway is cheapness and blast radius: if one of these ever does become reachable — through a loader change, a new rate-law type, or a refactor of index assignment — the symptom is a silently wrong trajectory in the hottest path in the simulator, and there is no diagnostic trail back to the cause.

Proposed work

Turn each guard into a throw carrying the reaction index and the offending parameter index. An exception here is safe: the call sites are already inside the C++ exception-translation boundary, and a model that reaches one of these states cannot produce a meaningful trajectory anyway.

Roughly an hour, including tests that construct the malformed state through ModelBuilder and assert the throw.

Related

#580 — same policy, different sites (_net_reader.py:227, net_file_loader.cpp:237, ssa_simulator.cpp:1523). This issue extends that rule to the rate kernel. _exceptions.py:68's documented refuse-don't-default contract is the stated policy these sites contradict.

Dominant language
Python
Stars
2
Forks
3
Avg merge
6h 57m
Merged PRs (30d)
75

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from lanl/bngsim

All issues in lanl/bngsim

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.