@@ -82,6 +82,8 @@ function DiffEqBase.solve(jump_prob::JumpProblem, alg::SimpleTauLeaping;
8282end
8383
8484function compute_hor (reactant_stoch, numjumps)
85+ # Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV.
86+ # HOR is the sum of stoichiometric coefficients of reactants in reaction j.
8587 hor = zeros (Int, numjumps)
8688 for j in 1 : numjumps
8789 order = sum (stoch for (spec_idx, stoch) in reactant_stoch[j]; init= 0 )
@@ -94,7 +96,9 @@ function compute_hor(reactant_stoch, numjumps)
9496end
9597
9698function precompute_reaction_conditions (reactant_stoch, hor, numspecies, numjumps)
97- # Initialize reaction_conditions as a vector of vectors of tuples
99+ # Precompute reaction conditions for each species i, storing reactions j where i is a reactant,
100+ # along with stoichiometry (nu_ij) and HOR (hor_j), to optimize compute_gi.
101+ # Reactant stoichiometry is used per Cao et al. (2006), Section IV, for g_i calculations.
98102 reaction_conditions = [Vector () for _ in 1 : numspecies]
99103 for j in 1 : numjumps
100104 for (spec_idx, stoch) in reactant_stoch[j]
@@ -107,6 +111,14 @@ function precompute_reaction_conditions(reactant_stoch, hor, numspecies, numjump
107111end
108112
109113function compute_gi (u, reaction_conditions, i)
114+ # Compute g_i for species i, bounding the relative change in propensity functions,
115+ # as per Cao et al. (2006), Section IV (between equations 27-28).
116+ # g_i is the maximum over all reactions j where species i is a reactant:
117+ # - HOR = 1: g_i = 1
118+ # - HOR = 2, nu_ij = 1: g_i = 2
119+ # - HOR = 2, nu_ij >= 2: g_i = x_i * (2/nu_ij + 1/(nu_ij - 1))
120+ # - HOR = 3, nu_ij = 1: g_i = 3
121+ # - HOR = 3, nu_ij >= 2: g_i = 1.5 * x_i * (2/nu_ij + 1/(nu_ij - 1))
110122 max_gi = 1
111123 for (j, nu_ij, hor_j) in reaction_conditions[i]
112124 if hor_j == 1
@@ -131,20 +143,25 @@ function compute_gi(u, reaction_conditions, i)
131143end
132144
133145function compute_tau_explicit (u, rate_cache, nu, p, t, epsilon, rate, dtmin, reaction_conditions, numjumps)
146+ # Compute the tau-leaping step-size using equation (8) from Cao et al. (2006):
147+ # 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) }
148+ # where mu_i(x) and sigma_i^2(x) are defined in equations (9a) and (9b):
149+ # mu_i(x) = sum_j nu_ij * a_j(x), sigma_i^2(x) = sum_j nu_ij^2 * a_j(x)
150+ # I_rs is the set of reactant species (assumed to be all species here, as critical reactions are not specified).
134151 rate (rate_cache, u, p, t)
135152 tau = Inf
136153 for i in 1 : length (u)
137154 mu = zero (eltype (u))
138155 sigma2 = zero (eltype (u))
139156 for j in 1 : size (nu, 2 )
140- mu += nu[i, j] * rate_cache[j]
141- sigma2 += nu[i, j]^ 2 * rate_cache[j]
157+ mu += nu[i, j] * rate_cache[j] # Equation (9a)
158+ sigma2 += nu[i, j]^ 2 * rate_cache[j] # Equation (9b)
142159 end
143160 gi = compute_gi (u, reaction_conditions, i)
144- bound = max (epsilon * u[i] / gi, 1.0 )
145- mu_term = abs (mu) > 0 ? bound / abs (mu) : Inf
146- sigma_term = sigma2 > 0 ? bound^ 2 / sigma2 : Inf
147- tau = min (tau, mu_term, sigma_term)
161+ bound = max (epsilon * u[i] / gi, 1.0 ) # max(epsilon * x_i / g_i, 1)
162+ mu_term = abs (mu) > 0 ? bound / abs (mu) : Inf # First term in equation (8)
163+ sigma_term = sigma2 > 0 ? bound^ 2 / sigma2 : Inf # Second term in equation (8)
164+ tau = min (tau, mu_term, sigma_term) # Equation (8)
148165 end
149166 return max (tau, dtmin)
150167end
@@ -229,7 +246,7 @@ function DiffEqBase.solve(jump_prob::JumpProblem, alg::SimpleAdaptiveTauLeaping;
229246 end
230247 u_new = u_current + du
231248 if any (< (0 ), u_new)
232- # Halve tau to avoid negative populations, as per Cao et al. (2006, J. Chem. Phys., DOI: 10.1063/1.2159468)
249+ # Halve tau to avoid negative populations, as per Cao et al. (2006), Section 3.3
233250 tau /= 2
234251 continue
235252 end
0 commit comments