From 88b838ccc5490f07f01dc8d5798b03cd3f7789d2 Mon Sep 17 00:00:00 2001 From: Jim Schaff Date: Thu, 30 Apr 2026 01:42:23 -0400 Subject: [PATCH] Fix NPE in StochMathMapping.getProbabilityRate for symbol-less rate expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Expression.getSymbols()` is documented to return null when the expression has no free symbols (see Expression.java:545). The general-kinetics path in StochMathMapping was iterating over the result with an unguarded for-each, producing an NPE whose corpus signature is: Cannot read the array length because "" is null at cbit.vcell.mapping.StochMathMapping.getProbabilityRate (StochMathMapping.java:145) This trips when a non-mass-action rate expression flattens to a constant or otherwise contains no free symbols (e.g. a Michaelis-Menten rate where all parameters and species have already been resolved upstream). The fix mirrors the established null-guard pattern already used in AbstractStochMathMapping.getSubstitutedExpr (line 155). The general-kinetics fallback in StochasticTransformer is the intended path for non-mass-action kinetics — it takes the authoritative reaction rate as a net flux and uses max(rate, 0) as the irreversible forward rate, which is exactly the right shape for stochastic simulation. The NPE was preventing that path from completing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/java/cbit/vcell/mapping/StochMathMapping.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vcell-core/src/main/java/cbit/vcell/mapping/StochMathMapping.java b/vcell-core/src/main/java/cbit/vcell/mapping/StochMathMapping.java index a332ecbbcb..aeea1906f6 100644 --- a/vcell-core/src/main/java/cbit/vcell/mapping/StochMathMapping.java +++ b/vcell-core/src/main/java/cbit/vcell/mapping/StochMathMapping.java @@ -142,7 +142,11 @@ private Expression getProbabilityRate(ReactionStep reactionStep, GeneralKinetics // collect symbolTableEntries for speciesContexts within netRateExpr and replace with concentration parameter netRateExpr = new Expression(netRateExpr); - for (String symbol : netRateExpr.getSymbols()) { + // Expression.getSymbols() returns null when the expression has no free symbols + // (e.g. a constant rate, or a rate that only references already-resolved values). + String[] symbols = netRateExpr.getSymbols(); + for (int i = 0; symbols != null && i < symbols.length; i++) { + String symbol = symbols[i]; SymbolTableEntry symbolTableEntry = netRateExpr.getSymbolBinding(symbol); if (symbolTableEntry instanceof Kinetics.KineticsProxyParameter proxyParameter && proxyParameter.getTarget() instanceof SpeciesContext sc) {