@@ -81,6 +81,8 @@ function DiffEqBase.solve(jump_prob::JumpProblem, alg::SimpleTauLeaping;
8181end
8282
8383function compute_hor (reactant_stoch, numjumps)
84+ # Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV.
85+ # HOR is the sum of stoichiometric coefficients of reactants in reaction j.
8486 hor = zeros (Int, numjumps)
8587 for j in 1 : numjumps
8688 order = sum (stoch for (spec_idx, stoch) in reactant_stoch[j]; init= 0 )
@@ -93,7 +95,9 @@ function compute_hor(reactant_stoch, numjumps)
9395end
9496
9597function precompute_reaction_conditions (reactant_stoch, hor, numspecies, numjumps)
96- # Initialize reaction_conditions as a vector of vectors of tuples
98+ # Precompute reaction conditions for each species i, storing reactions j where i is a reactant,
99+ # along with stoichiometry (nu_ij) and HOR (hor_j), to optimize compute_gi.
100+ # Reactant stoichiometry is used per Cao et al. (2006), Section IV, for g_i calculations.
97101 reaction_conditions = [Vector () for _ in 1 : numspecies]
98102 for j in 1 : numjumps
99103 for (spec_idx, stoch) in reactant_stoch[j]
@@ -106,6 +110,14 @@ function precompute_reaction_conditions(reactant_stoch, hor, numspecies, numjump
106110end
107111
108112function compute_gi (u, reaction_conditions, i)
113+ # Compute g_i for species i, bounding the relative change in propensity functions,
114+ # as per Cao et al. (2006), Section IV (between equations 27-28).
115+ # g_i is the maximum over all reactions j where species i is a reactant:
116+ # - HOR = 1: g_i = 1
117+ # - HOR = 2, nu_ij = 1: g_i = 2
118+ # - HOR = 2, nu_ij >= 2: g_i = x_i * (2/nu_ij + 1/(nu_ij - 1))
119+ # - HOR = 3, nu_ij = 1: g_i = 3
120+ # - HOR = 3, nu_ij >= 2: g_i = 1.5 * x_i * (2/nu_ij + 1/(nu_ij - 1))
109121 max_gi = 1
110122 for (j, nu_ij, hor_j) in reaction_conditions[i]
111123 if hor_j == 1
@@ -130,20 +142,25 @@ function compute_gi(u, reaction_conditions, i)
130142end
131143
132144function compute_tau_explicit (u, rate_cache, nu, p, t, epsilon, rate, dtmin, reaction_conditions, numjumps)
145+ # Compute the tau-leaping step-size using equation (8) from Cao et al. (2006):
146+ # tau = min_{i in I_rs} { max(epsilon * x_i / g_i, 1) / |mu_i(x)|, max(epsilon * x_i / g_i, 1)^2 / sigma_i^2(x) }
147+ # where mu_i(x) and sigma_i^2(x) are defined in equations (9a) and (9b):
148+ # mu_i(x) = sum_j nu_ij * a_j(x), sigma_i^2(x) = sum_j nu_ij^2 * a_j(x)
149+ # I_rs is the set of reactant species (assumed to be all species here, as critical reactions are not specified).
133150 rate (rate_cache, u, p, t)
134151 tau = Inf
135152 for i in 1 : length (u)
136153 mu = zero (eltype (u))
137154 sigma2 = zero (eltype (u))
138155 for j in 1 : size (nu, 2 )
139- mu += nu[i, j] * rate_cache[j]
140- sigma2 += nu[i, j]^ 2 * rate_cache[j]
156+ mu += nu[i, j] * rate_cache[j] # Equation (9a)
157+ sigma2 += nu[i, j]^ 2 * rate_cache[j] # Equation (9b)
141158 end
142159 gi = compute_gi (u, reaction_conditions, i)
143- bound = max (epsilon * u[i] / gi, 1.0 )
144- mu_term = abs (mu) > 0 ? bound / abs (mu) : Inf
145- sigma_term = sigma2 > 0 ? bound^ 2 / sigma2 : Inf
146- tau = min (tau, mu_term, sigma_term)
160+ bound = max (epsilon * u[i] / gi, 1.0 ) # max(epsilon * x_i / g_i, 1)
161+ mu_term = abs (mu) > 0 ? bound / abs (mu) : Inf # First term in equation (8)
162+ sigma_term = sigma2 > 0 ? bound^ 2 / sigma2 : Inf # Second term in equation (8)
163+ tau = min (tau, mu_term, sigma_term) # Equation (8)
147164 end
148165 return max (tau, dtmin)
149166end
@@ -228,7 +245,7 @@ function DiffEqBase.solve(jump_prob::JumpProblem, alg::SimpleAdaptiveTauLeaping;
228245 end
229246 u_new = u_current + du
230247 if any (< (0 ), u_new)
231- # Halve tau to avoid negative populations, as per Cao et al. (2006, J. Chem. Phys., DOI: 10.1063/1.2159468)
248+ # Halve tau to avoid negative populations, as per Cao et al. (2006), Section 3.3
232249 tau /= 2
233250 continue
234251 end
0 commit comments