Maintainer triage (June 29, 2026)
- Status: Verified bug against the current
master source in this fork.
- Severity: High.
- Area labels: area:kinetics.
- Tackle batch: P4 - high-impact numerical/model correctness.
The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: high | Category: kinetics | Location: PharmaPy/Kinetics.py lines 564-598, 603-609
What's wrong
get_rxn_rates computes net rates with the reverse term when keq_params is not None: f_terms = equilibrium_model(conc, temp, delta_hrxn) which returns f_term - r_term (lines 606-607, 529). However, when jac=True (line 596-598) it calls derivatives(conc, temp), and derivatives ALWAYS uses self.kinetic_model (= elem_f_model, forward only) and self.df_dstates (= elem_df_dstates, forward only) at lines 566-571. The reverse contribution -d/dC[ prod(C_prod^beta)/Keq ] is never added to the Jacobian.
Why it's incorrect
The net rate is r = kfprod(C_react^alpha) - (kf/Keq)prod(C_prod^beta). Its derivative wrt concentrations must include the reverse term: dr/dC_k = kfalpha_kf/C_k - (kf/Keq)beta_kg/C_k. The code returns only the first (forward) part, so the analytical state Jacobian disagrees with the actual rate model for any reversible (keq) reaction. Any implicit/Newton integrator or sensitivity analysis using this Jacobian gets an inconsistent (wrong) derivative, degrading convergence and giving incorrect sensitivities.
Suggested regression test
For a reversible reaction A<->B with finite Keq, compare derivatives(conc,temp) against a finite-difference Jacobian of get_rxn_rates(conc,temp,overall_rates=True). They will differ by the omitted reverse-term derivative whenever the reverse rate is nonzero.
Verification: 2/2 adversarial reviewers confirmed (refuted: 0).
Maintainer triage (June 29, 2026)
mastersource in this fork.The source review confirmed that the cited code path and failure mode are still present. The original audit details are preserved below for reproduction notes and suggested fixes.
Original audit details
Part of the model-correctness audit — see #17.
Severity: high | Category: kinetics | Location:
PharmaPy/Kinetics.pylines 564-598, 603-609What's wrong
get_rxn_rates computes net rates with the reverse term when keq_params is not None: f_terms = equilibrium_model(conc, temp, delta_hrxn) which returns f_term - r_term (lines 606-607, 529). However, when jac=True (line 596-598) it calls derivatives(conc, temp), and derivatives ALWAYS uses self.kinetic_model (= elem_f_model, forward only) and self.df_dstates (= elem_df_dstates, forward only) at lines 566-571. The reverse contribution -d/dC[ prod(C_prod^beta)/Keq ] is never added to the Jacobian.
Why it's incorrect
The net rate is r = kfprod(C_react^alpha) - (kf/Keq)prod(C_prod^beta). Its derivative wrt concentrations must include the reverse term: dr/dC_k = kfalpha_kf/C_k - (kf/Keq)beta_kg/C_k. The code returns only the first (forward) part, so the analytical state Jacobian disagrees with the actual rate model for any reversible (keq) reaction. Any implicit/Newton integrator or sensitivity analysis using this Jacobian gets an inconsistent (wrong) derivative, degrading convergence and giving incorrect sensitivities.
Suggested regression test
For a reversible reaction A<->B with finite Keq, compare derivatives(conc,temp) against a finite-difference Jacobian of get_rxn_rates(conc,temp,overall_rates=True). They will differ by the omitted reverse-term derivative whenever the reverse rate is nonzero.
Verification: 2/2 adversarial reviewers confirmed (refuted: 0).