Skip to content

Commit

Permalink
Merge branch 'master' into api_doc_proposed_changes
Browse files Browse the repository at this point in the history
  • Loading branch information
TorkelE committed Jun 1, 2024
2 parents c54a093 + 71e9563 commit 2754419
Show file tree
Hide file tree
Showing 11 changed files with 476 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@latest
with:
version: '1'
version: '1.10.2'
- name: Install dependencies
run: julia --project=docs/ -e 'ENV["JULIA_PKG_SERVER"] = ""; using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c"
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634"
Expand Down
14 changes: 6 additions & 8 deletions docs/pages.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,30 @@ pages = Any[
# Events.
#"model_creation/parametric_stoichiometry.md",# Distributed parameters, rates, and initial conditions.
# Loading and writing models to files.
"model_creation/model_visualisation.md",
#"model_creation/model_visualisation.md",
#"model_creation/network_analysis.md",
"model_creation/chemistry_related_functionality.md",
"Model creation examples" => Any[
"model_creation/examples/basic_CRN_library.md",
#"model_creation/examples/basic_CRN_library.md",
"model_creation/examples/programmatic_generative_linear_pathway.md",
#"model_creation/examples/hodgkin_huxley_equation.md",
#"model_creation/examples/smoluchowski_coagulation_equation.md"
]
],
"Model simulation" => Any[
"model_simulation/simulation_introduction.md",
# Simulation introduction.
"model_simulation/simulation_plotting.md",
"model_simulation/simulation_structure_interfacing.md",
"model_simulation/ensemble_simulations.md",
#"model_simulation/ensemble_simulations.md",
# Stochastic simulation statistical analysis.
"model_simulation/ode_simulation_performance.md",
# ODE Performance considerations/advice.
# SDE Performance considerations/advice.
# Jump Performance considerations/advice.
# Finite state projection
],
"Steady state analysis" => Any[
"steady_state_functionality/homotopy_continuation.md",
"steady_state_functionality/nonlinear_solve.md",
#"steady_state_functionality/nonlinear_solve.md",
"steady_state_functionality/steady_state_stability_computation.md",
"steady_state_functionality/bifurcation_diagrams.md",
"steady_state_functionality/dynamical_systems.md"
Expand All @@ -51,7 +49,7 @@ pages = Any[
# ODE parameter fitting using Turing.
# SDE/Jump fitting.
"inverse_problems/behaviour_optimisation.md",
"inverse_problems/structural_identifiability.md",
#"inverse_problems/structural_identifiability.md", # Broken on Julia v1.10.3, requires v1.10.2 or 1.10.4.
# Practical identifiability.
"inverse_problems/global_sensitivity_analysis.md",
"Inverse problem examples" => Any[
Expand All @@ -69,4 +67,4 @@ pages = Any[
# ],
#"FAQs" => "faqs.md",
"API" => "api.md"
]
]
1 change: 1 addition & 0 deletions docs/src/assets/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DiffEqParamEstim = "1130ab10-4a5a-5621-a13d-e4788d82bd4c"
DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634"
Expand Down
7 changes: 5 additions & 2 deletions docs/src/model_simulation/simulation_introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ Next, let us consider a simulation for another parameter set:
```@example simulation_intro_sde
sprob = remake(sprob; u0 = [:X1 => 100.0, :X2 => 200.0], p = [:k1 => 200.0, :k2 => 500.0])
sol = solve(sprob, STrapezoid())
nothing # hide
```
```@example simulation_intro_sde
sol = solve(sprob, STrapezoid(); seed = 12345) # hide
plot(sol)
```
Expand Down Expand Up @@ -317,9 +320,9 @@ plot(sol)
### [Designating aggregators and simulation methods for jump simulations](@id simulation_intro_jumps_solver_designation)
Jump simulations (just like ODEs and SDEs) are performed using solver methods. Unlike ODEs and SDEs, jump simulations are carried out by two different types of methods acting in tandem. First, an *aggregator* method is used to (after each reaction) determine the time to, and type of, the next reaction. Next, a simulation method is used to actually carry out the simulation.

Several different aggregators are available (a full list is provided [here](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-for-Exact-Simulation)). To designate a specific one, provide it as the third argument to the `JumpProblem`. E.g. to designate that Gillespie's direct method (`Direct`) should be used, use:
Several different aggregators are available (a full list is provided [here](https://docs.sciml.ai/JumpProcesses/stable/jump_types/#Jump-Aggregators-for-Exact-Simulation)). To designate a specific one, provide it as the third argument to the `JumpProblem`. E.g. to designate that the sorting direct method (`SortingDirect`) should be used, use:
```@example simulation_intro_jumps
jprob = JumpProblem(two_state_model, dprob, Direct())
jprob = JumpProblem(two_state_model, dprob, SortingDirect())
nothing # hide
```
Especially for large systems, the choice of aggregator is relevant to simulation performance. A guide for aggregator selection is provided [here](@ref ref).
Expand Down
8 changes: 4 additions & 4 deletions docs/src/steady_state_functionality/bifurcation_diagrams.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ nothing # hide

Finally, we compute our bifurcation diagram using:
```@example ex1
bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
nothing # hide
```
Where `PALC()` designates that we wish to use the pseudo arclength continuation method to track our solution. The third argument (`2`) designates the maximum number of recursions when branches of branches are computed (branches appear as continuation encounters certain bifurcation points). For diagrams with highly branched structures (rare for many common small chemical reaction networks) this input is important. Finally, `bothside = true` designates that we wish to perform continuation on both sides of the initial point (which is typically the case).
Expand All @@ -69,7 +69,7 @@ opt_newton = NewtonPar(tol = 1e-9, max_iterations = 1000)
opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2],
dsmin = 0.001, dsmax = 0.01, max_steps = 1000,
newton_options = opt_newton)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside=true)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
nothing # hide
```
(however, in this case these additional settings have no significant effect on the result)
Expand All @@ -79,7 +79,7 @@ Let's consider the previous case, but instead compute the bifurcation diagram ov
```@example ex1
p_span = (2.0, 15.0)
opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br= true)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
plot(bif_dia; xguide = "k1", yguide = "X")
```
Here, in the bistable region, we only see a single branch. The reason is that the continuation algorithm starts at our initial guess (here made at $k1 = 4.0$ for $(X,Y) = (5.0,2.0)$) and tracks the diagram from there. However, with the upper bound set at $k1=15.0$ the bifurcation diagram has a disjoint branch structure, preventing the full diagram from being computed by continuation alone. In this case it could be solved by increasing the bound from $k1=15.0$, however, this is not possible in all cases. In these cases, *deflation* can be used. This is described in the [BifurcationKit documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/dev/tutorials/tutorials2/#Snaking-computed-with-deflation).
Expand All @@ -103,7 +103,7 @@ bprob = BifurcationProblem(kinase_model, u_guess, p_start, :d; plot_var = :Xp, u
p_span = (0.1, 10.0)
opts_br = ContinuationPar(p_min = p_span[1], p_max = p_span[2], max_steps = 1000)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, opts_br; bothside = true)
bif_dia = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside = true)
plot(bif_dia; xguide = "d", yguide = "Xp")
```
This bifurcation diagram does not contain any interesting features (such as bifurcation points), and only shows how the steady state concentration of $Xp$ is reduced as $d$ increases.
Expand Down
15 changes: 8 additions & 7 deletions src/Catalyst.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ module Catalyst
using DocStringExtensions
using SparseArrays, DiffEqBase, Reexport, Setfield
using LaTeXStrings, Latexify, Requires
using JumpProcesses: JumpProcesses,
JumpProblem, MassActionJump, ConstantRateJump,
VariableRateJump
using JumpProcesses: JumpProcesses, JumpProblem,
MassActionJump, ConstantRateJump, VariableRateJump,
SpatialMassActionJump

# ModelingToolkit imports and convenience functions we use
using ModelingToolkit
Expand Down Expand Up @@ -165,20 +165,21 @@ export make_si_ode

### Spatial Reaction Networks ###

# spatial reactions
# Spatial reactions.
include("spatial_reaction_systems/spatial_reactions.jl")
export TransportReaction, TransportReactions, @transport_reaction
export isedgeparameter

# lattice reaction systems
# Lattice reaction systems
include("spatial_reaction_systems/lattice_reaction_systems.jl")
export LatticeReactionSystem
export spatial_species, vertex_parameters, edge_parameters

# variosu utility functions
# Various utility functions
include("spatial_reaction_systems/utility.jl")

# spatial lattice ode systems.
# Specific spatial problem types.
include("spatial_reaction_systems/spatial_ODE_systems.jl")
include("spatial_reaction_systems/lattice_jump_systems.jl")

end # module
136 changes: 136 additions & 0 deletions src/spatial_reaction_systems/lattice_jump_systems.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
### JumpProblem ###

# Builds a spatial DiscreteProblem from a Lattice Reaction System.
function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...)
if !is_transport_system(lrs)
error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.")
end

# Converts potential symmaps to varmaps
# Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated.
u0_in = symmap_to_varmap(lrs, u0_in)
p_in = (p_in isa Tuple{<:Any,<:Any}) ?
(symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) :
symmap_to_varmap(lrs, p_in)

# Converts u0 and p to their internal forms.
# u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...].
u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts)
# Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter.
# These elements are length 1 vectors (if the parameter is uniform),
# or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively).
vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs)

# Returns a DiscreteProblem.
# Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally.
return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...)
end

# Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System.
function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs),
combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...)
# Error checks.
if !isnothing(dprob.f.sys)
error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?")
end

# Computes hopping constants and mass action jumps (requires some internal juggling).
# Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`).
# Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these).
# Hence the parameters of this one does nto actually matter. If at some point JumpProcess can
# handle parameters this can be updated and improved.
# The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes.
hopping_constants = make_hopping_constants(dprob, lrs)
sma_jumps = make_spatial_majumps(dprob, lrs)
non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1]))

return JumpProblem(non_spat_dprob, aggregator, sma_jumps;
hopping_constants, spatial_system = lrs.lattice, name, kwargs...)
end

# Creates the hopping constants from a discrete problem and a lattice reaction system.
function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem)
# Creates the all_diff_rates vector, containing for each species, its transport rate across all edges.
# If transport rate is uniform for one species, the vector have a single element, else one for each edge.
spatial_rates_dict = Dict(compute_all_transport_rates(dprob.p[1], dprob.p[2], lrs))
all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)]

# Creates the hopping constant Matrix. It contains one element for each combination of species and vertex.
# Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge.
hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j]))
for i in 1:(lrs.num_species), j in 1:(lrs.num_verts)]

# For each edge, finds each position in `hopping_constants`.
for (e_idx, e) in enumerate(edges(lrs.lattice))
dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src])
# For each species, sets that hopping rate.
for s_idx in 1:(lrs.num_species)
hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx)
end
end

return hopping_constants
end

# Creates a SpatialMassActionJump struct from a (spatial) DiscreteProblem and a LatticeReactionSystem.
# Could implementation a version which, if all reaction's rates are uniform, returns a MassActionJump.
# Not sure if there is any form of performance improvement from that though. Possibly is not the case.
function make_spatial_majumps(dprob, lrs::LatticeReactionSystem)
# Creates a vector, storing which reactions have spatial components.
is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)]

# Creates templates for the rates (uniform and spatial) and the stoichiometries.
# We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump.
# The reason is that we need to re-order the reactions so that uniform appears first, and spatial next.
u_rates = Vector{Float64}(undef, length(reactions(lrs.rs)) - count(is_spatials))
s_rates = Matrix{Float64}(undef, count(is_spatials), lrs.num_verts)
reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs)))
net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs)))

# Loops through reactions with non-spatial rates, computes their rates and stoichiometries.
cur_rx = 1;
for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs))
is_spat && continue
u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1]
substoich_map = Pair.(rx.substrates, rx.substoich)
reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs)
net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs)
cur_rx += 1
end
# Loops through reactions with spatial rates, computes their rates and stoichiometries.
for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs))
is_spat || continue
s_rates[cur_rx-length(u_rates),:] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])
substoich_map = Pair.(rx.substrates, rx.substoich)
reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs)
net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs)
cur_rx += 1
end
# SpatialMassActionJump expects empty rate containers to be nothing.
isempty(u_rates) && (u_rates = nothing)
(count(is_spatials)==0) && (s_rates = nothing)

return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich)
end

### Extra ###

# Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality).
function int_map(map_in, sys) where {T,S}
return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in]
end

# Currently unused. If we want to create certain types of MassActionJumps (instead of SpatialMassActionJumps) we can take this one back.
# Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin).
# function make_majumps(non_spat_dprob, rs::ReactionSystem)
# # Computes various required inputs for assembling the mass action jumps.
# js = convert(JumpSystem, rs)
# statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs)))
# eqs = equations(js)
# invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2])
#
# # Assembles the non-spatial mass action jumps.
# p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p
# majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype)
# return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper)
# end

0 comments on commit 2754419

Please sign in to comment.