-
-
Notifications
You must be signed in to change notification settings - Fork 38
SimpleExplicitTauLeaping solver #513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
SimpleExplicitTauLeaping solver #513
Conversation
Rebase onto master |
f266a9d
to
5963f3e
Compare
@sivasathyaseeelan can you make the correctness test more strict. Compare against I’m reading through the references to refresh my memory on how the adaptive stepping works, and will try to finish that tomorrow and give you some more feedback afterwards. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sivasathyaseeelan, to speed up review I suggest splitting this into separate PRs for explicit vs. implicit. Let's get explicit merged and then we can work on implicit.
@ChrisRackauckas I think there are some design decisions to be made here for getting at the individual rate functions and stochiometry vectors. So it is just a question if you want to work on that as part of these or as a followup -- I'd defer to what you want, but I don't think these should be advertised until we get that settled and have more extensive testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General comments:
- Please add comments citing the formulas you are using from the original paper for the leap selection.
- Please revise with an eye towards making this non-allocating during the solution timestepping, see the examples I've highlighted but keep in mind they are not all the cases of extraneous allocations.
@sivasathyaseeelan this PR needs to be updated for the changes I just merged to master. |
b5c55b2
to
7f21b85
Compare
ca51129
to
5415947
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sivasathyaseeelan getting better. I've only reviewed the tau-selection code -- once that seems to be the right algorithm / approach I'll give you a more thorough review.
src/simple_regular_solve.jl
Outdated
function compute_gi(u, reaction_conditions, i) | ||
# Compute g_i for species i to bound the relative change in propensity functions, | ||
# as per Cao et al. (2006), Section IV, equation (27). | ||
# g_i is determined by the highest order of reaction (HOR) where species i is a reactant: | ||
# - HOR = 1 (first-order, e.g., S_i -> products): g_i = 1 | ||
# - HOR = 2 (second-order): | ||
# - nu_ij = 1 (e.g., S_i + S_k -> products): g_i = 2 | ||
# - nu_ij = 2 (e.g., 2S_i -> products): g_i = 2 + 1/(x_i - 1) | ||
# - HOR = 3 (third-order): | ||
# - nu_ij = 1 (e.g., S_i + S_k + S_m -> products): g_i = 3 | ||
# - nu_ij = 2 (e.g., 2S_i + S_k -> products): g_i = (3/2) * (2 + 1/(x_i - 1)) | ||
# - nu_ij = 3 (e.g., 3S_i -> products): g_i = 3 + 1/(x_i - 1) + 2/(x_i - 2) | ||
# Uses precomputed reaction_conditions to optimize checks for HOR = 2 or 3 with nu_ij >= 2. | ||
max_hor = maximum(isempty(reaction_conditions[i]) ? 0 : [hor_j for (j, nu_ij, hor_j) in reaction_conditions[i]]) | ||
max_gi = 1 | ||
for (j, nu_ij, hor_j) in reaction_conditions[i] | ||
if hor_j == max_hor | ||
if hor_j == 1 | ||
max_gi = max(max_gi, 1) | ||
elseif hor_j == 2 | ||
if nu_ij == 1 | ||
max_gi = max(max_gi, 2) | ||
elseif nu_ij == 2 | ||
if u[i] > 1 # Ensure x_i - 1 > 0 | ||
gi = 2 + 1 / (u[i] - 1) | ||
max_gi = max(max_gi, ceil(Int64, gi)) | ||
end | ||
end | ||
elseif hor_j == 3 | ||
if nu_ij == 1 | ||
max_gi = max(max_gi, 3) | ||
elseif nu_ij == 2 | ||
if u[i] > 1 # Ensure x_i - 1 > 0 | ||
gi = 1.5 * (2 + 1 / (u[i] - 1)) | ||
max_gi = max(max_gi, ceil(Int64, gi)) | ||
end | ||
elseif nu_ij == 3 | ||
if u[i] > 2 # Ensure x_i - 2 > 0 | ||
gi = 3 + 1 / (u[i] - 1) + 2 / (u[i] - 2) | ||
max_gi = max(max_gi, ceil(Int64, gi)) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
return max_gi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is getting better, but there are still some issues.
- Just initialize
max_gi = zero(typeof(t))
and get rid of all theseceil
's and conversions to integers. WIth the correction formulasg_i
can be a floating point type so I think it makes sense to just give it the same type as the time variable. - You are currently doing
num_species * num_rxs
work per timestep in computing all theseg_i
, when it should only benum_species
work. You need to precalculate when initializing the solverhor_order
, wherehor_order[i]
is the maximum substrate order of speciesi
over all reactions. Then you use this to determine whichg_i
case you are in. For example, ifHOR(i) == 2
andhor_order[i] == 1
you just setg_i = 2
, but ifHOR(i) == 2
andhor_order[i] == 2
you use the formula that involvesu[i]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now compute_gi
is in O(num_species) time complexity
# HOR is the sum of stoichiometric coefficients of reactants in reaction j. | ||
hor = zeros(Int, numjumps) | ||
for j in 1:numjumps | ||
order = sum(stoch for (spec_idx, stoch) in reactant_stoch[j]; init=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
order = sum(stoch for (spec_idx, stoch) in reactant_stoch[j]; init=0) | |
order = sum(stoch for (spec_idx, stoch) in reactant_stoch[j]) |
sum
can figure this out and avoid type assumptions.
function compute_hor(reactant_stoch, numjumps) | ||
# Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV. | ||
# HOR is the sum of stoichiometric coefficients of reactants in reaction j. | ||
hor = zeros(Int, numjumps) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hor = zeros(Int, numjumps) | |
hor = zeros(Int, numjumps) |
Instead of Int
, extract the type from reactant_stoch
via a parametric input to this function.
function compute_hor(reactant_stoch, numjumps) | ||
# Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV. | ||
# HOR is the sum of stoichiometric coefficients of reactants in reaction j. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function compute_hor(reactant_stoch, numjumps) | |
# Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV. | |
# HOR is the sum of stoichiometric coefficients of reactants in reaction j. | |
# Compute the highest order of reaction (HOR) for each reaction j, as per Cao et al. (2006), Section IV. | |
# HOR is the sum of stoichiometric coefficients of reactants in reaction j. | |
function compute_hor(reactant_stoch, numjumps) |
And please update other functions accordingly. Place these kind of design comments before the function, not right inside it.
max_hor = zeros(Int, numspecies) | ||
max_stoich = zeros(Int, numspecies) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't assume Int
, just match the eltype(hor)
.
function compute_gi(u, max_hor, max_stoich, i, t) | ||
# Compute g_i for species i to bound the relative change in propensity functions, | ||
# as per Cao et al. (2006), Section IV, equation (27). | ||
# g_i is determined by the highest order of reaction (HOR) and maximum stoichiometry (nu_ij) where species i is a reactant: | ||
# - HOR = 1 (first-order, e.g., S_i -> products): g_i = 1 | ||
# - HOR = 2 (second-order): | ||
# - nu_ij = 1 (e.g., S_i + S_k -> products): g_i = 2 | ||
# - nu_ij = 2 (e.g., 2S_i -> products): g_i = 2 + 1/(x_i - 1) | ||
# - HOR = 3 (third-order): | ||
# - nu_ij = 1 (e.g., S_i + S_k + S_m -> products): g_i = 3 | ||
# - nu_ij = 2 (e.g., 2S_i + S_k -> products): g_i = (3/2) * (2 + 1/(x_i - 1)) | ||
# - nu_ij = 3 (e.g., 3S_i -> products): g_i = 3 + 1/(x_i - 1) + 2/(x_i - 2) | ||
# Uses precomputed max_hor and max_stoich to reduce work to O(num_species) per timestep. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function compute_gi(u, max_hor, max_stoich, i, t) | |
# Compute g_i for species i to bound the relative change in propensity functions, | |
# as per Cao et al. (2006), Section IV, equation (27). | |
# g_i is determined by the highest order of reaction (HOR) and maximum stoichiometry (nu_ij) where species i is a reactant: | |
# - HOR = 1 (first-order, e.g., S_i -> products): g_i = 1 | |
# - HOR = 2 (second-order): | |
# - nu_ij = 1 (e.g., S_i + S_k -> products): g_i = 2 | |
# - nu_ij = 2 (e.g., 2S_i -> products): g_i = 2 + 1/(x_i - 1) | |
# - HOR = 3 (third-order): | |
# - nu_ij = 1 (e.g., S_i + S_k + S_m -> products): g_i = 3 | |
# - nu_ij = 2 (e.g., 2S_i + S_k -> products): g_i = (3/2) * (2 + 1/(x_i - 1)) | |
# - nu_ij = 3 (e.g., 3S_i -> products): g_i = 3 + 1/(x_i - 1) + 2/(x_i - 2) | |
# Uses precomputed max_hor and max_stoich to reduce work to O(num_species) per timestep. | |
# Compute g_i for species i to bound the relative change in propensity functions, | |
# as per Cao et al. (2006), Section IV, equation (27). | |
# g_i is determined by the highest order of reaction (HOR) and maximum stoichiometry (nu_ij) where species i is a reactant: | |
# - HOR = 1 (first-order, e.g., S_i -> products): g_i = 1 | |
# - HOR = 2 (second-order): | |
# - nu_ij = 1 (e.g., S_i + S_k -> products): g_i = 2 | |
# - nu_ij = 2 (e.g., 2S_i -> products): g_i = 2 + 1/(x_i - 1) | |
# - HOR = 3 (third-order): | |
# - nu_ij = 1 (e.g., S_i + S_k + S_m -> products): g_i = 3 | |
# - nu_ij = 2 (e.g., 2S_i + S_k -> products): g_i = (3/2) * (2 + 1/(x_i - 1)) | |
# - nu_ij = 3 (e.g., 3S_i -> products): g_i = 3 + 1/(x_i - 1) + 2/(x_i - 2) | |
# Uses precomputed max_hor and max_stoich to reduce work to O(num_species) per timestep. | |
function compute_gi(u, max_hor, max_stoich, i, t) |
max_hor, max_stoich = precompute_reaction_conditions(reactant_stoch, hor, length(u0), numjumps) | ||
|
||
# Set up saveat_times | ||
saveat_times = nothing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
saveat_times = nothing |
Not needed.
|
||
save_idx = 1 | ||
|
||
while t_current < t_end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For type stability reasons, this while loop should probably be a separate function you call.
if !isempty(saveat_times) && save_idx <= length(saveat_times) && t_current + tau > saveat_times[save_idx] | ||
tau = saveat_times[save_idx] - t_current | ||
end | ||
counts .= pois_rand.(rng, max.(rate_cache * tau, 0.0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
counts .= pois_rand.(rng, max.(rate_cache * tau, 0.0)) | |
counts .= pois_rand.(rng, max.(rate_cache * tau, zero(tau))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, if a particular rate is <= 0 why not just set the count to zero directly and avoid the call to pois_rand
for it? That seems a better approach than using max
in this way.
for i in eachindex(u_new) | ||
u_new[i] = max(u_new[i], 0) | ||
end | ||
t_new = t_current + tau |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't the step rejection if any component of u_new
is negative make this unneeded? i.e. at this point don't you know that all the entries in u_new
are non-negative?
end | ||
end | ||
end | ||
u_new = u_current + du |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is allocating... Have separate pre-declatred vectors for u_new
and u_current
and broadcast here.
SimpleExplicitTauLeaping Plot:
Checklist
contributor guidelines, in particular the SciML Style Guide and
COLPRAC.
Additional context
Add any other context about the problem here.