Skip to content

The sensitivity df/dp path has no #95 derivation budget, so a genome-scale Functional model can hang the build instead of declining #90

Description

@wshlavacek

Summary

#95 bounded the build-time symbolic derivation for the analytical Jacobian:
a model that does not derive within budget falls back to the finite-difference
Jacobian instead of hanging the build. The sensitivity df/dp path added by
#55 (#65/#66/#67/#68) does the same kind of sympy work and has no budget at
all
.

python/bngsim/_codegen.py contains zero references to deadline. Its three
sp.diff call sites are all unguarded:

line function what it differentiates
1564 _derived_expr_partials_numeric a derived-parameter / IC-seed / switch-threshold expression
1679 _compute_derived_param_jacobian a derived rate constant w.r.t. its primaries
5879 _functional_rate_law_partials a Functional rate law w.r.t. every parameter it reads

The Jacobian path, by contrast, has the whole apparatus: _derivation_budget_s(n_species),
the BNGSIM_JAC_DERIV_BUDGET_S override, _DerivationBudgetExceeded, and a
check inside differentiate_rate_law's per-observable loop
(python/bngsim/_jacobian.py:349) so overshoot is bounded to one rate law even
for a pathological single reaction.

#55 itself flagged this in its "What still has to bail" list — "an N-parameter
df/dp multiplies the SymPy work by N. Needs the same budget guard"
— and each
of the four stages deferred it because the corpus never came close.

Why it has not bitten

Measured over all 185 non-Elementary corpus models during #66: 2.6 s total,
0.31 s worst model.
Nowhere near any plausible budget.

Why it will

Line 5879 is the one that scales badly. It runs one sp.diff per (rate law,
parameter it reads) pair, so cost grows with the product of rate-law size and
parameter count — exactly the axis #95 found super-linear for the Jacobian. The
genome-scale models #95 and #187 identify (BIOMD0000000496, 0000000628,
0000000595, MODEL1001200000) derive for 40 s to >1 min for the Jacobian alone,
where the ODE solve is already sub-second.

A model of that size with a Functional rate law and a large
sensitivity_params list would multiply that, and the failure mode is a build
that appears to hang rather than one that declines and says why. Worse for
rr_parity-style harnesses, which time build+solve together, so a slow
derivation reads as an ODE timeout (the #95 symptom).

Suggested fix

Reuse the #95 machinery rather than adding a second budget — the same reasoning
#68 applied to the switch-condition guard:

  1. Thread a deadline through _functional_dfdp_terms ->
    _functional_rate_law_partials, checked before each sp.diff in the
    per-parameter loop, so overshoot is bounded to one (law, parameter) pair.
  2. On _DerivationBudgetExceeded, decline the analytic sensitivity RHS through
    the existing _warn_functional_sens_rhs_refused, so it lands with every
    other decline reason and CVODES' difference quotient answers instead —
    correct, slower, and loud.
  3. Decide whether the budget is shared with the Jacobian's or separate. Shared
    is simpler but means a model that spent its budget on the Jacobian silently
    loses the sensitivity RHS too; separate doubles the worst-case build time.
    Worth settling explicitly rather than by accident.
  4. _derived_expr_partials_numeric and _compute_derived_param_jacobian are
    lower risk (one expression, not N x rate laws) but are on the same path and
    should probably take the same deadline for consistency.

Not in scope

Michaelis-Menten's df/dp (#88) is closed form and calls no sympy at all, so it
adds nothing to this and needs no guard.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions