From 0d32835fef795fba36ad6e7a9ddd86048d58de97 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 15:14:12 -0400 Subject: [PATCH 001/134] Begin tests --- test/runtests.jl | 5 ++++- .../lattice_reaction_networks.jl | 20 +++++++++++++++++++ .../simulate_PDEs.jl | 0 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 test/spatial_reaction_systems/lattice_reaction_networks.jl rename test/{model_simulation => spatial_reaction_systems}/simulate_PDEs.jl (100%) diff --git a/test/runtests.jl b/test/runtests.jl index d4e09a94c8..fe9feaa4c3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,7 +35,10 @@ using SafeTestsets @time @safetestset "SDE System Simulations" begin include("model_simulation/simulate_SDEs.jl") end @time @safetestset "Jump System Simulations" begin include("model_simulation/simulate_jumps.jl") end @time @safetestset "DiffEq Steady State Solving" begin include("model_simulation/solve_steady_state_problems.jl") end - @time @safetestset "PDE Systems Simulations" begin include("model_simulation/simulate_PDEs.jl") end + + ### Tests Spatial Network Simulations. ### + @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end + @time @safetestset "Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_networks.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_networks.jl b/test/spatial_reaction_systems/lattice_reaction_networks.jl new file mode 100644 index 0000000000..23ab3099fc --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_networks.jl @@ -0,0 +1,20 @@ +### Very simple tests to have during development ### + +using Catalyst, OrdinaryDiffEq, Graphs + +rs = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end A B +srs = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], []), ([], [1]))] +lattice = grid([20, 20]) +lrs = LatticeReactionSystem(rs, srs, lattice); + +u0_in = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] +tspan = (0.0, 100.0) +p_in = [:A => 1.0, :B => 4.0, :D => 0.2] + +oprob = ODEProblem(lrs, u0_in, tspan, p_in) +@test solve(oprob, Tsit5()).retcode == :Success \ No newline at end of file diff --git a/test/model_simulation/simulate_PDEs.jl b/test/spatial_reaction_systems/simulate_PDEs.jl similarity index 100% rename from test/model_simulation/simulate_PDEs.jl rename to test/spatial_reaction_systems/simulate_PDEs.jl From 8256e21c79ece476cda9bb3de7a771852c57a92f Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 15:16:33 -0400 Subject: [PATCH 002/134] Reupload everything --- src/Catalyst.jl | 5 + src/lattice_reaction_system.jl | 270 ++++++++++++++++++ test/runtests.jl | 2 +- ...etworks.jl => lattice_reaction_systems.jl} | 0 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 src/lattice_reaction_system.jl rename test/spatial_reaction_systems/{lattice_reaction_networks.jl => lattice_reaction_systems.jl} (100%) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 61f4850034..5d2dd3eac2 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -68,6 +68,11 @@ export @reaction_network, @add_reactions, @reaction, @species include("registered_functions.jl") export mm, mmr, hill, hillr, hillar +# spatial reaction networks +include("lattice_reaction_system.jl") +export SpatialReaction, DiffusionReaction, OnewaySpatialReaction +export LatticeReactionSystem + # functions to query network properties include("networkapi.jl") export species, nonspecies, reactionparams, reactions, speciesmap, paramsmap diff --git a/src/lattice_reaction_system.jl b/src/lattice_reaction_system.jl new file mode 100644 index 0000000000..91ffe350f6 --- /dev/null +++ b/src/lattice_reaction_system.jl @@ -0,0 +1,270 @@ +### Spatial Reaction Structure. ### +# Describing a spatial reaction that involves species from two neighbouring compartments. +# Currently only permit constant rate. +struct SpatialReaction + """The rate function (excluding mass action terms). Currentl only cosntants supported""" + rate::Symbol + """Reaction substrates (source and destination).""" + substrates::Tuple{Vector{Symbol}, Vector{Symbol}} + """Reaction products (source and destination).""" + products::Tuple{Vector{Symbol}, Vector{Symbol}} + """The stoichiometric coefficients of the reactants (source and destination).""" + substoich::Tuple{Vector{Int64}, Vector{Int64}} + """The stoichiometric coefficients of the products (source and destination).""" + prodstoich::Tuple{Vector{Int64}, Vector{Int64}} + """The net stoichiometric coefficients of all species changed by the reaction (source and destination).""" + netstoich::Tuple{Vector{Pair{Symbol,Int64}}, Vector{Pair{Symbol,Int64}}} + """ + `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. + `true` if `rate` represents the full reaction rate law. + Currently only `false`, is supported. + """ + only_use_rate::Bool + + """These are similar to substrates, products, and netstoich, but ses species index (instead ) """ + function SpatialReaction(rate, substrates::Tuple{Vector, Vector}, products::Tuple{Vector, Vector}, substoich::Tuple{Vector{Int64}, Vector{Int64}}, prodstoich::Tuple{Vector{Int64}, Vector{Int64}}; only_use_rate = false) + new(rate, substrates, products, substoich, prodstoich, get_netstoich.(substrates, products, substoich, prodstoich), only_use_rate) + end +end + +# As a spatial reaction, but replaces the species (and parameter) symbols with their index. +# For internal use only (to avoid having to constantly look up species indexes). +struct SpatialReactionIndexed + rate::Int64 + substrates::Tuple{Vector{Int64}, Vector{Int64}} + products::Tuple{Vector{Int64}, Vector{Int64}} + substoich::Tuple{Vector{Int64}, Vector{Int64}} + prodstoich::Tuple{Vector{Int64}, Vector{Int64}} + netstoich::Tuple{Vector{Pair{Int64,Int64}}, Vector{Pair{Int64,Int64}}} + only_use_rate::Bool + + function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}) + get_s_idx(species::Symbol) = findfirst(species .== (species_list)) + rate = findfirst(sr.rate .== (param_list)) + substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) + products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) + netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i])) for i in 1:2]) + new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, sr.only_use_rate) + end +end + +""" + DiffusionReaction(rate,species) + +Simple function to create a diffusion spatial reaction. + Equivalent to SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) +""" +DiffusionReaction(rate,species) = SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) + +""" + OnewaySpatialReaction(rate, substrates, products, substoich, prodstoich) + +Simple function to create a spatial reactions where all substrates are in teh soruce compartment, and all products in the destination. +Equivalent to SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) +""" +OnewaySpatialReaction(rate, substrates::Vector, products::Vector, substoich::Vector{Int64}, prodstoich::Vector{Int64}) = SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) + + + +### Lattice Reaction Network Structure ### +# Couples: +# A reaction network (that is simulated within each compartment). +# A set of spatial reactions (denoting interaction between comaprtments). +# A network of compartments (a meta graph that can contain some additional infro for each compartment). +# The lattice is a DiGraph, normals graphs are converted to DiGraphs (with one edge in each direction). +struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs + """The spatial reactions defined between individual nodes.""" + rs::ReactionSystem + """The spatial reactions defined between individual nodes.""" + spatial_reactions::Vector{SpatialReaction} + """The graph on which the lattice is defined.""" + lattice::DiGraph + """Dependent (state) variables representing amount of each species. Must not contain the + independent variable.""" + + function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) + return new(rs, spatial_reactions, lattice) + end + function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) + return new(rs, spatial_reactions, DiGraph(lattice)) + end +end + +### ODEProblem ### +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, + p = DiffEqBase.NullParameters(), args...; + jac=true, sparse=true, kwargs...) + @unpack rs, spatial_reactions, lattice = lrs + + spatial_params = unique(getfield.(spatial_reactions, :rate)) + pV_in, pE_in = split_parameters(p, spatial_params) + u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) + pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) + pE_idxes = Dict(reverse.(enumerate(spatial_params))) + + nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) + u0 = Vector(reshape(matrix_form(u0, nV, u_idxs),1:nS*nV)) + + pV = matrix_form(pV_in, nV, pV_idxes) + pE = matrix_form(pE_in, nE, pE_idxes) + + ofun = build_odefunction(lrs, spatial_params, jac, sparse) + return ODEProblem(ofun, u0, tspan, (pV, pE), args...; kwargs...) +end + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(parameters::Tuple, spatial_params::Vector{Symbol}) = parameters +function split_parameters(parameters::Vector, spatial_params::Vector{Symbol}) + filter(p -> !in(p[1], spatial_params), parameters), + filter(p -> in(p[1], spatial_params), parameters) +end + +# Converts species and parameters to matrices form. +matrix_form(input::Matrix, args...) = input +function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) + mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) +end +function matrix_form(input::Vector, n, index_dict) + matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), + n, index_dict) +end + +# Builds an ODEFunction. +function build_odefunction(lrs::LatticeReactionSystem, spatial_params::Vector{Symbol}, use_jac::Bool, sparse::Bool) + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) + nS,nV = length.([states(lrs.rs), vertices(lrs.lattice)]) + spatial_reactions = [SpatialReactionIndexed(sr, Symbol.(getfield.(states(lrs.rs), :f)), spatial_params) for sr in lrs.spatial_reactions] + + f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) + jac = build_jac(ofunc,nS,nV,spatial_reactions, lrs.lattice.fadjlist) + jac_prototype = build_jac_prototype(nS,nV,spatial_reactions, lrs) + + return ODEFunction(f; jac=(use_jac ? jac : nothing), jac_prototype=(use_jac ? (sparse ? SparseArrays.sparse(jac_prototype) : jac_prototype) : nothing)) +end + +# Creates a function for simulating the spatial ODE with spatial reactions. +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, adjlist::Vector{Vector{Int64}}) + return function(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:nV + ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + end + + # Updates for spatial reactions. + for comp_i::Int64 in 1:nV + for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions + rate::Float64 = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + for stoich::Pair{Int64,Int64} in sr.netstoich[1] + du[get_index(comp_i,stoich[1],nS)] += rate * stoich[2] + end + for stoich::Pair{Int64,Int64} in sr.netstoich[2] + du[get_index(comp_j,stoich[1],nS)] += rate * stoich[2] + end + end + end + end +end + +# Get the rate of a specific reaction. +function get_rate(sr::SpatialReactionIndexed, pE::Matrix{Float64}, u_src, u_dst) + product::Float64 = pE[sr.rate] + !isempty(sr.substrates[1]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) + product *= u_src[sub]^stoich / factorial(stoich) + end + !isempty(sr.substrates[2]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[sub]^stoich / factorial(stoich) + end + return product +end + +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, adjlist::Vector{Vector{Int64}}) + return function(J, u, p, t) + J .= 0 + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:nV + ofunc.jac((@view J[get_indexes(comp_i,nS),get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + end + + # Updates for spatial reactions. + for comp_i::Int64 in 1:nV + for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions + for sub::Int64 in sr.substrates[1] + rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + for stoich::Pair{Int64,Int64} in sr.netstoich[1] + J[get_index(comp_i,stoich[1],nS),get_index(comp_i,sub,nS)] += rate * stoich[2] + end + for stoich::Pair{Int64,Int64} in sr.netstoich[2] + J[get_index(comp_j,stoich[1],nS),get_index(comp_i,sub,nS)] += rate * stoich[2] + end + end + for sub::Int64 in sr.substrates[2] + rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j,nS)]), (@view u[get_indexes(comp_i,nS)])) + for stoich::Pair{Int64,Int64} in sr.netstoich[1] + J[get_index(comp_i,stoich[1],nS),get_index(comp_j,sub,nS)] += rate * stoich[2] + end + for stoich::Pair{Int64,Int64} in sr.netstoich[2] + J[get_index(comp_j,stoich[1],nS),get_index(comp_j,sub,nS)] += rate * stoich[2] + end + end + end + end + end +end + +function get_rate_differential(sr::SpatialReactionIndexed, pE::Matrix{Float64}, diff_species::Int64, u_src, u_dst) + product::Float64 = pE[sr.rate] + !isempty(sr.substrates[1]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) + if diff_species==sub + product *= stoich*u_src[sub]^(stoich-1) / factorial(stoich) + else + product *= u_src[sub]^stoich / factorial(stoich) + end + end + !isempty(sr.substrates[2]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[sub]^stoich / factorial(stoich) + end + return product +end + +function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, lrs::LatticeReactionSystem) + jac_prototype::Matrix{Float64} = zeros(nS*nV,nS*nV) + + # Sets non-spatial reactions. + # Tries to utilise sparsity within each comaprtment. + for comp_i in 1:nV, reaction in reactions(lrs.rs) + for substrate in reaction.substrates, ns in reaction.netstoich + sub_idx = findfirst(isequal(substrate), states(lrs.rs)) + spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) + jac_prototype[get_index(comp_i,spec_idx,nS), get_index(comp_i,sub_idx,nS)] = 1 + end + end + + # Updates for spatial reactions. + for comp_i::Int64 in 1:nV + for comp_j::Int64 in lrs.lattice.fadjlist[comp_i]::Vector{Int64}, sr::SpatialReactionIndexed in spatial_reactions + for sub::Int64 in sr.substrates[1] + for stoich::Pair{Int64,Int64} in sr.netstoich[1] + jac_prototype[get_index(comp_i,stoich[1],nS),get_index(comp_i,sub,nS)] = 1.0 + end + for stoich::Pair{Int64,Int64} in sr.netstoich[2] + jac_prototype[get_index(comp_j,stoich[1],nS),get_index(comp_i,sub,nS)] = 1.0 + end + end + for sub::Int64 in sr.substrates[2] + for stoich::Pair{Int64,Int64} in sr.netstoich[1] + jac_prototype[get_index(comp_i,stoich[1],nS),get_index(comp_j,sub,nS)] = 1.0 + end + for stoich::Pair{Int64,Int64} in sr.netstoich[2] + jac_prototype[get_index(comp_j,stoich[1],nS),get_index(comp_j,sub,nS)] = 1.0 + end + end + end + end + return jac_prototype +end + +# Gets the index of a species (or a node's species) in the u array. +get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species +get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index fe9feaa4c3..9f61c4c293 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -38,7 +38,7 @@ using SafeTestsets ### Tests Spatial Network Simulations. ### @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end - @time @safetestset "Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_networks.jl") end + @time @safetestset "Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_networks.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl similarity index 100% rename from test/spatial_reaction_systems/lattice_reaction_networks.jl rename to test/spatial_reaction_systems/lattice_reaction_systems.jl From 2355316705dc9dd72efad5a6fba7fa6a41e7d41b Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 16:01:28 -0400 Subject: [PATCH 003/134] add graph imports --- src/Catalyst.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 5d2dd3eac2..798759a4f9 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -30,7 +30,8 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec get_unit, check_equations import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show -import MacroTools, Graphs +import MacroTools +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow From ef14e79a5d3fb2bb873c19702f28b5ae45a41761 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 18:19:40 -0400 Subject: [PATCH 004/134] Start preparing tests --- src/lattice_reaction_system.jl | 25 +- .../lattice_reaction_systems.jl | 298 +++++++++++++++++- 2 files changed, 300 insertions(+), 23 deletions(-) diff --git a/src/lattice_reaction_system.jl b/src/lattice_reaction_system.jl index 91ffe350f6..97f4662170 100644 --- a/src/lattice_reaction_system.jl +++ b/src/lattice_reaction_system.jl @@ -54,7 +54,7 @@ end Simple function to create a diffusion spatial reaction. Equivalent to SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) """ -DiffusionReaction(rate,species) = SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) +DiffusionReaction(rate,species) = SpatialReaction(rate,([species],Symbol[]),(Symbol[],[species]),([1],Int64[]),(Int64[],[1])) """ OnewaySpatialReaction(rate, substrates, products, substoich, prodstoich) @@ -62,7 +62,7 @@ DiffusionReaction(rate,species) = SpatialReaction(rate,([species],[]),([],[speci Simple function to create a spatial reactions where all substrates are in teh soruce compartment, and all products in the destination. Equivalent to SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) """ -OnewaySpatialReaction(rate, substrates::Vector, products::Vector, substoich::Vector{Int64}, prodstoich::Vector{Int64}) = SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) +OnewaySpatialReaction(rate, substrates::Vector, products::Vector, substoich::Vector{Int64}, prodstoich::Vector{Int64}) = SpatialReaction(rate,(substrates,Symbol[]),(Symbol[],products),(substoich,Int64[]),(Int64[],prodstoich)) @@ -77,16 +77,18 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p rs::ReactionSystem """The spatial reactions defined between individual nodes.""" spatial_reactions::Vector{SpatialReaction} + """A list of parameters that occur in the spatial reactions.""" + spatial_params::Vector{Symbol} """The graph on which the lattice is defined.""" lattice::DiGraph """Dependent (state) variables representing amount of each species. Must not contain the independent variable.""" function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) - return new(rs, spatial_reactions, lattice) + return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), lattice) end function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) - return new(rs, spatial_reactions, DiGraph(lattice)) + return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), DiGraph(lattice)) end end @@ -97,11 +99,10 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, jac=true, sparse=true, kwargs...) @unpack rs, spatial_reactions, lattice = lrs - spatial_params = unique(getfield.(spatial_reactions, :rate)) - pV_in, pE_in = split_parameters(p, spatial_params) + pV_in, pE_in = split_parameters(p, lrs.spatial_params) u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) - pE_idxes = Dict(reverse.(enumerate(spatial_params))) + pE_idxes = Dict(reverse.(enumerate(lrs.spatial_params))) nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) u0 = Vector(reshape(matrix_form(u0, nV, u_idxs),1:nS*nV)) @@ -109,7 +110,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, pV = matrix_form(pV_in, nV, pV_idxes) pE = matrix_form(pE_in, nE, pE_idxes) - ofun = build_odefunction(lrs, spatial_params, jac, sparse) + ofun = build_odefunction(lrs, jac, sparse) return ODEProblem(ofun, u0, tspan, (pV, pE), args...; kwargs...) end @@ -121,20 +122,22 @@ function split_parameters(parameters::Vector, spatial_params::Vector{Symbol}) end # Converts species and parameters to matrices form. -matrix_form(input::Matrix, args...) = input +matrix_form(input::Matrix{Float64}, args...) = input function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) + isempty(input) && return zeros(0,n) mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) end function matrix_form(input::Vector, n, index_dict) + isempty(input) && return zeros(0,n) matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), n, index_dict) end # Builds an ODEFunction. -function build_odefunction(lrs::LatticeReactionSystem, spatial_params::Vector{Symbol}, use_jac::Bool, sparse::Bool) +function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bool) ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) nS,nV = length.([states(lrs.rs), vertices(lrs.lattice)]) - spatial_reactions = [SpatialReactionIndexed(sr, Symbol.(getfield.(states(lrs.rs), :f)), spatial_params) for sr in lrs.spatial_reactions] + spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params) for sr in lrs.spatial_reactions] f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) jac = build_jac(ofunc,nS,nV,spatial_reactions, lrs.lattice.fadjlist) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 23ab3099fc..dc95d0e680 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,20 +1,294 @@ -### Very simple tests to have during development ### +### Fetches Stuff ### -using Catalyst, OrdinaryDiffEq, Graphs +# Fetch packages. +using Catalyst, OrdinaryDiffEq, Random, Test +using BenchmarkTools, Statistics +using Graphs -rs = @reaction_network begin +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + + +### Helper Functions ### +rand_v_vals(grid) = rand(length(vertices(grid))) +rand_v_vals(grid, x::Number) = rand_v_vals(grid)*x +rand_e_vals(grid) = rand(length(edges(grid))) +rand_e_vals(grid, x::Number) = rand_e_vals(grid)*x + +function make_u0_matrix(value_map, vals, symbols) + (length(symbols)==0) && (return zeros(0, length(vals))) + d = Dict(value_map) + return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] +end + + +### Declares Models ### + +# Small non-stiff network. +binding_system = @reaction_network begin + (kB,kD), X +Y <--> XY +end +binding_p = [:kB => 2.0, :kD => 0.5] + +binding_dif_x = DiffusionReaction(:dX, :X) +binding_dif_y = DiffusionReaction(:dY, :Y) +binding_dif_xy = DiffusionReaction(:dXY, :XY) +binding_osr_xy1 = OnewaySpatialReaction(:d_ord_1, [:X, :Y], [:X, :Y], [1, 1], [1, 1]) +binding_osr_xy2 = OnewaySpatialReaction(:d_ord_2, [:X, :Y], [:XY], [1, 1], [1]) +binding_osr_xy3 = OnewaySpatialReaction(:d_ord_3, [:X, :Y], [:X, :Y], [2, 2], [2, 2]) +binding_sr_1 = SpatialReaction(:d_sr_1, ([:X], [:Y]), ([:Y], [:X]), ([1], [1]), ([1], [1])) +binding_sr_2 = SpatialReaction(:d_sr_2, ([:X, :Y], [:XY]), ([:XY], []), ([1, 1], [2]), ([1], Vector{Int64}())) +binding_srs_1 = [binding_dif_x] +binding_srs_2 = [binding_dif_x, binding_dif_y, binding_dif_xy] +binding_srs_3 = [binding_sr_1, binding_sr_2] +binding_srs_4 = [binding_dif_x, binding_dif_y, binding_dif_xy, binding_osr_xy1, binding_osr_xy2, binding_osr_xy3, binding_sr_1, binding_sr_2] + +# Mid-sized non-stiff system. +CuH_Amination_system = @reaction_network begin + 10.0^kp1, CuoAc + Ligand --> CuoAcLigand + 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc + 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand + 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene + 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand + 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E + 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane + 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand + 10.0^kdc, CuHLigand + CuHLigand --> Decomposition +end +CuH_Amination_p = [] + +CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) +CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) +CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) +CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) +CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) +CuH_Amination_srs1 = [CuH_Amination_diff_1] +CuH_Amination_srs2 = [CuH_Amination_diff_1, CuH_Amination_diff_2, CuH_Amination_diff_3, CuH_Amination_diff_4, CuH_Amination_diff_5] + +# Small stiff system. +brusselator_system = @reaction_network begin A, ∅ → X 1, 2X + Y → 3X B, X → Y 1, X → ∅ -end A B -srs = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], []), ([], [1]))] -lattice = grid([20, 20]) -lrs = LatticeReactionSystem(rs, srs, lattice); +end +brusselator_p = [:A => 1.0, :B => 4.0] + +brusselator_dif_x = DiffusionReaction(:dX, :X) +brusselator_dif_y = DiffusionReaction(:dY, :Y) +binding_osr_x2 = OnewaySpatialReaction(:d_ord_1, [:X], [:X], [2], [2]) +brusselator_dif_sr = SpatialReaction(:D, ([:X], [:Y]), ([:Y], [:X]), ([1], [2]), ([1], [1])) +brusselator_srs_1 = [brusselator_dif_x] +brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] +brusselator_srs_3 = [binding_osr_x2] +brusselator_srs_4 = [brusselator_dif_x, brusselator_dif_sr] + +# Mid-sized stiff system. +sigmaB_system = @reaction_network begin + kDeg, (w,w2,w2v,v,w2v2,vP,σB,w2σB) ⟶ ∅ + kDeg, vPp ⟶ phos + (kBw,kDw), 2w ⟷ w2 + (kB1,kD1), w2 + v ⟷ w2v + (kB2,kD2), w2v + v ⟷ w2v2 + kK1, w2v ⟶ w2 + vP + kK2, w2v2 ⟶ w2v + vP + (kB3,kD3), w2 + σB ⟷ w2σB + (kB4,kD4), w2σB + v ⟷ w2v + σB + (kB5,kD5), vP + phos ⟷ vPp + kP, vPp ⟶ v + phos + v0*((1+F*σB)/(K+σB)), ∅ ⟶ σB + λW*v0*((1+F*σB)/(K+σB)), ∅ ⟶ w + λV*v0*((1+F*σB)/(K+σB)), ∅ ⟶ v +end +sigmaB_p = [:kBw => 3600, :kDw => 18, :kD => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, :kB4 => 1800, :kB5 => 3600, + :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, :kP => 180, :kDeg => 0.7, + :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] + +sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) +sigmaB_dif_w = DiffusionReaction(:Dw, :w) +sigmaB_dif_v = DiffusionReaction(:v, :v) +sigmaB_srs1 = [sigmaB_dif_σB] +sigmaB_srs1 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] + + +### Declares Lattices ### + +# Grids. +small_2d_grid = Graphs.grid([5, 5]) +medium_2d_grid = Graphs.grid([20, 20]) +large_2d_grid = Graphs.grid([100, 100]) + +small_3d_grid = Graphs.grid([5, 5, 5]) +medium_3d_grid = Graphs.grid([20, 20, 20]) +large_3d_grid = Graphs.grid([100, 100, 100]) + +# Paths. +short_path = path_graph(100) +long_path = path_graph(1000) + +# Directed cycle. +small_directed_cycle = cycle_graph(100) +large_directed_cycle = cycle_graph(1000) + + +### Test No Error During Runs ### +for grid in [small_2d_grid, short_path, small_directed_cycle] + # Stiff case + for srs in [Vector{SpatialReaction}(), brusselator_srs_1, brusselator_srs_2, brusselator_srs_3, brusselator_srs_4] + lrs = LatticeReactionSystem(brusselator_system, srs, grid) + u0_1 = [:X => 1.0, :Y => 20.0] + u0_2 = [:X => rand_v_vals(grid, 10.0), :Y => 2.0] + u0_3 = [:X => rand_v_vals(grid, 20), :Y => rand_v_vals(grid, 10)] + u0_4 = make_u0_matrix(u0_3, vertices(grid), map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4] + p1 = [:A => 1.0, :B => 4.0] + p2 = [:A => 0.5 .+ rand_v_vals(grid, 0.5), :B => 4.0] + p3 = [:A => 0.5 .+ rand_v_vals(grid, 0.5), :B => 4.0 .+ rand_v_vals(grid, 1.0)] + p4 = make_u0_matrix(p2, vertices(grid), Symbol.(parameters(lrs.rs))) + for pV in [p1, p2, p3, p4] + pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) + pE_2 = map(sp -> sp => rand(), lrs.spatial_params) + pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) + pE_4 = make_u0_matrix(pE_3, edges(grid), lrs.spatial_params) + for pE in [pE_1, pE_2, pE_3, pE_4] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); sparse=false) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + end + end + end + end + + # Non-stiff case + for srs in [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4] + lrs = LatticeReactionSystem(binding_system, srs, grid) + u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] + u0_2 = [:X => rand_v_vals(grid), :Y => 2.0, :XY => 0.0] + u0_3 = [:X => 1.0, :Y => rand_v_vals(grid), :XY => rand_v_vals(grid)] + u0_4 = [:X => rand_v_vals(grid), :Y => rand_v_vals(grid), :XY => rand_v_vals(grid,3)] + u0_5 = make_u0_matrix(u0_3, vertices(grid), map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:kB => 2.0, :kD => 0.5] + p2 = [:kB => 2.0, :kD => rand_v_vals(grid)] + p3 = [:kB => rand_v_vals(grid), :kD => rand_v_vals(grid)] + p4 = make_u0_matrix(p1, vertices(grid), Symbol.(parameters(lrs.rs))) + for pV in [p1, p2, p3, p4] + pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) + pE_2 = map(sp -> sp => rand(), lrs.spatial_params) + pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) + pE_4 = make_u0_matrix(pE_3, edges(grid), lrs.spatial_params) + for pE in [pE_1, pE_2, pE_3, pE_4] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); jac=false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + end + end + end + end +end + + +### Tests Runtimes ### + +# Timings require figuring out. + +# Small grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(binding_system, binding_srs_2, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + pV = binding_p + pE = [:dX => 0.1, :dY => 0.2] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end + +# Large grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(binding_system, binding_srs_2, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + pV = binding_p + pE = [:dX => 0.1, :dY => 0.2] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end + +# Small grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice,10), :Y => rand_v_vals(lrs.lattice,10)] + pV = brusselator_p + pE = [:dX => 0.2,] + oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) + runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end + +# Large grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice,10), :Y => rand_v_vals(lrs.lattice,10)] + pV = brusselator_p + pE = [:dX => 0.2,] + oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) + runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end + +# Small grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) + u0 = [] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end + +# Large grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) + u0 = [] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end -u0_in = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] -tspan = (0.0, 100.0) -p_in = [:A => 1.0, :B => 4.0, :D => 0.2] +# Small grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end -oprob = ODEProblem(lrs, u0_in, tspan, p_in) -@test solve(oprob, Tsit5()).retcode == :Success \ No newline at end of file +# Large grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") + @test runtime < 2*1 +end \ No newline at end of file From 5f34876ea081f19de7bec83628d1143dd4835a47 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 11:36:32 -0400 Subject: [PATCH 005/134] test updates --- src/lattice_reaction_system.jl | 10 +- .../lattice_reaction_systems.jl | 1085 ++++++++++++++++- 2 files changed, 1053 insertions(+), 42 deletions(-) diff --git a/src/lattice_reaction_system.jl b/src/lattice_reaction_system.jl index 97f4662170..7bb4717c3b 100644 --- a/src/lattice_reaction_system.jl +++ b/src/lattice_reaction_system.jl @@ -140,10 +140,10 @@ function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bo spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params) for sr in lrs.spatial_reactions] f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) - jac = build_jac(ofunc,nS,nV,spatial_reactions, lrs.lattice.fadjlist) - jac_prototype = build_jac_prototype(nS,nV,spatial_reactions, lrs) + jac = (use_jac ? build_jac(ofunc,nS,nV,spatial_reactions, lrs.lattice.fadjlist) : nothing) + jac_prototype = (use_jac ? build_jac_prototype(nS,nV,spatial_reactions, lrs, sparse) : nothing) - return ODEFunction(f; jac=(use_jac ? jac : nothing), jac_prototype=(use_jac ? (sparse ? SparseArrays.sparse(jac_prototype) : jac_prototype) : nothing)) + return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) end # Creates a function for simulating the spatial ODE with spatial reactions. @@ -231,8 +231,8 @@ function get_rate_differential(sr::SpatialReactionIndexed, pE::Matrix{Float64}, return product end -function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, lrs::LatticeReactionSystem) - jac_prototype::Matrix{Float64} = zeros(nS*nV,nS*nV) +function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, lrs::LatticeReactionSystem, sparse::Bool) + jac_prototype = (sparse ? spzeros(nS*nV,nS*nV) : zeros(nS*nV,nS*nV)::Matrix{Float64}) # Sets non-spatial reactions. # Tries to utilise sparsity within each comaprtment. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index dc95d0e680..b8bcd713bd 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -56,15 +56,16 @@ CuH_Amination_system = @reaction_network begin 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand 10.0^kdc, CuHLigand + CuHLigand --> Decomposition end -CuH_Amination_p = [] +CuH_Amination_p = [:kp1 => 1.2, :kp2 => -0.72, :k1 => 0.57, :k_1 => -3.5, :k2 => -0.35, :k_2 => -0.77, :k3 => -0.025, :kam => -2.6, :kdc => -3.0] +CuH_Amination_u0 = [:CuoAc => 0.0065, :Ligand => 0.0072, :CuoAcLigand => 0.0, :Silane => 0.65, :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) -CuH_Amination_srs1 = [CuH_Amination_diff_1] -CuH_Amination_srs2 = [CuH_Amination_diff_1, CuH_Amination_diff_2, CuH_Amination_diff_3, CuH_Amination_diff_4, CuH_Amination_diff_5] +CuH_Amination_srs_1 = [CuH_Amination_diff_1] +CuH_Amination_srs_2 = [CuH_Amination_diff_1, CuH_Amination_diff_2, CuH_Amination_diff_3, CuH_Amination_diff_4, CuH_Amination_diff_5] # Small stiff system. brusselator_system = @reaction_network begin @@ -101,15 +102,16 @@ sigmaB_system = @reaction_network begin λW*v0*((1+F*σB)/(K+σB)), ∅ ⟶ w λV*v0*((1+F*σB)/(K+σB)), ∅ ⟶ v end -sigmaB_p = [:kBw => 3600, :kDw => 18, :kD => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, :kB4 => 1800, :kB5 => 3600, +sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, :kB4 => 1800, :kB5 => 3600, :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, :kP => 180, :kDeg => 0.7, :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] +sigmaB_u0 = [:w => 1.0, :w2 => 1.0, :w2v => 1.0, :v => 1.0, :w2v2 => 1.0, :vP => 1.0, :σB => 1.0, :w2σB => 1.0, :vPp => 0.0, :phos => 0.4] sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) sigmaB_dif_w = DiffusionReaction(:Dw, :w) -sigmaB_dif_v = DiffusionReaction(:v, :v) -sigmaB_srs1 = [sigmaB_dif_σB] -sigmaB_srs1 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] +sigmaB_dif_v = DiffusionReaction(:Dv, :v) +sigmaB_srs_1 = [sigmaB_dif_σB] +sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] ### Declares Lattices ### @@ -195,7 +197,8 @@ end ### Tests Runtimes ### -# Timings require figuring out. +# Timings currently are from Torkel's computer. + # Small grid, small, non-stiff, system. let @@ -204,9 +207,12 @@ let pV = binding_p pE = [:dX => 0.1, :dY => 0.2] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.000895 + runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Large grid, small, non-stiff, system. @@ -216,9 +222,12 @@ let pV = binding_p pE = [:dX => 0.1, :dY => 0.2] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00091 + runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Small grid, small, stiff, system. @@ -228,9 +237,12 @@ let pV = brusselator_p pE = [:dX => 0.2,] oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) - runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.086 + runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Large grid, small, stiff, system. @@ -240,33 +252,42 @@ let pV = brusselator_p pE = [:dX => 0.2,] oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) - runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 120.0 + runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Small grid, mid-sized, non-stiff, system. let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) - u0 = [] + u0 = [:CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :CuoAcLigand => 0.0, :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00293 + runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Large grid, mid-sized, non-stiff, system. let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) - u0 = [] + u0 = [:CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :CuoAcLigand => 0.0, :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, Tsit5())).times)/1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 1.54 + runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Small grid, mid-sized, stiff, system. @@ -275,10 +296,13 @@ let u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.0228 + runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target end # Large grid, mid-sized, stiff, system. @@ -287,8 +311,995 @@ let u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - runtime = median((@benchmark solve(oprob, QNDF())).times)/1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard:$(1)") - @test runtime < 2*1 -end \ No newline at end of file + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 127.0 + runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2*runtime_target +end + +states(sigmaB_system) + +sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) +sigmaB_dif_w = DiffusionReaction(:Dw, :w) +sigmaB_dif_v = DiffusionReaction(:v, :v) +sigmaB_srs_1 = [sigmaB_dif_σB] +sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] + + +CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) +CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) +CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) +CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) +CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) + + +lrs = LatticeReactionSystem(binding_system, brusselator_srs_2, small_2d_grid) +u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] +pV = binding_p +pE = [:dX => 0.1, :dY => 0.2] +oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + +runtime = minimum((@benchmark solve(oprob, Tsit5())).times) + +(@benchmark solve(oprob, Tsit5())).times + +bm = @benchmark solve(oprob, Tsit5()) + +median(bm.times) + + + + + +brusselator_system = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end +brusselator_p = [:A => 1.0, :B => 4.0] + +brusselator_dif_x = DiffusionReaction(:dX, :X) +brusselator_dif_y = DiffusionReaction(:dY, :Y) +binding_osr_x2 = OnewaySpatialReaction(:d_ord_1, [:X], [:X], [2], [2]) +brusselator_dif_sr = SpatialReaction(:D, ([:X], [:Y]), ([:Y], [:X]), ([1], [2]), ([1], [1])) +brusselator_srs_1 = [brusselator_dif_x] +brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] +brusselator_srs_3 = [binding_osr_x2] +brusselator_srs_4 = [brusselator_dif_x, brusselator_dif_sr] + + +length(edges(small_directed_cycle)) + +typeof(zeros(0,3)) + +pE = matrix_form(pE_in, nE, pE_idxes) + +zeros(1,0) + +pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) + +lrs.spatial_reactions + +using Test + +grid = [small_2d_grid, short_path, small_directed_cycle][1] +srs = [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4][2] +lrs = LatticeReactionSystem(binding_system, srs, grid) + +SciMLBase.successful_retcode(sol) +sol.retcode + +grid = [small_2d_grid, short_path, small_directed_cycle][1] +srs = [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4][2] +lrs = LatticeReactionSystem(binding_system, srs, grid) + +u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] +u0_2 = [:X => rand_v_vals(grid), :Y => 2.0, :XY => 0.0] +u0_3 = [:X => 1.0, :Y => rand_v_vals(grid), :XY => rand_v_vals(grid)] +u0_4 = [:X => rand_v_vals(grid), :Y => rand_v_vals(grid), :XY => rand_v_vals(grid,3)] +u0_5 = make_u0_matrix(u0_3, grid, map(s -> Symbol(s.f), species(lrs.rs))) +u0 = [u0_1, u0_2, u0_3, u0_4, u0_5][3] + +p1 = [:kB => 2.0, :kD => 0.5] +p2 = [:kB => 2.0, :kD => rand_v_vals(grid)] +p3 = [:kB => rand_v_vals(grid), :kD => rand_v_vals(grid)] +p4 = make_u0_matrix(p1, grid, Symbol.(parameters(lrs.rs))) +p = p3 + +pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) +pE_2 = map(sp -> sp => rand(), lrs.spatial_params) +pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) +pE_4 = make_u0_matrix(pE_3, grid, lrs.spatial_params) +pE = pE_3 + +oprob = ODEProblem(lrs, u0, (0.0,10.0), (p, pE)) +sol = solve(oprob, Tsit5()) + + + + + + + +rs = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end A B +spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] +lattice = Graphs.grid([20, 20]) +lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) + +u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] +tspan = (0.0, 100.0) +p = [:A => 1.0, :B => 4.0, :D => 0.2] + +oprob = ODEProblem(lrs, u0, (0.0,100.0), p) +@time sol = solve(oprob, Tsit5()) +@time sol = solve(oprob, QNDF()) + +plot(sol,idxs=[1,11]) + +@profview sol = solve(oprob, Tsit5()) +@profview sol = solve(oprob, Rosenbrock23()) + +@btime solve(oprob, Tsit5()) +@btime solve(oprob, QNDF()) + + +sizes = 10:10:200 +explit_btimes = [] +implicit_btimes = [] +@time for s in sizes + println("Running for size $s.") + rs = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ + end A B + spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] + lattice = Graphs.grid([10, s]) + lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) + + u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] + tspan = (0.0, 100.0) + p = [:A => 1.0, :B => 4.0, :D => 0.2] + + oprob = ODEProblem(lrs, u0, (0.0,100.0), p) + b1 = (@benchmark solve(oprob, Tsit5())) + b2 = (@benchmark solve(oprob, QNDF())) + push!(explit_btimes, median(b1.times)) + push!(implicit_btimes, median(b2.times)) + println("Explicit runtim: $(median(b1.times)/1000000000)") + println("Implicit runtim: $(median(b2.times)/1000000000)\n") +end + +plot(sizes,explit_btimes ./1000000000; label="Explicit runtimes") +plot(sizes,implicit_btimes ./1000000000; label="Implicit runtimes") +plot!(yaxis=:log10) + + +plot(sizes,implicit_btimes ./ explit_btimes; label="Explicit runtimes") + + +plot((explit_btimes ./1000000000) ./ sizes; label="Explicit runtimes") +plot((implicit_btimes ./1000000000)./ sizes; label="Implicit runtimes") + + +u_tmp = [1.0, 2.0] +p = [1.0, 4.0] +ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) +J = zeros(2,2) +ofunc(J,u_tmp,p,0.0) +J + +using SparseArrays +J = [1.0 0; 1 0] +J2 = sparse(J) +ofunc(J2,u_tmp,p,0.0) +J2 + + + + + + + + + + +spatial_params = unique(getfield.(spatial_reactions, :rate)) +pV_in, pE_in = Catalyst.split_parameters(p, spatial_params) +u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) +pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) +pE_idxes = Dict(reverse.(enumerate(spatial_params))) + +nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) +u0 = Vector(reshape(Catalyst.matrix_form(u0, nV, u_idxs),1:nS*nV)) + +pV = Catalyst.matrix_form(pV_in, nV, pV_idxes) +pE = Catalyst.matrix_form(pE_in, nE, pE_idxes) + +ofun = Catalyst.build_odefunction(lrs, true, spatial_params) + +supertype(typeof(ofun)) +ofun isa SciMLBase.AbstractODEFunction{true} + + + +matrix_form(input::Matrix, args...) = input +function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) + mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) +end +function matrix_form(input::Vector, n, index_dict) + matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), + n, index_dict) +end + +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +spatial_params = unique(getfield.(spatial_reactions, :rate)) +pV_in, pE_in = split_parameters(p, spatial_params) +u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) +pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) +pE_idxes = Dict(reverse.(enumerate(spatial_params))) + +nS, nV, nE = get_sizes(lrs) +u0 = Vector(reshape(matrix_form(u0, nV, u_idxs),1:nS*nV)) + +pV = matrix_form(pV_in, nV, pV_idxes) +pE = matrix_form(pE_in, nE, pE_idxes) + +# As a spatial reaction, but replaces the species (and parameter) symbols with their index. +struct SpatialReactionIndexed + rate::Int64 + substrates::Tuple{Vector{Int64}, Vector{Int64}} + products::Tuple{Vector{Int64}, Vector{Int64}} + substoich::Tuple{Vector{Int64}, Vector{Int64}} + prodstoich::Tuple{Vector{Int64}, Vector{Int64}} + netstoich::Tuple{Vector{Pair{Int64,Int64}}, Vector{Pair{Int64,Int64}}} + only_use_rate::Bool + + function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}) + get_s_idx(species::Symbol) = findfirst(species .== (species_list)) + rate = findfirst(sr.rate .== (param_list)) + substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) + products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) + netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i])) for i in 1:2]) + new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, sr.only_use_rate) + end +end + + + +ofunc = ODEFunction(convert(ODESystem, lrs.rs)) +nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) +srs_idxed = [SpatialReactionIndexed(sr, Symbol.(getfield.(states(rs), :f)), spatial_params) for sr in spatial_reactions] + +f = build_f(ofunc,nS,nV,srs_idxed) +jac = build_jac(ofunc,nS,nV,srs_idxed) +jac_prototype = build_jac_prototype(nS,nV,srs_idxed) + +ofun = ODEFunction(f; jac=jac, jac_prototype=(true ? sparse(jac_prototype) : jac_prototype)) + +ODEProblem(ofun, u0, tspan, (pV, pE)) + +function build_f(ofunc,nS,nV,srs_idxed) + + return function(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed + rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + for (comp,idx) in [(comp_i,1),(comp_j,2)] + for stoich in sr.netstoich[idx] + du[get_index(comp,stoich[idx],nS)] += rate * stoich[2] + end + end + end + end + end +end + +function build_jac(ofunc,nS,nV,srs_idxed) + base_zero = zeros(nS*nV,nS*nV) + + return function(J, u, p, t) + J .= base_zero + + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc.jac((@view J[get_indexes(comp_i,nS),get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed + for (idx1, comp1) in [(1,comp_i),(2,comp_j)], sub in sr.substrates[idx1] + rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + for (idx2, comp2) in [(1,comp_i),(2,comp_j)], stoich in sr.netstoich[idx2] + J[get_index(comp2,stoich[1],nS,u_idxs),get_index(comp1,sub,nS,u_idxs)] += rate * stoich[2] + end + end + end + end + end +end + +function get_rate_differential(sr, pE, diff_species, u_src, u_dst) + product = pE[sr.rate] + !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) + if diff_species==sub + product *= stoich*u_src[sub]^(stoich-1) / factorial(stoich) + else + product *= u_src[sub]^stoich / factorial(stoich) + end + end + !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[sub]^stoich / factorial(stoich) + end + return product +end + +findfirst(isequal(reactions(rs)[1].netstoich[1][1]), states(rs)) + +function build_jac_prototype(nS,nV,srs_idxed) + jac_prototype = zeros(nS*nV,nS*nV) + + # Sets non-spatial reactions. + for comp_i in 1:nV, reaction in reactions(lrs.rs) + for substrate in reaction.substrates, ns in reaction.netstoich + sub_idx = findfirst(isequal(substrate), states(lrs.rs)) + spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) + jac_prototype[spec_idx, sub_idx] = 1 + end + end + + for comp_i in 1:nV + for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed + for (idx1, comp1) in [(1,comp_i),(2,comp_j)], sub in sr.substrates[idx1] + for (idx2, comp2) in [(1,comp_i),(2,comp_j)], stoich in sr.netstoich[idx2] + jac_prototype[get_index(comp2,stoich[1],nS),get_index(comp1,sub,nS)] = 1 + end + end + end + end + + return jac_prototype +end + + +# Get the rate of a specific reaction. +function get_rate(sr, pE, u_src, u_dst) + product = pE[sr.rate] + for (u,idx) in [(u_src,1),(u_dst,2)] + !isempty(sr.substrates[idx]) && for (sub,stoich) in zip(sr.substrates[idx], sr.substoich[idx]) + product *= u[sub]^stoich / factorial(stoich) + end + end + return product +end + + +function build_odefunction(lrs::LatticeReactionSystem; sparse=true) + ofunc = ODEFunction(convert(ODESystem, lrs.rs)) + nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) + sr_info = [(rate=get_rate(sr, lrs.spatial_reactions), substrate=get_substrate_idxs(sr,lrs.rs), netstoich=get_netstoich_idxs(sr,lrs.rs)) for sr in lrs.spatial_reactions] + + f = build_f(ofunc,nS,nV,sr_info) + jac = build_jac(ofunc,nS,nV,sr_info) + jac_prototype = build_jac_prototype(ofunc,nS,nV,sr_info) + + return ODEFunction(f; jac=jac, jac_prototype=(sparse ? sparse(jac_prototype) : jac_prototype)) +end + +get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species +get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS + + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(parameters::Tuple, spatial_params) = parameters +function split_parameters(parameters::Vector, spatial_params) + filter(p -> !in(p[1], spatial_params), parameters), + filter(p -> in(p[1], spatial_params), parameters) +end + +# Converts species and parameters to matrices form. +matrix_form(input::Matrix, args...) = input +function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) + mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) +end +function matrix_form(input::Vector, n, index_dict) + matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), + n, index_dict) +end + +get_sizes(lrs) = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) + +get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species +get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +fieldnames(typeof(reactions(rs)[1])) +reactions(rs)[1].substrates +first.(reactions(rs)[1].netstoich)[1] + +fieldnames(typeof(rs)) + +fieldnames(typeof(first.(reactions(rs)[1].netstoich)[1])) + +reaction.substrates + +to_sym() +sub_syms = +for comp_i in 1:nV, reaction in reactions(lrs.rs) + for substrate in Symbol.(getfield.(reaction.substrates, :f)), reactant in Symbol.(getfield.(first.(reactions(rs)[1].netstoich), :f)) + jac_p[u_idxs[reactant],u_idxs[substrate]] = 1 + end +end + +for comp_i in 1:nV + for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in lrs.spatial_reactions + for substrate in sr.substrates[1], reactant in first.(sr.netstoich[1]) + + end + end +end + +for sub in sr.substrates[1] + + for ns in sr.netstoich[1] + jpp[get_index(comp_i,ns[1],nS,u_idxs),get_index(comp_i,sub,nS,u_idxs)] = 1 + end +end +for sub in sr.substrates[1], ns in sr.netstoich[2] + jpp[get_index(comp_j,ns[1],nS,u_idxs),get_index(comp_i,sub,nS,u_idxs)] = 1 +end + + +Symbol(first.(reactions(rs)[1].netstoich)[1].f) + +reactions(lrs.rs)[3].substrates + +spatial_params = unique(getfield.(spatial_reactions, :rate)) +pV_in, pE_in = split_parameters(p, spatial_params) +nV, nE = length.([vertices(lattice), edges(lattice)]) +u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) +pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) +pE_idxes = Dict(reverse.(enumerate(spatial_params))) + +u0 + +u0 = matrix_form(u0, nV, u_idxs) +pV = matrix_form(pV_in, nV, pV_idxes) +pE = matrix_form(pE_in, nE, pE_idxes) + +u0_vec = Vector(reshape(u0,1:prod(size(u0)))) + +ofun = ODEFunction(build_f(lrs, u_idxs, pE_idxes); jac=build_jac(lrs, u_idxs, pE_idxes), jac_prototype=build_jac_prototype(lrs, u_idxs, pE_idxes)) + +split_parameters(parameters::Tuple, spatial_params) = parameters +function split_parameters(parameters::Vector, spatial_params) + filter(p -> !in(p[1], spatial_params), parameters), + filter(p -> in(p[1], spatial_params), parameters) +end; + +# Converts species and parameters to matrices form. +matrix_form(input::Matrix, args...) = input +function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) + mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) +end +function matrix_form(input::Vector, n, index_dict) + matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), + n, index_dict) +end; + + + +oprob = ODEProblem(lrs, u0, (0.0,100.0), p) +@btime sol = solve(oprob, Tsit5()) +@btime sol = solve(oprob, Rosenbrock23()) + + +@profview solve(oprob, Tsit5()) +@profview solve(oprob, Rosenbrock23()) + +sizes = 10:10:100 +explit_btimes = [] +implicit_btimes = [] +for s in sizes + rs = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ + end A B + spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] + lattice = Graphs.grid([10, s]) + lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) + + u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] + tspan = (0.0, 100.0) + p = [:A => 1.0, :B => 4.0, :D => 0.2] + + oprob = ODEProblem(lrs, u0, tspan, p) + b1 = (@benchmark solve(oprob, Tsit5())) + b2 = (@benchmark solve(oprob, Rosenbrock23())) + push!(explit_btimes, median(b1.times)) + push!(implicit_btimes, median(b2.times)) +end + + +plot(sizes,explit_btimes; label="Explicit runtimes") +plot!(sizes,implicit_btimes; label="Implicit runtimes") + +s = 10 +rs = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end A B +spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] +lattice = Graphs.grid([s, s]) +lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) + +u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] +tspan = (0.0, 100.0) +p = [:A => 1.0, :B => 4.0, :D => 0.2] + +oprob = ODEProblem(lrs, u0, tspan, p) +b1 = (@benchmark solve(oprob, Tsit5())) + +b2 = (@benchmark solve(oprob, Rosenbrock23())) + +explit_btimes = map(n -> b_timings(n, Tsit5()), N) +implicit_btimes = map(n -> b_timings(n, Rosenbrock23()), N) + +explit_btimes +implicit_btimes + +bts = b_timings(100, Tsit5()) + + +plot(N,explit_btimes; label="Explicit runtimes") +plot!(N,implicit_btimes; label="Implicit runtimes") + +benchmark_timings + +expliti_btimes + +@btime sleep(1) + +b = (@benchmark sleep(0.1)) +median(b.times) +fieldnames(typeof(b)) + +@time sol = solve(oprob, Rosenbrock23()) +plot(sol; idxs=[1,11]) +@time sol = solve(oprob, Tsit5()) +plot(sol; idxs=[1,11]) + +@profview solve(oprob, Rosenbrock23()) +@profview solve(oprob, Tsit5()) + +@time sol = solve(oprob, Tsit5()) +@profview solve(oprob, Tsit5()) + + + + + +spatial_params = unique(getfield.(spatial_reactions, :rate)) +pV_in, pE_in = Catalyst.split_parameters(p, spatial_params) +nV, nE = length.([vertices(lattice), edges(lattice)]) +u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) +pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) +pE_idxes = Dict(reverse.(enumerate(spatial_params))) + +u0_mat = Catalyst.matrix_form(u0, nV, u_idxs) +u0_vec = Vector(reshape(u0_mat,1:prod(size(u0_mat)))) +pV = Catalyst.matrix_form(pV_in, nV, pV_idxes) +pE = Catalyst.matrix_form(pE_in, nE, pE_idxes) + + + +ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) +u = deepcopy(u0_vec) +du = zeros(length(u0_vec)) + +nS = length(states(rs)) + +return function internal___spatial___f(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc((@view du[get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], + sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} + rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) + + for stoich in sr.netstoich[1] + du[get_index(comp_i,stoich[1])] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + du[get_index(comp_j,stoich[1])] += rate * stoich[2] + end + end + end +end +function get_rate(sr, pE, u_src, u_dst, u_idxs, pE_idxes) + product = pE[pE_idxes[sr.rate]] + !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) + product *= u_src[u_idxs[sub]]^stoich / factorial(stoich) + end + !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) + end + return product +end + +fieldnames(typeof(reactions(rs)[1])) + +oprob = ODEProblem(internal___spatial___f, u0_vec, (0.0,100.0), (pV, pE)) +@time sol = solve(oprob, Rosenbrock23()) + +plot(sol; idxs=[1,11]) + +plot(getindex.((sol.u),1)) +plot!(getindex.((sol.u),3)) + +reactions(rs)[2].substrates isa Vector{<:Term} + +typeof(reactions(rs)[2].substrates[1]) +findfirst(isequal.(reactions(rs)[2].substrates[1],states(rs))) + +syms_to_idxs(reactions(rs)[2].substrates, states(rs)) +reactions(rs)[2].substrates + +zip([1,3], spatial_reactions.netstoich) + +spatial_reactions[1].netstoich + +odelingToolkit.var_ +spatial_reactions[1].netstoich + +spatial_reactions[1].substrates + +(1 for i in 1:2) + +sr = spatial_reactions[1] + + + +syms_to_idxs(sr.substrates[2], states(lrs.rs)) + +# For a vector of Symbolics or Symbols, find their indexes in an array. +syms_to_idxs(syms, syms_reference::Vector{<:Term}) = [sym_to_idxs(sym, syms_reference) for sym in syms]::Vector{Int64} +# For a Symbolic or Symbol, find its index in an array. +sym_to_idxs(sym::Term, syms_reference::Vector{<:Term}) = findfirst(isequal.(sym, syms_reference))::Int64 +sym_to_idxs(sym::Symbol, syms_reference::Vector{<:Term}) = findfirst(isequal.(sym, Symbol.(getfield.(syms_reference,:f))))::Int64 + + + + +function build_odefunction(lrs::LatticeReactionSystem; sparse=true) + ofunc = ODEFunction(convert(ODESystem, lrs.rs)) + nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) + sr_info = [(rate=get_rate(sr, lrs.spatial_reactions), substrate=get_substrate_idxs(sr,lrs.rs), netstoich=get_netstoich_idxs(sr,lrs.rs)) for sr in lrs.spatial_reactions] + + f = build_f(ofunc,nS,nV,sr_info) + jac = build_jac(ofunc,nS,nV,sr_info) + jac_prototype = build_jac_prototype(ofunc,nS,nV,sr_info) + + return ODEFunction(f; jac=jac, jac_prototype=(sparse ? sparse(jac_prototype) : jac_prototype)) +end + + + + +# Creates a function for simulating the spatial ODE with spatial reactions. +function build_f(ofunc, nS, nV, sr_info) + + return function(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], + sr in sr_info + + + + + rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + + for stoich in sr.netstoich[1] + du[get_index(comp_i,stoich[1],nS,u_idxs)] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + du[get_index(comp_j,stoich[1],nS,u_idxs)] += rate * stoich[2] + end + end + end + end +end + +function get_rate(sr, pE, u_src, u_dst, pE_idxes) + product = pE[sr.rate] + !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) + product *= u_src[sub]^stoich / factorial(stoich) + end + !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) + end + return product +end + + +return function internal___spatial___jac(J, u, p, t) + J .= zeros(length(u),length(u)) + + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc.jac((@view J[get_indexes(comp_i),get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], + sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} + + for sub in sr.substrates[1] + rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) + for stoich in sr.netstoich[1] + J[get_index(comp_i,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + J[get_index(comp_j,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] + end + end + + for sub in sr.substrates[2] + rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j)]), (@view u[get_indexes(comp_i)]), u_idxs, pE_idxes) + for stoich in sr.netstoich[1] + J[get_index(comp_i,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + J[get_index(comp_j,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] + end + end + end + end +end +get_index(container::Int64,species::Symbol) = (container-1)*nS + u_idxs[species] +get_indexes(container::Int64) = (container-1)*nS+1:container*nS + +# Get the rate differential of a specific reaction. +function get_rate_differential(sr, pE, diff_species, u_src, u_dst, u_idxs, pE_idxes) + product = pE[pE_idxes[sr.rate]] + !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) + (diff_species==sub) && (product *= stoich*u_src[u_idxs[sub]]^(stoich-1) / factorial(stoich)) + (diff_species!=sub) && (product *= u_src[u_idxs[sub]]^stoich / factorial(stoich)) + end + !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) + end + return product +end + + +jpp = zeros(nS*nV,nS*nV) +foreach(i -> jpp[(i-1)*nS+1:i*nS,(i-1)*nS+1:i*nS] = ones(nS,nS), 1:nV) +for comp_i in 1:nV + for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], + sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} + + for sub in sr.substrates[1], ns in sr.netstoich[1] + jpp[get_index(comp_i,ns[1]),get_index(comp_i,sub)] = 1 + end + for sub in sr.substrates[1], ns in sr.netstoich[2] + jpp[get_index(comp_j,ns[1]),get_index(comp_i,sub)] = 1 + end + for sub in sr.substrates[2], ns in sr.netstoich[1] + jpp[get_index(comp_i,ns[1]),get_index(comp_j,sub)] = 1 + end + for sub in sr.substrates[2], ns in sr.netstoich[2] + jpp[get_index(comp_j,ns[1]),get_index(comp_j,sub)] = 1 + end + end +end + +jac_prototype = sparse(jpp) +ofun_jac = ODEFunction(internal___spatial___f; jac=internal___spatial___jac, jac_prototype=jac_prototype) + +oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) +@time sol = solve(oprob, Rosenbrock23()) + +plot(sol; idxs=[1,11]) + +return function jac_2node(J, u, p, t) + A,B = p[1][:,1] + D, = p[2][:,1] + X1,Y1,X2,Y2 = u + J .= zeros(4,4) + + J[1,1] = X1*Y1 - (1+B+D) + J[1,2] = 0.5*X1^2 + J[1,3] = D + + J[2,1] = B - X1*Y1 + J[2,2] = -0.5*X1^2 + + J[3,1] = D + J[3,3] = X2*Y2 - (1+B+D) + J[3,4] = 0.5*X2^2 + + J[4,3] = B - X2*Y2 + J[4,4] = -0.5*X2^2 +end + +ofun_jac = ODEFunction(internal___spatial___f; jac=jac_2node) +oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) +@time sol = solve(oprob, Rosenbrock23()) + +plot(getindex.((sol.u),1)) +plot!(getindex.((sol.u),3)) + +print_m(m) = foreach(i -> println(collect(m)[i,:]), 1:size(m)[1]) + +u_tmp = [2.035941193990462, 2.7893001714815364, 1.650680925465682, 3.1349007948289445] + +J1 = zeros(4,4) +jac_2node(J1, u_tmp, (pV,pE), 0.0) +print_m(J1) + +J2 = zeros(4,4) +internal___spatial___jac(J2, u_tmp, (pV,pE), 0.0) +print_m(J2) + +print_m(J1.-J2) + + +ofun_jac = ODEFunction(internal___spatial___f; jac=internal___spatial___jac) +oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) +@time sol = solve(oprob, Rosenbrock23(); maxiters=100) + +plot(getindex.((sol.u),1)) +plot!(getindex.((sol.u),3)) + + +return function internal___spatial___jac(J, u, p, t) + # Updates for non-spatial reactions. + for comp_i in 1:nV + ofunc.jac((@view J[get_indexes(comp_i),get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) + end + + # Updates for spatial reactions. + for comp_i in 1:nV + for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], + sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} + + for sub in sr.substrates[1] + rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) + for stoich in sr.netstoich[1] + J[get_index(comp_i,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + J[get_index(comp_j,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] + end + end + + for sub in sr.substrates[2] + rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j)]), (@view u[get_indexes(comp_i)]), u_idxs, pE_idxes) + for stoich in sr.netstoich[1] + J[get_index(comp_i,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] + end + for stoich in sr.netstoich[2] + J[get_index(comp_j,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] + end + end + end + end +end + +using FiniteDiff +function my_f(x) + du = zeros(8) + internal___spatial___f(du, x, (pV,pE), 0.0) + return du +end +function my_j(x) + J = zeros(8,8) + internal___spatial___jac(J, x, (pV,pE), 0.0) + return J +end + + +u0_tmp = 100*rand(8) +maximum(FiniteDiff.finite_difference_jacobian(my_f, u0_cpy) .- my_j(u0_cpy)) \ No newline at end of file From e64f67f14167fcee87dba4924401b045f4686f01 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 15:03:32 -0400 Subject: [PATCH 006/134] update tests --- Project.toml | 3 +- .../lattice_reaction_systems.jl | 34 ++++++++++--------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Project.toml b/Project.toml index e38955b5f0..52199a4cb2 100644 --- a/Project.toml +++ b/Project.toml @@ -39,6 +39,7 @@ Unitful = "1.12.4" julia = "1.6" [extras] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -56,4 +57,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["DomainSets", "Graphviz_jll", "LinearAlgebra", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"] +test = ["BenchmarkTools", "DomainSets", "Graphviz_jll", "LinearAlgebra", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"] diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index b8bcd713bd..a5d261385e 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -209,12 +209,13 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.000895 - runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + runtime_target = 0.00089 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end + # Large grid, small, non-stiff, system. let lrs = LatticeReactionSystem(binding_system, binding_srs_2, large_2d_grid) @@ -224,8 +225,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00091 - runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + runtime_target = 0.451 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -239,8 +240,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.086 - runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + runtime_target = 0.05 + runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -254,12 +255,13 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 120.0 - runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + runtime_target = 140.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end + # Small grid, mid-sized, non-stiff, system. let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) @@ -270,7 +272,7 @@ let @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.00293 - runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -284,8 +286,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 1.54 - runtime = minimum((@benchmark solve(oprob, Tsit5())).times)/1000000000 + runtime_target = 1.257 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -299,8 +301,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.0228 - runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + runtime_target = 0.023 + runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -314,8 +316,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 127.0 - runtime = minimum((@benchmark solve(oprob, QNDF())).times)/1000000000 + runtime_target = 111. + runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -342,7 +344,7 @@ pV = binding_p pE = [:dX => 0.1, :dY => 0.2] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) -runtime = minimum((@benchmark solve(oprob, Tsit5())).times) +runtime = minimum((@benchmark solve($oprob, Tsit5())).times) (@benchmark solve(oprob, Tsit5())).times From d91ee2b153c70f0a36d464a7d3e872bfa9b49779 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 16:11:49 -0400 Subject: [PATCH 007/134] test fix --- .../lattice_reaction_systems.jl | 986 +----------------- 1 file changed, 1 insertion(+), 985 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index a5d261385e..aaeb1ad6ac 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -320,988 +320,4 @@ let runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target -end - -states(sigmaB_system) - -sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) -sigmaB_dif_w = DiffusionReaction(:Dw, :w) -sigmaB_dif_v = DiffusionReaction(:v, :v) -sigmaB_srs_1 = [sigmaB_dif_σB] -sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] - - -CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) -CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) -CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) -CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) -CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) - - -lrs = LatticeReactionSystem(binding_system, brusselator_srs_2, small_2d_grid) -u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] -pV = binding_p -pE = [:dX => 0.1, :dY => 0.2] -oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) - -runtime = minimum((@benchmark solve($oprob, Tsit5())).times) - -(@benchmark solve(oprob, Tsit5())).times - -bm = @benchmark solve(oprob, Tsit5()) - -median(bm.times) - - - - - -brusselator_system = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ -end -brusselator_p = [:A => 1.0, :B => 4.0] - -brusselator_dif_x = DiffusionReaction(:dX, :X) -brusselator_dif_y = DiffusionReaction(:dY, :Y) -binding_osr_x2 = OnewaySpatialReaction(:d_ord_1, [:X], [:X], [2], [2]) -brusselator_dif_sr = SpatialReaction(:D, ([:X], [:Y]), ([:Y], [:X]), ([1], [2]), ([1], [1])) -brusselator_srs_1 = [brusselator_dif_x] -brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] -brusselator_srs_3 = [binding_osr_x2] -brusselator_srs_4 = [brusselator_dif_x, brusselator_dif_sr] - - -length(edges(small_directed_cycle)) - -typeof(zeros(0,3)) - -pE = matrix_form(pE_in, nE, pE_idxes) - -zeros(1,0) - -pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) - -lrs.spatial_reactions - -using Test - -grid = [small_2d_grid, short_path, small_directed_cycle][1] -srs = [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4][2] -lrs = LatticeReactionSystem(binding_system, srs, grid) - -SciMLBase.successful_retcode(sol) -sol.retcode - -grid = [small_2d_grid, short_path, small_directed_cycle][1] -srs = [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4][2] -lrs = LatticeReactionSystem(binding_system, srs, grid) - -u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] -u0_2 = [:X => rand_v_vals(grid), :Y => 2.0, :XY => 0.0] -u0_3 = [:X => 1.0, :Y => rand_v_vals(grid), :XY => rand_v_vals(grid)] -u0_4 = [:X => rand_v_vals(grid), :Y => rand_v_vals(grid), :XY => rand_v_vals(grid,3)] -u0_5 = make_u0_matrix(u0_3, grid, map(s -> Symbol(s.f), species(lrs.rs))) -u0 = [u0_1, u0_2, u0_3, u0_4, u0_5][3] - -p1 = [:kB => 2.0, :kD => 0.5] -p2 = [:kB => 2.0, :kD => rand_v_vals(grid)] -p3 = [:kB => rand_v_vals(grid), :kD => rand_v_vals(grid)] -p4 = make_u0_matrix(p1, grid, Symbol.(parameters(lrs.rs))) -p = p3 - -pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) -pE_2 = map(sp -> sp => rand(), lrs.spatial_params) -pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) -pE_4 = make_u0_matrix(pE_3, grid, lrs.spatial_params) -pE = pE_3 - -oprob = ODEProblem(lrs, u0, (0.0,10.0), (p, pE)) -sol = solve(oprob, Tsit5()) - - - - - - - -rs = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ -end A B -spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] -lattice = Graphs.grid([20, 20]) -lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) - -u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] -tspan = (0.0, 100.0) -p = [:A => 1.0, :B => 4.0, :D => 0.2] - -oprob = ODEProblem(lrs, u0, (0.0,100.0), p) -@time sol = solve(oprob, Tsit5()) -@time sol = solve(oprob, QNDF()) - -plot(sol,idxs=[1,11]) - -@profview sol = solve(oprob, Tsit5()) -@profview sol = solve(oprob, Rosenbrock23()) - -@btime solve(oprob, Tsit5()) -@btime solve(oprob, QNDF()) - - -sizes = 10:10:200 -explit_btimes = [] -implicit_btimes = [] -@time for s in sizes - println("Running for size $s.") - rs = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ - end A B - spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] - lattice = Graphs.grid([10, s]) - lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) - - u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] - tspan = (0.0, 100.0) - p = [:A => 1.0, :B => 4.0, :D => 0.2] - - oprob = ODEProblem(lrs, u0, (0.0,100.0), p) - b1 = (@benchmark solve(oprob, Tsit5())) - b2 = (@benchmark solve(oprob, QNDF())) - push!(explit_btimes, median(b1.times)) - push!(implicit_btimes, median(b2.times)) - println("Explicit runtim: $(median(b1.times)/1000000000)") - println("Implicit runtim: $(median(b2.times)/1000000000)\n") -end - -plot(sizes,explit_btimes ./1000000000; label="Explicit runtimes") -plot(sizes,implicit_btimes ./1000000000; label="Implicit runtimes") -plot!(yaxis=:log10) - - -plot(sizes,implicit_btimes ./ explit_btimes; label="Explicit runtimes") - - -plot((explit_btimes ./1000000000) ./ sizes; label="Explicit runtimes") -plot((implicit_btimes ./1000000000)./ sizes; label="Implicit runtimes") - - -u_tmp = [1.0, 2.0] -p = [1.0, 4.0] -ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) -J = zeros(2,2) -ofunc(J,u_tmp,p,0.0) -J - -using SparseArrays -J = [1.0 0; 1 0] -J2 = sparse(J) -ofunc(J2,u_tmp,p,0.0) -J2 - - - - - - - - - - -spatial_params = unique(getfield.(spatial_reactions, :rate)) -pV_in, pE_in = Catalyst.split_parameters(p, spatial_params) -u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) -pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) -pE_idxes = Dict(reverse.(enumerate(spatial_params))) - -nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) -u0 = Vector(reshape(Catalyst.matrix_form(u0, nV, u_idxs),1:nS*nV)) - -pV = Catalyst.matrix_form(pV_in, nV, pV_idxes) -pE = Catalyst.matrix_form(pE_in, nE, pE_idxes) - -ofun = Catalyst.build_odefunction(lrs, true, spatial_params) - -supertype(typeof(ofun)) -ofun isa SciMLBase.AbstractODEFunction{true} - - - -matrix_form(input::Matrix, args...) = input -function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) - mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) -end -function matrix_form(input::Vector, n, index_dict) - matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), - n, index_dict) -end - -1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -spatial_params = unique(getfield.(spatial_reactions, :rate)) -pV_in, pE_in = split_parameters(p, spatial_params) -u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) -pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) -pE_idxes = Dict(reverse.(enumerate(spatial_params))) - -nS, nV, nE = get_sizes(lrs) -u0 = Vector(reshape(matrix_form(u0, nV, u_idxs),1:nS*nV)) - -pV = matrix_form(pV_in, nV, pV_idxes) -pE = matrix_form(pE_in, nE, pE_idxes) - -# As a spatial reaction, but replaces the species (and parameter) symbols with their index. -struct SpatialReactionIndexed - rate::Int64 - substrates::Tuple{Vector{Int64}, Vector{Int64}} - products::Tuple{Vector{Int64}, Vector{Int64}} - substoich::Tuple{Vector{Int64}, Vector{Int64}} - prodstoich::Tuple{Vector{Int64}, Vector{Int64}} - netstoich::Tuple{Vector{Pair{Int64,Int64}}, Vector{Pair{Int64,Int64}}} - only_use_rate::Bool - - function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}) - get_s_idx(species::Symbol) = findfirst(species .== (species_list)) - rate = findfirst(sr.rate .== (param_list)) - substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) - products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) - netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i])) for i in 1:2]) - new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, sr.only_use_rate) - end -end - - - -ofunc = ODEFunction(convert(ODESystem, lrs.rs)) -nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) -srs_idxed = [SpatialReactionIndexed(sr, Symbol.(getfield.(states(rs), :f)), spatial_params) for sr in spatial_reactions] - -f = build_f(ofunc,nS,nV,srs_idxed) -jac = build_jac(ofunc,nS,nV,srs_idxed) -jac_prototype = build_jac_prototype(nS,nV,srs_idxed) - -ofun = ODEFunction(f; jac=jac, jac_prototype=(true ? sparse(jac_prototype) : jac_prototype)) - -ODEProblem(ofun, u0, tspan, (pV, pE)) - -function build_f(ofunc,nS,nV,srs_idxed) - - return function(du, u, p, t) - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed - rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) - for (comp,idx) in [(comp_i,1),(comp_j,2)] - for stoich in sr.netstoich[idx] - du[get_index(comp,stoich[idx],nS)] += rate * stoich[2] - end - end - end - end - end -end - -function build_jac(ofunc,nS,nV,srs_idxed) - base_zero = zeros(nS*nV,nS*nV) - - return function(J, u, p, t) - J .= base_zero - - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc.jac((@view J[get_indexes(comp_i,nS),get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed - for (idx1, comp1) in [(1,comp_i),(2,comp_j)], sub in sr.substrates[idx1] - rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) - for (idx2, comp2) in [(1,comp_i),(2,comp_j)], stoich in sr.netstoich[idx2] - J[get_index(comp2,stoich[1],nS,u_idxs),get_index(comp1,sub,nS,u_idxs)] += rate * stoich[2] - end - end - end - end - end -end - -function get_rate_differential(sr, pE, diff_species, u_src, u_dst) - product = pE[sr.rate] - !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) - if diff_species==sub - product *= stoich*u_src[sub]^(stoich-1) / factorial(stoich) - else - product *= u_src[sub]^stoich / factorial(stoich) - end - end - !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[sub]^stoich / factorial(stoich) - end - return product -end - -findfirst(isequal(reactions(rs)[1].netstoich[1][1]), states(rs)) - -function build_jac_prototype(nS,nV,srs_idxed) - jac_prototype = zeros(nS*nV,nS*nV) - - # Sets non-spatial reactions. - for comp_i in 1:nV, reaction in reactions(lrs.rs) - for substrate in reaction.substrates, ns in reaction.netstoich - sub_idx = findfirst(isequal(substrate), states(lrs.rs)) - spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) - jac_prototype[spec_idx, sub_idx] = 1 - end - end - - for comp_i in 1:nV - for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in srs_idxed - for (idx1, comp1) in [(1,comp_i),(2,comp_j)], sub in sr.substrates[idx1] - for (idx2, comp2) in [(1,comp_i),(2,comp_j)], stoich in sr.netstoich[idx2] - jac_prototype[get_index(comp2,stoich[1],nS),get_index(comp1,sub,nS)] = 1 - end - end - end - end - - return jac_prototype -end - - -# Get the rate of a specific reaction. -function get_rate(sr, pE, u_src, u_dst) - product = pE[sr.rate] - for (u,idx) in [(u_src,1),(u_dst,2)] - !isempty(sr.substrates[idx]) && for (sub,stoich) in zip(sr.substrates[idx], sr.substoich[idx]) - product *= u[sub]^stoich / factorial(stoich) - end - end - return product -end - - -function build_odefunction(lrs::LatticeReactionSystem; sparse=true) - ofunc = ODEFunction(convert(ODESystem, lrs.rs)) - nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - sr_info = [(rate=get_rate(sr, lrs.spatial_reactions), substrate=get_substrate_idxs(sr,lrs.rs), netstoich=get_netstoich_idxs(sr,lrs.rs)) for sr in lrs.spatial_reactions] - - f = build_f(ofunc,nS,nV,sr_info) - jac = build_jac(ofunc,nS,nV,sr_info) - jac_prototype = build_jac_prototype(ofunc,nS,nV,sr_info) - - return ODEFunction(f; jac=jac, jac_prototype=(sparse ? sparse(jac_prototype) : jac_prototype)) -end - -get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species -get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS - - -# Splits parameters into those for the compartments and those for the connections. -split_parameters(parameters::Tuple, spatial_params) = parameters -function split_parameters(parameters::Vector, spatial_params) - filter(p -> !in(p[1], spatial_params), parameters), - filter(p -> in(p[1], spatial_params), parameters) -end - -# Converts species and parameters to matrices form. -matrix_form(input::Matrix, args...) = input -function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) - mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) -end -function matrix_form(input::Vector, n, index_dict) - matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), - n, index_dict) -end - -get_sizes(lrs) = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - -get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species -get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -fieldnames(typeof(reactions(rs)[1])) -reactions(rs)[1].substrates -first.(reactions(rs)[1].netstoich)[1] - -fieldnames(typeof(rs)) - -fieldnames(typeof(first.(reactions(rs)[1].netstoich)[1])) - -reaction.substrates - -to_sym() -sub_syms = -for comp_i in 1:nV, reaction in reactions(lrs.rs) - for substrate in Symbol.(getfield.(reaction.substrates, :f)), reactant in Symbol.(getfield.(first.(reactions(rs)[1].netstoich), :f)) - jac_p[u_idxs[reactant],u_idxs[substrate]] = 1 - end -end - -for comp_i in 1:nV - for comp_j in (lrs.lattice.fadjlist)[comp_i], sr in lrs.spatial_reactions - for substrate in sr.substrates[1], reactant in first.(sr.netstoich[1]) - - end - end -end - -for sub in sr.substrates[1] - - for ns in sr.netstoich[1] - jpp[get_index(comp_i,ns[1],nS,u_idxs),get_index(comp_i,sub,nS,u_idxs)] = 1 - end -end -for sub in sr.substrates[1], ns in sr.netstoich[2] - jpp[get_index(comp_j,ns[1],nS,u_idxs),get_index(comp_i,sub,nS,u_idxs)] = 1 -end - - -Symbol(first.(reactions(rs)[1].netstoich)[1].f) - -reactions(lrs.rs)[3].substrates - -spatial_params = unique(getfield.(spatial_reactions, :rate)) -pV_in, pE_in = split_parameters(p, spatial_params) -nV, nE = length.([vertices(lattice), edges(lattice)]) -u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) -pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) -pE_idxes = Dict(reverse.(enumerate(spatial_params))) - -u0 - -u0 = matrix_form(u0, nV, u_idxs) -pV = matrix_form(pV_in, nV, pV_idxes) -pE = matrix_form(pE_in, nE, pE_idxes) - -u0_vec = Vector(reshape(u0,1:prod(size(u0)))) - -ofun = ODEFunction(build_f(lrs, u_idxs, pE_idxes); jac=build_jac(lrs, u_idxs, pE_idxes), jac_prototype=build_jac_prototype(lrs, u_idxs, pE_idxes)) - -split_parameters(parameters::Tuple, spatial_params) = parameters -function split_parameters(parameters::Vector, spatial_params) - filter(p -> !in(p[1], spatial_params), parameters), - filter(p -> in(p[1], spatial_params), parameters) -end; - -# Converts species and parameters to matrices form. -matrix_form(input::Matrix, args...) = input -function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) - mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) -end -function matrix_form(input::Vector, n, index_dict) - matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), - n, index_dict) -end; - - - -oprob = ODEProblem(lrs, u0, (0.0,100.0), p) -@btime sol = solve(oprob, Tsit5()) -@btime sol = solve(oprob, Rosenbrock23()) - - -@profview solve(oprob, Tsit5()) -@profview solve(oprob, Rosenbrock23()) - -sizes = 10:10:100 -explit_btimes = [] -implicit_btimes = [] -for s in sizes - rs = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ - end A B - spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] - lattice = Graphs.grid([10, s]) - lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) - - u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] - tspan = (0.0, 100.0) - p = [:A => 1.0, :B => 4.0, :D => 0.2] - - oprob = ODEProblem(lrs, u0, tspan, p) - b1 = (@benchmark solve(oprob, Tsit5())) - b2 = (@benchmark solve(oprob, Rosenbrock23())) - push!(explit_btimes, median(b1.times)) - push!(implicit_btimes, median(b2.times)) -end - - -plot(sizes,explit_btimes; label="Explicit runtimes") -plot!(sizes,implicit_btimes; label="Implicit runtimes") - -s = 10 -rs = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ -end A B -spatial_reactions = [SpatialReaction(:D, ([:X], []), ([], [:X]), ([1], Vector{Int64}()), (Vector{Int64}(), [1]))] -lattice = Graphs.grid([s, s]) -lrs = LatticeReactionSystem(rs, spatial_reactions, lattice) - -u0 = [:X => 10 * rand(nv(lattice)), :Y => 10 * rand(nv(lattice))] -tspan = (0.0, 100.0) -p = [:A => 1.0, :B => 4.0, :D => 0.2] - -oprob = ODEProblem(lrs, u0, tspan, p) -b1 = (@benchmark solve(oprob, Tsit5())) - -b2 = (@benchmark solve(oprob, Rosenbrock23())) - -explit_btimes = map(n -> b_timings(n, Tsit5()), N) -implicit_btimes = map(n -> b_timings(n, Rosenbrock23()), N) - -explit_btimes -implicit_btimes - -bts = b_timings(100, Tsit5()) - - -plot(N,explit_btimes; label="Explicit runtimes") -plot!(N,implicit_btimes; label="Implicit runtimes") - -benchmark_timings - -expliti_btimes - -@btime sleep(1) - -b = (@benchmark sleep(0.1)) -median(b.times) -fieldnames(typeof(b)) - -@time sol = solve(oprob, Rosenbrock23()) -plot(sol; idxs=[1,11]) -@time sol = solve(oprob, Tsit5()) -plot(sol; idxs=[1,11]) - -@profview solve(oprob, Rosenbrock23()) -@profview solve(oprob, Tsit5()) - -@time sol = solve(oprob, Tsit5()) -@profview solve(oprob, Tsit5()) - - - - - -spatial_params = unique(getfield.(spatial_reactions, :rate)) -pV_in, pE_in = Catalyst.split_parameters(p, spatial_params) -nV, nE = length.([vertices(lattice), edges(lattice)]) -u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) -pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) -pE_idxes = Dict(reverse.(enumerate(spatial_params))) - -u0_mat = Catalyst.matrix_form(u0, nV, u_idxs) -u0_vec = Vector(reshape(u0_mat,1:prod(size(u0_mat)))) -pV = Catalyst.matrix_form(pV_in, nV, pV_idxes) -pE = Catalyst.matrix_form(pE_in, nE, pE_idxes) - - - -ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) -u = deepcopy(u0_vec) -du = zeros(length(u0_vec)) - -nS = length(states(rs)) - -return function internal___spatial___f(du, u, p, t) - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc((@view du[get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], - sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} - rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) - - for stoich in sr.netstoich[1] - du[get_index(comp_i,stoich[1])] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - du[get_index(comp_j,stoich[1])] += rate * stoich[2] - end - end - end -end -function get_rate(sr, pE, u_src, u_dst, u_idxs, pE_idxes) - product = pE[pE_idxes[sr.rate]] - !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) - product *= u_src[u_idxs[sub]]^stoich / factorial(stoich) - end - !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) - end - return product -end - -fieldnames(typeof(reactions(rs)[1])) - -oprob = ODEProblem(internal___spatial___f, u0_vec, (0.0,100.0), (pV, pE)) -@time sol = solve(oprob, Rosenbrock23()) - -plot(sol; idxs=[1,11]) - -plot(getindex.((sol.u),1)) -plot!(getindex.((sol.u),3)) - -reactions(rs)[2].substrates isa Vector{<:Term} - -typeof(reactions(rs)[2].substrates[1]) -findfirst(isequal.(reactions(rs)[2].substrates[1],states(rs))) - -syms_to_idxs(reactions(rs)[2].substrates, states(rs)) -reactions(rs)[2].substrates - -zip([1,3], spatial_reactions.netstoich) - -spatial_reactions[1].netstoich - -odelingToolkit.var_ -spatial_reactions[1].netstoich - -spatial_reactions[1].substrates - -(1 for i in 1:2) - -sr = spatial_reactions[1] - - - -syms_to_idxs(sr.substrates[2], states(lrs.rs)) - -# For a vector of Symbolics or Symbols, find their indexes in an array. -syms_to_idxs(syms, syms_reference::Vector{<:Term}) = [sym_to_idxs(sym, syms_reference) for sym in syms]::Vector{Int64} -# For a Symbolic or Symbol, find its index in an array. -sym_to_idxs(sym::Term, syms_reference::Vector{<:Term}) = findfirst(isequal.(sym, syms_reference))::Int64 -sym_to_idxs(sym::Symbol, syms_reference::Vector{<:Term}) = findfirst(isequal.(sym, Symbol.(getfield.(syms_reference,:f))))::Int64 - - - - -function build_odefunction(lrs::LatticeReactionSystem; sparse=true) - ofunc = ODEFunction(convert(ODESystem, lrs.rs)) - nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - sr_info = [(rate=get_rate(sr, lrs.spatial_reactions), substrate=get_substrate_idxs(sr,lrs.rs), netstoich=get_netstoich_idxs(sr,lrs.rs)) for sr in lrs.spatial_reactions] - - f = build_f(ofunc,nS,nV,sr_info) - jac = build_jac(ofunc,nS,nV,sr_info) - jac_prototype = build_jac_prototype(ofunc,nS,nV,sr_info) - - return ODEFunction(f; jac=jac, jac_prototype=(sparse ? sparse(jac_prototype) : jac_prototype)) -end - - - - -# Creates a function for simulating the spatial ODE with spatial reactions. -function build_f(ofunc, nS, nV, sr_info) - - return function(du, u, p, t) - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], - sr in sr_info - - - - - rate = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) - - for stoich in sr.netstoich[1] - du[get_index(comp_i,stoich[1],nS,u_idxs)] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - du[get_index(comp_j,stoich[1],nS,u_idxs)] += rate * stoich[2] - end - end - end - end -end - -function get_rate(sr, pE, u_src, u_dst, pE_idxes) - product = pE[sr.rate] - !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) - product *= u_src[sub]^stoich / factorial(stoich) - end - !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) - end - return product -end - - -return function internal___spatial___jac(J, u, p, t) - J .= zeros(length(u),length(u)) - - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc.jac((@view J[get_indexes(comp_i),get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], - sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} - - for sub in sr.substrates[1] - rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) - for stoich in sr.netstoich[1] - J[get_index(comp_i,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - J[get_index(comp_j,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] - end - end - - for sub in sr.substrates[2] - rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j)]), (@view u[get_indexes(comp_i)]), u_idxs, pE_idxes) - for stoich in sr.netstoich[1] - J[get_index(comp_i,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - J[get_index(comp_j,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] - end - end - end - end -end -get_index(container::Int64,species::Symbol) = (container-1)*nS + u_idxs[species] -get_indexes(container::Int64) = (container-1)*nS+1:container*nS - -# Get the rate differential of a specific reaction. -function get_rate_differential(sr, pE, diff_species, u_src, u_dst, u_idxs, pE_idxes) - product = pE[pE_idxes[sr.rate]] - !isempty(sr.substrates[1]) && for (sub,stoich) in zip(sr.substrates[1], sr.substoich[1]) - (diff_species==sub) && (product *= stoich*u_src[u_idxs[sub]]^(stoich-1) / factorial(stoich)) - (diff_species!=sub) && (product *= u_src[u_idxs[sub]]^stoich / factorial(stoich)) - end - !isempty(sr.substrates[2]) && for (sub,stoich) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[u_idxs[sub]]^stoich / factorial(stoich) - end - return product -end - - -jpp = zeros(nS*nV,nS*nV) -foreach(i -> jpp[(i-1)*nS+1:i*nS,(i-1)*nS+1:i*nS] = ones(nS,nS), 1:nV) -for comp_i in 1:nV - for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], - sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} - - for sub in sr.substrates[1], ns in sr.netstoich[1] - jpp[get_index(comp_i,ns[1]),get_index(comp_i,sub)] = 1 - end - for sub in sr.substrates[1], ns in sr.netstoich[2] - jpp[get_index(comp_j,ns[1]),get_index(comp_i,sub)] = 1 - end - for sub in sr.substrates[2], ns in sr.netstoich[1] - jpp[get_index(comp_i,ns[1]),get_index(comp_j,sub)] = 1 - end - for sub in sr.substrates[2], ns in sr.netstoich[2] - jpp[get_index(comp_j,ns[1]),get_index(comp_j,sub)] = 1 - end - end -end - -jac_prototype = sparse(jpp) -ofun_jac = ODEFunction(internal___spatial___f; jac=internal___spatial___jac, jac_prototype=jac_prototype) - -oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) -@time sol = solve(oprob, Rosenbrock23()) - -plot(sol; idxs=[1,11]) - -return function jac_2node(J, u, p, t) - A,B = p[1][:,1] - D, = p[2][:,1] - X1,Y1,X2,Y2 = u - J .= zeros(4,4) - - J[1,1] = X1*Y1 - (1+B+D) - J[1,2] = 0.5*X1^2 - J[1,3] = D - - J[2,1] = B - X1*Y1 - J[2,2] = -0.5*X1^2 - - J[3,1] = D - J[3,3] = X2*Y2 - (1+B+D) - J[3,4] = 0.5*X2^2 - - J[4,3] = B - X2*Y2 - J[4,4] = -0.5*X2^2 -end - -ofun_jac = ODEFunction(internal___spatial___f; jac=jac_2node) -oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) -@time sol = solve(oprob, Rosenbrock23()) - -plot(getindex.((sol.u),1)) -plot!(getindex.((sol.u),3)) - -print_m(m) = foreach(i -> println(collect(m)[i,:]), 1:size(m)[1]) - -u_tmp = [2.035941193990462, 2.7893001714815364, 1.650680925465682, 3.1349007948289445] - -J1 = zeros(4,4) -jac_2node(J1, u_tmp, (pV,pE), 0.0) -print_m(J1) - -J2 = zeros(4,4) -internal___spatial___jac(J2, u_tmp, (pV,pE), 0.0) -print_m(J2) - -print_m(J1.-J2) - - -ofun_jac = ODEFunction(internal___spatial___f; jac=internal___spatial___jac) -oprob = ODEProblem(ofun_jac, u0_vec, (0.0,100.0), (pV, pE)) -@time sol = solve(oprob, Rosenbrock23(); maxiters=100) - -plot(getindex.((sol.u),1)) -plot!(getindex.((sol.u),3)) - - -return function internal___spatial___jac(J, u, p, t) - # Updates for non-spatial reactions. - for comp_i in 1:nV - ofunc.jac((@view J[get_indexes(comp_i),get_indexes(comp_i)]), (@view u[get_indexes(comp_i)]), p[1][:,comp_i], t) - end - - # Updates for spatial reactions. - for comp_i in 1:nV - for comp_j::Int64 in (lrs.lattice.fadjlist::Vector{Vector{Int64}})[comp_i], - sr::SpatialReaction in lrs.spatial_reactions::Vector{SpatialReaction} - - for sub in sr.substrates[1] - rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i)]), (@view u[get_indexes(comp_j)]), u_idxs, pE_idxes) - for stoich in sr.netstoich[1] - J[get_index(comp_i,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - J[get_index(comp_j,stoich[1]),get_index(comp_i,sub)] += rate * stoich[2] - end - end - - for sub in sr.substrates[2] - rate = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j)]), (@view u[get_indexes(comp_i)]), u_idxs, pE_idxes) - for stoich in sr.netstoich[1] - J[get_index(comp_i,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] - end - for stoich in sr.netstoich[2] - J[get_index(comp_j,stoich[1]),get_index(comp_j,sub)] += rate * stoich[2] - end - end - end - end -end - -using FiniteDiff -function my_f(x) - du = zeros(8) - internal___spatial___f(du, x, (pV,pE), 0.0) - return du -end -function my_j(x) - J = zeros(8,8) - internal___spatial___jac(J, x, (pV,pE), 0.0) - return J -end - - -u0_tmp = 100*rand(8) -maximum(FiniteDiff.finite_difference_jacobian(my_f, u0_cpy) .- my_j(u0_cpy)) \ No newline at end of file +end \ No newline at end of file From 0d58d6c5333656fd8469c137e8184ca8875cca61 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 28 Jun 2023 17:54:09 -0400 Subject: [PATCH 008/134] update --- src/Catalyst.jl | 4 +- src/lattice_reaction_system_diffusion.jl | 209 ++++++++++++++++++ ....jl => lattice_reaction_system_general.jl} | 102 ++++++--- .../lattice_reaction_systems.jl | 88 ++++---- 4 files changed, 331 insertions(+), 72 deletions(-) create mode 100644 src/lattice_reaction_system_diffusion.jl rename src/{lattice_reaction_system.jl => lattice_reaction_system_general.jl} (70%) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 798759a4f9..162a3cc835 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -70,8 +70,8 @@ include("registered_functions.jl") export mm, mmr, hill, hillr, hillar # spatial reaction networks -include("lattice_reaction_system.jl") -export SpatialReaction, DiffusionReaction, OnewaySpatialReaction +include("lattice_reaction_system_diffusion.jl") +export DiffusionReaction export LatticeReactionSystem # functions to query network properties diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl new file mode 100644 index 0000000000..055ff8e427 --- /dev/null +++ b/src/lattice_reaction_system_diffusion.jl @@ -0,0 +1,209 @@ +### Spatial Reaction Structure. ### + +# Abstract spatial reaction structures. +abstract type AbstractSpatialReaction end + +# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. +# Currently only permit constant rates. +struct DiffusionReaction <: AbstractSpatialReaction + """The rate function (excluding mass action terms). Currentl only constants supported""" + rate::Symbol + """The species that is subject to difusion.""" + species::Symbol +end + +### Lattice Reaction Network Structure ### +# Desribes a spatial reaction network over a graph. +struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs + """The reaction system within each comaprtment.""" + rs::ReactionSystem + """The spatial reactions defined between individual nodes.""" + spatial_reactions::Vector{<:AbstractSpatialReaction} + """The graph on which the lattice is defined.""" + lattice::DiGraph + + # Derrived values. + """A list of parameters that occur in the spatial reactions.""" + spatial_params::Vector{Symbol} + """The number of compartments.""" + nC::Int64 + """The number of species.""" + nS::Int64 + + function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) + return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), length(species(rs))) + end + function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice)) + end +end + +### ODEProblem ### +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; + jac=true, sparse=true, kwargs...) + + u0 = resort_values(u0_in, [Symbol(s.f) for s in species(lrs.rs)]) + u0 = [get_component_value(u0, species, comp) for comp in 1:lrs.nC for species in 1:lrs.nS] + pV, pE = split_parameters(p_in, lrs.spatial_params) + pV = resort_values(pV, Symbol.(parameters(lrs.rs))) + pE = resort_values(pE, lrs.spatial_params) + + ofun = build_odefunction(lrs, pV, pE, jac, sparse) + return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) +end + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(ps::Tuple{<:Any, <:Any}, spatial_params::Vector{Symbol}) = ps +split_parameters(ps::Vector{<:Pair}, spatial_params::Vector{Symbol}) = (filter(p->!(p[1] in spatial_params), ps), filter(p->p[1] in spatial_params, ps)) +split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) = (ps[1:length(ps)-length(spatial_params)], ps[length(ps)-length(spatial_params)+1:end]) +# Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. +resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) = last.(sort(values; by=val -> findfirst(val[1].==symbols))) +resort_values(values::Any, symbols::Vector{Symbol}) = values + +# Builds an ODEFunction. +function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=use_jac, sparse=false) + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac=use_jac, sparse=true) + diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) for s in getfield.(lrs.spatial_reactions,:species)] + + f = build_f(ofunc, pV, pE, diffusion_species, lrs) + jac_prototype = (use_jac ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero=true) : nothing) + jac = (use_jac ? build_jac(ofunc, pV, pE, diffusion_species, lrs, jac_prototype; sparse=sparse) : nothing) + return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) +end + +# Creates a function for simulating the spatial ODE with spatial reactions. +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem) + leaving_rates = zeros(length(diffusion_species), lrs.nC) + for (s_idx,species) in enumerate(diffusion_species), (e_idx, e) in enumerate(edges(lrs.lattice)) + leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) + end + p_base = deepcopy(first.(pV)) + p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + + return function(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:lrs.nC + ofunc((@view du[get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + end + + for (s_idx::Int64,species::Int64) in enumerate(diffusion_species) + for comp_i::Int64 in 1:lrs.nC + du[get_index(comp_i,species,lrs.nS)] -= leaving_rates[s_idx, comp_i] * u[get_index(comp_i,species,lrs.nS)] + end + for (e_idx::Int64,edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges + du[get_index(edge.dst,species,lrs.nS)] += get_component_value(pE, s_idx, e_idx) * u[get_index(edge.src,species,lrs.nS)] + end + end + end +end + +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem, jac_prototype::SparseMatrixCSC{Float64, Int64}; sparse=true) + p_base = deepcopy(first.(pV)) + p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] + + if sparse + return function(J, u, p, t) + # Sets the base according to the spatial reactions. + J.nzval .= jac_prototype.nzval + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:lrs.nC + ofunc.jac((@view J[get_indexes(comp_i,lrs.nS),get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + end + end + else + jac_diffusion = Matrix(jac_prototype) + return function(J, u, p, t) + # Sets the base according to the spatial reactions. + J .= jac_diffusion + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:lrs.nC + ofunc.jac((@view J[get_indexes(comp_i,lrs.nS),get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + end + end + end +end + +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem; set_nonzero=false) + only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype,s,s) for s in 1:lrs.nS] + + # Declares sparse array content. + J_colptr = fill(1,lrs.nC*lrs.nS+1) + J_nzval = fill(0.0, lrs.nC*(nnz(ns_jac_prototype) + count(only_diff)) + length(edges(lrs.lattice))*length(diffusion_species)) + J_rowval = fill(0, length(J_nzval)) + + # Finds filled elements. + for comp in 1:lrs.nC, s in 1:lrs.nS + col_idx = get_index(comp, s, lrs.nS) + + # Column values. + local_elements = in(s, diffusion_species)*(length(lrs.lattice.fadjlist[comp]) + only_diff[s]) + diffusion_elements = -(ns_jac_prototype.colptr[s+1:-1:s]...) + J_colptr[col_idx+1] = J_colptr[col_idx] + local_elements + diffusion_elements + + # Row values. + rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:ns_jac_prototype.colptr[s+1]-1] .+ (comp-1)*lrs.nS + if in(s, diffusion_species) + diffusion_rows = (lrs.lattice.fadjlist[comp] .-1) .* lrs.nS .+ s + split_idx = findfirst(diffusion_rows .> rows[1]) + isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) + rows = vcat(diffusion_rows[1:split_idx-1],rows,diffusion_rows[split_idx:end]) + if only_diff[s] + split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) + isnothing(split_idx) && (split_idx = length(rows) + 1) + insert!(rows, split_idx, get_index(comp, s, lrs.nS)) + end + end + J_rowval[J_colptr[col_idx]:J_colptr[col_idx+1]-1] = rows + end + + # Set element values. + if set_nonzero + J_nzval .= 1.0 + else + for (s_idx,s) in enumerate(diffusion_species), (e_idx,edge) in enumerate(edges(lrs.lattice)) + col_start = J_colptr[get_index(edge.src, s, lrs.nS)] + col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 + column_view = @view J_rowval[col_start:col_end] + + # Updates the source value. + val_idx_src = col_start + findfirst(column_view .== get_index(edge.src, s_idx, lrs.nS)) - 1 + J_nzval[val_idx_src] -= get_component_value(pE, s_idx, e_idx) + + # Updates the destination value. + val_idx_dst = col_start + findfirst(column_view .== get_index(edge.dst, s_idx, lrs.nS)) - 1 + J_nzval[val_idx_dst] += get_component_value(pE, s_idx, e_idx) + end + end + + return SparseMatrixCSC(lrs.nS*lrs.nC, lrs.nS*lrs.nC, J_colptr, J_rowval, J_nzval) +end + +# Gets the index of a species in the u array. +get_index(comp::Int64,species::Int64, nS::Int64) = (comp-1)*nS + species +# Gets the indexes of a compartment's species in the u array. +get_indexes(comp::Int64, nS::Int64) = (comp-1)*nS+1:comp*nS + +# For set of values (stoed in a variety of possible forms), a given component (species or parameter), and a place (eitehr a compartment or edge), find that components value at that place. +get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) = vals[component, place] +get_component_value(vals::Vector, component::Int64, place::Int64) = get_component_value(vals[component], place) +get_component_value(vals::Vector{Float64}, place::Int64) = vals[place] +get_component_value(vals::Float64, place::Int64) = vals + +# Updated the base parameter vector with the values for a specific compartment. +make_p_vector!(p_base, p::Matrix{Float64}, p_update_idx, comp) = p[:, comp] +function make_p_vector!(p_base, p, p_update_idx, comp_i) + for idx in p_update_idx + p_base[idx] = p[idx][comp_i] + end + return p_base +end + +# Gets all the values in a specific palce. +get_component_values(vals::Matrix{Float64}, place::Int64, nComponents::Int64) = (@view vals[:, place]) +get_component_values(vals, place::Int64, nComponents::Int64) = [get_component_value(vals, component, place) for component in 1:nComponents] diff --git a/src/lattice_reaction_system.jl b/src/lattice_reaction_system_general.jl similarity index 70% rename from src/lattice_reaction_system.jl rename to src/lattice_reaction_system_general.jl index 7bb4717c3b..f79a1a4527 100644 --- a/src/lattice_reaction_system.jl +++ b/src/lattice_reaction_system_general.jl @@ -1,8 +1,13 @@ +### CURRENTLY NOT IN USE ### + + ### Spatial Reaction Structure. ### # Describing a spatial reaction that involves species from two neighbouring compartments. # Currently only permit constant rate. -struct SpatialReaction - """The rate function (excluding mass action terms). Currentl only cosntants supported""" + +# A general spatial reaction. +struct SpatialReaction <: AbstractSpatialReaction + """The rate function (excluding mass action terms). Currentl only constants supported""" rate::Symbol """Reaction substrates (source and destination).""" substrates::Tuple{Vector{Symbol}, Vector{Symbol}} @@ -36,15 +41,20 @@ struct SpatialReactionIndexed substoich::Tuple{Vector{Int64}, Vector{Int64}} prodstoich::Tuple{Vector{Int64}, Vector{Int64}} netstoich::Tuple{Vector{Pair{Int64,Int64}}, Vector{Pair{Int64,Int64}}} + netstoich_new::Tuple{Dict{Int64,Int64},Dict{Int64,Int64}} only_use_rate::Bool - function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}) + function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}; reverse_direction=false) get_s_idx(species::Symbol) = findfirst(species .== (species_list)) rate = findfirst(sr.rate .== (param_list)) substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i])) for i in 1:2]) - new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, sr.only_use_rate) + netstoich_new = Tuple([Dict(Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i]))) for i in 1:2]) + if reverse_direction + return new(rate, reverse.([substrates, products, sr.substoich, sr.prodstoich, netstoich, netstoich_new])..., sr.only_use_rate) + end + new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, netstoich_new, sr.only_use_rate) end end @@ -105,7 +115,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, pE_idxes = Dict(reverse.(enumerate(lrs.spatial_params))) nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - u0 = Vector(reshape(matrix_form(u0, nV, u_idxs),1:nS*nV)) + u0 = Vector(reshape(matrix_form(u0, nV, u_idxs)',1:nS*nV)) pV = matrix_form(pV_in, nV, pV_idxes) pE = matrix_form(pE_in, nE, pE_idxes) @@ -137,10 +147,20 @@ end function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bool) ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) nS,nV = length.([states(lrs.rs), vertices(lrs.lattice)]) + + species_list = map(s -> Symbol(s.f), species(lrs.rs)) spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params) for sr in lrs.spatial_reactions] + spatial_reactions_reversed = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params; reverse_direction=true) for sr in lrs.spatial_reactions] + spatial_reactions_dict = Dict{Int64,Vector{SpatialReactionIndexed}}([i => Vector{SpatialReactionIndexed}() for i in 1:nS]) + for sr in spatial_reactions, sub in sr.substrates[1] + push!(spatial_reactions_dict[sub], sr) + end + for sr in spatial_reactions_reversed, sub in sr.substrates[1] + push!(spatial_reactions_dict[sub], sr) + end f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) - jac = (use_jac ? build_jac(ofunc,nS,nV,spatial_reactions, lrs.lattice.fadjlist) : nothing) + jac = (use_jac ? build_jac(ofunc,nS,nV,spatial_reactions,spatial_reactions_dict, lrs.lattice.fadjlist) : nothing) jac_prototype = (use_jac ? build_jac_prototype(nS,nV,spatial_reactions, lrs, sparse) : nothing) return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) @@ -151,18 +171,18 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int6 return function(du, u, p, t) # Updates for non-spatial reactions. for comp_i::Int64 in 1:nV - ofunc((@view du[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + ofunc((@view du[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view p[1][:,comp_i]), t) end # Updates for spatial reactions. for comp_i::Int64 in 1:nV for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions - rate::Float64 = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + rate::Float64 = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) for stoich::Pair{Int64,Int64} in sr.netstoich[1] - du[get_index(comp_i,stoich[1],nS)] += rate * stoich[2] + du[get_index(comp_i,stoich[1],nV)] += rate * stoich[2] end for stoich::Pair{Int64,Int64} in sr.netstoich[2] - du[get_index(comp_j,stoich[1],nS)] += rate * stoich[2] + du[get_index(comp_j,stoich[1],nV)] += rate * stoich[2] end end end @@ -181,34 +201,62 @@ function get_rate(sr::SpatialReactionIndexed, pE::Matrix{Float64}, u_src, u_dst) return product end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, adjlist::Vector{Vector{Int64}}) +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, spatial_reactions_dict::Dict{Int64,Vector{SpatialReactionIndexed}}, adjlist::Vector{Vector{Int64}}) return function(J, u, p, t) J .= 0 # Updates for non-spatial reactions. for comp_i::Int64 in 1:nV - ofunc.jac((@view J[get_indexes(comp_i,nS),get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_i,nS)]), (@view p[1][:,comp_i]), t) + ofunc.jac((@view J[get_indexes(comp_i,nV,nV*nS),get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view p[1][:,comp_i]), t) end # Updates for spatial reactions. + # for species_i in 1:nS, comp_i in 1:nV + # i_idx = sub + (comp_i-1)*nS + # for j_idx in J.rowval[J.colptr[i_idx]:J.colptr[i_idx+1]-1] + # species_j + # end + # for comp_j::Int64 in adjlist[comp_i], sr in spatial_reactions_dict[sub] + # rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) + # for stoich::Pair{Int64,Int64} in sr.netstoich[1] + # J.nzval[idxs_loolup_dict[stoich[1]]] += rate * stoich[2] + # end + # end + # end + + # idxs_loolup_dict = Dict{Int64,Int64}(Pair.(1:nV*nS, 0)) + # for sub in 1:nS, comp_i in 1:nV + # # Creates a look up, so that we for a given species (in a given container) can find its position in J.nzval. + # sub_idx = sub + (comp_i-1)*nS + # for (idx,species) in enumerate(J.rowval[J.colptr[sub_idx]:J.colptr[sub_idx+1]-1]) + # idxs_loolup_dict[species] = J.colptr[sub_idx] + idx - 1 + # end + # for comp_j::Int64 in adjlist[comp_i], sr in spatial_reactions_dict[sub] + # rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) + # for stoich::Pair{Int64,Int64} in sr.netstoich[1] + # J.nzval[idxs_loolup_dict[stoich[1]]] += rate * stoich[2] + # end + # end + # end + for comp_i::Int64 in 1:nV for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions for sub::Int64 in sr.substrates[1] - rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nS)]), (@view u[get_indexes(comp_j,nS)])) + rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) for stoich::Pair{Int64,Int64} in sr.netstoich[1] - J[get_index(comp_i,stoich[1],nS),get_index(comp_i,sub,nS)] += rate * stoich[2] + J[get_index(comp_i,stoich[1],nV),get_index(comp_i,sub,nV)] += rate * stoich[2] end for stoich::Pair{Int64,Int64} in sr.netstoich[2] - J[get_index(comp_j,stoich[1],nS),get_index(comp_i,sub,nS)] += rate * stoich[2] + J[get_index(comp_j,stoich[1],nV),get_index(comp_i,sub,nV)] += rate * stoich[2] end end for sub::Int64 in sr.substrates[2] - rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j,nS)]), (@view u[get_indexes(comp_i,nS)])) + rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)])) for stoich::Pair{Int64,Int64} in sr.netstoich[1] - J[get_index(comp_i,stoich[1],nS),get_index(comp_j,sub,nS)] += rate * stoich[2] + J[get_index(comp_i,stoich[1],nV),get_index(comp_j,sub,nV)] += rate * stoich[2] end for stoich::Pair{Int64,Int64} in sr.netstoich[2] - J[get_index(comp_j,stoich[1],nS),get_index(comp_j,sub,nS)] += rate * stoich[2] + J[get_index(comp_j,stoich[1],nV),get_index(comp_j,sub,nV)] += rate * stoich[2] end end end @@ -240,27 +288,26 @@ function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{Spa for substrate in reaction.substrates, ns in reaction.netstoich sub_idx = findfirst(isequal(substrate), states(lrs.rs)) spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) - jac_prototype[get_index(comp_i,spec_idx,nS), get_index(comp_i,sub_idx,nS)] = 1 + jac_prototype[get_index(comp_i,spec_idx,nV), get_index(comp_i,sub_idx,nV)] = 1 end end - # Updates for spatial reactions. for comp_i::Int64 in 1:nV for comp_j::Int64 in lrs.lattice.fadjlist[comp_i]::Vector{Int64}, sr::SpatialReactionIndexed in spatial_reactions for sub::Int64 in sr.substrates[1] for stoich::Pair{Int64,Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i,stoich[1],nS),get_index(comp_i,sub,nS)] = 1.0 + jac_prototype[get_index(comp_i,stoich[1],nV),get_index(comp_i,sub,nV)] = 1.0 end for stoich::Pair{Int64,Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j,stoich[1],nS),get_index(comp_i,sub,nS)] = 1.0 + jac_prototype[get_index(comp_j,stoich[1],nV),get_index(comp_i,sub,nV)] = 1.0 end end for sub::Int64 in sr.substrates[2] for stoich::Pair{Int64,Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i,stoich[1],nS),get_index(comp_j,sub,nS)] = 1.0 + jac_prototype[get_index(comp_i,stoich[1],nV),get_index(comp_j,sub,nV)] = 1.0 end for stoich::Pair{Int64,Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j,stoich[1],nS),get_index(comp_j,sub,nS)] = 1.0 + jac_prototype[get_index(comp_j,stoich[1],nV),get_index(comp_j,sub,nV)] = 1.0 end end end @@ -269,5 +316,8 @@ function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{Spa end # Gets the index of a species (or a node's species) in the u array. -get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species -get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS \ No newline at end of file +# get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species +# get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS + +get_index(container::Int64,species::Int64,nC) = (species-1)*nC + container +get_indexes(container::Int64,nC,l) = container:nC:l \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index aaeb1ad6ac..56340fe50c 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -136,58 +136,58 @@ large_directed_cycle = cycle_graph(1000) ### Test No Error During Runs ### for grid in [small_2d_grid, short_path, small_directed_cycle] - # Stiff case - for srs in [Vector{SpatialReaction}(), brusselator_srs_1, brusselator_srs_2, brusselator_srs_3, brusselator_srs_4] - lrs = LatticeReactionSystem(brusselator_system, srs, grid) - u0_1 = [:X => 1.0, :Y => 20.0] - u0_2 = [:X => rand_v_vals(grid, 10.0), :Y => 2.0] - u0_3 = [:X => rand_v_vals(grid, 20), :Y => rand_v_vals(grid, 10)] - u0_4 = make_u0_matrix(u0_3, vertices(grid), map(s -> Symbol(s.f), species(lrs.rs))) - for u0 in [u0_1, u0_2, u0_3, u0_4] - p1 = [:A => 1.0, :B => 4.0] - p2 = [:A => 0.5 .+ rand_v_vals(grid, 0.5), :B => 4.0] - p3 = [:A => 0.5 .+ rand_v_vals(grid, 0.5), :B => 4.0 .+ rand_v_vals(grid, 1.0)] - p4 = make_u0_matrix(p2, vertices(grid), Symbol.(parameters(lrs.rs))) - for pV in [p1, p2, p3, p4] + # Non-stiff case + for srs in [Vector{DiffusionReaction}(), binding_srs_1, binding_srs_2] + lrs = LatticeReactionSystem(binding_system, srs, grid) + u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] + u0_2 = [:X => rand_v_vals(lrs.lattice), :Y => 2.0, :XY => 0.0] + u0_3 = [:X => 1.0, :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + u0_4 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice,3)] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:kB => 2.0, :kD => 0.5] + p2 = [:kB => 2.0, :kD => rand_v_vals(lrs.lattice)] + p3 = [:kB => rand_v_vals(lrs.lattice), :kD => rand_v_vals(lrs.lattice)] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) pE_2 = map(sp -> sp => rand(), lrs.spatial_params) - pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) - pE_4 = make_u0_matrix(pE_3, edges(grid), lrs.spatial_params) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); sparse=false) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); jac=false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) end end end end - # Non-stiff case - for srs in [Vector{SpatialReaction}(), binding_srs_1, binding_srs_2, binding_srs_3, binding_srs_4] - lrs = LatticeReactionSystem(binding_system, srs, grid) - u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] - u0_2 = [:X => rand_v_vals(grid), :Y => 2.0, :XY => 0.0] - u0_3 = [:X => 1.0, :Y => rand_v_vals(grid), :XY => rand_v_vals(grid)] - u0_4 = [:X => rand_v_vals(grid), :Y => rand_v_vals(grid), :XY => rand_v_vals(grid,3)] - u0_5 = make_u0_matrix(u0_3, vertices(grid), map(s -> Symbol(s.f), species(lrs.rs))) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:kB => 2.0, :kD => 0.5] - p2 = [:kB => 2.0, :kD => rand_v_vals(grid)] - p3 = [:kB => rand_v_vals(grid), :kD => rand_v_vals(grid)] - p4 = make_u0_matrix(p1, vertices(grid), Symbol.(parameters(lrs.rs))) + # Stiff case + for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] + lrs = LatticeReactionSystem(brusselator_system, srs, grid) + u0_1 = [:X => 1.0, :Y => 20.0] + u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] + u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] + u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4] + p1 = [:A => 1.0, :B => 4.0] + p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] + p3 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0)] + p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) pE_2 = map(sp -> sp => rand(), lrs.spatial_params) - pE_3 = map(sp -> sp => rand_e_vals(grid, 0.2), lrs.spatial_params) - pE_4 = make_u0_matrix(pE_3, edges(grid), lrs.spatial_params) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); jac=false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); sparse=false) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) end end end @@ -209,7 +209,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00089 + runtime_target = 0.001 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -225,7 +225,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.451 + runtime_target = 0.5 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -240,7 +240,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.05 + runtime_target = 0.1 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -255,7 +255,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 140.0 + runtime_target = 200.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -271,7 +271,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00293 + runtime_target = 0.005 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -286,7 +286,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 1.257 + runtime_target = 2 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -301,7 +301,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.023 + runtime_target = 0.025 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -316,7 +316,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 111. + runtime_target = 150. runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target From 99273d0605cec302913750ef740ee2463ac2cfa8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 09:25:36 -0400 Subject: [PATCH 009/134] test update --- .../lattice_reaction_systems.jl | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 56340fe50c..a0fbde7b62 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,5 +1,6 @@ ### Fetches Stuff ### + # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test using BenchmarkTools, Statistics @@ -34,15 +35,8 @@ binding_p = [:kB => 2.0, :kD => 0.5] binding_dif_x = DiffusionReaction(:dX, :X) binding_dif_y = DiffusionReaction(:dY, :Y) binding_dif_xy = DiffusionReaction(:dXY, :XY) -binding_osr_xy1 = OnewaySpatialReaction(:d_ord_1, [:X, :Y], [:X, :Y], [1, 1], [1, 1]) -binding_osr_xy2 = OnewaySpatialReaction(:d_ord_2, [:X, :Y], [:XY], [1, 1], [1]) -binding_osr_xy3 = OnewaySpatialReaction(:d_ord_3, [:X, :Y], [:X, :Y], [2, 2], [2, 2]) -binding_sr_1 = SpatialReaction(:d_sr_1, ([:X], [:Y]), ([:Y], [:X]), ([1], [1]), ([1], [1])) -binding_sr_2 = SpatialReaction(:d_sr_2, ([:X, :Y], [:XY]), ([:XY], []), ([1, 1], [2]), ([1], Vector{Int64}())) binding_srs_1 = [binding_dif_x] binding_srs_2 = [binding_dif_x, binding_dif_y, binding_dif_xy] -binding_srs_3 = [binding_sr_1, binding_sr_2] -binding_srs_4 = [binding_dif_x, binding_dif_y, binding_dif_xy, binding_osr_xy1, binding_osr_xy2, binding_osr_xy3, binding_sr_1, binding_sr_2] # Mid-sized non-stiff system. CuH_Amination_system = @reaction_network begin @@ -78,12 +72,8 @@ brusselator_p = [:A => 1.0, :B => 4.0] brusselator_dif_x = DiffusionReaction(:dX, :X) brusselator_dif_y = DiffusionReaction(:dY, :Y) -binding_osr_x2 = OnewaySpatialReaction(:d_ord_1, [:X], [:X], [2], [2]) -brusselator_dif_sr = SpatialReaction(:D, ([:X], [:Y]), ([:Y], [:X]), ([1], [2]), ([1], [1])) brusselator_srs_1 = [brusselator_dif_x] brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] -brusselator_srs_3 = [binding_osr_x2] -brusselator_srs_4 = [brusselator_dif_x, brusselator_dif_sr] # Mid-sized stiff system. sigmaB_system = @reaction_network begin @@ -196,20 +186,18 @@ end ### Tests Runtimes ### - # Timings currently are from Torkel's computer. - # Small grid, small, non-stiff, system. let lrs = LatticeReactionSystem(binding_system, binding_srs_2, small_2d_grid) u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] pV = binding_p - pE = [:dX => 0.1, :dY => 0.2] + pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.001 + runtime_target = 0.00089 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -221,11 +209,11 @@ let lrs = LatticeReactionSystem(binding_system, binding_srs_2, large_2d_grid) u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] pV = binding_p - pE = [:dX => 0.1, :dY => 0.2] + pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.5 + runtime_target = 0.451 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -240,7 +228,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.1 + runtime_target = 0.05 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -255,13 +243,12 @@ let oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 200.0 + runtime_target = 140.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end - # Small grid, mid-sized, non-stiff, system. let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) @@ -271,7 +258,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.005 + runtime_target = 0.00293 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -286,7 +273,7 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 2 + runtime_target = 1.257 runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target @@ -301,9 +288,9 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.025 + runtime_target = 0.023 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end @@ -316,8 +303,8 @@ let oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 150. + runtime_target = 111.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2*runtime_target end \ No newline at end of file From 63cfb97bdbbea91f2e8f56d44b211c4f3b9408f4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 09:29:05 -0400 Subject: [PATCH 010/134] formating --- src/lattice_reaction_system_diffusion.jl | 168 ++++++---- src/lattice_reaction_system_general.jl | 249 +++++++++------ .../lattice_reaction_systems.jl | 296 ++++++++++++------ 3 files changed, 471 insertions(+), 242 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 055ff8e427..8cf7961dbd 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -31,7 +31,9 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p nS::Int64 function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) - return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), length(species(rs))) + return new(rs, spatial_reactions, lattice, + unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), + length(species(rs))) end function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice)) @@ -42,10 +44,10 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; - jac=true, sparse=true, kwargs...) - + jac = true, sparse = true, kwargs...) u0 = resort_values(u0_in, [Symbol(s.f) for s in species(lrs.rs)]) - u0 = [get_component_value(u0, species, comp) for comp in 1:lrs.nC for species in 1:lrs.nS] + u0 = [get_component_value(u0, species, comp) for comp in 1:(lrs.nC) + for species in 1:(lrs.nS)] pV, pE = split_parameters(p_in, lrs.spatial_params) pV = resort_values(pV, Symbol.(parameters(lrs.rs))) pE = resort_values(pE, lrs.spatial_params) @@ -56,142 +58,188 @@ end # Splits parameters into those for the compartments and those for the connections. split_parameters(ps::Tuple{<:Any, <:Any}, spatial_params::Vector{Symbol}) = ps -split_parameters(ps::Vector{<:Pair}, spatial_params::Vector{Symbol}) = (filter(p->!(p[1] in spatial_params), ps), filter(p->p[1] in spatial_params, ps)) -split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) = (ps[1:length(ps)-length(spatial_params)], ps[length(ps)-length(spatial_params)+1:end]) +function split_parameters(ps::Vector{<:Pair}, spatial_params::Vector{Symbol}) + (filter(p -> !(p[1] in spatial_params), ps), filter(p -> p[1] in spatial_params, ps)) +end +function split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) + (ps[1:(length(ps) - length(spatial_params))], + ps[(length(ps) - length(spatial_params) + 1):end]) +end # Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. -resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) = last.(sort(values; by=val -> findfirst(val[1].==symbols))) +function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) + last.(sort(values; by = val -> findfirst(val[1] .== symbols))) +end resort_values(values::Any, symbols::Vector{Symbol}) = values # Builds an ODEFunction. function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=use_jac, sparse=false) - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac=use_jac, sparse=true) - diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) for s in getfield.(lrs.spatial_reactions,:species)] + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) + diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) + for s in getfield.(lrs.spatial_reactions, :species)] f = build_f(ofunc, pV, pE, diffusion_species, lrs) - jac_prototype = (use_jac ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero=true) : nothing) - jac = (use_jac ? build_jac(ofunc, pV, pE, diffusion_species, lrs, jac_prototype; sparse=sparse) : nothing) - return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) + jac_prototype = (use_jac ? + build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, + lrs; set_nonzero = true) : nothing) + jac = (use_jac ? + build_jac(ofunc, pV, pE, diffusion_species, lrs, jac_prototype; + sparse = sparse) : nothing) + return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) end # Creates a function for simulating the spatial ODE with spatial reactions. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem) +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, + diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem) leaving_rates = zeros(length(diffusion_species), lrs.nC) - for (s_idx,species) in enumerate(diffusion_species), (e_idx, e) in enumerate(edges(lrs.lattice)) + for (s_idx, species) in enumerate(diffusion_species), + (e_idx, e) in enumerate(edges(lrs.lattice)) + leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) - end + end p_base = deepcopy(first.(pV)) p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - return function(du, u, p, t) + return function (du, u, p, t) # Updates for non-spatial reactions. - for comp_i::Int64 in 1:lrs.nC - ofunc((@view du[get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + for comp_i::Int64 in 1:(lrs.nC) + ofunc((@view du[get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + make_p_vector!(p_base, p, p_update_idx, comp_i), t) end - for (s_idx::Int64,species::Int64) in enumerate(diffusion_species) - for comp_i::Int64 in 1:lrs.nC - du[get_index(comp_i,species,lrs.nS)] -= leaving_rates[s_idx, comp_i] * u[get_index(comp_i,species,lrs.nS)] + for (s_idx::Int64, species::Int64) in enumerate(diffusion_species) + for comp_i::Int64 in 1:(lrs.nC) + du[get_index(comp_i, species, lrs.nS)] -= leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, species, + lrs.nS)] end - for (e_idx::Int64,edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges - du[get_index(edge.dst,species,lrs.nS)] += get_component_value(pE, s_idx, e_idx) * u[get_index(edge.src,species,lrs.nS)] + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges + du[get_index(edge.dst, species, lrs.nS)] += get_component_value(pE, s_idx, + e_idx) * + u[get_index(edge.src, species, + lrs.nS)] end end end end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem, jac_prototype::SparseMatrixCSC{Float64, Int64}; sparse=true) +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, + diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem, + jac_prototype::SparseMatrixCSC{Float64, Int64}; sparse = true) p_base = deepcopy(first.(pV)) p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] - - if sparse - return function(J, u, p, t) + + if sparse + return function (J, u, p, t) # Sets the base according to the spatial reactions. J.nzval .= jac_prototype.nzval # Updates for non-spatial reactions. - for comp_i::Int64 in 1:lrs.nC - ofunc.jac((@view J[get_indexes(comp_i,lrs.nS),get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + for comp_i::Int64 in 1:(lrs.nC) + ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + make_p_vector!(p_base, p, p_update_idx, comp_i), t) end end else jac_diffusion = Matrix(jac_prototype) - return function(J, u, p, t) + return function (J, u, p, t) # Sets the base according to the spatial reactions. J .= jac_diffusion # Updates for non-spatial reactions. - for comp_i::Int64 in 1:lrs.nC - ofunc.jac((@view J[get_indexes(comp_i,lrs.nS),get_indexes(comp_i,lrs.nS)]), (@view u[get_indexes(comp_i,lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) + for comp_i::Int64 in 1:(lrs.nC) + ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + make_p_vector!(p_base, p, p_update_idx, comp_i), t) end end end end -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, pE, diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem; set_nonzero=false) - only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype,s,s) for s in 1:lrs.nS] +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, pE, + diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem; + set_nonzero = false) + only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype, s, s) + for s in 1:(lrs.nS)] # Declares sparse array content. - J_colptr = fill(1,lrs.nC*lrs.nS+1) - J_nzval = fill(0.0, lrs.nC*(nnz(ns_jac_prototype) + count(only_diff)) + length(edges(lrs.lattice))*length(diffusion_species)) + J_colptr = fill(1, lrs.nC * lrs.nS + 1) + J_nzval = fill(0.0, + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_species)) J_rowval = fill(0, length(J_nzval)) # Finds filled elements. - for comp in 1:lrs.nC, s in 1:lrs.nS + for comp in 1:(lrs.nC), s in 1:(lrs.nS) col_idx = get_index(comp, s, lrs.nS) - + # Column values. - local_elements = in(s, diffusion_species)*(length(lrs.lattice.fadjlist[comp]) + only_diff[s]) - diffusion_elements = -(ns_jac_prototype.colptr[s+1:-1:s]...) - J_colptr[col_idx+1] = J_colptr[col_idx] + local_elements + diffusion_elements - + local_elements = in(s, diffusion_species) * + (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) + diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements + # Row values. - rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:ns_jac_prototype.colptr[s+1]-1] .+ (comp-1)*lrs.nS + rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ + (comp - 1) * lrs.nS if in(s, diffusion_species) - diffusion_rows = (lrs.lattice.fadjlist[comp] .-1) .* lrs.nS .+ s + diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s split_idx = findfirst(diffusion_rows .> rows[1]) isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) - rows = vcat(diffusion_rows[1:split_idx-1],rows,diffusion_rows[split_idx:end]) + rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, + diffusion_rows[split_idx:end]) if only_diff[s] split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) isnothing(split_idx) && (split_idx = length(rows) + 1) insert!(rows, split_idx, get_index(comp, s, lrs.nS)) end end - J_rowval[J_colptr[col_idx]:J_colptr[col_idx+1]-1] = rows + J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows end # Set element values. if set_nonzero J_nzval .= 1.0 - else - for (s_idx,s) in enumerate(diffusion_species), (e_idx,edge) in enumerate(edges(lrs.lattice)) + else + for (s_idx, s) in enumerate(diffusion_species), + (e_idx, edge) in enumerate(edges(lrs.lattice)) + col_start = J_colptr[get_index(edge.src, s, lrs.nS)] col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 column_view = @view J_rowval[col_start:col_end] # Updates the source value. - val_idx_src = col_start + findfirst(column_view .== get_index(edge.src, s_idx, lrs.nS)) - 1 + val_idx_src = col_start + + findfirst(column_view .== get_index(edge.src, s_idx, lrs.nS)) - 1 J_nzval[val_idx_src] -= get_component_value(pE, s_idx, e_idx) # Updates the destination value. - val_idx_dst = col_start + findfirst(column_view .== get_index(edge.dst, s_idx, lrs.nS)) - 1 + val_idx_dst = col_start + + findfirst(column_view .== get_index(edge.dst, s_idx, lrs.nS)) - 1 J_nzval[val_idx_dst] += get_component_value(pE, s_idx, e_idx) end end - return SparseMatrixCSC(lrs.nS*lrs.nC, lrs.nS*lrs.nC, J_colptr, J_rowval, J_nzval) + return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end # Gets the index of a species in the u array. -get_index(comp::Int64,species::Int64, nS::Int64) = (comp-1)*nS + species +get_index(comp::Int64, species::Int64, nS::Int64) = (comp - 1) * nS + species # Gets the indexes of a compartment's species in the u array. -get_indexes(comp::Int64, nS::Int64) = (comp-1)*nS+1:comp*nS +get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) # For set of values (stoed in a variety of possible forms), a given component (species or parameter), and a place (eitehr a compartment or edge), find that components value at that place. -get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) = vals[component, place] -get_component_value(vals::Vector, component::Int64, place::Int64) = get_component_value(vals[component], place) +function get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) + vals[component, place] +end +function get_component_value(vals::Vector, component::Int64, place::Int64) + get_component_value(vals[component], place) +end get_component_value(vals::Vector{Float64}, place::Int64) = vals[place] get_component_value(vals::Float64, place::Int64) = vals @@ -205,5 +253,9 @@ function make_p_vector!(p_base, p, p_update_idx, comp_i) end # Gets all the values in a specific palce. -get_component_values(vals::Matrix{Float64}, place::Int64, nComponents::Int64) = (@view vals[:, place]) -get_component_values(vals, place::Int64, nComponents::Int64) = [get_component_value(vals, component, place) for component in 1:nComponents] +function get_component_values(vals::Matrix{Float64}, place::Int64, nComponents::Int64) + (@view vals[:, place]) +end +function get_component_values(vals, place::Int64, nComponents::Int64) + [get_component_value(vals, component, place) for component in 1:nComponents] +end diff --git a/src/lattice_reaction_system_general.jl b/src/lattice_reaction_system_general.jl index f79a1a4527..6ae2ae03ae 100644 --- a/src/lattice_reaction_system_general.jl +++ b/src/lattice_reaction_system_general.jl @@ -1,6 +1,5 @@ ### CURRENTLY NOT IN USE ### - ### Spatial Reaction Structure. ### # Describing a spatial reaction that involves species from two neighbouring compartments. # Currently only permit constant rate. @@ -18,7 +17,7 @@ struct SpatialReaction <: AbstractSpatialReaction """The stoichiometric coefficients of the products (source and destination).""" prodstoich::Tuple{Vector{Int64}, Vector{Int64}} """The net stoichiometric coefficients of all species changed by the reaction (source and destination).""" - netstoich::Tuple{Vector{Pair{Symbol,Int64}}, Vector{Pair{Symbol,Int64}}} + netstoich::Tuple{Vector{Pair{Symbol, Int64}}, Vector{Pair{Symbol, Int64}}} """ `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. `true` if `rate` represents the full reaction rate law. @@ -27,8 +26,13 @@ struct SpatialReaction <: AbstractSpatialReaction only_use_rate::Bool """These are similar to substrates, products, and netstoich, but ses species index (instead ) """ - function SpatialReaction(rate, substrates::Tuple{Vector, Vector}, products::Tuple{Vector, Vector}, substoich::Tuple{Vector{Int64}, Vector{Int64}}, prodstoich::Tuple{Vector{Int64}, Vector{Int64}}; only_use_rate = false) - new(rate, substrates, products, substoich, prodstoich, get_netstoich.(substrates, products, substoich, prodstoich), only_use_rate) + function SpatialReaction(rate, substrates::Tuple{Vector, Vector}, + products::Tuple{Vector, Vector}, + substoich::Tuple{Vector{Int64}, Vector{Int64}}, + prodstoich::Tuple{Vector{Int64}, Vector{Int64}}; + only_use_rate = false) + new(rate, substrates, products, substoich, prodstoich, + get_netstoich.(substrates, products, substoich, prodstoich), only_use_rate) end end @@ -40,21 +44,33 @@ struct SpatialReactionIndexed products::Tuple{Vector{Int64}, Vector{Int64}} substoich::Tuple{Vector{Int64}, Vector{Int64}} prodstoich::Tuple{Vector{Int64}, Vector{Int64}} - netstoich::Tuple{Vector{Pair{Int64,Int64}}, Vector{Pair{Int64,Int64}}} - netstoich_new::Tuple{Dict{Int64,Int64},Dict{Int64,Int64}} + netstoich::Tuple{Vector{Pair{Int64, Int64}}, Vector{Pair{Int64, Int64}}} + netstoich_new::Tuple{Dict{Int64, Int64}, Dict{Int64, Int64}} only_use_rate::Bool - function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, param_list::Vector{Symbol}; reverse_direction=false) + function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, + param_list::Vector{Symbol}; reverse_direction = false) get_s_idx(species::Symbol) = findfirst(species .== (species_list)) rate = findfirst(sr.rate .== (param_list)) substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) - netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i])) for i in 1:2]) - netstoich_new = Tuple([Dict(Pair.(get_s_idx.(first.(sr.netstoich[i])), last.(sr.netstoich[i]))) for i in 1:2]) - if reverse_direction - return new(rate, reverse.([substrates, products, sr.substoich, sr.prodstoich, netstoich, netstoich_new])..., sr.only_use_rate) + netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), + last.(sr.netstoich[i])) for i in 1:2]) + netstoich_new = Tuple([Dict(Pair.(get_s_idx.(first.(sr.netstoich[i])), + last.(sr.netstoich[i]))) for i in 1:2]) + if reverse_direction + return new(rate, + reverse.([ + substrates, + products, + sr.substoich, + sr.prodstoich, + netstoich, + netstoich_new, + ])..., sr.only_use_rate) end - new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, netstoich_new, sr.only_use_rate) + new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, + netstoich_new, sr.only_use_rate) end end @@ -64,7 +80,10 @@ end Simple function to create a diffusion spatial reaction. Equivalent to SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) """ -DiffusionReaction(rate,species) = SpatialReaction(rate,([species],Symbol[]),(Symbol[],[species]),([1],Int64[]),(Int64[],[1])) +function DiffusionReaction(rate, species) + SpatialReaction(rate, ([species], Symbol[]), (Symbol[], [species]), ([1], Int64[]), + (Int64[], [1])) +end """ OnewaySpatialReaction(rate, substrates, products, substoich, prodstoich) @@ -72,9 +91,11 @@ DiffusionReaction(rate,species) = SpatialReaction(rate,([species],Symbol[]),(Sym Simple function to create a spatial reactions where all substrates are in teh soruce compartment, and all products in the destination. Equivalent to SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) """ -OnewaySpatialReaction(rate, substrates::Vector, products::Vector, substoich::Vector{Int64}, prodstoich::Vector{Int64}) = SpatialReaction(rate,(substrates,Symbol[]),(Symbol[],products),(substoich,Int64[]),(Int64[],prodstoich)) - - +function OnewaySpatialReaction(rate, substrates::Vector, products::Vector, + substoich::Vector{Int64}, prodstoich::Vector{Int64}) + SpatialReaction(rate, (substrates, Symbol[]), (Symbol[], products), + (substoich, Int64[]), (Int64[], prodstoich)) +end ### Lattice Reaction Network Structure ### # Couples: @@ -95,10 +116,12 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p independent variable.""" function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) - return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), lattice) + return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), + lattice) end function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) - return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), DiGraph(lattice)) + return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), + DiGraph(lattice)) end end @@ -106,7 +129,7 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, p = DiffEqBase.NullParameters(), args...; - jac=true, sparse=true, kwargs...) + jac = true, sparse = true, kwargs...) @unpack rs, spatial_reactions, lattice = lrs pV_in, pE_in = split_parameters(p, lrs.spatial_params) @@ -114,8 +137,8 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) pE_idxes = Dict(reverse.(enumerate(lrs.spatial_params))) - nS,nV,nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - u0 = Vector(reshape(matrix_form(u0, nV, u_idxs)',1:nS*nV)) + nS, nV, nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) + u0 = Vector(reshape(matrix_form(u0, nV, u_idxs)', 1:(nS * nV))) pV = matrix_form(pV_in, nV, pV_idxes) pE = matrix_form(pE_in, nE, pE_idxes) @@ -134,24 +157,34 @@ end # Converts species and parameters to matrices form. matrix_form(input::Matrix{Float64}, args...) = input function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) - isempty(input) && return zeros(0,n) + isempty(input) && return zeros(0, n) mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) end function matrix_form(input::Vector, n, index_dict) - isempty(input) && return zeros(0,n) + isempty(input) && return zeros(0, n) matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), n, index_dict) end # Builds an ODEFunction. function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bool) - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac=true) - nS,nV = length.([states(lrs.rs), vertices(lrs.lattice)]) - + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = true) + nS, nV = length.([states(lrs.rs), vertices(lrs.lattice)]) + species_list = map(s -> Symbol(s.f), species(lrs.rs)) - spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params) for sr in lrs.spatial_reactions] - spatial_reactions_reversed = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), lrs.spatial_params; reverse_direction=true) for sr in lrs.spatial_reactions] - spatial_reactions_dict = Dict{Int64,Vector{SpatialReactionIndexed}}([i => Vector{SpatialReactionIndexed}() for i in 1:nS]) + spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), + lrs.spatial_params) + for sr in lrs.spatial_reactions] + spatial_reactions_reversed = [SpatialReactionIndexed(sr, + map(s -> Symbol(s.f), + species(lrs.rs)), + lrs.spatial_params; + reverse_direction = true) + for sr in lrs.spatial_reactions] + spatial_reactions_dict = Dict{Int64, Vector{SpatialReactionIndexed}}([i => Vector{ + SpatialReactionIndexed + }() + for i in 1:nS]) for sr in spatial_reactions, sub in sr.substrates[1] push!(spatial_reactions_dict[sub], sr) end @@ -160,29 +193,39 @@ function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bo end f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) - jac = (use_jac ? build_jac(ofunc,nS,nV,spatial_reactions,spatial_reactions_dict, lrs.lattice.fadjlist) : nothing) - jac_prototype = (use_jac ? build_jac_prototype(nS,nV,spatial_reactions, lrs, sparse) : nothing) + jac = (use_jac ? + build_jac(ofunc, nS, nV, spatial_reactions, spatial_reactions_dict, + lrs.lattice.fadjlist) : nothing) + jac_prototype = (use_jac ? build_jac_prototype(nS, nV, spatial_reactions, lrs, sparse) : + nothing) - return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) + return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) end # Creates a function for simulating the spatial ODE with spatial reactions. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, adjlist::Vector{Vector{Int64}}) - return function(du, u, p, t) +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, + spatial_reactions::Vector{SpatialReactionIndexed}, + adjlist::Vector{Vector{Int64}}) + return function (du, u, p, t) # Updates for non-spatial reactions. for comp_i::Int64 in 1:nV - ofunc((@view du[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view p[1][:,comp_i]), t) + ofunc((@view du[get_indexes(comp_i, nV, nV * nS)]), + (@view u[get_indexes(comp_i, nV, nV * nS)]), (@view p[1][:, comp_i]), t) end - + # Updates for spatial reactions. for comp_i::Int64 in 1:nV - for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions - rate::Float64 = get_rate(sr, p[2], (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) - for stoich::Pair{Int64,Int64} in sr.netstoich[1] - du[get_index(comp_i,stoich[1],nV)] += rate * stoich[2] + for comp_j::Int64 in adjlist[comp_i], + sr::SpatialReactionIndexed in spatial_reactions + + rate::Float64 = get_rate(sr, p[2], + (@view u[get_indexes(comp_i, nV, nV * nS)]), + (@view u[get_indexes(comp_j, nV, nV * nS)])) + for stoich::Pair{Int64, Int64} in sr.netstoich[1] + du[get_index(comp_i, stoich[1], nV)] += rate * stoich[2] end - for stoich::Pair{Int64,Int64} in sr.netstoich[2] - du[get_index(comp_j,stoich[1],nV)] += rate * stoich[2] + for stoich::Pair{Int64, Int64} in sr.netstoich[2] + du[get_index(comp_j, stoich[1], nV)] += rate * stoich[2] end end end @@ -192,24 +235,32 @@ end # Get the rate of a specific reaction. function get_rate(sr::SpatialReactionIndexed, pE::Matrix{Float64}, u_src, u_dst) product::Float64 = pE[sr.rate] - !isempty(sr.substrates[1]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) - product *= u_src[sub]^stoich / factorial(stoich) - end - !isempty(sr.substrates[2]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[sub]^stoich / factorial(stoich) - end + !isempty(sr.substrates[1]) && + for (sub::Int64, stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) + product *= u_src[sub]^stoich / factorial(stoich) + end + !isempty(sr.substrates[2]) && + for (sub::Int64, stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[sub]^stoich / factorial(stoich) + end return product end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, spatial_reactions_dict::Dict{Int64,Vector{SpatialReactionIndexed}}, adjlist::Vector{Vector{Int64}}) - return function(J, u, p, t) +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, + spatial_reactions::Vector{SpatialReactionIndexed}, + spatial_reactions_dict::Dict{Int64, Vector{SpatialReactionIndexed}}, + adjlist::Vector{Vector{Int64}}) + return function (J, u, p, t) J .= 0 # Updates for non-spatial reactions. for comp_i::Int64 in 1:nV - ofunc.jac((@view J[get_indexes(comp_i,nV,nV*nS),get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view p[1][:,comp_i]), t) + ofunc.jac((@view J[get_indexes(comp_i, nV, nV * nS), + get_indexes(comp_i, nV, nV * nS)]), + (@view u[get_indexes(comp_i, nV, nV * nS)]), (@view p[1][:, comp_i]), + t) end - + # Updates for spatial reactions. # for species_i in 1:nS, comp_i in 1:nV # i_idx = sub + (comp_i-1)*nS @@ -240,23 +291,37 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::In # end for comp_i::Int64 in 1:nV - for comp_j::Int64 in adjlist[comp_i], sr::SpatialReactionIndexed in spatial_reactions + for comp_j::Int64 in adjlist[comp_i], + sr::SpatialReactionIndexed in spatial_reactions + for sub::Int64 in sr.substrates[1] - rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) - for stoich::Pair{Int64,Int64} in sr.netstoich[1] - J[get_index(comp_i,stoich[1],nV),get_index(comp_i,sub,nV)] += rate * stoich[2] + rate::Float64 = get_rate_differential(sr, p[2], sub, + (@view u[get_indexes(comp_i, nV, + nV * nS)]), + (@view u[get_indexes(comp_j, nV, + nV * nS)])) + for stoich::Pair{Int64, Int64} in sr.netstoich[1] + J[get_index(comp_i, stoich[1], nV), get_index(comp_i, sub, nV)] += rate * + stoich[2] end - for stoich::Pair{Int64,Int64} in sr.netstoich[2] - J[get_index(comp_j,stoich[1],nV),get_index(comp_i,sub,nV)] += rate * stoich[2] + for stoich::Pair{Int64, Int64} in sr.netstoich[2] + J[get_index(comp_j, stoich[1], nV), get_index(comp_i, sub, nV)] += rate * + stoich[2] end end for sub::Int64 in sr.substrates[2] - rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_j,nV,nV*nS)]), (@view u[get_indexes(comp_i,nV,nV*nS)])) - for stoich::Pair{Int64,Int64} in sr.netstoich[1] - J[get_index(comp_i,stoich[1],nV),get_index(comp_j,sub,nV)] += rate * stoich[2] + rate::Float64 = get_rate_differential(sr, p[2], sub, + (@view u[get_indexes(comp_j, nV, + nV * nS)]), + (@view u[get_indexes(comp_i, nV, + nV * nS)])) + for stoich::Pair{Int64, Int64} in sr.netstoich[1] + J[get_index(comp_i, stoich[1], nV), get_index(comp_j, sub, nV)] += rate * + stoich[2] end - for stoich::Pair{Int64,Int64} in sr.netstoich[2] - J[get_index(comp_j,stoich[1],nV),get_index(comp_j,sub,nV)] += rate * stoich[2] + for stoich::Pair{Int64, Int64} in sr.netstoich[2] + J[get_index(comp_j, stoich[1], nV), get_index(comp_j, sub, nV)] += rate * + stoich[2] end end end @@ -264,50 +329,58 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::In end end -function get_rate_differential(sr::SpatialReactionIndexed, pE::Matrix{Float64}, diff_species::Int64, u_src, u_dst) +function get_rate_differential(sr::SpatialReactionIndexed, pE::Matrix{Float64}, + diff_species::Int64, u_src, u_dst) product::Float64 = pE[sr.rate] - !isempty(sr.substrates[1]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) - if diff_species==sub - product *= stoich*u_src[sub]^(stoich-1) / factorial(stoich) - else - product *= u_src[sub]^stoich / factorial(stoich) + !isempty(sr.substrates[1]) && + for (sub::Int64, stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) + if diff_species == sub + product *= stoich * u_src[sub]^(stoich - 1) / factorial(stoich) + else + product *= u_src[sub]^stoich / factorial(stoich) + end + end + !isempty(sr.substrates[2]) && + for (sub::Int64, stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) + product *= u_dst[sub]^stoich / factorial(stoich) end - end - !isempty(sr.substrates[2]) && for (sub::Int64,stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[sub]^stoich / factorial(stoich) - end return product end -function build_jac_prototype(nS::Int64, nV::Int64, spatial_reactions::Vector{SpatialReactionIndexed}, lrs::LatticeReactionSystem, sparse::Bool) - jac_prototype = (sparse ? spzeros(nS*nV,nS*nV) : zeros(nS*nV,nS*nV)::Matrix{Float64}) +function build_jac_prototype(nS::Int64, nV::Int64, + spatial_reactions::Vector{SpatialReactionIndexed}, + lrs::LatticeReactionSystem, sparse::Bool) + jac_prototype = (sparse ? spzeros(nS * nV, nS * nV) : + zeros(nS * nV, nS * nV)::Matrix{Float64}) # Sets non-spatial reactions. # Tries to utilise sparsity within each comaprtment. - for comp_i in 1:nV, reaction in reactions(lrs.rs) + for comp_i in 1:nV, reaction in reactions(lrs.rs) for substrate in reaction.substrates, ns in reaction.netstoich sub_idx = findfirst(isequal(substrate), states(lrs.rs)) spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) - jac_prototype[get_index(comp_i,spec_idx,nV), get_index(comp_i,sub_idx,nV)] = 1 + jac_prototype[get_index(comp_i, spec_idx, nV), get_index(comp_i, sub_idx, nV)] = 1 end end for comp_i::Int64 in 1:nV - for comp_j::Int64 in lrs.lattice.fadjlist[comp_i]::Vector{Int64}, sr::SpatialReactionIndexed in spatial_reactions + for comp_j::Int64 in lrs.lattice.fadjlist[comp_i]::Vector{Int64}, + sr::SpatialReactionIndexed in spatial_reactions + for sub::Int64 in sr.substrates[1] - for stoich::Pair{Int64,Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i,stoich[1],nV),get_index(comp_i,sub,nV)] = 1.0 + for stoich::Pair{Int64, Int64} in sr.netstoich[1] + jac_prototype[get_index(comp_i, stoich[1], nV), get_index(comp_i, sub, nV)] = 1.0 end - for stoich::Pair{Int64,Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j,stoich[1],nV),get_index(comp_i,sub,nV)] = 1.0 + for stoich::Pair{Int64, Int64} in sr.netstoich[2] + jac_prototype[get_index(comp_j, stoich[1], nV), get_index(comp_i, sub, nV)] = 1.0 end end for sub::Int64 in sr.substrates[2] - for stoich::Pair{Int64,Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i,stoich[1],nV),get_index(comp_j,sub,nV)] = 1.0 + for stoich::Pair{Int64, Int64} in sr.netstoich[1] + jac_prototype[get_index(comp_i, stoich[1], nV), get_index(comp_j, sub, nV)] = 1.0 end - for stoich::Pair{Int64,Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j,stoich[1],nV),get_index(comp_j,sub,nV)] = 1.0 + for stoich::Pair{Int64, Int64} in sr.netstoich[2] + jac_prototype[get_index(comp_j, stoich[1], nV), get_index(comp_j, sub, nV)] = 1.0 end end end @@ -319,5 +392,5 @@ end # get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species # get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS -get_index(container::Int64,species::Int64,nC) = (species-1)*nC + container -get_indexes(container::Int64,nC,l) = container:nC:l \ No newline at end of file +get_index(container::Int64, species::Int64, nC) = (species - 1) * nC + container +get_indexes(container::Int64, nC, l) = container:nC:l diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index a0fbde7b62..66bc2ef805 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,6 +1,5 @@ ### Fetches Stuff ### - # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test using BenchmarkTools, Statistics @@ -10,26 +9,22 @@ using Graphs using StableRNGs rng = StableRNG(12345) - ### Helper Functions ### rand_v_vals(grid) = rand(length(vertices(grid))) -rand_v_vals(grid, x::Number) = rand_v_vals(grid)*x +rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x rand_e_vals(grid) = rand(length(edges(grid))) -rand_e_vals(grid, x::Number) = rand_e_vals(grid)*x +rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x function make_u0_matrix(value_map, vals, symbols) - (length(symbols)==0) && (return zeros(0, length(vals))) + (length(symbols) == 0) && (return zeros(0, length(vals))) d = Dict(value_map) return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] end - ### Declares Models ### # Small non-stiff network. -binding_system = @reaction_network begin - (kB,kD), X +Y <--> XY -end +binding_system = @reaction_network begin (kB, kD), X + Y <--> XY end binding_p = [:kB => 2.0, :kD => 0.5] binding_dif_x = DiffusionReaction(:dX, :X) @@ -50,8 +45,33 @@ CuH_Amination_system = @reaction_network begin 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand 10.0^kdc, CuHLigand + CuHLigand --> Decomposition end -CuH_Amination_p = [:kp1 => 1.2, :kp2 => -0.72, :k1 => 0.57, :k_1 => -3.5, :k2 => -0.35, :k_2 => -0.77, :k3 => -0.025, :kam => -2.6, :kdc => -3.0] -CuH_Amination_u0 = [:CuoAc => 0.0065, :Ligand => 0.0072, :CuoAcLigand => 0.0, :Silane => 0.65, :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] +CuH_Amination_p = [ + :kp1 => 1.2, + :kp2 => -0.72, + :k1 => 0.57, + :k_1 => -3.5, + :k2 => -0.35, + :k_2 => -0.77, + :k3 => -0.025, + :kam => -2.6, + :kdc => -3.0, +] +CuH_Amination_u0 = [ + :CuoAc => 0.0065, + :Ligand => 0.0072, + :CuoAcLigand => 0.0, + :Silane => 0.65, + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, +] CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) @@ -59,7 +79,13 @@ CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) CuH_Amination_srs_1 = [CuH_Amination_diff_1] -CuH_Amination_srs_2 = [CuH_Amination_diff_1, CuH_Amination_diff_2, CuH_Amination_diff_3, CuH_Amination_diff_4, CuH_Amination_diff_5] +CuH_Amination_srs_2 = [ + CuH_Amination_diff_1, + CuH_Amination_diff_2, + CuH_Amination_diff_3, + CuH_Amination_diff_4, + CuH_Amination_diff_5, +] # Small stiff system. brusselator_system = @reaction_network begin @@ -77,25 +103,38 @@ brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] # Mid-sized stiff system. sigmaB_system = @reaction_network begin - kDeg, (w,w2,w2v,v,w2v2,vP,σB,w2σB) ⟶ ∅ - kDeg, vPp ⟶ phos - (kBw,kDw), 2w ⟷ w2 - (kB1,kD1), w2 + v ⟷ w2v - (kB2,kD2), w2v + v ⟷ w2v2 - kK1, w2v ⟶ w2 + vP - kK2, w2v2 ⟶ w2v + vP - (kB3,kD3), w2 + σB ⟷ w2σB - (kB4,kD4), w2σB + v ⟷ w2v + σB - (kB5,kD5), vP + phos ⟷ vPp - kP, vPp ⟶ v + phos - v0*((1+F*σB)/(K+σB)), ∅ ⟶ σB - λW*v0*((1+F*σB)/(K+σB)), ∅ ⟶ w - λV*v0*((1+F*σB)/(K+σB)), ∅ ⟶ v + kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ + kDeg, vPp ⟶ phos + (kBw, kDw), 2w ⟷ w2 + (kB1, kD1), w2 + v ⟷ w2v + (kB2, kD2), w2v + v ⟷ w2v2 + kK1, w2v ⟶ w2 + vP + kK2, w2v2 ⟶ w2v + vP + (kB3, kD3), w2 + σB ⟷ w2σB + (kB4, kD4), w2σB + v ⟷ w2v + σB + (kB5, kD5), vP + phos ⟷ vPp + kP, vPp ⟶ v + phos + v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ σB + λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w + λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v end -sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, :kB4 => 1800, :kB5 => 3600, - :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, :kP => 180, :kDeg => 0.7, - :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] -sigmaB_u0 = [:w => 1.0, :w2 => 1.0, :w2v => 1.0, :v => 1.0, :w2v2 => 1.0, :vP => 1.0, :σB => 1.0, :w2σB => 1.0, :vPp => 0.0, :phos => 0.4] +sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, + :kB4 => 1800, :kB5 => 3600, + :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, + :kP => 180, :kDeg => 0.7, + :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] +sigmaB_u0 = [ + :w => 1.0, + :w2 => 1.0, + :w2v => 1.0, + :v => 1.0, + :w2v2 => 1.0, + :vP => 1.0, + :σB => 1.0, + :w2σB => 1.0, + :vPp => 0.0, + :phos => 0.4, +] sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) sigmaB_dif_w = DiffusionReaction(:Dw, :w) @@ -103,7 +142,6 @@ sigmaB_dif_v = DiffusionReaction(:Dv, :v) sigmaB_srs_1 = [sigmaB_dif_σB] sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] - ### Declares Lattices ### # Grids. @@ -123,32 +161,36 @@ long_path = path_graph(1000) small_directed_cycle = cycle_graph(100) large_directed_cycle = cycle_graph(1000) - ### Test No Error During Runs ### for grid in [small_2d_grid, short_path, small_directed_cycle] # Non-stiff case - for srs in [Vector{DiffusionReaction}(), binding_srs_1, binding_srs_2] + for srs in [Vector{DiffusionReaction}(), binding_srs_1, binding_srs_2] lrs = LatticeReactionSystem(binding_system, srs, grid) u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] u0_2 = [:X => rand_v_vals(lrs.lattice), :Y => 2.0, :XY => 0.0] u0_3 = [:X => 1.0, :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] - u0_4 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice,3)] - u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + u0_4 = [ + :X => rand_v_vals(lrs.lattice), + :Y => rand_v_vals(lrs.lattice), + :XY => rand_v_vals(lrs.lattice, 3), + ] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:kB => 2.0, :kD => 0.5] p2 = [:kB => 2.0, :kD => rand_v_vals(lrs.lattice)] p3 = [:kB => rand_v_vals(lrs.lattice), :kD => rand_v_vals(lrs.lattice)] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) - for pV in [p1, p2, p3, p4] + for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) pE_2 = map(sp -> sp => rand(), lrs.spatial_params) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); jac=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) end end @@ -156,16 +198,20 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] end # Stiff case - for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] + for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] lrs = LatticeReactionSystem(brusselator_system, srs, grid) u0_1 = [:X => 1.0, :Y => 20.0] u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] - u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) + u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4] p1 = [:A => 1.0, :B => 4.0] p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] - p3 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0)] + p3 = [ + :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), + ] p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) @@ -173,10 +219,10 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV, pE); sparse=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); sparse = false) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) end end @@ -184,127 +230,185 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] end end - ### Tests Runtimes ### # Timings currently are from Torkel's computer. # Small grid, small, non-stiff, system. -let +let lrs = LatticeReactionSystem(binding_system, binding_srs_2, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + u0 = [ + :X => rand_v_vals(lrs.lattice), + :Y => rand_v_vals(lrs.lattice), + :XY => rand_v_vals(lrs.lattice), + ] pV = binding_p pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - + runtime_target = 0.00089 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end - # Large grid, small, non-stiff, system. -let +let lrs = LatticeReactionSystem(binding_system, binding_srs_2, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice), :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + u0 = [ + :X => rand_v_vals(lrs.lattice), + :Y => rand_v_vals(lrs.lattice), + :XY => rand_v_vals(lrs.lattice), + ] pV = binding_p pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - + runtime_target = 0.451 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, small, stiff, system. -let +let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice,10), :Y => rand_v_vals(lrs.lattice,10)] + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p - pE = [:dX => 0.2,] - oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - + runtime_target = 0.05 - runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, small, stiff, system. -let +let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice,10), :Y => rand_v_vals(lrs.lattice,10)] + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p - pE = [:dX => 0.2,] - oprob = ODEProblem(lrs, u0, (0.0,100.0), (pV,pE)) + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - + runtime_target = 140.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, mid-sized, non-stiff, system. -let +let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) - u0 = [:CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :CuoAcLigand => 0.0, :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - + runtime_target = 0.00293 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, mid-sized, non-stiff, system. -let +let lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) - u0 = [:CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :CuoAcLigand => 0.0, :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :CuHLigand => 0.0, :SilaneOAc => 0.0, :Styrene => 0.16, :AlkylCuLigand => 0.0, :Amine_E => 0.39, :AlkylAmine => 0.0, :Cu_ELigand => 0.0, :E_Silane => 0.0, :Amine => 0.0, :Decomposition => 0.0] + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] pV = CuH_Amination_p pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE); jac=false) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - + runtime_target = 1.257 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, mid-sized, stiff, system. -let +let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - + runtime_target = 0.023 - runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, mid-sized, stiff, system. -let +let lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [:w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :vPp => 0.0, :phos => 0.4] + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0,10.0), (pV,pE)) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - + runtime_target = 111.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times)/1000000000 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2*runtime_target -end \ No newline at end of file + @test runtime < 1.2 * runtime_target +end From 7db52c4be5c49e56e622dea79792be4f56571645 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 10:39:15 -0400 Subject: [PATCH 011/134] change time requirements --- .../lattice_reaction_systems.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 66bc2ef805..d55298b74f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -249,7 +249,7 @@ let runtime_target = 0.00089 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Large grid, small, non-stiff, system. @@ -268,7 +268,7 @@ let runtime_target = 0.451 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Small grid, small, stiff, system. @@ -283,7 +283,7 @@ let runtime_target = 0.05 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Large grid, small, stiff, system. @@ -298,7 +298,7 @@ let runtime_target = 140.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Small grid, mid-sized, non-stiff, system. @@ -328,7 +328,7 @@ let runtime_target = 0.00293 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Large grid, mid-sized, non-stiff, system. @@ -358,7 +358,7 @@ let runtime_target = 1.257 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Small grid, mid-sized, stiff, system. @@ -384,7 +384,7 @@ let runtime_target = 0.023 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end # Large grid, mid-sized, stiff, system. @@ -410,5 +410,5 @@ let runtime_target = 111.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < 10 * runtime_target end From 9594396fc0451c49ae808c043661e5a47bd7344d Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 16:45:55 -0400 Subject: [PATCH 012/134] use SIR test --- .../lattice_reaction_systems.jl | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index d55298b74f..1115bd0487 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -24,14 +24,18 @@ end ### Declares Models ### # Small non-stiff network. -binding_system = @reaction_network begin (kB, kD), X + Y <--> XY end -binding_p = [:kB => 2.0, :kD => 0.5] +SIR_system = @reaction_network begin + α, S + I --> 2I + β, I --> R +end +SIR_p = [:α => 0.1 / 1000, :β => 0.01] +SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] -binding_dif_x = DiffusionReaction(:dX, :X) -binding_dif_y = DiffusionReaction(:dY, :Y) -binding_dif_xy = DiffusionReaction(:dXY, :XY) -binding_srs_1 = [binding_dif_x] -binding_srs_2 = [binding_dif_x, binding_dif_y, binding_dif_xy] +SIR_dif_S = DiffusionReaction(:dS, :S) +SIR_dif_I = DiffusionReaction(:dI, :I) +SIR_dif_R = DiffusionReaction(:dR, :R) +SIR_srs_1 = [SIR_dif_S] +SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] # Mid-sized non-stiff system. CuH_Amination_system = @reaction_network begin @@ -164,27 +168,34 @@ large_directed_cycle = cycle_graph(1000) ### Test No Error During Runs ### for grid in [small_2d_grid, short_path, small_directed_cycle] # Non-stiff case - for srs in [Vector{DiffusionReaction}(), binding_srs_1, binding_srs_2] - lrs = LatticeReactionSystem(binding_system, srs, grid) - u0_1 = [:X => 1.0, :Y => 2.0, :XY => 0.0] - u0_2 = [:X => rand_v_vals(lrs.lattice), :Y => 2.0, :XY => 0.0] - u0_3 = [:X => 1.0, :Y => rand_v_vals(lrs.lattice), :XY => rand_v_vals(lrs.lattice)] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] + u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] + u0_3 = [ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ] u0_4 = [ - :X => rand_v_vals(lrs.lattice), - :Y => rand_v_vals(lrs.lattice), - :XY => rand_v_vals(lrs.lattice, 3), + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), ] u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:kB => 2.0, :kD => 0.5] - p2 = [:kB => 2.0, :kD => rand_v_vals(lrs.lattice)] - p3 = [:kB => rand_v_vals(lrs.lattice), :kD => rand_v_vals(lrs.lattice)] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) - pE_2 = map(sp -> sp => rand(), lrs.spatial_params) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) + pE_1 = map(sp -> sp => 0.01, lrs.spatial_params) + pE_2 = map(sp -> sp => 0.01, lrs.spatial_params) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), lrs.spatial_params) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @@ -235,40 +246,32 @@ end # Small grid, small, non-stiff, system. let - lrs = LatticeReactionSystem(binding_system, binding_srs_2, small_2d_grid) - u0 = [ - :X => rand_v_vals(lrs.lattice), - :Y => rand_v_vals(lrs.lattice), - :XY => rand_v_vals(lrs.lattice), - ] - pV = binding_p - pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00089 + runtime_target = 0.027 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, small, non-stiff, system. let - lrs = LatticeReactionSystem(binding_system, binding_srs_2, large_2d_grid) - u0 = [ - :X => rand_v_vals(lrs.lattice), - :Y => rand_v_vals(lrs.lattice), - :XY => rand_v_vals(lrs.lattice), - ] - pV = binding_p - pE = [:dX => 0.1, :dY => 0.2, :dXY => 0.05] + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) runtime_target = 0.451 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, small, stiff, system. From 573791c0be34249347cb08e82851b15ad73def59 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 16:57:56 -0400 Subject: [PATCH 013/134] update --- .../lattice_reaction_systems.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 1115bd0487..2e44debae9 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -198,7 +198,7 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), lrs.spatial_params) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @@ -250,10 +250,10 @@ let u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.027 + runtime_target = 0.00023 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2 * runtime_target @@ -265,10 +265,10 @@ let u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.451 + runtime_target = 0.1 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < 1.2 * runtime_target From 22436ca157a2176d596f8abf136b8744538fdeed Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 20:06:46 -0400 Subject: [PATCH 014/134] update --- src/lattice_reaction_system_diffusion.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 8cf7961dbd..b1a60d5748 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -79,12 +79,10 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp for s in getfield.(lrs.spatial_reactions, :species)] f = build_f(ofunc, pV, pE, diffusion_species, lrs) - jac_prototype = (use_jac ? + jac_prototype = (sparse ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = true) : nothing) - jac = (use_jac ? - build_jac(ofunc, pV, pE, diffusion_species, lrs, jac_prototype; - sparse = sparse) : nothing) + jac = (use_jac ? build_jac(ofunc, pV, pE, diffusion_species, lrs, (isnothing(jac_prototype) ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = true) : jac_prototype); sparse = sparse) : nothing) return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) end From e42f131931604e8273fc0ce0c16122b5b22fff02 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 30 Jun 2023 18:14:56 -0400 Subject: [PATCH 015/134] More tests --- src/lattice_reaction_system_diffusion.jl | 21 ++++-- .../lattice_reaction_systems.jl | 65 ++++++++++++++++++- 2 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index b1a60d5748..5de77ce590 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -29,14 +29,17 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p nC::Int64 """The number of species.""" nS::Int64 + """Whenever the initial input was a di graph.""" + init_digraph::Bool - function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) + + function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph; init_digraph=true) return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), - length(species(rs))) + length(species(rs)), init_digraph) end function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice)) + return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); init_digraph=false) end end @@ -51,6 +54,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, pV, pE = split_parameters(p_in, lrs.spatial_params) pV = resort_values(pV, Symbol.(parameters(lrs.rs))) pE = resort_values(pE, lrs.spatial_params) + lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice))/2)) ofun = build_odefunction(lrs, pV, pE, jac, sparse) return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) @@ -70,6 +74,13 @@ function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) last.(sort(values; by = val -> findfirst(val[1] .== symbols))) end resort_values(values::Any, symbols::Vector{Symbol}) = values +# If a graph was given as lattice, internal it has a digraph representation (2n edges), this duplicates edges parameters (if n values are given for one parameters). +duplicate_edge_params(pE::Matrix, nE::Float64) = (size(pE)[2]==nE) ? reshape(vcat(pE,pE),size(pE)[1],2*size(pE)[2]) : pE +duplicate_edge_params(pE::Vector, nE::Float64) = duplicate_edge_param.(pE, nE) +duplicate_edge_param(pe::Number, nE::Float64) = pe +duplicate_edge_param(pe::Pair{Symbol,Number}, nE::Float64) = pe +duplicate_edge_param(pe::Vector, nE::Float64) = (length(pe)==nE) ? hcat(pe,pe)'[1:end] : pe +duplicate_edge_param(pe::Pair{Symbol,Vector}, nE::Float64) = (length(pe[2])==nE) ? pe[1] => hcat(pe[2],pe[2])'[1:end] : pe # Builds an ODEFunction. function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) @@ -92,7 +103,6 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, leaving_rates = zeros(length(diffusion_species), lrs.nC) for (s_idx, species) in enumerate(diffusion_species), (e_idx, e) in enumerate(edges(lrs.lattice)) - leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) end p_base = deepcopy(first.(pV)) @@ -186,8 +196,9 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ (comp - 1) * lrs.nS if in(s, diffusion_species) + # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s - split_idx = findfirst(diffusion_rows .> rows[1]) + split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, diffusion_rows[split_idx:end]) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 2e44debae9..abee1d011e 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -2,7 +2,7 @@ # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test -using BenchmarkTools, Statistics +using BenchmarkTools, LinearAlgebra, Statistics using Graphs # Sets rnd number. @@ -241,6 +241,69 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] end end + +### Tests Special Cases ### + +# Create network with vaious combinations of graph/di-graph and parameters. +lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) +lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) +u0 = [:S => 990.0, :I => 20.0*rand_v_vals(lrs_digraph.lattice), :R => 0.0] +pV = SIR_p +pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] +pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] +pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01 0.01 0.01] +pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] +pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] +pE_graph_3 = [0.10 0.12 0.14 ; 0.01 0.01 0.01; 0.01 0.01 0.01] +oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_1)) +oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_2)) +oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_3)) +oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_1)) +oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_1)) +oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_2)) +oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_2)) +oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_3)) +oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_3)) +sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] +sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] +sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] +sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] +sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] +sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] +sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] +sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] +sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] + +@test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) + +# Creates networks with empty species or parameters. +binding_system_1 = @reaction_network begin + @species X(t) Y(t) XY(t) Z(t) V(t) W(t) + @parameters k1 k2 dX dXY dZ dV p1 p2 + (k1, k2), X + Y <--> XY +end +binding_sr_1 = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY), DiffusionReaction(:dZ, :Z), DiffusionReaction(:dV, :V)] +binding_lrs_1 = LatticeReactionSystem(binding_system_1, binding_sr_1, Graphs.grid([5, 5])) +binding_u0_1 = [:X => 1.0, :Y => 2.0*rand_v_vals(binding_lrs_1.lattice), :XY => 0.5, :Z => 2.0*rand_v_vals(binding_lrs_1.lattice), :V => 0.5, :W => 1.0] +binding_p_1 = [:k1 => 2.0, :k2 => 0.1 .+ rand_v_vals(binding_lrs_1.lattice), :dX => 1.0 .+ rand_e_vals(binding_lrs_1.lattice), :dXY => 3.0, :dZ => rand_e_vals(binding_lrs_1.lattice), :dV => 0.2, :p1 => 1.0, :p2 => rand_v_vals(binding_lrs_1.lattice)] +binding_oprob_1 = ODEProblem(binding_lrs_1, binding_u0_1, (0.0,10.0), binding_p_1) +binding_sol_1 = solve(binding_oprob_1, Tsit5()) + +binding_system_2 = @reaction_network begin + (k1, k2), X + Y <--> XY +end +binding_sr_2 = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] +binding_lrs_2 = LatticeReactionSystem(binding_system_2, binding_sr_2, Graphs.grid([5, 5])) +binding_u0_2 = binding_u0_1[1:3] +binding_p_2 = binding_p_1[1:4] +binding_oprob_2 = ODEProblem(binding_lrs_2, binding_u0_2, (0.0,10.0), binding_p_2) +binding_sol_2 = solve(binding_oprob_2, Tsit5()) + +for i = 1:25 + @test norm(binding_sol_1.u[end][(i-1)*6+1:(i-1)*6+3] .- binding_sol_2.u[end][(i-1)*3+1:(i-1)*3+3]) <1e-3 +end + + ### Tests Runtimes ### # Timings currently are from Torkel's computer. From be32b6cd16e2260d0de6953d3039ae7ec10d59eb Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 12:25:00 -0400 Subject: [PATCH 016/134] test improvement --- src/lattice_reaction_system_diffusion.jl | 35 ++- .../lattice_reaction_systems.jl | 218 ++++++++++++------ 2 files changed, 178 insertions(+), 75 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 5de77ce590..248a7bca1d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -32,14 +32,15 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a di graph.""" init_digraph::Bool - - function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph; init_digraph=true) + function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph; + init_digraph = true) return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), length(species(rs)), init_digraph) end function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); init_digraph=false) + return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); + init_digraph = false) end end @@ -52,9 +53,9 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, u0 = [get_component_value(u0, species, comp) for comp in 1:(lrs.nC) for species in 1:(lrs.nS)] pV, pE = split_parameters(p_in, lrs.spatial_params) - pV = resort_values(pV, Symbol.(parameters(lrs.rs))) + pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_params)) pE = resort_values(pE, lrs.spatial_params) - lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice))/2)) + lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice)) / 2)) ofun = build_odefunction(lrs, pV, pE, jac, sparse) return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) @@ -71,16 +72,24 @@ function split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) end # Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) + issetequal(first.(values), symbols) || + error("The system's species and parameters does not match those in the input. $(first.(values)) shoud match $(symbols).") last.(sort(values; by = val -> findfirst(val[1] .== symbols))) end resort_values(values::Any, symbols::Vector{Symbol}) = values # If a graph was given as lattice, internal it has a digraph representation (2n edges), this duplicates edges parameters (if n values are given for one parameters). -duplicate_edge_params(pE::Matrix, nE::Float64) = (size(pE)[2]==nE) ? reshape(vcat(pE,pE),size(pE)[1],2*size(pE)[2]) : pE +function duplicate_edge_params(pE::Matrix, nE::Float64) + (size(pE)[2] == nE) ? reshape(vcat(pE, pE), size(pE)[1], 2 * size(pE)[2]) : pE +end duplicate_edge_params(pE::Vector, nE::Float64) = duplicate_edge_param.(pE, nE) duplicate_edge_param(pe::Number, nE::Float64) = pe -duplicate_edge_param(pe::Pair{Symbol,Number}, nE::Float64) = pe -duplicate_edge_param(pe::Vector, nE::Float64) = (length(pe)==nE) ? hcat(pe,pe)'[1:end] : pe -duplicate_edge_param(pe::Pair{Symbol,Vector}, nE::Float64) = (length(pe[2])==nE) ? pe[1] => hcat(pe[2],pe[2])'[1:end] : pe +duplicate_edge_param(pe::Pair{Symbol, Number}, nE::Float64) = pe +function duplicate_edge_param(pe::Vector, nE::Float64) + (length(pe) == nE) ? hcat(pe, pe)'[1:end] : pe +end +function duplicate_edge_param(pe::Pair{Symbol, Vector}, nE::Float64) + (length(pe[2]) == nE) ? pe[1] => hcat(pe[2], pe[2])'[1:end] : pe +end # Builds an ODEFunction. function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) @@ -93,7 +102,12 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp jac_prototype = (sparse ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = true) : nothing) - jac = (use_jac ? build_jac(ofunc, pV, pE, diffusion_species, lrs, (isnothing(jac_prototype) ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = true) : jac_prototype); sparse = sparse) : nothing) + jac = (use_jac ? + build_jac(ofunc, pV, pE, diffusion_species, lrs, + (isnothing(jac_prototype) ? + build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, + lrs; set_nonzero = true) : jac_prototype); + sparse = sparse) : nothing) return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) end @@ -103,6 +117,7 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, leaving_rates = zeros(length(diffusion_species), lrs.nC) for (s_idx, species) in enumerate(diffusion_species), (e_idx, e) in enumerate(edges(lrs.lattice)) + leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) end p_base = deepcopy(first.(pV)) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index abee1d011e..521304c467 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,8 +1,6 @@ -### Fetches Stuff ### - # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test -using BenchmarkTools, LinearAlgebra, Statistics +using BenchmarkTools, Statistics using Graphs # Sets rnd number. @@ -23,7 +21,7 @@ end ### Declares Models ### -# Small non-stiff network. +# Small non-stiff system. SIR_system = @reaction_network begin α, S + I --> 2I β, I --> R @@ -37,6 +35,16 @@ SIR_dif_R = DiffusionReaction(:dR, :R) SIR_srs_1 = [SIR_dif_S] SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] +# Small non-stiff system. +binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end +binding_dif_X = DiffusionReaction(:dX, :X) +binding_dif_Y = DiffusionReaction(:dY, :Y) +binding_dif_XY = DiffusionReaction(:dXY, :XY) +binding_sr = [binding_dif_X, binding_dif_Y, binding_dif_XY] + +binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] +binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] + # Mid-sized non-stiff system. CuH_Amination_system = @reaction_network begin 10.0^kp1, CuoAc + Ligand --> CuoAcLigand @@ -161,6 +169,12 @@ large_3d_grid = Graphs.grid([100, 100, 100]) short_path = path_graph(100) long_path = path_graph(1000) +# Unconnected graphs. +unconnected_graph = SimpleGraph(10) + +# Undirected cycle. +undirected_cycle = cycle_graph(49) + # Directed cycle. small_directed_cycle = cycle_graph(100) large_directed_cycle = cycle_graph(1000) @@ -241,69 +255,128 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] end end +### Tests Simulation Correctness ### + +# Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) + u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] + pV = brusselator_p + pE = [:dX => 0.2] + oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) + oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + + for i in 1:length(vertices(unconnected_graph)) + @test all(isapprox.(sol_nonspatial.u[end], + sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) + end +end + +# Checks that result becomes homogeneous on a connected lattice. +let + lrs = LatticeReactionSystem(binding_system, binding_sr, undirected_cycle) + u0 = [ + :X => 1.0 .+ rand_v_vals(lrs.lattice), + :Y => 2.0 * rand_v_vals(lrs.lattice), + :XY => 0.5, + ] + oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) + ss = solve(oprob, Tsit5()).u[end] + + @test all(isapprox.(ss[1:3:end], ss[1])) + @test all(isapprox.(ss[2:3:end], ss[2])) + @test all(isapprox.(ss[3:3:end], ss[3])) +end ### Tests Special Cases ### # Create network with vaious combinations of graph/di-graph and parameters. -lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) -lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) -u0 = [:S => 990.0, :I => 20.0*rand_v_vals(lrs_digraph.lattice), :R => 0.0] -pV = SIR_p -pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] -pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] -pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01 0.01 0.01] -pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] -pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] -pE_graph_3 = [0.10 0.12 0.14 ; 0.01 0.01 0.01; 0.01 0.01 0.01] -oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_1)) -oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_2)) -oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0,500.0), (pV,pE_digraph_3)) -oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_1)) -oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_1)) -oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_2)) -oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_2)) -oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_digraph_3)) -oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0,500.0), (pV,pE_graph_3)) -sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] -sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] -sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] -sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] -sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] -sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] -sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] -sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] -sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] - -@test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) +let + lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) + lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] + pV = SIR_p + pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] + pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] + pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; + 0.01 0.01 0.01 0.01 0.01 0.01] + pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] + pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] + pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] + oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) + oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) + oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) + sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] + sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] + sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] + sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] + sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] + sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] + sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] + sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] + sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] + + @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== + sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== + sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) +end # Creates networks with empty species or parameters. -binding_system_1 = @reaction_network begin - @species X(t) Y(t) XY(t) Z(t) V(t) W(t) - @parameters k1 k2 dX dXY dZ dV p1 p2 - (k1, k2), X + Y <--> XY -end -binding_sr_1 = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY), DiffusionReaction(:dZ, :Z), DiffusionReaction(:dV, :V)] -binding_lrs_1 = LatticeReactionSystem(binding_system_1, binding_sr_1, Graphs.grid([5, 5])) -binding_u0_1 = [:X => 1.0, :Y => 2.0*rand_v_vals(binding_lrs_1.lattice), :XY => 0.5, :Z => 2.0*rand_v_vals(binding_lrs_1.lattice), :V => 0.5, :W => 1.0] -binding_p_1 = [:k1 => 2.0, :k2 => 0.1 .+ rand_v_vals(binding_lrs_1.lattice), :dX => 1.0 .+ rand_e_vals(binding_lrs_1.lattice), :dXY => 3.0, :dZ => rand_e_vals(binding_lrs_1.lattice), :dV => 0.2, :p1 => 1.0, :p2 => rand_v_vals(binding_lrs_1.lattice)] -binding_oprob_1 = ODEProblem(binding_lrs_1, binding_u0_1, (0.0,10.0), binding_p_1) -binding_sol_1 = solve(binding_oprob_1, Tsit5()) - -binding_system_2 = @reaction_network begin - (k1, k2), X + Y <--> XY -end -binding_sr_2 = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] -binding_lrs_2 = LatticeReactionSystem(binding_system_2, binding_sr_2, Graphs.grid([5, 5])) -binding_u0_2 = binding_u0_1[1:3] -binding_p_2 = binding_p_1[1:4] -binding_oprob_2 = ODEProblem(binding_lrs_2, binding_u0_2, (0.0,10.0), binding_p_2) -binding_sol_2 = solve(binding_oprob_2, Tsit5()) - -for i = 1:25 - @test norm(binding_sol_1.u[end][(i-1)*6+1:(i-1)*6+3] .- binding_sol_2.u[end][(i-1)*3+1:(i-1)*3+3]) <1e-3 +let + binding_system_alt = @reaction_network begin + @species X(t) Y(t) XY(t) Z(t) V(t) W(t) + @parameters k1 k2 dX dXY dZ dV p1 p2 + (k1, k2), X + Y <--> XY + end + binding_sr_alt = [ + DiffusionReaction(:dX, :X), + DiffusionReaction(:dXY, :XY), + DiffusionReaction(:dZ, :Z), + DiffusionReaction(:dV, :V), + ] + lrs_alt = LatticeReactionSystem(binding_system_alt, binding_sr_alt, small_2d_grid) + u0_alt = [ + :X => 1.0, + :Y => 2.0 * rand_v_vals(lrs_alt.lattice), + :XY => 0.5, + :Z => 2.0 * rand_v_vals(lrs_alt.lattice), + :V => 0.5, + :W => 1.0, + ] + p_alt = [ + :k1 => 2.0, + :k2 => 0.1 .+ rand_v_vals(lrs_alt.lattice), + :dX => 1.0 .+ rand_e_vals(lrs_alt.lattice), + :dXY => 3.0, + :dZ => rand_e_vals(lrs_alt.lattice), + :dV => 0.2, + :p1 => 1.0, + :p2 => rand_v_vals(lrs_alt.lattice), + ] + oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) + ss_alt = solve(oprob_alt, Tsit5()).u[end] + + binding_sr_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] + lrs = LatticeReactionSystem(binding_system, binding_sr_main, small_2d_grid) + u0 = u0_alt[1:3] + p = p_alt[1:4] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), p) + ss = solve(oprob, Tsit5()).u[end] + + for i in 1:25 + @test isapprox(ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)], + ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)]) < 1e-3 + end end - ### Tests Runtimes ### # Timings currently are from Torkel's computer. @@ -349,7 +422,22 @@ let runtime_target = 0.05 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target +end + +# Medium grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 1.066 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < 1.2 * runtime_target end # Large grid, small, stiff, system. @@ -364,7 +452,7 @@ let runtime_target = 140.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, mid-sized, non-stiff, system. @@ -394,7 +482,7 @@ let runtime_target = 0.00293 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, mid-sized, non-stiff, system. @@ -424,7 +512,7 @@ let runtime_target = 1.257 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Small grid, mid-sized, stiff, system. @@ -450,7 +538,7 @@ let runtime_target = 0.023 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end # Large grid, mid-sized, stiff, system. @@ -476,5 +564,5 @@ let runtime_target = 111.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 10 * runtime_target + @test runtime < 1.2 * runtime_target end From 305dcf2ecd297ffa2c4f81c7329b182fec73d8dc Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 12:54:13 -0400 Subject: [PATCH 017/134] add a test --- src/lattice_reaction_system_diffusion.jl | 9 +++++++-- .../lattice_reaction_systems.jl | 19 ++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 248a7bca1d..fff0c4bc48 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -11,6 +11,8 @@ struct DiffusionReaction <: AbstractSpatialReaction """The species that is subject to difusion.""" species::Symbol end +# Creates a vector of DiffusionReactions. +DiffusionReaction(diffusion_reactions) = [DiffusionReaction(dr[1],dr[2]) for dr in diffusion_reactions] ### Lattice Reaction Network Structure ### # Desribes a spatial reaction network over a graph. @@ -32,16 +34,19 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a di graph.""" init_digraph::Bool - function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph; + function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), length(species(rs)), init_digraph) end - function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) + function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::SimpleGraph) return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); init_digraph = false) end + function LatticeReactionSystem(rs, spatial_reaction::AbstractSpatialReaction, lattice::Graphs.AbstractGraph) + return LatticeReactionSystem(rs, [spatial_reaction], lattice) + end end ### ODEProblem ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 521304c467..dbf86a01f5 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -37,11 +37,7 @@ SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] # Small non-stiff system. binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_dif_X = DiffusionReaction(:dX, :X) -binding_dif_Y = DiffusionReaction(:dY, :Y) -binding_dif_XY = DiffusionReaction(:dXY, :XY) -binding_sr = [binding_dif_X, binding_dif_Y, binding_dif_XY] - +binding_sr = DiffusionReactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] @@ -377,6 +373,19 @@ let end end +# System with single spatial reaction. +let + lrs_1 = LatticeReactionSystem(SIR_system, SIR_dif_S, small_2d_grid) + lrs_2 = LatticeReactionSystem(SIR_system, [SIR_dif_S], small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss_1, ss_2)) +end + + ### Tests Runtimes ### # Timings currently are from Torkel's computer. From a08b3b30619e6576a145803bdec61d15ecf7f0e2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:45:25 -0400 Subject: [PATCH 018/134] update and more tests --- src/Catalyst.jl | 2 +- src/lattice_reaction_system_diffusion.jl | 2 +- .../lattice_reaction_systems.jl | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 162a3cc835..dec4653678 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -71,7 +71,7 @@ export mm, mmr, hill, hillr, hillar # spatial reaction networks include("lattice_reaction_system_diffusion.jl") -export DiffusionReaction +export DiffusionReaction, DiffusionReactions export LatticeReactionSystem # functions to query network properties diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index fff0c4bc48..745056eaa9 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -126,7 +126,7 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) end p_base = deepcopy(first.(pV)) - p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] + p_update_idx = (p_base isa Vector) ? findall(typeof.(pV) .== Vector{Float64}) : [] enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) return function (du, u, p, t) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index dbf86a01f5..885439290d 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -153,6 +153,7 @@ sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] ### Declares Lattices ### # Grids. +very_small_2d_grid = Graphs.grid([2, 2]) small_2d_grid = Graphs.grid([5, 5]) medium_2d_grid = Graphs.grid([20, 20]) large_2d_grid = Graphs.grid([100, 100]) @@ -385,6 +386,27 @@ let @test all(isequal.(ss_1, ss_2)) end +# Various ways to give parameters and initial conditions. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) + u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] + u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] + u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] + pV_1 = [:α => 0.1/1000, :β => [0.01, 0.02, 0.01, 0.03]] + pV_2 = [0.1/1000, [0.01, 0.02, 0.01, 0.03]] + pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] + pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] + pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] + pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01;] + + p1 = [:α => 0.1/1000, :β => [0.01, 0.02, 0.01, 0.03], :dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] + ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] + for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] + ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss,ss_1_1)) + end +end + ### Tests Runtimes ### # Timings currently are from Torkel's computer. From 9764b2a3d3e0ab656fa62d74b887aa8d3646d999 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:45:41 -0400 Subject: [PATCH 019/134] format --- src/lattice_reaction_system_diffusion.jl | 13 +++++++++---- .../lattice_reaction_systems.jl | 19 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 745056eaa9..241d435f5b 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -12,7 +12,9 @@ struct DiffusionReaction <: AbstractSpatialReaction species::Symbol end # Creates a vector of DiffusionReactions. -DiffusionReaction(diffusion_reactions) = [DiffusionReaction(dr[1],dr[2]) for dr in diffusion_reactions] +function DiffusionReaction(diffusion_reactions) + [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] +end ### Lattice Reaction Network Structure ### # Desribes a spatial reaction network over a graph. @@ -34,17 +36,20 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a di graph.""" init_digraph::Bool - function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::DiGraph; + function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), length(species(rs)), init_digraph) end - function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::SimpleGraph) + function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::SimpleGraph) return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); init_digraph = false) end - function LatticeReactionSystem(rs, spatial_reaction::AbstractSpatialReaction, lattice::Graphs.AbstractGraph) + function LatticeReactionSystem(rs, spatial_reaction::AbstractSpatialReaction, + lattice::Graphs.AbstractGraph) return LatticeReactionSystem(rs, [spatial_reaction], lattice) end end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 885439290d..3677595277 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -392,22 +392,27 @@ let u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] - pV_1 = [:α => 0.1/1000, :β => [0.01, 0.02, 0.01, 0.03]] - pV_2 = [0.1/1000, [0.01, 0.02, 0.01, 0.03]] + pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] + pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] - pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01;] - - p1 = [:α => 0.1/1000, :β => [0.01, 0.02, 0.01, 0.03], :dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] + pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] + + p1 = [ + :α => 0.1 / 1000, + :β => [0.01, 0.02, 0.01, 0.03], + :dS => [0.01, 0.02, 0.03, 0.04], + :dI => 0.01, + :dR => 0.01, + ] ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] - @test all(isequal.(ss,ss_1_1)) + @test all(isequal.(ss, ss_1_1)) end end - ### Tests Runtimes ### # Timings currently are from Torkel's computer. From 3ac33ccede6f3f6fd5b6a33c8bc842715eeccb0b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:52:53 -0400 Subject: [PATCH 020/134] internal test update --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 3677595277..f4ce70269c 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -8,9 +8,9 @@ using StableRNGs rng = StableRNG(12345) ### Helper Functions ### -rand_v_vals(grid) = rand(length(vertices(grid))) +rand_v_vals(grid) = rand(nv(grid)) rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_e_vals(grid) = rand(length(edges(grid))) +rand_e_vals(grid) = rand(ne(grid)) rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x function make_u0_matrix(value_map, vals, symbols) @@ -265,7 +265,7 @@ let sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - for i in 1:length(vertices(unconnected_graph)) + for i in 1:nv(unconnected_graph) @test all(isapprox.(sol_nonspatial.u[end], sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) end From 22e21649764dbde5a6c87f91c5c205eb3a82b30f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 14:12:20 -0400 Subject: [PATCH 021/134] fix --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 241d435f5b..f344f2a289 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -12,7 +12,7 @@ struct DiffusionReaction <: AbstractSpatialReaction species::Symbol end # Creates a vector of DiffusionReactions. -function DiffusionReaction(diffusion_reactions) +function DiffusionReactions(diffusion_reactions) [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] end From 435bbd6bbd37274e3aae62b639c901add48edf17 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 12:31:08 -0400 Subject: [PATCH 022/134] updaye --- .../lattice_reaction_systems.jl | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index f4ce70269c..ab3cdfb3fe 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -110,6 +110,7 @@ brusselator_srs_1 = [brusselator_dif_x] brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] # Mid-sized stiff system. +# Unsure about stifness, but non-spatial version oscillates for this parameter set. sigmaB_system = @reaction_network begin kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ kDeg, vPp ⟶ phos @@ -414,7 +415,8 @@ let end ### Tests Runtimes ### -# Timings currently are from Torkel's computer. +# Current timings are taken from the SciML CI server. +runtime_reduction_margin = 2.0 # Small grid, small, non-stiff, system. let @@ -425,10 +427,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00023 + runtime_target = 0.00060 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Large grid, small, non-stiff, system. @@ -440,10 +442,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.1 + runtime_target = 0.26 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Small grid, small, stiff, system. @@ -455,10 +457,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.05 + runtime_target = 0.17 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Medium grid, small, stiff, system. @@ -470,10 +472,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 1.066 + runtime_target = 2.3 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Large grid, small, stiff, system. @@ -485,10 +487,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 140.0 + runtime_target = 170.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Small grid, mid-sized, non-stiff, system. @@ -515,10 +517,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00293 + runtime_target = 0.0016 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Large grid, mid-sized, non-stiff, system. @@ -545,10 +547,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 1.257 + runtime_target = 0.67 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Small grid, mid-sized, stiff, system. @@ -571,10 +573,10 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 0.023 + runtime_target = 0.019 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end # Large grid, mid-sized, stiff, system. @@ -597,8 +599,8 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 111.0 + runtime_target = 35.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < 1.2 * runtime_target + @test runtime < runtime_reduction_margin * runtime_target end From 0b6c5778e58cb921e6cb8b3b7b959e8d3dbd4dfe Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 17:44:17 -0400 Subject: [PATCH 023/134] add test and fixes --- src/lattice_reaction_system_diffusion.jl | 73 ++++++------------- .../lattice_reaction_systems.jl | 37 +++++++++- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index f344f2a289..3827bb5a67 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -109,16 +109,10 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp for s in getfield.(lrs.spatial_reactions, :species)] f = build_f(ofunc, pV, pE, diffusion_species, lrs) - jac_prototype = (sparse ? - build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, - lrs; set_nonzero = true) : nothing) - jac = (use_jac ? - build_jac(ofunc, pV, pE, diffusion_species, lrs, - (isnothing(jac_prototype) ? - build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, - lrs; set_nonzero = true) : jac_prototype); - sparse = sparse) : nothing) - return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) + jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = use_jac) : nothing + jac = use_jac ? build_jac(ofunc, pV, lrs, jac_prototype, sparse) : nothing + sparse || (jac_prototype = nothing) + return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) end # Creates a function for simulating the spatial ODE with spatial reactions. @@ -158,38 +152,21 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, end end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, - diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem, - jac_prototype::SparseMatrixCSC{Float64, Int64}; sparse = true) +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing,SparseMatrixCSC{Float64, Int64}}, sparse::Bool) p_base = deepcopy(first.(pV)) p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] + new_jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) + + return function (J, u, p, t) + # Sets the base according to the spatial reactions. + sparse ? (J.nzval .= new_jac_values) : (J .= new_jac_values) - if sparse - return function (J, u, p, t) - # Sets the base according to the spatial reactions. - J.nzval .= jac_prototype.nzval - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - make_p_vector!(p_base, p, p_update_idx, comp_i), t) - end - end - else - jac_diffusion = Matrix(jac_prototype) - return function (J, u, p, t) - # Sets the base according to the spatial reactions. - J .= jac_diffusion - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - make_p_vector!(p_base, p, p_update_idx, comp_i), t) - end + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(lrs.nC) + ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + make_p_vector!(p_base, p, p_update_idx, comp_i), t) end end end @@ -199,7 +176,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, set_nonzero = false) only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] - + # Declares sparse array content. J_colptr = fill(1, lrs.nC * lrs.nS + 1) J_nzval = fill(0.0, @@ -237,7 +214,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end # Set element values. - if set_nonzero + if !set_nonzero J_nzval .= 1.0 else for (s_idx, s) in enumerate(diffusion_species), @@ -249,12 +226,12 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Updates the source value. val_idx_src = col_start + - findfirst(column_view .== get_index(edge.src, s_idx, lrs.nS)) - 1 + findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 J_nzval[val_idx_src] -= get_component_value(pE, s_idx, e_idx) # Updates the destination value. val_idx_dst = col_start + - findfirst(column_view .== get_index(edge.dst, s_idx, lrs.nS)) - 1 + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 J_nzval[val_idx_dst] += get_component_value(pE, s_idx, e_idx) end end @@ -267,7 +244,7 @@ get_index(comp::Int64, species::Int64, nS::Int64) = (comp - 1) * nS + species # Gets the indexes of a compartment's species in the u array. get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) -# For set of values (stoed in a variety of possible forms), a given component (species or parameter), and a place (eitehr a compartment or edge), find that components value at that place. +# For set of values (stoed in a variety of possible forms), a given component (species or parameter), and a place (either a compartment or edge), find that components value at that place. function get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) vals[component, place] end @@ -285,11 +262,3 @@ function make_p_vector!(p_base, p, p_update_idx, comp_i) end return p_base end - -# Gets all the values in a specific palce. -function get_component_values(vals::Matrix{Float64}, place::Int64, nComponents::Int64) - (@view vals[:, place]) -end -function get_component_values(vals, place::Int64, nComponents::Int64) - [get_component_value(vals, component, place) for component in 1:nComponents] -end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index ab3cdfb3fe..8cd10500a2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -414,9 +414,39 @@ let end end +# Checks that variosu combinations of jac and sparse gives the same result. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=false, sparse=false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=false, sparse=true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=true, sparse=false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=true, sparse=true) + + ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] + @test all(isapprox.(ss, solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) + @test all(isapprox.(ss, solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) + @test all(isapprox.(ss, solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) +end + +# Splitting parameters by position +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + p1 = ([:α => 0.1 / 1000, :β => 0.01], [:dS => 0.01, :dI => 0.01, :dR => 0.01]) + p2 = [:α => 0.1 / 1000, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob1 = ODEProblem(lrs, u0, (0.0, 500.0), p1; jac = false) + oprob2 = ODEProblem(lrs, u0, (0.0, 500.0), p2; jac = false) + + @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) +end + + ### Tests Runtimes ### # Current timings are taken from the SciML CI server. -runtime_reduction_margin = 2.0 +runtime_reduction_margin = 10.0 # Small grid, small, non-stiff, system. let @@ -449,6 +479,7 @@ let end # Small grid, small, stiff, system. + let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] @@ -487,7 +518,7 @@ let oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 170.0 + runtime_target = 170. runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target @@ -603,4 +634,4 @@ let runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target -end +end \ No newline at end of file From 68786848e24363bc8f1536d98c8145f517bea0b7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 17:44:30 -0400 Subject: [PATCH 024/134] format --- src/lattice_reaction_system_diffusion.jl | 23 +++++++++------ .../lattice_reaction_systems.jl | 29 +++++++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 3827bb5a67..f69b76ce3b 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -109,10 +109,12 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp for s in getfield.(lrs.spatial_reactions, :species)] f = build_f(ofunc, pV, pE, diffusion_species, lrs) - jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, lrs; set_nonzero = use_jac) : nothing + jac_prototype = (use_jac || sparse) ? + build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, + lrs; set_nonzero = use_jac) : nothing jac = use_jac ? build_jac(ofunc, pV, lrs, jac_prototype, sparse) : nothing - sparse || (jac_prototype = nothing) - return ODEFunction(f; jac=jac, jac_prototype=jac_prototype) + sparse || (jac_prototype = nothing) + return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) end # Creates a function for simulating the spatial ODE with spatial reactions. @@ -152,11 +154,14 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, end end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing,SparseMatrixCSC{Float64, Int64}}, sparse::Bool) +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, + lrs::LatticeReactionSystem, + jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + sparse::Bool) p_base = deepcopy(first.(pV)) p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] new_jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) - + return function (J, u, p, t) # Sets the base according to the spatial reactions. sparse ? (J.nzval .= new_jac_values) : (J .= new_jac_values) @@ -164,9 +169,9 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, lrs::LatticeR # Updates for non-spatial reactions. for comp_i::Int64 in 1:(lrs.nC) ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - make_p_vector!(p_base, p, p_update_idx, comp_i), t) + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + make_p_vector!(p_base, p, p_update_idx, comp_i), t) end end end @@ -176,7 +181,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, set_nonzero = false) only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] - + # Declares sparse array content. J_colptr = fill(1, lrs.nC * lrs.nS + 1) J_nzval = fill(0.0, diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 8cd10500a2..67570acf8e 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -415,20 +415,26 @@ let end # Checks that variosu combinations of jac and sparse gives the same result. -let +let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=false, sparse=false) - oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=false, sparse=true) - oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=true, sparse=false) - oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac=true, sparse=true) + oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] - @test all(isapprox.(ss, solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) - @test all(isapprox.(ss, solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) - @test all(isapprox.(ss, solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; rtol=0.0001)) + @test all(isapprox.(ss, + solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) end # Splitting parameters by position @@ -440,10 +446,9 @@ let oprob1 = ODEProblem(lrs, u0, (0.0, 500.0), p1; jac = false) oprob2 = ODEProblem(lrs, u0, (0.0, 500.0), p2; jac = false) - @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) + @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) end - ### Tests Runtimes ### # Current timings are taken from the SciML CI server. runtime_reduction_margin = 10.0 @@ -518,7 +523,7 @@ let oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 170. + runtime_target = 170.0 runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target @@ -634,4 +639,4 @@ let runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target -end \ No newline at end of file +end From 40ef2d9a75e328bb3a40d49e82aeee79d189cca0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jul 2023 11:48:42 -0400 Subject: [PATCH 025/134] remove old file --- src/lattice_reaction_system_general.jl | 396 ------------------------- 1 file changed, 396 deletions(-) delete mode 100644 src/lattice_reaction_system_general.jl diff --git a/src/lattice_reaction_system_general.jl b/src/lattice_reaction_system_general.jl deleted file mode 100644 index 6ae2ae03ae..0000000000 --- a/src/lattice_reaction_system_general.jl +++ /dev/null @@ -1,396 +0,0 @@ -### CURRENTLY NOT IN USE ### - -### Spatial Reaction Structure. ### -# Describing a spatial reaction that involves species from two neighbouring compartments. -# Currently only permit constant rate. - -# A general spatial reaction. -struct SpatialReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currentl only constants supported""" - rate::Symbol - """Reaction substrates (source and destination).""" - substrates::Tuple{Vector{Symbol}, Vector{Symbol}} - """Reaction products (source and destination).""" - products::Tuple{Vector{Symbol}, Vector{Symbol}} - """The stoichiometric coefficients of the reactants (source and destination).""" - substoich::Tuple{Vector{Int64}, Vector{Int64}} - """The stoichiometric coefficients of the products (source and destination).""" - prodstoich::Tuple{Vector{Int64}, Vector{Int64}} - """The net stoichiometric coefficients of all species changed by the reaction (source and destination).""" - netstoich::Tuple{Vector{Pair{Symbol, Int64}}, Vector{Pair{Symbol, Int64}}} - """ - `false` (default) if `rate` should be multiplied by mass action terms to give the rate law. - `true` if `rate` represents the full reaction rate law. - Currently only `false`, is supported. - """ - only_use_rate::Bool - - """These are similar to substrates, products, and netstoich, but ses species index (instead ) """ - function SpatialReaction(rate, substrates::Tuple{Vector, Vector}, - products::Tuple{Vector, Vector}, - substoich::Tuple{Vector{Int64}, Vector{Int64}}, - prodstoich::Tuple{Vector{Int64}, Vector{Int64}}; - only_use_rate = false) - new(rate, substrates, products, substoich, prodstoich, - get_netstoich.(substrates, products, substoich, prodstoich), only_use_rate) - end -end - -# As a spatial reaction, but replaces the species (and parameter) symbols with their index. -# For internal use only (to avoid having to constantly look up species indexes). -struct SpatialReactionIndexed - rate::Int64 - substrates::Tuple{Vector{Int64}, Vector{Int64}} - products::Tuple{Vector{Int64}, Vector{Int64}} - substoich::Tuple{Vector{Int64}, Vector{Int64}} - prodstoich::Tuple{Vector{Int64}, Vector{Int64}} - netstoich::Tuple{Vector{Pair{Int64, Int64}}, Vector{Pair{Int64, Int64}}} - netstoich_new::Tuple{Dict{Int64, Int64}, Dict{Int64, Int64}} - only_use_rate::Bool - - function SpatialReactionIndexed(sr::SpatialReaction, species_list::Vector{Symbol}, - param_list::Vector{Symbol}; reverse_direction = false) - get_s_idx(species::Symbol) = findfirst(species .== (species_list)) - rate = findfirst(sr.rate .== (param_list)) - substrates = Tuple([get_s_idx.(sr.substrates[i]) for i in 1:2]) - products = Tuple([get_s_idx.(sr.products[i]) for i in 1:2]) - netstoich = Tuple([Pair.(get_s_idx.(first.(sr.netstoich[i])), - last.(sr.netstoich[i])) for i in 1:2]) - netstoich_new = Tuple([Dict(Pair.(get_s_idx.(first.(sr.netstoich[i])), - last.(sr.netstoich[i]))) for i in 1:2]) - if reverse_direction - return new(rate, - reverse.([ - substrates, - products, - sr.substoich, - sr.prodstoich, - netstoich, - netstoich_new, - ])..., sr.only_use_rate) - end - new(rate, substrates, products, sr.substoich, sr.prodstoich, netstoich, - netstoich_new, sr.only_use_rate) - end -end - -""" - DiffusionReaction(rate,species) - -Simple function to create a diffusion spatial reaction. - Equivalent to SpatialReaction(rate,([species],[]),([],[species]),([1],[]),([],[1])) -""" -function DiffusionReaction(rate, species) - SpatialReaction(rate, ([species], Symbol[]), (Symbol[], [species]), ([1], Int64[]), - (Int64[], [1])) -end - -""" - OnewaySpatialReaction(rate, substrates, products, substoich, prodstoich) - -Simple function to create a spatial reactions where all substrates are in teh soruce compartment, and all products in the destination. -Equivalent to SpatialReaction(rate,(substrates,[]),([],products),(substoich,[]),([],prodstoich)) -""" -function OnewaySpatialReaction(rate, substrates::Vector, products::Vector, - substoich::Vector{Int64}, prodstoich::Vector{Int64}) - SpatialReaction(rate, (substrates, Symbol[]), (Symbol[], products), - (substoich, Int64[]), (Int64[], prodstoich)) -end - -### Lattice Reaction Network Structure ### -# Couples: -# A reaction network (that is simulated within each compartment). -# A set of spatial reactions (denoting interaction between comaprtments). -# A network of compartments (a meta graph that can contain some additional infro for each compartment). -# The lattice is a DiGraph, normals graphs are converted to DiGraphs (with one edge in each direction). -struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs - """The spatial reactions defined between individual nodes.""" - rs::ReactionSystem - """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{SpatialReaction} - """A list of parameters that occur in the spatial reactions.""" - spatial_params::Vector{Symbol} - """The graph on which the lattice is defined.""" - lattice::DiGraph - """Dependent (state) variables representing amount of each species. Must not contain the - independent variable.""" - - function LatticeReactionSystem(rs, spatial_reactions, lattice::DiGraph) - return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), - lattice) - end - function LatticeReactionSystem(rs, spatial_reactions, lattice::SimpleGraph) - return new(rs, spatial_reactions, unique(getfield.(spatial_reactions, :rate)), - DiGraph(lattice)) - end -end - -### ODEProblem ### -# Creates an ODEProblem from a LatticeReactionSystem. -function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0, tspan, - p = DiffEqBase.NullParameters(), args...; - jac = true, sparse = true, kwargs...) - @unpack rs, spatial_reactions, lattice = lrs - - pV_in, pE_in = split_parameters(p, lrs.spatial_params) - u_idxs = Dict(reverse.(enumerate(Symbolics.getname.(states(rs))))) - pV_idxes = Dict(reverse.(enumerate(Symbol.(parameters(rs))))) - pE_idxes = Dict(reverse.(enumerate(lrs.spatial_params))) - - nS, nV, nE = length.([states(lrs.rs), vertices(lrs.lattice), edges(lrs.lattice)]) - u0 = Vector(reshape(matrix_form(u0, nV, u_idxs)', 1:(nS * nV))) - - pV = matrix_form(pV_in, nV, pV_idxes) - pE = matrix_form(pE_in, nE, pE_idxes) - - ofun = build_odefunction(lrs, jac, sparse) - return ODEProblem(ofun, u0, tspan, (pV, pE), args...; kwargs...) -end - -# Splits parameters into those for the compartments and those for the connections. -split_parameters(parameters::Tuple, spatial_params::Vector{Symbol}) = parameters -function split_parameters(parameters::Vector, spatial_params::Vector{Symbol}) - filter(p -> !in(p[1], spatial_params), parameters), - filter(p -> in(p[1], spatial_params), parameters) -end - -# Converts species and parameters to matrices form. -matrix_form(input::Matrix{Float64}, args...) = input -function matrix_form(input::Vector{Pair{Symbol, Vector{Float64}}}, n, index_dict) - isempty(input) && return zeros(0, n) - mapreduce(permutedims, vcat, last.(sort(input, by = i -> index_dict[i[1]]))) -end -function matrix_form(input::Vector, n, index_dict) - isempty(input) && return zeros(0, n) - matrix_form(map(i -> (i[2] isa Vector) ? i[1] => i[2] : i[1] => fill(i[2], n), input), - n, index_dict) -end - -# Builds an ODEFunction. -function build_odefunction(lrs::LatticeReactionSystem, use_jac::Bool, sparse::Bool) - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = true) - nS, nV = length.([states(lrs.rs), vertices(lrs.lattice)]) - - species_list = map(s -> Symbol(s.f), species(lrs.rs)) - spatial_reactions = [SpatialReactionIndexed(sr, map(s -> Symbol(s.f), species(lrs.rs)), - lrs.spatial_params) - for sr in lrs.spatial_reactions] - spatial_reactions_reversed = [SpatialReactionIndexed(sr, - map(s -> Symbol(s.f), - species(lrs.rs)), - lrs.spatial_params; - reverse_direction = true) - for sr in lrs.spatial_reactions] - spatial_reactions_dict = Dict{Int64, Vector{SpatialReactionIndexed}}([i => Vector{ - SpatialReactionIndexed - }() - for i in 1:nS]) - for sr in spatial_reactions, sub in sr.substrates[1] - push!(spatial_reactions_dict[sub], sr) - end - for sr in spatial_reactions_reversed, sub in sr.substrates[1] - push!(spatial_reactions_dict[sub], sr) - end - - f = build_f(ofunc, nS, nV, spatial_reactions, lrs.lattice.fadjlist) - jac = (use_jac ? - build_jac(ofunc, nS, nV, spatial_reactions, spatial_reactions_dict, - lrs.lattice.fadjlist) : nothing) - jac_prototype = (use_jac ? build_jac_prototype(nS, nV, spatial_reactions, lrs, sparse) : - nothing) - - return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) -end - -# Creates a function for simulating the spatial ODE with spatial reactions. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, - spatial_reactions::Vector{SpatialReactionIndexed}, - adjlist::Vector{Vector{Int64}}) - return function (du, u, p, t) - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:nV - ofunc((@view du[get_indexes(comp_i, nV, nV * nS)]), - (@view u[get_indexes(comp_i, nV, nV * nS)]), (@view p[1][:, comp_i]), t) - end - - # Updates for spatial reactions. - for comp_i::Int64 in 1:nV - for comp_j::Int64 in adjlist[comp_i], - sr::SpatialReactionIndexed in spatial_reactions - - rate::Float64 = get_rate(sr, p[2], - (@view u[get_indexes(comp_i, nV, nV * nS)]), - (@view u[get_indexes(comp_j, nV, nV * nS)])) - for stoich::Pair{Int64, Int64} in sr.netstoich[1] - du[get_index(comp_i, stoich[1], nV)] += rate * stoich[2] - end - for stoich::Pair{Int64, Int64} in sr.netstoich[2] - du[get_index(comp_j, stoich[1], nV)] += rate * stoich[2] - end - end - end - end -end - -# Get the rate of a specific reaction. -function get_rate(sr::SpatialReactionIndexed, pE::Matrix{Float64}, u_src, u_dst) - product::Float64 = pE[sr.rate] - !isempty(sr.substrates[1]) && - for (sub::Int64, stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) - product *= u_src[sub]^stoich / factorial(stoich) - end - !isempty(sr.substrates[2]) && - for (sub::Int64, stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[sub]^stoich / factorial(stoich) - end - return product -end - -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, nS::Int64, nV::Int64, - spatial_reactions::Vector{SpatialReactionIndexed}, - spatial_reactions_dict::Dict{Int64, Vector{SpatialReactionIndexed}}, - adjlist::Vector{Vector{Int64}}) - return function (J, u, p, t) - J .= 0 - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:nV - ofunc.jac((@view J[get_indexes(comp_i, nV, nV * nS), - get_indexes(comp_i, nV, nV * nS)]), - (@view u[get_indexes(comp_i, nV, nV * nS)]), (@view p[1][:, comp_i]), - t) - end - - # Updates for spatial reactions. - # for species_i in 1:nS, comp_i in 1:nV - # i_idx = sub + (comp_i-1)*nS - # for j_idx in J.rowval[J.colptr[i_idx]:J.colptr[i_idx+1]-1] - # species_j - # end - # for comp_j::Int64 in adjlist[comp_i], sr in spatial_reactions_dict[sub] - # rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) - # for stoich::Pair{Int64,Int64} in sr.netstoich[1] - # J.nzval[idxs_loolup_dict[stoich[1]]] += rate * stoich[2] - # end - # end - # end - - # idxs_loolup_dict = Dict{Int64,Int64}(Pair.(1:nV*nS, 0)) - # for sub in 1:nS, comp_i in 1:nV - # # Creates a look up, so that we for a given species (in a given container) can find its position in J.nzval. - # sub_idx = sub + (comp_i-1)*nS - # for (idx,species) in enumerate(J.rowval[J.colptr[sub_idx]:J.colptr[sub_idx+1]-1]) - # idxs_loolup_dict[species] = J.colptr[sub_idx] + idx - 1 - # end - # for comp_j::Int64 in adjlist[comp_i], sr in spatial_reactions_dict[sub] - # rate::Float64 = get_rate_differential(sr, p[2], sub, (@view u[get_indexes(comp_i,nV,nV*nS)]), (@view u[get_indexes(comp_j,nV,nV*nS)])) - # for stoich::Pair{Int64,Int64} in sr.netstoich[1] - # J.nzval[idxs_loolup_dict[stoich[1]]] += rate * stoich[2] - # end - # end - # end - - for comp_i::Int64 in 1:nV - for comp_j::Int64 in adjlist[comp_i], - sr::SpatialReactionIndexed in spatial_reactions - - for sub::Int64 in sr.substrates[1] - rate::Float64 = get_rate_differential(sr, p[2], sub, - (@view u[get_indexes(comp_i, nV, - nV * nS)]), - (@view u[get_indexes(comp_j, nV, - nV * nS)])) - for stoich::Pair{Int64, Int64} in sr.netstoich[1] - J[get_index(comp_i, stoich[1], nV), get_index(comp_i, sub, nV)] += rate * - stoich[2] - end - for stoich::Pair{Int64, Int64} in sr.netstoich[2] - J[get_index(comp_j, stoich[1], nV), get_index(comp_i, sub, nV)] += rate * - stoich[2] - end - end - for sub::Int64 in sr.substrates[2] - rate::Float64 = get_rate_differential(sr, p[2], sub, - (@view u[get_indexes(comp_j, nV, - nV * nS)]), - (@view u[get_indexes(comp_i, nV, - nV * nS)])) - for stoich::Pair{Int64, Int64} in sr.netstoich[1] - J[get_index(comp_i, stoich[1], nV), get_index(comp_j, sub, nV)] += rate * - stoich[2] - end - for stoich::Pair{Int64, Int64} in sr.netstoich[2] - J[get_index(comp_j, stoich[1], nV), get_index(comp_j, sub, nV)] += rate * - stoich[2] - end - end - end - end - end -end - -function get_rate_differential(sr::SpatialReactionIndexed, pE::Matrix{Float64}, - diff_species::Int64, u_src, u_dst) - product::Float64 = pE[sr.rate] - !isempty(sr.substrates[1]) && - for (sub::Int64, stoich::Int64) in zip(sr.substrates[1], sr.substoich[1]) - if diff_species == sub - product *= stoich * u_src[sub]^(stoich - 1) / factorial(stoich) - else - product *= u_src[sub]^stoich / factorial(stoich) - end - end - !isempty(sr.substrates[2]) && - for (sub::Int64, stoich::Int64) in zip(sr.substrates[2], sr.substoich[2]) - product *= u_dst[sub]^stoich / factorial(stoich) - end - return product -end - -function build_jac_prototype(nS::Int64, nV::Int64, - spatial_reactions::Vector{SpatialReactionIndexed}, - lrs::LatticeReactionSystem, sparse::Bool) - jac_prototype = (sparse ? spzeros(nS * nV, nS * nV) : - zeros(nS * nV, nS * nV)::Matrix{Float64}) - - # Sets non-spatial reactions. - # Tries to utilise sparsity within each comaprtment. - for comp_i in 1:nV, reaction in reactions(lrs.rs) - for substrate in reaction.substrates, ns in reaction.netstoich - sub_idx = findfirst(isequal(substrate), states(lrs.rs)) - spec_idx = findfirst(isequal(ns[1]), states(lrs.rs)) - jac_prototype[get_index(comp_i, spec_idx, nV), get_index(comp_i, sub_idx, nV)] = 1 - end - end - - for comp_i::Int64 in 1:nV - for comp_j::Int64 in lrs.lattice.fadjlist[comp_i]::Vector{Int64}, - sr::SpatialReactionIndexed in spatial_reactions - - for sub::Int64 in sr.substrates[1] - for stoich::Pair{Int64, Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i, stoich[1], nV), get_index(comp_i, sub, nV)] = 1.0 - end - for stoich::Pair{Int64, Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j, stoich[1], nV), get_index(comp_i, sub, nV)] = 1.0 - end - end - for sub::Int64 in sr.substrates[2] - for stoich::Pair{Int64, Int64} in sr.netstoich[1] - jac_prototype[get_index(comp_i, stoich[1], nV), get_index(comp_j, sub, nV)] = 1.0 - end - for stoich::Pair{Int64, Int64} in sr.netstoich[2] - jac_prototype[get_index(comp_j, stoich[1], nV), get_index(comp_j, sub, nV)] = 1.0 - end - end - end - end - return jac_prototype -end - -# Gets the index of a species (or a node's species) in the u array. -# get_index(container::Int64,species::Int64,nS) = (container-1)*nS + species -# get_indexes(container::Int64,nS) = (container-1)*nS+1:container*nS - -get_index(container::Int64, species::Int64, nC) = (species - 1) * nC + container -get_indexes(container::Int64, nC, l) = container:nC:l From c12ed86d95c021939011eae8c05f4ccb61a8b354 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 11:27:00 -0400 Subject: [PATCH 026/134] Comment out timing tests --- .../lattice_reaction_systems.jl | 365 +++++++++--------- 1 file changed, 185 insertions(+), 180 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 67570acf8e..1b35b5440f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -449,194 +449,199 @@ let @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) end -### Tests Runtimes ### -# Current timings are taken from the SciML CI server. -runtime_reduction_margin = 10.0 - -# Small grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.00060 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end -# Large grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.26 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, small, stiff, system. +### Runtime Checks ### +# Current timings are taken from the SciML CI server. +# Current not used, simply here for reference. +# Useful when attempting to optimise workflow. +if false + runtime_reduction_margin = 10.0 + + # Small grid, small, non-stiff, system. + let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00060 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + # Large grid, small, non-stiff, system. + let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.26 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Small grid, small, stiff, system. -# Medium grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + runtime_target = 0.17 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -# Large grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + # Medium grid, small, stiff, system. + let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 2.3 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Large grid, small, stiff, system. + let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 170.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -# Small grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.0016 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Small grid, mid-sized, non-stiff, system. + let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.0016 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -# Large grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.67 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Large grid, mid-sized, non-stiff, system. + let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.67 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -# Small grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Small grid, mid-sized, stiff, system. + let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.019 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end -# Large grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end + # Large grid, mid-sized, stiff, system. + let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 35.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target + end +end \ No newline at end of file From 62956d65f8ba3c1ca9350fa0c60072cca8880b82 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:08:58 -0400 Subject: [PATCH 027/134] Enable symbolics in diffusion reactions --- src/Catalyst.jl | 2 +- src/lattice_reaction_system_diffusion.jl | 21 ++++++---- .../lattice_reaction_systems.jl | 42 ++++++++++++++++--- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index dec4653678..0a4b6719b5 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -71,7 +71,7 @@ export mm, mmr, hill, hillr, hillar # spatial reaction networks include("lattice_reaction_system_diffusion.jl") -export DiffusionReaction, DiffusionReactions +export DiffusionReaction, diffusion_reactions export LatticeReactionSystem # functions to query network properties diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index f69b76ce3b..9276679d5a 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -7,12 +7,12 @@ abstract type AbstractSpatialReaction end # Currently only permit constant rates. struct DiffusionReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currentl only constants supported""" - rate::Symbol + rate::Union{Symbol,Num} """The species that is subject to difusion.""" - species::Symbol + species::Union{Symbol,Num} end # Creates a vector of DiffusionReactions. -function DiffusionReactions(diffusion_reactions) +function diffusion_reactions(diffusion_reactions) [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] end @@ -40,7 +40,7 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, - unique(getfield.(spatial_reactions, :rate)), length(vertices(lattice)), + Symbol.(unique(getfield.(spatial_reactions, :rate))), length(vertices(lattice)), length(species(rs)), init_digraph) end function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, @@ -58,13 +58,13 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = true, kwargs...) + jac = true, sparse = jac, kwargs...) u0 = resort_values(u0_in, [Symbol(s.f) for s in species(lrs.rs)]) u0 = [get_component_value(u0, species, comp) for comp in 1:(lrs.nC) for species in 1:(lrs.nS)] pV, pE = split_parameters(p_in, lrs.spatial_params) pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_params)) - pE = resort_values(pE, lrs.spatial_params) + pE = resort_values(pE, Symbol.(lrs.spatial_params)) lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice)) / 2)) ofun = build_odefunction(lrs, pV, pE, jac, sparse) @@ -82,9 +82,9 @@ function split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) end # Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) - issetequal(first.(values), symbols) || + issetequal(Symbol.(first.(values)), symbols) || error("The system's species and parameters does not match those in the input. $(first.(values)) shoud match $(symbols).") - last.(sort(values; by = val -> findfirst(val[1] .== symbols))) + last.(sort(values; by = val -> findfirst(Symbol(val[1]) .== symbols))) end resort_values(values::Any, symbols::Vector{Symbol}) = values # If a graph was given as lattice, internal it has a digraph representation (2n edges), this duplicates edges parameters (if n values are given for one parameters). @@ -106,7 +106,7 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) - for s in getfield.(lrs.spatial_reactions, :species)] + for s in to_sym.(getfield.(lrs.spatial_reactions, :species))] f = build_f(ofunc, pV, pE, diffusion_species, lrs) jac_prototype = (use_jac || sparse) ? @@ -267,3 +267,6 @@ function make_p_vector!(p_base, p, p_update_idx, comp_i) end return p_base end + +# Converts a Union{Symbol,Num} tp a Symbol. +to_sym(s::Union{Symbol,Num}) = (s isa Symbol) ? s : Symbol(s.val.f) \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 1b35b5440f..a4f1c26a29 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -37,7 +37,7 @@ SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] # Small non-stiff system. binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_sr = DiffusionReactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) +binding_srs = DiffusionReactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] @@ -274,7 +274,7 @@ end # Checks that result becomes homogeneous on a connected lattice. let - lrs = LatticeReactionSystem(binding_system, binding_sr, undirected_cycle) + lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) u0 = [ :X => 1.0 .+ rand_v_vals(lrs.lattice), :Y => 2.0 * rand_v_vals(lrs.lattice), @@ -290,6 +290,36 @@ end ### Tests Special Cases ### +# Creates network with various combiantions of Symbls and Nums in diffusion reactions. +let + @parameters dS dI dR + @variables t + @species S(t) I(t) R(t) + SIR_srs_numsym_1 = diffusion_reactions([(:dS, :S), (:dI, :I), (:dR, :R)]) + SIR_srs_numsym_2 = diffusion_reactions([(dS, :S), (dI, :I), (dR, :R)]) + SIR_srs_numsym_3 = diffusion_reactions([(:dS, S), (:dI, I), (:dR, R)]) + SIR_srs_numsym_4 = diffusion_reactions([(dS, S), (dI, I), (dR, R)]) + SIR_srs_numsym_5 = diffusion_reactions([(dS, :S), (:dI, I), (dR, :R)]) + SIR_srs_numsym_6 = diffusion_reactions([(:dS, :S), (:dI, I), (dR, R)]) + + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(small_2d_grid), :R => 0.0] + pV = SIR_p + pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + pE_2 = [dS => 0.01, dI => 0.01, dR => 0.01] + pE_3 = [dS => 0.01, :dI => 0.01, :dR => 0.01] + ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac=false), Tsit5()).u[end] + ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac=true), Rosenbrock23()).u[end] + + for srs in [SIR_srs_numsym_1, SIR_srs_numsym_2, SIR_srs_numsym_3, SIR_srs_numsym_4, SIR_srs_numsym_5, SIR_srs_numsym_6], pE in [pE_1, pE_2, pE_3] + lrs = LatticeReactionSystem(SIR_system, srs, small_2d_grid) + ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac=false), Tsit5()).u[end] + ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac=true), Rosenbrock23()).u[end] + @test all(isapprox.(ss_explicit, ss_explicit_base)) + @test all(isapprox.(ss_implicit, ss_implicit_base)) + end +end + + # Create network with vaious combinations of graph/di-graph and parameters. let lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) @@ -334,13 +364,13 @@ let @parameters k1 k2 dX dXY dZ dV p1 p2 (k1, k2), X + Y <--> XY end - binding_sr_alt = [ + binding_srs_alt = [ DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY), DiffusionReaction(:dZ, :Z), DiffusionReaction(:dV, :V), ] - lrs_alt = LatticeReactionSystem(binding_system_alt, binding_sr_alt, small_2d_grid) + lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_grid) u0_alt = [ :X => 1.0, :Y => 2.0 * rand_v_vals(lrs_alt.lattice), @@ -362,8 +392,8 @@ let oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) ss_alt = solve(oprob_alt, Tsit5()).u[end] - binding_sr_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] - lrs = LatticeReactionSystem(binding_system, binding_sr_main, small_2d_grid) + binding_srs_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] + lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_grid) u0 = u0_alt[1:3] p = p_alt[1:4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), p) From 10e1e1704a59e8b617bbae33365bde2a98fa8867 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:10:02 -0400 Subject: [PATCH 028/134] format --- src/lattice_reaction_system_diffusion.jl | 9 +++--- .../lattice_reaction_systems.jl | 29 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 9276679d5a..25071f281a 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -7,9 +7,9 @@ abstract type AbstractSpatialReaction end # Currently only permit constant rates. struct DiffusionReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currentl only constants supported""" - rate::Union{Symbol,Num} + rate::Union{Symbol, Num} """The species that is subject to difusion.""" - species::Union{Symbol,Num} + species::Union{Symbol, Num} end # Creates a vector of DiffusionReactions. function diffusion_reactions(diffusion_reactions) @@ -40,7 +40,8 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, - Symbol.(unique(getfield.(spatial_reactions, :rate))), length(vertices(lattice)), + Symbol.(unique(getfield.(spatial_reactions, :rate))), + length(vertices(lattice)), length(species(rs)), init_digraph) end function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, @@ -269,4 +270,4 @@ function make_p_vector!(p_base, p, p_update_idx, comp_i) end # Converts a Union{Symbol,Num} tp a Symbol. -to_sym(s::Union{Symbol,Num}) = (s isa Symbol) ? s : Symbol(s.val.f) \ No newline at end of file +to_sym(s::Union{Symbol, Num}) = (s isa Symbol) ? s : Symbol(s.val.f) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index a4f1c26a29..fff62aef46 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -307,19 +307,25 @@ let pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] pE_2 = [dS => 0.01, dI => 0.01, dR => 0.01] pE_3 = [dS => 0.01, :dI => 0.01, :dR => 0.01] - ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac=false), Tsit5()).u[end] - ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac=true), Rosenbrock23()).u[end] - - for srs in [SIR_srs_numsym_1, SIR_srs_numsym_2, SIR_srs_numsym_3, SIR_srs_numsym_4, SIR_srs_numsym_5, SIR_srs_numsym_6], pE in [pE_1, pE_2, pE_3] + ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = false), Tsit5()).u[end] + ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = true), Rosenbrock23()).u[end] + + for srs in [ + SIR_srs_numsym_1, + SIR_srs_numsym_2, + SIR_srs_numsym_3, + SIR_srs_numsym_4, + SIR_srs_numsym_5, + SIR_srs_numsym_6, + ], pE in [pE_1, pE_2, pE_3] lrs = LatticeReactionSystem(SIR_system, srs, small_2d_grid) - ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac=false), Tsit5()).u[end] - ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac=true), Rosenbrock23()).u[end] + ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false), Tsit5()).u[end] + ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = true), Rosenbrock23()).u[end] @test all(isapprox.(ss_explicit, ss_explicit_base)) @test all(isapprox.(ss_implicit, ss_implicit_base)) end end - # Create network with vaious combinations of graph/di-graph and parameters. let lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) @@ -479,7 +485,6 @@ let @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) end - ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. @@ -565,7 +570,8 @@ if false # Small grid, mid-sized, non-stiff, system. let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, small_2d_grid) + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + small_2d_grid) u0 = [ :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), @@ -595,7 +601,8 @@ if false # Large grid, mid-sized, non-stiff, system. let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, large_2d_grid) + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + large_2d_grid) u0 = [ :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), @@ -674,4 +681,4 @@ if false println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end -end \ No newline at end of file +end From 11e4e5e58784494d87015241ad175de99c9a5976 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:30:56 -0400 Subject: [PATCH 029/134] small fix --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index fff62aef46..7fa7d6b815 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -37,7 +37,7 @@ SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] # Small non-stiff system. binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_srs = DiffusionReactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) +binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] From cca2b4eb23ed4a236753fd561274b5fdd681d168 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 13 Jul 2023 15:14:40 -0400 Subject: [PATCH 030/134] remove BenchmarkTools test dependency --- .../lattice_reaction_systems.jl | 385 +++++++++--------- 1 file changed, 192 insertions(+), 193 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 7fa7d6b815..d4603e0010 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,6 +1,6 @@ # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test -using BenchmarkTools, Statistics +using Statistics using Graphs # Sets rnd number. @@ -489,196 +489,195 @@ end # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. # Useful when attempting to optimise workflow. -if false - runtime_reduction_margin = 10.0 - - # Small grid, small, non-stiff, system. - let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.00060 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Large grid, small, non-stiff, system. - let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.26 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Small grid, small, stiff, system. - - let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Medium grid, small, stiff, system. - let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - # Large grid, small, stiff, system. - let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Small grid, mid-sized, non-stiff, system. - let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - small_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.0016 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Large grid, mid-sized, non-stiff, system. - let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - large_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.67 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Small grid, mid-sized, stiff, system. - let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end - - # Large grid, mid-sized, stiff, system. - let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target - end -end +# runtime_reduction_margin = 10.0 +# +# # Small grid, small, non-stiff, system. +# let +# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) +# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] +# pV = SIR_p +# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] +# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) +# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) +# +# runtime_target = 0.00060 +# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 +# println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Large grid, small, non-stiff, system. +# let +# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) +# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] +# pV = SIR_p +# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] +# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) +# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) +# +# runtime_target = 0.26 +# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 +# println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Small grid, small, stiff, system. +# +# let +# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) +# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] +# pV = brusselator_p +# pE = [:dX => 0.2] +# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) +# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) +# +# runtime_target = 0.17 +# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 +# println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Medium grid, small, stiff, system. +# let +# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) +# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] +# pV = brusselator_p +# pE = [:dX => 0.2] +# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) +# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) +# +# runtime_target = 2.3 +# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 +# println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Large grid, small, stiff, system. +# let +# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) +# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] +# pV = brusselator_p +# pE = [:dX => 0.2] +# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) +# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) +# +# runtime_target = 170.0 +# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 +# println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Small grid, mid-sized, non-stiff, system. +# let +# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, +# small_2d_grid) +# u0 = [ +# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), +# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), +# :CuoAcLigand => 0.0, +# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :CuHLigand => 0.0, +# :SilaneOAc => 0.0, +# :Styrene => 0.16, +# :AlkylCuLigand => 0.0, +# :Amine_E => 0.39, +# :AlkylAmine => 0.0, +# :Cu_ELigand => 0.0, +# :E_Silane => 0.0, +# :Amine => 0.0, +# :Decomposition => 0.0, +# ] +# pV = CuH_Amination_p +# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] +# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) +# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) +# +# runtime_target = 0.0016 +# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 +# println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Large grid, mid-sized, non-stiff, system. +# let +# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, +# large_2d_grid) +# u0 = [ +# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), +# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), +# :CuoAcLigand => 0.0, +# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :CuHLigand => 0.0, +# :SilaneOAc => 0.0, +# :Styrene => 0.16, +# :AlkylCuLigand => 0.0, +# :Amine_E => 0.39, +# :AlkylAmine => 0.0, +# :Cu_ELigand => 0.0, +# :E_Silane => 0.0, +# :Amine => 0.0, +# :Decomposition => 0.0, +# ] +# pV = CuH_Amination_p +# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] +# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) +# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) +# +# runtime_target = 0.67 +# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 +# println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Small grid, mid-sized, stiff, system. +# let +# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) +# u0 = [ +# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :vPp => 0.0, +# :phos => 0.4, +# ] +# pV = sigmaB_p +# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] +# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) +# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) +# +# runtime_target = 0.019 +# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 +# println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end +# +# # Large grid, mid-sized, stiff, system. +# let +# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) +# u0 = [ +# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), +# :vPp => 0.0, +# :phos => 0.4, +# ] +# pV = sigmaB_p +# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] +# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) +# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) +# +# runtime_target = 35.0 +# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 +# println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") +# @test runtime < runtime_reduction_margin * runtime_target +# end \ No newline at end of file From 0df5cee597ce849297f91a371ae92fb215e408eb Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 1 Aug 2023 10:56:13 -0400 Subject: [PATCH 031/134] use Num's everywhere --- Project.toml | 3 +- src/lattice_reaction_system_diffusion.jl | 44 +++++++++++++++--------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/Project.toml b/Project.toml index 52199a4cb2..e38955b5f0 100644 --- a/Project.toml +++ b/Project.toml @@ -39,7 +39,6 @@ Unitful = "1.12.4" julia = "1.6" [extras] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -57,4 +56,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["BenchmarkTools", "DomainSets", "Graphviz_jll", "LinearAlgebra", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"] +test = ["DomainSets", "Graphviz_jll", "LinearAlgebra", "NonlinearSolve", "OrdinaryDiffEq", "Random", "SafeTestsets", "SciMLBase", "SciMLNLSolve", "StableRNGs", "Statistics", "SteadyStateDiffEq", "StochasticDiffEq", "Test", "Unitful"] diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 25071f281a..bf50d7b778 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -6,10 +6,19 @@ abstract type AbstractSpatialReaction end # A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. # Currently only permit constant rates. struct DiffusionReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currentl only constants supported""" - rate::Union{Symbol, Num} + """The rate function (excluding mass action terms). Currently only constants supported""" + rate::Num """The species that is subject to difusion.""" - species::Union{Symbol, Num} + species::Num + + # Default (for when both inputs are Nums). + function DiffusionReaction(rate::Num,species::Num) + return new(rate,species) + end + # If at least one input is not a Num, converts it to that. + function DiffusionReaction(rate::Any,species::Any) + return new(Symbolics.variable.([rate,species])...) + end end # Creates a vector of DiffusionReactions. function diffusion_reactions(diffusion_reactions) @@ -28,7 +37,7 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p # Derrived values. """A list of parameters that occur in the spatial reactions.""" - spatial_params::Vector{Symbol} + spatial_param_syms::Vector{Symbol} """The number of compartments.""" nC::Int64 """The number of species.""" @@ -63,9 +72,9 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, u0 = resort_values(u0_in, [Symbol(s.f) for s in species(lrs.rs)]) u0 = [get_component_value(u0, species, comp) for comp in 1:(lrs.nC) for species in 1:(lrs.nS)] - pV, pE = split_parameters(p_in, lrs.spatial_params) - pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_params)) - pE = resort_values(pE, Symbol.(lrs.spatial_params)) + pV, pE = split_parameters(p_in, lrs.spatial_param_syms) + pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_param_syms)) + pE = resort_values(pE, lrs.spatial_param_syms) lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice)) / 2)) ofun = build_odefunction(lrs, pV, pE, jac, sparse) @@ -73,18 +82,18 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Splits parameters into those for the compartments and those for the connections. -split_parameters(ps::Tuple{<:Any, <:Any}, spatial_params::Vector{Symbol}) = ps -function split_parameters(ps::Vector{<:Pair}, spatial_params::Vector{Symbol}) - (filter(p -> !(p[1] in spatial_params), ps), filter(p -> p[1] in spatial_params, ps)) +split_parameters(ps::Tuple{<:Any, <:Any}, spatial_param_syms::Vector{Symbol}) = ps +function split_parameters(ps::Vector{<:Pair}, spatial_param_syms::Vector{Symbol}) + (filter(p -> !(p[1] in spatial_param_syms), ps), filter(p -> Symbol(p[1]) in spatial_param_syms, ps)) end -function split_parameters(ps::Vector{<:Number}, spatial_params::Vector{Symbol}) - (ps[1:(length(ps) - length(spatial_params))], - ps[(length(ps) - length(spatial_params) + 1):end]) +function split_parameters(ps::Vector{<:Number}, spatial_param_syms::Vector{Symbol}) + (ps[1:(length(ps) - length(spatial_param_syms))], + ps[(length(ps) - length(spatial_param_syms) + 1):end]) end # Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) issetequal(Symbol.(first.(values)), symbols) || - error("The system's species and parameters does not match those in the input. $(first.(values)) shoud match $(symbols).") + error("The system's species and parameters does not match those in the input. $(Symbol.(first.(values))) shoud match $(symbols).") last.(sort(values; by = val -> findfirst(Symbol(val[1]) .== symbols))) end resort_values(values::Any, symbols::Vector{Symbol}) = values @@ -107,7 +116,7 @@ function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sp ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) - for s in to_sym.(getfield.(lrs.spatial_reactions, :species))] + for s in Symbol.(getfield.(lrs.spatial_reactions, :species))] f = build_f(ofunc, pV, pE, diffusion_species, lrs) jac_prototype = (use_jac || sparse) ? @@ -139,6 +148,7 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, make_p_vector!(p_base, p, p_update_idx, comp_i), t) end + # Updates for spatial diffusion reactions. for (s_idx::Int64, species::Int64) in enumerate(diffusion_species) for comp_i::Int64 in 1:(lrs.nC) du[get_index(comp_i, species, lrs.nS)] -= leaving_rates[s_idx, comp_i] * @@ -164,7 +174,7 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, new_jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) return function (J, u, p, t) - # Sets the base according to the spatial reactions. + # Updates for the spatial reactions. sparse ? (J.nzval .= new_jac_values) : (J .= new_jac_values) # Updates for non-spatial reactions. @@ -250,7 +260,7 @@ get_index(comp::Int64, species::Int64, nS::Int64) = (comp - 1) * nS + species # Gets the indexes of a compartment's species in the u array. get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) -# For set of values (stoed in a variety of possible forms), a given component (species or parameter), and a place (either a compartment or edge), find that components value at that place. +# For set of values (stored in a variety of possible forms), a given component (species or parameter), and a place (either a compartment or edge), find that components value at that place. function get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) vals[component, place] end From 845d5b6945423e0c4d6775d5fb33657f0fb97b88 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 1 Aug 2023 11:37:35 -0400 Subject: [PATCH 032/134] update --- .../lattice_reaction_systems.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index d4603e0010..09d957be67 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -205,10 +205,10 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.01, lrs.spatial_params) - pE_2 = map(sp -> sp => 0.01, lrs.spatial_params) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), lrs.spatial_params) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) + pE_1 = map(sp -> sp => 0.01, lrs.spatial_param_syms) + pE_2 = map(sp -> sp => 0.01, lrs.spatial_param_syms) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), lrs.spatial_param_syms) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) @@ -237,10 +237,10 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] ] p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.2, lrs.spatial_params) - pE_2 = map(sp -> sp => rand(), lrs.spatial_params) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_params) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_params) + pE_1 = map(sp -> sp => 0.2, lrs.spatial_param_syms) + pE_2 = map(sp -> sp => rand(), lrs.spatial_param_syms) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_param_syms) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) From 5e9c2dc2a713caee8b9c21f764b0ece69bbdb20a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 11:52:42 -0400 Subject: [PATCH 033/134] use sym properly --- src/lattice_reaction_system_diffusion.jl | 80 ++++++++++++++++--- .../lattice_reaction_systems.jl | 9 ++- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index bf50d7b778..4156133dfb 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -10,20 +10,35 @@ struct DiffusionReaction <: AbstractSpatialReaction rate::Num """The species that is subject to difusion.""" species::Num + """A symbol representation of the species that is subject to difusion.""" + species_sym::Symbol # Required for identification in certain cases. - # Default (for when both inputs are Nums). - function DiffusionReaction(rate::Num,species::Num) - return new(rate,species) + # Creates a diffusion reaction. + function DiffusionReaction(rate::Num, species::Num) + new(rate, species, ModelingToolkit.getname(species)) end - # If at least one input is not a Num, converts it to that. - function DiffusionReaction(rate::Any,species::Any) - return new(Symbolics.variable.([rate,species])...) + function DiffusionReaction(rate::Number, species::Num) + new(Num(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Symbol, species::Num) + new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Num, species::Symbol) + new(rate, Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Number, species::Symbol) + new(Num(rate), Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Symbol, species::Symbol) + new(Symbolics.variable(rate), Symbolics.variable(species), species) end end # Creates a vector of DiffusionReactions. function diffusion_reactions(diffusion_reactions) [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] end +# Gets the parameters in a diffusion reaction. +ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) ### Lattice Reaction Network Structure ### # Desribes a spatial reaction network over a graph. @@ -49,7 +64,10 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, - Symbol.(unique(getfield.(spatial_reactions, :rate))), + Symbol.(setdiff(filter(s -> Symbolics.issym(s), + vcat(Symbolics.get_variables.(getfield.(spatial_reactions, + :rate))...)), + parameters(rs))), length(vertices(lattice)), length(species(rs)), init_digraph) end @@ -63,6 +81,11 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p return LatticeReactionSystem(rs, [spatial_reaction], lattice) end end +# Gets the parameters in a lattice reaction system. +function ModelingToolkit.parameters(lrs::LatticeReactionSystem) + unique(vcat(parameters(lrs.rs), + Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) +end ### ODEProblem ### # Creates an ODEProblem from a LatticeReactionSystem. @@ -74,7 +97,8 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, for species in 1:(lrs.nS)] pV, pE = split_parameters(p_in, lrs.spatial_param_syms) pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_param_syms)) - pE = resort_values(pE, lrs.spatial_param_syms) + #pE = resort_values(pE, lrs.spatial_param_syms) + pE = compute_diffusion_rates(p_in, pE, lrs) lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice)) / 2)) ofun = build_odefunction(lrs, pV, pE, jac, sparse) @@ -84,12 +108,14 @@ end # Splits parameters into those for the compartments and those for the connections. split_parameters(ps::Tuple{<:Any, <:Any}, spatial_param_syms::Vector{Symbol}) = ps function split_parameters(ps::Vector{<:Pair}, spatial_param_syms::Vector{Symbol}) - (filter(p -> !(p[1] in spatial_param_syms), ps), filter(p -> Symbol(p[1]) in spatial_param_syms, ps)) + (filter(p -> !(p[1] in spatial_param_syms), ps), + filter(p -> Symbol(p[1]) in spatial_param_syms, ps)) end function split_parameters(ps::Vector{<:Number}, spatial_param_syms::Vector{Symbol}) (ps[1:(length(ps) - length(spatial_param_syms))], ps[(length(ps) - length(spatial_param_syms) + 1):end]) end + # Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) issetequal(Symbol.(first.(values)), symbols) || @@ -97,6 +123,7 @@ function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) last.(sort(values; by = val -> findfirst(Symbol(val[1]) .== symbols))) end resort_values(values::Any, symbols::Vector{Symbol}) = values + # If a graph was given as lattice, internal it has a digraph representation (2n edges), this duplicates edges parameters (if n values are given for one parameters). function duplicate_edge_params(pE::Matrix, nE::Float64) (size(pE)[2] == nE) ? reshape(vcat(pE, pE), size(pE)[1], 2 * size(pE)[2]) : pE @@ -111,12 +138,43 @@ function duplicate_edge_param(pe::Pair{Symbol, Vector}, nE::Float64) (length(pe[2]) == nE) ? pe[1] => hcat(pe[2], pe[2])'[1:end] : pe end +# Computes the diffusion rates for each diffusion reaction. +function compute_diffusion_rates(p_in::Tuple{<:Vector, <:Vector}, pE::Vector{<:Pair}, + lrs::LatticeReactionSystem) + compute_diffusion_rates([p_in[1]; p_in[2]], pE, lrs) +end +function compute_diffusion_rates(p_in, pE, lrs::LatticeReactionSystem) + if !(p_in isa Vector{<:Pair}) + !all(Symbolics.issym.(Symbolics.unwrap.(getfield.(lrs.spatial_reactions, :rate)))) && + error("Parameter values are NOT given as a map. This is only possible when all spatial reaction rates are single parameters.") + return resort_values(pE, lrs.spatial_param_syms) + end + p_full_dict = Dict([Symbolics.variable(p_val[1]) => p_val[2] for p_val in p_in]) + param_dependencies = Symbolics.jacobian_sparsity(getfield.(lrs.spatial_reactions, + :rate), parameters(lrs)) + diff_rate = [get_diff_rate(filter(x -> (count(isequal.(x[1], + parameters(lrs)[param_dependencies[idx, + :]])) == + 1), p_full_dict), sr) + for (idx, sr) in enumerate(lrs.spatial_reactions)] +end +function get_diff_rate(p_specific_dict, sr::DiffusionReaction) + all(v isa Float64 for v in values(p_specific_dict)) && + (return Symbolics.value(substitute(sr.rate, p_specific_dict))) + l = maximum(length.(values(p_specific_dict))) + [Symbolics.value(substitute(sr.rate, get_sub_p_dict(p_specific_dict, i))) for i in 1:l] +end +function get_sub_p_dict(p_specific_dict, i) + Dict([p_specific_dict[k] isa Vector ? k => p_specific_dict[k][i] : + k => p_specific_dict[k] for k in keys(p_specific_dict)]) +end + # Builds an ODEFunction. function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_species = Int64[findfirst(s .== [Symbol(s.f) for s in species(lrs.rs)]) - for s in Symbol.(getfield.(lrs.spatial_reactions, :species))] + diffusion_species = Int64[findfirst(isequal.(s, [Symbol(s.f) for s in species(lrs.rs)])) + for s in getfield.(lrs.spatial_reactions, :species_sym)] f = build_f(ofunc, pV, pE, diffusion_species, lrs) jac_prototype = (use_jac || sparse) ? diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 09d957be67..80bfb318c9 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -207,7 +207,8 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.01, lrs.spatial_param_syms) pE_2 = map(sp -> sp => 0.01, lrs.spatial_param_syms) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), lrs.spatial_param_syms) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + lrs.spatial_param_syms) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) @@ -239,7 +240,8 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] for pV in [p1, p2, p3, p4] pE_1 = map(sp -> sp => 0.2, lrs.spatial_param_syms) pE_2 = map(sp -> sp => rand(), lrs.spatial_param_syms) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), lrs.spatial_param_syms) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), + lrs.spatial_param_syms) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @@ -490,6 +492,7 @@ end # Current not used, simply here for reference. # Useful when attempting to optimise workflow. +# using BenchmarkTools # runtime_reduction_margin = 10.0 # # # Small grid, small, non-stiff, system. @@ -680,4 +683,4 @@ end # runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 # println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") # @test runtime < runtime_reduction_margin * runtime_target -# end \ No newline at end of file +# end From e30e7686462f415c4eb186ed3cdd3ae59cea7e33 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:02:29 -0400 Subject: [PATCH 034/134] add test --- src/lattice_reaction_system_diffusion.jl | 7 +- .../lattice_reaction_systems.jl | 119 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 4156133dfb..f5aa81a0d1 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -232,8 +232,8 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, new_jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) return function (J, u, p, t) - # Updates for the spatial reactions. - sparse ? (J.nzval .= new_jac_values) : (J .= new_jac_values) + # Because of weird stuff where the Jacobian is not reset that I don't understand properly. + sparse ? (J.nzval .= 0.0) : (J .= 0.0) # Updates for non-spatial reactions. for comp_i::Int64 in 1:(lrs.nC) @@ -242,6 +242,9 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, (@view u[get_indexes(comp_i, lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) end + + # Updates for the spatial reactions. + sparse ? (J.nzval .+= new_jac_values) : (J .+= new_jac_values) end end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 80bfb318c9..363170f358 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -487,6 +487,125 @@ let @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) end + +### Compare to Hand-written Functions ### + +# Compares the brusselator for a line of cells. +let + function spatial_brusselator_f(du, u, p, t) + # Non-spatial + for i = 1:2:length(u)-1 + du[i] = p[1] + 0.5 * (u[i]^2) * u[i+1] - u[i] - p[2]*u[i] + du[i+1] = p[2]*u[i] - 0.5 * (u[i]^2) * u[i+1] + end + + # Spatial + du[1] += p[3]*(u[3] - u[1]) + du[end-1] += p[3]*(u[end-3] - u[end-1]) + for i = 3:2:length(u)-3 + du[i] += p[3]*(u[i-2] + u[i+2] - 2u[i]) + end + end + function spatial_brusselator_jac(J, u, p, t) + J .= 0 + # Non-spatial + for i = 1:2:length(u)-1 + J[i,i] = u[i]*u[i+1]-1-p[2] + J[i,i+1] = 0.5 * (u[i]^2) + J[i+1,i] = p[2] - u[i]*u[i+1] + J[i+1,i+1] = - 0.5 * (u[i]^2) + end + + # Spatial + J[1,1] -= p[3] + J[1,3] += p[3] + J[end-1,end-1] -= p[3] + J[end-1,end-3] += p[3] + for i = 3:2:length(u)-3 + J[i,i] -= 2*p[3] + J[i,i-2] += p[3] + J[i,i+2] += p[3] + end + end + function spatial_brusselator_jac_sparse(J, u, p, t) + # Spatial + J.nzval .= 0.0 + J.nzval[7:6:end-9] .= -2p[3] + J.nzval[1] = -p[3] + J.nzval[end-3] = -p[3] + J.nzval[3:3:end-4] .= p[3] + + # Non-spatial + for i in 1:1:Int64(lenth(u)/2 -1) + j = 6(i-1)+1 + J.nzval[j] = u[i]*u[i+1]-1-p[2] + J.nzval[j+1] = 0.5 * (u[i]^2) + J.nzval[j+3] = p[2] - u[i]*u[i+1] + J.nzval[j+4] = - 0.5 * (u[i]^2) + end + J.nzval[end-3] = u[end-1]*u[end]-1-p[end-1] + J.nzval[end-2] = 0.5 * (u[end-1]^2) + J.nzval[end-1] = p[2] - u[end-1]*u[end] + J.nzval[end] = - 0.5 * (u[end-1]^2) + end + function make_jac_prototype(u0) + jac_prototype_pre = zeros(length(u0), length(u0)) + for i = 1:2:(length(u0)-1) + jac_prototype_pre[i,i] = 1 + jac_prototype_pre[i+1,i] = 1 + jac_prototype_pre[i,i+1] = 1 + jac_prototype_pre[i+1,i+1] = 1 + end + for i = 3:2:(length(u0)-1) + jac_prototype_pre[i-2,i] = 1 + jac_prototype_pre[i,i-2] = 1 + end + return sparse(jac_prototype_pre) + end + + u0 = 2*rand(10000) + p = [1.0, 4.0, 0.1] + tspan = (0.0,100.0) + + ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac=spatial_brusselator_jac) + ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac=spatial_brusselator_jac, jac_prototype=make_jac_prototype(u0)) + + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, path_graph(Int64(length(u0)/2))) + u0V = [:X => u0[1:2:end-1], :Y => u0[2:2:end]] + pV = [:A => p[1], :B => p[2]] + pE = [:dX => p[3]] + ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE);jac=true,sparse=false).f + ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE);jac=true,sparse=true).f + + du_hw_dense = deepcopy(u0) + du_hw_sparse = deepcopy(u0) + du_aut_dense = deepcopy(u0) + du_aut_sparse = deepcopy(u0) + + ofun_hw_dense(du_hw_dense , u0, p, 0.0) + ofun_hw_sparse(du_hw_sparse , u0, p, 0.0) + ofun_aut_dense(du_aut_dense , u0, p, 0.0) + ofun_aut_sparse(du_aut_sparse , u0, p, 0.0) + + @test isapprox(du_hw_dense, du_aut_dense) + @test isapprox(du_hw_sparse, du_aut_sparse) + + + J_hw_dense = deepcopy(zeros(length(u0),length(u0))) + J_hw_sparse = deepcopy(make_jac_prototype(u0)) + J_aut_dense = deepcopy(zeros(length(u0),length(u0))) + J_aut_sparse = deepcopy(make_jac_prototype(u0)) + + ofun_hw_dense.jac(J_hw_dense , u0, p, 0.0) + ofun_hw_sparse.jac(J_hw_sparse , u0, p, 0.0) + ofun_aut_dense.jac(J_aut_dense , u0, p, 0.0) + ofun_aut_sparse.jac(J_aut_sparse , u0, p, 0.0) + + @test isapprox(J_hw_dense, J_aut_dense) + @test isapprox(J_hw_sparse, J_aut_sparse) +end + + ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. From 59f92b43f379dde57e1cac1e2b676f25fefb17b4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:05:57 -0400 Subject: [PATCH 035/134] another update --- src/lattice_reaction_system_diffusion.jl | 2 +- .../lattice_reaction_systems.jl | 147 +++++++++--------- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index f5aa81a0d1..aca8f8ce32 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -242,7 +242,7 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, (@view u[get_indexes(comp_i, lrs.nS)]), make_p_vector!(p_base, p, p_update_idx, comp_i), t) end - + # Updates for the spatial reactions. sparse ? (J.nzval .+= new_jac_values) : (J .+= new_jac_values) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 363170f358..8f74dfd333 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -487,125 +487,124 @@ let @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) end - ### Compare to Hand-written Functions ### # Compares the brusselator for a line of cells. let function spatial_brusselator_f(du, u, p, t) # Non-spatial - for i = 1:2:length(u)-1 - du[i] = p[1] + 0.5 * (u[i]^2) * u[i+1] - u[i] - p[2]*u[i] - du[i+1] = p[2]*u[i] - 0.5 * (u[i]^2) * u[i+1] + for i in 1:2:(length(u) - 1) + du[i] = p[1] + 0.5 * (u[i]^2) * u[i + 1] - u[i] - p[2] * u[i] + du[i + 1] = p[2] * u[i] - 0.5 * (u[i]^2) * u[i + 1] end - + # Spatial - du[1] += p[3]*(u[3] - u[1]) - du[end-1] += p[3]*(u[end-3] - u[end-1]) - for i = 3:2:length(u)-3 - du[i] += p[3]*(u[i-2] + u[i+2] - 2u[i]) + du[1] += p[3] * (u[3] - u[1]) + du[end - 1] += p[3] * (u[end - 3] - u[end - 1]) + for i in 3:2:(length(u) - 3) + du[i] += p[3] * (u[i - 2] + u[i + 2] - 2u[i]) end end function spatial_brusselator_jac(J, u, p, t) J .= 0 # Non-spatial - for i = 1:2:length(u)-1 - J[i,i] = u[i]*u[i+1]-1-p[2] - J[i,i+1] = 0.5 * (u[i]^2) - J[i+1,i] = p[2] - u[i]*u[i+1] - J[i+1,i+1] = - 0.5 * (u[i]^2) + for i in 1:2:(length(u) - 1) + J[i, i] = u[i] * u[i + 1] - 1 - p[2] + J[i, i + 1] = 0.5 * (u[i]^2) + J[i + 1, i] = p[2] - u[i] * u[i + 1] + J[i + 1, i + 1] = -0.5 * (u[i]^2) end - + # Spatial - J[1,1] -= p[3] - J[1,3] += p[3] - J[end-1,end-1] -= p[3] - J[end-1,end-3] += p[3] - for i = 3:2:length(u)-3 - J[i,i] -= 2*p[3] - J[i,i-2] += p[3] - J[i,i+2] += p[3] + J[1, 1] -= p[3] + J[1, 3] += p[3] + J[end - 1, end - 1] -= p[3] + J[end - 1, end - 3] += p[3] + for i in 3:2:(length(u) - 3) + J[i, i] -= 2 * p[3] + J[i, i - 2] += p[3] + J[i, i + 2] += p[3] end - end + end function spatial_brusselator_jac_sparse(J, u, p, t) # Spatial J.nzval .= 0.0 - J.nzval[7:6:end-9] .= -2p[3] + J.nzval[7:6:(end - 9)] .= -2p[3] J.nzval[1] = -p[3] - J.nzval[end-3] = -p[3] - J.nzval[3:3:end-4] .= p[3] - + J.nzval[end - 3] = -p[3] + J.nzval[3:3:(end - 4)] .= p[3] + # Non-spatial - for i in 1:1:Int64(lenth(u)/2 -1) - j = 6(i-1)+1 - J.nzval[j] = u[i]*u[i+1]-1-p[2] - J.nzval[j+1] = 0.5 * (u[i]^2) - J.nzval[j+3] = p[2] - u[i]*u[i+1] - J.nzval[j+4] = - 0.5 * (u[i]^2) + for i in 1:1:Int64(lenth(u) / 2 - 1) + j = 6(i - 1) + 1 + J.nzval[j] = u[i] * u[i + 1] - 1 - p[2] + J.nzval[j + 1] = 0.5 * (u[i]^2) + J.nzval[j + 3] = p[2] - u[i] * u[i + 1] + J.nzval[j + 4] = -0.5 * (u[i]^2) end - J.nzval[end-3] = u[end-1]*u[end]-1-p[end-1] - J.nzval[end-2] = 0.5 * (u[end-1]^2) - J.nzval[end-1] = p[2] - u[end-1]*u[end] - J.nzval[end] = - 0.5 * (u[end-1]^2) + J.nzval[end - 3] = u[end - 1] * u[end] - 1 - p[end - 1] + J.nzval[end - 2] = 0.5 * (u[end - 1]^2) + J.nzval[end - 1] = p[2] - u[end - 1] * u[end] + J.nzval[end] = -0.5 * (u[end - 1]^2) end function make_jac_prototype(u0) jac_prototype_pre = zeros(length(u0), length(u0)) - for i = 1:2:(length(u0)-1) - jac_prototype_pre[i,i] = 1 - jac_prototype_pre[i+1,i] = 1 - jac_prototype_pre[i,i+1] = 1 - jac_prototype_pre[i+1,i+1] = 1 + for i in 1:2:(length(u0) - 1) + jac_prototype_pre[i, i] = 1 + jac_prototype_pre[i + 1, i] = 1 + jac_prototype_pre[i, i + 1] = 1 + jac_prototype_pre[i + 1, i + 1] = 1 end - for i = 3:2:(length(u0)-1) - jac_prototype_pre[i-2,i] = 1 - jac_prototype_pre[i,i-2] = 1 + for i in 3:2:(length(u0) - 1) + jac_prototype_pre[i - 2, i] = 1 + jac_prototype_pre[i, i - 2] = 1 end return sparse(jac_prototype_pre) end - - u0 = 2*rand(10000) - p = [1.0, 4.0, 0.1] - tspan = (0.0,100.0) - - ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac=spatial_brusselator_jac) - ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac=spatial_brusselator_jac, jac_prototype=make_jac_prototype(u0)) - - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, path_graph(Int64(length(u0)/2))) - u0V = [:X => u0[1:2:end-1], :Y => u0[2:2:end]] + + u0 = 2 * rand(10000) + p = [1.0, 4.0, 0.1] + tspan = (0.0, 100.0) + + ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) + ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, + jac_prototype = make_jac_prototype(u0)) + + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, + path_graph(Int64(length(u0) / 2))) + u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] pV = [:A => p[1], :B => p[2]] pE = [:dX => p[3]] - ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE);jac=true,sparse=false).f - ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE);jac=true,sparse=true).f + ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = false).f + ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = true).f du_hw_dense = deepcopy(u0) du_hw_sparse = deepcopy(u0) du_aut_dense = deepcopy(u0) du_aut_sparse = deepcopy(u0) - ofun_hw_dense(du_hw_dense , u0, p, 0.0) - ofun_hw_sparse(du_hw_sparse , u0, p, 0.0) - ofun_aut_dense(du_aut_dense , u0, p, 0.0) - ofun_aut_sparse(du_aut_sparse , u0, p, 0.0) - + ofun_hw_dense(du_hw_dense, u0, p, 0.0) + ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) + ofun_aut_dense(du_aut_dense, u0, p, 0.0) + ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) + @test isapprox(du_hw_dense, du_aut_dense) @test isapprox(du_hw_sparse, du_aut_sparse) - - - J_hw_dense = deepcopy(zeros(length(u0),length(u0))) + + J_hw_dense = deepcopy(zeros(length(u0), length(u0))) J_hw_sparse = deepcopy(make_jac_prototype(u0)) - J_aut_dense = deepcopy(zeros(length(u0),length(u0))) + J_aut_dense = deepcopy(zeros(length(u0), length(u0))) J_aut_sparse = deepcopy(make_jac_prototype(u0)) - - ofun_hw_dense.jac(J_hw_dense , u0, p, 0.0) - ofun_hw_sparse.jac(J_hw_sparse , u0, p, 0.0) - ofun_aut_dense.jac(J_aut_dense , u0, p, 0.0) - ofun_aut_sparse.jac(J_aut_sparse , u0, p, 0.0) - + + ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) + ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) + ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) + ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) + @test isapprox(J_hw_dense, J_aut_dense) @test isapprox(J_hw_sparse, J_aut_sparse) end - ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. From 587d00e9138c128aa10e73ad0f50fb7826f2e213 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:51:13 -0400 Subject: [PATCH 036/134] update --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 8f74dfd333..8a929df5dc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,6 +1,6 @@ # Fetch packages. using Catalyst, OrdinaryDiffEq, Random, Test -using Statistics +using Statistics, SparseArrays using Graphs # Sets rnd number. From aaf355912fb0ea03f52db87cf64a9f761594a5b2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 12:41:51 -0400 Subject: [PATCH 037/134] Internal revamp and spatial jump support --- Project.toml | 1 + src/Catalyst.jl | 4 +- src/lattice_reaction_system_diffusion.jl | 348 +++++++++++------- .../lattice_reaction_systems.jl | 122 +++--- 4 files changed, 276 insertions(+), 199 deletions(-) diff --git a/Project.toml b/Project.toml index e38955b5f0..51606037ef 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 0a4b6719b5..daea48f641 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -8,6 +8,7 @@ using SparseArrays, DiffEqBase, Reexport using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump +using SplitApplyCombine # ModelingToolkit imports and convenience functions we use using ModelingToolkit @@ -71,8 +72,9 @@ export mm, mmr, hill, hillr, hillar # spatial reaction networks include("lattice_reaction_system_diffusion.jl") -export DiffusionReaction, diffusion_reactions +export DiffusionReaction, diffusion_reactions, isdiffusionparameter export LatticeReactionSystem +export compartment_parameters, diffusion_parameters, diffusion_species # functions to query network properties include("networkapi.jl") diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index aca8f8ce32..af146e0182 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -1,4 +1,16 @@ -### Spatial Reaction Structure. ### +### Diffusion Reaction Structure. ### + +# Implements the diffusionparameter metadata field. +struct DiffusionParameter end +Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter + +isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) +function isdiffusionparameter(x, default=false) + p = Symbolics.getparent(x, nothing) + p === nothing || (x = p) + Symbolics.getmetadata(x, DiffusionParameter, default) +end + # Abstract spatial reaction structures. abstract type AbstractSpatialReaction end @@ -55,6 +67,8 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p spatial_param_syms::Vector{Symbol} """The number of compartments.""" nC::Int64 + """The number of edges.""" + nE::Int64 """The number of species.""" nS::Int64 """Whenever the initial input was a di graph.""" @@ -64,12 +78,10 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p lattice::DiGraph; init_digraph = true) return new(rs, spatial_reactions, lattice, - Symbol.(setdiff(filter(s -> Symbolics.issym(s), - vcat(Symbolics.get_variables.(getfield.(spatial_reactions, - :rate))...)), - parameters(rs))), - length(vertices(lattice)), - length(species(rs)), init_digraph) + Symbol.(setdiff(filter(s -> Symbolics.issym(s), + vcat(Symbolics.get_variables.(getfield.(spatial_reactions, :rate))...)), parameters(rs))), + length(vertices(lattice)), length(edges(lattice)), length(species(rs)), + init_digraph) end function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::SimpleGraph) @@ -81,184 +93,204 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p return LatticeReactionSystem(rs, [spatial_reaction], lattice) end end +# Gets the species of a lattice reaction system. +species(lrs::LatticeReactionSystem) = species(lrs.rs) +diffusion_species(lrs::LatticeReactionSystem) = filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), species(lrs.rs)) + # Gets the parameters in a lattice reaction system. function ModelingToolkit.parameters(lrs::LatticeReactionSystem) unique(vcat(parameters(lrs.rs), Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) end +compartment_parameters(lrs::LatticeReactionSystem) = filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) +diffusion_parameters(lrs::LatticeReactionSystem) = filter(p -> is_spatial_param(p, lrs), parameters(lrs)) -### ODEProblem ### -# Creates an ODEProblem from a LatticeReactionSystem. -function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, kwargs...) - u0 = resort_values(u0_in, [Symbol(s.f) for s in species(lrs.rs)]) - u0 = [get_component_value(u0, species, comp) for comp in 1:(lrs.nC) - for species in 1:(lrs.nS)] - pV, pE = split_parameters(p_in, lrs.spatial_param_syms) - pV = resort_values(pV, setdiff(Symbol.(parameters(lrs.rs)), lrs.spatial_param_syms)) - #pE = resort_values(pE, lrs.spatial_param_syms) - pE = compute_diffusion_rates(p_in, pE, lrs) - lrs.init_digraph || (pE = duplicate_edge_params(pE, length(edges(lrs.lattice)) / 2)) - - ofun = build_odefunction(lrs, pV, pE, jac, sparse) - return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) +# Checks whenever a parameter is a spatial parameter or not. +function is_spatial_param(p, lrs) + hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. + return (any(isequal.(p,parameters(lrs.rs))) ? false : true) end -# Splits parameters into those for the compartments and those for the connections. -split_parameters(ps::Tuple{<:Any, <:Any}, spatial_param_syms::Vector{Symbol}) = ps -function split_parameters(ps::Vector{<:Pair}, spatial_param_syms::Vector{Symbol}) - (filter(p -> !(p[1] in spatial_param_syms), ps), - filter(p -> Symbol(p[1]) in spatial_param_syms, ps)) -end -function split_parameters(ps::Vector{<:Number}, spatial_param_syms::Vector{Symbol}) - (ps[1:(length(ps) - length(spatial_param_syms))], - ps[(length(ps) - length(spatial_param_syms) + 1):end]) + +### Processes Input u0 & p ### + +# From u0 input, extracts their values and store them in the internal format. +function lattice_process_u0(u0_in, u0_symbols, nC) + u0 = lattice_process_input(u0_in, u0_symbols, nC) + check_vector_lengths(u0, nC) + expand_component_values(u0, nC) end -# Sorts a parameter (or species) vector along parameter (or species) index, and remove the Symbol in the pair. -function resort_values(values::Vector{<:Pair}, symbols::Vector{Symbol}) - issetequal(Symbol.(first.(values)), symbols) || - error("The system's species and parameters does not match those in the input. $(Symbol.(first.(values))) shoud match $(symbols).") - last.(sort(values; by = val -> findfirst(Symbol(val[1]) .== symbols))) +# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. +function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) + pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) + pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) + pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) + lrs.init_digraph || foreach(pD_vals -> duplicate_diff_params!(pD_vals, lrs), pD) + check_vector_lengths(pC, lrs.nC); check_vector_lengths(pD, lrs.nE) + return pC,pD end -resort_values(values::Any, symbols::Vector{Symbol}) = values -# If a graph was given as lattice, internal it has a digraph representation (2n edges), this duplicates edges parameters (if n values are given for one parameters). -function duplicate_edge_params(pE::Matrix, nE::Float64) - (size(pE)[2] == nE) ? reshape(vcat(pE, pE), size(pE)[1], 2 * size(pE)[2]) : pE +# Splits parameters into those for the compartments and those for the connections. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +split_parameters(ps::Vector{<:Number}, args...) = error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") +function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, p_diff_symbols::Vector) + pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] + pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] + (sum(length.([pC_in, pD_in])) != length(ps)) && error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") + return pC_in,pD_in end -duplicate_edge_params(pE::Vector, nE::Float64) = duplicate_edge_param.(pE, nE) -duplicate_edge_param(pe::Number, nE::Float64) = pe -duplicate_edge_param(pe::Pair{Symbol, Number}, nE::Float64) = pe -function duplicate_edge_param(pe::Vector, nE::Float64) - (length(pe) == nE) ? hcat(pe, pe)'[1:end] : pe + +# If the input is given in a map form, teh vector needs sorting and the first value removed. +function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) + (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") + sorted_input = sort(input; by=p->findfirst(ModelingToolkit.getname(p[1]) .== symbols)) + return lattice_process_input(last.(sorted_input), symbols, args...) end -function duplicate_edge_param(pe::Pair{Symbol, Vector}, nE::Float64) - (length(pe[2]) == nE) ? pe[1] => hcat(pe[2], pe[2])'[1:end] : pe +# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). +lattice_process_input(input::Matrix{<:Number}, args...) = lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) +lattice_process_input(input::Array{<:Number,3}, args...) = error("Have not yet written code for correctly mapping the position of reverse edge to correct location in the full edge vector.") +lattice_process_input(input::Vector{<:Any}, args...) = lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], args...) +lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input +check_vector_lengths(input::Vector{<:Vector}, n) = isempty(setdiff(unique(length.(input)),[1, n])) || error("Some inputs where given values of inappropriate length.") + +# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. +function duplicate_diff_params!(pD_vals::Vector{Float64}, lrs::LatticeReactionSystem) + (2length(pD_vals) == lrs.nE) && error("Currently, even if an undirected graph is provided, you still have to provide parameter values for each edge separately.") end -# Computes the diffusion rates for each diffusion reaction. -function compute_diffusion_rates(p_in::Tuple{<:Vector, <:Vector}, pE::Vector{<:Pair}, - lrs::LatticeReactionSystem) - compute_diffusion_rates([p_in[1]; p_in[2]], pE, lrs) +# For a set of input values on the given forms, and their symbolics, convert into a dictionary. +vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) +# Produces a dictionary with all parameter values. +param_dict(pC, pD, lrs) = merge(vals_to_dict(compartment_parameters(lrs), pC), vals_to_dict(diffusion_parameters(lrs), pD)) + +# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). +function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, pD::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) + param_value_dict = param_dict(pC, pD, lrs) + return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in diffusion_species(lrs)] end -function compute_diffusion_rates(p_in, pE, lrs::LatticeReactionSystem) - if !(p_in isa Vector{<:Pair}) - !all(Symbolics.issym.(Symbolics.unwrap.(getfield.(lrs.spatial_reactions, :rate)))) && - error("Parameter values are NOT given as a map. This is only possible when all spatial reaction rates are single parameters.") - return resort_values(pE, lrs.spatial_param_syms) - end - p_full_dict = Dict([Symbolics.variable(p_val[1]) => p_val[2] for p_val in p_in]) - param_dependencies = Symbolics.jacobian_sparsity(getfield.(lrs.spatial_reactions, - :rate), parameters(lrs)) - diff_rate = [get_diff_rate(filter(x -> (count(isequal.(x[1], - parameters(lrs)[param_dependencies[idx, - :]])) == - 1), p_full_dict), sr) - for (idx, sr) in enumerate(lrs.spatial_reactions)] +function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(ModelingToolkit.getname(s),sr.species_sym), lrs.spatial_reactions) + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. + return rates[1].rate end -function get_diff_rate(p_specific_dict, sr::DiffusionReaction) - all(v isa Float64 for v in values(p_specific_dict)) && - (return Symbolics.value(substitute(sr.rate, p_specific_dict))) - l = maximum(length.(values(p_specific_dict))) - [Symbolics.value(substitute(sr.rate, get_sub_p_dict(p_specific_dict, i))) for i in 1:l] +function compute_diffusion_rates(rate_law::Num, param_value_dict::Dict{Any,Vector{Float64}}, nE::Int64) + relevant_parameters = Symbolics.get_variables(rate_law) + if all(length(param_value_dict[P])==1 for P in relevant_parameters) + return [substitute(rate_law, Dict(p => param_value_dict[p][1] for p in relevant_parameters))] + end + return [substitute(rate_law, Dict(p => get_component_value(param_value_dict[p], idxE) for p in relevant_parameters)) for idxE in 1:nE] end -function get_sub_p_dict(p_specific_dict, i) - Dict([p_specific_dict[k] isa Vector ? k => p_specific_dict[k][i] : - k => p_specific_dict[k] for k in keys(p_specific_dict)]) + + +### ODEProblem ### + +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; + jac = true, sparse = jac, kwargs...) + + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC,pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), Symbol.(diffusion_parameters(lrs)), lrs) + ofun = build_odefunction(lrs, pC, pD, jac, sparse) + return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) end -# Builds an ODEFunction. -function build_odefunction(lrs::LatticeReactionSystem, pV, pE, use_jac::Bool, sparse::Bool) +# Builds an ODEFunction for a spatial ODEProblem. +function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) + # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_species = Int64[findfirst(isequal.(s, [Symbol(s.f) for s in species(lrs.rs)])) - for s in getfield.(lrs.spatial_reactions, :species_sym)] - - f = build_f(ofunc, pV, pE, diffusion_species, lrs) + diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) + diffusion_rates = [findfirst(isequal.(diff_rates[1], states(lrs.rs))) => diff_rates[2] for diff_rates in diffusion_rates_speciesmap] + + f = build_f(ofunc, pC, diffusion_rates, lrs) jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, pE, diffusion_species, + build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? build_jac(ofunc, pV, lrs, jac_prototype, sparse) : nothing - sparse || (jac_prototype = nothing) - return ODEFunction(f; jac = jac, jac_prototype = jac_prototype) + jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) end -# Creates a function for simulating the spatial ODE with spatial reactions. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pV, pE, - diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem) - leaving_rates = zeros(length(diffusion_species), lrs.nC) - for (s_idx, species) in enumerate(diffusion_species), +# Builds the forcing (f) function for a reaction system on a lattice. +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, + diffusion_rates::Vector{Pair{Int64,Vector{Float64}}}, lrs::LatticeReactionSystem) + + leaving_rates = zeros(length(diffusion_rates), lrs.nC) + for (s_idx, rates) in enumerate(last.(diffusion_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) - leaving_rates[s_idx, e.src] += get_component_value(pE, s_idx, e_idx) + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) end - p_base = deepcopy(first.(pV)) - p_update_idx = (p_base isa Vector) ? findall(typeof.(pV) .== Vector{Float64}) : [] + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + return function (du, u, p, t) # Updates for non-spatial reactions. for comp_i::Int64 in 1:(lrs.nC) ofunc((@view du[get_indexes(comp_i, lrs.nS)]), (@view u[get_indexes(comp_i, lrs.nS)]), - make_p_vector!(p_base, p, p_update_idx, comp_i), t) + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) end # Updates for spatial diffusion reactions. - for (s_idx::Int64, species::Int64) in enumerate(diffusion_species) + for (s_idx, (s,rates)) in enumerate(diffusion_rates) for comp_i::Int64 in 1:(lrs.nC) - du[get_index(comp_i, species, lrs.nS)] -= leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, species, + du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, s, lrs.nS)] end for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges - du[get_index(edge.dst, species, lrs.nS)] += get_component_value(pE, s_idx, - e_idx) * - u[get_index(edge.src, species, + du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * + u[get_index(edge.src, s, lrs.nS)] end end end end -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pV, +# Builds the Jacobian function for a reaction system on a lattice. +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) - p_base = deepcopy(first.(pV)) - p_update_idx = (p_base isa Vector) ? findall(typeof.(p_base) .== Vector{Float64}) : [] - new_jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) + add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : J -> (J .+= Matrix(jac_prototype))) return function (J, u, p, t) # Because of weird stuff where the Jacobian is not reset that I don't understand properly. - sparse ? (J.nzval .= 0.0) : (J .= 0.0) + reset_J_vals(J) # Updates for non-spatial reactions. for comp_i::Int64 in 1:(lrs.nC) ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), get_indexes(comp_i, lrs.nS)]), (@view u[get_indexes(comp_i, lrs.nS)]), - make_p_vector!(p_base, p, p_update_idx, comp_i), t) + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) end # Updates for the spatial reactions. - sparse ? (J.nzval .+= new_jac_values) : (J .+= new_jac_values) + add_diff_J_vals(J) end end -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, pE, - diffusion_species::Vector{Int64}, lrs::LatticeReactionSystem; +# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + diffusion_rates, lrs::LatticeReactionSystem; set_nonzero = false) - only_diff = [(s in diffusion_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] + diff_species = first.(diffusion_rates) + # Gets list of indexes for species that diffuse, but are invovled in no other reaction. + only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] # Declares sparse array content. J_colptr = fill(1, lrs.nC * lrs.nS + 1) J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_species)) + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_rates)) J_rowval = fill(0, length(J_nzval)) # Finds filled elements. @@ -266,7 +298,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, col_idx = get_index(comp, s, lrs.nS) # Column values. - local_elements = in(s, diffusion_species) * + local_elements = in(s, diff_species) * (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements @@ -274,7 +306,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Row values. rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ (comp - 1) * lrs.nS - if in(s, diffusion_species) + if in(s, diff_species) # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) @@ -294,7 +326,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, if !set_nonzero J_nzval .= 1.0 else - for (s_idx, s) in enumerate(diffusion_species), + for (s_idx, (s,rates)) in enumerate(diffusion_rates), (e_idx, edge) in enumerate(edges(lrs.lattice)) col_start = J_colptr[get_index(edge.src, s, lrs.nS)] @@ -304,41 +336,83 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Updates the source value. val_idx_src = col_start + findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 - J_nzval[val_idx_src] -= get_component_value(pE, s_idx, e_idx) + J_nzval[val_idx_src] -= get_component_value(rates, e_idx) # Updates the destination value. val_idx_dst = col_start + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 - J_nzval[val_idx_dst] += get_component_value(pE, s_idx, e_idx) + J_nzval[val_idx_dst] += get_component_value(rates, e_idx) end end return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end -# Gets the index of a species in the u array. -get_index(comp::Int64, species::Int64, nS::Int64) = (comp - 1) * nS + species -# Gets the indexes of a compartment's species in the u array. -get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) -# For set of values (stored in a variety of possible forms), a given component (species or parameter), and a place (either a compartment or edge), find that components value at that place. -function get_component_value(vals::Matrix{Float64}, component::Int64, place::Int64) - vals[component, place] +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC,pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), Symbol.(diffusion_parameters(lrs)), lrs) + return DiscreteProblem(lrs.rs, u0, tspan, (pC,pD), args...; kwargs...) end -function get_component_value(vals::Vector, component::Int64, place::Int64) - get_component_value(vals[component], place) + +# 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), checks = false, kwargs...) + dprob.p isa Tuple{Vector{Vector{Float64}},Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + hopping_constants = make_hopping_constants(dprob, lrs) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) + return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) end -get_component_value(vals::Vector{Float64}, place::Int64) = vals[place] -get_component_value(vals::Float64, place::Int64) = vals - -# Updated the base parameter vector with the values for a specific compartment. -make_p_vector!(p_base, p::Matrix{Float64}, p_update_idx, comp) = p[:, comp] -function make_p_vector!(p_base, p, p_update_idx, comp_i) - for idx in p_update_idx - p_base[idx] = p[idx][comp_i] + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], lrs)) + all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] for s in species(lrs)] + if length.(all_diff_rates) == 1 + return Catalyst.matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:lrs.nS, j in 1:lrs.nC] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:lrs.nS + push!(hopping_constants[s_idx, e.src], Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants end - return p_base end -# Converts a Union{Symbol,Num} tp a Symbol. -to_sym(s::Union{Symbol, Num}) = (s isa Symbol) ? s : Symbol(s.val.f) +# Creates the mass action jumps from a discrete problem and a lattice reaction system. +function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, combinatoric_ratelaws, checks) + any(length.(dprob.p[1]) .> 1) && error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) + ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; hopping_constants=hopping_constants, spatial_system = lrs.lattice, combinatoric_ratelaws=combinatoric_ratelaws, checks=checks) + (length(___jprob.variable_jumps) != 0) && error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump +end + +### Accessing State & Parameter Array Values ### + +# Gets the index in the u array of species s in compartment comp (when their are nS species). +get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s +# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). +get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) + +# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. +get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64) = get_component_value(values[component_idx], location_idx) +get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64, location_types::Vector{Bool}) = get_component_value(values[component_idx], location_idx, location_types[component_idx]) +get_component_value(values::Vector{<:Number}, location_idx::Int64) = get_component_value(values, location_idx, length(values)==1) +get_component_value(values::Vector{<:Number}, location_idx::Int64, location_type::Bool) = location_type ? values[1] : values[location_idx] +# Converts a vector of vectors to a long vector. +expand_component_values(values::Vector{<:Vector}, n) = vcat([get_component_value.(values,comp) for comp in 1:n]...) +expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) = vcat([get_component_value.(values,comp,location_types) for comp in 1:n]...) +# Creates a view of the pC vector at a given comaprtment. +function view_pC_vector(pC, comp, pC_location_types, pC_idxs) + mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) +end +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). +matrix_expand_component_values(values::Vector{<:Vector}, n) = reshape(expand_component_values(values, n), length(values), n) \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 8a929df5dc..b62f1902bb 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -329,47 +329,47 @@ let end # Create network with vaious combinations of graph/di-graph and parameters. -let - lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) - lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] - pV = SIR_p - pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] - pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] - pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; - 0.01 0.01 0.01 0.01 0.01 0.01] - pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] - pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] - pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] - oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) - oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) - oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) - sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] - sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] - sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] - sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] - sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] - sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] - sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] - sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] - sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] - - @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== - sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== - sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) -end +# let +# lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) +# lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) +# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] +# pV = SIR_p +# pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] +# pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] +# pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; +# 0.01 0.01 0.01 0.01 0.01 0.01] +# pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] +# pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] +# pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] +# oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) +# oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) +# oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) +# oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) +# oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) +# oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) +# oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) +# oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) +# oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) +# sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] +# sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] +# sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] +# sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] +# sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] +# sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] +# sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] +# sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] +# sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] +# +# @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== +# sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== +# sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) +# end # Creates networks with empty species or parameters. let binding_system_alt = @reaction_network begin @species X(t) Y(t) XY(t) Z(t) V(t) W(t) - @parameters k1 k2 dX dXY dZ dV p1 p2 + @parameters k1 k2 dX [diffusionparameter=true] dXY [diffusionparameter=true] dZ [diffusionparameter=true] dV [diffusionparameter=true] p1 p2 (k1, k2), X + Y <--> XY end binding_srs_alt = [ @@ -426,31 +426,31 @@ let end # Various ways to give parameters and initial conditions. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) - u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] - u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] - u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] - pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] - pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] - pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] - pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] - pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] - pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] - - p1 = [ - :α => 0.1 / 1000, - :β => [0.01, 0.02, 0.01, 0.03], - :dS => [0.01, 0.02, 0.03, 0.04], - :dI => 0.01, - :dR => 0.01, - ] - ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] - for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] - ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] - @test all(isequal.(ss, ss_1_1)) - end -end +# let +# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) +# u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] +# u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] +# u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] +# pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] +# pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] +# pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] +# pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] +# pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] +# pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] +# +# p1 = [ +# :α => 0.1 / 1000, +# :β => [0.01, 0.02, 0.01, 0.03], +# :dS => [0.01, 0.02, 0.03, 0.04], +# :dI => 0.01, +# :dR => 0.01, +# ] +# ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] +# for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] +# ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] +# @test all(isequal.(ss, ss_1_1)) +# end +# end # Checks that variosu combinations of jac and sparse gives the same result. let From c672cefd19dadf496794d8c05e7c2872a1bea00d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:40:22 -0400 Subject: [PATCH 038/134] Add spatial tests. --- src/Catalyst.jl | 2 +- src/lattice_reaction_system_diffusion.jl | 40 ++-- .../lattice_reaction_systems.jl | 214 +++++++++++------- 3 files changed, 152 insertions(+), 104 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index daea48f641..108930f368 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,7 +32,7 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, Graphs.SimpleDiGraphFromIterator, Graphs.nv, Graphs.ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index af146e0182..2fae51d367 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -11,7 +11,6 @@ function isdiffusionparameter(x, default=false) Symbolics.getmetadata(x, DiffusionParameter, default) end - # Abstract spatial reaction structures. abstract type AbstractSpatialReaction end @@ -63,8 +62,6 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p lattice::DiGraph # Derrived values. - """A list of parameters that occur in the spatial reactions.""" - spatial_param_syms::Vector{Symbol} """The number of compartments.""" nC::Int64 """The number of edges.""" @@ -74,25 +71,18 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a di graph.""" init_digraph::Bool - function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::DiGraph; - init_digraph = true) - return new(rs, spatial_reactions, lattice, - Symbol.(setdiff(filter(s -> Symbolics.issym(s), - vcat(Symbolics.get_variables.(getfield.(spatial_reactions, :rate))...)), parameters(rs))), - length(vertices(lattice)), length(edges(lattice)), length(species(rs)), - init_digraph) + function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::DiGraph; init_digraph = true) + return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph) end - function LatticeReactionSystem(rs, spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, DiGraph(lattice); - init_digraph = false) + function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); init_digraph = false) end - function LatticeReactionSystem(rs, spatial_reaction::AbstractSpatialReaction, - lattice::Graphs.AbstractGraph) + function LatticeReactionSystem(rs::ReactionSystem, spatial_reaction::AbstractSpatialReaction, lattice::Graphs.AbstractGraph) return LatticeReactionSystem(rs, [spatial_reaction], lattice) end end +# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). +graph_to_digraph(g) = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)),reverse.(edges(g)))), : , 1)[:]) # Gets the species of a lattice reaction system. species(lrs::LatticeReactionSystem) = species(lrs.rs) diffusion_species(lrs::LatticeReactionSystem) = filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), species(lrs.rs)) @@ -126,8 +116,8 @@ function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeRea pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) - lrs.init_digraph || foreach(pD_vals -> duplicate_diff_params!(pD_vals, lrs), pD) - check_vector_lengths(pC, lrs.nC); check_vector_lengths(pD, lrs.nE) + lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) + check_vector_lengths(pC, lrs.nC); check_vector_lengths(pD, lrs.nE) return pC,pD end @@ -149,14 +139,14 @@ function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, a end # Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). lattice_process_input(input::Matrix{<:Number}, args...) = lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) -lattice_process_input(input::Array{<:Number,3}, args...) = error("Have not yet written code for correctly mapping the position of reverse edge to correct location in the full edge vector.") -lattice_process_input(input::Vector{<:Any}, args...) = lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], args...) +lattice_process_input(input::Array{<:Number,3}, args...) = error("3 dimensional array parameter inpur currently not supported.") +lattice_process_input(input::Vector{<:Any}, args...) = isempty(input) ? Vector{Vector{Float64}}() : lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], args...) lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input check_vector_lengths(input::Vector{<:Vector}, n) = isempty(setdiff(unique(length.(input)),[1, n])) || error("Some inputs where given values of inappropriate length.") # For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. -function duplicate_diff_params!(pD_vals::Vector{Float64}, lrs::LatticeReactionSystem) - (2length(pD_vals) == lrs.nE) && error("Currently, even if an undirected graph is provided, you still have to provide parameter values for each edge separately.") +function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, lrs::LatticeReactionSystem) + (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) end # For a set of input values on the given forms, and their symbolics, convert into a dictionary. @@ -202,7 +192,7 @@ function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64 ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal.(diff_rates[1], states(lrs.rs))) => diff_rates[2] for diff_rates in diffusion_rates_speciesmap] + diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] for diff_rates in diffusion_rates_speciesmap] f = build_f(ofunc, pC, diffusion_rates, lrs) jac_prototype = (use_jac || sparse) ? @@ -353,7 +343,7 @@ end # Builds a spatial DiscreteProblem from a Lattice Reaction System. -# Creates an ODEProblem from a LatticeReactionSystem. +# Creates a DiscreteProblem from a LatticeReactionSystem. function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) pC,pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), Symbol.(diffusion_parameters(lrs)), lrs) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index b62f1902bb..201c7896f0 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,24 +1,33 @@ # Fetch packages. -using Catalyst, OrdinaryDiffEq, Random, Test -using Statistics, SparseArrays +using Catalyst, JumpProcesses, OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test using Graphs # Sets rnd number. using StableRNGs rng = StableRNG(12345) + ### Helper Functions ### + +# Generates ranomised intiial condition or paraemter values. rand_v_vals(grid) = rand(nv(grid)) rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x rand_e_vals(grid) = rand(ne(grid)) rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x - function make_u0_matrix(value_map, vals, symbols) (length(symbols) == 0) && (return zeros(0, length(vals))) d = Dict(value_map) return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] end +# Converts to integer value (for JumpProcess simulations). +make_values_int(values::Vector{<:Pair}) = [val[1] => round.(Int64,val[2]) for val in values] +make_values_int(values::Matrix{<:Number}) = round.(Int64,values) +make_values_int(values::Vector{<:Number}) = round.(Int64,values) +make_values_int(values::Vector{Vector}) = [round.(Int64,vals) for vals in values] + + ### Declares Models ### # Small non-stiff system. @@ -258,21 +267,22 @@ end ### Tests Simulation Correctness ### # Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) - u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] - pV = brusselator_p - pE = [:dX => 0.2] - oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) - oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - - for i in 1:nv(unconnected_graph) - @test all(isapprox.(sol_nonspatial.u[end], - sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) - end -end +# Temporarily removed until imrpvoed graph creation routine is created (fairly trivial). +# let +# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) +# u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] +# pV = brusselator_p +# pE = [:dX => 0.2] +# oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) +# oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) +# sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) +# sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) +# +# for i in 1:nv(unconnected_graph) +# @test all(isapprox.(sol_nonspatial.u[end], +# sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) +# end +# end # Checks that result becomes homogeneous on a connected lattice. let @@ -329,41 +339,41 @@ let end # Create network with vaious combinations of graph/di-graph and parameters. -# let -# lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) -# lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) -# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] -# pV = SIR_p -# pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] -# pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] -# pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; -# 0.01 0.01 0.01 0.01 0.01 0.01] -# pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] -# pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] -# pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] -# oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) -# oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) -# oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) -# oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) -# oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) -# oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) -# oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) -# oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) -# oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) -# sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] -# sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] -# sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] -# sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] -# sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] -# sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] -# sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] -# sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] -# sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] -# -# @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== -# sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== -# sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) -# end +let + lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) + lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] + pV = SIR_p + pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] + pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] + pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; + 0.01 0.01 0.01 0.01 0.01 0.01] + pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] + pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] + pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] + oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) + oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) + oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) + sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] + sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] + sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] + sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] + sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] + sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] + sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] + sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] + sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] + + @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== + sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== + sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) +end # Creates networks with empty species or parameters. let @@ -426,31 +436,31 @@ let end # Various ways to give parameters and initial conditions. -# let -# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) -# u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] -# u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] -# u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] -# pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] -# pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] -# pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] -# pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] -# pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] -# pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] -# -# p1 = [ -# :α => 0.1 / 1000, -# :β => [0.01, 0.02, 0.01, 0.03], -# :dS => [0.01, 0.02, 0.03, 0.04], -# :dI => 0.01, -# :dR => 0.01, -# ] -# ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] -# for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] -# ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] -# @test all(isequal.(ss, ss_1_1)) -# end -# end +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) + u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] + u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] + u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] + pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] + pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] + pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] + pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] + pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] + pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] + + p1 = [ + :α => 0.1 / 1000, + :β => [0.01, 0.02, 0.01, 0.03], + :dS => [0.01, 0.02, 0.03, 0.04], + :dI => 0.01, + :dR => 0.01, + ] + ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] + for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] + ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss, ss_1_1)) + end +end # Checks that variosu combinations of jac and sparse gives the same result. let @@ -605,6 +615,54 @@ let @test isapprox(J_hw_sparse, J_aut_sparse) end + +### Spatial Jump System Tests ### + +# Tests that there are no errors during runs. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0]) + u0_3 = make_values_int([ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_4 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs)))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(diffusion_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + @time solve(jprob, SSAStepper()) + end + end + end + end + end +end + + ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. From 3e50e7457907748b68681dc295d800729bdbb198 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:56:24 -0400 Subject: [PATCH 039/134] format --- src/Catalyst.jl | 3 +- src/lattice_reaction_system_diffusion.jl | 256 +++++++++++++++-------- 2 files changed, 175 insertions(+), 84 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 108930f368..cccce66c3a 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,7 +32,8 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, Graphs.SimpleDiGraphFromIterator, Graphs.nv, Graphs.ne +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, + Graphs.SimpleDiGraphFromIterator, Graphs.nv, Graphs.ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 2fae51d367..9cedc4aa3d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -5,7 +5,7 @@ struct DiffusionParameter end Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) -function isdiffusionparameter(x, default=false) +function isdiffusionparameter(x, default = false) p = Symbolics.getparent(x, nothing) p === nothing || (x = p) Symbolics.getmetadata(x, DiffusionParameter, default) @@ -71,37 +71,55 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a di graph.""" init_digraph::Bool - function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::DiGraph; init_digraph = true) - return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph) + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::DiGraph; init_digraph = true) + return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), + length(species(rs)), init_digraph) end - function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); init_digraph = false) + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); + init_digraph = false) end - function LatticeReactionSystem(rs::ReactionSystem, spatial_reaction::AbstractSpatialReaction, lattice::Graphs.AbstractGraph) + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reaction::AbstractSpatialReaction, + lattice::Graphs.AbstractGraph) return LatticeReactionSystem(rs, [spatial_reaction], lattice) end end # Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). -graph_to_digraph(g) = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)),reverse.(edges(g)))), : , 1)[:]) +function graph_to_digraph(g) + SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)), + reverse.(edges(g)))), :, 1)[:]) +end # Gets the species of a lattice reaction system. species(lrs::LatticeReactionSystem) = species(lrs.rs) -diffusion_species(lrs::LatticeReactionSystem) = filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), species(lrs.rs)) +function diffusion_species(lrs::LatticeReactionSystem) + filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), + species(lrs.rs)) +end # Gets the parameters in a lattice reaction system. function ModelingToolkit.parameters(lrs::LatticeReactionSystem) unique(vcat(parameters(lrs.rs), Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) end -compartment_parameters(lrs::LatticeReactionSystem) = filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) -diffusion_parameters(lrs::LatticeReactionSystem) = filter(p -> is_spatial_param(p, lrs), parameters(lrs)) +function compartment_parameters(lrs::LatticeReactionSystem) + filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) +end +function diffusion_parameters(lrs::LatticeReactionSystem) + filter(p -> is_spatial_param(p, lrs), parameters(lrs)) +end # Checks whenever a parameter is a spatial parameter or not. function is_spatial_param(p, lrs) - hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. - return (any(isequal.(p,parameters(lrs.rs))) ? false : true) + hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && + (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. + return (any(isequal.(p, parameters(lrs.rs))) ? false : true) end - ### Processes Input u0 & p ### # From u0 input, extracts their values and store them in the internal format. @@ -116,84 +134,118 @@ function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeRea pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) - lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) - check_vector_lengths(pC, lrs.nC); check_vector_lengths(pD, lrs.nE) - return pC,pD + lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) + check_vector_lengths(pC, lrs.nC) + check_vector_lengths(pD, lrs.nE) + return pC, pD end # Splits parameters into those for the compartments and those for the connections. split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps -split_parameters(ps::Vector{<:Number}, args...) = error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") -function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, p_diff_symbols::Vector) +function split_parameters(ps::Vector{<:Number}, args...) + error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") +end +function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, + p_diff_symbols::Vector) pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] - (sum(length.([pC_in, pD_in])) != length(ps)) && error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") - return pC_in,pD_in + (sum(length.([pC_in, pD_in])) != length(ps)) && + error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") + return pC_in, pD_in end # If the input is given in a map form, teh vector needs sorting and the first value removed. function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) - (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") - sorted_input = sort(input; by=p->findfirst(ModelingToolkit.getname(p[1]) .== symbols)) + (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && + error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") + sorted_input = sort(input; + by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) return lattice_process_input(last.(sorted_input), symbols, args...) end # Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). -lattice_process_input(input::Matrix{<:Number}, args...) = lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) -lattice_process_input(input::Array{<:Number,3}, args...) = error("3 dimensional array parameter inpur currently not supported.") -lattice_process_input(input::Vector{<:Any}, args...) = isempty(input) ? Vector{Vector{Float64}}() : lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], args...) +function lattice_process_input(input::Matrix{<:Number}, args...) + lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) +end +function lattice_process_input(input::Array{<:Number, 3}, args...) + error("3 dimensional array parameter inpur currently not supported.") +end +function lattice_process_input(input::Vector{<:Any}, args...) + isempty(input) ? Vector{Vector{Float64}}() : + lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], + args...) +end lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input -check_vector_lengths(input::Vector{<:Vector}, n) = isempty(setdiff(unique(length.(input)),[1, n])) || error("Some inputs where given values of inappropriate length.") +function check_vector_lengths(input::Vector{<:Vector}, n) + isempty(setdiff(unique(length.(input)), [1, n])) || + error("Some inputs where given values of inappropriate length.") +end # For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. -function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, lrs::LatticeReactionSystem) +function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, + lrs::LatticeReactionSystem) (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) end # For a set of input values on the given forms, and their symbolics, convert into a dictionary. vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) # Produces a dictionary with all parameter values. -param_dict(pC, pD, lrs) = merge(vals_to_dict(compartment_parameters(lrs), pC), vals_to_dict(diffusion_parameters(lrs), pD)) +function param_dict(pC, pD, lrs) + merge(vals_to_dict(compartment_parameters(lrs), pC), + vals_to_dict(diffusion_parameters(lrs), pD)) +end # Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, pD::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) +function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, + lrs::LatticeReactionSystem) param_value_dict = param_dict(pC, pD, lrs) - return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in diffusion_species(lrs)] + return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), + param_value_dict, lrs.nE)) + for s in diffusion_species(lrs)] end function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(ModelingToolkit.getname(s),sr.species_sym), lrs.spatial_reactions) + rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), + lrs.spatial_reactions) (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. return rates[1].rate end -function compute_diffusion_rates(rate_law::Num, param_value_dict::Dict{Any,Vector{Float64}}, nE::Int64) +function compute_diffusion_rates(rate_law::Num, + param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) relevant_parameters = Symbolics.get_variables(rate_law) - if all(length(param_value_dict[P])==1 for P in relevant_parameters) - return [substitute(rate_law, Dict(p => param_value_dict[p][1] for p in relevant_parameters))] + if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) + return [ + substitute(rate_law, + Dict(p => param_value_dict[p][1] for p in relevant_parameters)), + ] end - return [substitute(rate_law, Dict(p => get_component_value(param_value_dict[p], idxE) for p in relevant_parameters)) for idxE in 1:nE] + return [substitute(rate_law, + Dict(p => get_component_value(param_value_dict[p], idxE) + for p in relevant_parameters)) for idxE in 1:nE] end - ### ODEProblem ### # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; jac = true, sparse = jac, kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC,pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), Symbol.(diffusion_parameters(lrs)), lrs) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) ofun = build_odefunction(lrs, pC, pD, jac, sparse) return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) +function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] for diff_rates in diffusion_rates_speciesmap] - + diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] + for diff_rates in diffusion_rates_speciesmap] + f = build_f(ofunc, pC, diffusion_rates, lrs) jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, @@ -204,8 +256,8 @@ end # Builds the forcing (f) function for a reaction system on a lattice. function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - diffusion_rates::Vector{Pair{Int64,Vector{Float64}}}, lrs::LatticeReactionSystem) - + diffusion_rates::Vector{Pair{Int64, Vector{Float64}}}, + lrs::LatticeReactionSystem) leaving_rates = zeros(length(diffusion_rates), lrs.nC) for (s_idx, rates) in enumerate(last.(diffusion_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) @@ -216,7 +268,6 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, pC_idxs = 1:length(pC) enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - return function (du, u, p, t) # Updates for non-spatial reactions. for comp_i::Int64 in 1:(lrs.nC) @@ -226,16 +277,16 @@ function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, end # Updates for spatial diffusion reactions. - for (s_idx, (s,rates)) in enumerate(diffusion_rates) + for (s_idx, (s, rates)) in enumerate(diffusion_rates) for comp_i::Int64 in 1:(lrs.nC) du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, - lrs.nS)] + u[get_index(comp_i, s, + lrs.nS)] end for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, - lrs.nS)] + u[get_index(edge.src, s, + lrs.nS)] end end end @@ -249,7 +300,8 @@ function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, pC_location_types = length.(pC) .== 1 pC_idxs = 1:length(pC) reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) - add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : J -> (J .+= Matrix(jac_prototype))) + add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : + J -> (J .+= Matrix(jac_prototype))) return function (J, u, p, t) # Because of weird stuff where the Jacobian is not reset that I don't understand properly. @@ -270,17 +322,18 @@ end # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - diffusion_rates, lrs::LatticeReactionSystem; + diffusion_rates, lrs::LatticeReactionSystem; set_nonzero = false) - diff_species = first.(diffusion_rates) + diff_species = first.(diffusion_rates) # Gets list of indexes for species that diffuse, but are invovled in no other reaction. - only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] + only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) + for s in 1:(lrs.nS)] # Declares sparse array content. J_colptr = fill(1, lrs.nC * lrs.nS + 1) J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_rates)) + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_rates)) J_rowval = fill(0, length(J_nzval)) # Finds filled elements. @@ -316,7 +369,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, if !set_nonzero J_nzval .= 1.0 else - for (s_idx, (s,rates)) in enumerate(diffusion_rates), + for (s_idx, (s, rates)) in enumerate(diffusion_rates), (e_idx, edge) in enumerate(edges(lrs.lattice)) col_start = J_colptr[get_index(edge.src, s, lrs.nS)] @@ -338,38 +391,50 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end - ### JumpProblem ### # Builds a spatial DiscreteProblem from a Lattice Reaction System. # Creates a DiscreteProblem from a LatticeReactionSystem. -function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; kwargs...) u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC,pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), Symbol.(diffusion_parameters(lrs)), lrs) - return DiscreteProblem(lrs.rs, u0, tspan, (pC,pD), args...; kwargs...) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), checks = false, kwargs...) - dprob.p isa Tuple{Vector{Vector{Float64}},Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; + name = nameof(lrs.rs), + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), + checks = false, kwargs...) + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || + error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") hopping_constants = make_hopping_constants(dprob, lrs) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) - ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) - return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, + checks) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + spatial_system = lrs.lattice, save_positions = (true, false)) end # Creates the hopping constants from a discrete problem and a lattice reaction system. function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) - diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], lrs)) - all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] for s in species(lrs)] + diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], + lrs)) + all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] + for s in species(lrs)] if length.(all_diff_rates) == 1 - return Catalyst.matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) + return Catalyst.matrix_expand_component_values(all_diff_rates, + length(vertices(lrs.lattice))) else - hopping_constants = [Vector{Float64}() for i in 1:lrs.nS, j in 1:lrs.nC] + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:lrs.nS - push!(hopping_constants[s_idx, e.src], Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) end end return hopping_constants @@ -377,11 +442,19 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, combinatoric_ratelaws, checks) - any(length.(dprob.p[1]) .> 1) && error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") - ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) - ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; hopping_constants=hopping_constants, spatial_system = lrs.lattice, combinatoric_ratelaws=combinatoric_ratelaws, checks=checks) - (length(___jprob.variable_jumps) != 0) && error("Currently, for lattice jump simulations, variable rate jumps are not supported.") +function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) + any(length.(dprob.p[1]) .> 1) && + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + (length(___jprob.variable_jumps) != 0) && + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") return ___jprob.massaction_jump end @@ -393,16 +466,33 @@ get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) # We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. -get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64) = get_component_value(values[component_idx], location_idx) -get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64, location_types::Vector{Bool}) = get_component_value(values[component_idx], location_idx, location_types[component_idx]) -get_component_value(values::Vector{<:Number}, location_idx::Int64) = get_component_value(values, location_idx, length(values)==1) -get_component_value(values::Vector{<:Number}, location_idx::Int64, location_type::Bool) = location_type ? values[1] : values[location_idx] +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64) + get_component_value(values[component_idx], location_idx) +end +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64, location_types::Vector{Bool}) + get_component_value(values[component_idx], location_idx, location_types[component_idx]) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64) + get_component_value(values, location_idx, length(values) == 1) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64, + location_type::Bool) + location_type ? values[1] : values[location_idx] +end # Converts a vector of vectors to a long vector. -expand_component_values(values::Vector{<:Vector}, n) = vcat([get_component_value.(values,comp) for comp in 1:n]...) -expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) = vcat([get_component_value.(values,comp,location_types) for comp in 1:n]...) +function expand_component_values(values::Vector{<:Vector}, n) + vcat([get_component_value.(values, comp) for comp in 1:n]...) +end +function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) + vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) +end # Creates a view of the pC vector at a given comaprtment. function view_pC_vector(pC, comp, pC_location_types, pC_idxs) mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). -matrix_expand_component_values(values::Vector{<:Vector}, n) = reshape(expand_component_values(values, n), length(values), n) \ No newline at end of file +function matrix_expand_component_values(values::Vector{<:Vector}, n) + reshape(expand_component_values(values, n), length(values), n) +end From 74f4042a8e005079fa0125e6f97e748956437018 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:56:32 -0400 Subject: [PATCH 040/134] format --- .../lattice_reaction_systems.jl | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 201c7896f0..9b25a072b0 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -7,7 +7,6 @@ using Graphs using StableRNGs rng = StableRNG(12345) - ### Helper Functions ### # Generates ranomised intiial condition or paraemter values. @@ -22,11 +21,12 @@ function make_u0_matrix(value_map, vals, symbols) end # Converts to integer value (for JumpProcess simulations). -make_values_int(values::Vector{<:Pair}) = [val[1] => round.(Int64,val[2]) for val in values] -make_values_int(values::Matrix{<:Number}) = round.(Int64,values) -make_values_int(values::Vector{<:Number}) = round.(Int64,values) -make_values_int(values::Vector{Vector}) = [round.(Int64,vals) for vals in values] - +function make_values_int(values::Vector{<:Pair}) + [val[1] => round.(Int64, val[2]) for val in values] +end +make_values_int(values::Matrix{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] ### Declares Models ### @@ -379,7 +379,9 @@ end let binding_system_alt = @reaction_network begin @species X(t) Y(t) XY(t) Z(t) V(t) W(t) - @parameters k1 k2 dX [diffusionparameter=true] dXY [diffusionparameter=true] dZ [diffusionparameter=true] dV [diffusionparameter=true] p1 p2 + @parameters k1 k2 dX [diffusionparameter = true] dXY [diffusionparameter = true] dZ [ + diffusionparameter = true, + ] dV [diffusionparameter = true] p1 p2 (k1, k2), X + Y <--> XY end binding_srs_alt = [ @@ -615,7 +617,6 @@ let @test isapprox(J_hw_sparse, J_aut_sparse) end - ### Spatial Jump System Tests ### # Tests that there are no errors during runs. @@ -624,19 +625,23 @@ let for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) - u0_2 = make_values_int([:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 1.0, + :R => 0.0, + ]) u0_3 = make_values_int([ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) u0_4 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs)))) + map(s -> Symbol(s.f), species(lrs.rs)))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:α => 0.1 / 1000, :β => 0.01] p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] @@ -646,11 +651,14 @@ let ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_1 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(diffusion_parameters(lrs))) + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), + ModelingToolkit.getname.(diffusion_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) jprob = JumpProblem(lrs, dprob, NSM()) @@ -662,7 +670,6 @@ let end end - ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. From dbded6591e5e965d90f8775034286c8528ab5d27 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 18:18:02 -0400 Subject: [PATCH 041/134] test fix --- .../lattice_reaction_systems.jl | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 9b25a072b0..85f7aac093 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -20,6 +20,11 @@ function make_u0_matrix(value_map, vals, symbols) return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] end +# Gets a symbol list of spatial parameters. +function spatial_param_syms(lrs::LatticReactionSystem) + ModelingToolkit.getname.(diffusion_species(lrs)) +end + # Converts to integer value (for JumpProcess simulations). function make_values_int(values::Vector{<:Pair}) [val[1] => round.(Int64, val[2]) for val in values] @@ -214,11 +219,12 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.01, lrs.spatial_param_syms) - pE_2 = map(sp -> sp => 0.01, lrs.spatial_param_syms) + println() + pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - lrs.spatial_param_syms) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) + spatial_param_syms(lrs)) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) @@ -247,11 +253,11 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] ] p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.2, lrs.spatial_param_syms) - pE_2 = map(sp -> sp => rand(), lrs.spatial_param_syms) + pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => rand(), spatial_param_syms(lrs)) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), - lrs.spatial_param_syms) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), lrs.spatial_param_syms) + spatial_param_syms(lrs)) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) for pE in [pE_1, pE_2, pE_3, pE_4] oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) @test SciMLBase.successful_retcode(solve(oprob, QNDF())) From 74cf628a4a19f91a2f19e112bad157a2ece5a84b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:18:19 -0400 Subject: [PATCH 042/134] Remove Jump Stuff --- src/lattice_reaction_system_diffusion.jl | 66 ------------------- .../lattice_reaction_systems.jl | 54 +-------------- 2 files changed, 1 insertion(+), 119 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 9cedc4aa3d..7da193d263 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -391,72 +391,6 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end -### JumpProblem ### - -# Builds a spatial DiscreteProblem from a Lattice Reaction System. - -# Creates a DiscreteProblem from a LatticeReactionSystem. -function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), - checks = false, kwargs...) - dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || - error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") - hopping_constants = make_hopping_constants(dprob, lrs) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, - checks) - ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, - spatial_system = lrs.lattice, save_positions = (true, false)) -end - -# Creates the hopping constants from a discrete problem and a lattice reaction system. -function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) - diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], - lrs)) - all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] - for s in species(lrs)] - if length.(all_diff_rates) == 1 - return Catalyst.matrix_expand_component_values(all_diff_rates, - length(vertices(lrs.lattice))) - else - hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] - for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:(lrs.nS) - push!(hopping_constants[s_idx, e.src], - Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) - end - end - return hopping_constants - end -end - -# Creates the mass action jumps from a discrete problem and a lattice reaction system. -function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) - any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") - ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) - (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump -end ### Accessing State & Parameter Array Values ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 85f7aac093..6d921f00d2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -21,7 +21,7 @@ function make_u0_matrix(value_map, vals, symbols) end # Gets a symbol list of spatial parameters. -function spatial_param_syms(lrs::LatticReactionSystem) +function spatial_param_syms(lrs::LatticeReactionSystem) ModelingToolkit.getname.(diffusion_species(lrs)) end @@ -623,58 +623,6 @@ let @test isapprox(J_hw_sparse, J_aut_sparse) end -### Spatial Jump System Tests ### - -# Tests that there are no errors during runs. -let - for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] - lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) - u0_2 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 1.0, - :R => 0.0, - ]) - u0_3 = make_values_int([ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_4 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs)))) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] - p3 = [ - :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), - :β => 0.02 * rand_v_vals(lrs.lattice), - ] - p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) - for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - for pE in [pE_1, pE_2, pE_3, pE_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - jprob = JumpProblem(lrs, dprob, NSM()) - @time solve(jprob, SSAStepper()) - end - end - end - end - end -end ### Runtime Checks ### # Current timings are taken from the SciML CI server. From 3bfb2709f208cc9893eee87c15d24dc8491766d8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:25:30 -0400 Subject: [PATCH 043/134] fix --- src/lattice_reaction_system_diffusion.jl | 6 ++-- .../lattice_reaction_systems.jl | 31 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 7da193d263..04db9f0a40 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -90,9 +90,11 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p end end # Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). -function graph_to_digraph(g) - SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)), +function graph_to_digraph(g1) + g2 = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)), reverse.(edges(g)))), :, 1)[:]) + add_vertices!(g2, nv(g1) - nv(g2)) + return g2 end # Gets the species of a lattice reaction system. species(lrs::LatticeReactionSystem) = species(lrs.rs) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 6d921f00d2..f6d05c2bd4 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -273,22 +273,21 @@ end ### Tests Simulation Correctness ### # Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. -# Temporarily removed until imrpvoed graph creation routine is created (fairly trivial). -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) -# u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) -# oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) -# sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) -# -# for i in 1:nv(unconnected_graph) -# @test all(isapprox.(sol_nonspatial.u[end], -# sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) -# end -# end +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) + u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] + pV = brusselator_p + pE = [:dX => 0.2] + oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) + oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + + for i in 1:nv(unconnected_graph) + @test all(isapprox.(sol_nonspatial.u[end], + sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) + end +end # Checks that result becomes homogeneous on a connected lattice. let From a502fee1cd9ed4012864107454cc1b080f8928bf Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:57:40 -0400 Subject: [PATCH 044/134] fix --- src/lattice_reaction_system_diffusion.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 04db9f0a40..5dd41e8df9 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -91,8 +91,8 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p end # Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). function graph_to_digraph(g1) - g2 = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g)), - reverse.(edges(g)))), :, 1)[:]) + g2 = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), + reverse.(edges(g1)))), :, 1)[:]) add_vertices!(g2, nv(g1) - nv(g2)) return g2 end From a8b55807862341859e722532849a62413637ef9f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 16:25:58 -0400 Subject: [PATCH 045/134] test update --- src/Catalyst.jl | 3 +-- src/lattice_reaction_system_diffusion.jl | 4 ++-- test/spatial_reaction_systems/lattice_reaction_systems.jl | 3 +-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index cccce66c3a..6f451aaa0c 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,8 +32,7 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, - Graphs.SimpleDiGraphFromIterator, Graphs.nv, Graphs.ne +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, Graphs.add_vertices!, Graphs.nv, Graphs.ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 5dd41e8df9..cac39ffd26 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -91,7 +91,7 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p end # Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). function graph_to_digraph(g1) - g2 = SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), + g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), reverse.(edges(g1)))), :, 1)[:]) add_vertices!(g2, nv(g1) - nv(g2)) return g2 @@ -258,7 +258,7 @@ end # Builds the forcing (f) function for a reaction system on a lattice. function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - diffusion_rates::Vector{Pair{Int64, Vector{Float64}}}, + diffusion_rates::Vector, lrs::LatticeReactionSystem) leaving_rates = zeros(length(diffusion_rates), lrs.nC) for (s_idx, rates) in enumerate(last.(diffusion_rates)), diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index f6d05c2bd4..a34b5eeeb2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -22,7 +22,7 @@ end # Gets a symbol list of spatial parameters. function spatial_param_syms(lrs::LatticeReactionSystem) - ModelingToolkit.getname.(diffusion_species(lrs)) + ModelingToolkit.getname.(diffusion_parameters(lrs)) end # Converts to integer value (for JumpProcess simulations). @@ -219,7 +219,6 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1, p2, p3, p4] - println() pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), From 83808fb8db817d777c5c747521a55fabf8b1ffd2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 07:26:32 -0400 Subject: [PATCH 046/134] Spatial Jump Implementation --- src/Catalyst.jl | 3 +- src/compound.jl | 16 +-- src/lattice_reaction_system_diffusion.jl | 69 +++++++++++- .../lattice_reaction_systems.jl | 100 ++++++++++++++++++ 4 files changed, 178 insertions(+), 10 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index a229f694a8..72cb17c229 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,7 +32,8 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, Graphs.add_vertices!, Graphs.nv, Graphs.ne +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, + Graphs.add_vertices!, Graphs.nv, Graphs.ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow diff --git a/src/compound.jl b/src/compound.jl index 9f2ac5bca2..01c9081232 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index cac39ffd26..15c1ec9bbd 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -92,7 +92,8 @@ end # Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). function graph_to_digraph(g1) g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), - reverse.(edges(g1)))), :, 1)[:]) + reverse.(edges(g1)))), :, + 1)[:]) add_vertices!(g2, nv(g1) - nv(g2)) return g2 end @@ -393,6 +394,72 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates a DiscreteProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), + checks = false, kwargs...) + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || + error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + hopping_constants = make_hopping_constants(dprob, lrs) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, + checks) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + spatial_system = lrs.lattice, save_positions = (true, false)) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], + lrs)) + all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] + for s in species(lrs)] + if length.(all_diff_rates) == 1 + return Catalyst.matrix_expand_component_values(all_diff_rates, + length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants + end +end + +# Creates the mass action jumps from a discrete problem and a lattice reaction system. +function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) + any(length.(dprob.p[1]) .> 1) && + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + (length(___jprob.variable_jumps) != 0) && + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump +end ### Accessing State & Parameter Array Values ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index a34b5eeeb2..89e39c1178 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -621,6 +621,106 @@ let @test isapprox(J_hw_sparse, J_aut_sparse) end +### Spatial Jump System Tests ### + +# Tests that there are no errors during runs. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 1.0, + :R => 0.0, + ]) + u0_3 = make_values_int([ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_4 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs)))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + @time solve(jprob, SSAStepper()) + end + end + end + end + end +end + +# Tests that the correct hopping rates and initial conditions are generated. +# Hoppping rates should be on the form D_{s,i,j}. +let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + + # Prepares various u0 input types. + u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] + u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] + u0_3 = [1.0, 2.0, 3.0] + u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] + u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + + # Prepare various (comaprtment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + + # Checks hopping rates and u0 correct. + true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + # Provides parameters as a tupple. + for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pC_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), p) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end ### Runtime Checks ### # Current timings are taken from the SciML CI server. From cddc9fb2f4854f06182d377c25d5b5b39295cde1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 08:52:27 -0400 Subject: [PATCH 047/134] test update --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 89e39c1178..ccc18e323f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -666,7 +666,7 @@ let for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) jprob = JumpProblem(lrs, dprob, NSM()) - @time solve(jprob, SSAStepper()) + solve(jprob, SSAStepper()) end end end @@ -707,7 +707,7 @@ let for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pC_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), p) + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates From 3bb908ed63d86fdcb54c35382447c41ce11f028d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 09:44:29 -0400 Subject: [PATCH 048/134] test update --- .../lattice_reaction_systems.jl | 200 +++++++++++++++++- 1 file changed, 196 insertions(+), 4 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index ccc18e323f..bb31c00330 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -675,7 +675,7 @@ let end # Tests that the correct hopping rates and initial conditions are generated. -# Hoppping rates should be on the form D_{s,i,j}. +# In this base case, hoppping rates should be on the form D_{s,i,j}. let # Prepares the system. lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) @@ -687,7 +687,7 @@ let u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) - # Prepare various (comaprtment) parameter input types. + # Prepare various (compartment) parameter input types. pC_1 = [:β => 0.2, :α => 0.1] pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] pC_3 = [0.1, 0.2] @@ -701,12 +701,12 @@ let pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) - # Checks hopping rates and u0 correct. + # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. - for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pC_3, pD_4, pD_5] + for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @@ -722,6 +722,198 @@ let end end +# Currently not in use, but will be added as more cases are enabled. +if false + # Tests hopping rates of the form D_{s,i,j} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_3] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i} + let + # Prepares special system and diffusion reactions (depending on compartments). + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pD = [] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s}*L_{i,j} + # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). + let + SIR_system_special = @reaction_network begin + @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack dS, dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1, :dI => 0.01] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, ] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0, :dS => 0.005] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] + pD_3 = [0.005, 2.0] + pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i}*L_{i,j} + let + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI connection_d = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] + pD_3 = [2.0] + pD_4 = [fill(0.2, ne(small_2d_grid))] + pD_5 = [fill(0.2, 1, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end + ### Runtime Checks ### # Current timings are taken from the SciML CI server. # Current not used, simply here for reference. From a5f58bfc132d3a2ac38ef9da15ec74fe2ba269b3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 10:03:07 -0400 Subject: [PATCH 049/134] Spatial tests file reordering --- test/runtests.jl | 3 +- .../lattice_reaction_systems.jl | 1113 ----------------- .../lattice_reaction_systems_ODEs.jl | 443 +++++++ ...ttice_reaction_systems_ODEs_performance.jl | 212 ++++ .../lattice_reaction_systems_jumps.jl | 309 +++++ test/spatial_test_networks.jl | 186 +++ 6 files changed, 1152 insertions(+), 1114 deletions(-) delete mode 100644 test/spatial_reaction_systems/lattice_reaction_systems.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl create mode 100644 test/spatial_test_networks.jl diff --git a/test/runtests.jl b/test/runtests.jl index 5c4e7fce7a..413b4df4ff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,8 @@ using SafeTestsets ### Tests Spatial Network Simulations. ### @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end - @time @safetestset "Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl deleted file mode 100644 index bb31c00330..0000000000 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ /dev/null @@ -1,1113 +0,0 @@ -# Fetch packages. -using Catalyst, JumpProcesses, OrdinaryDiffEq -using Random, Statistics, SparseArrays, Test -using Graphs - -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - -### Helper Functions ### - -# Generates ranomised intiial condition or paraemter values. -rand_v_vals(grid) = rand(nv(grid)) -rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_e_vals(grid) = rand(ne(grid)) -rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x -function make_u0_matrix(value_map, vals, symbols) - (length(symbols) == 0) && (return zeros(0, length(vals))) - d = Dict(value_map) - return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] -end - -# Gets a symbol list of spatial parameters. -function spatial_param_syms(lrs::LatticeReactionSystem) - ModelingToolkit.getname.(diffusion_parameters(lrs)) -end - -# Converts to integer value (for JumpProcess simulations). -function make_values_int(values::Vector{<:Pair}) - [val[1] => round.(Int64, val[2]) for val in values] -end -make_values_int(values::Matrix{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] - -### Declares Models ### - -# Small non-stiff system. -SIR_system = @reaction_network begin - α, S + I --> 2I - β, I --> R -end -SIR_p = [:α => 0.1 / 1000, :β => 0.01] -SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] - -SIR_dif_S = DiffusionReaction(:dS, :S) -SIR_dif_I = DiffusionReaction(:dI, :I) -SIR_dif_R = DiffusionReaction(:dR, :R) -SIR_srs_1 = [SIR_dif_S] -SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] - -# Small non-stiff system. -binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) -binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] -binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] - -# Mid-sized non-stiff system. -CuH_Amination_system = @reaction_network begin - 10.0^kp1, CuoAc + Ligand --> CuoAcLigand - 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc - 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand - 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene - 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand - 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E - 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane - 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand - 10.0^kdc, CuHLigand + CuHLigand --> Decomposition -end -CuH_Amination_p = [ - :kp1 => 1.2, - :kp2 => -0.72, - :k1 => 0.57, - :k_1 => -3.5, - :k2 => -0.35, - :k_2 => -0.77, - :k3 => -0.025, - :kam => -2.6, - :kdc => -3.0, -] -CuH_Amination_u0 = [ - :CuoAc => 0.0065, - :Ligand => 0.0072, - :CuoAcLigand => 0.0, - :Silane => 0.65, - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, -] - -CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) -CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) -CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) -CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) -CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) -CuH_Amination_srs_1 = [CuH_Amination_diff_1] -CuH_Amination_srs_2 = [ - CuH_Amination_diff_1, - CuH_Amination_diff_2, - CuH_Amination_diff_3, - CuH_Amination_diff_4, - CuH_Amination_diff_5, -] - -# Small stiff system. -brusselator_system = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ -end -brusselator_p = [:A => 1.0, :B => 4.0] - -brusselator_dif_x = DiffusionReaction(:dX, :X) -brusselator_dif_y = DiffusionReaction(:dY, :Y) -brusselator_srs_1 = [brusselator_dif_x] -brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] - -# Mid-sized stiff system. -# Unsure about stifness, but non-spatial version oscillates for this parameter set. -sigmaB_system = @reaction_network begin - kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ - kDeg, vPp ⟶ phos - (kBw, kDw), 2w ⟷ w2 - (kB1, kD1), w2 + v ⟷ w2v - (kB2, kD2), w2v + v ⟷ w2v2 - kK1, w2v ⟶ w2 + vP - kK2, w2v2 ⟶ w2v + vP - (kB3, kD3), w2 + σB ⟷ w2σB - (kB4, kD4), w2σB + v ⟷ w2v + σB - (kB5, kD5), vP + phos ⟷ vPp - kP, vPp ⟶ v + phos - v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ σB - λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w - λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v -end -sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, - :kB4 => 1800, :kB5 => 3600, - :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, - :kP => 180, :kDeg => 0.7, - :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] -sigmaB_u0 = [ - :w => 1.0, - :w2 => 1.0, - :w2v => 1.0, - :v => 1.0, - :w2v2 => 1.0, - :vP => 1.0, - :σB => 1.0, - :w2σB => 1.0, - :vPp => 0.0, - :phos => 0.4, -] - -sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) -sigmaB_dif_w = DiffusionReaction(:Dw, :w) -sigmaB_dif_v = DiffusionReaction(:Dv, :v) -sigmaB_srs_1 = [sigmaB_dif_σB] -sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] - -### Declares Lattices ### - -# Grids. -very_small_2d_grid = Graphs.grid([2, 2]) -small_2d_grid = Graphs.grid([5, 5]) -medium_2d_grid = Graphs.grid([20, 20]) -large_2d_grid = Graphs.grid([100, 100]) - -small_3d_grid = Graphs.grid([5, 5, 5]) -medium_3d_grid = Graphs.grid([20, 20, 20]) -large_3d_grid = Graphs.grid([100, 100, 100]) - -# Paths. -short_path = path_graph(100) -long_path = path_graph(1000) - -# Unconnected graphs. -unconnected_graph = SimpleGraph(10) - -# Undirected cycle. -undirected_cycle = cycle_graph(49) - -# Directed cycle. -small_directed_cycle = cycle_graph(100) -large_directed_cycle = cycle_graph(1000) - -### Test No Error During Runs ### -for grid in [small_2d_grid, short_path, small_directed_cycle] - # Non-stiff case - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] - lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] - u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] - u0_3 = [ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ] - u0_4 = [ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ] - u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs))) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] - p3 = [ - :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), - :β => 0.02 * rand_v_vals(lrs.lattice), - ] - p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) - for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - spatial_param_syms(lrs)) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - end - end - end - end - - # Stiff case - for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] - lrs = LatticeReactionSystem(brusselator_system, srs, grid) - u0_1 = [:X => 1.0, :Y => 20.0] - u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] - u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] - u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs))) - for u0 in [u0_1, u0_2, u0_3, u0_4] - p1 = [:A => 1.0, :B => 4.0] - p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] - p3 = [ - :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), - ] - p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) - for pV in [p1, p2, p3, p4] - pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) - pE_2 = map(sp -> sp => rand(), spatial_param_syms(lrs)) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), - spatial_param_syms(lrs)) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) - for pE in [pE_1, pE_2, pE_3, pE_4] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); sparse = false) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - end - end - end - end -end - -### Tests Simulation Correctness ### - -# Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) - u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] - pV = brusselator_p - pE = [:dX => 0.2] - oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) - oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) - - for i in 1:nv(unconnected_graph) - @test all(isapprox.(sol_nonspatial.u[end], - sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) - end -end - -# Checks that result becomes homogeneous on a connected lattice. -let - lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) - u0 = [ - :X => 1.0 .+ rand_v_vals(lrs.lattice), - :Y => 2.0 * rand_v_vals(lrs.lattice), - :XY => 0.5, - ] - oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) - ss = solve(oprob, Tsit5()).u[end] - - @test all(isapprox.(ss[1:3:end], ss[1])) - @test all(isapprox.(ss[2:3:end], ss[2])) - @test all(isapprox.(ss[3:3:end], ss[3])) -end - -### Tests Special Cases ### - -# Creates network with various combiantions of Symbls and Nums in diffusion reactions. -let - @parameters dS dI dR - @variables t - @species S(t) I(t) R(t) - SIR_srs_numsym_1 = diffusion_reactions([(:dS, :S), (:dI, :I), (:dR, :R)]) - SIR_srs_numsym_2 = diffusion_reactions([(dS, :S), (dI, :I), (dR, :R)]) - SIR_srs_numsym_3 = diffusion_reactions([(:dS, S), (:dI, I), (:dR, R)]) - SIR_srs_numsym_4 = diffusion_reactions([(dS, S), (dI, I), (dR, R)]) - SIR_srs_numsym_5 = diffusion_reactions([(dS, :S), (:dI, I), (dR, :R)]) - SIR_srs_numsym_6 = diffusion_reactions([(:dS, :S), (:dI, I), (dR, R)]) - - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(small_2d_grid), :R => 0.0] - pV = SIR_p - pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - pE_2 = [dS => 0.01, dI => 0.01, dR => 0.01] - pE_3 = [dS => 0.01, :dI => 0.01, :dR => 0.01] - ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = false), Tsit5()).u[end] - ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = true), Rosenbrock23()).u[end] - - for srs in [ - SIR_srs_numsym_1, - SIR_srs_numsym_2, - SIR_srs_numsym_3, - SIR_srs_numsym_4, - SIR_srs_numsym_5, - SIR_srs_numsym_6, - ], pE in [pE_1, pE_2, pE_3] - lrs = LatticeReactionSystem(SIR_system, srs, small_2d_grid) - ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false), Tsit5()).u[end] - ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = true), Rosenbrock23()).u[end] - @test all(isapprox.(ss_explicit, ss_explicit_base)) - @test all(isapprox.(ss_implicit, ss_implicit_base)) - end -end - -# Create network with vaious combinations of graph/di-graph and parameters. -let - lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) - lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] - pV = SIR_p - pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] - pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] - pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; - 0.01 0.01 0.01 0.01 0.01 0.01] - pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] - pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] - pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] - oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) - oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) - oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) - oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) - oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) - oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) - sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] - sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] - sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] - sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] - sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] - sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] - sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] - sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] - sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] - - @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== - sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== - sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) -end - -# Creates networks with empty species or parameters. -let - binding_system_alt = @reaction_network begin - @species X(t) Y(t) XY(t) Z(t) V(t) W(t) - @parameters k1 k2 dX [diffusionparameter = true] dXY [diffusionparameter = true] dZ [ - diffusionparameter = true, - ] dV [diffusionparameter = true] p1 p2 - (k1, k2), X + Y <--> XY - end - binding_srs_alt = [ - DiffusionReaction(:dX, :X), - DiffusionReaction(:dXY, :XY), - DiffusionReaction(:dZ, :Z), - DiffusionReaction(:dV, :V), - ] - lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_grid) - u0_alt = [ - :X => 1.0, - :Y => 2.0 * rand_v_vals(lrs_alt.lattice), - :XY => 0.5, - :Z => 2.0 * rand_v_vals(lrs_alt.lattice), - :V => 0.5, - :W => 1.0, - ] - p_alt = [ - :k1 => 2.0, - :k2 => 0.1 .+ rand_v_vals(lrs_alt.lattice), - :dX => 1.0 .+ rand_e_vals(lrs_alt.lattice), - :dXY => 3.0, - :dZ => rand_e_vals(lrs_alt.lattice), - :dV => 0.2, - :p1 => 1.0, - :p2 => rand_v_vals(lrs_alt.lattice), - ] - oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) - ss_alt = solve(oprob_alt, Tsit5()).u[end] - - binding_srs_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] - lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_grid) - u0 = u0_alt[1:3] - p = p_alt[1:4] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), p) - ss = solve(oprob, Tsit5()).u[end] - - for i in 1:25 - @test isapprox(ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)], - ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)]) < 1e-3 - end -end - -# System with single spatial reaction. -let - lrs_1 = LatticeReactionSystem(SIR_system, SIR_dif_S, small_2d_grid) - lrs_2 = LatticeReactionSystem(SIR_system, [SIR_dif_S], small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01] - ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] - ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] - @test all(isequal.(ss_1, ss_2)) -end - -# Various ways to give parameters and initial conditions. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) - u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] - u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] - u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] - pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] - pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] - pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] - pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] - pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] - pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] - - p1 = [ - :α => 0.1 / 1000, - :β => [0.01, 0.02, 0.01, 0.03], - :dS => [0.01, 0.02, 0.03, 0.04], - :dI => 0.01, - :dR => 0.01, - ] - ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] - for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] - ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] - @test all(isequal.(ss, ss_1_1)) - end -end - -# Checks that variosu combinations of jac and sparse gives the same result. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) - oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) - oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) - oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) - - ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] - @test all(isapprox.(ss, - solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) -end - -# Splitting parameters by position -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - p1 = ([:α => 0.1 / 1000, :β => 0.01], [:dS => 0.01, :dI => 0.01, :dR => 0.01]) - p2 = [:α => 0.1 / 1000, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob1 = ODEProblem(lrs, u0, (0.0, 500.0), p1; jac = false) - oprob2 = ODEProblem(lrs, u0, (0.0, 500.0), p2; jac = false) - - @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) -end - -### Compare to Hand-written Functions ### - -# Compares the brusselator for a line of cells. -let - function spatial_brusselator_f(du, u, p, t) - # Non-spatial - for i in 1:2:(length(u) - 1) - du[i] = p[1] + 0.5 * (u[i]^2) * u[i + 1] - u[i] - p[2] * u[i] - du[i + 1] = p[2] * u[i] - 0.5 * (u[i]^2) * u[i + 1] - end - - # Spatial - du[1] += p[3] * (u[3] - u[1]) - du[end - 1] += p[3] * (u[end - 3] - u[end - 1]) - for i in 3:2:(length(u) - 3) - du[i] += p[3] * (u[i - 2] + u[i + 2] - 2u[i]) - end - end - function spatial_brusselator_jac(J, u, p, t) - J .= 0 - # Non-spatial - for i in 1:2:(length(u) - 1) - J[i, i] = u[i] * u[i + 1] - 1 - p[2] - J[i, i + 1] = 0.5 * (u[i]^2) - J[i + 1, i] = p[2] - u[i] * u[i + 1] - J[i + 1, i + 1] = -0.5 * (u[i]^2) - end - - # Spatial - J[1, 1] -= p[3] - J[1, 3] += p[3] - J[end - 1, end - 1] -= p[3] - J[end - 1, end - 3] += p[3] - for i in 3:2:(length(u) - 3) - J[i, i] -= 2 * p[3] - J[i, i - 2] += p[3] - J[i, i + 2] += p[3] - end - end - function spatial_brusselator_jac_sparse(J, u, p, t) - # Spatial - J.nzval .= 0.0 - J.nzval[7:6:(end - 9)] .= -2p[3] - J.nzval[1] = -p[3] - J.nzval[end - 3] = -p[3] - J.nzval[3:3:(end - 4)] .= p[3] - - # Non-spatial - for i in 1:1:Int64(lenth(u) / 2 - 1) - j = 6(i - 1) + 1 - J.nzval[j] = u[i] * u[i + 1] - 1 - p[2] - J.nzval[j + 1] = 0.5 * (u[i]^2) - J.nzval[j + 3] = p[2] - u[i] * u[i + 1] - J.nzval[j + 4] = -0.5 * (u[i]^2) - end - J.nzval[end - 3] = u[end - 1] * u[end] - 1 - p[end - 1] - J.nzval[end - 2] = 0.5 * (u[end - 1]^2) - J.nzval[end - 1] = p[2] - u[end - 1] * u[end] - J.nzval[end] = -0.5 * (u[end - 1]^2) - end - function make_jac_prototype(u0) - jac_prototype_pre = zeros(length(u0), length(u0)) - for i in 1:2:(length(u0) - 1) - jac_prototype_pre[i, i] = 1 - jac_prototype_pre[i + 1, i] = 1 - jac_prototype_pre[i, i + 1] = 1 - jac_prototype_pre[i + 1, i + 1] = 1 - end - for i in 3:2:(length(u0) - 1) - jac_prototype_pre[i - 2, i] = 1 - jac_prototype_pre[i, i - 2] = 1 - end - return sparse(jac_prototype_pre) - end - - u0 = 2 * rand(10000) - p = [1.0, 4.0, 0.1] - tspan = (0.0, 100.0) - - ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) - ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, - jac_prototype = make_jac_prototype(u0)) - - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, - path_graph(Int64(length(u0) / 2))) - u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] - pV = [:A => p[1], :B => p[2]] - pE = [:dX => p[3]] - ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = false).f - ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = true).f - - du_hw_dense = deepcopy(u0) - du_hw_sparse = deepcopy(u0) - du_aut_dense = deepcopy(u0) - du_aut_sparse = deepcopy(u0) - - ofun_hw_dense(du_hw_dense, u0, p, 0.0) - ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) - ofun_aut_dense(du_aut_dense, u0, p, 0.0) - ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) - - @test isapprox(du_hw_dense, du_aut_dense) - @test isapprox(du_hw_sparse, du_aut_sparse) - - J_hw_dense = deepcopy(zeros(length(u0), length(u0))) - J_hw_sparse = deepcopy(make_jac_prototype(u0)) - J_aut_dense = deepcopy(zeros(length(u0), length(u0))) - J_aut_sparse = deepcopy(make_jac_prototype(u0)) - - ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) - ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) - ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) - ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) - - @test isapprox(J_hw_dense, J_aut_dense) - @test isapprox(J_hw_sparse, J_aut_sparse) -end - -### Spatial Jump System Tests ### - -# Tests that there are no errors during runs. -let - for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] - lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) - u0_2 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 1.0, - :R => 0.0, - ]) - u0_3 = make_values_int([ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_4 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs)))) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - p1 = [:α => 0.1 / 1000, :β => 0.01] - p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] - p3 = [ - :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), - :β => 0.02 * rand_v_vals(lrs.lattice), - ] - p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) - for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - for pE in [pE_1, pE_2, pE_3, pE_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - jprob = JumpProblem(lrs, dprob, NSM()) - solve(jprob, SSAStepper()) - end - end - end - end - end -end - -# Tests that the correct hopping rates and initial conditions are generated. -# In this base case, hoppping rates should be on the form D_{s,i,j}. -let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - - # Prepares various u0 input types. - u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] - u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] - u0_3 = [1.0, 2.0, 3.0] - u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] - u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) - - # Checks hopping rates and u0 are correct. - true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] - true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) - for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] - # Provides parameters as a tupple. - for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.prob.u0 == true_u0 - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.prob.u0 == true_u0 - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end -end - -# Currently not in use, but will be added as more cases are enabled. -if false - # Tests hopping rates of the form D_{s,i,j} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_3] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i} - let - # Prepares special system and diffusion reactions (depending on compartments). - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pD = [] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s}*L_{i,j} - # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). - let - SIR_system_special = @reaction_network begin - @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack dS, dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1, :dI => 0.01] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, ] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0, :dS => 0.005] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] - pD_3 = [0.005, 2.0] - pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i}*L_{i,j} - let - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI connection_d = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] - pD_3 = [2.0] - pD_4 = [fill(0.2, ne(small_2d_grid))] - pD_5 = [fill(0.2, 1, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end -end - -### Runtime Checks ### -# Current timings are taken from the SciML CI server. -# Current not used, simply here for reference. -# Useful when attempting to optimise workflow. - -# using BenchmarkTools -# runtime_reduction_margin = 10.0 -# -# # Small grid, small, non-stiff, system. -# let -# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) -# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] -# pV = SIR_p -# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] -# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.00060 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, small, non-stiff, system. -# let -# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) -# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] -# pV = SIR_p -# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] -# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.26 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, small, stiff, system. -# -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 0.17 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Medium grid, small, stiff, system. -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 2.3 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, small, stiff, system. -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 170.0 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, mid-sized, non-stiff, system. -# let -# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, -# small_2d_grid) -# u0 = [ -# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :CuoAcLigand => 0.0, -# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :CuHLigand => 0.0, -# :SilaneOAc => 0.0, -# :Styrene => 0.16, -# :AlkylCuLigand => 0.0, -# :Amine_E => 0.39, -# :AlkylAmine => 0.0, -# :Cu_ELigand => 0.0, -# :E_Silane => 0.0, -# :Amine => 0.0, -# :Decomposition => 0.0, -# ] -# pV = CuH_Amination_p -# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.0016 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, mid-sized, non-stiff, system. -# let -# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, -# large_2d_grid) -# u0 = [ -# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :CuoAcLigand => 0.0, -# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :CuHLigand => 0.0, -# :SilaneOAc => 0.0, -# :Styrene => 0.16, -# :AlkylCuLigand => 0.0, -# :Amine_E => 0.39, -# :AlkylAmine => 0.0, -# :Cu_ELigand => 0.0, -# :E_Silane => 0.0, -# :Amine => 0.0, -# :Decomposition => 0.0, -# ] -# pV = CuH_Amination_p -# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.67 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, mid-sized, stiff, system. -# let -# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) -# u0 = [ -# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vPp => 0.0, -# :phos => 0.4, -# ] -# pV = sigmaB_p -# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 0.019 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, mid-sized, stiff, system. -# let -# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) -# u0 = [ -# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vPp => 0.0, -# :phos => 0.4, -# ] -# pV = sigmaB_p -# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 35.0 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl new file mode 100644 index 0000000000..34a9bc7ac9 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -0,0 +1,443 @@ +### Preparations ### + +# Fetch packages. +using OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + + +### Test No Error During Runs ### +for grid in [small_2d_grid, short_path, small_directed_cycle] + # Non-stiff case + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] + u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] + u0_3 = [ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ] + u0_4 = [ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1, p2, p3, p4] + pE_1 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => 0.01, spatial_param_syms(lrs)) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + spatial_param_syms(lrs)) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) + for pE in [pE_1, pE_2, pE_3, pE_4] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + end + end + end + end + + # Stiff case + for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] + lrs = LatticeReactionSystem(brusselator_system, srs, grid) + u0_1 = [:X => 1.0, :Y => 20.0] + u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] + u0_3 = [:X => rand_v_vals(lrs.lattice, 20), :Y => rand_v_vals(lrs.lattice, 10)] + u0_4 = make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs))) + for u0 in [u0_1, u0_2, u0_3, u0_4] + p1 = [:A => 1.0, :B => 4.0] + p2 = [:A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), :B => 4.0] + p3 = [ + :A => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :B => 4.0 .+ rand_v_vals(lrs.lattice, 1.0), + ] + p4 = make_u0_matrix(p2, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1, p2, p3, p4] + pE_1 = map(sp -> sp => 0.2, spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => rand(), spatial_param_syms(lrs)) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.2), + spatial_param_syms(lrs)) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), spatial_param_syms(lrs)) + for pE in [pE_1, pE_2, pE_3, pE_4] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); sparse = false) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + end + end + end + end +end + +### Tests Simulation Correctness ### + +# Checks that non-spatial brusselator simulation is identical to all on an unconnected lattice. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, unconnected_graph) + u0 = [:X => 2.0 + 2.0 * rand(), :Y => 10.0 + 10.0 * rand()] + pV = brusselator_p + pE = [:dX => 0.2] + oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) + oprob_spatial = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + sol_nonspatial = solve(oprob_nonspatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + sol_spatial = solve(oprob_spatial, QNDF(); abstol = 1e-12, reltol = 1e-12) + + for i in 1:nv(unconnected_graph) + @test all(isapprox.(sol_nonspatial.u[end], + sol_spatial.u[end][((i - 1) * 2 + 1):((i - 1) * 2 + 2)])) + end +end + +# Checks that result becomes homogeneous on a connected lattice. +let + lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) + u0 = [ + :X => 1.0 .+ rand_v_vals(lrs.lattice), + :Y => 2.0 * rand_v_vals(lrs.lattice), + :XY => 0.5, + ] + oprob = ODEProblem(lrs, u0, (0.0, 1000.0), binding_p; tstops = 0.1:0.1:1000.0) + ss = solve(oprob, Tsit5()).u[end] + + @test all(isapprox.(ss[1:3:end], ss[1])) + @test all(isapprox.(ss[2:3:end], ss[2])) + @test all(isapprox.(ss[3:3:end], ss[3])) +end + +### Tests Special Cases ### + +# Creates network with various combiantions of Symbls and Nums in diffusion reactions. +let + @parameters dS dI dR + @variables t + @species S(t) I(t) R(t) + SIR_srs_numsym_1 = diffusion_reactions([(:dS, :S), (:dI, :I), (:dR, :R)]) + SIR_srs_numsym_2 = diffusion_reactions([(dS, :S), (dI, :I), (dR, :R)]) + SIR_srs_numsym_3 = diffusion_reactions([(:dS, S), (:dI, I), (:dR, R)]) + SIR_srs_numsym_4 = diffusion_reactions([(dS, S), (dI, I), (dR, R)]) + SIR_srs_numsym_5 = diffusion_reactions([(dS, :S), (:dI, I), (dR, :R)]) + SIR_srs_numsym_6 = diffusion_reactions([(:dS, :S), (:dI, I), (dR, R)]) + + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(small_2d_grid), :R => 0.0] + pV = SIR_p + pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + pE_2 = [dS => 0.01, dI => 0.01, dR => 0.01] + pE_3 = [dS => 0.01, :dI => 0.01, :dR => 0.01] + ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = false), Tsit5()).u[end] + ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = true), Rosenbrock23()).u[end] + + for srs in [ + SIR_srs_numsym_1, + SIR_srs_numsym_2, + SIR_srs_numsym_3, + SIR_srs_numsym_4, + SIR_srs_numsym_5, + SIR_srs_numsym_6, + ], pE in [pE_1, pE_2, pE_3] + lrs = LatticeReactionSystem(SIR_system, srs, small_2d_grid) + ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false), Tsit5()).u[end] + ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = true), Rosenbrock23()).u[end] + @test all(isapprox.(ss_explicit, ss_explicit_base)) + @test all(isapprox.(ss_implicit, ss_implicit_base)) + end +end + +# Create network with vaious combinations of graph/di-graph and parameters. +let + lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) + lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] + pV = SIR_p + pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] + pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] + pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; + 0.01 0.01 0.01 0.01 0.01 0.01] + pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] + pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] + pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] + oprob_digraph_1 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_digraph_2 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_digraph_3 = ODEProblem(lrs_digraph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_11 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_1)) + oprob_graph_12 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_1)) + oprob_graph_21 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_2)) + oprob_graph_22 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_2)) + oprob_graph_31 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_digraph_3)) + oprob_graph_32 = ODEProblem(lrs_graph, u0, (0.0, 500.0), (pV, pE_graph_3)) + sim_end_digraph_1 = solve(oprob_digraph_1, Tsit5()).u[end] + sim_end_digraph_2 = solve(oprob_digraph_2, Tsit5()).u[end] + sim_end_digraph_3 = solve(oprob_digraph_3, Tsit5()).u[end] + sim_end_graph_11 = solve(oprob_graph_11, Tsit5()).u[end] + sim_end_graph_12 = solve(oprob_graph_12, Tsit5()).u[end] + sim_end_graph_21 = solve(oprob_graph_21, Tsit5()).u[end] + sim_end_graph_22 = solve(oprob_graph_22, Tsit5()).u[end] + sim_end_graph_31 = solve(oprob_graph_31, Tsit5()).u[end] + sim_end_graph_32 = solve(oprob_graph_32, Tsit5()).u[end] + + @test all(sim_end_digraph_1 .== sim_end_digraph_2 .== sim_end_digraph_3 .== + sim_end_graph_11 .== sim_end_graph_12 .== sim_end_graph_21 .== + sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) +end + +# Creates networks with empty species or parameters. +let + binding_system_alt = @reaction_network begin + @species X(t) Y(t) XY(t) Z(t) V(t) W(t) + @parameters k1 k2 dX [diffusionparameter = true] dXY [diffusionparameter = true] dZ [ + diffusionparameter = true, + ] dV [diffusionparameter = true] p1 p2 + (k1, k2), X + Y <--> XY + end + binding_srs_alt = [ + DiffusionReaction(:dX, :X), + DiffusionReaction(:dXY, :XY), + DiffusionReaction(:dZ, :Z), + DiffusionReaction(:dV, :V), + ] + lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_grid) + u0_alt = [ + :X => 1.0, + :Y => 2.0 * rand_v_vals(lrs_alt.lattice), + :XY => 0.5, + :Z => 2.0 * rand_v_vals(lrs_alt.lattice), + :V => 0.5, + :W => 1.0, + ] + p_alt = [ + :k1 => 2.0, + :k2 => 0.1 .+ rand_v_vals(lrs_alt.lattice), + :dX => 1.0 .+ rand_e_vals(lrs_alt.lattice), + :dXY => 3.0, + :dZ => rand_e_vals(lrs_alt.lattice), + :dV => 0.2, + :p1 => 1.0, + :p2 => rand_v_vals(lrs_alt.lattice), + ] + oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) + ss_alt = solve(oprob_alt, Tsit5()).u[end] + + binding_srs_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] + lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_grid) + u0 = u0_alt[1:3] + p = p_alt[1:4] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), p) + ss = solve(oprob, Tsit5()).u[end] + + for i in 1:25 + @test isapprox(ss_alt[((i - 1) * 6 + 1):((i - 1) * 6 + 3)], + ss[((i - 1) * 3 + 1):((i - 1) * 3 + 3)]) < 1e-3 + end +end + +# System with single spatial reaction. +let + lrs_1 = LatticeReactionSystem(SIR_system, SIR_dif_S, small_2d_grid) + lrs_2 = LatticeReactionSystem(SIR_system, [SIR_dif_S], small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss_1, ss_2)) +end + +# Various ways to give parameters and initial conditions. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) + u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] + u0_2 = [990.0, [1.0, 3.0, 2.0, 5.0], 0.0] + u0_3 = [990.0 990.0 990.0 990.0; 1.0 3.0 2.0 5.0; 0.0 0.0 0.0 0.0] + pV_1 = [:α => 0.1 / 1000, :β => [0.01, 0.02, 0.01, 0.03]] + pV_2 = [0.1 / 1000, [0.01, 0.02, 0.01, 0.03]] + pV_3 = [0.1/1000 0.1/1000 0.1/1000 0.1/1000; 0.01 0.02 0.01 0.03] + pE_1 = [:dS => [0.01, 0.02, 0.03, 0.04], :dI => 0.01, :dR => 0.01] + pE_2 = [[0.01, 0.02, 0.03, 0.04], :0.01, 0.01] + pE_3 = [0.01 0.02 0.03 0.04; 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01] + + p1 = [ + :α => 0.1 / 1000, + :β => [0.01, 0.02, 0.01, 0.03], + :dS => [0.01, 0.02, 0.03, 0.04], + :dI => 0.01, + :dR => 0.01, + ] + ss_1_1 = solve(ODEProblem(lrs, u0_1, (0.0, 1.0), p1), Tsit5()).u[end] + for u0 in [u0_1, u0_2, u0_3], pV in [pV_1, pV_2, pV_3], pE in [pE_1, pE_2, pE_3] + ss = solve(ODEProblem(lrs, u0, (0.0, 1.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss, ss_1_1)) + end +end + +# Checks that variosu combinations of jac and sparse gives the same result. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) + + ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] + @test all(isapprox.(ss, + solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) +end + +# Splitting parameters by position +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + p1 = ([:α => 0.1 / 1000, :β => 0.01], [:dS => 0.01, :dI => 0.01, :dR => 0.01]) + p2 = [:α => 0.1 / 1000, :β => 0.01, :dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob1 = ODEProblem(lrs, u0, (0.0, 500.0), p1; jac = false) + oprob2 = ODEProblem(lrs, u0, (0.0, 500.0), p2; jac = false) + + @test all(isapprox.(solve(oprob1, Tsit5()).u[end], solve(oprob2, Tsit5()).u[end])) +end + +### Compare to Hand-written Functions ### + +# Compares the brusselator for a line of cells. +let + function spatial_brusselator_f(du, u, p, t) + # Non-spatial + for i in 1:2:(length(u) - 1) + du[i] = p[1] + 0.5 * (u[i]^2) * u[i + 1] - u[i] - p[2] * u[i] + du[i + 1] = p[2] * u[i] - 0.5 * (u[i]^2) * u[i + 1] + end + + # Spatial + du[1] += p[3] * (u[3] - u[1]) + du[end - 1] += p[3] * (u[end - 3] - u[end - 1]) + for i in 3:2:(length(u) - 3) + du[i] += p[3] * (u[i - 2] + u[i + 2] - 2u[i]) + end + end + function spatial_brusselator_jac(J, u, p, t) + J .= 0 + # Non-spatial + for i in 1:2:(length(u) - 1) + J[i, i] = u[i] * u[i + 1] - 1 - p[2] + J[i, i + 1] = 0.5 * (u[i]^2) + J[i + 1, i] = p[2] - u[i] * u[i + 1] + J[i + 1, i + 1] = -0.5 * (u[i]^2) + end + + # Spatial + J[1, 1] -= p[3] + J[1, 3] += p[3] + J[end - 1, end - 1] -= p[3] + J[end - 1, end - 3] += p[3] + for i in 3:2:(length(u) - 3) + J[i, i] -= 2 * p[3] + J[i, i - 2] += p[3] + J[i, i + 2] += p[3] + end + end + function spatial_brusselator_jac_sparse(J, u, p, t) + # Spatial + J.nzval .= 0.0 + J.nzval[7:6:(end - 9)] .= -2p[3] + J.nzval[1] = -p[3] + J.nzval[end - 3] = -p[3] + J.nzval[3:3:(end - 4)] .= p[3] + + # Non-spatial + for i in 1:1:Int64(lenth(u) / 2 - 1) + j = 6(i - 1) + 1 + J.nzval[j] = u[i] * u[i + 1] - 1 - p[2] + J.nzval[j + 1] = 0.5 * (u[i]^2) + J.nzval[j + 3] = p[2] - u[i] * u[i + 1] + J.nzval[j + 4] = -0.5 * (u[i]^2) + end + J.nzval[end - 3] = u[end - 1] * u[end] - 1 - p[end - 1] + J.nzval[end - 2] = 0.5 * (u[end - 1]^2) + J.nzval[end - 1] = p[2] - u[end - 1] * u[end] + J.nzval[end] = -0.5 * (u[end - 1]^2) + end + function make_jac_prototype(u0) + jac_prototype_pre = zeros(length(u0), length(u0)) + for i in 1:2:(length(u0) - 1) + jac_prototype_pre[i, i] = 1 + jac_prototype_pre[i + 1, i] = 1 + jac_prototype_pre[i, i + 1] = 1 + jac_prototype_pre[i + 1, i + 1] = 1 + end + for i in 3:2:(length(u0) - 1) + jac_prototype_pre[i - 2, i] = 1 + jac_prototype_pre[i, i - 2] = 1 + end + return sparse(jac_prototype_pre) + end + + u0 = 2 * rand(10000) + p = [1.0, 4.0, 0.1] + tspan = (0.0, 100.0) + + ofun_hw_dense = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac) + ofun_hw_sparse = ODEFunction(spatial_brusselator_f; jac = spatial_brusselator_jac, + jac_prototype = make_jac_prototype(u0)) + + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, + path_graph(Int64(length(u0) / 2))) + u0V = [:X => u0[1:2:(end - 1)], :Y => u0[2:2:end]] + pV = [:A => p[1], :B => p[2]] + pE = [:dX => p[3]] + ofun_aut_dense = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = false).f + ofun_aut_sparse = ODEProblem(lrs, u0V, tspan, (pV, pE); jac = true, sparse = true).f + + du_hw_dense = deepcopy(u0) + du_hw_sparse = deepcopy(u0) + du_aut_dense = deepcopy(u0) + du_aut_sparse = deepcopy(u0) + + ofun_hw_dense(du_hw_dense, u0, p, 0.0) + ofun_hw_sparse(du_hw_sparse, u0, p, 0.0) + ofun_aut_dense(du_aut_dense, u0, p, 0.0) + ofun_aut_sparse(du_aut_sparse, u0, p, 0.0) + + @test isapprox(du_hw_dense, du_aut_dense) + @test isapprox(du_hw_sparse, du_aut_sparse) + + J_hw_dense = deepcopy(zeros(length(u0), length(u0))) + J_hw_sparse = deepcopy(make_jac_prototype(u0)) + J_aut_dense = deepcopy(zeros(length(u0), length(u0))) + J_aut_sparse = deepcopy(make_jac_prototype(u0)) + + ofun_hw_dense.jac(J_hw_dense, u0, p, 0.0) + ofun_hw_sparse.jac(J_hw_sparse, u0, p, 0.0) + ofun_aut_dense.jac(J_aut_dense, u0, p, 0.0) + ofun_aut_sparse.jac(J_aut_sparse, u0, p, 0.0) + + @test isapprox(J_hw_dense, J_aut_dense) + @test isapprox(J_hw_sparse, J_aut_sparse) +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl new file mode 100644 index 0000000000..3d705e0eb9 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -0,0 +1,212 @@ +# Not actually run in CI, but useful for reference of ODE simulation performance across updates. + +### Preparations ### + +# Fetch packages. +using OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Runtime Checks ### +# Current timings are taken from the SciML CI server. +# Current not used, simply here for reference. +# Useful when attempting to optimise workflow. + +# using BenchmarkTools +# runtime_reduction_margin = 10.0 + +# Small grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00060 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.26 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, small, stiff, system. + +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.17 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Medium grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 2.3 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 170.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + small_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.0016 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + large_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.67 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.019 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 35.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl new file mode 100644 index 0000000000..fc3aae2045 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -0,0 +1,309 @@ +### Preparations ### + +# Fetch packages. +using JumpProcesses +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Correctness Tests ### + +# Tests that there are no errors during runs. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 1.0, + :R => 0.0, + ]) + u0_3 = make_values_int([ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_4 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs)))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + solve(jprob, SSAStepper()) + end + end + end + end + end +end + +### Input Handling Tests ### + +# Tests that the correct hopping rates and initial conditions are generated. +# In this base case, hoppping rates should be on the form D_{s,i,j}. +let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + + # Prepares various u0 input types. + u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] + u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] + u0_3 = [1.0, 2.0, 3.0] + u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] + u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + + # Checks hopping rates and u0 are correct. + true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + # Provides parameters as a tupple. + for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end + +### Hoping Rates Computation Tests ### + +# Currently not in use, but will be added as more cases are enabled. +if false + # Tests hopping rates of the form D_{s,i,j} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_3] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i} + let + # Prepares special system and diffusion reactions (depending on compartments). + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pD = [] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s}*L_{i,j} + # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). + let + SIR_system_special = @reaction_network begin + @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack dS, dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1, :dI => 0.01] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, ] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0, :dS => 0.005] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] + pD_3 = [0.005, 2.0] + pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i}*L_{i,j} + let + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI connection_d = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] + pD_3 = [2.0] + pD_4 = [fill(0.2, ne(small_2d_grid))] + pD_5 = [fill(0.2, 1, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl new file mode 100644 index 0000000000..271649010e --- /dev/null +++ b/test/spatial_test_networks.jl @@ -0,0 +1,186 @@ +### Fetch packages ### +using Catalyst, Graphs + +### Helper Functions ### + +# Generates ranomised intiial condition or paraemter values. +rand_v_vals(grid) = rand(nv(grid)) +rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x +rand_e_vals(grid) = rand(ne(grid)) +rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x +function make_u0_matrix(value_map, vals, symbols) + (length(symbols) == 0) && (return zeros(0, length(vals))) + d = Dict(value_map) + return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] +end + +# Gets a symbol list of spatial parameters. +function spatial_param_syms(lrs::LatticeReactionSystem) + ModelingToolkit.getname.(diffusion_parameters(lrs)) +end + +# Converts to integer value (for JumpProcess simulations). +function make_values_int(values::Vector{<:Pair}) + [val[1] => round.(Int64, val[2]) for val in values] +end +make_values_int(values::Matrix{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] + +### Declares Models ### + +# Small non-stiff system. +SIR_system = @reaction_network begin + α, S + I --> 2I + β, I --> R +end +SIR_p = [:α => 0.1 / 1000, :β => 0.01] +SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] + +SIR_dif_S = DiffusionReaction(:dS, :S) +SIR_dif_I = DiffusionReaction(:dI, :I) +SIR_dif_R = DiffusionReaction(:dR, :R) +SIR_srs_1 = [SIR_dif_S] +SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] + +# Small non-stiff system. +binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end +binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) +binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] +binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] + +# Mid-sized non-stiff system. +CuH_Amination_system = @reaction_network begin + 10.0^kp1, CuoAc + Ligand --> CuoAcLigand + 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc + 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand + 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene + 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand + 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E + 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane + 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand + 10.0^kdc, CuHLigand + CuHLigand --> Decomposition +end +CuH_Amination_p = [ + :kp1 => 1.2, + :kp2 => -0.72, + :k1 => 0.57, + :k_1 => -3.5, + :k2 => -0.35, + :k_2 => -0.77, + :k3 => -0.025, + :kam => -2.6, + :kdc => -3.0, +] +CuH_Amination_u0 = [ + :CuoAc => 0.0065, + :Ligand => 0.0072, + :CuoAcLigand => 0.0, + :Silane => 0.65, + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, +] + +CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) +CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) +CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) +CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) +CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) +CuH_Amination_srs_1 = [CuH_Amination_diff_1] +CuH_Amination_srs_2 = [ + CuH_Amination_diff_1, + CuH_Amination_diff_2, + CuH_Amination_diff_3, + CuH_Amination_diff_4, + CuH_Amination_diff_5, +] + +# Small stiff system. +brusselator_system = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end +brusselator_p = [:A => 1.0, :B => 4.0] + +brusselator_dif_x = DiffusionReaction(:dX, :X) +brusselator_dif_y = DiffusionReaction(:dY, :Y) +brusselator_srs_1 = [brusselator_dif_x] +brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] + +# Mid-sized stiff system. +# Unsure about stifness, but non-spatial version oscillates for this parameter set. +sigmaB_system = @reaction_network begin + kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ + kDeg, vPp ⟶ phos + (kBw, kDw), 2w ⟷ w2 + (kB1, kD1), w2 + v ⟷ w2v + (kB2, kD2), w2v + v ⟷ w2v2 + kK1, w2v ⟶ w2 + vP + kK2, w2v2 ⟶ w2v + vP + (kB3, kD3), w2 + σB ⟷ w2σB + (kB4, kD4), w2σB + v ⟷ w2v + σB + (kB5, kD5), vP + phos ⟷ vPp + kP, vPp ⟶ v + phos + v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ σB + λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w + λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v +end +sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, + :kB4 => 1800, :kB5 => 3600, + :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, + :kP => 180, :kDeg => 0.7, + :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] +sigmaB_u0 = [ + :w => 1.0, + :w2 => 1.0, + :w2v => 1.0, + :v => 1.0, + :w2v2 => 1.0, + :vP => 1.0, + :σB => 1.0, + :w2σB => 1.0, + :vPp => 0.0, + :phos => 0.4, +] + +sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) +sigmaB_dif_w = DiffusionReaction(:Dw, :w) +sigmaB_dif_v = DiffusionReaction(:Dv, :v) +sigmaB_srs_1 = [sigmaB_dif_σB] +sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] + +### Declares Lattices ### + +# Grids. +very_small_2d_grid = Graphs.grid([2, 2]) +small_2d_grid = Graphs.grid([5, 5]) +medium_2d_grid = Graphs.grid([20, 20]) +large_2d_grid = Graphs.grid([100, 100]) + +small_3d_grid = Graphs.grid([5, 5, 5]) +medium_3d_grid = Graphs.grid([20, 20, 20]) +large_3d_grid = Graphs.grid([100, 100, 100]) + +# Paths. +short_path = path_graph(100) +long_path = path_graph(1000) + +# Unconnected graphs. +unconnected_graph = SimpleGraph(10) + +# Undirected cycle. +undirected_cycle = cycle_graph(49) + +# Directed cycle. +small_directed_cycle = cycle_graph(100) +large_directed_cycle = cycle_graph(1000) \ No newline at end of file From 3ac5d38dd921aace0905f13887b7bd301336c41a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 11:08:34 -0400 Subject: [PATCH 050/134] testupdate --- test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl | 2 +- .../lattice_reaction_systems_ODEs_performance.jl | 2 +- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 34a9bc7ac9..7d7df1addc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -9,7 +9,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Test No Error During Runs ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index 3d705e0eb9..ebc5130dac 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -11,7 +11,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Runtime Checks ### # Current timings are taken from the SciML CI server. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index fc3aae2045..664a9c7f7f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -9,7 +9,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Correctness Tests ### From 0891dc1ef37ecabcdfab44e18c4b99b463f72736 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 12:16:28 -0400 Subject: [PATCH 051/134] testupdate --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 664a9c7f7f..5aa7cba1c3 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -268,7 +268,7 @@ if false α, S + I --> 2I β, I --> R end - @unpack comp_dS, comp_dI connection_d = SIR_system_special + @unpack comp_dS, comp_dI, connection_d = SIR_system_special SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] # Prepares the system. From 5d9e129aad56e2eb2a613de1a59166baaceb178a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 18:14:35 +0200 Subject: [PATCH 052/134] Fix stableRNG usage --- src/lattice_reaction_system_diffusion.jl | 1 + .../lattice_reaction_systems_ODEs.jl | 4 ---- .../lattice_reaction_systems_ODEs_performance.jl | 4 ---- .../lattice_reaction_systems_jumps.jl | 4 ---- test/spatial_test_networks.jl | 8 ++++++-- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 15c1ec9bbd..a75c54cadb 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -445,6 +445,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, combinatoric_ratelaws, checks) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 7d7df1addc..f61f770f55 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -4,10 +4,6 @@ using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index ebc5130dac..e416ab6fb2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -6,10 +6,6 @@ using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 5aa7cba1c3..eedb26ed8b 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -4,10 +4,6 @@ using JumpProcesses using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 271649010e..bf08aab518 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -1,12 +1,16 @@ ### Fetch packages ### using Catalyst, Graphs +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + ### Helper Functions ### # Generates ranomised intiial condition or paraemter values. -rand_v_vals(grid) = rand(nv(grid)) +rand_v_vals(grid) = rand(rng, nv(grid)) rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_e_vals(grid) = rand(ne(grid)) +rand_e_vals(grid) = rand(rng, ne(grid)) rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x function make_u0_matrix(value_map, vals, symbols) (length(symbols) == 0) && (return zeros(0, length(vals))) From cefc59ee453ec5403a3547728279ba271f705417 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 12:19:28 -0400 Subject: [PATCH 053/134] Update src/lattice_reaction_system_diffusion.jl Co-authored-by: Vasily Ilin --- src/lattice_reaction_system_diffusion.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index a75c54cadb..09adefe8a4 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -459,7 +459,11 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump +function make_majumps(lrs::LatticeReactionSystem) + jumps = assemble_jumps(lrs.rs) + if !(jumps[end] isa MassActionJump) + error("Only MassAction Jumps are currently allowed in spatial simulation.") + return jumps end ### Accessing State & Parameter Array Values ### From 5a3c09c9be40f42c9206fc908979e6cdc55352f5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:18:15 +0200 Subject: [PATCH 054/134] add ABC test and reset make_majumps --- src/lattice_reaction_system_diffusion.jl | 35 ++++++------ .../lattice_reaction_systems_jumps.jl | 55 ++++++++++++++++++- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 09adefe8a4..5b7cd4670c 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -415,8 +415,8 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") hopping_constants = make_hopping_constants(dprob, lrs) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, - checks) + #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, @@ -445,27 +445,28 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) + first.(dprob.p[1])) ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") -function make_majumps(lrs::LatticeReactionSystem) - jumps = assemble_jumps(lrs.rs) - if !(jumps[end] isa MassActionJump) - error("Only MassAction Jumps are currently allowed in spatial simulation.") - return jumps + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump end +# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) +# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) +# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") +# return jumps +# end + ### Accessing State & Parameter Array Values ### # Gets the index in the u array of species s in compartment comp (when their are nS species). @@ -503,4 +504,4 @@ end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) reshape(expand_component_values(values, n), length(values), n) -end +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index eedb26ed8b..430468828f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -302,4 +302,57 @@ if false @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates end end -end \ No newline at end of file +end + +using Catalyst, JumpProcesses, Graphs +### ABC Model Test (from JumpProcesses) ### +let + # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). + Nsims = 100 + reltol = 0.05 + non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations + dim = 1 + linear_size = 5 + dims = Tuple(repeat([linear_size], dim)) + domain_size = 1.0 #μ-meter + mesh_size = domain_size / linear_size + rates = [0.1 / mesh_size, 1.0] + diffusivity = 1.0 + num_species = 3 + + # Make model. + rn = @reaction_network begin + (kB,kD), A + B <--> C + end + srs = diffusion_reactions([(:D, :A), (:D, :B), (:D, :C)]) + lattice = Graphs.grid(dims) + lrs = LatticeReactionSystem(rn, srs, lattice) + + + # Set simulation parameters and create problems + u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] + tspan = (0.0, 10.0) + pC = [:kB => rates[1], :kD => rates[2]] + pD = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pC, pD)) + jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. + + # Tests. + function get_mean_end_state(jump_prob, Nsims) + end_state = zeros(size(jump_prob.prob.u0)) + for i in 1:Nsims + sol = solve(jump_prob, SSAStepper()) + end_state .+= sol.u[end] + end + end_state / Nsims + end + for jprob in jump_problems + solution = solve(jprob, SSAStepper()) + mean_end_state = get_mean_end_state(jprob, Nsims) + mean_end_state = reshape(mean_end_state, num_species, num_nodes) + diff = sum(mean_end_state, dims = 2) - non_spatial_mean + for (i, d) in enumerate(diff) + @test abs(d) < reltol * non_spatial_mean[i] + end + end +end From e0d7f3f27cebd372c77fa5c951a14a9e68c40673 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:19:13 +0200 Subject: [PATCH 055/134] remove tests for future functionality --- .../lattice_reaction_systems_jumps.jl | 194 ------------------ 1 file changed, 194 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 430468828f..930fb972a6 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -110,200 +110,6 @@ let end end -### Hoping Rates Computation Tests ### - -# Currently not in use, but will be added as more cases are enabled. -if false - # Tests hopping rates of the form D_{s,i,j} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_3] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i} - let - # Prepares special system and diffusion reactions (depending on compartments). - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pD = [] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s}*L_{i,j} - # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). - let - SIR_system_special = @reaction_network begin - @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack dS, dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1, :dI => 0.01] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, ] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0, :dS => 0.005] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] - pD_3 = [0.005, 2.0] - pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i}*L_{i,j} - let - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI, connection_d = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] - pD_3 = [2.0] - pD_4 = [fill(0.2, ne(small_2d_grid))] - pD_5 = [fill(0.2, 1, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end -end - using Catalyst, JumpProcesses, Graphs ### ABC Model Test (from JumpProcesses) ### let From fa910110b44944c2abb64edaa34ce998b92d5223 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 14:45:59 +0200 Subject: [PATCH 056/134] explanatory note --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 5b7cd4670c..24f8f5332b 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -460,7 +460,7 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg error("Currently, for lattice jump simulations, variable rate jumps are not supported.") return ___jprob.massaction_jump end - +# We would want something like this, but it errors if used. # function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) # jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) # (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") From fb5977f3fe618c1667e20c53ccd8bc19492663e2 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 10:37:32 -0400 Subject: [PATCH 057/134] Drop formatting changes in `compound.jl`. --- src/compound.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compound.jl b/src/compound.jl index 01c9081232..9f2ac5bca2 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound From f00289c0e6f2b3a101017fd47ea8cbec1dd7bc9d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 16:40:18 +0200 Subject: [PATCH 058/134] use functors --- src/Catalyst.jl | 2 +- src/lattice_reaction_system_diffusion.jl | 163 +++++++++++++---------- 2 files changed, 96 insertions(+), 69 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index a229f694a8..e0c380036f 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,7 +32,7 @@ import ModelingToolkit: check_variables, check_parameters, _iszero, _merge, chec import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.vertices, Graphs.edges, Graphs.add_vertices!, Graphs.nv, Graphs.ne +import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.SimpleDiGraph, Graphs.vertices, Graphs.edges, Graphs.add_vertices!, Graphs.nv, Graphs.ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index cac39ffd26..74efa92455 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -225,6 +225,53 @@ function compute_diffusion_rates(rate_law::Num, for p in relevant_parameters)) for idxE in 1:nE] end +### Spatial ODE Functor Structures ### + +# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +struct LatticeDiffusionODEf + ofunc::SciMLBase.AbstractODEFunction{true} + nC::Int64 + nS::Int64 + pC::Vector{Vector{Float64}} + pC_location_types::Vector{Bool} + pC_idxs::UnitRange{Int64} + diffusion_rates::Vector + leaving_rates::Matrix{Float64} + enumerated_edges::Base.Iterators.Enumerate{Graphs.SimpleGraphs.SimpleEdgeIter{SimpleDiGraph{Int64}}} + + function LatticeDiffusionODEf(ofunc::SciMLBase.AbstractODEFunction{true}, pC, diffusion_rates::Vector, lrs::LatticeReactionSystem) + leaving_rates = zeros(length(diffusion_rates), lrs.nC) + for (s_idx, rates) in enumerate(last.(diffusion_rates)), + (e_idx, e) in enumerate(edges(lrs.lattice)) + + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + end + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + new(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, diffusion_rates, leaving_rates, enumerated_edges) + end +end + +# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +# Thinking that LatticeDiffusionODEjac maybe should be a parameteric type or have subtypes to designate whenever it is sparse or not. +struct LatticeDiffusionODEjac + ofunc::SciMLBase.AbstractODEFunction{true} + nC::Int64 + nS::Int64 + pC::Vector{Vector{Float64}} + pC_location_types::Vector{Bool} + pC_idxs::UnitRange{Int64} + sparse::Bool + jac_values + + function LatticeDiffusionODEjac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + new(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, sparse, sparse ? jac_prototype.nzval : Matrix(jac_prototype)) + end +end + ### ODEProblem ### # Creates an ODEProblem from a LatticeReactionSystem. @@ -248,80 +295,14 @@ function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64 diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] for diff_rates in diffusion_rates_speciesmap] - f = build_f(ofunc, pC, diffusion_rates, lrs) + f = LatticeDiffusionODEf(ofunc, pC, diffusion_rates, lrs) jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + jac = use_jac ? LatticeDiffusionODEjac(ofunc, pC, lrs, jac_prototype, sparse) : nothing return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) end -# Builds the forcing (f) function for a reaction system on a lattice. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - diffusion_rates::Vector, - lrs::LatticeReactionSystem) - leaving_rates = zeros(length(diffusion_rates), lrs.nC) - for (s_idx, rates) in enumerate(last.(diffusion_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) - - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) - end - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - - return function (du, u, p, t) - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc((@view du[get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for spatial diffusion reactions. - for (s_idx, (s, rates)) in enumerate(diffusion_rates) - for comp_i::Int64 in 1:(lrs.nC) - du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, - lrs.nS)] - end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges - du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, - lrs.nS)] - end - end - end -end - -# Builds the Jacobian function for a reaction system on a lattice. -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - lrs::LatticeReactionSystem, - jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - sparse::Bool) - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) - add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : - J -> (J .+= Matrix(jac_prototype))) - - return function (J, u, p, t) - # Because of weird stuff where the Jacobian is not reset that I don't understand properly. - reset_J_vals(J) - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for the spatial reactions. - add_diff_J_vals(J) - end -end - # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, diffusion_rates, lrs::LatticeReactionSystem; @@ -393,6 +374,52 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) end +# Defines the forcing functors effect on the (spatial) ODE system. +function (f_func::LatticeDiffusionODEf)(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(f_func.nC) + f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), + (@view u[get_indexes(comp_i, f_func.nS)]), + view_pC_vector(p, comp_i, f_func.pC_location_types, f_func.pC_idxs), t) + end + + # Updates for spatial diffusion reactions. + for (s_idx, (s, rates)) in enumerate(f_func.diffusion_rates) + for comp_i::Int64 in 1:(f_func.nC) + du[get_index(comp_i, s, f_func.nS)] -= f_func.leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, s, + f_func.nS)] + end + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges + du[get_index(edge.dst, s, f_func.nS)] += get_component_value(rates, e_idx) * + u[get_index(edge.src, s, + f_func.nS)] + end + end +end + +# Defines the jacobian functors effect on the (spatial) ODE system. +function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) + # Because of weird stuff where the Jacobian is not reset that I don't understand properly. + reset_J_vals!(J) + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(jac_func.nC) + jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.nS), + get_indexes(comp_i, jac_func.nS)]), + (@view u[get_indexes(comp_i, jac_func.nS)]), + view_pC_vector(p, comp_i, jac_func.pC_location_types, jac_func.pC_idxs), t) + end + + # Updates for the spatial reactions. + add_diff_J_vals!(J, jac_func) +end +# Resets the jacobian matrix within a jac call. +reset_J_vals!(J::Matrix) = (J .= 0.0) +reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) +# Updates the jacobian matrix with the difussion values. +add_diff_J_vals!(J::SparseMatrixCSC, jac_func::LatticeDiffusionODEjac) = (J.nzval .+= jac_func.jac_values) +add_diff_J_vals!(J::Matrix, jac_func::LatticeDiffusionODEjac) = (J .+= jac_func.jac_values) ### Accessing State & Parameter Array Values ### From 7a60c190c81d6296782125d0e8f1ccff7f7e705a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 17:29:45 +0200 Subject: [PATCH 059/134] functor update --- src/lattice_reaction_system_diffusion.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 74efa92455..f0a7821c55 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -228,18 +228,18 @@ end ### Spatial ODE Functor Structures ### # Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. -struct LatticeDiffusionODEf - ofunc::SciMLBase.AbstractODEFunction{true} +struct LatticeDiffusionODEf{R,S,T} + ofunc::R nC::Int64 nS::Int64 pC::Vector{Vector{Float64}} pC_location_types::Vector{Bool} pC_idxs::UnitRange{Int64} - diffusion_rates::Vector + diffusion_rates::Vector{S} leaving_rates::Matrix{Float64} - enumerated_edges::Base.Iterators.Enumerate{Graphs.SimpleGraphs.SimpleEdgeIter{SimpleDiGraph{Int64}}} + enumerated_edges::T - function LatticeDiffusionODEf(ofunc::SciMLBase.AbstractODEFunction{true}, pC, diffusion_rates::Vector, lrs::LatticeReactionSystem) + function LatticeDiffusionODEf(ofunc::R, pC, diffusion_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} leaving_rates = zeros(length(diffusion_rates), lrs.nC) for (s_idx, rates) in enumerate(last.(diffusion_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) @@ -249,26 +249,26 @@ struct LatticeDiffusionODEf pC_location_types = length.(pC) .== 1 pC_idxs = 1:length(pC) enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - new(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, diffusion_rates, leaving_rates, enumerated_edges) + new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, diffusion_rates, leaving_rates, enumerated_edges) end end # Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. -# Thinking that LatticeDiffusionODEjac maybe should be a parameteric type or have subtypes to designate whenever it is sparse or not. -struct LatticeDiffusionODEjac - ofunc::SciMLBase.AbstractODEFunction{true} +struct LatticeDiffusionODEjac{S,T} + ofunc::S nC::Int64 nS::Int64 pC::Vector{Vector{Float64}} pC_location_types::Vector{Bool} pC_idxs::UnitRange{Int64} sparse::Bool - jac_values + jac_values::T - function LatticeDiffusionODEjac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) + function LatticeDiffusionODEjac(ofunc::S, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} pC_location_types = length.(pC) .== 1 pC_idxs = 1:length(pC) - new(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, sparse, sparse ? jac_prototype.nzval : Matrix(jac_prototype)) + jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) + new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, sparse, jac_values) end end From 8fb32c7c1e4d96ec28e83f330eff0e9bef44519e Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 19:29:41 -0400 Subject: [PATCH 060/134] Fix test. --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 930fb972a6..9ecca8a319 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -119,6 +119,7 @@ let non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations dim = 1 linear_size = 5 + num_nodes = linear_size^dim dims = Tuple(repeat([linear_size], dim)) domain_size = 1.0 #μ-meter mesh_size = domain_size / linear_size From 1cdd23fc6c3fe978f000417e156f29c66d431f41 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:04:21 -0400 Subject: [PATCH 061/134] Construct massaction jumps without building a `JumpProblem`. --- src/lattice_reaction_system_diffusion.jl | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 24f8f5332b..c3d72424e3 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -414,12 +414,14 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator checks = false, kwargs...) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + if any(length.(dprob.p[1]) .> 1) + error("Spatial reaction rates are currently not supported in lattice jump simulations.") + end hopping_constants = make_hopping_constants(dprob, lrs) - #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) - return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + majumps_ = make_majumps(___dprob, lrs.rs) + return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) end @@ -445,27 +447,18 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) - any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") - ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) - (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump -end -# We would want something like this, but it errors if used. -# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) -# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) -# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") -# return jumps -# end +function make_majumps(non_spatial_prob, rs::ReactionSystem) + prob = non_spatial_prob + + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + eqs = equations(js) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) + return majumps +end ### Accessing State & Parameter Array Values ### From cbb2dbd6209879cf8cc77b6ca5492923476e308d Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:07:03 -0400 Subject: [PATCH 062/134] Fix comment. --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index c3d72424e3..524e2e500d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -446,7 +446,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end end -# Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Creates the mass action jumps from a discrete problem and a reaction system. function make_majumps(non_spatial_prob, rs::ReactionSystem) prob = non_spatial_prob From f910f927fd675271e9f6e03c0f1b3c95b15812b6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 15:31:46 -0400 Subject: [PATCH 063/134] revamp test and start SplitApplyCombine removal (will remove dependency once disucssion resolved) --- src/lattice_reaction_system_diffusion.jl | 31 +- test/runtests.jl | 2 +- ...attice_reaction_systems_ODE_performance.jl | 208 +++++++++ ...ms.jl => lattice_reaction_systems_ODEs.jl} | 393 +----------------- test/spatial_test_networks.jl | 190 +++++++++ 5 files changed, 421 insertions(+), 403 deletions(-) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl rename test/spatial_reaction_systems/{lattice_reaction_systems.jl => lattice_reaction_systems_ODEs.jl} (56%) create mode 100644 test/spatial_test_networks.jl diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index f0a7821c55..74f37deb73 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -233,8 +233,8 @@ struct LatticeDiffusionODEf{R,S,T} nC::Int64 nS::Int64 pC::Vector{Vector{Float64}} - pC_location_types::Vector{Bool} - pC_idxs::UnitRange{Int64} + work_pC::Vector{Float64} + enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} diffusion_rates::Vector{S} leaving_rates::Matrix{Float64} enumerated_edges::T @@ -246,10 +246,10 @@ struct LatticeDiffusionODEf{R,S,T} leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) end - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) + work_pC = zeros(lrs.nC) + enumerated_pC_idx_types = enumerate(length.(pC) .== 1) enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, diffusion_rates, leaving_rates, enumerated_edges) + new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, diffusion_rates, leaving_rates, enumerated_edges) end end @@ -259,16 +259,16 @@ struct LatticeDiffusionODEjac{S,T} nC::Int64 nS::Int64 pC::Vector{Vector{Float64}} - pC_location_types::Vector{Bool} - pC_idxs::UnitRange{Int64} + work_pC::Vector{Float64} + enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} sparse::Bool jac_values::T function LatticeDiffusionODEjac(ofunc::S, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) + work_pC = zeros(lrs.nC) + enumerated_pC_idx_types = enumerate(length.(pC) .== 1) jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) - new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, pC_location_types, pC_idxs, sparse, jac_values) + new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, sparse, jac_values) end end @@ -380,7 +380,7 @@ function (f_func::LatticeDiffusionODEf)(du, u, p, t) for comp_i::Int64 in 1:(f_func.nC) f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), (@view u[get_indexes(comp_i, f_func.nS)]), - view_pC_vector(p, comp_i, f_func.pC_location_types, f_func.pC_idxs), t) + view_pC_vector!(f_func.work_pC, p, comp_i, f_func.enumerated_pC_idx_types), t) end # Updates for spatial diffusion reactions. @@ -408,7 +408,7 @@ function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.nS), get_indexes(comp_i, jac_func.nS)]), (@view u[get_indexes(comp_i, jac_func.nS)]), - view_pC_vector(p, comp_i, jac_func.pC_location_types, jac_func.pC_idxs), t) + view_pC_vector!(jac_func.work_pC, p, comp_i, jac_func.enumerated_pC_idx_types), t) end # Updates for the spatial reactions. @@ -452,8 +452,11 @@ function expand_component_values(values::Vector{<:Vector}, n, location_types::Ve vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) end # Creates a view of the pC vector at a given comaprtment. -function view_pC_vector(pC, comp, pC_location_types, pC_idxs) - mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) +function view_pC_vector!(work_pC, pC, comp, enumerated_pC_idx_types) + for (idx,loc_type) in enumerated_pC_idx_types + work_pC[idx] = (loc_type ? pC[idx][1] : pC[idx][comp]) + end + return work_pC end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) diff --git a/test/runtests.jl b/test/runtests.jl index 5c4e7fce7a..87f41306a9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,7 @@ using SafeTestsets ### Tests Spatial Network Simulations. ### @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end - @time @safetestset "Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end + @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl new file mode 100644 index 0000000000..e416ab6fb2 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl @@ -0,0 +1,208 @@ +# Not actually run in CI, but useful for reference of ODE simulation performance across updates. + +### Preparations ### + +# Fetch packages. +using OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test + +# Fetch test networks. +include("../spatial_test_networks.jl") + +### Runtime Checks ### +# Current timings are taken from the SciML CI server. +# Current not used, simply here for reference. +# Useful when attempting to optimise workflow. + +# using BenchmarkTools +# runtime_reduction_margin = 10.0 + +# Small grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00060 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.26 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, small, stiff, system. + +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.17 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Medium grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 2.3 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 170.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + small_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.0016 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + large_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.67 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.019 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 35.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl similarity index 56% rename from test/spatial_reaction_systems/lattice_reaction_systems.jl rename to test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index a34b5eeeb2..7ad7125f99 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -1,195 +1,11 @@ +### Preparations ### + # Fetch packages. -using Catalyst, JumpProcesses, OrdinaryDiffEq +using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test -using Graphs - -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - -### Helper Functions ### - -# Generates ranomised intiial condition or paraemter values. -rand_v_vals(grid) = rand(nv(grid)) -rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x -rand_e_vals(grid) = rand(ne(grid)) -rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x -function make_u0_matrix(value_map, vals, symbols) - (length(symbols) == 0) && (return zeros(0, length(vals))) - d = Dict(value_map) - return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] -end -# Gets a symbol list of spatial parameters. -function spatial_param_syms(lrs::LatticeReactionSystem) - ModelingToolkit.getname.(diffusion_parameters(lrs)) -end - -# Converts to integer value (for JumpProcess simulations). -function make_values_int(values::Vector{<:Pair}) - [val[1] => round.(Int64, val[2]) for val in values] -end -make_values_int(values::Matrix{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] - -### Declares Models ### - -# Small non-stiff system. -SIR_system = @reaction_network begin - α, S + I --> 2I - β, I --> R -end -SIR_p = [:α => 0.1 / 1000, :β => 0.01] -SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] - -SIR_dif_S = DiffusionReaction(:dS, :S) -SIR_dif_I = DiffusionReaction(:dI, :I) -SIR_dif_R = DiffusionReaction(:dR, :R) -SIR_srs_1 = [SIR_dif_S] -SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] - -# Small non-stiff system. -binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) -binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] -binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] - -# Mid-sized non-stiff system. -CuH_Amination_system = @reaction_network begin - 10.0^kp1, CuoAc + Ligand --> CuoAcLigand - 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc - 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand - 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene - 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand - 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E - 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane - 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand - 10.0^kdc, CuHLigand + CuHLigand --> Decomposition -end -CuH_Amination_p = [ - :kp1 => 1.2, - :kp2 => -0.72, - :k1 => 0.57, - :k_1 => -3.5, - :k2 => -0.35, - :k_2 => -0.77, - :k3 => -0.025, - :kam => -2.6, - :kdc => -3.0, -] -CuH_Amination_u0 = [ - :CuoAc => 0.0065, - :Ligand => 0.0072, - :CuoAcLigand => 0.0, - :Silane => 0.65, - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, -] - -CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) -CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) -CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) -CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) -CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) -CuH_Amination_srs_1 = [CuH_Amination_diff_1] -CuH_Amination_srs_2 = [ - CuH_Amination_diff_1, - CuH_Amination_diff_2, - CuH_Amination_diff_3, - CuH_Amination_diff_4, - CuH_Amination_diff_5, -] - -# Small stiff system. -brusselator_system = @reaction_network begin - A, ∅ → X - 1, 2X + Y → 3X - B, X → Y - 1, X → ∅ -end -brusselator_p = [:A => 1.0, :B => 4.0] - -brusselator_dif_x = DiffusionReaction(:dX, :X) -brusselator_dif_y = DiffusionReaction(:dY, :Y) -brusselator_srs_1 = [brusselator_dif_x] -brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] - -# Mid-sized stiff system. -# Unsure about stifness, but non-spatial version oscillates for this parameter set. -sigmaB_system = @reaction_network begin - kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ - kDeg, vPp ⟶ phos - (kBw, kDw), 2w ⟷ w2 - (kB1, kD1), w2 + v ⟷ w2v - (kB2, kD2), w2v + v ⟷ w2v2 - kK1, w2v ⟶ w2 + vP - kK2, w2v2 ⟶ w2v + vP - (kB3, kD3), w2 + σB ⟷ w2σB - (kB4, kD4), w2σB + v ⟷ w2v + σB - (kB5, kD5), vP + phos ⟷ vPp - kP, vPp ⟶ v + phos - v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ σB - λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w - λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v -end -sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, - :kB4 => 1800, :kB5 => 3600, - :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, - :kP => 180, :kDeg => 0.7, - :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] -sigmaB_u0 = [ - :w => 1.0, - :w2 => 1.0, - :w2v => 1.0, - :v => 1.0, - :w2v2 => 1.0, - :vP => 1.0, - :σB => 1.0, - :w2σB => 1.0, - :vPp => 0.0, - :phos => 0.4, -] - -sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) -sigmaB_dif_w = DiffusionReaction(:Dw, :w) -sigmaB_dif_v = DiffusionReaction(:Dv, :v) -sigmaB_srs_1 = [sigmaB_dif_σB] -sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] - -### Declares Lattices ### - -# Grids. -very_small_2d_grid = Graphs.grid([2, 2]) -small_2d_grid = Graphs.grid([5, 5]) -medium_2d_grid = Graphs.grid([20, 20]) -large_2d_grid = Graphs.grid([100, 100]) - -small_3d_grid = Graphs.grid([5, 5, 5]) -medium_3d_grid = Graphs.grid([20, 20, 20]) -large_3d_grid = Graphs.grid([100, 100, 100]) - -# Paths. -short_path = path_graph(100) -long_path = path_graph(1000) - -# Unconnected graphs. -unconnected_graph = SimpleGraph(10) - -# Undirected cycle. -undirected_cycle = cycle_graph(49) - -# Directed cycle. -small_directed_cycle = cycle_graph(100) -large_directed_cycle = cycle_graph(1000) +# Fetch test networks. +include("../spatial_test_networks.jl") ### Test No Error During Runs ### for grid in [small_2d_grid, short_path, small_directed_cycle] @@ -620,202 +436,3 @@ let @test isapprox(J_hw_dense, J_aut_dense) @test isapprox(J_hw_sparse, J_aut_sparse) end - - -### Runtime Checks ### -# Current timings are taken from the SciML CI server. -# Current not used, simply here for reference. -# Useful when attempting to optimise workflow. - -# using BenchmarkTools -# runtime_reduction_margin = 10.0 -# -# # Small grid, small, non-stiff, system. -# let -# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) -# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] -# pV = SIR_p -# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] -# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.00060 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, small, non-stiff, system. -# let -# lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) -# u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] -# pV = SIR_p -# pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] -# oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.26 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, small, stiff, system. -# -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 0.17 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Medium grid, small, stiff, system. -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 2.3 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, small, stiff, system. -# let -# lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) -# u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] -# pV = brusselator_p -# pE = [:dX => 0.2] -# oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 170.0 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, mid-sized, non-stiff, system. -# let -# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, -# small_2d_grid) -# u0 = [ -# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :CuoAcLigand => 0.0, -# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :CuHLigand => 0.0, -# :SilaneOAc => 0.0, -# :Styrene => 0.16, -# :AlkylCuLigand => 0.0, -# :Amine_E => 0.39, -# :AlkylAmine => 0.0, -# :Cu_ELigand => 0.0, -# :E_Silane => 0.0, -# :Amine => 0.0, -# :Decomposition => 0.0, -# ] -# pV = CuH_Amination_p -# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.0016 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, mid-sized, non-stiff, system. -# let -# lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, -# large_2d_grid) -# u0 = [ -# :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), -# :CuoAcLigand => 0.0, -# :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :CuHLigand => 0.0, -# :SilaneOAc => 0.0, -# :Styrene => 0.16, -# :AlkylCuLigand => 0.0, -# :Amine_E => 0.39, -# :AlkylAmine => 0.0, -# :Cu_ELigand => 0.0, -# :E_Silane => 0.0, -# :Amine => 0.0, -# :Decomposition => 0.0, -# ] -# pV = CuH_Amination_p -# pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) -# @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) -# -# runtime_target = 0.67 -# runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 -# println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Small grid, mid-sized, stiff, system. -# let -# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) -# u0 = [ -# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vPp => 0.0, -# :phos => 0.4, -# ] -# pV = sigmaB_p -# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 0.019 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end -# -# # Large grid, mid-sized, stiff, system. -# let -# lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) -# u0 = [ -# :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), -# :vPp => 0.0, -# :phos => 0.4, -# ] -# pV = sigmaB_p -# pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] -# oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) -# @test SciMLBase.successful_retcode(solve(oprob, QNDF())) -# -# runtime_target = 35.0 -# runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 -# println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") -# @test runtime < runtime_reduction_margin * runtime_target -# end diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl new file mode 100644 index 0000000000..bf08aab518 --- /dev/null +++ b/test/spatial_test_networks.jl @@ -0,0 +1,190 @@ +### Fetch packages ### +using Catalyst, Graphs + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +### Helper Functions ### + +# Generates ranomised intiial condition or paraemter values. +rand_v_vals(grid) = rand(rng, nv(grid)) +rand_v_vals(grid, x::Number) = rand_v_vals(grid) * x +rand_e_vals(grid) = rand(rng, ne(grid)) +rand_e_vals(grid, x::Number) = rand_e_vals(grid) * x +function make_u0_matrix(value_map, vals, symbols) + (length(symbols) == 0) && (return zeros(0, length(vals))) + d = Dict(value_map) + return [(d[s] isa Vector) ? d[s][v] : d[s] for s in symbols, v in 1:length(vals)] +end + +# Gets a symbol list of spatial parameters. +function spatial_param_syms(lrs::LatticeReactionSystem) + ModelingToolkit.getname.(diffusion_parameters(lrs)) +end + +# Converts to integer value (for JumpProcess simulations). +function make_values_int(values::Vector{<:Pair}) + [val[1] => round.(Int64, val[2]) for val in values] +end +make_values_int(values::Matrix{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{<:Number}) = round.(Int64, values) +make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] + +### Declares Models ### + +# Small non-stiff system. +SIR_system = @reaction_network begin + α, S + I --> 2I + β, I --> R +end +SIR_p = [:α => 0.1 / 1000, :β => 0.01] +SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] + +SIR_dif_S = DiffusionReaction(:dS, :S) +SIR_dif_I = DiffusionReaction(:dI, :I) +SIR_dif_R = DiffusionReaction(:dR, :R) +SIR_srs_1 = [SIR_dif_S] +SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] + +# Small non-stiff system. +binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end +binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) +binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] +binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] + +# Mid-sized non-stiff system. +CuH_Amination_system = @reaction_network begin + 10.0^kp1, CuoAc + Ligand --> CuoAcLigand + 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc + 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand + 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene + 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand + 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E + 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane + 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand + 10.0^kdc, CuHLigand + CuHLigand --> Decomposition +end +CuH_Amination_p = [ + :kp1 => 1.2, + :kp2 => -0.72, + :k1 => 0.57, + :k_1 => -3.5, + :k2 => -0.35, + :k_2 => -0.77, + :k3 => -0.025, + :kam => -2.6, + :kdc => -3.0, +] +CuH_Amination_u0 = [ + :CuoAc => 0.0065, + :Ligand => 0.0072, + :CuoAcLigand => 0.0, + :Silane => 0.65, + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, +] + +CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) +CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) +CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) +CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) +CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) +CuH_Amination_srs_1 = [CuH_Amination_diff_1] +CuH_Amination_srs_2 = [ + CuH_Amination_diff_1, + CuH_Amination_diff_2, + CuH_Amination_diff_3, + CuH_Amination_diff_4, + CuH_Amination_diff_5, +] + +# Small stiff system. +brusselator_system = @reaction_network begin + A, ∅ → X + 1, 2X + Y → 3X + B, X → Y + 1, X → ∅ +end +brusselator_p = [:A => 1.0, :B => 4.0] + +brusselator_dif_x = DiffusionReaction(:dX, :X) +brusselator_dif_y = DiffusionReaction(:dY, :Y) +brusselator_srs_1 = [brusselator_dif_x] +brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] + +# Mid-sized stiff system. +# Unsure about stifness, but non-spatial version oscillates for this parameter set. +sigmaB_system = @reaction_network begin + kDeg, (w, w2, w2v, v, w2v2, vP, σB, w2σB) ⟶ ∅ + kDeg, vPp ⟶ phos + (kBw, kDw), 2w ⟷ w2 + (kB1, kD1), w2 + v ⟷ w2v + (kB2, kD2), w2v + v ⟷ w2v2 + kK1, w2v ⟶ w2 + vP + kK2, w2v2 ⟶ w2v + vP + (kB3, kD3), w2 + σB ⟷ w2σB + (kB4, kD4), w2σB + v ⟷ w2v + σB + (kB5, kD5), vP + phos ⟷ vPp + kP, vPp ⟶ v + phos + v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ σB + λW * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ w + λV * v0 * ((1 + F * σB) / (K + σB)), ∅ ⟶ v +end +sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, + :kB4 => 1800, :kB5 => 3600, + :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, + :kP => 180, :kDeg => 0.7, + :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] +sigmaB_u0 = [ + :w => 1.0, + :w2 => 1.0, + :w2v => 1.0, + :v => 1.0, + :w2v2 => 1.0, + :vP => 1.0, + :σB => 1.0, + :w2σB => 1.0, + :vPp => 0.0, + :phos => 0.4, +] + +sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) +sigmaB_dif_w = DiffusionReaction(:Dw, :w) +sigmaB_dif_v = DiffusionReaction(:Dv, :v) +sigmaB_srs_1 = [sigmaB_dif_σB] +sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] + +### Declares Lattices ### + +# Grids. +very_small_2d_grid = Graphs.grid([2, 2]) +small_2d_grid = Graphs.grid([5, 5]) +medium_2d_grid = Graphs.grid([20, 20]) +large_2d_grid = Graphs.grid([100, 100]) + +small_3d_grid = Graphs.grid([5, 5, 5]) +medium_3d_grid = Graphs.grid([20, 20, 20]) +large_3d_grid = Graphs.grid([100, 100, 100]) + +# Paths. +short_path = path_graph(100) +long_path = path_graph(1000) + +# Unconnected graphs. +unconnected_graph = SimpleGraph(10) + +# Undirected cycle. +undirected_cycle = cycle_graph(49) + +# Directed cycle. +small_directed_cycle = cycle_graph(100) +large_directed_cycle = cycle_graph(1000) \ No newline at end of file From 7eafc0ddc47698135e60a4799eec02a55364e76e Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 15:35:15 -0400 Subject: [PATCH 064/134] fix broadcasting issue --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 74f37deb73..9a24a5a74d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -119,7 +119,7 @@ end function is_spatial_param(p, lrs) hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. - return (any(isequal.(p, parameters(lrs.rs))) ? false : true) + return (any(isequal(p), parameters(lrs.rs)) ? false : true) end ### Processes Input u0 & p ### From 66408ecea12f827b358854a94ca5fa12665c5e48 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 16:25:13 -0400 Subject: [PATCH 065/134] reorder files. --- .../lattice_reaction_systems.jl} | 196 ----------------- .../spatial_ODE_systems.jl | 197 ++++++++++++++++++ 2 files changed, 197 insertions(+), 196 deletions(-) rename src/{lattice_reaction_system_diffusion.jl => spatial_reaction_systems/lattice_reaction_systems.jl} (58%) create mode 100644 src/spatial_reaction_systems/spatial_ODE_systems.jl diff --git a/src/lattice_reaction_system_diffusion.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl similarity index 58% rename from src/lattice_reaction_system_diffusion.jl rename to src/spatial_reaction_systems/lattice_reaction_systems.jl index 9a24a5a74d..8de4e763d7 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -225,202 +225,6 @@ function compute_diffusion_rates(rate_law::Num, for p in relevant_parameters)) for idxE in 1:nE] end -### Spatial ODE Functor Structures ### - -# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. -struct LatticeDiffusionODEf{R,S,T} - ofunc::R - nC::Int64 - nS::Int64 - pC::Vector{Vector{Float64}} - work_pC::Vector{Float64} - enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} - diffusion_rates::Vector{S} - leaving_rates::Matrix{Float64} - enumerated_edges::T - - function LatticeDiffusionODEf(ofunc::R, pC, diffusion_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} - leaving_rates = zeros(length(diffusion_rates), lrs.nC) - for (s_idx, rates) in enumerate(last.(diffusion_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) - - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) - end - work_pC = zeros(lrs.nC) - enumerated_pC_idx_types = enumerate(length.(pC) .== 1) - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, diffusion_rates, leaving_rates, enumerated_edges) - end -end - -# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. -struct LatticeDiffusionODEjac{S,T} - ofunc::S - nC::Int64 - nS::Int64 - pC::Vector{Vector{Float64}} - work_pC::Vector{Float64} - enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} - sparse::Bool - jac_values::T - - function LatticeDiffusionODEjac(ofunc::S, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} - work_pC = zeros(lrs.nC) - enumerated_pC_idx_types = enumerate(length.(pC) .== 1) - jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) - new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, sparse, jac_values) - end -end - -### ODEProblem ### - -# Creates an ODEProblem from a LatticeReactionSystem. -function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - ofun = build_odefunction(lrs, pC, pD, jac, sparse) - return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) -end - -# Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) - # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] - for diff_rates in diffusion_rates_speciesmap] - - f = LatticeDiffusionODEf(ofunc, pC, diffusion_rates, lrs) - jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, - lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? LatticeDiffusionODEjac(ofunc, pC, lrs, jac_prototype, sparse) : nothing - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) -end - -# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - diffusion_rates, lrs::LatticeReactionSystem; - set_nonzero = false) - diff_species = first.(diffusion_rates) - # Gets list of indexes for species that diffuse, but are invovled in no other reaction. - only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] - - # Declares sparse array content. - J_colptr = fill(1, lrs.nC * lrs.nS + 1) - J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_rates)) - J_rowval = fill(0, length(J_nzval)) - - # Finds filled elements. - for comp in 1:(lrs.nC), s in 1:(lrs.nS) - col_idx = get_index(comp, s, lrs.nS) - - # Column values. - local_elements = in(s, diff_species) * - (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) - diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) - J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements - - # Row values. - rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ - (comp - 1) * lrs.nS - if in(s, diff_species) - # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. - diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s - split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) - isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) - rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, - diffusion_rows[split_idx:end]) - if only_diff[s] - split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) - isnothing(split_idx) && (split_idx = length(rows) + 1) - insert!(rows, split_idx, get_index(comp, s, lrs.nS)) - end - end - J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows - end - - # Set element values. - if !set_nonzero - J_nzval .= 1.0 - else - for (s_idx, (s, rates)) in enumerate(diffusion_rates), - (e_idx, edge) in enumerate(edges(lrs.lattice)) - - col_start = J_colptr[get_index(edge.src, s, lrs.nS)] - col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 - column_view = @view J_rowval[col_start:col_end] - - # Updates the source value. - val_idx_src = col_start + - findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 - J_nzval[val_idx_src] -= get_component_value(rates, e_idx) - - # Updates the destination value. - val_idx_dst = col_start + - findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 - J_nzval[val_idx_dst] += get_component_value(rates, e_idx) - end - end - - return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) -end - -# Defines the forcing functors effect on the (spatial) ODE system. -function (f_func::LatticeDiffusionODEf)(du, u, p, t) - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(f_func.nC) - f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), - (@view u[get_indexes(comp_i, f_func.nS)]), - view_pC_vector!(f_func.work_pC, p, comp_i, f_func.enumerated_pC_idx_types), t) - end - - # Updates for spatial diffusion reactions. - for (s_idx, (s, rates)) in enumerate(f_func.diffusion_rates) - for comp_i::Int64 in 1:(f_func.nC) - du[get_index(comp_i, s, f_func.nS)] -= f_func.leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, - f_func.nS)] - end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges - du[get_index(edge.dst, s, f_func.nS)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, - f_func.nS)] - end - end -end - -# Defines the jacobian functors effect on the (spatial) ODE system. -function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) - # Because of weird stuff where the Jacobian is not reset that I don't understand properly. - reset_J_vals!(J) - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(jac_func.nC) - jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.nS), - get_indexes(comp_i, jac_func.nS)]), - (@view u[get_indexes(comp_i, jac_func.nS)]), - view_pC_vector!(jac_func.work_pC, p, comp_i, jac_func.enumerated_pC_idx_types), t) - end - - # Updates for the spatial reactions. - add_diff_J_vals!(J, jac_func) -end -# Resets the jacobian matrix within a jac call. -reset_J_vals!(J::Matrix) = (J .= 0.0) -reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) -# Updates the jacobian matrix with the difussion values. -add_diff_J_vals!(J::SparseMatrixCSC, jac_func::LatticeDiffusionODEjac) = (J.nzval .+= jac_func.jac_values) -add_diff_J_vals!(J::Matrix, jac_func::LatticeDiffusionODEjac) = (J .+= jac_func.jac_values) - ### Accessing State & Parameter Array Values ### # Gets the index in the u array of species s in compartment comp (when their are nS species). diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl new file mode 100644 index 0000000000..156a9169d2 --- /dev/null +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -0,0 +1,197 @@ + + +### Spatial ODE Functor Structures ### + +# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +struct LatticeDiffusionODEf{R,S,T} + ofunc::R + nC::Int64 + nS::Int64 + pC::Vector{Vector{Float64}} + work_pC::Vector{Float64} + enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} + diffusion_rates::Vector{S} + leaving_rates::Matrix{Float64} + enumerated_edges::T + + function LatticeDiffusionODEf(ofunc::R, pC, diffusion_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} + leaving_rates = zeros(length(diffusion_rates), lrs.nC) + for (s_idx, rates) in enumerate(last.(diffusion_rates)), + (e_idx, e) in enumerate(edges(lrs.lattice)) + + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + end + work_pC = zeros(lrs.nC) + enumerated_pC_idx_types = enumerate(length.(pC) .== 1) + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, diffusion_rates, leaving_rates, enumerated_edges) + end +end + +# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +struct LatticeDiffusionODEjac{S,T} + ofunc::S + nC::Int64 + nS::Int64 + pC::Vector{Vector{Float64}} + work_pC::Vector{Float64} + enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} + sparse::Bool + jac_values::T + + function LatticeDiffusionODEjac(ofunc::S, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} + work_pC = zeros(lrs.nC) + enumerated_pC_idx_types = enumerate(length.(pC) .== 1) + jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) + new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, sparse, jac_values) + end +end + +### ODEProblem ### + +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; + jac = true, sparse = jac, kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + ofun = build_odefunction(lrs, pC, pD, jac, sparse) + return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) +end + +# Builds an ODEFunction for a spatial ODEProblem. +function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) + # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) + diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) + diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] + for diff_rates in diffusion_rates_speciesmap] + + f = LatticeDiffusionODEf(ofunc, pC, diffusion_rates, lrs) + jac_prototype = (use_jac || sparse) ? + build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, + lrs; set_nonzero = use_jac) : nothing + jac = use_jac ? LatticeDiffusionODEjac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) +end + +# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + diffusion_rates, lrs::LatticeReactionSystem; + set_nonzero = false) + diff_species = first.(diffusion_rates) + # Gets list of indexes for species that diffuse, but are invovled in no other reaction. + only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) + for s in 1:(lrs.nS)] + + # Declares sparse array content. + J_colptr = fill(1, lrs.nC * lrs.nS + 1) + J_nzval = fill(0.0, + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_rates)) + J_rowval = fill(0, length(J_nzval)) + + # Finds filled elements. + for comp in 1:(lrs.nC), s in 1:(lrs.nS) + col_idx = get_index(comp, s, lrs.nS) + + # Column values. + local_elements = in(s, diff_species) * + (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) + diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements + + # Row values. + rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ + (comp - 1) * lrs.nS + if in(s, diff_species) + # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. + diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s + split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) + isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) + rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, + diffusion_rows[split_idx:end]) + if only_diff[s] + split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) + isnothing(split_idx) && (split_idx = length(rows) + 1) + insert!(rows, split_idx, get_index(comp, s, lrs.nS)) + end + end + J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows + end + + # Set element values. + if !set_nonzero + J_nzval .= 1.0 + else + for (s_idx, (s, rates)) in enumerate(diffusion_rates), + (e_idx, edge) in enumerate(edges(lrs.lattice)) + + col_start = J_colptr[get_index(edge.src, s, lrs.nS)] + col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 + column_view = @view J_rowval[col_start:col_end] + + # Updates the source value. + val_idx_src = col_start + + findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 + J_nzval[val_idx_src] -= get_component_value(rates, e_idx) + + # Updates the destination value. + val_idx_dst = col_start + + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 + J_nzval[val_idx_dst] += get_component_value(rates, e_idx) + end + end + + return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) +end + +# Defines the forcing functors effect on the (spatial) ODE system. +function (f_func::LatticeDiffusionODEf)(du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(f_func.nC) + f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), + (@view u[get_indexes(comp_i, f_func.nS)]), + view_pC_vector!(f_func.work_pC, p, comp_i, f_func.enumerated_pC_idx_types), t) + end + + # Updates for spatial diffusion reactions. + for (s_idx, (s, rates)) in enumerate(f_func.diffusion_rates) + for comp_i::Int64 in 1:(f_func.nC) + du[get_index(comp_i, s, f_func.nS)] -= f_func.leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, s, + f_func.nS)] + end + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges + du[get_index(edge.dst, s, f_func.nS)] += get_component_value(rates, e_idx) * + u[get_index(edge.src, s, + f_func.nS)] + end + end +end + +# Defines the jacobian functors effect on the (spatial) ODE system. +function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) + # Because of weird stuff where the Jacobian is not reset that I don't understand properly. + reset_J_vals!(J) + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(jac_func.nC) + jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.nS), + get_indexes(comp_i, jac_func.nS)]), + (@view u[get_indexes(comp_i, jac_func.nS)]), + view_pC_vector!(jac_func.work_pC, p, comp_i, jac_func.enumerated_pC_idx_types), t) + end + + # Updates for the spatial reactions. + add_diff_J_vals!(J, jac_func) +end +# Resets the jacobian matrix within a jac call. +reset_J_vals!(J::Matrix) = (J .= 0.0) +reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) +# Updates the jacobian matrix with the difussion values. +add_diff_J_vals!(J::SparseMatrixCSC, jac_func::LatticeDiffusionODEjac) = (J.nzval .+= jac_func.jac_values) +add_diff_J_vals!(J::Matrix, jac_func::LatticeDiffusionODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file From eb79f6675ad8aae1f8be0eb12b2aac2de96eba7e Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 16:31:40 -0400 Subject: [PATCH 066/134] Move spatial reactions to separate file --- .../lattice_reaction_systems.jl | 79 +++---------------- .../spatial_reactions.jl | 54 +++++++++++++ 2 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 src/spatial_reaction_systems/spatial_reactions.jl diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 8de4e763d7..0976fb6d8b 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,56 +1,3 @@ -### Diffusion Reaction Structure. ### - -# Implements the diffusionparameter metadata field. -struct DiffusionParameter end -Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter - -isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) -function isdiffusionparameter(x, default = false) - p = Symbolics.getparent(x, nothing) - p === nothing || (x = p) - Symbolics.getmetadata(x, DiffusionParameter, default) -end - -# Abstract spatial reaction structures. -abstract type AbstractSpatialReaction end - -# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. -# Currently only permit constant rates. -struct DiffusionReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currently only constants supported""" - rate::Num - """The species that is subject to difusion.""" - species::Num - """A symbol representation of the species that is subject to difusion.""" - species_sym::Symbol # Required for identification in certain cases. - - # Creates a diffusion reaction. - function DiffusionReaction(rate::Num, species::Num) - new(rate, species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Number, species::Num) - new(Num(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Symbol, species::Num) - new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Num, species::Symbol) - new(rate, Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Number, species::Symbol) - new(Num(rate), Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Symbol, species::Symbol) - new(Symbolics.variable(rate), Symbolics.variable(species), species) - end -end -# Creates a vector of DiffusionReactions. -function diffusion_reactions(diffusion_reactions) - [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] -end -# Gets the parameters in a diffusion reaction. -ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) - ### Lattice Reaction Network Structure ### # Desribes a spatial reaction network over a graph. struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs @@ -68,7 +15,7 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p nE::Int64 """The number of species.""" nS::Int64 - """Whenever the initial input was a di graph.""" + """Whenever the initial input was a digraph.""" init_digraph::Bool function LatticeReactionSystem(rs::ReactionSystem, @@ -77,19 +24,19 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph) end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); - init_digraph = false) - end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reaction::AbstractSpatialReaction, - lattice::Graphs.AbstractGraph) - return LatticeReactionSystem(rs, [spatial_reaction], lattice) - end end -# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). +function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); + init_digraph = false) +end +function LatticeReactionSystem(rs::ReactionSystem, + spatial_reaction::AbstractSpatialReaction, + lattice::Graphs.AbstractGraph) + return LatticeReactionSystem(rs, [spatial_reaction], lattice) +end +# Converts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). function graph_to_digraph(g1) g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), reverse.(edges(g1)))), :, 1)[:]) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl new file mode 100644 index 0000000000..a9b734f625 --- /dev/null +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -0,0 +1,54 @@ +### Spatial Reaction Structures ### + +# Abstract spatial reaction structures. +abstract type AbstractSpatialReaction end + +### Transport Reaction Structures ### + +# Implements the transportparameter metadata field. +struct TransportParameter end +Symbolics.option_to_metadata_type(::Val{:transportparameter}) = TransportParameter + +istransportparameter(x::Num, args...) = istransportparameter(Symbolics.unwrap(x), args...) +function istransportparameter(x, default = false) + p = Symbolics.getparent(x, nothing) + p === nothing || (x = p) + Symbolics.getmetadata(x, TransportParameter, default) +end + +# A transport reaction. These are simple to hanlde, and should cover most types of spatial reactions. +# Currently only permit constant rates. +struct TransportReaction <: AbstractSpatialReaction + """The rate function (excluding mass action terms). Currently only constants supported""" + rate::Num + """The species that is subject to difusion.""" + species::Num + """A symbol representation of the species that is subject to difusion.""" + species_sym::Symbol # Required for identification in certain cases. + + # Creates a diffusion reaction. + function TransportReaction(rate::Num, species::Num) + new(rate, species, ModelingToolkit.getname(species)) + end + function TransportReaction(rate::Number, species::Num) + new(Num(rate), species, ModelingToolkit.getname(species)) + end + function TransportReaction(rate::Symbol, species::Num) + new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) + end + function TransportReaction(rate::Num, species::Symbol) + new(rate, Symbolics.variable(species), species) + end + function TransportReaction(rate::Number, species::Symbol) + new(Num(rate), Symbolics.variable(species), species) + end + function TransportReaction(rate::Symbol, species::Symbol) + new(Symbolics.variable(rate), Symbolics.variable(species), species) + end +end +# Creates a vector of TransportReactions. +function transport_reactions(transport_reactions) + [TransportReaction(dr[1], dr[2]) for dr in transport_reactions] +end +# Gets the parameters in a transport reaction. +ModelingToolkit.parameters(dr::TransportReaction) = Symbolics.get_variables(dr.rate) \ No newline at end of file From 11507b5f5719934e35a988bc365b82527a39e424 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 17:24:37 -0400 Subject: [PATCH 067/134] wip --- .../lattice_reaction_systems.jl | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 0976fb6d8b..cdf81e7229 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -17,6 +17,10 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p nS::Int64 """Whenever the initial input was a digraph.""" init_digraph::Bool + """Species that may move spatially.""" + spatial_species::Bool + """Parameters which values are tied to edges (adjacencies)..""" + edge_parameters::Bool function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, @@ -25,17 +29,16 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p length(species(rs)), init_digraph) end end -function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); - init_digraph = false) +function LatticeReactionSystem(rs, srs, lat::SimpleGraph) + return LatticeReactionSystem(rs, srs, graph_to_digraph(lat); init_digraph = false) end -function LatticeReactionSystem(rs::ReactionSystem, - spatial_reaction::AbstractSpatialReaction, - lattice::Graphs.AbstractGraph) - return LatticeReactionSystem(rs, [spatial_reaction], lattice) +function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat) + return LatticeReactionSystem(rs, [ss], lat) end +function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat::SimpleGraph) + return LatticeReactionSystem(rs, [sr], graph_to_digraph(lat); init_digraph = false) +end + # Converts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). function graph_to_digraph(g1) g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), @@ -43,6 +46,21 @@ function graph_to_digraph(g1) add_vertices!(g2, nv(g1) - nv(g2)) return g2 end + +### Lattice ReactionSystem Getters ### + +# Get all species. +species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spatial_species]) +# Get all species that may be transported. +spatial_species(lrs::LatticeReactionSystem) = lrs.spatial_species + +# Get all parameters. +ModelingToolkit.parameters(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.edge_parameters]) +# Get all parameters which values are tied to vertexes (compartments). +vertex_parameters(lrs::LatticeReactionSystem) = setdiff(parameters(lrs), edge_parameters(lrs)) +# Get all parameters which values are tied to edges (adjacencies). +edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters + # Gets the species of a lattice reaction system. species(lrs::LatticeReactionSystem) = species(lrs.rs) function diffusion_species(lrs::LatticeReactionSystem) From 0b7a426711205139415e67c64e242c981edab7ea Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 08:47:32 -0400 Subject: [PATCH 068/134] updates --- src/Catalyst.jl | 15 +- .../lattice_reaction_systems.jl | 115 +++++++-------- .../spatial_ODE_systems.jl | 136 +++++++++--------- .../spatial_reactions.jl | 45 ++++-- test/runtests.jl | 1 + .../lattice_reaction_systems.jl | 0 6 files changed, 160 insertions(+), 152 deletions(-) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index e0c380036f..e26b011244 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -70,12 +70,6 @@ export @reaction_network, @add_reactions, @reaction, @species include("registered_functions.jl") export mm, mmr, hill, hillr, hillar -# spatial reaction networks -include("lattice_reaction_system_diffusion.jl") -export DiffusionReaction, diffusion_reactions, isdiffusionparameter -export LatticeReactionSystem -export compartment_parameters, diffusion_parameters, diffusion_species - # functions to query network properties include("networkapi.jl") export species, nonspecies, reactionparams, reactions, speciesmap, paramsmap @@ -105,4 +99,13 @@ include("compound.jl") export @compound export components, iscompound, coefficients +# spatial reactions +include("spatial_reaction_systems/spatial_reactions.jl") +export TransportReaction, transport_reactions, isedgeparameter + +# lattice reaction systems +include("spatial_reaction_systems/lattice_reaction_systems.jl") +export LatticeReactionSystem +export spatial_species, vertex_parameters, edge_parameters + end # module diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index cdf81e7229..f63a6cea12 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -10,7 +10,7 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p # Derrived values. """The number of compartments.""" - nC::Int64 + nV::Int64 """The number of edges.""" nE::Int64 """The number of species.""" @@ -18,22 +18,26 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a digraph.""" init_digraph::Bool """Species that may move spatially.""" - spatial_species::Bool + spatial_species::Vector{BasicSymbolic{Real}} """Parameters which values are tied to edges (adjacencies)..""" - edge_parameters::Bool + edge_parameters::Vector{BasicSymbolic{Real}} function LatticeReactionSystem(rs::ReactionSystem, spatial_reactions::Vector{<:AbstractSpatialReaction}, lattice::DiGraph; init_digraph = true) - return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), - length(species(rs)), init_digraph) + spatial_species = unique(spatial_species.(spatial_reactions)) + rs_edge_parameters = filter(isedgeparameter, parameters(rs)) + srs_edge_parameters = setdiff(vcat(parameters.(spatial_reactions)), parameters(rs)) + edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) + foreach(sr -> check_spatial_reaction_validity(rs, rs), spatial_reactions) + return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph, spatial_species, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) return LatticeReactionSystem(rs, srs, graph_to_digraph(lat); init_digraph = false) end function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat) - return LatticeReactionSystem(rs, [ss], lat) + return LatticeReactionSystem(rs, [sr], lat) end function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat::SimpleGraph) return LatticeReactionSystem(rs, [sr], graph_to_digraph(lat); init_digraph = false) @@ -61,50 +65,27 @@ vertex_parameters(lrs::LatticeReactionSystem) = setdiff(parameters(lrs), edge_pa # Get all parameters which values are tied to edges (adjacencies). edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters -# Gets the species of a lattice reaction system. -species(lrs::LatticeReactionSystem) = species(lrs.rs) -function diffusion_species(lrs::LatticeReactionSystem) - filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), - species(lrs.rs)) -end - -# Gets the parameters in a lattice reaction system. -function ModelingToolkit.parameters(lrs::LatticeReactionSystem) - unique(vcat(parameters(lrs.rs), - Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) -end -function compartment_parameters(lrs::LatticeReactionSystem) - filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) -end -function diffusion_parameters(lrs::LatticeReactionSystem) - filter(p -> is_spatial_param(p, lrs), parameters(lrs)) -end - -# Checks whenever a parameter is a spatial parameter or not. -function is_spatial_param(p, lrs) - hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && - (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. - return (any(isequal(p), parameters(lrs.rs)) ? false : true) -end +# Checks if a lattice reaction system is a pure (linear) transport reaction system. +is_transport_system(lrs::LatticeReactionSystem) = all(typeof.(lrs.spatial_reactions) .== TransportReaction) ### Processes Input u0 & p ### # From u0 input, extracts their values and store them in the internal format. -function lattice_process_u0(u0_in, u0_symbols, nC) - u0 = lattice_process_input(u0_in, u0_symbols, nC) - check_vector_lengths(u0, nC) - expand_component_values(u0, nC) +function lattice_process_u0(u0_in, u0_symbols, nV) + u0 = lattice_process_input(u0_in, u0_symbols, nV) + check_vector_lengths(u0, nV) + expand_component_values(u0, nV) end # From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) - pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) - pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) - pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) - lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) - check_vector_lengths(pC, lrs.nC) - check_vector_lengths(pD, lrs.nE) - return pC, pD + pV_in, pE_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) + pV = lattice_process_input(pV_in, p_comp_symbols, lrs.nV) + pE = lattice_process_input(pE_in, p_diff_symbols, lrs.nE) + lrs.init_digraph || foreach(idx -> duplicate_spat_params!(pE, idx, lrs), 1:length(pE)) + check_vector_lengths(pV, lrs.nV) + check_vector_lengths(pE, lrs.nE) + return pV, pE end # Splits parameters into those for the compartments and those for the connections. @@ -114,14 +95,14 @@ function split_parameters(ps::Vector{<:Number}, args...) end function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, p_diff_symbols::Vector) - pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] - pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] - (sum(length.([pC_in, pD_in])) != length(ps)) && - error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") - return pC_in, pD_in + pV_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] + pE_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] + (sum(length.([pV_in, pE_in])) != length(ps)) && + error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pV_in, pE_in]))))") + return pV_in, pE_in end -# If the input is given in a map form, teh vector needs sorting and the first value removed. +# If the input is given in a map form, the vector needs sorting and the first value removed. function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") @@ -147,36 +128,36 @@ function check_vector_lengths(input::Vector{<:Vector}, n) error("Some inputs where given values of inappropriate length.") end -# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. -function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, +# For spatial parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, expand so that the same value are given for both values on the edge. +function duplicate_spat_params!(pE::Vector{Vector{Float64}}, idx::Int64, lrs::LatticeReactionSystem) - (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) + (2length(pE[idx]) == lrs.nE) && (pE[idx] = [p_val for p_val in pE[idx] for _ in 1:2]) end # For a set of input values on the given forms, and their symbolics, convert into a dictionary. vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) # Produces a dictionary with all parameter values. -function param_dict(pC, pD, lrs) - merge(vals_to_dict(compartment_parameters(lrs), pC), - vals_to_dict(diffusion_parameters(lrs), pD)) +function param_dict(pV, pE, lrs) + merge(vals_to_dict(compartment_parameters(lrs), pV), + vals_to_dict(diffusion_parameters(lrs), pE)) end -# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, +# Computes the spatial rates and stores them in a format (Dictionary of species index to rates across all edges). +function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, + pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - param_value_dict = param_dict(pC, pD, lrs) - return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), + param_value_dict = param_dict(pV, pE, lrs) + return [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) - for s in diffusion_species(lrs)] + for s in spatial_species(lrs)] end -function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) +function get_spatial_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), lrs.spatial_reactions) (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. return rates[1].rate end -function compute_diffusion_rates(rate_law::Num, +function compute_spatial_rates(rate_law::Num, param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) relevant_parameters = Symbolics.get_variables(rate_law) if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) @@ -220,12 +201,12 @@ end function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) end -# Creates a view of the pC vector at a given comaprtment. -function view_pC_vector!(work_pC, pC, comp, enumerated_pC_idx_types) - for (idx,loc_type) in enumerated_pC_idx_types - work_pC[idx] = (loc_type ? pC[idx][1] : pC[idx][comp]) +# Creates a view of the pV vector at a given comaprtment. +function view_pV_vector!(work_pV, pV, comp, enumerated_pV_idx_types) + for (idx,loc_type) in enumerated_pV_idx_types + work_pV[idx] = (loc_type ? pV[idx][1] : pV[idx][comp]) end - return work_pC + return work_pV end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 156a9169d2..57c13c8218 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,49 +1,47 @@ - - ### Spatial ODE Functor Structures ### -# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +# Functor structure containg the information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeDiffusionODEf{R,S,T} ofunc::R - nC::Int64 + nV::Int64 nS::Int64 - pC::Vector{Vector{Float64}} - work_pC::Vector{Float64} - enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} - diffusion_rates::Vector{S} + pV::Vector{Vector{Float64}} + work_pV::Vector{Float64} + enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} + spatial_rates::Vector{S} leaving_rates::Matrix{Float64} enumerated_edges::T - function LatticeDiffusionODEf(ofunc::R, pC, diffusion_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} - leaving_rates = zeros(length(diffusion_rates), lrs.nC) - for (s_idx, rates) in enumerate(last.(diffusion_rates)), + function LatticeDiffusionODEf(ofunc::R, pV, spatial_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} + leaving_rates = zeros(length(spatial_rates), lrs.nV) + for (s_idx, rates) in enumerate(last.(spatial_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) end - work_pC = zeros(lrs.nC) - enumerated_pC_idx_types = enumerate(length.(pC) .== 1) + work_pV = zeros(lrs.nV) + enumerated_pV_idx_types = enumerate(length.(pV) .== 1) enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, diffusion_rates, leaving_rates, enumerated_edges) + new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nV, lrs.nS, pV, work_pV, enumerated_pV_idx_types, spatial_rates, leaving_rates, enumerated_edges) end end -# Functor structure containg the information for the forcing function of a spatial ODE with diffusion on a lattice. +# Functor structure containg the information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeDiffusionODEjac{S,T} ofunc::S - nC::Int64 + nV::Int64 nS::Int64 - pC::Vector{Vector{Float64}} - work_pC::Vector{Float64} - enumerated_pC_idx_types::Base.Iterators.Enumerate{BitVector} + pV::Vector{Vector{Float64}} + work_pV::Vector{Float64} + enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} sparse::Bool jac_values::T - function LatticeDiffusionODEjac(ofunc::S, pC, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} - work_pC = zeros(lrs.nC) - enumerated_pC_idx_types = enumerate(length.(pC) .== 1) + function LatticeDiffusionODEjac(ofunc::S, pV, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} + work_pV = zeros(lrs.nV) + enumerated_pV_idx_types = enumerate(length.(pV) .== 1) jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) - new{S,typeof(jac_values)}(ofunc, lrs.nC, lrs.nS, pC, work_pC, enumerated_pC_idx_types, sparse, jac_values) + new{S,typeof(jac_values)}(ofunc, lrs.nV, lrs.nS, pV, work_pV, enumerated_pV_idx_types, sparse, jac_values) end end @@ -53,68 +51,68 @@ end function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; jac = true, sparse = jac, kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - ofun = build_odefunction(lrs, pC, pD, jac, sparse) - return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) + is_transport_system(lrs) || error("Currently lattice ODE simulations only supported when all spatial reactions are transport reactions.") + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nV) + pV, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(edge_parameters(lrs)), lrs) + ofun = build_odefunction(lrs, pV, pD, jac, sparse) + return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, +function build_odefunction(lrs::LatticeReactionSystem, pV::Vector{Vector{Float64}}, pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) - # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. + # Prepeares (non-spatial) ODE functions and list of spatially moving species and their rates. ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] - for diff_rates in diffusion_rates_speciesmap] + spatial_rates_speciesmap = compute_all_spatial_rates(pV, pD, lrs) + spatial_rates = [findfirst(isequal(spat_rates[1]), states(lrs.rs)) => spat_rates[2] + for spat_rates in spatial_rates_speciesmap] - f = LatticeDiffusionODEf(ofunc, pC, diffusion_rates, lrs) + f = LatticeDiffusionODEf(ofunc, pV, spatial_rates, lrs) jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, + build_jac_prototype(ofunc_sparse.jac_prototype, spatial_rates, lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? LatticeDiffusionODEjac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + jac = use_jac ? LatticeDiffusionODEjac(ofunc, pV, lrs, jac_prototype, sparse) : nothing return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) end # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - diffusion_rates, lrs::LatticeReactionSystem; +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, spatial_rates, lrs::LatticeReactionSystem; set_nonzero = false) - diff_species = first.(diffusion_rates) - # Gets list of indexes for species that diffuse, but are invovled in no other reaction. - only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) + spat_species = first.(spatial_rates) + # Gets list of indexes for species that move spatially, but are invovled in no other reaction. + only_spat = [(s in spat_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] # Declares sparse array content. - J_colptr = fill(1, lrs.nC * lrs.nS + 1) + J_colptr = fill(1, lrs.nV * lrs.nS + 1) J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_rates)) + lrs.nV * (nnz(ns_jac_prototype) + count(only_spat)) + + length(edges(lrs.lattice)) * length(spatial_rates)) J_rowval = fill(0, length(J_nzval)) # Finds filled elements. - for comp in 1:(lrs.nC), s in 1:(lrs.nS) + for comp in 1:(lrs.nV), s in 1:(lrs.nS) col_idx = get_index(comp, s, lrs.nS) # Column values. - local_elements = in(s, diff_species) * - (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) - diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) - J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements + local_elements = in(s, spat_species) * + (length(lrs.lattice.fadjlist[comp]) + only_spat[s]) + spatial_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + spatial_elements_elements # Row values. rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ (comp - 1) * lrs.nS - if in(s, diff_species) - # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. - diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s - split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) - isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) - rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, - diffusion_rows[split_idx:end]) - if only_diff[s] + if in(s, spat_species) + # Finds the location of the spatial_elements elements, and inserts the elements from the non-spatial part into this. + spatial_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s + split_idx = isempty(rows) ? 1 : findfirst(spatial_rows .> rows[1]) + isnothing(split_idx) && (split_idx = length(spatial_rows) + 1) + rows = vcat(spatial_rows[1:(split_idx - 1)], rows, + spatial_rows[split_idx:end]) + if only_spat[s] split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) isnothing(split_idx) && (split_idx = length(rows) + 1) insert!(rows, split_idx, get_index(comp, s, lrs.nS)) @@ -127,7 +125,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, if !set_nonzero J_nzval .= 1.0 else - for (s_idx, (s, rates)) in enumerate(diffusion_rates), + for (s_idx, (s, rates)) in enumerate(spatial_rates), (e_idx, edge) in enumerate(edges(lrs.lattice)) col_start = J_colptr[get_index(edge.src, s, lrs.nS)] @@ -146,21 +144,21 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end end - return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) + return SparseMatrixCSC(lrs.nS * lrs.nV, lrs.nS * lrs.nV, J_colptr, J_rowval, J_nzval) end # Defines the forcing functors effect on the (spatial) ODE system. function (f_func::LatticeDiffusionODEf)(du, u, p, t) # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(f_func.nC) + for comp_i::Int64 in 1:(f_func.nV) f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), (@view u[get_indexes(comp_i, f_func.nS)]), - view_pC_vector!(f_func.work_pC, p, comp_i, f_func.enumerated_pC_idx_types), t) + view_pV_vector!(f_func.work_pV, p, comp_i, f_func.enumerated_pV_idx_types), t) end - # Updates for spatial diffusion reactions. - for (s_idx, (s, rates)) in enumerate(f_func.diffusion_rates) - for comp_i::Int64 in 1:(f_func.nC) + # Updates for spatial reactions. + for (s_idx, (s, rates)) in enumerate(f_func.spatial_rates) + for comp_i::Int64 in 1:(f_func.nV) du[get_index(comp_i, s, f_func.nS)] -= f_func.leaving_rates[s_idx, comp_i] * u[get_index(comp_i, s, f_func.nS)] @@ -179,19 +177,19 @@ function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) reset_J_vals!(J) # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(jac_func.nC) + for comp_i::Int64 in 1:(jac_func.nV) jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.nS), get_indexes(comp_i, jac_func.nS)]), (@view u[get_indexes(comp_i, jac_func.nS)]), - view_pC_vector!(jac_func.work_pC, p, comp_i, jac_func.enumerated_pC_idx_types), t) + view_pV_vector!(jac_func.work_pV, p, comp_i, jac_func.enumerated_pV_idx_types), t) end # Updates for the spatial reactions. - add_diff_J_vals!(J, jac_func) + add_spat_J_vals!(J, jac_func) end # Resets the jacobian matrix within a jac call. reset_J_vals!(J::Matrix) = (J .= 0.0) reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) # Updates the jacobian matrix with the difussion values. -add_diff_J_vals!(J::SparseMatrixCSC, jac_func::LatticeDiffusionODEjac) = (J.nzval .+= jac_func.jac_values) -add_diff_J_vals!(J::Matrix, jac_func::LatticeDiffusionODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file +add_spat_J_vals!(J::SparseMatrixCSC, jac_func::LatticeDiffusionODEjac) = (J.nzval .+= jac_func.jac_values) +add_spat_J_vals!(J::Matrix, jac_func::LatticeDiffusionODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index a9b734f625..693a22460e 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -3,26 +3,26 @@ # Abstract spatial reaction structures. abstract type AbstractSpatialReaction end -### Transport Reaction Structures ### - -# Implements the transportparameter metadata field. -struct TransportParameter end -Symbolics.option_to_metadata_type(::Val{:transportparameter}) = TransportParameter +# Implements the edgeparameter metadata field. +struct EdgeParameter end +Symbolics.option_to_metadata_type(::Val{:edgeparameter}) = EdgeParameter -istransportparameter(x::Num, args...) = istransportparameter(Symbolics.unwrap(x), args...) -function istransportparameter(x, default = false) +isedgeparameter(x::Num, args...) = isedgeparameter(Symbolics.unwrap(x), args...) +function isedgeparameter(x, default = false) p = Symbolics.getparent(x, nothing) p === nothing || (x = p) - Symbolics.getmetadata(x, TransportParameter, default) + Symbolics.getmetadata(x, EdgeParameter, default) end +### Transport Reaction Structures ### + # A transport reaction. These are simple to hanlde, and should cover most types of spatial reactions. # Currently only permit constant rates. struct TransportReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currently only constants supported""" rate::Num """The species that is subject to difusion.""" - species::Num + species::BasicSymbolic{Real} """A symbol representation of the species that is subject to difusion.""" species_sym::Symbol # Required for identification in certain cases. @@ -50,5 +50,30 @@ end function transport_reactions(transport_reactions) [TransportReaction(dr[1], dr[2]) for dr in transport_reactions] end + +# Macro for creating a transport reaction. +macro transport_reaction(species::Symbol, rate::Expr) + make_transport_reaction(species, MacroTools.striplines(rate)) +end +function make_transport_reaction(species, rate) + quote + @parameters + @variable t + @species $(species)(t) + return TransportReaction(species, rate) + end +end + # Gets the parameters in a transport reaction. -ModelingToolkit.parameters(dr::TransportReaction) = Symbolics.get_variables(dr.rate) \ No newline at end of file +ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.rate) + +# Gets the species in a transport reaction. +spatial_species(tr::TransportReaction) = [tr.species] + +# Checks that a trnasport reaction is valid for a given reaction system. +function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction) + # Checks that the rate does not depend on species. + isempty(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))).") + # Checks that the species does not exist in the system with different metadata. + any([isequal(tr.species, s) && !isequal(tr.species.metadata, s.metadata)]) || error("A transport reaction used a species ($(tr.species)) with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") +end diff --git a/test/runtests.jl b/test/runtests.jl index 87f41306a9..bd01f84d5a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,6 +39,7 @@ using SafeTestsets ### Tests Spatial Network Simulations. ### @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end + @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end ### Tests network visualization. ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl new file mode 100644 index 0000000000..e69de29bb2 From e5ef053a4f3f9ef81b88ec262a81cd3d858f06b8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 11:41:52 -0400 Subject: [PATCH 069/134] Finish LatticeReactionSystem revamp --- src/Catalyst.jl | 3 +- .../lattice_reaction_systems.jl | 31 +++++---- .../spatial_reactions.jl | 69 +++++++++++-------- 3 files changed, 59 insertions(+), 44 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index e26b011244..1c844503d7 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -101,7 +101,8 @@ export components, iscompound, coefficients # spatial reactions include("spatial_reaction_systems/spatial_reactions.jl") -export TransportReaction, transport_reactions, isedgeparameter +export TransportReaction, transport_reactions, @transport_reaction +export isedgeparameter # lattice reaction systems include("spatial_reaction_systems/lattice_reaction_systems.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index f63a6cea12..50dcc6109a 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,12 +1,12 @@ ### Lattice Reaction Network Structure ### # Desribes a spatial reaction network over a graph. -struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs +struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs """The reaction system within each comaprtment.""" - rs::ReactionSystem + rs::ReactionSystem{S} """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{<:AbstractSpatialReaction} + spatial_reactions::Vector{T} """The graph on which the lattice is defined.""" - lattice::DiGraph + lattice::SimpleDiGraph{Int64} # Derrived values. """The number of compartments.""" @@ -18,19 +18,22 @@ struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this p """Whenever the initial input was a digraph.""" init_digraph::Bool """Species that may move spatially.""" - spatial_species::Vector{BasicSymbolic{Real}} + spat_species::Vector{BasicSymbolic{Real}} """Parameters which values are tied to edges (adjacencies)..""" edge_parameters::Vector{BasicSymbolic{Real}} - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::DiGraph; init_digraph = true) - spatial_species = unique(spatial_species.(spatial_reactions)) + function LatticeReactionSystem(rs::ReactionSystem{S}, + spatial_reactions::Vector{T}, + lattice::DiGraph; init_digraph = true) where {S, T} + (T <: AbstractSpatialReaction) || error("The secodn argument must be a vector of AbstractSpatialReaction subtypes.") # There probably some better way to acertain that T has that type. Not sure how. + spat_species = unique(vcat(spatial_species.(spatial_reactions)...)) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) - srs_edge_parameters = setdiff(vcat(parameters.(spatial_reactions)), parameters(rs)) + srs_edge_parameters = setdiff(vcat(parameters.(spatial_reactions)...), parameters(rs)) edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) - foreach(sr -> check_spatial_reaction_validity(rs, rs), spatial_reactions) - return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph, spatial_species, edge_parameters) + + + foreach(sr -> check_spatial_reaction_validity(rs, sr), spatial_reactions) + return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph, spat_species, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) @@ -54,9 +57,9 @@ end ### Lattice ReactionSystem Getters ### # Get all species. -species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spatial_species]) +species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species]) # Get all species that may be transported. -spatial_species(lrs::LatticeReactionSystem) = lrs.spatial_species +spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species # Get all parameters. ModelingToolkit.parameters(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.edge_parameters]) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 693a22460e..962ab3d8d8 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -3,10 +3,13 @@ # Abstract spatial reaction structures. abstract type AbstractSpatialReaction end +### EdgeParameter Metadata ### + # Implements the edgeparameter metadata field. struct EdgeParameter end Symbolics.option_to_metadata_type(::Val{:edgeparameter}) = EdgeParameter +# Implements the isedgeparameter check function. isedgeparameter(x::Num, args...) = isedgeparameter(Symbolics.unwrap(x), args...) function isedgeparameter(x, default = false) p = Symbolics.getparent(x, nothing) @@ -17,55 +20,44 @@ end ### Transport Reaction Structures ### # A transport reaction. These are simple to hanlde, and should cover most types of spatial reactions. -# Currently only permit constant rates. +# Only permit constant rates (possibly consisting of several parameters). struct TransportReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currently only constants supported""" rate::Num """The species that is subject to difusion.""" species::BasicSymbolic{Real} - """A symbol representation of the species that is subject to difusion.""" - species_sym::Symbol # Required for identification in certain cases. # Creates a diffusion reaction. function TransportReaction(rate::Num, species::Num) - new(rate, species, ModelingToolkit.getname(species)) - end - function TransportReaction(rate::Number, species::Num) - new(Num(rate), species, ModelingToolkit.getname(species)) - end - function TransportReaction(rate::Symbol, species::Num) - new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) - end - function TransportReaction(rate::Num, species::Symbol) - new(rate, Symbolics.variable(species), species) - end - function TransportReaction(rate::Number, species::Symbol) - new(Num(rate), Symbolics.variable(species), species) - end - function TransportReaction(rate::Symbol, species::Symbol) - new(Symbolics.variable(rate), Symbolics.variable(species), species) + new(rate, species.val) end end +# If the rate is a value, covert that to a numemric expression. +function TransportReaction(rate::Number, args...) + TransportReaction(Num(rate), args...) +end # Creates a vector of TransportReactions. function transport_reactions(transport_reactions) - [TransportReaction(dr[1], dr[2]) for dr in transport_reactions] + [TransportReaction(tr[1], tr[2]) for tr in transport_reactions] end # Macro for creating a transport reaction. -macro transport_reaction(species::Symbol, rate::Expr) - make_transport_reaction(species, MacroTools.striplines(rate)) +macro transport_reaction(rateex::ExprValues, species::Symbol) + make_transport_reaction(MacroTools.striplines(rateex), species) end -function make_transport_reaction(species, rate) +function make_transport_reaction(rateex, species) + parameters = []; + find_parameters_in_rate!(parameters, rateex) quote - @parameters - @variable t + @parameters $(parameters...) + @variables t @species $(species)(t) - return TransportReaction(species, rate) + TransportReaction($rateex, $species) end end # Gets the parameters in a transport reaction. -ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.rate) +ModelingToolkit.parameters(tr::TransportReaction) = convert(Vector{BasicSymbolic{Real}}, Symbolics.get_variables(tr.rate)) # Gets the species in a transport reaction. spatial_species(tr::TransportReaction) = [tr.species] @@ -73,7 +65,26 @@ spatial_species(tr::TransportReaction) = [tr.species] # Checks that a trnasport reaction is valid for a given reaction system. function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction) # Checks that the rate does not depend on species. - isempty(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))).") + isempty(intersect(ModelingToolkit.getname.(species(rs)), ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))).") # Checks that the species does not exist in the system with different metadata. - any([isequal(tr.species, s) && !isequal(tr.species.metadata, s.metadata)]) || error("A transport reaction used a species ($(tr.species)) with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") + any([isequal(tr.species, s) && isequal(tr.species.metadata, s.metadata) for s in species(rs)]) || error("A transport reaction used a species ($(tr.species)) with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") + any([isequal(rs_p, tr_p) && isequal(rs_p.metadata, tr_p.metadata) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)]) || error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end + +### Utility ### +# Loops through a rate and extract all parameters. +function find_parameters_in_rate!(parameters, rateex::ExprValues) + if rateex isa Symbol + if !(rateex in [:ℯ, :pi, :π]) + push!(parameters, rateex) + elseif rateex in [:t, :∅, forbidden_symbols_error...] + error("Forbidden term $(rateex) used in transport reaction rate.") + end + elseif rateex isa Expr + # note, this (correctly) skips $(...) expressions + for i in 2:length(rateex.args) + find_parameters_in_rate!(parameters, rateex.args[i]) + end + end + nothing +end \ No newline at end of file From e46e1adeada43bdec1f3f563571227b13d784691 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 11:59:02 -0400 Subject: [PATCH 070/134] redo test networks --- test/spatial_test_networks.jl | 55 ++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index bf08aab518..9e00617e23 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -41,15 +41,18 @@ end SIR_p = [:α => 0.1 / 1000, :β => 0.01] SIR_u0 = [:S => 999.0, :I => 1.0, :R => 0.0] -SIR_dif_S = DiffusionReaction(:dS, :S) -SIR_dif_I = DiffusionReaction(:dI, :I) -SIR_dif_R = DiffusionReaction(:dR, :R) -SIR_srs_1 = [SIR_dif_S] -SIR_srs_2 = [SIR_dif_S, SIR_dif_I, SIR_dif_R] +SIR_tr_S = @transport_reaction dS S +SIR_tr_I = @transport_reaction dI I +SIR_tr_R = @transport_reaction dR R +SIR_srs_1 = [SIR_tr_S] +SIR_srs_2 = [SIR_tr_S, SIR_tr_I, SIR_tr_R] # Small non-stiff system. binding_system = @reaction_network begin (k1, k2), X + Y <--> XY end -binding_srs = diffusion_reactions([(:dX, :X), (:dY, :Y), (:dXY, :XY)]) +binding_tr_X = @transport_reaction dX X +binding_tr_Y = @transport_reaction dY Y +binding_tr_XY = @transport_reaction dXY XY +binding_srs = [binding_tr_X, binding_tr_Y, binding_tr_XY] binding_u0 = [:X => 1.0, :Y => 2.0, :XY => 0.5] binding_p = [:k1 => 2.0, :k2 => 0.1, :dX => 3.0, :dY => 5.0, :dXY => 2.0] @@ -93,18 +96,18 @@ CuH_Amination_u0 = [ :Decomposition => 0.0, ] -CuH_Amination_diff_1 = DiffusionReaction(:D1, :CuoAc) -CuH_Amination_diff_2 = DiffusionReaction(:D2, :Silane) -CuH_Amination_diff_3 = DiffusionReaction(:D3, :Cu_ELigand) -CuH_Amination_diff_4 = DiffusionReaction(:D4, :Amine) -CuH_Amination_diff_5 = DiffusionReaction(:D5, :CuHLigand) -CuH_Amination_srs_1 = [CuH_Amination_diff_1] +CuH_Amination_tr_1 = @transport_reaction D1 CuoAc +CuH_Amination_tr_2 = @transport_reaction D2 Silane +CuH_Amination_tr_3 = @transport_reaction D3 Cu_ELigand +CuH_Amination_tr_4 = @transport_reaction D4 Amine +CuH_Amination_tr_5 = @transport_reaction D5 CuHLigand +CuH_Amination_srs_1 = [CuH_Amination_tr_1] CuH_Amination_srs_2 = [ - CuH_Amination_diff_1, - CuH_Amination_diff_2, - CuH_Amination_diff_3, - CuH_Amination_diff_4, - CuH_Amination_diff_5, + CuH_Amination_tr_1, + CuH_Amination_tr_2, + CuH_Amination_tr_3, + CuH_Amination_tr_4, + CuH_Amination_tr_5, ] # Small stiff system. @@ -116,10 +119,10 @@ brusselator_system = @reaction_network begin end brusselator_p = [:A => 1.0, :B => 4.0] -brusselator_dif_x = DiffusionReaction(:dX, :X) -brusselator_dif_y = DiffusionReaction(:dY, :Y) -brusselator_srs_1 = [brusselator_dif_x] -brusselator_srs_2 = [brusselator_dif_x, brusselator_dif_y] +brusselator_tr_x = @transport_reaction dX X +brusselator_tr_y = @transport_reaction dY Y +brusselator_srs_1 = [brusselator_tr_x] +brusselator_srs_2 = [brusselator_tr_x, brusselator_tr_y] # Mid-sized stiff system. # Unsure about stifness, but non-spatial version oscillates for this parameter set. @@ -157,11 +160,11 @@ sigmaB_u0 = [ :phos => 0.4, ] -sigmaB_dif_σB = DiffusionReaction(:DσB, :σB) -sigmaB_dif_w = DiffusionReaction(:Dw, :w) -sigmaB_dif_v = DiffusionReaction(:Dv, :v) -sigmaB_srs_1 = [sigmaB_dif_σB] -sigmaB_srs_2 = [sigmaB_dif_σB, sigmaB_dif_w, sigmaB_dif_v] +sigmaB_tr_σB = @transport_reaction DσB σB +sigmaB_tr_w = @transport_reaction Dw w +sigmaB_tr_v = @transport_reaction Dv v +sigmaB_srs_1 = [sigmaB_tr_σB] +sigmaB_srs_2 = [sigmaB_tr_σB, sigmaB_tr_w, sigmaB_tr_v] ### Declares Lattices ### From ee755a2d9e974bc170f725aac721e317f8df928e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 19:43:27 -0400 Subject: [PATCH 071/134] LatticeReactionSystem revamp --- src/Catalyst.jl | 3 + .../lattice_reaction_systems.jl | 57 ++++++++++--------- .../spatial_ODE_systems.jl | 17 ++++-- .../spatial_reactions.jl | 6 +- 4 files changed, 49 insertions(+), 34 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 1c844503d7..2cdac847cc 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -109,4 +109,7 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters +# spatial lattice ode systems. +include("spatial_reaction_systems/spatial_ODE_systems.jl") + end # module diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 50dcc6109a..57743c1372 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -62,29 +62,41 @@ species(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.spat_species] spatial_species(lrs::LatticeReactionSystem) = lrs.spat_species # Get all parameters. -ModelingToolkit.parameters(lrs::LatticeReactionSystem) = unique([species(lrs.rs); lrs.edge_parameters]) +ModelingToolkit.parameters(lrs::LatticeReactionSystem) = unique([parameters(lrs.rs); lrs.edge_parameters]) # Get all parameters which values are tied to vertexes (compartments). vertex_parameters(lrs::LatticeReactionSystem) = setdiff(parameters(lrs), edge_parameters(lrs)) # Get all parameters which values are tied to edges (adjacencies). edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters +# Gets the lrs name (same as rs name). +ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) + # Checks if a lattice reaction system is a pure (linear) transport reaction system. is_transport_system(lrs::LatticeReactionSystem) = all(typeof.(lrs.spatial_reactions) .== TransportReaction) ### Processes Input u0 & p ### +# Required to make symmapt_to_varmap to work. +function _symbol_to_var(lrs::LatticeReactionSystem, sym) + p_idx = findfirst(sym==p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) + isnothing(p_idx) || return parameters(lrs)[p_idx] + s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) + isnothing(s_idx) || return species(lrs)[s_idx] + error("Could not find property parameter/species $sym in lattice reaction system.") +end + # From u0 input, extracts their values and store them in the internal format. -function lattice_process_u0(u0_in, u0_symbols, nV) - u0 = lattice_process_input(u0_in, u0_symbols, nV) +function lattice_process_u0(u0_in, u0_syms, nV) + u0 = lattice_process_input(u0_in, u0_syms, nV) check_vector_lengths(u0, nV) expand_component_values(u0, nV) end # From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. -function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) - pV_in, pE_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) - pV = lattice_process_input(pV_in, p_comp_symbols, lrs.nV) - pE = lattice_process_input(pE_in, p_diff_symbols, lrs.nE) +function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) + pV_in, pE_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) + pV = lattice_process_input(pV_in, p_vertex_syms, lrs.nV) + pE = lattice_process_input(pE_in, p_edge_syms, lrs.nE) lrs.init_digraph || foreach(idx -> duplicate_spat_params!(pE, idx, lrs), 1:length(pE)) check_vector_lengths(pV, lrs.nV) check_vector_lengths(pE, lrs.nE) @@ -106,12 +118,10 @@ function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, end # If the input is given in a map form, the vector needs sorting and the first value removed. -function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) - (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && - error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") - sorted_input = sort(input; - by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) - return lattice_process_input(last.(sorted_input), symbols, args...) +function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) + isempty(setdiff(first.(input), syms)) || error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") + sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) + return lattice_process_input(last.(sorted_input), syms, args...) end # Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). function lattice_process_input(input::Matrix{<:Number}, args...) @@ -125,7 +135,7 @@ function lattice_process_input(input::Vector{<:Any}, args...) lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], args...) end -lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input +lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input function check_vector_lengths(input::Vector{<:Vector}, n) isempty(setdiff(unique(length.(input)), [1, n])) || error("Some inputs where given values of inappropriate length.") @@ -141,27 +151,22 @@ end vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) # Produces a dictionary with all parameter values. function param_dict(pV, pE, lrs) - merge(vals_to_dict(compartment_parameters(lrs), pV), - vals_to_dict(diffusion_parameters(lrs), pE)) + merge(vals_to_dict(vertex_parameters(lrs), pV), + vals_to_dict(edge_parameters(lrs), pE)) end # Computes the spatial rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, - pE::Vector{Vector{Float64}}, - lrs::LatticeReactionSystem) +function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) param_value_dict = param_dict(pV, pE, lrs) - return [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), - param_value_dict, lrs.nE)) - for s in spatial_species(lrs)] + return [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in spatial_species(lrs)] end -function get_spatial_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), - lrs.spatial_reactions) +function get_spatial_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. return rates[1].rate end function compute_spatial_rates(rate_law::Num, - param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) + param_value_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, nE::Int64) relevant_parameters = Symbolics.get_variables(rate_law) if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) return [ diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 57c13c8218..fbe4abe1c3 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -52,9 +52,16 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; jac = true, sparse = jac, kwargs...) is_transport_system(lrs) || error("Currently lattice ODE simulations only supported when all spatial reactions are transport reactions.") - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nV) - pV, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(edge_parameters(lrs)), lrs) + + # Converts potential symmaps to varmaps. + 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 Vector{Vector{Float64}} form. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) + pV, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + + # Creates ODEProblem. ofun = build_odefunction(lrs, pV, pD, jac, sparse) return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) end @@ -100,13 +107,13 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, local_elements = in(s, spat_species) * (length(lrs.lattice.fadjlist[comp]) + only_spat[s]) spatial_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) - J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + spatial_elements_elements + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + spatial_elements # Row values. rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ (comp - 1) * lrs.nS if in(s, spat_species) - # Finds the location of the spatial_elements elements, and inserts the elements from the non-spatial part into this. + # Finds the location of the spatial_elements, and inserts the elements from the non-spatial part into this. spatial_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s split_idx = isempty(rows) ? 1 : findfirst(spatial_rows .> rows[1]) isnothing(split_idx) && (split_idx = length(spatial_rows) + 1) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 962ab3d8d8..c624240a1e 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -62,13 +62,13 @@ ModelingToolkit.parameters(tr::TransportReaction) = convert(Vector{BasicSymbolic # Gets the species in a transport reaction. spatial_species(tr::TransportReaction) = [tr.species] -# Checks that a trnasport reaction is valid for a given reaction system. +# Checks that a transport reaction is valid for a given reaction system. function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction) # Checks that the rate does not depend on species. isempty(intersect(ModelingToolkit.getname.(species(rs)), ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))).") # Checks that the species does not exist in the system with different metadata. - any([isequal(tr.species, s) && isequal(tr.species.metadata, s.metadata) for s in species(rs)]) || error("A transport reaction used a species ($(tr.species)) with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") - any([isequal(rs_p, tr_p) && isequal(rs_p.metadata, tr_p.metadata) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)]) || error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") + any([isequal(tr.species, s) && !isequal(tr.species.metadata, s.metadata) for s in species(rs)]) && error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") + any([isequal(rs_p, tr_p) && !isequal(rs_p.metadata, tr_p.metadata) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)]) && error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") end ### Utility ### From 3bc52f7dab7bc8c5d96e7fe24d69919422b51235 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 20:16:07 -0400 Subject: [PATCH 072/134] test updates --- .../lattice_reaction_systems.jl | 10 ++-- .../lattice_reaction_systems_ODEs.jl | 55 +++---------------- test/spatial_test_networks.jl | 2 +- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 57743c1372..74f690fc2f 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -108,12 +108,10 @@ split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps function split_parameters(ps::Vector{<:Number}, args...) error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") end -function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, - p_diff_symbols::Vector) - pV_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] - pE_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] - (sum(length.([pV_in, pE_in])) != length(ps)) && - error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pV_in, pE_in]))))") +function split_parameters(ps::Vector{<:Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) + pV_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + pE_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] + (sum(length.([pV_in, pE_in])) != length(ps)) && error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pV_in, pE_in]))))") return pV_in, pE_in end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 7ad7125f99..1f0608fe37 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -122,42 +122,6 @@ end ### Tests Special Cases ### -# Creates network with various combiantions of Symbls and Nums in diffusion reactions. -let - @parameters dS dI dR - @variables t - @species S(t) I(t) R(t) - SIR_srs_numsym_1 = diffusion_reactions([(:dS, :S), (:dI, :I), (:dR, :R)]) - SIR_srs_numsym_2 = diffusion_reactions([(dS, :S), (dI, :I), (dR, :R)]) - SIR_srs_numsym_3 = diffusion_reactions([(:dS, S), (:dI, I), (:dR, R)]) - SIR_srs_numsym_4 = diffusion_reactions([(dS, S), (dI, I), (dR, R)]) - SIR_srs_numsym_5 = diffusion_reactions([(dS, :S), (:dI, I), (dR, :R)]) - SIR_srs_numsym_6 = diffusion_reactions([(:dS, :S), (:dI, I), (dR, R)]) - - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(small_2d_grid), :R => 0.0] - pV = SIR_p - pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - pE_2 = [dS => 0.01, dI => 0.01, dR => 0.01] - pE_3 = [dS => 0.01, :dI => 0.01, :dR => 0.01] - ss_explicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = false), Tsit5()).u[end] - ss_implicit_base = solve(ODEProblem(LatticeReactionSystem(SIR_system, SIR_srs_numsym_1, small_2d_grid), u0, (0.0, 10.0), (pV, pE_1); jac = true), Rosenbrock23()).u[end] - - for srs in [ - SIR_srs_numsym_1, - SIR_srs_numsym_2, - SIR_srs_numsym_3, - SIR_srs_numsym_4, - SIR_srs_numsym_5, - SIR_srs_numsym_6, - ], pE in [pE_1, pE_2, pE_3] - lrs = LatticeReactionSystem(SIR_system, srs, small_2d_grid) - ss_explicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false), Tsit5()).u[end] - ss_implicit = solve(ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = true), Rosenbrock23()).u[end] - @test all(isapprox.(ss_explicit, ss_explicit_base)) - @test all(isapprox.(ss_implicit, ss_implicit_base)) - end -end - # Create network with vaious combinations of graph/di-graph and parameters. let lrs_digraph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_digraph(3)) @@ -199,16 +163,15 @@ end let binding_system_alt = @reaction_network begin @species X(t) Y(t) XY(t) Z(t) V(t) W(t) - @parameters k1 k2 dX [diffusionparameter = true] dXY [diffusionparameter = true] dZ [ - diffusionparameter = true, - ] dV [diffusionparameter = true] p1 p2 + @parameters k1 k2 dX [edgeparameter = true] dXY [edgeparameter = true] dZ [edgeparameter = true] dV [edgeparameter = true] p1 p2 (k1, k2), X + Y <--> XY end + @unpack dX, dXY, dZ, dV, X, XY, Z, V = binding_system_alt binding_srs_alt = [ - DiffusionReaction(:dX, :X), - DiffusionReaction(:dXY, :XY), - DiffusionReaction(:dZ, :Z), - DiffusionReaction(:dV, :V), + TransportReaction(dX, X), + TransportReaction(dXY, XY), + TransportReaction(dZ, Z), + TransportReaction(dV, V), ] lrs_alt = LatticeReactionSystem(binding_system_alt, binding_srs_alt, small_2d_grid) u0_alt = [ @@ -232,7 +195,7 @@ let oprob_alt = ODEProblem(lrs_alt, u0_alt, (0.0, 10.0), p_alt) ss_alt = solve(oprob_alt, Tsit5()).u[end] - binding_srs_main = [DiffusionReaction(:dX, :X), DiffusionReaction(:dXY, :XY)] + binding_srs_main = [TransportReaction(dX, X), TransportReaction(dXY, XY)] lrs = LatticeReactionSystem(binding_system, binding_srs_main, small_2d_grid) u0 = u0_alt[1:3] p = p_alt[1:4] @@ -247,8 +210,8 @@ end # System with single spatial reaction. let - lrs_1 = LatticeReactionSystem(SIR_system, SIR_dif_S, small_2d_grid) - lrs_2 = LatticeReactionSystem(SIR_system, [SIR_dif_S], small_2d_grid) + lrs_1 = LatticeReactionSystem(SIR_system, SIR_tr_S, small_2d_grid) + lrs_2 = LatticeReactionSystem(SIR_system, [SIR_tr_S], small_2d_grid) u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] pV = SIR_p pE = [:dS => 0.01] diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 9e00617e23..64f0c3ae0c 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -20,7 +20,7 @@ end # Gets a symbol list of spatial parameters. function spatial_param_syms(lrs::LatticeReactionSystem) - ModelingToolkit.getname.(diffusion_parameters(lrs)) + ModelingToolkit.getname.(edge_parameters(lrs)) end # Converts to integer value (for JumpProcess simulations). From f9607c2710235dc8bfe8afde5d3f88dc50050b22 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 13:55:05 -0400 Subject: [PATCH 073/134] More tests --- .../lattice_reaction_systems.jl | 24 +-- .../spatial_ODE_systems.jl | 23 ++- .../spatial_reactions.jl | 11 +- .../lattice_reaction_systems_ODEs.jl | 165 ++++++++++++++---- 4 files changed, 172 insertions(+), 51 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 74f690fc2f..4fa787e870 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -25,15 +25,14 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t function LatticeReactionSystem(rs::ReactionSystem{S}, spatial_reactions::Vector{T}, lattice::DiGraph; init_digraph = true) where {S, T} - (T <: AbstractSpatialReaction) || error("The secodn argument must be a vector of AbstractSpatialReaction subtypes.") # There probably some better way to acertain that T has that type. Not sure how. + (T <: AbstractSpatialReaction) || error("The second argument must be a vector of AbstractSpatialReaction subtypes.") # There probably some better way to acertain that T has that type. Not sure how. spat_species = unique(vcat(spatial_species.(spatial_reactions)...)) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) srs_edge_parameters = setdiff(vcat(parameters.(spatial_reactions)...), parameters(rs)) edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) - - foreach(sr -> check_spatial_reaction_validity(rs, sr), spatial_reactions) - return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(species(rs)), init_digraph, spat_species, edge_parameters) + foreach(sr -> check_spatial_reaction_validity(rs, sr; edge_parameters=edge_parameters), spatial_reactions) + return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(unique([species(rs); spat_species])), init_digraph, spat_species, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) @@ -88,7 +87,7 @@ end # From u0 input, extracts their values and store them in the internal format. function lattice_process_u0(u0_in, u0_syms, nV) u0 = lattice_process_input(u0_in, u0_syms, nV) - check_vector_lengths(u0, nV) + check_vector_lengths(u0, length(u0_syms), nV) expand_component_values(u0, nV) end @@ -98,8 +97,8 @@ function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactio pV = lattice_process_input(pV_in, p_vertex_syms, lrs.nV) pE = lattice_process_input(pE_in, p_edge_syms, lrs.nE) lrs.init_digraph || foreach(idx -> duplicate_spat_params!(pE, idx, lrs), 1:length(pE)) - check_vector_lengths(pV, lrs.nV) - check_vector_lengths(pE, lrs.nE) + check_vector_lengths(pV, length(p_vertex_syms), lrs.nV) + check_vector_lengths(pE, length(p_edge_syms), lrs.nE) return pV, pE end @@ -134,9 +133,11 @@ function lattice_process_input(input::Vector{<:Any}, args...) args...) end lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input -function check_vector_lengths(input::Vector{<:Vector}, n) - isempty(setdiff(unique(length.(input)), [1, n])) || - error("Some inputs where given values of inappropriate length.") + +# Checks that a value vector have the right length, as well as that of all its sub vectors. +function check_vector_lengths(input::Vector{<:Vector}, n_syms, n_locations) + (length(input)==n_syms) || error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") + isempty(setdiff(unique(length.(input)), [1, n_locations])) || error("Some inputs where given values of inappropriate length.") end # For spatial parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, expand so that the same value are given for both values on the edge. @@ -156,7 +157,8 @@ end # Computes the spatial rates and stores them in a format (Dictionary of species index to rates across all edges). function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) param_value_dict = param_dict(pV, pE, lrs) - return [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in spatial_species(lrs)] + unsorted_rates = [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in spatial_species(lrs)] + return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) end function get_spatial_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index fbe4abe1c3..514a0b6a4b 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -56,24 +56,24 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, # Converts potential symmaps to varmaps. 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 Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) - pV, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + pV, pE = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. - ofun = build_odefunction(lrs, pV, pD, jac, sparse) + ofun = build_odefunction(lrs, pV, pE, jac, sparse) return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. function build_odefunction(lrs::LatticeReactionSystem, pV::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) + pE::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) # Prepeares (non-spatial) ODE functions and list of spatially moving species and their rates. ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - spatial_rates_speciesmap = compute_all_spatial_rates(pV, pD, lrs) - spatial_rates = [findfirst(isequal(spat_rates[1]), states(lrs.rs)) => spat_rates[2] + spatial_rates_speciesmap = compute_all_spatial_rates(pV, pE, lrs) + spatial_rates = [findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] for spat_rates in spatial_rates_speciesmap] f = LatticeDiffusionODEf(ofunc, pV, spatial_rates, lrs) @@ -88,6 +88,7 @@ end function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, spatial_rates, lrs::LatticeReactionSystem; set_nonzero = false) spat_species = first.(spatial_rates) + # Gets list of indexes for species that move spatially, but are invovled in no other reaction. only_spat = [(s in spat_species) && !Base.isstored(ns_jac_prototype, s, s) for s in 1:(lrs.nS)] @@ -127,7 +128,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows end - + # Set element values. if !set_nonzero J_nzval .= 1.0 @@ -145,6 +146,14 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, J_nzval[val_idx_src] -= get_component_value(rates, e_idx) # Updates the destination value. + # println() + # println() + # println(col_start) + # println(column_view) + # println(get_index(edge.dst, s, lrs.nS)) + # println(col_start) + # println(col_end) + # println(column_view) val_idx_dst = col_start + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 J_nzval[val_idx_dst] += get_component_value(rates, e_idx) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index c624240a1e..77e8a4d9e0 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -63,13 +63,18 @@ ModelingToolkit.parameters(tr::TransportReaction) = convert(Vector{BasicSymbolic spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. -function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction) +function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReaction; edge_parameters=[]) + # Checks that the species exist in the reaction system (ODE simulation code becomes difficult if this is not required, as non-spatial jacobian and f function generated from rs is of wrong size). + any(isequal(tr.species), species(rs)) || error("Currently, species used in TransportReactions must also be in non-spatial ReactionSystem. This is not the case for $(tr.species).") # Checks that the rate does not depend on species. - isempty(intersect(ModelingToolkit.getname.(species(rs)), ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname(species(rs)), ModelingToolkit.getname(Symbolics.get_variables(tr.rate)))).") + isempty(intersect(ModelingToolkit.getname.(species(rs)), ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)))) || error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname.(species(rs)), ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)))).") # Checks that the species does not exist in the system with different metadata. any([isequal(tr.species, s) && !isequal(tr.species.metadata, s.metadata) for s in species(rs)]) && error("A transport reaction used a species, $(tr.species), with metadata not matching its lattice reaction system. Please fetch this species from the reaction system and used in transport reaction creation.") - any([isequal(rs_p, tr_p) && !isequal(rs_p.metadata, tr_p.metadata) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)]) && error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") + any([isequal(rs_p, tr_p) && !equivalent_metadata(rs_p, tr_p) for rs_p in parameters(rs), tr_p in Symbolics.get_variables(tr.rate)]) && error("A transport reaction used a parameter with metadata not matching its lattice reaction system. Please fetch this parameter from the reaction system and used in transport reaction creation.") + # Checks that no edge parameter occur among rates of non-spatial reactions. + any([!isempty(intersect(Symbolics.get_variables(r.rate), edge_parameters)) for r in reactions(rs)]) && error("Edge paramter(s) were found as a rate of a non-spatial reaction.") end +equivalent_metadata(p1, p2) = isempty(setdiff(p1.metadata, p2.metadata, [Catalyst.EdgeParameter => true])) ### Utility ### # Loops through a rate and extract all parameters. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 1f0608fe37..43fe71bbc3 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -7,10 +7,10 @@ using Random, Statistics, SparseArrays, Test # Fetch test networks. include("../spatial_test_networks.jl") -### Test No Error During Runs ### +### Tests Simulations Don't Error ### for grid in [small_2d_grid, short_path, small_directed_cycle] # Non-stiff case - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999.0, :I => 1.0, :R => 0.0] u0_2 = [:S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), :I => 1.0, :R => 0.0] @@ -52,7 +52,7 @@ for grid in [small_2d_grid, short_path, small_directed_cycle] end # Stiff case - for srs in [Vector{DiffusionReaction}(), brusselator_srs_1, brusselator_srs_2] + for srs in [Vector{TransportReaction}(), brusselator_srs_1, brusselator_srs_2] lrs = LatticeReactionSystem(brusselator_system, srs, grid) u0_1 = [:X => 1.0, :Y => 20.0] u0_2 = [:X => rand_v_vals(lrs.lattice, 10.0), :Y => 2.0] @@ -120,6 +120,134 @@ let @test all(isapprox.(ss[3:3:end], ss[3])) end +# Checks that various combinations of jac and sparse gives the same result. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) + oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) + oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) + oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) + + ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] + @test all(isapprox.(ss, + solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) + @test all(isapprox.(ss, + solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; + rtol = 0.0001)) +end + +### Test Transport Reaction Types ### + +# Compares where spatial reactions are created with/without the macro. +let + @parameters dS dI + @unpack S, I = SIR_system + tr_1 = TransportReaction(dS, S) + tr_2 = TransportReaction(dI, I) + tr_macros_1 = @transport_reaction dS S + tr_macros_2 = @transport_reaction dI I + + lrs_1 = LatticeReactionSystem(SIR_system, [tr_1, tr_2], small_2d_grid) + lrs_2 = LatticeReactionSystem(SIR_system, [tr_macros_1, tr_macros_2], small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + pV = [:α => 0.1 / 1000, :β => 0.01] + pE = [:dS => 0.01, :dI => 0.01] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss_1, ss_2)) +end + +# Tries non-trivial diffusion rates. +let + SIR_tr_S_alt = @transport_reaction dS1+dS2 S + SIR_tr_I_alt = @transport_reaction dI1*dI2 I + SIR_tr_R_alt = @transport_reaction log(dR1)+dR2 R + SIR_srs_2_alt = [SIR_tr_S_alt, SIR_tr_I_alt, SIR_tr_R_alt] + lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2_alt, small_2d_grid) + + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + pV = [:α => 0.1 / 1000, :β => 0.01] + pE_1 = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + pE_2 = [:dS1 => 0.005, :dS1 => 0.005, :dI1 => 2, :dI2 => 0.005, :dR1 => 1.010050167084168, :dR2 => 1.0755285551056204e-16] + + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE_1)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE_2)), Tsit5()).u[end] + @test all(isapprox.(ss_1, ss_2)) +end + +# Tries various ways of creating TransportReactions. +let + CuH_Amination_system_alt_1 = @reaction_network begin + @species Newspecies1(t) Newspecies2(t) + @parameters dCuoAc [edgeparameter=true] dLigand dSilane dStyrene dCu_ELigand + 10.0^kp1, CuoAc + Ligand --> CuoAcLigand + 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc + 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand + 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene + 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand + 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E + 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane + 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand + 10.0^kdc, CuHLigand + CuHLigand --> Decomposition + end + @unpack dLigand, dSilane, Silane = CuH_Amination_system_alt_1 + @parameters dAmine_E dNewspecies1 + @variables t + @species Ligand(t) Amine_E(t) Newspecies1(t) + tr_alt_1_1 = TransportReaction(dLigand, Ligand) + tr_alt_1_2 = TransportReaction(dSilane, Silane) + tr_alt_1_3 = TransportReaction(dAmine_E, Amine_E) + tr_alt_1_4 = TransportReaction(dNewspecies1, Newspecies1) + tr_alt_1_5 = @transport_reaction dDecomposition Decomposition + tr_alt_1_6 = @transport_reaction dCu_ELigand Cu_ELigand + tr_alt_1_7 = @transport_reaction dNewspecies2 Newspecies2 + CuH_Amination_srs_alt_1 = [tr_alt_1_1, tr_alt_1_2, tr_alt_1_3, tr_alt_1_4, tr_alt_1_5, tr_alt_1_6, tr_alt_1_7] + lrs_1 = LatticeReactionSystem(CuH_Amination_system_alt_1, CuH_Amination_srs_alt_1, small_2d_grid) + + CuH_Amination_system_alt_2 = @reaction_network begin + @species Newspecies1(t) Newspecies2(t) + @parameters dCuoAc [edgeparameter=true] dLigand dSilane dStyrene dCu_ELigand + 10.0^kp1, CuoAc + Ligand --> CuoAcLigand + 10.0^kp2, CuoAcLigand + Silane --> CuHLigand + SilaneOAc + 10.0^k1, CuHLigand + Styrene --> AlkylCuLigand + 10.0^k_1, AlkylCuLigand --> CuHLigand + Styrene + 10.0^k2, AlkylCuLigand + Amine_E --> AlkylAmine + Cu_ELigand + 10.0^k_2, AlkylAmine + Cu_ELigand --> AlkylCuLigand + Amine_E + 10.0^k3, Cu_ELigand + Silane --> CuHLigand + E_Silane + 10.0^kam, CuHLigand + Amine_E --> Amine + Cu_ELigand + 10.0^kdc, CuHLigand + CuHLigand --> Decomposition + end + @unpack Decomposition, dCu_ELigand, Cu_ELigand = CuH_Amination_system_alt_2 + @parameters dNewspecies2 dDecomposition + @variables t + @species Newspecies2(t) + tr_alt_2_1 = @transport_reaction dLigand Ligand + tr_alt_2_2 = @transport_reaction dSilane Silane + tr_alt_2_3 = @transport_reaction dAmine_E Amine_E + tr_alt_2_4 = @transport_reaction dNewspecies1 Newspecies1 + tr_alt_2_5 = TransportReaction(dDecomposition, Decomposition) + tr_alt_2_6 = TransportReaction(dCu_ELigand, Cu_ELigand) + tr_alt_2_7 = TransportReaction(dNewspecies2, Newspecies2) + CuH_Amination_srs_alt_2 = [tr_alt_2_1, tr_alt_2_2, tr_alt_2_3, tr_alt_2_4, tr_alt_2_5, tr_alt_2_6, tr_alt_2_7] + lrs_2 = LatticeReactionSystem(CuH_Amination_system_alt_2, CuH_Amination_srs_alt_2, small_2d_grid) + + u0 = [CuH_Amination_u0; :Newspecies1 => 0.1; :Newspecies2 => 0.1] + pV = [CuH_Amination_p; :dLigand => 0.01; :dSilane => 0.01; :dCu_ELigand => 0.009; :dStyrene => -10000.0] + pE = [:dAmine_E => 0.011, :dNewspecies1 => 0.013, :dDecomposition => 0.015, :dNewspecies2 => 0.016, :dCuoAc => -10000.0] + + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE)), Tsit5()).u[end] + @test all(isequal.(ss_1, ss_2)) +end + ### Tests Special Cases ### # Create network with vaious combinations of graph/di-graph and parameters. @@ -159,7 +287,7 @@ let sim_end_graph_22 .== sim_end_graph_31 .== sim_end_graph_32) end -# Creates networks with empty species or parameters. +# Creates networks where some species or parameters have no effect on the system. let binding_system_alt = @reaction_network begin @species X(t) Y(t) XY(t) Z(t) V(t) W(t) @@ -208,7 +336,7 @@ let end end -# System with single spatial reaction. +# Checks that system with single spatial reaction can be created without inputting it as a vector. let lrs_1 = LatticeReactionSystem(SIR_system, SIR_tr_S, small_2d_grid) lrs_2 = LatticeReactionSystem(SIR_system, [SIR_tr_S], small_2d_grid) @@ -220,7 +348,7 @@ let @test all(isequal.(ss_1, ss_2)) end -# Various ways to give parameters and initial conditions. +# Provides initial conditions and parameters in various different ways. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) u0_1 = [:S => 990.0, :I => [1.0, 3.0, 2.0, 5.0], :R => 0.0] @@ -247,30 +375,7 @@ let end end -# Checks that variosu combinations of jac and sparse gives the same result. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = false) - oprob_sparse = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = false, sparse = true) - oprob_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = false) - oprob_sparse_jac = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE); jac = true, sparse = true) - - ss = solve(oprob, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end] - @test all(isapprox.(ss, - solve(oprob_sparse, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) - @test all(isapprox.(ss, - solve(oprob_sparse_jac, Rosenbrock23(); abstol = 1e-10, reltol = 1e-10).u[end]; - rtol = 0.0001)) -end - -# Splitting parameters by position +# Confirms parameters can be inputed in [pV; pE] and (pV, pE) form. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] From 19c9a67cd2d5feff4cffc2c0f37ba8ccbd3a895f Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 14:30:00 -0400 Subject: [PATCH 074/134] Remove SplitApplyCombine dependency --- Project.toml | 1 - src/Catalyst.jl | 1 - 2 files changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index 3891b7fc45..3c9f94dfd1 100644 --- a/Project.toml +++ b/Project.toml @@ -17,7 +17,6 @@ Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 2cdac847cc..82eb7a2567 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -8,7 +8,6 @@ using SparseArrays, DiffEqBase, Reexport using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump -using SplitApplyCombine # ModelingToolkit imports and convenience functions we use using ModelingToolkit From 146d90ad46391635ca402ee6f18e7b2881a4f5a9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 14:55:28 -0400 Subject: [PATCH 075/134] More tests --- .../spatial_reactions.jl | 1 + .../lattice_reaction_systems.jl | 190 ++++++++++++++++++ 2 files changed, 191 insertions(+) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 77e8a4d9e0..64b833d66b 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -60,6 +60,7 @@ end ModelingToolkit.parameters(tr::TransportReaction) = convert(Vector{BasicSymbolic{Real}}, Symbolics.get_variables(tr.rate)) # Gets the species in a transport reaction. +species(tr::TransportReaction) = [tr.species] spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index e69de29bb2..25c3da30b6 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -0,0 +1,190 @@ +### Preparations ### + +# Fetch packages. +using Catalyst, Graphs, Test + +# Pre declares a grid. +grid = Graphs.grid([2, 2]) + +### Tests LatticeReactionSystem Getters Correctness ### + +# Test case 1. +let + rs = @reaction_network begin + (p, 1), 0 <--> X + end + tr = @transport_reaction d X + lrs = LatticeReactionSystem(rs, tr, grid) + + @test ModelingToolkit.getname.(species(lrs)) == [:X] + @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X] + @test ModelingToolkit.getname.(parameters(lrs)) == [:p, :d] + @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:p] + @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:d] +end + +# Test case 2. +let + rs = @reaction_network begin + @parameters pX [edgeparameter=true] pY dX [edgeparameter=true] dY + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + end + tr_1 = @transport_reaction dX X + tr_2 = @transport_reaction dY Y + lrs = LatticeReactionSystem(rs, [tr_1, tr_2], grid) + + @test ModelingToolkit.getname.(species(lrs)) == [:X, :Y] + @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X, :Y] + @test ModelingToolkit.getname.(parameters(lrs)) == [:pX, :pY, :dX, :dY] + @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:pY, :dY] + @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:pX, :dX] +end + +# Test case 3. +let + rs = @reaction_network begin + @parameters dX p + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + end + tr_1 = @transport_reaction dX X + lrs = LatticeReactionSystem(rs, tr_1, grid) + + @test ModelingToolkit.getname.(species(lrs)) == [:X, :Y] + @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X] + @test ModelingToolkit.getname.(parameters(lrs)) == [:dX, :p, :pX, :pY] + @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:dX, :p, :pX, :pY] + @test ModelingToolkit.getname.(edge_parameters(lrs)) == [] +end + +# Test case 4. +let + rs = @reaction_network begin + @species W(t) + @parameters pX pY dX [edgeparameter=true] dY + (pX, 1), 0 <--> X + (pY, 1), 0 <--> Y + (pZ, 1), 0 <--> Z + (pV, 1), 0 <--> V + end + @unpack dX, X, V = rs + @parameters dV dW + @variables t + @species W(t) + tr_1 = TransportReaction(dX, X) + tr_2 = @transport_reaction dY Y + tr_3 = @transport_reaction dZ Z + tr_4 = TransportReaction(dV, V) + tr_5 = TransportReaction(dW, W) + lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) + + @test ModelingToolkit.getname.(species(lrs)) == [:W, :X, :Y, :Z, :V] + @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X, :Y, :Z, :V, :W] + @test ModelingToolkit.getname.(parameters(lrs)) == [:pX, :pY, :dX, :dY, :pZ, :pV, :dZ, :dV, :dW] + @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:pX, :pY, :dY, :pZ, :pV] + @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX, :dZ, :dV, :dW] +end + +### Tests Spatial Reactions Getters Correctness ### + +# Test case 1. +let + tr_1 = @transport_reaction dX X + tr_2 = @transport_reaction dY1*dY2 Y + @test ModelingToolkit.getname.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] + @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] + @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] +end + +# Test case 2. +let + rs = @reaction_network begin + @species X(t) Y(t) + @parameters dX dY1 dY2 + end + @unpack X, Y, dX, dY1, dY2 = rs + tr_1 = TransportReaction(dX, X) + tr_2 = TransportReaction(dY1*dY2, Y) + @test isequal(species(tr_1), [X]) + @test isequal(species(tr_1), [X]) + @test isequal(spatial_species(tr_2), [Y]) + @test isequal(spatial_species(tr_2), [Y]) + @test isequal(parameters(tr_1), [dX]) + @test isequal(parameters(tr_2), [dY1, dY2]) +end + +### Test Interpolation ### + +# Does not currently work. The 3 tr_macro_ lines generate errors. +# Test case 1. +# let +# rs = @reaction_network begin +# @species X(t) Y(t) Z(t) +# @parameters dX dY1 dY2 dZ +# end +# @unpack X, Y, Z, dX, dY1, dY2, dZ = rs +# rate1 = dX +# rate2 = dY1*dY2 +# species3 = Z +# tr_1 = TransportReaction(dX, X) +# tr_2 = TransportReaction(dY1*dY2, Y) +# tr_2 = TransportReaction(dZ, Z) +# tr_macro_1 = @transport_reaction $dX X +# tr_macro_2 = @transport_reaction $(rate2) Y +# tr_macro_3 = @transport_reaction dZ $species3 +# +# @teest isequal(tr_1, tr_macro_1) +# @teest isequal(tr_2, tr_macro_2) +# @teest isequal(tr_3, tr_macro_3) +# end + +### Tests Error generation ### + +# Network where diffusion species is not declared in non-spatial network. +let + rs = @reaction_network begin + (p, d), 0 <--> X + end + tr = @transport_reaction D Y + @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) +end + +# Network where the rate depend on a species +let + rs = @reaction_network begin + @species Y(t) + (p, d), 0 <--> X + end + tr = @transport_reaction D*Y X + @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) +end + +# Network with edge parameter in non-spatial reaction rate. +let + rs = @reaction_network begin + @parameters p [edgeparameter=true] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) +end + +# Network where metadata has been added in rs (which is not seen in transport reaction). +let + rs = @reaction_network begin + @species X(t) [description="Species with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) + + rs = @reaction_network begin + @parameters D [description="Parameter with added metadata"] + (p, d), 0 <--> X + end + tr = @transport_reaction D X + @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) +end + From 2219eced752447cbd106481ad342966ed114262a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 15:39:53 -0400 Subject: [PATCH 076/134] fix --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 25c3da30b6..90088e4798 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -26,7 +26,7 @@ end # Test case 2. let rs = @reaction_network begin - @parameters pX [edgeparameter=true] pY dX [edgeparameter=true] dY + @parameters pX pY dX [edgeparameter=true] dY (pX, 1), 0 <--> X (pY, 1), 0 <--> Y end @@ -37,8 +37,8 @@ let @test ModelingToolkit.getname.(species(lrs)) == [:X, :Y] @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X, :Y] @test ModelingToolkit.getname.(parameters(lrs)) == [:pX, :pY, :dX, :dY] - @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:pY, :dY] - @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:pX, :dX] + @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:pX, :pY, :dY] + @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX] end # Test case 3. From b7831fb80ee46f9c41d57467deacdf058b3c5421 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 14 Sep 2023 23:57:27 -0400 Subject: [PATCH 077/134] Additional tests --- .../spatial_reactions.jl | 8 +-- .../lattice_reaction_systems.jl | 67 ++++++++++++++++++- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 64b833d66b..8f4ab6099f 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -49,7 +49,7 @@ function make_transport_reaction(rateex, species) parameters = []; find_parameters_in_rate!(parameters, rateex) quote - @parameters $(parameters...) + $(isempty(parameters) ? nothing : :(@parameters $(parameters...))) @variables t @species $(species)(t) TransportReaction($rateex, $species) @@ -81,10 +81,10 @@ equivalent_metadata(p1, p2) = isempty(setdiff(p1.metadata, p2.metadata, [Catalys # Loops through a rate and extract all parameters. function find_parameters_in_rate!(parameters, rateex::ExprValues) if rateex isa Symbol - if !(rateex in [:ℯ, :pi, :π]) - push!(parameters, rateex) - elseif rateex in [:t, :∅, forbidden_symbols_error...] + if rateex in [:t, :∅, :im, :nothing, CONSERVED_CONSTANT_SYMBOL] error("Forbidden term $(rateex) used in transport reaction rate.") + elseif !(rateex in [:ℯ, :pi, :π]) + push!(parameters, rateex) end elseif rateex isa Expr # note, this (correctly) skips $(...) expressions diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 90088e4798..0aad5f08ae 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -24,6 +24,17 @@ let end # Test case 2. +let + rs = @reaction_network begin + @parameters p1 p2 [edgeparameter=true] + end + @unpack p1, p2 = rs + + @test !isedgeparameter(p1) + @test isedgeparameter(p2) +end + +# Test case 3. let rs = @reaction_network begin @parameters pX pY dX [edgeparameter=true] dY @@ -41,7 +52,7 @@ let @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX] end -# Test case 3. +# Test case 4. let rs = @reaction_network begin @parameters dX p @@ -58,7 +69,7 @@ let @test ModelingToolkit.getname.(edge_parameters(lrs)) == [] end -# Test case 4. +# Test case 5. let rs = @reaction_network begin @species W(t) @@ -86,6 +97,17 @@ let @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX, :dZ, :dV, :dW] end +# Test case 6. +let + rs = @reaction_network customname begin + (p, 1), 0 <--> X + end + tr = @transport_reaction d X + lrs = LatticeReactionSystem(rs, tr, grid) + + @test nameof(lrs) == :customname +end + ### Tests Spatial Reactions Getters Correctness ### # Test case 1. @@ -115,6 +137,47 @@ let @test isequal(parameters(tr_2), [dY1, dY2]) end +### Tests Spatial Reactions Generation ### + +# Tests TransportReaction with non-trivial rate. +let + rs = @reaction_network begin + @parameters dV dE [edgeparameter=true] + (p,1), 0 <--> X + end + @unpack dV, dE, X = rs + + tr = TransportReaction(dV*dE, X) + @test isequal(tr.rate, dV*dE) +end + +# Tests transport_reactions function for creating TransportReactions. +let + rs = @reaction_network begin + @parameters d + (p,1), 0 <--> X + end + @unpack d, X = rs + trs = transport_reactions([(d, X), (d, X)]) + @test isequal(trs[1], trs[2]) +end + +# Test reactions with constants in rate. +let + @variables t + @species X(t) Y(t) + + tr_1 = TransportReaction(1.5, X) + tr_1_macro = @transport_reaction 1.5 X + @test isequal(tr_1.rate, tr_1_macro.rate) + @test isequal(tr_1.species, tr_1_macro.species) + + tr_2 = TransportReaction(π, Y) + tr_2_macro = @transport_reaction π Y + @test isequal(tr_2.rate, tr_2_macro.rate) + @test isequal(tr_2.species, tr_2_macro.species) +end + ### Test Interpolation ### # Does not currently work. The 3 tr_macro_ lines generate errors. From 09a85846d9437aeb0925eebc41313df20b18bfe2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 12:21:57 -0400 Subject: [PATCH 078/134] Update performance benchmarks (not run, by saved in repo) --- ...attice_reaction_systems_ODE_performance.jl | 54 +++++++------------ test/spatial_test_networks.jl | 2 +- 2 files changed, 20 insertions(+), 36 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl index e416ab6fb2..c3c801c603 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl @@ -14,7 +14,7 @@ include("../spatial_test_networks.jl") # Current not used, simply here for reference. # Useful when attempting to optimise workflow. -# using BenchmarkTools +# using BenchmarkTools, Sundials # runtime_reduction_margin = 10.0 # Small grid, small, non-stiff, system. @@ -26,7 +26,7 @@ let oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.00060 + runtime_target = 0.00027 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target @@ -41,43 +41,27 @@ let oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.26 + runtime_target = 0.12 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end # Small grid, small, stiff, system. - let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + runtime_target = 0.013 + runtime = minimum((@benchmark solve($oprob, CVODE_BDF(linear_solver=:GMRES))).times) / 1000000000 println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end -# Medium grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - # Large grid, small, stiff, system. let lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) @@ -85,10 +69,10 @@ let pV = brusselator_p pE = [:dX => 0.2] oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + runtime_target = 11. + runtime = minimum((@benchmark solve($oprob, CVODE_BDF(linear_solver=:GMRES))).times) / 1000000000 println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end @@ -118,7 +102,7 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.0016 + runtime_target = 0.0012 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target @@ -149,7 +133,7 @@ let oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - runtime_target = 0.67 + runtime_target = 0.56 runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target @@ -172,11 +156,11 @@ let ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + oprob = ODEProblem(lrs, u0, (0.0, 50.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + runtime_target = 0.61 + runtime = minimum((@benchmark solve($oprob, CVODE_BDF(linear_solver=:GMRES))).times) / 1000000000 println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end @@ -198,11 +182,11 @@ let ] pV = sigmaB_p pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) # Time reduced from 50.0 (which casues Julai to crash). + @test SciMLBase.successful_retcode(solve(oprob, CVODE_BDF(linear_solver=:GMRES))) - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + runtime_target = 59. + runtime = minimum((@benchmark solve($oprob, CVODE_BDF(linear_solver=:GMRES))).times) / 1000000000 println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") @test runtime < runtime_reduction_margin * runtime_target end diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 64f0c3ae0c..11f9d5b2d3 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -144,7 +144,7 @@ sigmaB_system = @reaction_network begin end sigmaB_p = [:kBw => 3600, :kDw => 18, :kB1 => 3600, :kB2 => 3600, :kB3 => 3600, :kB4 => 1800, :kB5 => 3600, - :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 12, + :kD1 => 18, :kD2 => 18, :kD3 => 18, :kD4 => 1800, :kD5 => 18, :kK1 => 36, :kK2 => 6, :kP => 180, :kDeg => 0.7, :v0 => 0.4, :F => 30, :K => 0.2, :λW => 4, :λV => 4.5] sigmaB_u0 = [ From a2f8088d54a3dba10fe383b6b06027ef8cf17f50 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 07:26:32 -0400 Subject: [PATCH 079/134] Spatial Jump Implementation --- src/compound.jl | 16 +- src/lattice_reaction_system_diffusion.jl | 501 ++++++++++++++++++ .../lattice_reaction_systems.jl | 1 + 3 files changed, 510 insertions(+), 8 deletions(-) create mode 100644 src/lattice_reaction_system_diffusion.jl diff --git a/src/compound.jl b/src/compound.jl index 9f2ac5bca2..01c9081232 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl new file mode 100644 index 0000000000..15c1ec9bbd --- /dev/null +++ b/src/lattice_reaction_system_diffusion.jl @@ -0,0 +1,501 @@ +### Diffusion Reaction Structure. ### + +# Implements the diffusionparameter metadata field. +struct DiffusionParameter end +Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter + +isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) +function isdiffusionparameter(x, default = false) + p = Symbolics.getparent(x, nothing) + p === nothing || (x = p) + Symbolics.getmetadata(x, DiffusionParameter, default) +end + +# Abstract spatial reaction structures. +abstract type AbstractSpatialReaction end + +# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. +# Currently only permit constant rates. +struct DiffusionReaction <: AbstractSpatialReaction + """The rate function (excluding mass action terms). Currently only constants supported""" + rate::Num + """The species that is subject to difusion.""" + species::Num + """A symbol representation of the species that is subject to difusion.""" + species_sym::Symbol # Required for identification in certain cases. + + # Creates a diffusion reaction. + function DiffusionReaction(rate::Num, species::Num) + new(rate, species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Number, species::Num) + new(Num(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Symbol, species::Num) + new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Num, species::Symbol) + new(rate, Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Number, species::Symbol) + new(Num(rate), Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Symbol, species::Symbol) + new(Symbolics.variable(rate), Symbolics.variable(species), species) + end +end +# Creates a vector of DiffusionReactions. +function diffusion_reactions(diffusion_reactions) + [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] +end +# Gets the parameters in a diffusion reaction. +ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) + +### Lattice Reaction Network Structure ### +# Desribes a spatial reaction network over a graph. +struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs + """The reaction system within each comaprtment.""" + rs::ReactionSystem + """The spatial reactions defined between individual nodes.""" + spatial_reactions::Vector{<:AbstractSpatialReaction} + """The graph on which the lattice is defined.""" + lattice::DiGraph + + # Derrived values. + """The number of compartments.""" + nC::Int64 + """The number of edges.""" + nE::Int64 + """The number of species.""" + nS::Int64 + """Whenever the initial input was a di graph.""" + init_digraph::Bool + + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::DiGraph; init_digraph = true) + return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), + length(species(rs)), init_digraph) + end + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); + init_digraph = false) + end + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reaction::AbstractSpatialReaction, + lattice::Graphs.AbstractGraph) + return LatticeReactionSystem(rs, [spatial_reaction], lattice) + end +end +# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). +function graph_to_digraph(g1) + g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), + reverse.(edges(g1)))), :, + 1)[:]) + add_vertices!(g2, nv(g1) - nv(g2)) + return g2 +end +# Gets the species of a lattice reaction system. +species(lrs::LatticeReactionSystem) = species(lrs.rs) +function diffusion_species(lrs::LatticeReactionSystem) + filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), + species(lrs.rs)) +end + +# Gets the parameters in a lattice reaction system. +function ModelingToolkit.parameters(lrs::LatticeReactionSystem) + unique(vcat(parameters(lrs.rs), + Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) +end +function compartment_parameters(lrs::LatticeReactionSystem) + filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) +end +function diffusion_parameters(lrs::LatticeReactionSystem) + filter(p -> is_spatial_param(p, lrs), parameters(lrs)) +end + +# Checks whenever a parameter is a spatial parameter or not. +function is_spatial_param(p, lrs) + hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && + (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. + return (any(isequal.(p, parameters(lrs.rs))) ? false : true) +end + +### Processes Input u0 & p ### + +# From u0 input, extracts their values and store them in the internal format. +function lattice_process_u0(u0_in, u0_symbols, nC) + u0 = lattice_process_input(u0_in, u0_symbols, nC) + check_vector_lengths(u0, nC) + expand_component_values(u0, nC) +end + +# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. +function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) + pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) + pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) + pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) + lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) + check_vector_lengths(pC, lrs.nC) + check_vector_lengths(pD, lrs.nE) + return pC, pD +end + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +function split_parameters(ps::Vector{<:Number}, args...) + error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") +end +function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, + p_diff_symbols::Vector) + pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] + pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] + (sum(length.([pC_in, pD_in])) != length(ps)) && + error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") + return pC_in, pD_in +end + +# If the input is given in a map form, teh vector needs sorting and the first value removed. +function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) + (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && + error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") + sorted_input = sort(input; + by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) + return lattice_process_input(last.(sorted_input), symbols, args...) +end +# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). +function lattice_process_input(input::Matrix{<:Number}, args...) + lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) +end +function lattice_process_input(input::Array{<:Number, 3}, args...) + error("3 dimensional array parameter inpur currently not supported.") +end +function lattice_process_input(input::Vector{<:Any}, args...) + isempty(input) ? Vector{Vector{Float64}}() : + lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], + args...) +end +lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input +function check_vector_lengths(input::Vector{<:Vector}, n) + isempty(setdiff(unique(length.(input)), [1, n])) || + error("Some inputs where given values of inappropriate length.") +end + +# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. +function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, + lrs::LatticeReactionSystem) + (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) +end + +# For a set of input values on the given forms, and their symbolics, convert into a dictionary. +vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) +# Produces a dictionary with all parameter values. +function param_dict(pC, pD, lrs) + merge(vals_to_dict(compartment_parameters(lrs), pC), + vals_to_dict(diffusion_parameters(lrs), pD)) +end + +# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). +function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, + lrs::LatticeReactionSystem) + param_value_dict = param_dict(pC, pD, lrs) + return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), + param_value_dict, lrs.nE)) + for s in diffusion_species(lrs)] +end +function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), + lrs.spatial_reactions) + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. + return rates[1].rate +end +function compute_diffusion_rates(rate_law::Num, + param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) + relevant_parameters = Symbolics.get_variables(rate_law) + if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) + return [ + substitute(rate_law, + Dict(p => param_value_dict[p][1] for p in relevant_parameters)), + ] + end + return [substitute(rate_law, + Dict(p => get_component_value(param_value_dict[p], idxE) + for p in relevant_parameters)) for idxE in 1:nE] +end + +### ODEProblem ### + +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; + jac = true, sparse = jac, kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + ofun = build_odefunction(lrs, pC, pD, jac, sparse) + return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) +end + +# Builds an ODEFunction for a spatial ODEProblem. +function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) + # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) + diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) + diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] + for diff_rates in diffusion_rates_speciesmap] + + f = build_f(ofunc, pC, diffusion_rates, lrs) + jac_prototype = (use_jac || sparse) ? + build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, + lrs; set_nonzero = use_jac) : nothing + jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) +end + +# Builds the forcing (f) function for a reaction system on a lattice. +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, + diffusion_rates::Vector, + lrs::LatticeReactionSystem) + leaving_rates = zeros(length(diffusion_rates), lrs.nC) + for (s_idx, rates) in enumerate(last.(diffusion_rates)), + (e_idx, e) in enumerate(edges(lrs.lattice)) + + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + end + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + + return function (du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(lrs.nC) + ofunc((@view du[get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) + end + + # Updates for spatial diffusion reactions. + for (s_idx, (s, rates)) in enumerate(diffusion_rates) + for comp_i::Int64 in 1:(lrs.nC) + du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, s, + lrs.nS)] + end + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges + du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * + u[get_index(edge.src, s, + lrs.nS)] + end + end + end +end + +# Builds the Jacobian function for a reaction system on a lattice. +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, + lrs::LatticeReactionSystem, + jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + sparse::Bool) + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) + add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : + J -> (J .+= Matrix(jac_prototype))) + + return function (J, u, p, t) + # Because of weird stuff where the Jacobian is not reset that I don't understand properly. + reset_J_vals(J) + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(lrs.nC) + ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) + end + + # Updates for the spatial reactions. + add_diff_J_vals(J) + end +end + +# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + diffusion_rates, lrs::LatticeReactionSystem; + set_nonzero = false) + diff_species = first.(diffusion_rates) + # Gets list of indexes for species that diffuse, but are invovled in no other reaction. + only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) + for s in 1:(lrs.nS)] + + # Declares sparse array content. + J_colptr = fill(1, lrs.nC * lrs.nS + 1) + J_nzval = fill(0.0, + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_rates)) + J_rowval = fill(0, length(J_nzval)) + + # Finds filled elements. + for comp in 1:(lrs.nC), s in 1:(lrs.nS) + col_idx = get_index(comp, s, lrs.nS) + + # Column values. + local_elements = in(s, diff_species) * + (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) + diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements + + # Row values. + rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ + (comp - 1) * lrs.nS + if in(s, diff_species) + # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. + diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s + split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) + isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) + rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, + diffusion_rows[split_idx:end]) + if only_diff[s] + split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) + isnothing(split_idx) && (split_idx = length(rows) + 1) + insert!(rows, split_idx, get_index(comp, s, lrs.nS)) + end + end + J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows + end + + # Set element values. + if !set_nonzero + J_nzval .= 1.0 + else + for (s_idx, (s, rates)) in enumerate(diffusion_rates), + (e_idx, edge) in enumerate(edges(lrs.lattice)) + + col_start = J_colptr[get_index(edge.src, s, lrs.nS)] + col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 + column_view = @view J_rowval[col_start:col_end] + + # Updates the source value. + val_idx_src = col_start + + findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 + J_nzval[val_idx_src] -= get_component_value(rates, e_idx) + + # Updates the destination value. + val_idx_dst = col_start + + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 + J_nzval[val_idx_dst] += get_component_value(rates, e_idx) + end + end + + return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) +end + +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates a DiscreteProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), + checks = false, kwargs...) + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || + error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + hopping_constants = make_hopping_constants(dprob, lrs) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, + checks) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + spatial_system = lrs.lattice, save_positions = (true, false)) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], + lrs)) + all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] + for s in species(lrs)] + if length.(all_diff_rates) == 1 + return Catalyst.matrix_expand_component_values(all_diff_rates, + length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants + end +end + +# Creates the mass action jumps from a discrete problem and a lattice reaction system. +function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) + any(length.(dprob.p[1]) .> 1) && + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + (length(___jprob.variable_jumps) != 0) && + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump +end + +### Accessing State & Parameter Array Values ### + +# Gets the index in the u array of species s in compartment comp (when their are nS species). +get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s +# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). +get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) + +# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64) + get_component_value(values[component_idx], location_idx) +end +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64, location_types::Vector{Bool}) + get_component_value(values[component_idx], location_idx, location_types[component_idx]) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64) + get_component_value(values, location_idx, length(values) == 1) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64, + location_type::Bool) + location_type ? values[1] : values[location_idx] +end +# Converts a vector of vectors to a long vector. +function expand_component_values(values::Vector{<:Vector}, n) + vcat([get_component_value.(values, comp) for comp in 1:n]...) +end +function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) + vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) +end +# Creates a view of the pC vector at a given comaprtment. +function view_pC_vector(pC, comp, pC_location_types, pC_idxs) + mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) +end +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). +function matrix_expand_component_values(values::Vector{<:Vector}, n) + reshape(expand_component_values(values, n), length(values), n) +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 0aad5f08ae..9b7308bc94 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -251,3 +251,4 @@ let @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) end +### Spatial Jump System Tests ### From 1d187db591e3c04b34108eaedb1ada4af8105e32 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 10:03:07 -0400 Subject: [PATCH 080/134] Spatial tests file reordering --- test/runtests.jl | 1 + ...ttice_reaction_systems_ODEs_performance.jl | 212 ++++++++++++ .../lattice_reaction_systems_jumps.jl | 309 ++++++++++++++++++ 3 files changed, 522 insertions(+) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl diff --git a/test/runtests.jl b/test/runtests.jl index bd01f84d5a..30c1a1f9f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,6 +41,7 @@ using SafeTestsets @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl new file mode 100644 index 0000000000..3d705e0eb9 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -0,0 +1,212 @@ +# Not actually run in CI, but useful for reference of ODE simulation performance across updates. + +### Preparations ### + +# Fetch packages. +using OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Runtime Checks ### +# Current timings are taken from the SciML CI server. +# Current not used, simply here for reference. +# Useful when attempting to optimise workflow. + +# using BenchmarkTools +# runtime_reduction_margin = 10.0 + +# Small grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00060 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.26 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, small, stiff, system. + +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.17 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Medium grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 2.3 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 170.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + small_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.0016 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + large_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.67 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.019 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 35.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl new file mode 100644 index 0000000000..fc3aae2045 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -0,0 +1,309 @@ +### Preparations ### + +# Fetch packages. +using JumpProcesses +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Correctness Tests ### + +# Tests that there are no errors during runs. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 1.0, + :R => 0.0, + ]) + u0_3 = make_values_int([ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_4 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs)))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + solve(jprob, SSAStepper()) + end + end + end + end + end +end + +### Input Handling Tests ### + +# Tests that the correct hopping rates and initial conditions are generated. +# In this base case, hoppping rates should be on the form D_{s,i,j}. +let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + + # Prepares various u0 input types. + u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] + u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] + u0_3 = [1.0, 2.0, 3.0] + u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] + u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + + # Checks hopping rates and u0 are correct. + true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + # Provides parameters as a tupple. + for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end + +### Hoping Rates Computation Tests ### + +# Currently not in use, but will be added as more cases are enabled. +if false + # Tests hopping rates of the form D_{s,i,j} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_3] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i} + let + # Prepares special system and diffusion reactions (depending on compartments). + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pD = [] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s}*L_{i,j} + # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). + let + SIR_system_special = @reaction_network begin + @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack dS, dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1, :dI => 0.01] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, ] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0, :dS => 0.005] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] + pD_3 = [0.005, 2.0] + pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i}*L_{i,j} + let + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI connection_d = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] + pD_3 = [2.0] + pD_4 = [fill(0.2, ne(small_2d_grid))] + pD_5 = [fill(0.2, 1, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end \ No newline at end of file From 11734efc7ad8b7a5a3e10714ff67758289a9e743 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 11:08:34 -0400 Subject: [PATCH 081/134] testupdate --- .../lattice_reaction_systems_ODEs_performance.jl | 2 +- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index 3d705e0eb9..ebc5130dac 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -11,7 +11,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Runtime Checks ### # Current timings are taken from the SciML CI server. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index fc3aae2045..664a9c7f7f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -9,7 +9,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Correctness Tests ### From cc370ea9f24ba1076d3711d8ce3912c98cd89a94 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 12:16:28 -0400 Subject: [PATCH 082/134] testupdate --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 664a9c7f7f..5aa7cba1c3 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -268,7 +268,7 @@ if false α, S + I --> 2I β, I --> R end - @unpack comp_dS, comp_dI connection_d = SIR_system_special + @unpack comp_dS, comp_dI, connection_d = SIR_system_special SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] # Prepares the system. From bcee84e457b6d85658347b704978355f385f0d4c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 18:14:35 +0200 Subject: [PATCH 083/134] Fix stableRNG usage --- src/lattice_reaction_system_diffusion.jl | 1 + .../lattice_reaction_systems_ODEs_performance.jl | 4 ---- .../lattice_reaction_systems_jumps.jl | 4 ---- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 15c1ec9bbd..a75c54cadb 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -445,6 +445,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, combinatoric_ratelaws, checks) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index ebc5130dac..e416ab6fb2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -6,10 +6,6 @@ using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 5aa7cba1c3..eedb26ed8b 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -4,10 +4,6 @@ using JumpProcesses using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") From 4558d0d73f0903b33edb85f930c16e8851485884 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 12:19:28 -0400 Subject: [PATCH 084/134] Update src/lattice_reaction_system_diffusion.jl Co-authored-by: Vasily Ilin --- src/lattice_reaction_system_diffusion.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index a75c54cadb..09adefe8a4 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -459,7 +459,11 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump +function make_majumps(lrs::LatticeReactionSystem) + jumps = assemble_jumps(lrs.rs) + if !(jumps[end] isa MassActionJump) + error("Only MassAction Jumps are currently allowed in spatial simulation.") + return jumps end ### Accessing State & Parameter Array Values ### From cfe5f88a8073112145bf4ce12d8088027b186d73 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:18:15 +0200 Subject: [PATCH 085/134] add ABC test and reset make_majumps --- src/lattice_reaction_system_diffusion.jl | 35 ++++++------ .../lattice_reaction_systems_jumps.jl | 55 ++++++++++++++++++- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 09adefe8a4..5b7cd4670c 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -415,8 +415,8 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") hopping_constants = make_hopping_constants(dprob, lrs) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, - checks) + #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, @@ -445,27 +445,28 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) + first.(dprob.p[1])) ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") -function make_majumps(lrs::LatticeReactionSystem) - jumps = assemble_jumps(lrs.rs) - if !(jumps[end] isa MassActionJump) - error("Only MassAction Jumps are currently allowed in spatial simulation.") - return jumps + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump end +# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) +# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) +# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") +# return jumps +# end + ### Accessing State & Parameter Array Values ### # Gets the index in the u array of species s in compartment comp (when their are nS species). @@ -503,4 +504,4 @@ end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) reshape(expand_component_values(values, n), length(values), n) -end +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index eedb26ed8b..430468828f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -302,4 +302,57 @@ if false @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates end end -end \ No newline at end of file +end + +using Catalyst, JumpProcesses, Graphs +### ABC Model Test (from JumpProcesses) ### +let + # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). + Nsims = 100 + reltol = 0.05 + non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations + dim = 1 + linear_size = 5 + dims = Tuple(repeat([linear_size], dim)) + domain_size = 1.0 #μ-meter + mesh_size = domain_size / linear_size + rates = [0.1 / mesh_size, 1.0] + diffusivity = 1.0 + num_species = 3 + + # Make model. + rn = @reaction_network begin + (kB,kD), A + B <--> C + end + srs = diffusion_reactions([(:D, :A), (:D, :B), (:D, :C)]) + lattice = Graphs.grid(dims) + lrs = LatticeReactionSystem(rn, srs, lattice) + + + # Set simulation parameters and create problems + u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] + tspan = (0.0, 10.0) + pC = [:kB => rates[1], :kD => rates[2]] + pD = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pC, pD)) + jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. + + # Tests. + function get_mean_end_state(jump_prob, Nsims) + end_state = zeros(size(jump_prob.prob.u0)) + for i in 1:Nsims + sol = solve(jump_prob, SSAStepper()) + end_state .+= sol.u[end] + end + end_state / Nsims + end + for jprob in jump_problems + solution = solve(jprob, SSAStepper()) + mean_end_state = get_mean_end_state(jprob, Nsims) + mean_end_state = reshape(mean_end_state, num_species, num_nodes) + diff = sum(mean_end_state, dims = 2) - non_spatial_mean + for (i, d) in enumerate(diff) + @test abs(d) < reltol * non_spatial_mean[i] + end + end +end From e1cd5682ffaf4a3f8482ce0bc4f7e9d95092aeb9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:19:13 +0200 Subject: [PATCH 086/134] remove tests for future functionality --- .../lattice_reaction_systems_jumps.jl | 194 ------------------ 1 file changed, 194 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 430468828f..930fb972a6 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -110,200 +110,6 @@ let end end -### Hoping Rates Computation Tests ### - -# Currently not in use, but will be added as more cases are enabled. -if false - # Tests hopping rates of the form D_{s,i,j} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_3] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i} - let - # Prepares special system and diffusion reactions (depending on compartments). - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pD = [] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s}*L_{i,j} - # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). - let - SIR_system_special = @reaction_network begin - @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack dS, dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1, :dI => 0.01] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, ] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0, :dS => 0.005] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] - pD_3 = [0.005, 2.0] - pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i}*L_{i,j} - let - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI, connection_d = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] - pD_3 = [2.0] - pD_4 = [fill(0.2, ne(small_2d_grid))] - pD_5 = [fill(0.2, 1, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end -end - using Catalyst, JumpProcesses, Graphs ### ABC Model Test (from JumpProcesses) ### let From c6776fcbfc324e4da25f4c3e1eb17484f1e2cd6a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 14:45:59 +0200 Subject: [PATCH 087/134] explanatory note --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 5b7cd4670c..24f8f5332b 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -460,7 +460,7 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg error("Currently, for lattice jump simulations, variable rate jumps are not supported.") return ___jprob.massaction_jump end - +# We would want something like this, but it errors if used. # function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) # jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) # (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") From abc2b7db8217bc1eccd2957e1911b2407d7412b0 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 10:37:32 -0400 Subject: [PATCH 088/134] Drop formatting changes in `compound.jl`. --- src/compound.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compound.jl b/src/compound.jl index 01c9081232..9f2ac5bca2 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound From 3af9d6f2d7c97dea47168ae18f4e27aa2a80adc7 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 19:29:41 -0400 Subject: [PATCH 089/134] Fix test. --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 930fb972a6..9ecca8a319 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -119,6 +119,7 @@ let non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations dim = 1 linear_size = 5 + num_nodes = linear_size^dim dims = Tuple(repeat([linear_size], dim)) domain_size = 1.0 #μ-meter mesh_size = domain_size / linear_size From 7aee9795592d4b26213ef381f35e8f87b529a472 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:04:21 -0400 Subject: [PATCH 090/134] Construct massaction jumps without building a `JumpProblem`. --- src/lattice_reaction_system_diffusion.jl | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 24f8f5332b..c3d72424e3 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -414,12 +414,14 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator checks = false, kwargs...) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + if any(length.(dprob.p[1]) .> 1) + error("Spatial reaction rates are currently not supported in lattice jump simulations.") + end hopping_constants = make_hopping_constants(dprob, lrs) - #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) - return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + majumps_ = make_majumps(___dprob, lrs.rs) + return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) end @@ -445,27 +447,18 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) - any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") - ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) - (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump -end -# We would want something like this, but it errors if used. -# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) -# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) -# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") -# return jumps -# end +function make_majumps(non_spatial_prob, rs::ReactionSystem) + prob = non_spatial_prob + + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + eqs = equations(js) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) + return majumps +end ### Accessing State & Parameter Array Values ### From 0109d6275ca5421b5f4dd5c52aa1d22a444da490 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:07:03 -0400 Subject: [PATCH 091/134] Fix comment. --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index c3d72424e3..524e2e500d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -446,7 +446,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end end -# Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Creates the mass action jumps from a discrete problem and a reaction system. function make_majumps(non_spatial_prob, rs::ReactionSystem) prob = non_spatial_prob From dc983bb50180b1e69ab7e8ecdaa04a6a73e1ec6a Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 16:51:55 -0400 Subject: [PATCH 092/134] Make spatial SSA part up to date. --- src/Catalyst.jl | 3 +- src/lattice_reaction_system_diffusion.jl | 500 ------------------ .../spatial_Jump_systems.jl | 66 +++ ...ttice_reaction_systems_ODEs_performance.jl | 208 -------- .../lattice_reaction_systems_jumps.jl | 83 ++- test/spatial_test_networks.jl | 8 - 6 files changed, 101 insertions(+), 767 deletions(-) delete mode 100644 src/lattice_reaction_system_diffusion.jl create mode 100644 src/spatial_reaction_systems/spatial_Jump_systems.jl delete mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 82eb7a2567..878cdefeb2 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -108,7 +108,8 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters -# spatial lattice ode systems. +# spatial lattice ode and jump systems. include("spatial_reaction_systems/spatial_ODE_systems.jl") +include("spatial_reaction_systems/spatial_Jump_systems.jl") end # module diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl deleted file mode 100644 index 524e2e500d..0000000000 --- a/src/lattice_reaction_system_diffusion.jl +++ /dev/null @@ -1,500 +0,0 @@ -### Diffusion Reaction Structure. ### - -# Implements the diffusionparameter metadata field. -struct DiffusionParameter end -Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter - -isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) -function isdiffusionparameter(x, default = false) - p = Symbolics.getparent(x, nothing) - p === nothing || (x = p) - Symbolics.getmetadata(x, DiffusionParameter, default) -end - -# Abstract spatial reaction structures. -abstract type AbstractSpatialReaction end - -# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. -# Currently only permit constant rates. -struct DiffusionReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currently only constants supported""" - rate::Num - """The species that is subject to difusion.""" - species::Num - """A symbol representation of the species that is subject to difusion.""" - species_sym::Symbol # Required for identification in certain cases. - - # Creates a diffusion reaction. - function DiffusionReaction(rate::Num, species::Num) - new(rate, species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Number, species::Num) - new(Num(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Symbol, species::Num) - new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Num, species::Symbol) - new(rate, Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Number, species::Symbol) - new(Num(rate), Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Symbol, species::Symbol) - new(Symbolics.variable(rate), Symbolics.variable(species), species) - end -end -# Creates a vector of DiffusionReactions. -function diffusion_reactions(diffusion_reactions) - [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] -end -# Gets the parameters in a diffusion reaction. -ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) - -### Lattice Reaction Network Structure ### -# Desribes a spatial reaction network over a graph. -struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs - """The reaction system within each comaprtment.""" - rs::ReactionSystem - """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{<:AbstractSpatialReaction} - """The graph on which the lattice is defined.""" - lattice::DiGraph - - # Derrived values. - """The number of compartments.""" - nC::Int64 - """The number of edges.""" - nE::Int64 - """The number of species.""" - nS::Int64 - """Whenever the initial input was a di graph.""" - init_digraph::Bool - - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::DiGraph; init_digraph = true) - return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), - length(species(rs)), init_digraph) - end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); - init_digraph = false) - end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reaction::AbstractSpatialReaction, - lattice::Graphs.AbstractGraph) - return LatticeReactionSystem(rs, [spatial_reaction], lattice) - end -end -# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). -function graph_to_digraph(g1) - g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), - reverse.(edges(g1)))), :, - 1)[:]) - add_vertices!(g2, nv(g1) - nv(g2)) - return g2 -end -# Gets the species of a lattice reaction system. -species(lrs::LatticeReactionSystem) = species(lrs.rs) -function diffusion_species(lrs::LatticeReactionSystem) - filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), - species(lrs.rs)) -end - -# Gets the parameters in a lattice reaction system. -function ModelingToolkit.parameters(lrs::LatticeReactionSystem) - unique(vcat(parameters(lrs.rs), - Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) -end -function compartment_parameters(lrs::LatticeReactionSystem) - filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) -end -function diffusion_parameters(lrs::LatticeReactionSystem) - filter(p -> is_spatial_param(p, lrs), parameters(lrs)) -end - -# Checks whenever a parameter is a spatial parameter or not. -function is_spatial_param(p, lrs) - hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && - (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. - return (any(isequal.(p, parameters(lrs.rs))) ? false : true) -end - -### Processes Input u0 & p ### - -# From u0 input, extracts their values and store them in the internal format. -function lattice_process_u0(u0_in, u0_symbols, nC) - u0 = lattice_process_input(u0_in, u0_symbols, nC) - check_vector_lengths(u0, nC) - expand_component_values(u0, nC) -end - -# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. -function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) - pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) - pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) - pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) - lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) - check_vector_lengths(pC, lrs.nC) - check_vector_lengths(pD, lrs.nE) - return pC, pD -end - -# Splits parameters into those for the compartments and those for the connections. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps -function split_parameters(ps::Vector{<:Number}, args...) - error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") -end -function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, - p_diff_symbols::Vector) - pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] - pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] - (sum(length.([pC_in, pD_in])) != length(ps)) && - error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") - return pC_in, pD_in -end - -# If the input is given in a map form, teh vector needs sorting and the first value removed. -function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) - (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && - error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") - sorted_input = sort(input; - by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) - return lattice_process_input(last.(sorted_input), symbols, args...) -end -# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). -function lattice_process_input(input::Matrix{<:Number}, args...) - lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) -end -function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter inpur currently not supported.") -end -function lattice_process_input(input::Vector{<:Any}, args...) - isempty(input) ? Vector{Vector{Float64}}() : - lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], - args...) -end -lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input -function check_vector_lengths(input::Vector{<:Vector}, n) - isempty(setdiff(unique(length.(input)), [1, n])) || - error("Some inputs where given values of inappropriate length.") -end - -# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. -function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, - lrs::LatticeReactionSystem) - (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) -end - -# For a set of input values on the given forms, and their symbolics, convert into a dictionary. -vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) -# Produces a dictionary with all parameter values. -function param_dict(pC, pD, lrs) - merge(vals_to_dict(compartment_parameters(lrs), pC), - vals_to_dict(diffusion_parameters(lrs), pD)) -end - -# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, - lrs::LatticeReactionSystem) - param_value_dict = param_dict(pC, pD, lrs) - return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), - param_value_dict, lrs.nE)) - for s in diffusion_species(lrs)] -end -function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), - lrs.spatial_reactions) - (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. - return rates[1].rate -end -function compute_diffusion_rates(rate_law::Num, - param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) - relevant_parameters = Symbolics.get_variables(rate_law) - if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) - return [ - substitute(rate_law, - Dict(p => param_value_dict[p][1] for p in relevant_parameters)), - ] - end - return [substitute(rate_law, - Dict(p => get_component_value(param_value_dict[p], idxE) - for p in relevant_parameters)) for idxE in 1:nE] -end - -### ODEProblem ### - -# Creates an ODEProblem from a LatticeReactionSystem. -function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - ofun = build_odefunction(lrs, pC, pD, jac, sparse) - return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) -end - -# Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) - # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] - for diff_rates in diffusion_rates_speciesmap] - - f = build_f(ofunc, pC, diffusion_rates, lrs) - jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, - lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) -end - -# Builds the forcing (f) function for a reaction system on a lattice. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - diffusion_rates::Vector, - lrs::LatticeReactionSystem) - leaving_rates = zeros(length(diffusion_rates), lrs.nC) - for (s_idx, rates) in enumerate(last.(diffusion_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) - - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) - end - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - - return function (du, u, p, t) - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc((@view du[get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for spatial diffusion reactions. - for (s_idx, (s, rates)) in enumerate(diffusion_rates) - for comp_i::Int64 in 1:(lrs.nC) - du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, - lrs.nS)] - end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges - du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, - lrs.nS)] - end - end - end -end - -# Builds the Jacobian function for a reaction system on a lattice. -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - lrs::LatticeReactionSystem, - jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - sparse::Bool) - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) - add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : - J -> (J .+= Matrix(jac_prototype))) - - return function (J, u, p, t) - # Because of weird stuff where the Jacobian is not reset that I don't understand properly. - reset_J_vals(J) - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for the spatial reactions. - add_diff_J_vals(J) - end -end - -# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - diffusion_rates, lrs::LatticeReactionSystem; - set_nonzero = false) - diff_species = first.(diffusion_rates) - # Gets list of indexes for species that diffuse, but are invovled in no other reaction. - only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] - - # Declares sparse array content. - J_colptr = fill(1, lrs.nC * lrs.nS + 1) - J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_rates)) - J_rowval = fill(0, length(J_nzval)) - - # Finds filled elements. - for comp in 1:(lrs.nC), s in 1:(lrs.nS) - col_idx = get_index(comp, s, lrs.nS) - - # Column values. - local_elements = in(s, diff_species) * - (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) - diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) - J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements - - # Row values. - rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ - (comp - 1) * lrs.nS - if in(s, diff_species) - # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. - diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s - split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) - isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) - rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, - diffusion_rows[split_idx:end]) - if only_diff[s] - split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) - isnothing(split_idx) && (split_idx = length(rows) + 1) - insert!(rows, split_idx, get_index(comp, s, lrs.nS)) - end - end - J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows - end - - # Set element values. - if !set_nonzero - J_nzval .= 1.0 - else - for (s_idx, (s, rates)) in enumerate(diffusion_rates), - (e_idx, edge) in enumerate(edges(lrs.lattice)) - - col_start = J_colptr[get_index(edge.src, s, lrs.nS)] - col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 - column_view = @view J_rowval[col_start:col_end] - - # Updates the source value. - val_idx_src = col_start + - findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 - J_nzval[val_idx_src] -= get_component_value(rates, e_idx) - - # Updates the destination value. - val_idx_dst = col_start + - findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 - J_nzval[val_idx_dst] += get_component_value(rates, e_idx) - end - end - - return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) -end - -### JumpProblem ### - -# Builds a spatial DiscreteProblem from a Lattice Reaction System. - -# Creates a DiscreteProblem from a LatticeReactionSystem. -function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), - checks = false, kwargs...) - dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || - error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") - if any(length.(dprob.p[1]) .> 1) - error("Spatial reaction rates are currently not supported in lattice jump simulations.") - end - hopping_constants = make_hopping_constants(dprob, lrs) - ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - majumps_ = make_majumps(___dprob, lrs.rs) - return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, - spatial_system = lrs.lattice, save_positions = (true, false)) -end - -# Creates the hopping constants from a discrete problem and a lattice reaction system. -function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) - diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], - lrs)) - all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] - for s in species(lrs)] - if length.(all_diff_rates) == 1 - return Catalyst.matrix_expand_component_values(all_diff_rates, - length(vertices(lrs.lattice))) - else - hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] - for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:(lrs.nS) - push!(hopping_constants[s_idx, e.src], - Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) - end - end - return hopping_constants - end -end - -# Creates the mass action jumps from a discrete problem and a reaction system. -function make_majumps(non_spatial_prob, rs::ReactionSystem) - prob = non_spatial_prob - - js = convert(JumpSystem, rs) - statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) - eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p - majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) - majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) - return majumps -end - -### Accessing State & Parameter Array Values ### - -# Gets the index in the u array of species s in compartment comp (when their are nS species). -get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s -# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). -get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) - -# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64) - get_component_value(values[component_idx], location_idx) -end -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) - get_component_value(values[component_idx], location_idx, location_types[component_idx]) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64) - get_component_value(values, location_idx, length(values) == 1) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64, - location_type::Bool) - location_type ? values[1] : values[location_idx] -end -# Converts a vector of vectors to a long vector. -function expand_component_values(values::Vector{<:Vector}, n) - vcat([get_component_value.(values, comp) for comp in 1:n]...) -end -function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) - vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) -end -# Creates a view of the pC vector at a given comaprtment. -function view_pC_vector(pC, comp, pC_location_types, pC_idxs) - mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) -end -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). -function matrix_expand_component_values(values::Vector{<:Vector}, n) - reshape(expand_component_values(values, n), length(values), n) -end \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl new file mode 100644 index 0000000000..37fac4eaf6 --- /dev/null +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -0,0 +1,66 @@ + + +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates a DiscreteProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) + is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + + # Converts potential symmaps to varmaps. + 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 Vector{Vector{Float64}} form. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) + pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + + # Creates DiscreteProblem. + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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),checks = false, kwargs...) + # Error checks. + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") + + # Creates JumpProblem. + hopping_constants = make_hopping_constants(dprob, lrs) + println(typeof(hopping_constants)) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nV), dprob.tspan, first.(dprob.p[1])) + majumps_ = make_majumps(___dprob, lrs.rs) + return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + spatial_rates_dict = Dict(compute_all_spatial_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)] + if false #all(length.(all_diff_rates) .== 1) + return matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nV)] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants + end +end + +# Creates the mass action jumps from a discrete problem and a reaction system. +function make_majumps(non_spatial_prob, rs::ReactionSystem) + prob = non_spatial_prob + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + eqs = equations(js) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) + return majumps +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl deleted file mode 100644 index e416ab6fb2..0000000000 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ /dev/null @@ -1,208 +0,0 @@ -# Not actually run in CI, but useful for reference of ODE simulation performance across updates. - -### Preparations ### - -# Fetch packages. -using OrdinaryDiffEq -using Random, Statistics, SparseArrays, Test - -# Fetch test networks. -include("../spatial_test_networks.jl") - -### Runtime Checks ### -# Current timings are taken from the SciML CI server. -# Current not used, simply here for reference. -# Useful when attempting to optimise workflow. - -# using BenchmarkTools -# runtime_reduction_margin = 10.0 - -# Small grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.00060 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.26 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, small, stiff, system. - -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Medium grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - small_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.0016 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - large_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.67 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 9ecca8a319..905a3994fc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,26 +12,13 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) - u0_2 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 1.0, - :R => 0.0, - ]) - u0_3 = make_values_int([ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_4 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs)))) + u0_1 = [:S => 999, :I => 1, :R => 0] + u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] + u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lrs.lattice)), :R => round.(Int64, 50 * rand_v_vals(lrs.lattice))] + u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => round.(50 * rand_v_vals(lrs.lattice)), :R => round.(50 * rand_v_vals(lrs.lattice))] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:α => 0.1 / 1000, :β => 0.01] p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] @@ -41,18 +28,14 @@ let ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), - ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) jprob = JumpProblem(lrs, dprob, NSM()) - solve(jprob, SSAStepper()) + @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end end end @@ -76,33 +59,33 @@ let u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + pV_1 = [:β => 0.2, :α => 0.1] + pV_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pV_3 = [0.1, 0.2] + pV_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pV_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + pE_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pE_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pE_3 = [0.01, 0.02, 0.03] + pE_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pE_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. - for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV,pE)) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates end # Provides parameters as a combined vector. - for pC in [pC_1], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + for pV in [pV_1], pE in [pE_1, pE_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pE; pV]) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates @@ -110,7 +93,6 @@ let end end -using Catalyst, JumpProcesses, Graphs ### ABC Model Test (from JumpProcesses) ### let # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). @@ -131,17 +113,18 @@ let rn = @reaction_network begin (kB,kD), A + B <--> C end - srs = diffusion_reactions([(:D, :A), (:D, :B), (:D, :C)]) + tr_1 = @transport_reaction D A + tr_2 = @transport_reaction D B + tr_3 = @transport_reaction D C lattice = Graphs.grid(dims) - lrs = LatticeReactionSystem(rn, srs, lattice) - + lrs = LatticeReactionSystem(rn, [tr_1, tr_2, tr_3], lattice) # Set simulation parameters and create problems u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] tspan = (0.0, 10.0) - pC = [:kB => rates[1], :kD => rates[2]] - pD = [:D => diffusivity] - dprob = DiscreteProblem(lrs, u0, tspan, (pC, pD)) + pV = [:kB => rates[1], :kD => rates[2]] + pE = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pV, pE)) jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. # Tests. @@ -162,4 +145,4 @@ let @test abs(d) < reltol * non_spatial_mean[i] end end -end +end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 11f9d5b2d3..1da79c23ff 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -23,14 +23,6 @@ function spatial_param_syms(lrs::LatticeReactionSystem) ModelingToolkit.getname.(edge_parameters(lrs)) end -# Converts to integer value (for JumpProcess simulations). -function make_values_int(values::Vector{<:Pair}) - [val[1] => round.(Int64, val[2]) for val in values] -end -make_values_int(values::Matrix{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] - ### Declares Models ### # Small non-stiff system. From 5e6191a735b197912b9e7284950a35cb1b2a94eb Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 20:48:49 -0400 Subject: [PATCH 093/134] update --- .../spatial_Jump_systems.jl | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 37fac4eaf6..0757e2efb9 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -1,5 +1,3 @@ - - ### JumpProblem ### # Builds a spatial DiscreteProblem from a Lattice Reaction System. @@ -23,12 +21,14 @@ 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),checks = false, kwargs...) # Error checks. + println() + println(dprob.p) + println(dprob.u0) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") # Creates JumpProblem. hopping_constants = make_hopping_constants(dprob, lrs) - println(typeof(hopping_constants)) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nV), dprob.tspan, first.(dprob.p[1])) majumps_ = make_majumps(___dprob, lrs.rs) return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) @@ -38,18 +38,15 @@ end function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) spatial_rates_dict = Dict(compute_all_spatial_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)] - if false #all(length.(all_diff_rates) .== 1) - return matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) - else - hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nV)] - for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:(lrs.nS) - push!(hopping_constants[s_idx, e.src], - get_component_value(all_diff_rates[s_idx], e_idx)) + hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) for i in 1:(lrs.nS), j in 1:(lrs.nV)] + for (e_idx, e) in enumerate(edges(lrs.lattice)), s_idx in 1:(lrs.nS) + for dst_idx in 1:length(hopping_constants[s_idx, e.src]) + if (hopping_constants[s_idx, e.src][dst_idx] == undef) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) end end - return hopping_constants end + return hopping_constants end # Creates the mass action jumps from a discrete problem and a reaction system. From dde4ee99e73ab6da47de876871a7ea8d89527f73 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 21:27:49 -0400 Subject: [PATCH 094/134] update --- .../spatial_Jump_systems.jl | 22 ++++++------------- .../lattice_reaction_systems_jumps.jl | 9 ++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 0757e2efb9..12b23f55f8 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -21,9 +21,6 @@ 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),checks = false, kwargs...) # Error checks. - println() - println(dprob.p) - println(dprob.u0) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") @@ -40,24 +37,19 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) for i in 1:(lrs.nS), j in 1:(lrs.nV)] for (e_idx, e) in enumerate(edges(lrs.lattice)), s_idx in 1:(lrs.nS) - for dst_idx in 1:length(hopping_constants[s_idx, e.src]) - if (hopping_constants[s_idx, e.src][dst_idx] == undef) - hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) - end - end + dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) end return hopping_constants end # Creates the mass action jumps from a discrete problem and a reaction system. -function make_majumps(non_spatial_prob, rs::ReactionSystem) - prob = non_spatial_prob +function make_majumps(non_spat_dprob, rs::ReactionSystem) js = convert(JumpSystem, rs) - statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) + 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) - majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) - return majumps + return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 905a3994fc..0b36b88b46 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -75,6 +75,9 @@ let # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + true_maj_scaled_rates = [0.1, 0.2] + true_maj_reactant_stoch = [[1 => 1, 2 => 1], [2 => 1]] + true_maj_net_stoch = [[1 => -1, 2 => 1], [2 => -1, 3 => 1]] for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] @@ -82,6 +85,9 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch end # Provides parameters as a combined vector. for pV in [pV_1], pE in [pE_1, pE_2] @@ -89,6 +95,9 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch end end end From 27daaf946127ac6b91f50186b9aee2f988540e3e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 12:45:16 -0400 Subject: [PATCH 095/134] up --- src/spatial_reaction_systems/spatial_Jump_systems.jl | 9 +++++++++ .../lattice_reaction_systems_jumps.jl | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 12b23f55f8..276dddc4a4 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -6,20 +6,29 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + println("\nMaking dprob") # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) + println(p_in) + println(typeof(p_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 Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + println((pC, pD)) + println(typeof((pC, pD))) # Creates DiscreteProblem. return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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),checks = false, kwargs...) + + println("\nMaking jprob") + println(dprob.p) + println(typeof(dprob.p)) # Error checks. dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 0b36b88b46..bc70eaa4eb 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -33,7 +33,13 @@ let pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] + println("\n\n\nHere: 1") + println(pE) + println(typeof(pE)) + println((pV, pE)) dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + println(dprob.p) + println(typeof(dprob.p)) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From c43e2c1e59a2ffba8f030a1e46864344b988a7a6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 13:21:59 -0400 Subject: [PATCH 096/134] test testing --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 30c1a1f9f2..41cbd9c34e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,7 @@ using SafeTestsets ### Run the tests ### @time begin + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests the properties of ReactionSystems. ### @time @safetestset "ReactionSystem" begin include("reactionsystem_structure/reactionsystem.jl") end From 7cccf7bd1ec71c16a5ba5b9b7b87da0bbd21bf15 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 13:41:37 -0400 Subject: [PATCH 097/134] test test --- .../spatial_Jump_systems.jl | 16 ++++++++++++---- .../lattice_reaction_systems_jumps.jl | 4 ---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 276dddc4a4..ac6ed3b935 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -6,17 +6,28 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") - println("\nMaking dprob") # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) + println() println(p_in) println(typeof(p_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) + println() + println(p_in) + println(typeof(p_in)) + # Converts u0 and p to Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + println() + println(pC) + println(pD) + println(typeof(pC)) + println(typeof(pD)) + + println() println((pC, pD)) println(typeof((pC, pD))) # Creates DiscreteProblem. @@ -26,9 +37,6 @@ 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),checks = false, kwargs...) - println("\nMaking jprob") - println(dprob.p) - println(typeof(dprob.p)) # Error checks. dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index bc70eaa4eb..192f5c9166 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -33,10 +33,6 @@ let pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] - println("\n\n\nHere: 1") - println(pE) - println(typeof(pE)) - println((pV, pE)) dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) println(dprob.p) println(typeof(dprob.p)) From bbcb06d390fa6d6a01b66d86c6a30f104d52dbf2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 15:07:12 -0400 Subject: [PATCH 098/134] test update --- .../spatial_Jump_systems.jl | 17 ----------------- .../lattice_reaction_systems_jumps.jl | 4 +--- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index ac6ed3b935..69120fea3d 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -8,28 +8,11 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_ # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) - println() - println(p_in) - println(typeof(p_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) - println() - println(p_in) - println(typeof(p_in)) - # Converts u0 and p to Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) - - println() - println(pC) - println(pD) - println(typeof(pC)) - println(typeof(pD)) - - println() - println((pC, pD)) - println(typeof((pC, pD))) # Creates DiscreteProblem. return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), args...; kwargs...) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 192f5c9166..3104fe3992 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,7 +12,7 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] + for srs in [SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] @@ -34,8 +34,6 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - println(dprob.p) - println(typeof(dprob.p)) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From ea24729bfd940a245fed732380baf1d17e952dd8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 15:23:51 -0400 Subject: [PATCH 099/134] test testing --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 3104fe3992..70d74d2dee 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -34,6 +34,7 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + println(dprob.p) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 0d4da1ac532af04753c49d6544981488abc41edd Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 16:05:23 -0400 Subject: [PATCH 100/134] fix --- src/spatial_reaction_systems/spatial_Jump_systems.jl | 3 +-- .../spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 69120fea3d..955266ce55 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -19,9 +19,8 @@ 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),checks = false, kwargs...) - # Error checks. - dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") # The second check (Vector{Vector} is needed becaus on the CI server somehow the Tuple{..., ...} is covnerted into a Vector[..., ...]). It does not happen when I run tests locally, so no ideal how to fix. any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") # Creates JumpProblem. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 70d74d2dee..0b36b88b46 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,7 +12,7 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [SIR_srs_1, SIR_srs_2] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] @@ -34,7 +34,6 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - println(dprob.p) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 83e1f2727c9e81985c8bf59dbc6c746c3b129788 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 16:27:58 -0400 Subject: [PATCH 101/134] test update --- test/runtests.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 41cbd9c34e..1396aca0cd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,8 +3,6 @@ using SafeTestsets ### Run the tests ### @time begin - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end - ### Tests the properties of ReactionSystems. ### @time @safetestset "ReactionSystem" begin include("reactionsystem_structure/reactionsystem.jl") end @time @safetestset "Higher Order Reactions" begin include("reactionsystem_structure/higher_order_reactions.jl") end From 89965e2e6f8494931dacbe47888f3dd2d0f39d9f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 21:04:02 -0400 Subject: [PATCH 102/134] Split of utility function and handle un-directed graph input properly --- src/Catalyst.jl | 5 + .../lattice_reaction_systems.jl | 160 +---------------- src/spatial_reaction_systems/utility.jl | 162 ++++++++++++++++++ .../lattice_reaction_systems_ODEs.jl | 36 ++++ 4 files changed, 205 insertions(+), 158 deletions(-) create mode 100644 src/spatial_reaction_systems/utility.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 82eb7a2567..7f1175bf20 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -98,6 +98,8 @@ include("compound.jl") export @compound export components, iscompound, coefficients +### Spatial Reaction Networks ### + # spatial reactions include("spatial_reaction_systems/spatial_reactions.jl") export TransportReaction, transport_reactions, @transport_reaction @@ -108,6 +110,9 @@ include("spatial_reaction_systems/lattice_reaction_systems.jl") export LatticeReactionSystem export spatial_species, vertex_parameters, edge_parameters +# variosu utility functions +include("spatial_reaction_systems/utility.jl") + # spatial lattice ode systems. include("spatial_reaction_systems/spatial_ODE_systems.jl") diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 4fa787e870..d2ce9d2ccc 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -36,21 +36,13 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) - return LatticeReactionSystem(rs, srs, graph_to_digraph(lat); init_digraph = false) + return LatticeReactionSystem(rs, srs, DiGraph(lat); init_digraph = false) end function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat) return LatticeReactionSystem(rs, [sr], lat) end function LatticeReactionSystem(rs, sr::AbstractSpatialReaction, lat::SimpleGraph) - return LatticeReactionSystem(rs, [sr], graph_to_digraph(lat); init_digraph = false) -end - -# Converts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). -function graph_to_digraph(g1) - g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), - reverse.(edges(g1)))), :, 1)[:]) - add_vertices!(g2, nv(g1) - nv(g2)) - return g2 + return LatticeReactionSystem(rs, [sr], DiGraph(lat); init_digraph = false) end ### Lattice ReactionSystem Getters ### @@ -72,151 +64,3 @@ ModelingToolkit.nameof(lrs::LatticeReactionSystem) = nameof(lrs.rs) # Checks if a lattice reaction system is a pure (linear) transport reaction system. is_transport_system(lrs::LatticeReactionSystem) = all(typeof.(lrs.spatial_reactions) .== TransportReaction) - -### Processes Input u0 & p ### - -# Required to make symmapt_to_varmap to work. -function _symbol_to_var(lrs::LatticeReactionSystem, sym) - p_idx = findfirst(sym==p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) - isnothing(p_idx) || return parameters(lrs)[p_idx] - s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) - isnothing(s_idx) || return species(lrs)[s_idx] - error("Could not find property parameter/species $sym in lattice reaction system.") -end - -# From u0 input, extracts their values and store them in the internal format. -function lattice_process_u0(u0_in, u0_syms, nV) - u0 = lattice_process_input(u0_in, u0_syms, nV) - check_vector_lengths(u0, length(u0_syms), nV) - expand_component_values(u0, nV) -end - -# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. -function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) - pV_in, pE_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) - pV = lattice_process_input(pV_in, p_vertex_syms, lrs.nV) - pE = lattice_process_input(pE_in, p_edge_syms, lrs.nE) - lrs.init_digraph || foreach(idx -> duplicate_spat_params!(pE, idx, lrs), 1:length(pE)) - check_vector_lengths(pV, length(p_vertex_syms), lrs.nV) - check_vector_lengths(pE, length(p_edge_syms), lrs.nE) - return pV, pE -end - -# Splits parameters into those for the compartments and those for the connections. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps -function split_parameters(ps::Vector{<:Number}, args...) - error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") -end -function split_parameters(ps::Vector{<:Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) - pV_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] - pE_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] - (sum(length.([pV_in, pE_in])) != length(ps)) && error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pV_in, pE_in]))))") - return pV_in, pE_in -end - -# If the input is given in a map form, the vector needs sorting and the first value removed. -function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) - isempty(setdiff(first.(input), syms)) || error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") - sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) - return lattice_process_input(last.(sorted_input), syms, args...) -end -# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). -function lattice_process_input(input::Matrix{<:Number}, args...) - lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) -end -function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter inpur currently not supported.") -end -function lattice_process_input(input::Vector{<:Any}, args...) - isempty(input) ? Vector{Vector{Float64}}() : - lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], - args...) -end -lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input - -# Checks that a value vector have the right length, as well as that of all its sub vectors. -function check_vector_lengths(input::Vector{<:Vector}, n_syms, n_locations) - (length(input)==n_syms) || error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") - isempty(setdiff(unique(length.(input)), [1, n_locations])) || error("Some inputs where given values of inappropriate length.") -end - -# For spatial parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, expand so that the same value are given for both values on the edge. -function duplicate_spat_params!(pE::Vector{Vector{Float64}}, idx::Int64, - lrs::LatticeReactionSystem) - (2length(pE[idx]) == lrs.nE) && (pE[idx] = [p_val for p_val in pE[idx] for _ in 1:2]) -end - -# For a set of input values on the given forms, and their symbolics, convert into a dictionary. -vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) -# Produces a dictionary with all parameter values. -function param_dict(pV, pE, lrs) - merge(vals_to_dict(vertex_parameters(lrs), pV), - vals_to_dict(edge_parameters(lrs), pE)) -end - -# Computes the spatial rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - param_value_dict = param_dict(pV, pE, lrs) - unsorted_rates = [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in spatial_species(lrs)] - return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) -end -function get_spatial_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) - (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. - return rates[1].rate -end -function compute_spatial_rates(rate_law::Num, - param_value_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, nE::Int64) - relevant_parameters = Symbolics.get_variables(rate_law) - if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) - return [ - substitute(rate_law, - Dict(p => param_value_dict[p][1] for p in relevant_parameters)), - ] - end - return [substitute(rate_law, - Dict(p => get_component_value(param_value_dict[p], idxE) - for p in relevant_parameters)) for idxE in 1:nE] -end - -### Accessing State & Parameter Array Values ### - -# Gets the index in the u array of species s in compartment comp (when their are nS species). -get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s -# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). -get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) - -# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64) - get_component_value(values[component_idx], location_idx) -end -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) - get_component_value(values[component_idx], location_idx, location_types[component_idx]) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64) - get_component_value(values, location_idx, length(values) == 1) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64, - location_type::Bool) - location_type ? values[1] : values[location_idx] -end -# Converts a vector of vectors to a long vector. -function expand_component_values(values::Vector{<:Vector}, n) - vcat([get_component_value.(values, comp) for comp in 1:n]...) -end -function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) - vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) -end -# Creates a view of the pV vector at a given comaprtment. -function view_pV_vector!(work_pV, pV, comp, enumerated_pV_idx_types) - for (idx,loc_type) in enumerated_pV_idx_types - work_pV[idx] = (loc_type ? pV[idx][1] : pV[idx][comp]) - end - return work_pV -end -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). -function matrix_expand_component_values(values::Vector{<:Vector}, n) - reshape(expand_component_values(values, n), length(values), n) -end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl new file mode 100644 index 0000000000..c7fcea1a60 --- /dev/null +++ b/src/spatial_reaction_systems/utility.jl @@ -0,0 +1,162 @@ +### Processes Input u0 & p ### + +# Required to make symmapt_to_varmap to work. +function _symbol_to_var(lrs::LatticeReactionSystem, sym) + p_idx = findfirst(sym==p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) + isnothing(p_idx) || return parameters(lrs)[p_idx] + s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) + isnothing(s_idx) || return species(lrs)[s_idx] + error("Could not find property parameter/species $sym in lattice reaction system.") +end + +# From u0 input, extracts their values and store them in the internal format. +function lattice_process_u0(u0_in, u0_syms, nV) + u0 = lattice_process_input(u0_in, u0_syms, nV) + check_vector_lengths(u0, length(u0_syms), nV) + expand_component_values(u0, nV) +end + +# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. +function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) + pV_in, pE_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) + pV = lattice_process_input(pV_in, p_vertex_syms, lrs.nV) + pE = lattice_process_input(pE_in, p_edge_syms, lrs.nE) + lrs.init_digraph || duplicate_spat_params!(pE, lrs) + check_vector_lengths(pV, length(p_vertex_syms), lrs.nV) + check_vector_lengths(pE, length(p_edge_syms), lrs.nE) + return pV, pE +end + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +function split_parameters(ps::Vector{<:Number}, args...) + error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") +end +function split_parameters(ps::Vector{<:Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) + pV_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + pE_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] + (sum(length.([pV_in, pE_in])) != length(ps)) && error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pV_in, pE_in]))))") + return pV_in, pE_in +end + +# If the input is given in a map form, the vector needs sorting and the first value removed. +function lattice_process_input(input::Vector{<:Pair}, syms::Vector{BasicSymbolic{Real}}, args...) + isempty(setdiff(first.(input), syms)) || error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") + sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) + return lattice_process_input(last.(sorted_input), syms, args...) +end +# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). +function lattice_process_input(input::Matrix{<:Number}, args...) + lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) +end +function lattice_process_input(input::Array{<:Number, 3}, args...) + error("3 dimensional array parameter inpur currently not supported.") +end +function lattice_process_input(input::Vector{<:Any}, args...) + isempty(input) ? Vector{Vector{Float64}}() : + lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], + args...) +end +lattice_process_input(input::Vector{<:Vector}, syms::Vector{BasicSymbolic{Real}}, n::Int64) = input + +# Checks that a value vector have the right length, as well as that of all its sub vectors. +function check_vector_lengths(input::Vector{<:Vector}, n_syms, n_locations) + (length(input)==n_syms) || error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") + isempty(setdiff(unique(length.(input)), [1, n_locations])) || error("Some inputs where given values of inappropriate length.") +end + +# For spatial parameters, if the lattice was given as an undirected graph of size n this is converted to a directed graph of size 2n. +# If spatial parameters are given with n values, we want to sue the same value for both directions. +# Since the order of edges in the new graph is non-trivial, this function distributes the n input values to a 2n length vector, putting teh correct value in each position. +function duplicate_spat_params!(pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) + cum_adjacency_counts = [0;cumsum(length.(lrs.lattice.fadjlist[1:end-1]))] + for idx in 1:length(pE) + (2length(pE[idx]) == lrs.nE) || continue # Only apply the function if the parameter have n values. + + new_vals = Vector{Float64}(undef,lrs.nE) # The new values. + original_edge_count = 0 # As we loop through the edges of the di-graph, this keeps track of each edge's index in the original graph. + for edge in edges(lrs.lattice) + (edge.src < edge.dst) ? (original_edge_count += 1) : continue # The digraph convertion only adds edges so that src > dst. + idx_fwd = cum_adjacency_counts[edge.src] + findfirst(isequal(edge.dst),lrs.lattice.fadjlist[edge.src]) # For original edge i -> j, finds the index of i -> j in DiGraph. + idx_bwd = cum_adjacency_counts[edge.dst] + findfirst(isequal(edge.src),lrs.lattice.fadjlist[edge.dst]) # For original edge i -> j, finds the index of j -> i in DiGraph. + new_vals[idx_fwd] = pE[idx][original_edge_count] + new_vals[idx_bwd] = pE[idx][original_edge_count] + end + pE[idx] = new_vals + end +end + +# For a set of input values on the given forms, and their symbolics, convert into a dictionary. +vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) +# Produces a dictionary with all parameter values. +function param_dict(pV, pE, lrs) + merge(vals_to_dict(vertex_parameters(lrs), pV), + vals_to_dict(edge_parameters(lrs), pE)) +end + +# Computes the spatial rates and stores them in a format (Dictionary of species index to rates across all edges). +function compute_all_spatial_rates(pV::Vector{Vector{Float64}}, pE::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) + param_value_dict = param_dict(pV, pE, lrs) + unsorted_rates = [s => Symbolics.value.(compute_spatial_rates(get_spatial_rate_law(s, lrs), param_value_dict, lrs.nE)) for s in spatial_species(lrs)] + return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) +end +function get_spatial_rate_law(s::BasicSymbolic{Real}, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(s, sr.species), lrs.spatial_reactions) + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. + return rates[1].rate +end +function compute_spatial_rates(rate_law::Num, + param_value_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, nE::Int64) + relevant_parameters = Symbolics.get_variables(rate_law) + if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) + return [ + substitute(rate_law, + Dict(p => param_value_dict[p][1] for p in relevant_parameters)), + ] + end + return [substitute(rate_law, + Dict(p => get_component_value(param_value_dict[p], idxE) + for p in relevant_parameters)) for idxE in 1:nE] +end + +### Accessing State & Parameter Array Values ### + +# Gets the index in the u array of species s in compartment comp (when their are nS species). +get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s +# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). +get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) + +# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64) + get_component_value(values[component_idx], location_idx) +end +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64, location_types::Vector{Bool}) + get_component_value(values[component_idx], location_idx, location_types[component_idx]) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64) + get_component_value(values, location_idx, length(values) == 1) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64, + location_type::Bool) + location_type ? values[1] : values[location_idx] +end +# Converts a vector of vectors to a long vector. +function expand_component_values(values::Vector{<:Vector}, n) + vcat([get_component_value.(values, comp) for comp in 1:n]...) +end +function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) + vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) +end +# Creates a view of the pV vector at a given comaprtment. +function view_pV_vector!(work_pV, pV, comp, enumerated_pV_idx_types) + for (idx,loc_type) in enumerated_pV_idx_types + work_pV[idx] = (loc_type ? pV[idx][1] : pV[idx][comp]) + end + return work_pV +end +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). +function matrix_expand_component_values(values::Vector{<:Vector}, n) + reshape(expand_component_values(values, n), length(values), n) +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 43fe71bbc3..a8e05cadbb 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -143,6 +143,42 @@ let rtol = 0.0001)) end +# Checks that, when non directed graphs are provided, the parameters are re-ordered correctly. +let + # Create the same lattice (one as digraph, one not). Algorithm depends on Graphs.jl reordering edges, hence the jumpled order. + lattice_1 = SimpleGraph(5) + lattice_2 = SimpleDiGraph(5) + + add_edge!(lattice_1, 5, 2) + add_edge!(lattice_1, 1, 4) + add_edge!(lattice_1, 1, 3) + add_edge!(lattice_1, 4, 3) + add_edge!(lattice_1, 4, 5) + + add_edge!(lattice_2, 4, 1) + add_edge!(lattice_2, 3, 4) + add_edge!(lattice_2, 5, 4) + add_edge!(lattice_2, 5, 2) + add_edge!(lattice_2, 4, 3) + add_edge!(lattice_2, 4, 5) + add_edge!(lattice_2, 3, 1) + add_edge!(lattice_2, 2, 5) + add_edge!(lattice_2, 1, 4) + add_edge!(lattice_2, 1, 3) + + lrs_1 = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice_1) + lrs_2 = LatticeReactionSystem(SIR_system, SIR_srs_2, lattice_2) + + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_1.lattice), :R => 0.0] + pV = [:α => 0.1 / 1000, :β => 0.01] + + pE_1 = [:dS => [1.3, 1.4, 2.5, 3.4, 4.5], :dI => 0.01, :dR => 0.02] + pE_2 = [:dS => [1.3, 1.4, 2.5, 1.3, 3.4, 1.4, 3.4, 4.5, 2.5, 4.5], :dI => 0.01, :dR => 0.02] + ss_1 = solve(ODEProblem(lrs_1, u0, (0.0, 500.0), (pV, pE_1)), Tsit5()).u[end] + ss_2 = solve(ODEProblem(lrs_2, u0, (0.0, 500.0), (pV, pE_2)), Tsit5()).u[end] + @test all(isequal.(ss_1, ss_2)) +end + ### Test Transport Reaction Types ### # Compares where spatial reactions are created with/without the macro. From 619589ff0f27fd29f6031e9e7f7f4e83c007eab9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 14:24:27 -0400 Subject: [PATCH 103/134] Test fix --- test/runtests.jl | 1 + .../lattice_reaction_systems_ODEs.jl | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index bd01f84d5a..30c1a1f9f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,6 +41,7 @@ using SafeTestsets @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index a8e05cadbb..39dfae628f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -292,10 +292,9 @@ let lrs_graph = LatticeReactionSystem(SIR_system, SIR_srs_2, complete_graph(3)) u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs_digraph.lattice), :R => 0.0] pV = SIR_p - pE_digraph_1 = [:dS => [0.10, 0.10, 0.12, 0.12, 0.14, 0.14], :dI => 0.01, :dR => 0.01] - pE_digraph_2 = [[0.10, 0.10, 0.12, 0.12, 0.14, 0.14], 0.01, 0.01] - pE_digraph_3 = [0.10 0.10 0.12 0.12 0.14 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; - 0.01 0.01 0.01 0.01 0.01 0.01] + pE_digraph_1 = [:dS => [0.10, 0.12, 0.10, 0.14, 0.12, 0.14], :dI => 0.01, :dR => 0.01] + pE_digraph_2 = [[0.10, 0.12, 0.10, 0.14, 0.12, 0.14], 0.01, 0.01] + pE_digraph_3 = [0.10 0.12 0.10 0.14 0.12 0.14; 0.01 0.01 0.01 0.01 0.01 0.01; 0.01 0.01 0.01 0.01 0.01 0.01] pE_graph_1 = [:dS => [0.10, 0.12, 0.14], :dI => 0.01, :dR => 0.01] pE_graph_2 = [[0.10, 0.12, 0.14], 0.01, 0.01] pE_graph_3 = [0.10 0.12 0.14; 0.01 0.01 0.01; 0.01 0.01 0.01] From 6550e97158746c3c03412bdec7053b3e641fef2f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 14:45:00 -0400 Subject: [PATCH 104/134] fix --- test/runtests.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 30c1a1f9f2..bd01f84d5a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,7 +41,6 @@ using SafeTestsets @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end From 03f7f208035c63f4a8908e936ec1024c38f78396 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 15:32:09 -0400 Subject: [PATCH 105/134] update history file. --- HISTORY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 7bc2db909e..ded3090ac1 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,6 +1,18 @@ # Breaking updates and feature summaries across releases ## Catalyst unreleased (master branch) +- Simulation of spatial ODEs now supported. For full details, please see /~https://github.com/SciML/Catalyst.jl/pull/644 and upcoming documentation. +- LatticeReactionSystem structure represents a spatiral reaction network: + ```julia + rn = @reaction_network begin + (p,d), 0 <--> X + end + tr = @transport_reaction D X + lattice = Graphs.grid([5, 5]) + lrs = LatticeReactionSystem(rn, [tr], lattice) + ``` + + ## Catalyst 13.2 - Array parameters, species, and variables can be use in the DSL if explicitly From 60794af5744ef8572e3db3a1c3343bc465db06c0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 07:26:32 -0400 Subject: [PATCH 106/134] Spatial Jump Implementation --- src/compound.jl | 16 ++++++++-------- .../lattice_reaction_systems.jl | 1 + 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/compound.jl b/src/compound.jl index 9f2ac5bca2..01c9081232 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 0aad5f08ae..9b7308bc94 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -251,3 +251,4 @@ let @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) end +### Spatial Jump System Tests ### From bc6952f582b12c37dbdabd8198f195047c261e56 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 10:03:07 -0400 Subject: [PATCH 107/134] Spatial tests file reordering --- test/runtests.jl | 1 + ...ttice_reaction_systems_ODEs_performance.jl | 212 ++++++++++++ .../lattice_reaction_systems_jumps.jl | 309 ++++++++++++++++++ 3 files changed, 522 insertions(+) create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl create mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl diff --git a/test/runtests.jl b/test/runtests.jl index bd01f84d5a..30c1a1f9f2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,6 +41,7 @@ using SafeTestsets @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end @time @safetestset "Lattice Reaction Systems" begin include("spatial_reaction_systems/lattice_reaction_systems.jl") end @time @safetestset "ODE Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_ODEs.jl") end + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests network visualization. ### @time @safetestset "Latexify" begin include("visualization/latexify.jl") end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl new file mode 100644 index 0000000000..3d705e0eb9 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -0,0 +1,212 @@ +# Not actually run in CI, but useful for reference of ODE simulation performance across updates. + +### Preparations ### + +# Fetch packages. +using OrdinaryDiffEq +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Runtime Checks ### +# Current timings are taken from the SciML CI server. +# Current not used, simply here for reference. +# Useful when attempting to optimise workflow. + +# using BenchmarkTools +# runtime_reduction_margin = 10.0 + +# Small grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.00060 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, non-stiff, system. +let + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) + u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] + pV = SIR_p + pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] + oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.26 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, small, stiff, system. + +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.17 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Medium grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 2.3 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, small, stiff, system. +let + lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) + u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] + pV = brusselator_p + pE = [:dX => 0.2] + oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 170.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + small_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.0016 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, non-stiff, system. +let + lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, + large_2d_grid) + u0 = [ + :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), + :CuoAcLigand => 0.0, + :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :CuHLigand => 0.0, + :SilaneOAc => 0.0, + :Styrene => 0.16, + :AlkylCuLigand => 0.0, + :Amine_E => 0.39, + :AlkylAmine => 0.0, + :Cu_ELigand => 0.0, + :E_Silane => 0.0, + :Amine => 0.0, + :Decomposition => 0.0, + ] + pV = CuH_Amination_p + pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) + @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) + + runtime_target = 0.67 + runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 + println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Small grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 0.019 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end + +# Large grid, mid-sized, stiff, system. +let + lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) + u0 = [ + :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), + :vPp => 0.0, + :phos => 0.4, + ] + pV = sigmaB_p + pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] + oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) + @test SciMLBase.successful_retcode(solve(oprob, QNDF())) + + runtime_target = 35.0 + runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 + println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") + @test runtime < runtime_reduction_margin * runtime_target +end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl new file mode 100644 index 0000000000..fc3aae2045 --- /dev/null +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -0,0 +1,309 @@ +### Preparations ### + +# Fetch packages. +using JumpProcesses +using Random, Statistics, SparseArrays, Test + +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + +# Fetch test networks. +include("spatial_test_networks.jl") + +### Correctness Tests ### + +# Tests that there are no errors during runs. +let + for grid in [small_2d_grid, short_path, small_directed_cycle] + for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + lrs = LatticeReactionSystem(SIR_system, srs, grid) + u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) + u0_2 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 1.0, + :R => 0.0, + ]) + u0_3 = make_values_int([ + :S => 950.0, + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_4 = make_values_int([ + :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), + :I => 50 * rand_v_vals(lrs.lattice), + :R => 50 * rand_v_vals(lrs.lattice), + ]) + u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), + map(s -> Symbol(s.f), species(lrs.rs)))) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + p1 = [:α => 0.1 / 1000, :β => 0.01] + p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] + p3 = [ + :α => 0.1 / 2000 * rand_v_vals(lrs.lattice), + :β => 0.02 * rand_v_vals(lrs.lattice), + ] + p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) + for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. + pE_1 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), + ModelingToolkit.getname.(diffusion_parameters(lrs))) + for pE in [pE_1, pE_2, pE_3, pE_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + jprob = JumpProblem(lrs, dprob, NSM()) + solve(jprob, SSAStepper()) + end + end + end + end + end +end + +### Input Handling Tests ### + +# Tests that the correct hopping rates and initial conditions are generated. +# In this base case, hoppping rates should be on the form D_{s,i,j}. +let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + + # Prepares various u0 input types. + u0_1 = [:I => 2.0, :S => 1.0, :R => 3.0] + u0_2 = [:I => fill(2., nv(small_2d_grid)), :S => 1.0, :R => 3.0] + u0_3 = [1.0, 2.0, 3.0] + u0_4 = [1.0, fill(2., nv(small_2d_grid)), 3.0] + u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + + # Checks hopping rates and u0 are correct. + true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] + true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] + # Provides parameters as a tupple. + for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.prob.u0 == true_u0 + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end + +### Hoping Rates Computation Tests ### + +# Currently not in use, but will be added as more cases are enabled. +if false + # Tests hopping rates of the form D_{s,i,j} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] + pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s} + let + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1] + + # Prepare various (diffusion) parameter input types. + pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pD_3 = [0.01, 0.02, 0.03] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_3] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i} + let + # Prepares special system and diffusion reactions (depending on compartments). + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pD = [] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s}*L_{i,j} + # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). + let + SIR_system_special = @reaction_network begin + @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack dS, dI = SIR_system_special + SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + pC = [:β => 0.2, :α => 0.1, :dI => 0.01] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, ] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pC_3 = [0.1, 0.2] + pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0, :dS => 0.005] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] + pD_3 = [0.005, 2.0] + pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pD in [pD_1, pD_2, pD_3, pD_4] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end + + # Tests hopping rates of the form D_{s,i}*L_{i,j} + let + SIR_system_special = @reaction_network begin + @parameters comp_dS comp_dI connection_d [diffusionparameter = true] + α, S + I --> 2I + β, I --> R + end + @unpack comp_dS, comp_dI connection_d = SIR_system_special + SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] + + # Prepares the system. + lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) + u0 = [:I => 2.0, :S => 1.0, :R => 3.0] + + # Prepare various (compartment) parameter input types. + pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] + pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] + pC_4 = [0.1, 0.2, 0.01, 0.02] + pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] + pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] + pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) + + # Prepare various (diffusion) parameter input types. + pD_1 = [:connection_d => 2.0] + pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] + pD_3 = [2.0] + pD_4 = [fill(0.2, ne(small_2d_grid))] + pD_5 = [fill(0.2, 1, ne(small_2d_grid))] + + # Checks hopping rates are correct. + true_hopping_rates = [] + # Provides parameters as a tupple. + for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + # Provides parameters as a combined vector. + for pC in [pC_1, pC_2], pD in [pD_1, pD_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + jprob = JumpProblem(lrs, dprob, NSM()) + @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + end + end +end \ No newline at end of file From 2c4efc0958484ccc34858593dc751a25653c5111 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 11:08:34 -0400 Subject: [PATCH 108/134] testupdate --- .../lattice_reaction_systems_ODEs_performance.jl | 2 +- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index 3d705e0eb9..ebc5130dac 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -11,7 +11,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Runtime Checks ### # Current timings are taken from the SciML CI server. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index fc3aae2045..664a9c7f7f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -9,7 +9,7 @@ using StableRNGs rng = StableRNG(12345) # Fetch test networks. -include("spatial_test_networks.jl") +include("../spatial_test_networks.jl") ### Correctness Tests ### From 1758ae3480d22b32598479344e31b851de3c75f6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 12:16:28 -0400 Subject: [PATCH 109/134] testupdate --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 664a9c7f7f..5aa7cba1c3 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -268,7 +268,7 @@ if false α, S + I --> 2I β, I --> R end - @unpack comp_dS, comp_dI connection_d = SIR_system_special + @unpack comp_dS, comp_dI, connection_d = SIR_system_special SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] # Prepares the system. From 7cbbcf73827d7a0366b270b925f74f5d562b2168 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 18:14:35 +0200 Subject: [PATCH 110/134] Fix stableRNG usage --- .../lattice_reaction_systems_ODEs_performance.jl | 4 ---- .../lattice_reaction_systems_jumps.jl | 4 ---- 2 files changed, 8 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl index ebc5130dac..e416ab6fb2 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl @@ -6,10 +6,6 @@ using OrdinaryDiffEq using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 5aa7cba1c3..eedb26ed8b 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -4,10 +4,6 @@ using JumpProcesses using Random, Statistics, SparseArrays, Test -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - # Fetch test networks. include("../spatial_test_networks.jl") From 724136d24fedfc284e2d210dc4e443c48dfd5995 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:18:15 +0200 Subject: [PATCH 111/134] add ABC test and reset make_majumps --- .../lattice_reaction_systems_jumps.jl | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index eedb26ed8b..430468828f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -302,4 +302,57 @@ if false @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates end end -end \ No newline at end of file +end + +using Catalyst, JumpProcesses, Graphs +### ABC Model Test (from JumpProcesses) ### +let + # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). + Nsims = 100 + reltol = 0.05 + non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations + dim = 1 + linear_size = 5 + dims = Tuple(repeat([linear_size], dim)) + domain_size = 1.0 #μ-meter + mesh_size = domain_size / linear_size + rates = [0.1 / mesh_size, 1.0] + diffusivity = 1.0 + num_species = 3 + + # Make model. + rn = @reaction_network begin + (kB,kD), A + B <--> C + end + srs = diffusion_reactions([(:D, :A), (:D, :B), (:D, :C)]) + lattice = Graphs.grid(dims) + lrs = LatticeReactionSystem(rn, srs, lattice) + + + # Set simulation parameters and create problems + u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] + tspan = (0.0, 10.0) + pC = [:kB => rates[1], :kD => rates[2]] + pD = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pC, pD)) + jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. + + # Tests. + function get_mean_end_state(jump_prob, Nsims) + end_state = zeros(size(jump_prob.prob.u0)) + for i in 1:Nsims + sol = solve(jump_prob, SSAStepper()) + end_state .+= sol.u[end] + end + end_state / Nsims + end + for jprob in jump_problems + solution = solve(jprob, SSAStepper()) + mean_end_state = get_mean_end_state(jprob, Nsims) + mean_end_state = reshape(mean_end_state, num_species, num_nodes) + diff = sum(mean_end_state, dims = 2) - non_spatial_mean + for (i, d) in enumerate(diff) + @test abs(d) < reltol * non_spatial_mean[i] + end + end +end From 3ec45e526c20abd94103c16f74f1807b10c56958 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:19:13 +0200 Subject: [PATCH 112/134] remove tests for future functionality --- .../lattice_reaction_systems_jumps.jl | 194 ------------------ 1 file changed, 194 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 430468828f..930fb972a6 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -110,200 +110,6 @@ let end end -### Hoping Rates Computation Tests ### - -# Currently not in use, but will be added as more cases are enabled. -if false - # Tests hopping rates of the form D_{s,i,j} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(lrs.lattice)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(lrs.lattice)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(lrs.lattice)), fill(0.02, ne(lrs.lattice)), fill(0.03, ne(lrs.lattice)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s} - let - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1] - - # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_3] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i} - let - # Prepares special system and diffusion reactions (depending on compartments). - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS, :S), DiffusionReaction(comp_dI, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pD = [] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s}*L_{i,j} - # Since dS and dI are constant across the system, these can both be diffusionparameter or not (bot hcases tested). - let - SIR_system_special = @reaction_network begin - @parameters dS [diffusionparameter = true] dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack dS, dI = SIR_system_special - SIR_srs_special = [DiffusionReaction(dS*connection_d, :S), DiffusionReaction(dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - pC = [:β => 0.2, :α => 0.1, :dI => 0.01] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, ] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0, :dS => 0.005] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid)), :dS => 0.005] - pD_3 = [0.005, 2.0] - pD_4 = [0.005, fill(0.2, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pD in [pD_1, pD_2, pD_3, pD_4] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end - - # Tests hopping rates of the form D_{s,i}*L_{i,j} - let - SIR_system_special = @reaction_network begin - @parameters comp_dS comp_dI connection_d [diffusionparameter = true] - α, S + I --> 2I - β, I --> R - end - @unpack comp_dS, comp_dI, connection_d = SIR_system_special - SIR_srs_special = [DiffusionReaction(comp_dS*connection_d, :S), DiffusionReaction(comp_dI*connection_d, :I)] - - # Prepares the system. - lrs = LatticeReactionSystem(SIR_system_special, SIR_srs_special, small_2d_grid) - u0 = [:I => 2.0, :S => 1.0, :R => 3.0] - - # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1, :comp_dI => 0.02, :comp_dS => 0.01] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_3 = [:β => 0.2, :α => 1.0, :comp_dI => fill(0.02, nv(small_2d_grid)), :comp_dS => 0.01] - pC_4 = [0.1, 0.2, 0.01, 0.02] - pC_5 = [0.1, 0.2, 0.01, fill(0.02, nv(small_2d_grid))] - pC_6 = [0.1, fill(0.2, nv(small_2d_grid)), 0.01, fill(0.02, nv(small_2d_grid))] - pC_7 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)), fill(0.01, nv(small_2d_grid)), fill(0.02, nv(small_2d_grid)))) - - # Prepare various (diffusion) parameter input types. - pD_1 = [:connection_d => 2.0] - pD_2 = [:connection_d => fill(0.2, ne(small_2d_grid))] - pD_3 = [2.0] - pD_4 = [fill(0.2, ne(small_2d_grid))] - pD_5 = [fill(0.2, 1, ne(small_2d_grid))] - - # Checks hopping rates are correct. - true_hopping_rates = [] - # Provides parameters as a tupple. - for pC in [pC_1, pC_2, pC_4, pC_5], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - # Provides parameters as a combined vector. - for pC in [pC_1, pC_2], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) - jprob = JumpProblem(lrs, dprob, NSM()) - @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates - end - end -end - using Catalyst, JumpProcesses, Graphs ### ABC Model Test (from JumpProcesses) ### let From 57a50280a98f0e3b2577640a239459be6bc8c893 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 10:37:32 -0400 Subject: [PATCH 113/134] Drop formatting changes in `compound.jl`. --- src/compound.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compound.jl b/src/compound.jl index 01c9081232..9f2ac5bca2 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound From dae6e049a0a33961215045bf402d892cad75ce19 Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 19:29:41 -0400 Subject: [PATCH 114/134] Fix test. --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 930fb972a6..9ecca8a319 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -119,6 +119,7 @@ let non_spatial_mean = [65.7395, 65.7395, 434.2605] #mean of 10,000 simulations dim = 1 linear_size = 5 + num_nodes = linear_size^dim dims = Tuple(repeat([linear_size], dim)) domain_size = 1.0 #μ-meter mesh_size = domain_size / linear_size From 9928608468f71742ff419d746dce561a6a7a7949 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 27 Aug 2023 07:26:32 -0400 Subject: [PATCH 115/134] Spatial Jump Implementation --- src/compound.jl | 16 +- src/lattice_reaction_system_diffusion.jl | 501 +++++++++++++++++++++++ 2 files changed, 509 insertions(+), 8 deletions(-) create mode 100644 src/lattice_reaction_system_diffusion.jl diff --git a/src/compound.jl b/src/compound.jl index 9f2ac5bca2..01c9081232 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl new file mode 100644 index 0000000000..15c1ec9bbd --- /dev/null +++ b/src/lattice_reaction_system_diffusion.jl @@ -0,0 +1,501 @@ +### Diffusion Reaction Structure. ### + +# Implements the diffusionparameter metadata field. +struct DiffusionParameter end +Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter + +isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) +function isdiffusionparameter(x, default = false) + p = Symbolics.getparent(x, nothing) + p === nothing || (x = p) + Symbolics.getmetadata(x, DiffusionParameter, default) +end + +# Abstract spatial reaction structures. +abstract type AbstractSpatialReaction end + +# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. +# Currently only permit constant rates. +struct DiffusionReaction <: AbstractSpatialReaction + """The rate function (excluding mass action terms). Currently only constants supported""" + rate::Num + """The species that is subject to difusion.""" + species::Num + """A symbol representation of the species that is subject to difusion.""" + species_sym::Symbol # Required for identification in certain cases. + + # Creates a diffusion reaction. + function DiffusionReaction(rate::Num, species::Num) + new(rate, species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Number, species::Num) + new(Num(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Symbol, species::Num) + new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) + end + function DiffusionReaction(rate::Num, species::Symbol) + new(rate, Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Number, species::Symbol) + new(Num(rate), Symbolics.variable(species), species) + end + function DiffusionReaction(rate::Symbol, species::Symbol) + new(Symbolics.variable(rate), Symbolics.variable(species), species) + end +end +# Creates a vector of DiffusionReactions. +function diffusion_reactions(diffusion_reactions) + [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] +end +# Gets the parameters in a diffusion reaction. +ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) + +### Lattice Reaction Network Structure ### +# Desribes a spatial reaction network over a graph. +struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs + """The reaction system within each comaprtment.""" + rs::ReactionSystem + """The spatial reactions defined between individual nodes.""" + spatial_reactions::Vector{<:AbstractSpatialReaction} + """The graph on which the lattice is defined.""" + lattice::DiGraph + + # Derrived values. + """The number of compartments.""" + nC::Int64 + """The number of edges.""" + nE::Int64 + """The number of species.""" + nS::Int64 + """Whenever the initial input was a di graph.""" + init_digraph::Bool + + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::DiGraph; init_digraph = true) + return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), + length(species(rs)), init_digraph) + end + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reactions::Vector{<:AbstractSpatialReaction}, + lattice::SimpleGraph) + return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); + init_digraph = false) + end + function LatticeReactionSystem(rs::ReactionSystem, + spatial_reaction::AbstractSpatialReaction, + lattice::Graphs.AbstractGraph) + return LatticeReactionSystem(rs, [spatial_reaction], lattice) + end +end +# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). +function graph_to_digraph(g1) + g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), + reverse.(edges(g1)))), :, + 1)[:]) + add_vertices!(g2, nv(g1) - nv(g2)) + return g2 +end +# Gets the species of a lattice reaction system. +species(lrs::LatticeReactionSystem) = species(lrs.rs) +function diffusion_species(lrs::LatticeReactionSystem) + filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), + species(lrs.rs)) +end + +# Gets the parameters in a lattice reaction system. +function ModelingToolkit.parameters(lrs::LatticeReactionSystem) + unique(vcat(parameters(lrs.rs), + Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) +end +function compartment_parameters(lrs::LatticeReactionSystem) + filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) +end +function diffusion_parameters(lrs::LatticeReactionSystem) + filter(p -> is_spatial_param(p, lrs), parameters(lrs)) +end + +# Checks whenever a parameter is a spatial parameter or not. +function is_spatial_param(p, lrs) + hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && + (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. + return (any(isequal.(p, parameters(lrs.rs))) ? false : true) +end + +### Processes Input u0 & p ### + +# From u0 input, extracts their values and store them in the internal format. +function lattice_process_u0(u0_in, u0_symbols, nC) + u0 = lattice_process_input(u0_in, u0_symbols, nC) + check_vector_lengths(u0, nC) + expand_component_values(u0, nC) +end + +# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. +function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) + pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) + pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) + pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) + lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) + check_vector_lengths(pC, lrs.nC) + check_vector_lengths(pD, lrs.nE) + return pC, pD +end + +# Splits parameters into those for the compartments and those for the connections. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +function split_parameters(ps::Vector{<:Number}, args...) + error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") +end +function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, + p_diff_symbols::Vector) + pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] + pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] + (sum(length.([pC_in, pD_in])) != length(ps)) && + error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") + return pC_in, pD_in +end + +# If the input is given in a map form, teh vector needs sorting and the first value removed. +function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) + (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && + error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") + sorted_input = sort(input; + by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) + return lattice_process_input(last.(sorted_input), symbols, args...) +end +# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). +function lattice_process_input(input::Matrix{<:Number}, args...) + lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) +end +function lattice_process_input(input::Array{<:Number, 3}, args...) + error("3 dimensional array parameter inpur currently not supported.") +end +function lattice_process_input(input::Vector{<:Any}, args...) + isempty(input) ? Vector{Vector{Float64}}() : + lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], + args...) +end +lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input +function check_vector_lengths(input::Vector{<:Vector}, n) + isempty(setdiff(unique(length.(input)), [1, n])) || + error("Some inputs where given values of inappropriate length.") +end + +# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. +function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, + lrs::LatticeReactionSystem) + (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) +end + +# For a set of input values on the given forms, and their symbolics, convert into a dictionary. +vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) +# Produces a dictionary with all parameter values. +function param_dict(pC, pD, lrs) + merge(vals_to_dict(compartment_parameters(lrs), pC), + vals_to_dict(diffusion_parameters(lrs), pD)) +end + +# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). +function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, + lrs::LatticeReactionSystem) + param_value_dict = param_dict(pC, pD, lrs) + return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), + param_value_dict, lrs.nE)) + for s in diffusion_species(lrs)] +end +function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) + rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), + lrs.spatial_reactions) + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. + return rates[1].rate +end +function compute_diffusion_rates(rate_law::Num, + param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) + relevant_parameters = Symbolics.get_variables(rate_law) + if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) + return [ + substitute(rate_law, + Dict(p => param_value_dict[p][1] for p in relevant_parameters)), + ] + end + return [substitute(rate_law, + Dict(p => get_component_value(param_value_dict[p], idxE) + for p in relevant_parameters)) for idxE in 1:nE] +end + +### ODEProblem ### + +# Creates an ODEProblem from a LatticeReactionSystem. +function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; + jac = true, sparse = jac, kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + ofun = build_odefunction(lrs, pC, pD, jac, sparse) + return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) +end + +# Builds an ODEFunction for a spatial ODEProblem. +function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, + pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) + # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) + diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) + diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] + for diff_rates in diffusion_rates_speciesmap] + + f = build_f(ofunc, pC, diffusion_rates, lrs) + jac_prototype = (use_jac || sparse) ? + build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, + lrs; set_nonzero = use_jac) : nothing + jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) +end + +# Builds the forcing (f) function for a reaction system on a lattice. +function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, + diffusion_rates::Vector, + lrs::LatticeReactionSystem) + leaving_rates = zeros(length(diffusion_rates), lrs.nC) + for (s_idx, rates) in enumerate(last.(diffusion_rates)), + (e_idx, e) in enumerate(edges(lrs.lattice)) + + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + end + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + + return function (du, u, p, t) + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(lrs.nC) + ofunc((@view du[get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) + end + + # Updates for spatial diffusion reactions. + for (s_idx, (s, rates)) in enumerate(diffusion_rates) + for comp_i::Int64 in 1:(lrs.nC) + du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * + u[get_index(comp_i, s, + lrs.nS)] + end + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges + du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * + u[get_index(edge.src, s, + lrs.nS)] + end + end + end +end + +# Builds the Jacobian function for a reaction system on a lattice. +function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, + lrs::LatticeReactionSystem, + jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + sparse::Bool) + pC_location_types = length.(pC) .== 1 + pC_idxs = 1:length(pC) + reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) + add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : + J -> (J .+= Matrix(jac_prototype))) + + return function (J, u, p, t) + # Because of weird stuff where the Jacobian is not reset that I don't understand properly. + reset_J_vals(J) + + # Updates for non-spatial reactions. + for comp_i::Int64 in 1:(lrs.nC) + ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), + get_indexes(comp_i, lrs.nS)]), + (@view u[get_indexes(comp_i, lrs.nS)]), + view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) + end + + # Updates for the spatial reactions. + add_diff_J_vals(J) + end +end + +# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, + diffusion_rates, lrs::LatticeReactionSystem; + set_nonzero = false) + diff_species = first.(diffusion_rates) + # Gets list of indexes for species that diffuse, but are invovled in no other reaction. + only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) + for s in 1:(lrs.nS)] + + # Declares sparse array content. + J_colptr = fill(1, lrs.nC * lrs.nS + 1) + J_nzval = fill(0.0, + lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + + length(edges(lrs.lattice)) * length(diffusion_rates)) + J_rowval = fill(0, length(J_nzval)) + + # Finds filled elements. + for comp in 1:(lrs.nC), s in 1:(lrs.nS) + col_idx = get_index(comp, s, lrs.nS) + + # Column values. + local_elements = in(s, diff_species) * + (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) + diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) + J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements + + # Row values. + rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ + (comp - 1) * lrs.nS + if in(s, diff_species) + # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. + diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s + split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) + isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) + rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, + diffusion_rows[split_idx:end]) + if only_diff[s] + split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) + isnothing(split_idx) && (split_idx = length(rows) + 1) + insert!(rows, split_idx, get_index(comp, s, lrs.nS)) + end + end + J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows + end + + # Set element values. + if !set_nonzero + J_nzval .= 1.0 + else + for (s_idx, (s, rates)) in enumerate(diffusion_rates), + (e_idx, edge) in enumerate(edges(lrs.lattice)) + + col_start = J_colptr[get_index(edge.src, s, lrs.nS)] + col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 + column_view = @view J_rowval[col_start:col_end] + + # Updates the source value. + val_idx_src = col_start + + findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 + J_nzval[val_idx_src] -= get_component_value(rates, e_idx) + + # Updates the destination value. + val_idx_dst = col_start + + findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 + J_nzval[val_idx_dst] += get_component_value(rates, e_idx) + end + end + + return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) +end + +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates a DiscreteProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, + p_in = DiffEqBase.NullParameters(), args...; kwargs...) + u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) + pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), + Symbol.(diffusion_parameters(lrs)), lrs) + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), + checks = false, kwargs...) + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || + error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + hopping_constants = make_hopping_constants(dprob, lrs) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, + checks) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + spatial_system = lrs.lattice, save_positions = (true, false)) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], + lrs)) + all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] + for s in species(lrs)] + if length.(all_diff_rates) == 1 + return Catalyst.matrix_expand_component_values(all_diff_rates, + length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants + end +end + +# Creates the mass action jumps from a discrete problem and a lattice reaction system. +function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) + any(length.(dprob.p[1]) .> 1) && + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, + first.(dprob.p[1])) + ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + (length(___jprob.variable_jumps) != 0) && + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump +end + +### Accessing State & Parameter Array Values ### + +# Gets the index in the u array of species s in compartment comp (when their are nS species). +get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s +# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). +get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) + +# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64) + get_component_value(values[component_idx], location_idx) +end +function get_component_value(values::Vector{<:Vector}, component_idx::Int64, + location_idx::Int64, location_types::Vector{Bool}) + get_component_value(values[component_idx], location_idx, location_types[component_idx]) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64) + get_component_value(values, location_idx, length(values) == 1) +end +function get_component_value(values::Vector{<:Number}, location_idx::Int64, + location_type::Bool) + location_type ? values[1] : values[location_idx] +end +# Converts a vector of vectors to a long vector. +function expand_component_values(values::Vector{<:Vector}, n) + vcat([get_component_value.(values, comp) for comp in 1:n]...) +end +function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) + vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) +end +# Creates a view of the pC vector at a given comaprtment. +function view_pC_vector(pC, comp, pC_location_types, pC_idxs) + mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) +end +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). +function matrix_expand_component_values(values::Vector{<:Vector}, n) + reshape(expand_component_values(values, n), length(values), n) +end From 9da5f31742a9db24f8cbbc43654908749f8afc8d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 18:14:35 +0200 Subject: [PATCH 116/134] Fix stableRNG usage --- src/lattice_reaction_system_diffusion.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 15c1ec9bbd..a75c54cadb 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -445,6 +445,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, combinatoric_ratelaws, checks) From fb525032616e290072805777b341df8817d2d925 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 12:19:28 -0400 Subject: [PATCH 117/134] Update src/lattice_reaction_system_diffusion.jl Co-authored-by: Vasily Ilin --- src/lattice_reaction_system_diffusion.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index a75c54cadb..09adefe8a4 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -459,7 +459,11 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump +function make_majumps(lrs::LatticeReactionSystem) + jumps = assemble_jumps(lrs.rs) + if !(jumps[end] isa MassActionJump) + error("Only MassAction Jumps are currently allowed in spatial simulation.") + return jumps end ### Accessing State & Parameter Array Values ### From 2ade53f67c052053da6c43a11cfad7cecfbd7a2f Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 2 Sep 2023 19:18:15 +0200 Subject: [PATCH 118/134] add ABC test and reset make_majumps --- src/lattice_reaction_system_diffusion.jl | 35 ++++++++++++------------ 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 09adefe8a4..5b7cd4670c 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -415,8 +415,8 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") hopping_constants = make_hopping_constants(dprob, lrs) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, - checks) + #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) + majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, @@ -445,27 +445,28 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -# Should be some way to get mass actions jumps directly from ReactionSystem. Haven't managed to find out how, but when possible, this can be simplified. function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) + hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, + combinatoric_ratelaws, checks) any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") + error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) + first.(dprob.p[1])) ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) + hopping_constants = hopping_constants, + spatial_system = lrs.lattice, + combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") -function make_majumps(lrs::LatticeReactionSystem) - jumps = assemble_jumps(lrs.rs) - if !(jumps[end] isa MassActionJump) - error("Only MassAction Jumps are currently allowed in spatial simulation.") - return jumps + error("Currently, for lattice jump simulations, variable rate jumps are not supported.") + return ___jprob.massaction_jump end +# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) +# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) +# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") +# return jumps +# end + ### Accessing State & Parameter Array Values ### # Gets the index in the u array of species s in compartment comp (when their are nS species). @@ -503,4 +504,4 @@ end # Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). function matrix_expand_component_values(values::Vector{<:Vector}, n) reshape(expand_component_values(values, n), length(values), n) -end +end \ No newline at end of file From 9a0d403f28d19bb139700c069ba605795486cb12 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 14:45:59 +0200 Subject: [PATCH 119/134] explanatory note --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 5b7cd4670c..24f8f5332b 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -460,7 +460,7 @@ function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggreg error("Currently, for lattice jump simulations, variable rate jumps are not supported.") return ___jprob.massaction_jump end - +# We would want something like this, but it errors if used. # function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) # jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) # (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") From 381262fa61a8ebfa334c9a860dd08d6c9c4ac72c Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 10:37:32 -0400 Subject: [PATCH 120/134] Drop formatting changes in `compound.jl`. --- src/compound.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/compound.jl b/src/compound.jl index 01c9081232..9f2ac5bca2 100644 --- a/src/compound.jl +++ b/src/compound.jl @@ -18,12 +18,12 @@ macro compound(species_expr, arr_expr...) # Construct the expressions that define the species species_expr = Expr(:macrocall, Symbol("@species"), LineNumberNode(0), - Expr(:call, species_name, species_arg)) + Expr(:call, species_name, species_arg)) # Construct the expression to set the iscompound metadata setmetadata_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundSpecies, - true)) + Catalyst.CompoundSpecies, + true)) # Ensure the expressions are evaluated in the correct scope by escaping them escaped_species_expr = esc(species_expr) @@ -49,22 +49,22 @@ macro compound(species_expr, arr_expr...) # Construct the expression to set the components metadata setcomponents_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundComponents, - $species_expr)) + Catalyst.CompoundComponents, + $species_expr)) # Ensure the expression is evaluated in the correct scope by escaping it escaped_setcomponents_expr = esc(setcomponents_expr) # Construct the expression to set the coefficients metadata setcoefficients_expr = :($(species_name) = ModelingToolkit.setmetadata($(species_name), - Catalyst.CompoundCoefficients, - $coeffs_expr)) + Catalyst.CompoundCoefficients, + $coeffs_expr)) escaped_setcoefficients_expr = esc(setcoefficients_expr) # Return a block that contains the escaped expressions return Expr(:block, escaped_species_expr, escaped_setmetadata_expr, - escaped_setcomponents_expr, escaped_setcoefficients_expr) + escaped_setcomponents_expr, escaped_setcoefficients_expr) end # Check if a species is a compound From 024fd0d19469b831736aab81651554b87772208e Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:04:21 -0400 Subject: [PATCH 121/134] Construct massaction jumps without building a `JumpProblem`. --- src/lattice_reaction_system_diffusion.jl | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index 24f8f5332b..c3d72424e3 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -414,12 +414,14 @@ function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator checks = false, kwargs...) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + if any(length.(dprob.p[1]) .> 1) + error("Spatial reaction rates are currently not supported in lattice jump simulations.") + end hopping_constants = make_hopping_constants(dprob, lrs) - #majumps = make_majumps(lrs, combinatoric_ratelaws=combinatoric_ratelaws) - majumps = make_majumps(dprob, lrs, aggregator, hopping_constants, combinatoric_ratelaws, checks) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, first.(dprob.p[1])) - return JumpProblem(___dprob, aggregator, majumps, hopping_constants = hopping_constants, + majumps_ = make_majumps(___dprob, lrs.rs) + return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) end @@ -445,27 +447,18 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end # Creates the mass action jumps from a discrete problem and a lattice reaction system. -function make_majumps(dprob::DiscreteProblem, lrs::LatticeReactionSystem, aggregator, - hopping_constants::Union{Matrix{<:Number}, Matrix{<:Vector}}, - combinatoric_ratelaws, checks) - any(length.(dprob.p[1]) .> 1) && - error("Currently, for lattice jump simulations, spatial non-diffusion parameters are not supported.") - ___dprob = DiscreteProblem(lrs.rs, reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - ___jprob = JumpProblem(lrs.rs, ___dprob, aggregator; - hopping_constants = hopping_constants, - spatial_system = lrs.lattice, - combinatoric_ratelaws = combinatoric_ratelaws, checks = checks) - (length(___jprob.variable_jumps) != 0) && - error("Currently, for lattice jump simulations, variable rate jumps are not supported.") - return ___jprob.massaction_jump -end -# We would want something like this, but it errors if used. -# function make_majumps(lrs::LatticeReactionSystem; combinatoric_ratelaws=get_combinatoric_ratelaws(lrs.rs)) -# jumps = assemble_jumps(lrs.rs; combinatoric_ratelaws=combinatoric_ratelaws) -# (jumps[end] isa MassActionJump) || error("Only MassAction Jumps are currently allowed in spatial simulation.") -# return jumps -# end +function make_majumps(non_spatial_prob, rs::ReactionSystem) + prob = non_spatial_prob + + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + eqs = equations(js) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) + return majumps +end ### Accessing State & Parameter Array Values ### From 0f865edbd32ea30d137f97baf2811af69131213f Mon Sep 17 00:00:00 2001 From: Vasily Ilin Date: Sun, 3 Sep 2023 22:07:03 -0400 Subject: [PATCH 122/134] Fix comment. --- src/lattice_reaction_system_diffusion.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl index c3d72424e3..524e2e500d 100644 --- a/src/lattice_reaction_system_diffusion.jl +++ b/src/lattice_reaction_system_diffusion.jl @@ -446,7 +446,7 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst end end -# Creates the mass action jumps from a discrete problem and a lattice reaction system. +# Creates the mass action jumps from a discrete problem and a reaction system. function make_majumps(non_spatial_prob, rs::ReactionSystem) prob = non_spatial_prob From b2a5732833f96012a889f777b2e18006c49725fb Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 16:51:55 -0400 Subject: [PATCH 123/134] Make spatial SSA part up to date. --- src/Catalyst.jl | 3 +- src/lattice_reaction_system_diffusion.jl | 500 ------------------ .../spatial_Jump_systems.jl | 66 +++ ...ttice_reaction_systems_ODEs_performance.jl | 208 -------- .../lattice_reaction_systems_jumps.jl | 83 ++- test/spatial_test_networks.jl | 8 - 6 files changed, 101 insertions(+), 767 deletions(-) delete mode 100644 src/lattice_reaction_system_diffusion.jl create mode 100644 src/spatial_reaction_systems/spatial_Jump_systems.jl delete mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 7f1175bf20..fb1ce5f038 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -113,7 +113,8 @@ export spatial_species, vertex_parameters, edge_parameters # variosu utility functions include("spatial_reaction_systems/utility.jl") -# spatial lattice ode systems. +# spatial lattice ode and jump systems. include("spatial_reaction_systems/spatial_ODE_systems.jl") +include("spatial_reaction_systems/spatial_Jump_systems.jl") end # module diff --git a/src/lattice_reaction_system_diffusion.jl b/src/lattice_reaction_system_diffusion.jl deleted file mode 100644 index 524e2e500d..0000000000 --- a/src/lattice_reaction_system_diffusion.jl +++ /dev/null @@ -1,500 +0,0 @@ -### Diffusion Reaction Structure. ### - -# Implements the diffusionparameter metadata field. -struct DiffusionParameter end -Symbolics.option_to_metadata_type(::Val{:diffusionparameter}) = DiffusionParameter - -isdiffusionparameter(x::Num, args...) = isdiffusionparameter(Symbolics.unwrap(x), args...) -function isdiffusionparameter(x, default = false) - p = Symbolics.getparent(x, nothing) - p === nothing || (x = p) - Symbolics.getmetadata(x, DiffusionParameter, default) -end - -# Abstract spatial reaction structures. -abstract type AbstractSpatialReaction end - -# A diffusion reaction. These are simple to hanlde, and should cover most types of spatial reactions. -# Currently only permit constant rates. -struct DiffusionReaction <: AbstractSpatialReaction - """The rate function (excluding mass action terms). Currently only constants supported""" - rate::Num - """The species that is subject to difusion.""" - species::Num - """A symbol representation of the species that is subject to difusion.""" - species_sym::Symbol # Required for identification in certain cases. - - # Creates a diffusion reaction. - function DiffusionReaction(rate::Num, species::Num) - new(rate, species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Number, species::Num) - new(Num(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Symbol, species::Num) - new(Symbolics.variable(rate), species, ModelingToolkit.getname(species)) - end - function DiffusionReaction(rate::Num, species::Symbol) - new(rate, Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Number, species::Symbol) - new(Num(rate), Symbolics.variable(species), species) - end - function DiffusionReaction(rate::Symbol, species::Symbol) - new(Symbolics.variable(rate), Symbolics.variable(species), species) - end -end -# Creates a vector of DiffusionReactions. -function diffusion_reactions(diffusion_reactions) - [DiffusionReaction(dr[1], dr[2]) for dr in diffusion_reactions] -end -# Gets the parameters in a diffusion reaction. -ModelingToolkit.parameters(dr::DiffusionReaction) = Symbolics.get_variables(dr.rate) - -### Lattice Reaction Network Structure ### -# Desribes a spatial reaction network over a graph. -struct LatticeReactionSystem # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs - """The reaction system within each comaprtment.""" - rs::ReactionSystem - """The spatial reactions defined between individual nodes.""" - spatial_reactions::Vector{<:AbstractSpatialReaction} - """The graph on which the lattice is defined.""" - lattice::DiGraph - - # Derrived values. - """The number of compartments.""" - nC::Int64 - """The number of edges.""" - nE::Int64 - """The number of species.""" - nS::Int64 - """Whenever the initial input was a di graph.""" - init_digraph::Bool - - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::DiGraph; init_digraph = true) - return new(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), - length(species(rs)), init_digraph) - end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reactions::Vector{<:AbstractSpatialReaction}, - lattice::SimpleGraph) - return LatticeReactionSystem(rs, spatial_reactions, graph_to_digraph(lattice); - init_digraph = false) - end - function LatticeReactionSystem(rs::ReactionSystem, - spatial_reaction::AbstractSpatialReaction, - lattice::Graphs.AbstractGraph) - return LatticeReactionSystem(rs, [spatial_reaction], lattice) - end -end -# Covnerts a graph to a digraph (in a way where we know where the new edges are in teh edge vector). -function graph_to_digraph(g1) - g2 = Graphs.SimpleDiGraphFromIterator(reshape(permutedims(hcat(collect(edges(g1)), - reverse.(edges(g1)))), :, - 1)[:]) - add_vertices!(g2, nv(g1) - nv(g2)) - return g2 -end -# Gets the species of a lattice reaction system. -species(lrs::LatticeReactionSystem) = species(lrs.rs) -function diffusion_species(lrs::LatticeReactionSystem) - filter(s -> ModelingToolkit.getname(s) in getfield.(lrs.spatial_reactions, :species_sym), - species(lrs.rs)) -end - -# Gets the parameters in a lattice reaction system. -function ModelingToolkit.parameters(lrs::LatticeReactionSystem) - unique(vcat(parameters(lrs.rs), - Symbolics.get_variables.(getfield.(lrs.spatial_reactions, :rate))...)) -end -function compartment_parameters(lrs::LatticeReactionSystem) - filter(p -> !is_spatial_param(p, lrs), parameters(lrs)) -end -function diffusion_parameters(lrs::LatticeReactionSystem) - filter(p -> is_spatial_param(p, lrs), parameters(lrs)) -end - -# Checks whenever a parameter is a spatial parameter or not. -function is_spatial_param(p, lrs) - hasmetadata(p, DiffusionParameter) && getmetadata(p, DiffusionParameter) && - (return true) # Wanted to just depend on metadata, but seems like we cannot implement that trivially. - return (any(isequal.(p, parameters(lrs.rs))) ? false : true) -end - -### Processes Input u0 & p ### - -# From u0 input, extracts their values and store them in the internal format. -function lattice_process_u0(u0_in, u0_symbols, nC) - u0 = lattice_process_input(u0_in, u0_symbols, nC) - check_vector_lengths(u0, nC) - expand_component_values(u0, nC) -end - -# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. -function lattice_process_p(p_in, p_comp_symbols, p_diff_symbols, lrs::LatticeReactionSystem) - pC_in, pD_in = split_parameters(p_in, p_comp_symbols, p_diff_symbols) - pC = lattice_process_input(pC_in, p_comp_symbols, lrs.nC) - pD = lattice_process_input(pD_in, p_diff_symbols, lrs.nE) - lrs.init_digraph || foreach(idx -> duplicate_diff_params!(pD, idx, lrs), 1:length(pD)) - check_vector_lengths(pC, lrs.nC) - check_vector_lengths(pD, lrs.nE) - return pC, pD -end - -# Splits parameters into those for the compartments and those for the connections. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps -function split_parameters(ps::Vector{<:Number}, args...) - error("When providing parameters for a spatial system as a single vector, the paired form (e.g :D =>1.0) must be used.") -end -function split_parameters(ps::Vector{<:Pair}, p_comp_symbols::Vector, - p_diff_symbols::Vector) - pC_in = [p for p in ps if Symbol(p[1]) in p_comp_symbols] - pD_in = [p for p in ps if Symbol(p[1]) in p_diff_symbols] - (sum(length.([pC_in, pD_in])) != length(ps)) && - error("These input parameters are not recongised: $(setdiff(first.(ps), vcat(first.([pC_in, pE_in]))))") - return pC_in, pD_in -end - -# If the input is given in a map form, teh vector needs sorting and the first value removed. -function lattice_process_input(input::Vector{<:Pair}, symbols::Vector{Symbol}, args...) - (length(setdiff(Symbol.(first.(input)), symbols)) != 0) && - error("Some input symbols are not recognised: $(setdiff(Symbol.(first.(input)), symbols)).") - sorted_input = sort(input; - by = p -> findfirst(ModelingToolkit.getname(p[1]) .== symbols)) - return lattice_process_input(last.(sorted_input), symbols, args...) -end -# Processes the input and gvies it in a form where it is a vector of vectors (some of which may have a single value). -function lattice_process_input(input::Matrix{<:Number}, args...) - lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) -end -function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter inpur currently not supported.") -end -function lattice_process_input(input::Vector{<:Any}, args...) - isempty(input) ? Vector{Vector{Float64}}() : - lattice_process_input([(val isa Vector{<:Number}) ? val : [val] for val in input], - args...) -end -lattice_process_input(input::Vector{<:Vector}, symbols::Vector{Symbol}, n::Int64) = input -function check_vector_lengths(input::Vector{<:Vector}, n) - isempty(setdiff(unique(length.(input)), [1, n])) || - error("Some inputs where given values of inappropriate length.") -end - -# For diffusion parameters, if the graph was given as an undirected graph of length n, and the paraemter have n values, exapnd so that the same value are given for both values on the edge. -function duplicate_diff_params!(pD::Vector{Vector{Float64}}, idx::Int64, - lrs::LatticeReactionSystem) - (2length(pD[idx]) == lrs.nE) && (pD[idx] = [p_val for p_val in pD[idx] for _ in 1:2]) -end - -# For a set of input values on the given forms, and their symbolics, convert into a dictionary. -vals_to_dict(syms::Vector, vals::Vector{<:Vector}) = Dict(zip(syms, vals)) -# Produces a dictionary with all parameter values. -function param_dict(pC, pD, lrs) - merge(vals_to_dict(compartment_parameters(lrs), pC), - vals_to_dict(diffusion_parameters(lrs), pD)) -end - -# Computes the diffusion rates and stores them in a format (Dictionary of species index to rates across all edges). -function compute_all_diffusion_rates(pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, - lrs::LatticeReactionSystem) - param_value_dict = param_dict(pC, pD, lrs) - return [s => Symbolics.value.(compute_diffusion_rates(get_diffusion_rate_law(s, lrs), - param_value_dict, lrs.nE)) - for s in diffusion_species(lrs)] -end -function get_diffusion_rate_law(s::Symbolics.BasicSymbolic, lrs::LatticeReactionSystem) - rates = filter(sr -> isequal(ModelingToolkit.getname(s), sr.species_sym), - lrs.spatial_reactions) - (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") # We could allows several and simply sum them though, easy change. - return rates[1].rate -end -function compute_diffusion_rates(rate_law::Num, - param_value_dict::Dict{Any, Vector{Float64}}, nE::Int64) - relevant_parameters = Symbolics.get_variables(rate_law) - if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) - return [ - substitute(rate_law, - Dict(p => param_value_dict[p][1] for p in relevant_parameters)), - ] - end - return [substitute(rate_law, - Dict(p => get_component_value(param_value_dict[p], idxE) - for p in relevant_parameters)) for idxE in 1:nE] -end - -### ODEProblem ### - -# Creates an ODEProblem from a LatticeReactionSystem. -function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - ofun = build_odefunction(lrs, pC, pD, jac, sparse) - return ODEProblem(ofun, u0, tspan, pC, args...; kwargs...) -end - -# Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pC::Vector{Vector{Float64}}, - pD::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) - # Prepeares (non-spatial) ODE functions and list of diffusing species and their rates. - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) - diffusion_rates_speciesmap = compute_all_diffusion_rates(pC, pD, lrs) - diffusion_rates = [findfirst(isequal(diff_rates[1]), states(lrs.rs)) => diff_rates[2] - for diff_rates in diffusion_rates_speciesmap] - - f = build_f(ofunc, pC, diffusion_rates, lrs) - jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, diffusion_rates, - lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? build_jac(ofunc, pC, lrs, jac_prototype, sparse) : nothing - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) -end - -# Builds the forcing (f) function for a reaction system on a lattice. -function build_f(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - diffusion_rates::Vector, - lrs::LatticeReactionSystem) - leaving_rates = zeros(length(diffusion_rates), lrs.nC) - for (s_idx, rates) in enumerate(last.(diffusion_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) - - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) - end - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) - - return function (du, u, p, t) - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc((@view du[get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for spatial diffusion reactions. - for (s_idx, (s, rates)) in enumerate(diffusion_rates) - for comp_i::Int64 in 1:(lrs.nC) - du[get_index(comp_i, s, lrs.nS)] -= leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, - lrs.nS)] - end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in enumerated_edges - du[get_index(edge.dst, s, lrs.nS)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, - lrs.nS)] - end - end - end -end - -# Builds the Jacobian function for a reaction system on a lattice. -function build_jac(ofunc::SciMLBase.AbstractODEFunction{true}, pC, - lrs::LatticeReactionSystem, - jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, - sparse::Bool) - pC_location_types = length.(pC) .== 1 - pC_idxs = 1:length(pC) - reset_J_vals = (sparse ? J -> (J.nzval .= 0.0) : J -> (J .= 0.0)) - add_diff_J_vals = (sparse ? J -> (J.nzval .+= jac_prototype.nzval) : - J -> (J .+= Matrix(jac_prototype))) - - return function (J, u, p, t) - # Because of weird stuff where the Jacobian is not reset that I don't understand properly. - reset_J_vals(J) - - # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(lrs.nC) - ofunc.jac((@view J[get_indexes(comp_i, lrs.nS), - get_indexes(comp_i, lrs.nS)]), - (@view u[get_indexes(comp_i, lrs.nS)]), - view_pC_vector(pC, comp_i, pC_location_types, pC_idxs), t) - end - - # Updates for the spatial reactions. - add_diff_J_vals(J) - end -end - -# Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. -function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, - diffusion_rates, lrs::LatticeReactionSystem; - set_nonzero = false) - diff_species = first.(diffusion_rates) - # Gets list of indexes for species that diffuse, but are invovled in no other reaction. - only_diff = [(s in diff_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] - - # Declares sparse array content. - J_colptr = fill(1, lrs.nC * lrs.nS + 1) - J_nzval = fill(0.0, - lrs.nC * (nnz(ns_jac_prototype) + count(only_diff)) + - length(edges(lrs.lattice)) * length(diffusion_rates)) - J_rowval = fill(0, length(J_nzval)) - - # Finds filled elements. - for comp in 1:(lrs.nC), s in 1:(lrs.nS) - col_idx = get_index(comp, s, lrs.nS) - - # Column values. - local_elements = in(s, diff_species) * - (length(lrs.lattice.fadjlist[comp]) + only_diff[s]) - diffusion_elements = -(ns_jac_prototype.colptr[(s + 1):-1:s]...) - J_colptr[col_idx + 1] = J_colptr[col_idx] + local_elements + diffusion_elements - - # Row values. - rows = ns_jac_prototype.rowval[ns_jac_prototype.colptr[s]:(ns_jac_prototype.colptr[s + 1] - 1)] .+ - (comp - 1) * lrs.nS - if in(s, diff_species) - # Finds the location of the diffusion elements, and inserts the elements from the non-spatial part into this. - diffusion_rows = (lrs.lattice.fadjlist[comp] .- 1) .* lrs.nS .+ s - split_idx = isempty(rows) ? 1 : findfirst(diffusion_rows .> rows[1]) - isnothing(split_idx) && (split_idx = length(diffusion_rows) + 1) - rows = vcat(diffusion_rows[1:(split_idx - 1)], rows, - diffusion_rows[split_idx:end]) - if only_diff[s] - split_idx = findfirst(rows .> get_index(comp, s, lrs.nS)) - isnothing(split_idx) && (split_idx = length(rows) + 1) - insert!(rows, split_idx, get_index(comp, s, lrs.nS)) - end - end - J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows - end - - # Set element values. - if !set_nonzero - J_nzval .= 1.0 - else - for (s_idx, (s, rates)) in enumerate(diffusion_rates), - (e_idx, edge) in enumerate(edges(lrs.lattice)) - - col_start = J_colptr[get_index(edge.src, s, lrs.nS)] - col_end = J_colptr[get_index(edge.src, s, lrs.nS) + 1] - 1 - column_view = @view J_rowval[col_start:col_end] - - # Updates the source value. - val_idx_src = col_start + - findfirst(column_view .== get_index(edge.src, s, lrs.nS)) - 1 - J_nzval[val_idx_src] -= get_component_value(rates, e_idx) - - # Updates the destination value. - val_idx_dst = col_start + - findfirst(column_view .== get_index(edge.dst, s, lrs.nS)) - 1 - J_nzval[val_idx_dst] += get_component_value(rates, e_idx) - end - end - - return SparseMatrixCSC(lrs.nS * lrs.nC, lrs.nS * lrs.nC, J_colptr, J_rowval, J_nzval) -end - -### JumpProblem ### - -# Builds a spatial DiscreteProblem from a Lattice Reaction System. - -# Creates a DiscreteProblem from a LatticeReactionSystem. -function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, - p_in = DiffEqBase.NullParameters(), args...; kwargs...) - u0 = lattice_process_u0(u0_in, ModelingToolkit.getname.(species(lrs)), lrs.nC) - pC, pD = lattice_process_p(p_in, Symbol.(compartment_parameters(lrs)), - Symbol.(diffusion_parameters(lrs)), lrs) - return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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), - checks = false, kwargs...) - dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || - error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") - if any(length.(dprob.p[1]) .> 1) - error("Spatial reaction rates are currently not supported in lattice jump simulations.") - end - hopping_constants = make_hopping_constants(dprob, lrs) - ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nC), dprob.tspan, - first.(dprob.p[1])) - majumps_ = make_majumps(___dprob, lrs.rs) - return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, - spatial_system = lrs.lattice, save_positions = (true, false)) -end - -# Creates the hopping constants from a discrete problem and a lattice reaction system. -function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) - diffusion_rates_dict = Dict(Catalyst.compute_all_diffusion_rates(dprob.p[1], dprob.p[2], - lrs)) - all_diff_rates = [haskey(diffusion_rates_dict, s) ? diffusion_rates_dict[s] : [0.0] - for s in species(lrs)] - if length.(all_diff_rates) == 1 - return Catalyst.matrix_expand_component_values(all_diff_rates, - length(vertices(lrs.lattice))) - else - hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nC)] - for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:(lrs.nS) - push!(hopping_constants[s_idx, e.src], - Catalyst.get_component_value(all_diff_rates[s_idx], e_idx)) - end - end - return hopping_constants - end -end - -# Creates the mass action jumps from a discrete problem and a reaction system. -function make_majumps(non_spatial_prob, rs::ReactionSystem) - prob = non_spatial_prob - - js = convert(JumpSystem, rs) - statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) - eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p - majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) - majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) - return majumps -end - -### Accessing State & Parameter Array Values ### - -# Gets the index in the u array of species s in compartment comp (when their are nS species). -get_index(comp::Int64, s::Int64, nS::Int64) = (comp - 1) * nS + s -# Gets the indexes in the u array of all species in comaprtment comp (when their are nS species). -get_indexes(comp::Int64, nS::Int64) = ((comp - 1) * nS + 1):(comp * nS) - -# We have many vectors of length 1 or n, for which we want to get value idx (or the one value, if length is 1), this function gets that. -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64) - get_component_value(values[component_idx], location_idx) -end -function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) - get_component_value(values[component_idx], location_idx, location_types[component_idx]) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64) - get_component_value(values, location_idx, length(values) == 1) -end -function get_component_value(values::Vector{<:Number}, location_idx::Int64, - location_type::Bool) - location_type ? values[1] : values[location_idx] -end -# Converts a vector of vectors to a long vector. -function expand_component_values(values::Vector{<:Vector}, n) - vcat([get_component_value.(values, comp) for comp in 1:n]...) -end -function expand_component_values(values::Vector{<:Vector}, n, location_types::Vector{Bool}) - vcat([get_component_value.(values, comp, location_types) for comp in 1:n]...) -end -# Creates a view of the pC vector at a given comaprtment. -function view_pC_vector(pC, comp, pC_location_types, pC_idxs) - mapview(p_idx -> pC_location_types[p_idx] ? pC[p_idx][1] : pC[p_idx][comp], pC_idxs) -end -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). -function matrix_expand_component_values(values::Vector{<:Vector}, n) - reshape(expand_component_values(values, n), length(values), n) -end \ No newline at end of file diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl new file mode 100644 index 0000000000..37fac4eaf6 --- /dev/null +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -0,0 +1,66 @@ + + +### JumpProblem ### + +# Builds a spatial DiscreteProblem from a Lattice Reaction System. + +# Creates a DiscreteProblem from a LatticeReactionSystem. +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) + is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + + # Converts potential symmaps to varmaps. + 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 Vector{Vector{Float64}} form. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) + pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + + # Creates DiscreteProblem. + return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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),checks = false, kwargs...) + # Error checks. + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") + + # Creates JumpProblem. + hopping_constants = make_hopping_constants(dprob, lrs) + println(typeof(hopping_constants)) + ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nV), dprob.tspan, first.(dprob.p[1])) + majumps_ = make_majumps(___dprob, lrs.rs) + return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) +end + +# Creates the hopping constants from a discrete problem and a lattice reaction system. +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) + spatial_rates_dict = Dict(compute_all_spatial_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)] + if false #all(length.(all_diff_rates) .== 1) + return matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) + else + hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nV)] + for (e_idx, e) in enumerate(edges(lrs.lattice)) + for s_idx in 1:(lrs.nS) + push!(hopping_constants[s_idx, e.src], + get_component_value(all_diff_rates[s_idx], e_idx)) + end + end + return hopping_constants + end +end + +# Creates the mass action jumps from a discrete problem and a reaction system. +function make_majumps(non_spatial_prob, rs::ReactionSystem) + prob = non_spatial_prob + js = convert(JumpSystem, rs) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + eqs = equations(js) + invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) + p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) + majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) + return majumps +end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl deleted file mode 100644 index e416ab6fb2..0000000000 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ /dev/null @@ -1,208 +0,0 @@ -# Not actually run in CI, but useful for reference of ODE simulation performance across updates. - -### Preparations ### - -# Fetch packages. -using OrdinaryDiffEq -using Random, Statistics, SparseArrays, Test - -# Fetch test networks. -include("../spatial_test_networks.jl") - -### Runtime Checks ### -# Current timings are taken from the SciML CI server. -# Current not used, simply here for reference. -# Useful when attempting to optimise workflow. - -# using BenchmarkTools -# runtime_reduction_margin = 10.0 - -# Small grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.00060 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.26 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, small, stiff, system. - -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Medium grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - small_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.0016 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - large_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.67 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 9ecca8a319..905a3994fc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,26 +12,13 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{DiffusionReaction}(), SIR_srs_1, SIR_srs_2] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) - u0_1 = make_values_int([:S => 999.0, :I => 1.0, :R => 0.0]) - u0_2 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 1.0, - :R => 0.0, - ]) - u0_3 = make_values_int([ - :S => 950.0, - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_4 = make_values_int([ - :S => 500.0 .+ 500.0 * rand_v_vals(lrs.lattice), - :I => 50 * rand_v_vals(lrs.lattice), - :R => 50 * rand_v_vals(lrs.lattice), - ]) - u0_5 = make_values_int(make_u0_matrix(u0_3, vertices(lrs.lattice), - map(s -> Symbol(s.f), species(lrs.rs)))) + u0_1 = [:S => 999, :I => 1, :R => 0] + u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] + u0_3 = [:S => 950, :I => round.(Int64, 50 * rand_v_vals(lrs.lattice)), :R => round.(Int64, 50 * rand_v_vals(lrs.lattice))] + u0_4 = [:S => round.(500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => round.(50 * rand_v_vals(lrs.lattice)), :R => round.(50 * rand_v_vals(lrs.lattice))] + u0_5 = make_u0_matrix(u0_3, vertices(lrs.lattice), map(s -> Symbol(s.f), species(lrs.rs))) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] p1 = [:α => 0.1 / 1000, :β => 0.01] p2 = [:α => 0.1 / 1000, :β => 0.02 * rand_v_vals(lrs.lattice)] @@ -41,18 +28,14 @@ let ] p4 = make_u0_matrix(p1, vertices(lrs.lattice), Symbol.(parameters(lrs.rs))) for pV in [p1] #, p2, p3, p4] # Removed until spatial non-diffusion parameters are supported. - pE_1 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_2 = map(sp -> sp => 0.01, - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), - ModelingToolkit.getname.(diffusion_parameters(lrs))) - pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), - ModelingToolkit.getname.(diffusion_parameters(lrs))) + pE_1 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_2 = map(sp -> sp => 0.01, ModelingToolkit.getname.(edge_parameters(lrs))) + pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) + pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) jprob = JumpProblem(lrs, dprob, NSM()) - solve(jprob, SSAStepper()) + @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end end end @@ -76,33 +59,33 @@ let u0_5 = permutedims(hcat(fill(1., nv(small_2d_grid)), fill(2., nv(small_2d_grid)), fill(3., nv(small_2d_grid)))) # Prepare various (compartment) parameter input types. - pC_1 = [:β => 0.2, :α => 0.1] - pC_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] - pC_3 = [0.1, 0.2] - pC_4 = [0.1, fill(0.2, nv(small_2d_grid))] - pC_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) + pV_1 = [:β => 0.2, :α => 0.1] + pV_2 = [:β => fill(0.2, nv(small_2d_grid)), :α => 1.0] + pV_3 = [0.1, 0.2] + pV_4 = [0.1, fill(0.2, nv(small_2d_grid))] + pV_5 = permutedims(hcat(fill(0.1, nv(small_2d_grid)), fill(0.2, nv(small_2d_grid)))) # Prepare various (diffusion) parameter input types. - pD_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] - pD_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] - pD_3 = [0.01, 0.02, 0.03] - pD_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] - pD_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) + pE_1 = [:dI => 0.02, :dS => 0.01, :dR => 0.03] + pE_2 = [:dI => 0.02, :dS => fill(0.01, ne(small_2d_grid)), :dR => 0.03] + pE_3 = [0.01, 0.02, 0.03] + pE_4 = [fill(0.01, ne(small_2d_grid)), 0.02, 0.03] + pE_5 = permutedims(hcat(fill(0.01, ne(small_2d_grid)), fill(0.02, ne(small_2d_grid)), fill(0.03, ne(small_2d_grid)))) # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. - for pC in [pC_1, pC_3], pD in [pD_1, pD_2, pD_3, pD_4, pD_5] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pC,pD)) + for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV,pE)) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates end # Provides parameters as a combined vector. - for pC in [pC_1], pD in [pD_1, pD_2] - dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pD; pC]) + for pV in [pV_1], pE in [pE_1, pE_2] + dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), [pE; pV]) jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates @@ -110,7 +93,6 @@ let end end -using Catalyst, JumpProcesses, Graphs ### ABC Model Test (from JumpProcesses) ### let # Preparations (stuff used in JumpProcesses examples ported over here, could be written directly into code). @@ -131,17 +113,18 @@ let rn = @reaction_network begin (kB,kD), A + B <--> C end - srs = diffusion_reactions([(:D, :A), (:D, :B), (:D, :C)]) + tr_1 = @transport_reaction D A + tr_2 = @transport_reaction D B + tr_3 = @transport_reaction D C lattice = Graphs.grid(dims) - lrs = LatticeReactionSystem(rn, srs, lattice) - + lrs = LatticeReactionSystem(rn, [tr_1, tr_2, tr_3], lattice) # Set simulation parameters and create problems u0 = [:A => [0,0,500,0,0], :B => [0,0,500,0,0], :C => 0] tspan = (0.0, 10.0) - pC = [:kB => rates[1], :kD => rates[2]] - pD = [:D => diffusivity] - dprob = DiscreteProblem(lrs, u0, tspan, (pC, pD)) + pV = [:kB => rates[1], :kD => rates[2]] + pE = [:D => diffusivity] + dprob = DiscreteProblem(lrs, u0, tspan, (pV, pE)) jump_problems = [JumpProblem(lrs, dprob, alg(); save_positions = (false, false)) for alg in [NSM, DirectCRDirect]] # NRM doesn't work. Might need Cartesian grid. # Tests. @@ -162,4 +145,4 @@ let @test abs(d) < reltol * non_spatial_mean[i] end end -end +end \ No newline at end of file diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 11f9d5b2d3..1da79c23ff 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -23,14 +23,6 @@ function spatial_param_syms(lrs::LatticeReactionSystem) ModelingToolkit.getname.(edge_parameters(lrs)) end -# Converts to integer value (for JumpProcess simulations). -function make_values_int(values::Vector{<:Pair}) - [val[1] => round.(Int64, val[2]) for val in values] -end -make_values_int(values::Matrix{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{<:Number}) = round.(Int64, values) -make_values_int(values::Vector{Vector}) = [round.(Int64, vals) for vals in values] - ### Declares Models ### # Small non-stiff system. From abd7bf4d01a06462ad0511deb1709dff6b317d76 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 20:48:49 -0400 Subject: [PATCH 124/134] update --- .../spatial_Jump_systems.jl | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 37fac4eaf6..0757e2efb9 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -1,5 +1,3 @@ - - ### JumpProblem ### # Builds a spatial DiscreteProblem from a Lattice Reaction System. @@ -23,12 +21,14 @@ 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),checks = false, kwargs...) # Error checks. + println() + println(dprob.p) + println(dprob.u0) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") # Creates JumpProblem. hopping_constants = make_hopping_constants(dprob, lrs) - println(typeof(hopping_constants)) ___dprob = DiscreteProblem(reshape(dprob.u0, lrs.nS, lrs.nV), dprob.tspan, first.(dprob.p[1])) majumps_ = make_majumps(___dprob, lrs.rs) return JumpProblem(___dprob, aggregator, majumps_, hopping_constants = hopping_constants, spatial_system = lrs.lattice, save_positions = (true, false)) @@ -38,18 +38,15 @@ end function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) spatial_rates_dict = Dict(compute_all_spatial_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)] - if false #all(length.(all_diff_rates) .== 1) - return matrix_expand_component_values(all_diff_rates, length(vertices(lrs.lattice))) - else - hopping_constants = [Vector{Float64}() for i in 1:(lrs.nS), j in 1:(lrs.nV)] - for (e_idx, e) in enumerate(edges(lrs.lattice)) - for s_idx in 1:(lrs.nS) - push!(hopping_constants[s_idx, e.src], - get_component_value(all_diff_rates[s_idx], e_idx)) + hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) for i in 1:(lrs.nS), j in 1:(lrs.nV)] + for (e_idx, e) in enumerate(edges(lrs.lattice)), s_idx in 1:(lrs.nS) + for dst_idx in 1:length(hopping_constants[s_idx, e.src]) + if (hopping_constants[s_idx, e.src][dst_idx] == undef) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) end end - return hopping_constants end + return hopping_constants end # Creates the mass action jumps from a discrete problem and a reaction system. From 00d917e0ace30e63f6b5b794f901a955c616e71f Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 21:27:49 -0400 Subject: [PATCH 125/134] update --- .../spatial_Jump_systems.jl | 22 ++++++------------- .../lattice_reaction_systems_jumps.jl | 9 ++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 0757e2efb9..12b23f55f8 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -21,9 +21,6 @@ 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),checks = false, kwargs...) # Error checks. - println() - println(dprob.p) - println(dprob.u0) dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") @@ -40,24 +37,19 @@ function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSyst all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) for i in 1:(lrs.nS), j in 1:(lrs.nV)] for (e_idx, e) in enumerate(edges(lrs.lattice)), s_idx in 1:(lrs.nS) - for dst_idx in 1:length(hopping_constants[s_idx, e.src]) - if (hopping_constants[s_idx, e.src][dst_idx] == undef) - hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) - end - end + dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) end return hopping_constants end # Creates the mass action jumps from a discrete problem and a reaction system. -function make_majumps(non_spatial_prob, rs::ReactionSystem) - prob = non_spatial_prob +function make_majumps(non_spat_dprob, rs::ReactionSystem) js = convert(JumpSystem, rs) - statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(ModelingToolkit.states(js))) + statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) eqs = equations(js) - invttype = prob.tspan[1] === nothing ? Float64 : typeof(1 / prob.tspan[2]) - p = (prob.p isa DiffEqBase.NullParameters || prob.p === nothing) ? Num[] : prob.p + invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) + 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) - majumps = ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) - return majumps + return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) end \ No newline at end of file diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 905a3994fc..0b36b88b46 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -75,6 +75,9 @@ let # Checks hopping rates and u0 are correct. true_u0 = [fill(1.0, 1, 25); fill(2.0, 1, 25); fill(3.0, 1, 25)] true_hopping_rates = cumsum.([fill(dval, length(v)) for dval in [0.01,0.02,0.03], v in small_2d_grid.fadjlist]) + true_maj_scaled_rates = [0.1, 0.2] + true_maj_reactant_stoch = [[1 => 1, 2 => 1], [2 => 1]] + true_maj_net_stoch = [[1 => -1, 2 => 1], [2 => -1, 3 => 1]] for u0 in [u0_1, u0_2, u0_3, u0_4, u0_5] # Provides parameters as a tupple. for pV in [pV_1, pV_3], pE in [pE_1, pE_2, pE_3, pE_4, pE_5] @@ -82,6 +85,9 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch end # Provides parameters as a combined vector. for pV in [pV_1], pE in [pE_1, pE_2] @@ -89,6 +95,9 @@ let jprob = JumpProblem(lrs, dprob, NSM()) @test jprob.prob.u0 == true_u0 @test jprob.discrete_jump_aggregation.hop_rates.hop_const_cumulative_sums == true_hopping_rates + @test jprob.massaction_jump.scaled_rates == true_maj_scaled_rates + @test jprob.massaction_jump.reactant_stoch == true_maj_reactant_stoch + @test jprob.massaction_jump.net_stoch == true_maj_net_stoch end end end From 2831fd33591ed1844715368c1deed3e2eebbbee7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 12:45:16 -0400 Subject: [PATCH 126/134] up --- src/spatial_reaction_systems/spatial_Jump_systems.jl | 9 +++++++++ .../lattice_reaction_systems_jumps.jl | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 12b23f55f8..276dddc4a4 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -6,20 +6,29 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") + println("\nMaking dprob") # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) + println(p_in) + println(typeof(p_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 Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + println((pC, pD)) + println(typeof((pC, pD))) # Creates DiscreteProblem. return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), 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),checks = false, kwargs...) + + println("\nMaking jprob") + println(dprob.p) + println(typeof(dprob.p)) # Error checks. dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 0b36b88b46..bc70eaa4eb 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -33,7 +33,13 @@ let pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] + println("\n\n\nHere: 1") + println(pE) + println(typeof(pE)) + println((pV, pE)) dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + println(dprob.p) + println(typeof(dprob.p)) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 197cd230f827807a1d883a6720f4b2c8f7bc05f8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 13:21:59 -0400 Subject: [PATCH 127/134] test testing --- test/runtests.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/runtests.jl b/test/runtests.jl index 30c1a1f9f2..41cbd9c34e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,7 @@ using SafeTestsets ### Run the tests ### @time begin + @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end ### Tests the properties of ReactionSystems. ### @time @safetestset "ReactionSystem" begin include("reactionsystem_structure/reactionsystem.jl") end From 5f015570c33694429ed91bf7d81620893ab1bdc9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 13:41:37 -0400 Subject: [PATCH 128/134] test test --- .../spatial_Jump_systems.jl | 16 ++++++++++++---- .../lattice_reaction_systems_jumps.jl | 4 ---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 276dddc4a4..ac6ed3b935 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -6,17 +6,28 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) is_transport_system(lrs) || error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") - println("\nMaking dprob") # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) + println() println(p_in) println(typeof(p_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) + println() + println(p_in) + println(typeof(p_in)) + # Converts u0 and p to Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + println() + println(pC) + println(pD) + println(typeof(pC)) + println(typeof(pD)) + + println() println((pC, pD)) println(typeof((pC, pD))) # Creates DiscreteProblem. @@ -26,9 +37,6 @@ 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),checks = false, kwargs...) - println("\nMaking jprob") - println(dprob.p) - println(typeof(dprob.p)) # Error checks. dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index bc70eaa4eb..192f5c9166 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -33,10 +33,6 @@ let pE_3 = map(sp -> sp => rand_e_vals(lrs.lattice, 0.01), ModelingToolkit.getname.(edge_parameters(lrs))) pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] - println("\n\n\nHere: 1") - println(pE) - println(typeof(pE)) - println((pV, pE)) dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) println(dprob.p) println(typeof(dprob.p)) From 76fd25cf765309683b403719ec4b0e570fe8e06e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 15:07:12 -0400 Subject: [PATCH 129/134] test update --- .../spatial_Jump_systems.jl | 17 ----------------- .../lattice_reaction_systems_jumps.jl | 4 +--- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index ac6ed3b935..69120fea3d 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -8,28 +8,11 @@ function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_ # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) - println() - println(p_in) - println(typeof(p_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) - println() - println(p_in) - println(typeof(p_in)) - # Converts u0 and p to Vector{Vector{Float64}} form. u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) pC, pD = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) - - println() - println(pC) - println(pD) - println(typeof(pC)) - println(typeof(pD)) - - println() - println((pC, pD)) - println(typeof((pC, pD))) # Creates DiscreteProblem. return DiscreteProblem(lrs.rs, u0, tspan, (pC, pD), args...; kwargs...) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 192f5c9166..3104fe3992 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,7 +12,7 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] + for srs in [SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] @@ -34,8 +34,6 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - println(dprob.p) - println(typeof(dprob.p)) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 2454f48d37e40c5a2a89ba224e2209f4bf689c84 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 15:23:51 -0400 Subject: [PATCH 130/134] test testing --- test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 3104fe3992..70d74d2dee 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -34,6 +34,7 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) + println(dprob.p) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 89c1e0e29d5f7845f37b88268698848823027ccb Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 16:05:23 -0400 Subject: [PATCH 131/134] fix --- src/spatial_reaction_systems/spatial_Jump_systems.jl | 3 +-- .../spatial_reaction_systems/lattice_reaction_systems_jumps.jl | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_Jump_systems.jl b/src/spatial_reaction_systems/spatial_Jump_systems.jl index 69120fea3d..955266ce55 100644 --- a/src/spatial_reaction_systems/spatial_Jump_systems.jl +++ b/src/spatial_reaction_systems/spatial_Jump_systems.jl @@ -19,9 +19,8 @@ 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),checks = false, kwargs...) - # Error checks. - dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") + dprob.p isa Tuple{Vector{Vector{Float64}}, Vector{Vector{Float64}}} || dprob.p isa Vector{Vector} || error("Parameters in input DiscreteProblem is of an unexpected type: $(typeof(dprob.p)). Was a LatticeReactionProblem passed into the DiscreteProblem when it was created?") # The second check (Vector{Vector} is needed becaus on the CI server somehow the Tuple{..., ...} is covnerted into a Vector[..., ...]). It does not happen when I run tests locally, so no ideal how to fix. any(length.(dprob.p[1]) .> 1) && error("Spatial reaction rates are currently not supported in lattice jump simulations.") # Creates JumpProblem. diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl index 70d74d2dee..0b36b88b46 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_jumps.jl @@ -12,7 +12,7 @@ include("../spatial_test_networks.jl") # Tests that there are no errors during runs. let for grid in [small_2d_grid, short_path, small_directed_cycle] - for srs in [SIR_srs_1, SIR_srs_2] + for srs in [Vector{TransportReaction}(), SIR_srs_1, SIR_srs_2] lrs = LatticeReactionSystem(SIR_system, srs, grid) u0_1 = [:S => 999, :I => 1, :R => 0] u0_2 = [:S => round.(Int64, 500.0 .+ 500.0 * rand_v_vals(lrs.lattice)), :I => 1, :R => 0, ] @@ -34,7 +34,6 @@ let pE_4 = make_u0_matrix(pE_3, edges(lrs.lattice), ModelingToolkit.getname.(edge_parameters(lrs))) for pE in [pE_1, pE_2, pE_3, pE_4] dprob = DiscreteProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - println(dprob.p) jprob = JumpProblem(lrs, dprob, NSM()) @test SciMLBase.successful_retcode(solve(jprob, SSAStepper())) end From 87f7ccc70493f957f67f8b657f53b3bc4b915dbd Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 16:27:58 -0400 Subject: [PATCH 132/134] test update --- test/runtests.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 41cbd9c34e..1396aca0cd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,8 +3,6 @@ using SafeTestsets ### Run the tests ### @time begin - @time @safetestset "Jump Lattice Systems Simulations" begin include("spatial_reaction_systems/lattice_reaction_systems_jumps.jl") end - ### Tests the properties of ReactionSystems. ### @time @safetestset "ReactionSystem" begin include("reactionsystem_structure/reactionsystem.jl") end @time @safetestset "Higher Order Reactions" begin include("reactionsystem_structure/higher_order_reactions.jl") end From 8f5b10f7a1433eb79cbdbb14eb2224b335fc470b Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 19 Sep 2023 21:52:29 -0400 Subject: [PATCH 133/134] fix merge --- ...ttice_reaction_systems_ODEs_performance.jl | 208 ------------------ 1 file changed, 208 deletions(-) delete mode 100644 test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl deleted file mode 100644 index e416ab6fb2..0000000000 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs_performance.jl +++ /dev/null @@ -1,208 +0,0 @@ -# Not actually run in CI, but useful for reference of ODE simulation performance across updates. - -### Preparations ### - -# Fetch packages. -using OrdinaryDiffEq -using Random, Statistics, SparseArrays, Test - -# Fetch test networks. -include("../spatial_test_networks.jl") - -### Runtime Checks ### -# Current timings are taken from the SciML CI server. -# Current not used, simply here for reference. -# Useful when attempting to optimise workflow. - -# using BenchmarkTools -# runtime_reduction_margin = 10.0 - -# Small grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, small_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.00060 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, non-stiff, system. -let - lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, large_2d_grid) - u0 = [:S => 990.0, :I => 20.0 * rand_v_vals(lrs.lattice), :R => 0.0] - pV = SIR_p - pE = [:dS => 0.01, :dI => 0.01, :dR => 0.01] - oprob = ODEProblem(lrs, u0, (0.0, 500.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.26 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, small, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, small, stiff, system. - -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, small_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.17 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Medium grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, medium_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 2.3 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Medium grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, small, stiff, system. -let - lrs = LatticeReactionSystem(brusselator_system, brusselator_srs_1, large_2d_grid) - u0 = [:X => rand_v_vals(lrs.lattice, 10), :Y => rand_v_vals(lrs.lattice, 10)] - pV = brusselator_p - pE = [:dX => 0.2] - oprob = ODEProblem(lrs, u0, (0.0, 100.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 170.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, small, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - small_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.0016 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Small grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, non-stiff, system. -let - lrs = LatticeReactionSystem(CuH_Amination_system, CuH_Amination_srs_2, - large_2d_grid) - u0 = [ - :CuoAc => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :Ligand => 0.005 .+ rand_v_vals(lrs.lattice, 0.005), - :CuoAcLigand => 0.0, - :Silane => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :CuHLigand => 0.0, - :SilaneOAc => 0.0, - :Styrene => 0.16, - :AlkylCuLigand => 0.0, - :Amine_E => 0.39, - :AlkylAmine => 0.0, - :Cu_ELigand => 0.0, - :E_Silane => 0.0, - :Amine => 0.0, - :Decomposition => 0.0, - ] - pV = CuH_Amination_p - pE = [:D1 => 0.1, :D2 => 0.1, :D3 => 0.1, :D4 => 0.1, :D5 => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE); jac = false) - @test SciMLBase.successful_retcode(solve(oprob, Tsit5())) - - runtime_target = 0.67 - runtime = minimum((@benchmark solve($oprob, Tsit5())).times) / 1000000000 - println("Large grid, mid-sized, non-stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Small grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, small_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 0.019 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Small grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end - -# Large grid, mid-sized, stiff, system. -let - lrs = LatticeReactionSystem(sigmaB_system, sigmaB_srs_2, large_2d_grid) - u0 = [ - :w => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :v => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2v2 => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vP => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :w2σB => 0.5 .+ rand_v_vals(lrs.lattice, 0.5), - :vPp => 0.0, - :phos => 0.4, - ] - pV = sigmaB_p - pE = [:DσB => 0.1, :Dw => 0.1, :Dv => 0.1] - oprob = ODEProblem(lrs, u0, (0.0, 10.0), (pV, pE)) - @test SciMLBase.successful_retcode(solve(oprob, QNDF())) - - runtime_target = 35.0 - runtime = minimum((@benchmark solve($oprob, QNDF())).times) / 1000000000 - println("Large grid, mid-sized, stiff, system. Runtime: $(runtime), previous standard: $(runtime_target)") - @test runtime < runtime_reduction_margin * runtime_target -end From 92cdc847eb10eb9c889c23008ca0a27581894c52 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 20 Sep 2023 11:01:44 -0400 Subject: [PATCH 134/134] update history file --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index 04c103b292..aaac7b45d3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,7 @@ ## Catalyst unreleased (master branch) - Simulation of spatial ODEs now supported. For full details, please see /~https://github.com/SciML/Catalyst.jl/pull/644 and upcoming documentation. +- Simulations of spatial Jumps are now supported. For full details see: /~https://github.com/SciML/Catalyst.jl/pull/663. For documentation on the spatial SSA solvers used, pelase see: https://docs.sciml.ai/JumpProcesses/stable/tutorials/spatial/. - LatticeReactionSystem structure represents a spatiral reaction network: ```julia rn = @reaction_network begin