From 665a199cd07709ba93b1825a15424eb507d20950 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 15:14:12 -0400 Subject: [PATCH 001/121] 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 221489f7a2..87bfcda3c1 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -40,7 +40,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 21f3f4436c07220476ba00d47f5baef2f0667279 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 15:16:33 -0400 Subject: [PATCH 002/121] 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 b33b6782d0..8f82de35b1 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -71,6 +71,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 87bfcda3c1..1423ffdcfd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -43,7 +43,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 b2c7a3b6f5c25b207680d7f12e2d2f1cdf1023aa Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 16:01:28 -0400 Subject: [PATCH 003/121] 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 8f82de35b1..e14a3b80c5 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,7 +32,8 @@ import ModelingToolkit: check_variables, 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 eeb7eb876ae505cc115f32e6b642bfebd3358d60 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 4 May 2023 18:19:40 -0400 Subject: [PATCH 004/121] 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 9ba272134b2f05eef7d0f2dd9382e0d0140b8a82 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 11:36:32 -0400 Subject: [PATCH 005/121] 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 1fb738a2e5b2425969e796ad08567084654f0d1c Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 15:03:32 -0400 Subject: [PATCH 006/121] update tests --- Project.toml | 1 + .../lattice_reaction_systems.jl | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/Project.toml b/Project.toml index 6300c1427e..bd8e79e63a 100644 --- a/Project.toml +++ b/Project.toml @@ -46,6 +46,7 @@ Unitful = "1.12.4" julia = "1.9" [extras] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85" HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" 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 93b69266ce0670eb6a35e2015621b89992452362 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 5 May 2023 16:11:49 -0400 Subject: [PATCH 007/121] 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 4593a3a4761dc244ea65e3e570d63b85bc924d35 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 28 Jun 2023 17:54:09 -0400 Subject: [PATCH 008/121] 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 e14a3b80c5..58053349c3 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -73,8 +73,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 283b46a611cb0c2a66b9f7741431daf67aaf7991 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 09:25:36 -0400 Subject: [PATCH 009/121] 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 c07c2af3a2f051c954e9735388f521fb25f8d397 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 09:29:05 -0400 Subject: [PATCH 010/121] 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 a2bb7cf80076a5287ae59583069280846101006d Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 10:39:15 -0400 Subject: [PATCH 011/121] 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 659a189f2407f9cb6c906a04f68865c357d1b288 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 16:45:55 -0400 Subject: [PATCH 012/121] 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 97d2cf0adb7bcdbadca70c377448513ba4b3c038 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 16:57:56 -0400 Subject: [PATCH 013/121] 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 ebb2d8809862c21514228b6dfe6026142b790358 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 29 Jun 2023 20:06:46 -0400 Subject: [PATCH 014/121] 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 e0d68f1f2fc0c5da50eb4e37c214cddd9f4064a6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 30 Jun 2023 18:14:56 -0400 Subject: [PATCH 015/121] 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 a334a34bea8bc446a3af9b4c6b924db9470522ba Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 12:25:00 -0400 Subject: [PATCH 016/121] 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 7f7bc88e5c09756cee128ea3c0274563fc0eedc6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 12:54:13 -0400 Subject: [PATCH 017/121] 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 585d4b47be86e0d1ef8f3128763cb630033d5468 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:45:25 -0400 Subject: [PATCH 018/121] 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 58053349c3..9358da0087 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -74,7 +74,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 4af6384641e15d16fd92de5328b8affac8b9a65c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:45:41 -0400 Subject: [PATCH 019/121] 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 1b8cf80c6e23cbb76e392449ad517590bf305f98 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 13:52:53 -0400 Subject: [PATCH 020/121] 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 4b66b59cdca2f46dbcde568ccc1cb551346529e2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 2 Jul 2023 14:12:20 -0400 Subject: [PATCH 021/121] 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 1b8f09f543db4f19bfe42754d588ce54816b7619 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 12:31:08 -0400 Subject: [PATCH 022/121] 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 147c0d8f97e263256749489b0f1b24aae74486c3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 17:44:17 -0400 Subject: [PATCH 023/121] 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 fbaa4883d77889d12c87d372769da22aae14c302 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 3 Jul 2023 17:44:30 -0400 Subject: [PATCH 024/121] 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 107858f8d53a14a070794c0f3c8a95ef49240ed8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 4 Jul 2023 11:48:42 -0400 Subject: [PATCH 025/121] 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 f284bc616f00b89f38479870ea4fdc267144091b Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 11:27:00 -0400 Subject: [PATCH 026/121] 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 5f627f3c2ac1986c3be22be7d83d242130ee3a19 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:08:58 -0400 Subject: [PATCH 027/121] 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 9358da0087..9062332dfa 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -74,7 +74,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 151c80a1eab12e7a0046d9fab69f2b1ab70b0033 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:10:02 -0400 Subject: [PATCH 028/121] 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 22826d5c29ec4a83d943af1984c659c42f9d579a Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 6 Jul 2023 12:30:56 -0400 Subject: [PATCH 029/121] 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 405f28c5dede90b21db2e5c503da177a2f1de3b6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 13 Jul 2023 15:14:40 -0400 Subject: [PATCH 030/121] 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 6393a794cd2c2ee25359b1d89e16847b5f16b44e Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 1 Aug 2023 10:56:13 -0400 Subject: [PATCH 031/121] use Num's everywhere --- Project.toml | 1 - src/lattice_reaction_system_diffusion.jl | 44 +++++++++++++++--------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Project.toml b/Project.toml index bd8e79e63a..6300c1427e 100644 --- a/Project.toml +++ b/Project.toml @@ -46,7 +46,6 @@ Unitful = "1.12.4" julia = "1.9" [extras] -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" Graphviz_jll = "3c863552-8265-54e4-a6dc-903eb78fde85" HomotopyContinuation = "f213a82b-91d6-5c5d-acf7-10f1c761b327" 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 024134ce72cf53572c3f2acebb72c2c2efe10801 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 1 Aug 2023 11:37:35 -0400 Subject: [PATCH 032/121] 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 8728a198c36f75ea402b97f0e88bb92de665d1e2 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 11:52:42 -0400 Subject: [PATCH 033/121] 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 bcf4efe98a2985c2e7a1374b5d9a1d883f0cd425 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:02:29 -0400 Subject: [PATCH 034/121] 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 1fef52588a81d6211d30a4a7f9f7699a4e59fe48 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:05:57 -0400 Subject: [PATCH 035/121] 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 e40e04ccce473173951ef8b4ae70177d9c3b723a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 2 Aug 2023 16:51:13 -0400 Subject: [PATCH 036/121] 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 24586bab51462a4675585b6e79e95e545d1fd49d Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 12:41:51 -0400 Subject: [PATCH 037/121] 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 6300c1427e..e34e6efcec 100644 --- a/Project.toml +++ b/Project.toml @@ -17,6 +17,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 9062332dfa..e1618cc326 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -9,6 +9,7 @@ using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump +using SplitApplyCombine # ModelingToolkit imports and convenience functions we use using ModelingToolkit @@ -74,8 +75,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 25abf21d6229c280d84029e3ea201e7eae5d15f6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:40:22 -0400 Subject: [PATCH 038/121] 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 e1618cc326..0eaa45947e 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -34,7 +34,7 @@ import ModelingToolkit: check_variables, 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 e64e12986cd726dfc71a1039bc923d6790838fa5 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:56:24 -0400 Subject: [PATCH 039/121] 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 0eaa45947e..f77205b4aa 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -34,7 +34,8 @@ import ModelingToolkit: check_variables, 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 52bb6e4b34f8497e4b1e1a656653055553dc44f4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 15:56:32 -0400 Subject: [PATCH 040/121] 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 5fb99bef1c9cc1b7053a3119cf71d02be6317302 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 12 Aug 2023 18:18:02 -0400 Subject: [PATCH 041/121] 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 abc4f4ef83014bb5e35e0bb8e86c05908a7bf837 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:18:19 -0400 Subject: [PATCH 042/121] 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 a474b1ba8d750b9e8cab8e6e6ce6e5c859f8e359 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:25:30 -0400 Subject: [PATCH 043/121] 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 bf0395a1221d10ccf082f6aa2294d9c7e5d58eb4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 11:57:40 -0400 Subject: [PATCH 044/121] 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 06cc919a45800bfc148c95f8e75de927bac75b60 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 13 Aug 2023 16:25:58 -0400 Subject: [PATCH 045/121] 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 f77205b4aa..3835e64819 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -34,8 +34,7 @@ import ModelingToolkit: check_variables, 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 7537bbe42650bebdbb86b97c11c3154a95c088b0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 16:40:18 +0200 Subject: [PATCH 046/121] 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 3835e64819..cffde18304 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -34,7 +34,7 @@ import ModelingToolkit: check_variables, 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 aaf6c7fa4568b2d9d17b5508761ea1ad114808d3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 3 Sep 2023 17:29:45 +0200 Subject: [PATCH 047/121] 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 8e221d9ff28c2dc71092d643651d677633ebe489 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 15:31:46 -0400 Subject: [PATCH 048/121] 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 1423ffdcfd..1632e2c463 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -43,7 +43,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 32dd7e1f0587463b7b057255fab7c7ff4a0bc144 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 15:35:15 -0400 Subject: [PATCH 049/121] 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 d97c4a1f48eb03ae043a0acdf49272ed53ba5bdb Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 16:25:13 -0400 Subject: [PATCH 050/121] 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 9d034df7f4bb1f2f4ff7a27b233accaee5f6e164 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 16:31:40 -0400 Subject: [PATCH 051/121] 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 1115e723a16d25b8db0b2c5406c21cacd2c2ea9c Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 11 Sep 2023 17:24:37 -0400 Subject: [PATCH 052/121] 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 35f0aeb41769144e690dc1f4d3effe84a96ab29b Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 08:47:32 -0400 Subject: [PATCH 053/121] 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 cffde18304..af23dc4d48 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -73,12 +73,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 @@ -115,4 +109,13 @@ export balance_reaction function hc_steady_states end export hc_steady_states +# 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 1632e2c463..e41d70fec7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -43,6 +43,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 01769cf98d80f0ccfe9715268a6a6296889acb69 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 11:41:52 -0400 Subject: [PATCH 054/121] 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 af23dc4d48..be02e8d5f6 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -111,7 +111,8 @@ export hc_steady_states # 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 f19c4c168068b7d142983280bba1866968ce6ce3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 11:59:02 -0400 Subject: [PATCH 055/121] 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 f3ad0630936899290a30602d694de2ddefc58040 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 19:43:27 -0400 Subject: [PATCH 056/121] 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 be02e8d5f6..41fa4ba3b7 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -119,4 +119,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 264965a063cf3a5b8a4c7629416f2ac4090ecd24 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 12 Sep 2023 20:16:07 -0400 Subject: [PATCH 057/121] 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 0ee1a53d43a8a3df236cdde7cc0a9ff41c1888ac Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 13:55:05 -0400 Subject: [PATCH 058/121] 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 186d09be55da6e6b56b0e84004011959cef0b658 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 14:30:00 -0400 Subject: [PATCH 059/121] Remove SplitApplyCombine dependency --- Project.toml | 1 - src/Catalyst.jl | 1 - 2 files changed, 2 deletions(-) diff --git a/Project.toml b/Project.toml index e34e6efcec..6300c1427e 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 41fa4ba3b7..2c0a7cf6b0 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -9,7 +9,6 @@ using LaTeXStrings, Latexify, Requires using JumpProcesses: JumpProcesses, JumpProblem, MassActionJump, ConstantRateJump, VariableRateJump -using SplitApplyCombine # ModelingToolkit imports and convenience functions we use using ModelingToolkit From e2d555f6a2e7bbc425fd937f4cd4742f31fe8066 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 14:55:28 -0400 Subject: [PATCH 060/121] 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 0b75dafb3c1ee304dd5fa73d155914760032327a Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 13 Sep 2023 15:39:53 -0400 Subject: [PATCH 061/121] 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 d851fb61b5c45bdb777cf689e3f3a7a4b1541a8c Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 14 Sep 2023 23:57:27 -0400 Subject: [PATCH 062/121] 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 4647bf23a821b7acba204bccb351aa837bd391dc Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 15 Sep 2023 12:21:57 -0400 Subject: [PATCH 063/121] 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 cf73270bbfaf1a100f2d5a61f3721e2145649ec4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 16 Sep 2023 21:04:02 -0400 Subject: [PATCH 064/121] 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 2c0a7cf6b0..8e367a245a 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -108,6 +108,8 @@ export balance_reaction function hc_steady_states end export hc_steady_states +### Spatial Reaction Networks ### + # spatial reactions include("spatial_reaction_systems/spatial_reactions.jl") export TransportReaction, transport_reactions, @transport_reaction @@ -118,6 +120,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 591a9f58f3fd81317245446a0215dc3772be899c Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 14:24:27 -0400 Subject: [PATCH 065/121] 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 e41d70fec7..f9c7d44529 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,6 +45,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 acd760f705ff799e385a7aa731267c9f0d81304b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 14:45:00 -0400 Subject: [PATCH 066/121] fix --- test/runtests.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index f9c7d44529..e41d70fec7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -45,7 +45,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 f0235d371fc53c493aa6b885cbf183e398bc326e Mon Sep 17 00:00:00 2001 From: Torkel Date: Sun, 17 Sep 2023 15:32:09 -0400 Subject: [PATCH 067/121] update history file. --- HISTORY.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 2bb952cd00..e02f11faff 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.5 - Added a CatalystHomotopyContinuationExtension extension, which exports the `hc_steady_state` function if HomotopyContinuation is exported. `hc_steady_state` finds the steady states of a reactin system using the homotopy continuation method. This feature is only available for julia versions 1.9+. Example: From 2e3a1cb195f6bf808348f579084e88bc5ae8e6a9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 16:40:37 -0400 Subject: [PATCH 068/121] Update src/spatial_reaction_systems/spatial_reactions.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 8f4ab6099f..0f9f44a7d0 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -24,7 +24,7 @@ end struct TransportReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currently only constants supported""" rate::Num - """The species that is subject to difusion.""" + """The species that is subject to diffusion.""" species::BasicSymbolic{Real} # Creates a diffusion reaction. From ae8c7b6d3f0c30e4c042f8ac310728a70005a68b Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 16:41:20 -0400 Subject: [PATCH 069/121] Update src/spatial_reaction_systems/lattice_reaction_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/lattice_reaction_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index d2ce9d2ccc..07c122c16b 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -63,4 +63,4 @@ edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters 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) +is_transport_system(lrs::LatticeReactionSystem) = all(sr -> sr isa TransportReaction, lrs.spatial_reactions) From d0644cef109bbeb8605335ccec3162df669663c0 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 16:42:08 -0400 Subject: [PATCH 070/121] Update src/Catalyst.jl Co-authored-by: Sam Isaacson --- src/Catalyst.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 8e367a245a..37b3ce6732 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -32,8 +32,8 @@ import ModelingToolkit: check_variables, get_unit, check_equations import Base: (==), hash, size, getindex, setindex, isless, Sort.defalg, length, show -import MacroTools -import Graphs, Graphs.DiGraph, Graphs.SimpleGraph, Graphs.SimpleDiGraph, Graphs.vertices, Graphs.edges, Graphs.add_vertices!, Graphs.nv, Graphs.ne +import MacroTools, Graphs +import Graphs: DiGraph, SimpleGraph, SimpleDiGraph, vertices, edges, add_vertices!, nv, ne import DataStructures: OrderedDict, OrderedSet import Parameters: @with_kw_noshow From cbbd07da7924950cc7977584e55c9f0e76b80921 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 16:43:01 -0400 Subject: [PATCH 071/121] Update src/spatial_reaction_systems/spatial_reactions.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 0f9f44a7d0..eb9d63a0af 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -66,7 +66,7 @@ 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; 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).") + any(isequal(tr.species), species(rs)) || error("Currently, species used in TransportReactions must have previously been declared within the 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)))).") # Checks that the species does not exist in the system with different metadata. From 1dd205c39c530deafd09992c71e7e2043779e2cb Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 16:43:15 -0400 Subject: [PATCH 072/121] Update src/spatial_reaction_systems/spatial_reactions.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index eb9d63a0af..5c9d603a22 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -46,7 +46,7 @@ macro transport_reaction(rateex::ExprValues, species::Symbol) make_transport_reaction(MacroTools.striplines(rateex), species) end function make_transport_reaction(rateex, species) - parameters = []; + parameters = ExprValues[] find_parameters_in_rate!(parameters, rateex) quote $(isempty(parameters) ? nothing : :(@parameters $(parameters...))) From 59da638e4e973b055ddad29eec2058f0b5e9ec0b Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 18:19:38 -0400 Subject: [PATCH 073/121] updates --- HISTORY.md | 14 +- src/Catalyst.jl | 2 +- .../lattice_reaction_systems.jl | 14 +- .../spatial_ODE_systems.jl | 133 ++++++++++-------- .../spatial_reactions.jl | 6 +- ...attice_reaction_systems_ODE_performance.jl | 0 .../lattice_reaction_systems.jl | 2 +- .../lattice_reaction_systems_ODEs.jl | 10 +- test/spatial_test_networks.jl | 4 +- 9 files changed, 101 insertions(+), 84 deletions(-) rename test/{spatial_reaction_systems => performance_benchmarks}/lattice_reaction_systems_ODE_performance.jl (100%) diff --git a/HISTORY.md b/HISTORY.md index e02f11faff..5b2c03e319 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,7 +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. -- LatticeReactionSystem structure represents a spatiral reaction network: +- LatticeReactionSystem structure represents a spatial reaction network: ```julia rn = @reaction_network begin (p,d), 0 <--> X @@ -10,12 +10,20 @@ tr = @transport_reaction D X lattice = Graphs.grid([5, 5]) lrs = LatticeReactionSystem(rn, [tr], lattice) +- Here, if a `u0` or `p` vector is given with scalar values: + ```julia + u0 = [:X => 1.0] + p = [:p => 1.0, :d => 0.5, :D => 0.1] ``` - + this value will be used across the entire system. If their values are instead vectors, different values are used across the spatial system. Here + ```julia + u0 = [:X => [1.0, 0.0, 0.0, ...]] + ``` + X's value will be `1.0` in the first vertex, but `0.0` in the remaining one (the system have 25 vertexes in total). SInce th parameters `p` and `d` are part of the non-spatial reaction network, their values are tied to vertexes. However, if the `D` parameter (which governs diffusion between vertexes) is given several values, these will instead correspond to the specific edges (and transportation along those edges.) ## Catalyst 13.5 -- Added a CatalystHomotopyContinuationExtension extension, which exports the `hc_steady_state` function if HomotopyContinuation is exported. `hc_steady_state` finds the steady states of a reactin system using the homotopy continuation method. This feature is only available for julia versions 1.9+. Example: +- Added a CatalystHomotopyContinuationExtension extension, which exports the `hc_steady_state` function if HomotopyContinuation is exported. `hc_steady_state` finds the steady states of a reaction system using the homotopy continuation method. This feature is only available for julia versions 1.9+. Example: ```julia wilhelm_2009_model = @reaction_network begin k1, Y --> 2X diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 37b3ce6732..33b95ebaab 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -112,7 +112,7 @@ export hc_steady_states # spatial reactions include("spatial_reaction_systems/spatial_reactions.jl") -export TransportReaction, transport_reactions, @transport_reaction +export TransportReaction, TransportReactions, @transport_reaction export isedgeparameter # lattice reaction systems diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 07c122c16b..a86ce5d7e1 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,14 +1,14 @@ ### Lattice Reaction Network Structure ### -# Desribes a spatial reaction network over a graph. +# Describes a spatial reaction network over a graph. struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs - """The reaction system within each comaprtment.""" + """The reaction system within each compartment.""" rs::ReactionSystem{S} """The spatial reactions defined between individual nodes.""" spatial_reactions::Vector{T} """The graph on which the lattice is defined.""" lattice::SimpleDiGraph{Int64} - # Derrived values. + # Derived values. """The number of compartments.""" nV::Int64 """The number of edges.""" @@ -19,7 +19,7 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t init_digraph::Bool """Species that may move spatially.""" spat_species::Vector{BasicSymbolic{Real}} - """Parameters which values are tied to edges (adjacencies)..""" + """Parameters which values are tied to edges (adjacencies), e.g. (possibly) have a unique value at each edge of the system.""" edge_parameters::Vector{BasicSymbolic{Real}} function LatticeReactionSystem(rs::ReactionSystem{S}, @@ -38,12 +38,6 @@ end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) 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], DiGraph(lat); init_digraph = false) -end ### Lattice ReactionSystem Getters ### diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 514a0b6a4b..8ef1ac746e 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -2,45 +2,62 @@ # Functor structure containg the information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeDiffusionODEf{R,S,T} + """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::R + """The number of vertices.""" nV::Int64 + """The number of species.""" nS::Int64 + """The values of the parameters which values are tied to vertexes.""" pV::Vector{Vector{Float64}} + """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(nV) vector. To avoid re-allocation they are written to this vector.""" work_pV::Vector{Float64} + """For each parameter in pV, it either have length nV or 1. To know whenever a parameter's value need expanding to the work_pV array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} - spatial_rates::Vector{S} + """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its diffusion rate values. If the diffusion rate is uniform, that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" + spatial_rates::Vector{Pair{Int64, Vector{Float64}}} + """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" leaving_rates::Matrix{Float64} + """An (enumerate'ed) itarator over all the edges of the lattice.""" enumerated_edges::T - function LatticeDiffusionODEf(ofunc::R, pV, spatial_rates::Vector{S}, lrs::LatticeReactionSystem) where {R, S, T} - leaving_rates = zeros(length(spatial_rates), lrs.nV) + function LatticeDiffusionODEf(ofunc::R, pV, spatial_rates::Vector{Pair{Int64, Vector{Float64}}}, lrs::LatticeReactionSystem) where {R, S} + leaving_rates = zeros(length(spatial_rates), lrs.nV) # Initialises the leaving rates matrix with zeros. for (s_idx, rates) in enumerate(last.(spatial_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) + (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all spatial rates (map from each diffusing species to its rates across edges). - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) # Updates the leaving rate for that combination of vertex and species. RHS finds the value of edge "e_idx" in the vector of diffusion rates ("rates"). end - work_pV = zeros(lrs.nV) - enumerated_pV_idx_types = enumerate(length.(pV) .== 1) - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) + work_pV = zeros(lrs.nV) # Initialises the work pV vector to be empty. + enumerated_pV_idx_types = enumerate(length.(pV) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). + enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. 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 spatial movement on a lattice. +# Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeDiffusionODEjac{S,T} + """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::S + """The number of vertices.""" nV::Int64 + """The number of species.""" nS::Int64 + """The values of the parameters which values are tied to vertexes.""" pV::Vector{Vector{Float64}} - work_pV::Vector{Float64} + """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(nV) vector. To avoid re-allocation they are written to this vector.""" + work_pV::Vector{Float64} + """For each parameter in pV, it either have length nV or 1. To know whenever a parameter's value need expanding to the work_pV array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} + """Whether the Jacobian is sparse or not.""" sparse::Bool + """The values of the Jacobian. All the diffusion rates. Eitehr in matrix form (for non-sparse, in this case with potential zeros) or as the "nzval" field of the sparse jacobian matrix.""" jac_values::T 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) + work_pV = zeros(lrs.nV) # Initialises the work pV vector to be empty. + enumerated_pV_idx_types = enumerate(length.(pV) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). + jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). new{S,typeof(jac_values)}(ofunc, lrs.nV, lrs.nS, pV, work_pV, enumerated_pV_idx_types, sparse, jac_values) end end @@ -55,49 +72,50 @@ 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) + 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) # Parameters can be given in Tuple form (where the first element is the vertex parameters and the second the edge parameters). In this case, we have to covert each element separately. - # Converts u0 and p to Vector{Vector{Float64}} form. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) - pV, pE = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) + # Converts u0 and p to their internal forms. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) # u0 becomes a vector ([species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]) + pV, pE = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Both pV and pE becomes vectors of vectors. Each have 1 element for each parameter. These elements are length 1 vectors (if the parameter is uniform), or length nV/nE, with unique values for each vertex/edge (for pV/pE, respectively). # Creates ODEProblem. - ofun = build_odefunction(lrs, pV, pE, jac, sparse) - return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) + ofun = build_odefunction(lrs, pV, pE, jac, sparse) # Builds the ODEFunction. + return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) # Creates a normal ODEProblem. end # Builds an ODEFunction for a spatial ODEProblem. function build_odefunction(lrs::LatticeReactionSystem, pV::Vector{Vector{Float64}}, 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, pE, lrs) + # Prepares (non-spatial) ODE functions and list of spatially moving species and their rates. + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. + spatial_rates_speciesmap = compute_all_spatial_rates(pV, pE, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. spatial_rates = [findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] - for spat_rates in spatial_rates_speciesmap] + for spat_rates in spatial_rates_speciesmap] # Remakes "spatial_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "spatial_rates_speciesmap" they instead were Symbolics. - f = LatticeDiffusionODEf(ofunc, pV, spatial_rates, lrs) + f = LatticeDiffusionODEf(ofunc, pV, spatial_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, spatial_rates, - lrs; set_nonzero = use_jac) : nothing - jac = use_jac ? LatticeDiffusionODEjac(ofunc, pV, lrs, jac_prototype, sparse) : nothing - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) + lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). + jac = use_jac ? LatticeDiffusionODEjac(ofunc, pV, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) # Creates the ODEFunction used in the ODEProblem. 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}, spatial_rates, lrs::LatticeReactionSystem; set_nonzero = false) - spat_species = first.(spatial_rates) + spat_species = first.(spatial_rates) # Gets a list of species with transportation (As their index in the full species vector. If you have species [X(t), Y(t)], with transportation for Y only, this becomes [2]). - # Gets list of indexes for species that move spatially, but are invovled in no other reaction. + # Gets list of indexes for species that move spatially, but are involved in no other reaction. only_spat = [(s in spat_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] + for s in 1:(lrs.nS)] # Probably rare, but these creates weird special cases where there block diagonal part of the Jacobian have empty spaces. # Declares sparse array content. - J_colptr = fill(1, lrs.nV * lrs.nS + 1) - J_nzval = fill(0.0, - lrs.nV * (nnz(ns_jac_prototype) + count(only_spat)) + - length(edges(lrs.lattice)) * length(spatial_rates)) + J_colptr = fill(1, lrs.nV * lrs.nS + 1) # Initiates the column values of the (sparse) jacobian. + J_nzval = fill(0.0, # Initiates the row values of the (sparse) jacobian. Has one value for each value in teh sparse jacobian. + lrs.nV * (nnz(ns_jac_prototype) + # The number of values due to the non-spatial jacobian (its number of values, and multiplied by the number of vertexes). + count(only_spat)) + # + length(edges(lrs.lattice)) * length(spatial_rates)) # Initiates the row values of the (sparse) jacobian. J_rowval = fill(0, length(J_nzval)) # Finds filled elements. @@ -146,14 +164,6 @@ 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) @@ -163,49 +173,50 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, 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. +# Defines the forcing functor's 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.nV) - f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), - (@view u[get_indexes(comp_i, f_func.nS)]), - view_pV_vector!(f_func.work_pV, p, comp_i, f_func.enumerated_pV_idx_types), t) + for comp_i::Int64 in 1:(f_func.nV) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. + f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. + (@view u[get_indexes(comp_i, f_func.nS)]), # Same as above, but reads the current species concentrations. + view_pV_vector!(f_func.work_pV, p, comp_i, f_func.enumerated_pV_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. + t) # Time. end # Updates for spatial reactions. - for (s_idx, (s, rates)) in enumerate(f_func.spatial_rates) - for comp_i::Int64 in 1:(f_func.nV) + for (s_idx, (s, rates)) in enumerate(f_func.spatial_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). + for comp_i::Int64 in 1:(f_func.nV) # Loops through all vertexes. 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)] + f_func.nS)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges # Loops through all 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)] + f_func.nS)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. end end end -# Defines the jacobian functors effect on the (spatial) ODE system. +# Defines the jacobian functor's 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) + reset_J_vals!(J) # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(jac_func.nV) + for comp_i::Int64 in 1:(jac_func.nV) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. 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_pV_vector!(jac_func.work_pV, p, comp_i, jac_func.enumerated_pV_idx_types), t) + view_pV_vector!(jac_func.work_pV, p, comp_i, jac_func.enumerated_pV_idx_types), t)# These inputs are the same as when f_func.ofunc was applied in the previous block. end # Updates for the spatial reactions. - add_spat_J_vals!(J, jac_func) + add_spat_J_vals!(J, jac_func) # Adds the Jacobian values from the diffusion reactions. end -# Resets the jacobian matrix within a jac call. -reset_J_vals!(J::Matrix) = (J .= 0.0) +# Resets the jacobian matrix within a jac call. Separate for spatial and non-spatial cases. +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. +# Updates the jacobian matrix with the difusion values. Separate for spatial and non-spatial cases. 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 5c9d603a22..9eaca64ace 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -19,7 +19,7 @@ end ### Transport Reaction Structures ### -# A transport reaction. These are simple to hanlde, and should cover most types of spatial reactions. +# A transport reaction. These are simple to handle, and should cover most types of spatial reactions. # Only permit constant rates (possibly consisting of several parameters). struct TransportReaction <: AbstractSpatialReaction """The rate function (excluding mass action terms). Currently only constants supported""" @@ -32,12 +32,12 @@ struct TransportReaction <: AbstractSpatialReaction new(rate, species.val) end end -# If the rate is a value, covert that to a numemric expression. +# If the rate is a value, covert that to a numeric expression. function TransportReaction(rate::Number, args...) TransportReaction(Num(rate), args...) end # Creates a vector of TransportReactions. -function transport_reactions(transport_reactions) +function TransportReactions(transport_reactions) [TransportReaction(tr[1], tr[2]) for tr in transport_reactions] end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl b/test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl similarity index 100% rename from test/spatial_reaction_systems/lattice_reaction_systems_ODE_performance.jl rename to test/performance_benchmarks/lattice_reaction_systems_ODE_performance.jl diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 0aad5f08ae..adc38cd97b 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -158,7 +158,7 @@ let (p,1), 0 <--> X end @unpack d, X = rs - trs = transport_reactions([(d, X), (d, X)]) + trs = TransportReactions([(d, X), (d, X)]) @test isequal(trs[1], trs[2]) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 39dfae628f..22ec74cafc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -7,6 +7,10 @@ using Random, Statistics, SparseArrays, Test # Fetch test networks. include("../spatial_test_networks.jl") +# Sets rnd number. +using StableRNGs +rng = StableRNG(12345) + ### Tests Simulations Don't Error ### for grid in [small_2d_grid, short_path, small_directed_cycle] # Non-stiff case @@ -69,7 +73,7 @@ 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, spatial_param_syms(lrs)) - pE_2 = map(sp -> sp => rand(), spatial_param_syms(lrs)) + pE_2 = map(sp -> sp => rand(rng), 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)) @@ -90,7 +94,7 @@ end # 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()] + u0 = [:X => 2.0 + 2.0 * rand(rng), :Y => 10.0 + 10.0 * rand(rng)] pV = brusselator_p pE = [:dX => 0.2] oprob_nonspatial = ODEProblem(brusselator_system, u0, (0.0, 100.0), pV) @@ -497,7 +501,7 @@ let return sparse(jac_prototype_pre) end - u0 = 2 * rand(10000) + u0 = 2 * rand(rng, 10000) p = [1.0, 4.0, 0.1] tspan = (0.0, 100.0) diff --git a/test/spatial_test_networks.jl b/test/spatial_test_networks.jl index 11f9d5b2d3..f2f9472e87 100644 --- a/test/spatial_test_networks.jl +++ b/test/spatial_test_networks.jl @@ -7,7 +7,7 @@ rng = StableRNG(12345) ### Helper Functions ### -# Generates ranomised intiial condition or paraemter values. +# Generates randomised initial condition or parameter 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)) @@ -125,7 +125,7 @@ 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. +# Unsure about stiffness, 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 From 3a21f446e4e2d64288f286e8a25e7889933eb166 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 3 Nov 2023 18:23:00 -0400 Subject: [PATCH 074/121] up --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 8ef1ac746e..65a2d56725 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -115,12 +115,12 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, J_nzval = fill(0.0, # Initiates the row values of the (sparse) jacobian. Has one value for each value in teh sparse jacobian. lrs.nV * (nnz(ns_jac_prototype) + # The number of values due to the non-spatial jacobian (its number of values, and multiplied by the number of vertexes). count(only_spat)) + # - length(edges(lrs.lattice)) * length(spatial_rates)) # Initiates the row values of the (sparse) jacobian. - J_rowval = fill(0, length(J_nzval)) + length(edges(lrs.lattice)) * length(spatial_rates)) # + J_rowval = fill(0, length(J_nzval)) # Finds filled elements. - for comp in 1:(lrs.nV), s in 1:(lrs.nS) - col_idx = get_index(comp, s, lrs.nS) + for comp in 1:(lrs.nV), s in 1:(lrs.nS) # Loops through all vertexes and species. + col_idx = get_index(comp, s, lrs.nS) # For the current vertex+species, finds its column (same as its index in the `u` vector). # Column values. local_elements = in(s, spat_species) * From fc4892532991cf9ed9e339b1bf07e1416f7c4c72 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 13:42:59 -0400 Subject: [PATCH 075/121] update --- .../lattice_reaction_systems.jl | 9 +- .../spatial_ODE_systems.jl | 220 +++++++----------- src/spatial_reaction_systems/utility.jl | 166 +++++++------ 3 files changed, 188 insertions(+), 207 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index a86ce5d7e1..deb9d1232e 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,6 +1,7 @@ ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a graph. struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs + # Input values. """The reaction system within each compartment.""" rs::ReactionSystem{S} """The spatial reactions defined between individual nodes.""" @@ -10,11 +11,11 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t # Derived values. """The number of compartments.""" - nV::Int64 + num_verts::Int64 """The number of edges.""" - nE::Int64 + num_edges::Int64 """The number of species.""" - nS::Int64 + num_species::Int64 """Whenever the initial input was a digraph.""" init_digraph::Bool """Species that may move spatially.""" @@ -25,7 +26,7 @@ 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 second 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 ascertain 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)) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 65a2d56725..d6b1d85a7a 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,37 +1,37 @@ ### Spatial ODE Functor Structures ### -# Functor structure containg the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeDiffusionODEf{R,S,T} +# Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. +struct LatticeDiffusionODEf{S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::R + ofunc::S """The number of vertices.""" - nV::Int64 + num_verts::Int64 """The number of species.""" - nS::Int64 + num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - pV::Vector{Vector{Float64}} - """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(nV) vector. To avoid re-allocation they are written to this vector.""" - work_pV::Vector{Float64} - """For each parameter in pV, it either have length nV or 1. To know whenever a parameter's value need expanding to the work_pV array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} - """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its diffusion rate values. If the diffusion rate is uniform, that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" - spatial_rates::Vector{Pair{Int64, Vector{Float64}}} + vert_ps::Vector{Vector{Float64}} + """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector.""" + work_vert_ps::Vector{Float64} + """For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" + enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} + """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" + transport_rates::Vector{Pair{Int64, Vector{Float64}}} """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" leaving_rates::Matrix{Float64} - """An (enumerate'ed) itarator over all the edges of the lattice.""" - enumerated_edges::T + """An (enumerate'ed) iterator over all the edges of the lattice.""" + enum_edges::T - function LatticeDiffusionODEf(ofunc::R, pV, spatial_rates::Vector{Pair{Int64, Vector{Float64}}}, lrs::LatticeReactionSystem) where {R, S} - leaving_rates = zeros(length(spatial_rates), lrs.nV) # Initialises the leaving rates matrix with zeros. - for (s_idx, rates) in enumerate(last.(spatial_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all spatial rates (map from each diffusing species to its rates across edges). + function LatticeDiffusionODEf(ofunc::S, vert_ps, transport_rates::Vector{Pair{Int64, Vector{Float64}}}, lrs::LatticeReactionSystem) where {S} + leaving_rates = zeros(length(transport_rates), lrs.num_verts) # Initialises the leaving rates matrix with zeros. + for (s_idx, rates) in enumerate(last.(transport_rates)), + (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all transport rates (map from each diffusing species to its rates across edges). leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) # Updates the leaving rate for that combination of vertex and species. RHS finds the value of edge "e_idx" in the vector of diffusion rates ("rates"). end - work_pV = zeros(lrs.nV) # Initialises the work pV vector to be empty. - enumerated_pV_idx_types = enumerate(length.(pV) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). - enumerated_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. - new{R,S,typeof(enumerated_edges)}(ofunc, lrs.nV, lrs.nS, pV, work_pV, enumerated_pV_idx_types, spatial_rates, leaving_rates, enumerated_edges) + work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. + enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (and enumerates it, since it always appear in this form). + enum_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. + new{S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) end end @@ -40,25 +40,25 @@ struct LatticeDiffusionODEjac{S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::S """The number of vertices.""" - nV::Int64 + num_verts::Int64 """The number of species.""" - nS::Int64 + num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - pV::Vector{Vector{Float64}} - """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(nV) vector. To avoid re-allocation they are written to this vector.""" - work_pV::Vector{Float64} - """For each parameter in pV, it either have length nV or 1. To know whenever a parameter's value need expanding to the work_pV array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enumerated_pV_idx_types::Base.Iterators.Enumerate{BitVector} + vert_ps::Vector{Vector{Float64}} + """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector.""" + work_vert_ps::Vector{Float64} + """For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" + enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} """Whether the Jacobian is sparse or not.""" sparse::Bool """The values of the Jacobian. All the diffusion rates. Eitehr in matrix form (for non-sparse, in this case with potential zeros) or as the "nzval" field of the sparse jacobian matrix.""" jac_values::T - function LatticeDiffusionODEjac(ofunc::S, pV, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S, T} - work_pV = zeros(lrs.nV) # Initialises the work pV vector to be empty. - enumerated_pV_idx_types = enumerate(length.(pV) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). + function LatticeDiffusionODEjac(ofunc::S, vert_ps, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S} + work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. + enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). - new{S,typeof(jac_values)}(ofunc, lrs.nV, lrs.nS, pV, work_pV, enumerated_pV_idx_types, sparse, jac_values) + new{S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) end end @@ -75,125 +75,85 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, 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) # Parameters can be given in Tuple form (where the first element is the vertex parameters and the second the edge parameters). In this case, we have to covert each element separately. # Converts u0 and p to their internal forms. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.nV) # u0 becomes a vector ([species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]) - pV, pE = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Both pV and pE becomes vectors of vectors. Each have 1 element for each parameter. These elements are length 1 vectors (if the parameter is uniform), or length nV/nE, with unique values for each vertex/edge (for pV/pE, respectively). + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) # u0 becomes a vector ([species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]). + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. These elements are length 1 vectors (if the parameter is uniform), or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). # Creates ODEProblem. - ofun = build_odefunction(lrs, pV, pE, jac, sparse) # Builds the ODEFunction. - return ODEProblem(ofun, u0, tspan, pV, args...; kwargs...) # Creates a normal ODEProblem. + ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse) # Builds the ODEFunction. + return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) # Creates a normal ODEProblem. end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, pV::Vector{Vector{Float64}}, - pE::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{Float64}}, + edge_ps::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) # Prepares (non-spatial) ODE functions and list of spatially moving species and their rates. - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. - spatial_rates_speciesmap = compute_all_spatial_rates(pV, pE, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. - spatial_rates = [findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] - for spat_rates in spatial_rates_speciesmap] # Remakes "spatial_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "spatial_rates_speciesmap" they instead were Symbolics. + ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. + transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. + transport_rates = [findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] + for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. - f = LatticeDiffusionODEf(ofunc, pV, spatial_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). + f = LatticeDiffusionODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, spatial_rates, - lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). - jac = use_jac ? LatticeDiffusionODEjac(ofunc, pV, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) # Creates the ODEFunction used in the ODEProblem. + build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, + lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). + jac = use_jac ? LatticeDiffusionODEjac(ofunc, vert_ps, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). + return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) # Creates the ODEFunction used in the ODEProblem. 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}, spatial_rates, lrs::LatticeReactionSystem; +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, trans_rates, lrs::LatticeReactionSystem; set_nonzero = false) - spat_species = first.(spatial_rates) # Gets a list of species with transportation (As their index in the full species vector. If you have species [X(t), Y(t)], with transportation for Y only, this becomes [2]). - - # Gets list of indexes for species that move spatially, but are involved in no other reaction. - only_spat = [(s in spat_species) && !Base.isstored(ns_jac_prototype, s, s) - for s in 1:(lrs.nS)] # Probably rare, but these creates weird special cases where there block diagonal part of the Jacobian have empty spaces. - - # Declares sparse array content. - J_colptr = fill(1, lrs.nV * lrs.nS + 1) # Initiates the column values of the (sparse) jacobian. - J_nzval = fill(0.0, # Initiates the row values of the (sparse) jacobian. Has one value for each value in teh sparse jacobian. - lrs.nV * (nnz(ns_jac_prototype) + # The number of values due to the non-spatial jacobian (its number of values, and multiplied by the number of vertexes). - 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.nV), s in 1:(lrs.nS) # Loops through all vertexes and species. - col_idx = get_index(comp, s, lrs.nS) # For the current vertex+species, finds its column (same as its index in the `u` vector). - - # Column values. - 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 - - # 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, 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)) - end - end - J_rowval[J_colptr[col_idx]:(J_colptr[col_idx + 1] - 1)] = rows - end - + # Finds the indexes of the transport species, and the species with transport only (and no non-spatial dynamics). + trans_species = first.(trans_rates) + trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) + + # Finds the indexes of all terms in the non-spatial jacobian. + ns_jac_prototype_idxs = findnz(ns_jac_prototype) + ns_i_idxs = ns_jac_prototype_idxs[1] + ns_j_idxs = ns_jac_prototype_idxs[2] + + # List the indexes of all non-zero Jacobian terms. + non_spat_terms = [[get_index(vert, s_i, lrs.num_species), get_index(vert, s_j, lrs.num_species)] for vert in 1:(lrs.num_verts) for (s_i, s_j) in zip(ns_i_idxs,ns_j_idxs)] # Indexes of elements due to non-spatial dynamics. + trans_only_leaving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.src, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_only_species] # Indexes due to terms for a species leaves its current vertex (but does not have non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already be accounted for. + trans_arriving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.dst, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_species] # Indexes due to terms for species arriving into a new vertex. + all_terms = [non_spat_terms; trans_only_leaving_terms; trans_arriving_terms] + + # Creates a jacobian prototype with 0 values in all positions). + jac_prototype = sparse(first.(all_terms), last.(all_terms), fill(0.0, length(all_terms))) + # Set element values. - if !set_nonzero - J_nzval .= 1.0 - else - 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)] - 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) + if set_nonzero + for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) # Loops through all species with transportation and all edges along which the can be transported. + jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.src, s, lrs.num_species)] -= get_component_value(rates, e_idx) # Term due to species leaving source vertex. + jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.dst, s, lrs.num_species)] += get_component_value(rates, e_idx) # Term due to species arriving to destination vertex. end end - return SparseMatrixCSC(lrs.nS * lrs.nV, lrs.nS * lrs.nV, J_colptr, J_rowval, J_nzval) + return jac_prototype end # Defines the forcing functor's 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.nV) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. - f_func.ofunc((@view du[get_indexes(comp_i, f_func.nS)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. - (@view u[get_indexes(comp_i, f_func.nS)]), # Same as above, but reads the current species concentrations. - view_pV_vector!(f_func.work_pV, p, comp_i, f_func.enumerated_pV_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. - t) # Time. + for comp_i::Int64 in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. + f_func.ofunc((@view du[get_indexes(comp_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. + (@view u[get_indexes(comp_i, f_func.num_species)]), # Same as above, but reads the current species concentrations. + view_vert_ps_vector!(f_func.work_vert_ps, p, comp_i, f_func.enum_v_ps_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. + t) # Time. end # Updates for spatial reactions. - for (s_idx, (s, rates)) in enumerate(f_func.spatial_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). - for comp_i::Int64 in 1:(f_func.nV) # Loops through all vertexes. - du[get_index(comp_i, s, f_func.nS)] -= f_func.leaving_rates[s_idx, comp_i] * + for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). + for comp_i::Int64 in 1:(f_func.num_verts) # Loops through all vertexes. + du[get_index(comp_i, s, f_func.num_species)] -= f_func.leaving_rates[s_idx, comp_i] * u[get_index(comp_i, s, - f_func.nS)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). + f_func.num_species)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enumerated_edges # Loops through all edges. - du[get_index(edge.dst, s, f_func.nS)] += get_component_value(rates, e_idx) * + for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enum_edges # Loops through all edges. + du[get_index(edge.dst, s, f_func.num_species)] += get_component_value(rates, e_idx) * u[get_index(edge.src, s, - f_func.nS)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. + f_func.num_species)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. end end end @@ -204,11 +164,11 @@ function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) reset_J_vals!(J) # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(jac_func.nV) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. - 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_pV_vector!(jac_func.work_pV, p, comp_i, jac_func.enumerated_pV_idx_types), t)# These inputs are the same as when f_func.ofunc was applied in the previous block. + for comp_i::Int64 in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. + jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.num_species), + get_indexes(comp_i, jac_func.num_species)]), + (@view u[get_indexes(comp_i, jac_func.num_species)]), + view_vert_ps_vector!(jac_func.work_vert_ps, p, comp_i, jac_func.enum_v_ps_idx_types), t) # These inputs are the same as when f_func.ofunc was applied in the previous block. end # Updates for the spatial reactions. diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index c7fcea1a60..50614e4575 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -1,162 +1,182 @@ ### Processes Input u0 & p ### -# Required to make symmapt_to_varmap to work. +# Defines _symbol_to_var, but where the system is a LRS. 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))) + p_idx = findfirst(sym==p_sym for p_sym in ModelingToolkit.getname.(parameters(lrs))) # Checks if sym is a parameter. isnothing(p_idx) || return parameters(lrs)[p_idx] - s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) + s_idx = findfirst(sym==s_sym for s_sym in ModelingToolkit.getname.(species(lrs))) # Checks if sym is a species. 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) +# From u0 input, extracts their values and store them in the internal format (a vector on the form [species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]). +function lattice_process_u0(u0_in, u0_syms, num_verts) + u0 = lattice_process_input(u0_in, u0_syms, num_verts) # u0 values can be given in various forms. This converts it to a Vector{Vector{}} form (Vector containing one vector for each species. Second vector has one value if species uniform across lattice, else one value for each vertex). + check_vector_lengths(u0, length(u0_syms), num_verts) # Perform various error checks on the (by the user provided) initial conditions. + expand_component_values(u0, num_verts) # Converts the Vector{Vector{}} format to a single Vector (with one values for each species and vertex). 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...) + vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) # If the user provided parameters as a single map (mixing vertex and edge parameters) , these are split into two separate vectors. + vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. + edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. + lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) # If the lattice was defined as an undirected graph (with N edges), but then provides N/2 values for some edge parameter, we presume they want to expand that parameters value so it has the same value in both directions. + check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) # Perform various error checks on the (by the user provided) vertex parameters. + check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) # Perform various error checks on the (by the user provided) edge parameters. + return vert_ps, edge_ps +end + +# Splits parameters into those for the vertexes and those for the edges. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps # If they are already split, return that. +function split_parameters(ps::Vector{<:Number}, args...) # Providing parameters to a spatial reaction system as a single vector of values (e.g. [1.0, 4.0, 0.1]) is not allowed, Either use tuple (e.g. ([1.0, 4.0], [0.1])) or map format (e.g. [A => 1.0, B => 4.0, D => 0.1]). 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 +function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) # Splitting is only done for Vectors of Pairs (where the first value is a Symbols, and the second a value). + vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] # The input vertex parameters. + edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] # The input edge parameters. + (sum(length.([vert_ps_in, edge_ps_in])) != length(ps)) && error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") # Error check, in case some input parameters where neither recognised as vertex or edge parameters. + return vert_ps_in, edge_ps_in end -# If the input is given in a map form, the vector needs sorting and the first value removed. +# Input is allowed on the following forms (after potential Symbol maps have been converted to Symbolic maps: + # - A vector of values, where the i'th value corresponds to the value of the i'th: initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). + # - A vector of vectors of values. The same as previously, but here the species/parameter can have different values across the spatial structure. + # - A map of symbols to their values. These can either be a single value (if uniform across the spatial structure) or a vector (with different values for each vertex/edge). These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). + # - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, indicating the value of each species at each vertex. +# The lattice_process_input function takes input initial conditions/vertex parameters/edge parameters of whichever form the user have used, and converts them to the Vector{Vector{}} form used internally. E.g. for parameters the top vector contain one vector for each parameter (using the same order as in parameters(::ReactionSystem)). If a parameter is uniformly-values across the spatial structure, its vector has a single value. Else, it has a number of values corresponding to the number of vertexes/edges (for edge/vertex parameters). Initial conditions works similarly. + +# If the input is given in a map form, the vector needs sorting and the first value removed. The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. 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). +# If the input is a matrix: Processes the input and gives it in a form where it is a vector of vectors (some of which may have a single value). Sends it back to lattice_process_input for reprocessing. 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.") + error("3 dimensional array parameter input currently not supported.") # Supposedly we want to support this type of input at some point. end +# If the input is a Vector containing both vectors and single values, converts it to the Vector{<:Vector} form. Technically this last lattice_process_input is probably not needed. 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 +# If the input is of the correct form already, return it. 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. +# Checks that a value vector have the right length, as well as that of all its sub vectors. Error check if e.g. the user does not provide values for all species/parameters, or for one, provides a vector of values, but that has the wrong length (e.g providing 7 values for one species, but there are 8 vertexes). 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) +# For transport parameters, if the lattice was given as an undirected graph of size n, this is converted to a directed graph of size 2n. +# If transport parameters are given with n values, we want to use 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 the correct value in each position. +function duplicate_trans_params!(edge_ps::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. + for idx in 1:length(edge_ps) # Loops through all edge parameter. + (2length(edge_ps[idx]) == lrs.num_edges) || continue # If the edge parameter already have one value for each directed edge, it is fine and we can continue. - 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. + # This entire thing depends on the fact that, in the edges(lattice) iterator, the edges are sorted by (1) Their source node, (2) Their destination node. + new_vals = Vector{Float64}(undef,lrs.num_edges) # A vector where we will put the edge parameters new values. Has the correct length (the number of directed edges in the lattice). + 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) # For each edge. + (edge.src < edge.dst) ? (original_edge_count += 1) : continue # The digraph conversion 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] + new_vals[idx_fwd] = edge_ps[idx][original_edge_count] + new_vals[idx_bwd] = edge_ps[idx][original_edge_count] end - pE[idx] = new_vals + edge_ps[idx] = new_vals # Replaces the edge parameters values with the updated value vector. 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)) +function param_dict(vert_ps, edge_ps, lrs) + merge(vals_to_dict(vertex_parameters(lrs), vert_ps), + vals_to_dict(edge_parameters(lrs), edge_ps)) 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) +# Computes the transport rates and stores them in a desired format (a Dictionary from species index to rates across all edges). +function compute_all_transport_rates(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) + param_value_dict = param_dict(vert_ps, edge_ps, lrs) # Creates a dict, allowing us to access the values of wll parameters. + unsorted_rates = [s => Symbolics.value.(compute_transport_rates(get_transport_rate_law(s, lrs), param_value_dict, lrs.num_edges)) for s in spatial_species(lrs)] # For all species with transportation, compute their transportation rate (across all edges). This is a vector, pairing each species to these rates. + return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem), +end +# For a species, retrieves the symbolic expression for its transportation rate (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). +function get_transport_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) +# For the numeric expression describing the rate of transport (likely only a single parameter, such as `D`), and the values of all our parameters, computes the transport rate(s). If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. Else a vector with each value corresponding to the rate at one specific edge. +function compute_transport_rates(rate_law::Num, + param_value_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) + relevant_parameters = Symbolics.get_variables(rate_law) # Extracts the parameters that this rate depend on. + if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) # If all these parameters are spatially uniform. return [ substitute(rate_law, - Dict(p => param_value_dict[p][1] for p in relevant_parameters)), + Dict(p => param_value_dict[p][1] for p in relevant_parameters)), # Computes a length 1 vector with this value. ] end - return [substitute(rate_law, + return [substitute(rate_law, # If at least on parameter the rate depends on have a value varying across all edges, we have to compute one rate value for each edge. Dict(p => get_component_value(param_value_dict[p], idxE) - for p in relevant_parameters)) for idxE in 1:nE] + for p in relevant_parameters)) for idxE in 1:num_edges] 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) +# Gets the index in the u array of species s in vertex vert (when their are num_species species). +get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species + s +# Gets the indexes in the u array of all species in vertex vert (when their are num_species species). +get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) -# 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. +# 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. Here: + # - values is the vector with the values of teh component across all locations (where the internal vectors may or may not be of size 1). + # - component_idx is the initial condition species/vertex parameter/edge parameters's index. This is predominantly used for parameters, for initial conditions, it is only used once (at initialisation) to re-process the input vector. + # - location_idx is the index of the vertex or edge for which we wish to access a initial condition or parameter values +# The first two function takes the full value vector, and call the function of at the components specific index. 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}) + location_idx::Int64, location_types::Vector{Bool}) # Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. This is stored in location_types. get_component_value(values[component_idx], location_idx, location_types[component_idx]) end +# For a components value (which is a vector of either length 1 or some other length), retrieves its value. 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] + location_type ? values[1] : values[location_idx] # Again, the location type (length of teh value vector) may be pre-computed. end -# Converts a vector of vectors to a long vector. + +# Converts a vector of vectors to a long vector. These are used when the initial condition is converted to a single vector (from vector of vector form). 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]) + +# Creates a view of the vert_ps vector at a given location. Provides a work vector to which the converted vector is written. +function view_vert_ps_vector!(work_vert_ps, vert_ps, comp, enumerated_vert_ps_idx_types) + for (idx,loc_type) in enumerated_vert_ps_idx_types # Loops through all parameters. + work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) # If the parameter is uniform across the spatial structure, it will have a length-1 value vector (which value we write to the work vector). Else, we extract it value at the specific location. end - return work_pV + return work_vert_ps end -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently used in Spatial Jump systems). + +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently only 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 63af0edf549b84b14cbaa0040c96235b141b04b4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 13:43:40 -0400 Subject: [PATCH 076/121] remove internal type ascretain --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index d6b1d85a7a..42c06d46b5 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -136,7 +136,7 @@ end # Defines the forcing functor's 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.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. + for comp_i in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. f_func.ofunc((@view du[get_indexes(comp_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. (@view u[get_indexes(comp_i, f_func.num_species)]), # Same as above, but reads the current species concentrations. view_vert_ps_vector!(f_func.work_vert_ps, p, comp_i, f_func.enum_v_ps_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. @@ -145,12 +145,12 @@ function (f_func::LatticeDiffusionODEf)(du, u, p, t) # Updates for spatial reactions. for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). - for comp_i::Int64 in 1:(f_func.num_verts) # Loops through all vertexes. + for comp_i in 1:(f_func.num_verts) # Loops through all vertexes. du[get_index(comp_i, s, f_func.num_species)] -= f_func.leaving_rates[s_idx, comp_i] * u[get_index(comp_i, s, f_func.num_species)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). end - for (e_idx::Int64, edge::Graphs.SimpleGraphs.SimpleEdge{Int64}) in f_func.enum_edges # Loops through all edges. + for (e_idx, edge) in f_func.enum_edges # Loops through all edges. du[get_index(edge.dst, s, f_func.num_species)] += get_component_value(rates, e_idx) * u[get_index(edge.src, s, f_func.num_species)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. @@ -164,7 +164,7 @@ function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) reset_J_vals!(J) # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). # Updates for non-spatial reactions. - for comp_i::Int64 in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. + for comp_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.num_species), get_indexes(comp_i, jac_func.num_species)]), (@view u[get_indexes(comp_i, jac_func.num_species)]), From f9d27147c7da3341269b4e82316073701c6f287b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 13:45:11 -0400 Subject: [PATCH 077/121] update --- .../spatial_ODE_systems.jl | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 42c06d46b5..0d1c38098b 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -136,23 +136,23 @@ end # Defines the forcing functor's effect on the (spatial) ODE system. function (f_func::LatticeDiffusionODEf)(du, u, p, t) # Updates for non-spatial reactions. - for comp_i in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. - f_func.ofunc((@view du[get_indexes(comp_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. - (@view u[get_indexes(comp_i, f_func.num_species)]), # Same as above, but reads the current species concentrations. - view_vert_ps_vector!(f_func.work_vert_ps, p, comp_i, f_func.enum_v_ps_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. + for vert_i in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. + f_func.ofunc((@view du[get_indexes(vert_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. + (@view u[get_indexes(vert_i, f_func.num_species)]), # Same as above, but reads the current species concentrations. + view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, f_func.enum_v_ps_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. t) # Time. end # Updates for spatial reactions. for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). - for comp_i in 1:(f_func.num_verts) # Loops through all vertexes. - du[get_index(comp_i, s, f_func.num_species)] -= f_func.leaving_rates[s_idx, comp_i] * - u[get_index(comp_i, s, + for vert_i in 1:(f_func.num_verts) # Loops through all vertexes. + du[get_index(vert_i, s, f_func.num_species)] -= f_func.leaving_rates[s_idx, vert_i] * + u[get_index(vert_i, s, f_func.num_species)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). end - for (e_idx, edge) in f_func.enum_edges # Loops through all edges. - du[get_index(edge.dst, s, f_func.num_species)] += get_component_value(rates, e_idx) * - u[get_index(edge.src, s, + for (e_idx, e) in f_func.enum_edges # Loops through all edges. + du[get_index(e.dst, s, f_func.num_species)] += get_component_value(rates, e_idx) * + u[get_index(e.src, s, f_func.num_species)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. end end @@ -164,11 +164,11 @@ function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) reset_J_vals!(J) # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). # Updates for non-spatial reactions. - for comp_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. - jac_func.ofunc.jac((@view J[get_indexes(comp_i, jac_func.num_species), - get_indexes(comp_i, jac_func.num_species)]), - (@view u[get_indexes(comp_i, jac_func.num_species)]), - view_vert_ps_vector!(jac_func.work_vert_ps, p, comp_i, jac_func.enum_v_ps_idx_types), t) # These inputs are the same as when f_func.ofunc was applied in the previous block. + for vert_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. + jac_func.ofunc.jac((@view J[get_indexes(vert_i, jac_func.num_species), + get_indexes(vert_i, jac_func.num_species)]), + (@view u[get_indexes(vert_i, jac_func.num_species)]), + view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types), t) # These inputs are the same as when f_func.ofunc was applied in the previous block. end # Updates for the spatial reactions. From c302fd5dd28bd2e3e4df2f671ce9af993ded3596 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 13:54:39 -0400 Subject: [PATCH 078/121] test 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 adc38cd97b..e011107f6c 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -14,7 +14,7 @@ let (p, 1), 0 <--> X end tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, tr, grid) + lrs = LatticeReactionSystem(rs, [tr], grid) @test ModelingToolkit.getname.(species(lrs)) == [:X] @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X] @@ -60,7 +60,7 @@ let (pY, 1), 0 <--> Y end tr_1 = @transport_reaction dX X - lrs = LatticeReactionSystem(rs, tr_1, grid) + lrs = LatticeReactionSystem(rs, [tr_1], grid) @test ModelingToolkit.getname.(species(lrs)) == [:X, :Y] @test ModelingToolkit.getname.(spatial_species(lrs)) == [:X] @@ -103,7 +103,7 @@ let (p, 1), 0 <--> X end tr = @transport_reaction d X - lrs = LatticeReactionSystem(rs, tr, grid) + lrs = LatticeReactionSystem(rs, [tr], grid) @test nameof(lrs) == :customname end @@ -211,7 +211,7 @@ let (p, d), 0 <--> X end tr = @transport_reaction D Y - @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end # Network where the rate depend on a species @@ -221,7 +221,7 @@ let (p, d), 0 <--> X end tr = @transport_reaction D*Y X - @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end # Network with edge parameter in non-spatial reaction rate. @@ -231,7 +231,7 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end # Network where metadata has been added in rs (which is not seen in transport reaction). @@ -241,13 +241,13 @@ let (p, d), 0 <--> X end tr = @transport_reaction D X - @test_throws ErrorException LatticeReactionSystem(rs, tr, grid) + @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) + @test_throws ErrorException LatticeReactionSystem(rs, [tr], grid) end From 1dd9658f3cb032b5fb02ec0098c5a58da69406cc Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 14:35:35 -0400 Subject: [PATCH 079/121] small fix --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0d1c38098b..81d6ac83c3 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -90,8 +90,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{Fl ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. - transport_rates = [findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] - for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. + transport_rates = Pair{Int64, Vector{Float64}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] + for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{Float64}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). f = LatticeDiffusionODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). jac_prototype = (use_jac || sparse) ? From 828a920e2e9c968d0dee3900a05fbdbcd7b63488 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 15:03:09 -0400 Subject: [PATCH 080/121] test changes --- .../lattice_reaction_systems.jl | 45 ++++++++++++++++--- .../lattice_reaction_systems_ODEs.jl | 18 ++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index e011107f6c..d155593dae 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -7,7 +7,6 @@ using Catalyst, Graphs, Test grid = Graphs.grid([2, 2]) ### Tests LatticeReactionSystem Getters Correctness ### - # Test case 1. let rs = @reaction_network begin @@ -21,6 +20,14 @@ let @test ModelingToolkit.getname.(parameters(lrs)) == [:p, :d] @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:p] @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:d] + + @unpack X, p = rs + d = edge_parameters(lrs)[1] + @test issetequal(species(lrs), [X]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [p, d]) + @test issetequal(vertex_parameters(lrs), [p]) + @test issetequal(edge_parameters(lrs), [d]) end # Test case 2. @@ -50,6 +57,13 @@ let @test ModelingToolkit.getname.(parameters(lrs)) == [:pX, :pY, :dX, :dY] @test ModelingToolkit.getname.(vertex_parameters(lrs)) == [:pX, :pY, :dY] @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX] + + @unpack X, Y, pX, pY, dX, dY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X, Y]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY]) + @test issetequal(edge_parameters(lrs), [dX]) end # Test case 4. @@ -67,6 +81,13 @@ let @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)) == [] + + @unpack dX, p, X, Y, pX, pY = rs + @test issetequal(species(lrs), [X, Y]) + @test issetequal(spatial_species(lrs), [X]) + @test issetequal(parameters(lrs), [dX, p, pX, pY]) + @test issetequal(vertex_parameters(lrs), [dX, p, pX, pY]) + @test issetequal(edge_parameters(lrs), []) end # Test case 5. @@ -95,6 +116,14 @@ let @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] + + @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs + dZ, dV, dW = edge_parameters(lrs)[2:end] + @test issetequal(species(lrs), [W, X, Y, Z, V]) + @test issetequal(spatial_species(lrs), [X, Y, Z, V, W]) + @test issetequal(parameters(lrs), [pX, pY, dX, dY, pZ, pV, dZ, dV, dW]) + @test issetequal(vertex_parameters(lrs), [pX, pY, dY, pZ, pV]) + @test issetequal(edge_parameters(lrs), [dX, dZ, dV, dW]) end # Test case 6. @@ -113,11 +142,17 @@ end # Test case 1. let tr_1 = @transport_reaction dX X - tr_2 = @transport_reaction dY1*dY2 Y + 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] + + @test issetequal(species(tr_1), [tr_1.species]) + @test issetequal(species(tr_2), [tr_2.species]) + @test issetequal(spatial_species(tr_1), [tr_1.species]) + @test issetequal(spatial_species(tr_2), [tr_2.species]) end # Test case 2. @@ -198,9 +233,9 @@ end # 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) +# @test isequal(tr_1, tr_macro_1) +# @test isequal(tr_2, tr_macro_2) +# @test isequal(tr_3, tr_macro_3) # end ### Tests Error generation ### diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 22ec74cafc..36669ddf9f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -149,7 +149,7 @@ 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. + # Create the same lattice (one as digraph, one not). Algorithm depends on Graphs.jl reordering edges, hence the jumbled order. lattice_1 = SimpleGraph(5) lattice_2 = SimpleDiGraph(5) @@ -290,7 +290,7 @@ end ### Tests Special Cases ### -# Create network with vaious combinations of graph/di-graph and parameters. +# Create network with various 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)) @@ -375,18 +375,6 @@ let end end -# 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) - 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 - # Provides initial conditions and parameters in various different ways. let lrs = LatticeReactionSystem(SIR_system, SIR_srs_2, very_small_2d_grid) @@ -414,7 +402,7 @@ let end end -# Confirms parameters can be inputed in [pV; pE] and (pV, pE) form. +# Confirms parameters can be provided 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 aa337076a25234dcba5bf8ce26d3e5240298ec3b Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 16:06:23 -0400 Subject: [PATCH 081/121] update --- .../lattice_reaction_systems.jl | 12 +++-- .../spatial_reactions.jl | 44 ++++++++++----- .../lattice_reaction_systems.jl | 54 ++++++++++--------- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index deb9d1232e..2a6eb6bc44 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -20,6 +20,10 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t init_digraph::Bool """Species that may move spatially.""" spat_species::Vector{BasicSymbolic{Real}} + """All parameters related to the lattice reaction system (both with spatial and non-spatial effects).""" + parameters::Vector{BasicSymbolic{Real}} + """Parameters which values are tied to vertexes (adjacencies), e.g. (possibly) have a unique value at each vertex of the system.""" + vertex_parameters::Vector{BasicSymbolic{Real}} """Parameters which values are tied to edges (adjacencies), e.g. (possibly) have a unique value at each edge of the system.""" edge_parameters::Vector{BasicSymbolic{Real}} @@ -31,9 +35,11 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t 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]) + vertex_parameters = filter(!isedgeparameter, parameters(rs)) + ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] # Ensures that the order begins similarly to in the non-spatial ReactionSystem. 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) + return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), length(unique([species(rs); spat_species])), init_digraph, spat_species, ps, vertex_parameters, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) @@ -48,9 +54,9 @@ 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([parameters(lrs.rs); lrs.edge_parameters]) +ModelingToolkit.parameters(lrs::LatticeReactionSystem) = lrs.parameters # Get all parameters which values are tied to vertexes (compartments). -vertex_parameters(lrs::LatticeReactionSystem) = setdiff(parameters(lrs), edge_parameters(lrs)) +vertex_parameters(lrs::LatticeReactionSystem) = lrs.vertex_parameters # Get all parameters which values are tied to edges (adjacencies). edge_parameters(lrs::LatticeReactionSystem) = lrs.edge_parameters diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 9eaca64ace..3c15736abb 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -23,36 +23,47 @@ end # 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 + rate::Any """The species that is subject to diffusion.""" species::BasicSymbolic{Real} # Creates a diffusion reaction. - function TransportReaction(rate::Num, species::Num) + function TransportReaction(rate, species) new(rate, species.val) end end -# If the rate is a value, covert that to a numeric expression. -function TransportReaction(rate::Number, args...) - TransportReaction(Num(rate), args...) -end # Creates a vector of TransportReactions. function TransportReactions(transport_reactions) [TransportReaction(tr[1], tr[2]) for tr in transport_reactions] end # Macro for creating a transport reaction. -macro transport_reaction(rateex::ExprValues, species::Symbol) +macro transport_reaction(rateex::ExprValues, species::ExprValues) make_transport_reaction(MacroTools.striplines(rateex), species) end function make_transport_reaction(rateex, species) + # Handle interpolation of variables + rateex = esc_dollars!(rateex) + species = esc_dollars!(species) + + # Parses input expression. parameters = ExprValues[] find_parameters_in_rate!(parameters, rateex) + + # Checks for input errors. + forbidden_symbol_check(union([species], parameters)) + + # Creates expressions corresponding to actual code from the internal DSL representation. + sexprs = get_sexpr([species], Dict{Symbol, Expr}()) + pexprs = get_pexpr(parameters, Dict{Symbol, Expr}()) + iv = :(@variables $(DEFAULT_IV_SYM)) + trxexpr = :(TransportReaction($rateex, $species)) + quote - $(isempty(parameters) ? nothing : :(@parameters $(parameters...))) - @variables t - @species $(species)(t) - TransportReaction($rateex, $species) + $pexprs + $iv + $sexprs + $trxexpr end end @@ -60,7 +71,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] +# species(tr::TransportReaction) = [tr.species] # Currently these two are identical. This can be added back in once we have complicated spatial reactions where the two cases are not identical. spatial_species(tr::TransportReaction) = [tr.species] # Checks that a transport reaction is valid for a given reaction system. @@ -70,13 +81,20 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti # 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(tr.species, s) && !isequivalent(tr.species, s) 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) && !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])) +# Since MTK's "isequal does not worry about metadata, we have to use a special function that accounts for this (important because whether something is an edge parameter is defined here). +function isequivalent(sym1, sym2) + !isequal(sym1, sym2) && (return false) + (sym1.metadata != sym2.metadata) && (return false) + return true +end + ### Utility ### # Loops through a rate and extract all parameters. function find_parameters_in_rate!(parameters, rateex::ExprValues) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index d155593dae..0ede1f384d 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -144,13 +144,15 @@ 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.(species(tr_1)) == ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] # species(::TransportReaction) currently not supported. + # @test ModelingToolkit.getname.(species(tr_2)) == ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] + @test ModelingToolkit.getname.(spatial_species(tr_1)) == [:X] + @test ModelingToolkit.getname.(spatial_species(tr_2)) == [:Y] @test ModelingToolkit.getname.(parameters(tr_1)) == [:dX] @test ModelingToolkit.getname.(parameters(tr_2)) == [:dY1, :dY2] - @test issetequal(species(tr_1), [tr_1.species]) - @test issetequal(species(tr_2), [tr_2.species]) + # @test issetequal(species(tr_1), [tr_1.species]) + # @test issetequal(species(tr_2), [tr_2.species]) @test issetequal(spatial_species(tr_1), [tr_1.species]) @test issetequal(spatial_species(tr_2), [tr_2.species]) end @@ -164,8 +166,8 @@ let @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(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]) @@ -217,26 +219,26 @@ end # 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 -# -# @test isequal(tr_1, tr_macro_1) -# @test isequal(tr_2, tr_macro_2) -# @test isequal(tr_3, tr_macro_3) -# end +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 # Currently does not work, something with meta programming. + + @test isequal(tr_1, tr_macro_1) + @test isequal(tr_2, tr_macro_2) + # @test isequal(tr_3, tr_macro_3) +end ### Tests Error generation ### From 0051a81a8e8f8821c2ffdcebcacef50d237e2f3a Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 16:37:03 -0400 Subject: [PATCH 082/121] updates --- .../lattice_reaction_systems.jl | 5 ++- .../spatial_ODE_systems.jl | 38 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 2a6eb6bc44..867bd7ab07 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -31,9 +31,10 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t spatial_reactions::Vector{T}, lattice::DiGraph; init_digraph = true) where {S, T} (T <: AbstractSpatialReaction) || error("The second argument must be a vector of AbstractSpatialReaction subtypes.") # There probably some better way to ascertain that T has that type. Not sure how. - spat_species = unique(vcat(spatial_species.(spatial_reactions)...)) + + spat_species = (isempty(spatial_reactions) ? Vector{BasicSymbolic{Real}}[] : unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions]))) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) - srs_edge_parameters = setdiff(vcat(parameters.(spatial_reactions)...), parameters(rs)) + srs_edge_parameters = (isempty(spatial_reactions) ? Vector{BasicSymbolic{Real}}[] : setdiff(reduce(vcat, [parameters(sr) for sr in spatial_reactions]), parameters(rs))) edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] # Ensures that the order begins similarly to in the non-spatial ReactionSystem. diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 81d6ac83c3..0aacc38851 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,27 +1,27 @@ ### Spatial ODE Functor Structures ### # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeDiffusionODEf{S,T} +struct LatticeDiffusionODEf{R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::S + ofunc::R """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{Float64}} + vert_ps::Vector{Vector{S}} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector.""" - work_vert_ps::Vector{Float64} + work_vert_ps::Vector{S} """For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" - transport_rates::Vector{Pair{Int64, Vector{Float64}}} + transport_rates::Vector{Pair{Int64, Vector{S}}} """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" - leaving_rates::Matrix{Float64} + leaving_rates::Matrix{S} """An (enumerate'ed) iterator over all the edges of the lattice.""" enum_edges::T - function LatticeDiffusionODEf(ofunc::S, vert_ps, transport_rates::Vector{Pair{Int64, Vector{Float64}}}, lrs::LatticeReactionSystem) where {S} + function LatticeDiffusionODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, lrs::LatticeReactionSystem) where {R,S} leaving_rates = zeros(length(transport_rates), lrs.num_verts) # Initialises the leaving rates matrix with zeros. for (s_idx, rates) in enumerate(last.(transport_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all transport rates (map from each diffusing species to its rates across edges). @@ -31,22 +31,22 @@ struct LatticeDiffusionODEf{S,T} work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (and enumerates it, since it always appear in this form). enum_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. - new{S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) + new{R,S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) end end # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeDiffusionODEjac{S,T} +struct LatticeDiffusionODEjac{R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::S + ofunc::R """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{Float64}} + vert_ps::Vector{Vector{S}} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector.""" - work_vert_ps::Vector{Float64} + work_vert_ps::Vector{S} """For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} """Whether the Jacobian is sparse or not.""" @@ -54,11 +54,11 @@ struct LatticeDiffusionODEjac{S,T} """The values of the Jacobian. All the diffusion rates. Eitehr in matrix form (for non-sparse, in this case with potential zeros) or as the "nzval" field of the sparse jacobian matrix.""" jac_values::T - function LatticeDiffusionODEjac(ofunc::S, vert_ps, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {S} + function LatticeDiffusionODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). - new{S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) + new{R,S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) end end @@ -84,14 +84,14 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Builds an ODEFunction for a spatial ODEProblem. -function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{Float64}}, - edge_ps::Vector{Vector{Float64}}, use_jac::Bool, sparse::Bool) +function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, + edge_ps::Vector{Vector{T}}, use_jac::Bool, sparse::Bool) where {T} # Prepares (non-spatial) ODE functions and list of spatially moving species and their rates. ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. - transport_rates = Pair{Int64, Vector{Float64}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] - for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{Float64}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). + transport_rates = Pair{Int64, Vector{T}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] + for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). f = LatticeDiffusionODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). jac_prototype = (use_jac || sparse) ? @@ -161,7 +161,7 @@ end # Defines the jacobian functor's 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) # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). + J .= 0.0 # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). # Updates for non-spatial reactions. for vert_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. From 2bf70c64f5d0636275ce1685237cb58b77cf9246 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 17:23:48 -0400 Subject: [PATCH 083/121] Test with manually computed Jacobian. --- .../lattice_reaction_systems_ODEs.jl | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 36669ddf9f..814d381f26 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -108,6 +108,89 @@ let end end +# Compares Jacobian and forcing functions of spatial system to analytically computed on. +let + # Creates LatticeReactionNetwork ODEProblem. + rs = @reaction_network begin + pX, 0 --> X + d, X --> 0 + pY*X, 0 --> Y + d, Y --> 0 + end + tr = @transport_reaction D X + lattice = path_graph(3) + lrs = LatticeReactionSystem(rs, [tr], lattice); + + D_vals = [0.2, 0.2, 0.3, 0.3] + u0 = [:X => [1.0, 2.0, 3.0], :Y => 1.0] + ps = [:pX => [2.0, 2.5, 3.0], :pY => 0.5, :d => 0.1, :D => D_vals] + oprob = ODEProblem(lrs, u0, (0.0, 0.0), ps) + + # Creates manual f and jac functions. + function f_manual!(du, u, p, t) + X1, Y1, X2, Y2, X3, Y3 = u + pX, d, pY = p + pX1, pX2, pX3 = pX + pY, = pY + d, = d + D1, D2, D3, D4 = D_vals + du[1] = pX1 - d*X1 - D1*X1 + D2*X2 + du[2] = pY*X1 - d*Y1 + du[3] = pX2 - d*X2 + D1*X1 - (D2+D3)*X2 + D4*X3 + du[4] = pY*X2 - d*Y2 + du[5] = pX3 - d*X3 + D3*X2 - D4*X3 + du[6] = pY*X3 - d*Y3 + end + function jac_manual!(J, u, p, t) + X1, Y1, X2, Y2, X3, Y3 = u + pX, d, pY = p + pX1, pX2, pX3 = pX + pY, = pY + d, = d + D1, D2, D3, D4 = D_vals + + J .= 0.0 + + J[1,1] = - d - D1 + J[1,2] = 0 + J[2,1] = pY + J[2,2] = - d + + J[3,3] = - d - D2 - D3 + J[3,4] = 0 + J[4,3] = pY + J[4,4] = - d + + J[5,5] = - d - D4 + J[5,6] = 0 + J[6,5] = pY + J[6,6] = - d + + J[1,3] = D1 + J[3,1] = D2 + J[3,5] = D3 + J[5,3] = D4 + end + + # Sets test input values. + u = rand(rng, 6) + p = [rand(rng, 3), rand(rng, 1), rand(rng, 1)] + + # Tests forcing function. + du1 = fill(0.0, 6) + du2 = fill(0.0, 6) + oprob.f(du1, u, p, 0.0) + f_manual!(du2, u, p, 0.0) + @test du1 == du2 + + # Tests Jacobian. + J1 = deepcopy(oprob.f.jac_prototype) + J2 = deepcopy(oprob.f.jac_prototype) + oprob.f.jac(J1, u, p, 0.0) + jac_manual!(J2, u, p, 0.0) + @test J1 == J2 +end + # Checks that result becomes homogeneous on a connected lattice. let lrs = LatticeReactionSystem(binding_system, binding_srs, undirected_cycle) From 17ba55380f43d289e09eb3de0fb3c235adc530a4 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 17:28:29 -0400 Subject: [PATCH 084/121] update --- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- test/spatial_reaction_systems/lattice_reaction_systems.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 3c15736abb..d599383e39 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -88,7 +88,7 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti end equivalent_metadata(p1, p2) = isempty(setdiff(p1.metadata, p2.metadata, [Catalyst.EdgeParameter => true])) -# Since MTK's "isequal does not worry about metadata, we have to use a special function that accounts for this (important because whether something is an edge parameter is defined here). +# Since MTK's "isequal" does not worry about metadata, we have to use a special function that accounts for this (important because whether something is an edge parameter is defined here). function isequivalent(sym1, sym2) !isequal(sym1, sym2) && (return false) (sym1.metadata != sym2.metadata) && (return false) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 0ede1f384d..dc2e53224c 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -230,13 +230,13 @@ let species3 = Z tr_1 = TransportReaction(dX, X) tr_2 = TransportReaction(dY1*dY2, Y) - tr_2 = TransportReaction(dZ, Z) + tr_3 = TransportReaction(dZ, Z) tr_macro_1 = @transport_reaction $dX X tr_macro_2 = @transport_reaction $(rate2) Y # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. - @test isequal(tr_1, tr_macro_1) - @test isequal(tr_2, tr_macro_2) + @test_broken isequal(tr_1, tr_macro_1) + @test_broken isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. # @test isequal(tr_3, tr_macro_3) end From ea8a0f6cd4825cc653100b420c3479eb363dd9d7 Mon Sep 17 00:00:00 2001 From: Torkel Date: Sat, 4 Nov 2023 17:55:59 -0400 Subject: [PATCH 085/121] =?UTF-8?q?=E2=89=88=20instead=20if=20=3D=3D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spatial_reaction_systems/lattice_reaction_systems_ODEs.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 814d381f26..404d71c387 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -181,14 +181,14 @@ let du2 = fill(0.0, 6) oprob.f(du1, u, p, 0.0) f_manual!(du2, u, p, 0.0) - @test du1 == du2 + @test du1 ≈ du2 # Tests Jacobian. J1 = deepcopy(oprob.f.jac_prototype) J2 = deepcopy(oprob.f.jac_prototype) oprob.f.jac(J1, u, p, 0.0) jac_manual!(J2, u, p, 0.0) - @test J1 == J2 + @test J1 ≈ J2 end # Checks that result becomes homogeneous on a connected lattice. From 2258e30884984dbb71fb1f128f3bde2888b9e51c Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 13 Nov 2023 17:38:36 -0500 Subject: [PATCH 086/121] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 5b2c03e319..59d0df77df 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,7 +1,7 @@ # 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. +- Simulation of spatial ODEs now supported. For full details, please see https://github.com/SciML/Catalyst.jl/pull/644 and upcoming documentation. Note that these methods are currently considered alpha, with the interface and approach changing even in non-breaking Catalyst releases. - LatticeReactionSystem structure represents a spatial reaction network: ```julia rn = @reaction_network begin From 49f05609f8fbb166c13f68a91be9d2c422c4982a Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 13 Nov 2023 17:40:19 -0500 Subject: [PATCH 087/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0aacc38851..0eebc0085b 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -51,7 +51,7 @@ struct LatticeDiffusionODEjac{R,S,T} enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} """Whether the Jacobian is sparse or not.""" sparse::Bool - """The values of the Jacobian. All the diffusion rates. Eitehr in matrix form (for non-sparse, in this case with potential zeros) or as the "nzval" field of the sparse jacobian matrix.""" + """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" jac_values::T function LatticeDiffusionODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} From f08777569564da1f16706dc367f955b9f0f2e336 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 13 Nov 2023 17:40:46 -0500 Subject: [PATCH 088/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0eebc0085b..51f793e033 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -160,8 +160,7 @@ end # Defines the jacobian functor's 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. - J .= 0.0 # Sets all Jacobian values to 0 (because they are not by default, this is weird but and I could not get it to work otherwise, tried to get Chris to explain but he wouldn't. Hopefully this can be improved once I get him to explain). + J .= 0.0 # Updates for non-spatial reactions. for vert_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. From fb15625b644c4142a587b706d8d472cb320196da Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:12:10 -0500 Subject: [PATCH 089/121] history file update --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index 59d0df77df..f2187b0ab4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -10,6 +10,7 @@ tr = @transport_reaction D X lattice = Graphs.grid([5, 5]) lrs = LatticeReactionSystem(rn, [tr], lattice) +``` - Here, if a `u0` or `p` vector is given with scalar values: ```julia u0 = [:X => 1.0] From f41ddef83912074947ba1aaa904b1cc7d6c84cb8 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:39:30 -0500 Subject: [PATCH 090/121] ups --- .../spatial_ODE_systems.jl | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 51f793e033..92bd4a7c38 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,7 +1,7 @@ ### Spatial ODE Functor Structures ### # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeDiffusionODEf{R,S,T} +struct LatticeTransportODEf{R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::R """The number of vertices.""" @@ -21,14 +21,14 @@ struct LatticeDiffusionODEf{R,S,T} """An (enumerate'ed) iterator over all the edges of the lattice.""" enum_edges::T - function LatticeDiffusionODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, lrs::LatticeReactionSystem) where {R,S} - leaving_rates = zeros(length(transport_rates), lrs.num_verts) # Initialises the leaving rates matrix with zeros. + function LatticeTransportODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, lrs::LatticeReactionSystem) where {R,S} + leaving_rates = zeros(length(transport_rates), lrs.num_verts) for (s_idx, rates) in enumerate(last.(transport_rates)), (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all transport rates (map from each diffusing species to its rates across edges). leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) # Updates the leaving rate for that combination of vertex and species. RHS finds the value of edge "e_idx" in the vector of diffusion rates ("rates"). end - work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. + work_vert_ps = zeros(lrs.num_verts) enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (and enumerates it, since it always appear in this form). enum_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. new{R,S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) @@ -36,7 +36,7 @@ struct LatticeDiffusionODEf{R,S,T} end # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeDiffusionODEjac{R,S,T} +struct LatticeTransportODEjac{R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::R """The number of vertices.""" @@ -54,8 +54,8 @@ struct LatticeDiffusionODEjac{R,S,T} """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" jac_values::T - function LatticeDiffusionODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} - work_vert_ps = zeros(lrs.num_verts) # Initialises the work vert_ps vector to be empty. + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} + work_vert_ps = zeros(lrs.num_verts) enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). new{R,S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) @@ -93,11 +93,11 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} transport_rates = Pair{Int64, Vector{T}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). - f = LatticeDiffusionODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). + f = LatticeTransportODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). - jac = use_jac ? LatticeDiffusionODEjac(ofunc, vert_ps, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). + jac = use_jac ? LatticeTransportODEjac(ofunc, vert_ps, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) # Creates the ODEFunction used in the ODEProblem. end @@ -134,7 +134,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, end # Defines the forcing functor's effect on the (spatial) ODE system. -function (f_func::LatticeDiffusionODEf)(du, u, p, t) +function (f_func::LatticeTransportODEf)(du, u, p, t) # Updates for non-spatial reactions. for vert_i in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. f_func.ofunc((@view du[get_indexes(vert_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. @@ -159,7 +159,7 @@ function (f_func::LatticeDiffusionODEf)(du, u, p, t) end # Defines the jacobian functor's effect on the (spatial) ODE system. -function (jac_func::LatticeDiffusionODEjac)(J, u, p, t) +function (jac_func::LatticeTransportODEjac)(J, u, p, t) J .= 0.0 # Updates for non-spatial reactions. @@ -177,5 +177,5 @@ end reset_J_vals!(J::Matrix) = (J .= 0.0) reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) # Updates the jacobian matrix with the difusion values. Separate for spatial and non-spatial cases. -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 +add_spat_J_vals!(J::SparseMatrixCSC, jac_func::LatticeTransportODEjac) = (J.nzval .+= jac_func.jac_values) +add_spat_J_vals!(J::Matrix, jac_func::LatticeTransportODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file From 7e7c3dd061cff45abb253432114216aae15bdb1d Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:41:30 -0500 Subject: [PATCH 091/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 92bd4a7c38..ac3faa0802 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -68,7 +68,7 @@ end 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.") + is_transport_system(lrs) || error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") # Converts potential symmaps to varmaps. u0_in = symmap_to_varmap(lrs, u0_in) From 31ea398a01b98d27a29596070e61cb7d031eb92c Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:42:18 -0500 Subject: [PATCH 092/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index ac3faa0802..19c615037f 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -75,7 +75,8 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, 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) # Parameters can be given in Tuple form (where the first element is the vertex parameters and the second the edge parameters). In this case, we have to covert each element separately. # Converts u0 and p to their internal forms. - u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) # u0 becomes a vector ([species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]). + # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. These elements are length 1 vectors (if the parameter is uniform), or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). # Creates ODEProblem. From f712fc8d1cb949b85c165b0bb039c7c82f2081e1 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:52:31 -0500 Subject: [PATCH 093/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 19c615037f..0815f018e5 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -23,10 +23,12 @@ struct LatticeTransportODEf{R,S,T} function LatticeTransportODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, lrs::LatticeReactionSystem) where {R,S} leaving_rates = zeros(length(transport_rates), lrs.num_verts) - for (s_idx, rates) in enumerate(last.(transport_rates)), - (e_idx, e) in enumerate(edges(lrs.lattice)) # Iterates through all edges, and all transport rates (map from each diffusing species to its rates across edges). - - leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) # Updates the leaving rate for that combination of vertex and species. RHS finds the value of edge "e_idx" in the vector of diffusion rates ("rates"). + for (s_idx, trpair) in enumerate(transport_rates) + rates = last(trpair) + for (e_idx, e) in enumerate(edges(lrs.lattice)) + # Updates the exit rate for species s_idx from vertex e.src + leaving_rates[s_idx, e.src] += get_component_value(rates, e_idx) + end end work_vert_ps = zeros(lrs.num_verts) enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (and enumerates it, since it always appear in this form). From 039213923b4a8c5371832d6d37d9d38f86905b53 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:52:46 -0500 Subject: [PATCH 094/121] ups --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0815f018e5..95ab370012 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -96,7 +96,7 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} transport_rates = Pair{Int64, Vector{T}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). - f = LatticeTransportODEf(ofunc, vert_ps, transport_rates, lrs) # Creates a functor for the ODE f function (incorporating spatial and non-spatial reactions). + f = LatticeTransportODEf(ofunc, vert_ps, transport_rates, lrs) jac_prototype = (use_jac || sparse) ? build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). @@ -173,12 +173,10 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types), t) # These inputs are the same as when f_func.ofunc was applied in the previous block. end - # Updates for the spatial reactions. - add_spat_J_vals!(J, jac_func) # Adds the Jacobian values from the diffusion reactions. + # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). + add_spat_J_vals!(J, jac_func) end -# Resets the jacobian matrix within a jac call. Separate for spatial and non-spatial cases. -reset_J_vals!(J::Matrix) = (J .= 0.0) -reset_J_vals!(J::SparseMatrixCSC) = (J.nzval .= 0.0) -# Updates the jacobian matrix with the difusion values. Separate for spatial and non-spatial cases. + +# Updates the jacobian matrix with the diffusion values. Separate for spatial and non-spatial cases. add_spat_J_vals!(J::SparseMatrixCSC, jac_func::LatticeTransportODEjac) = (J.nzval .+= jac_func.jac_values) add_spat_J_vals!(J::Matrix, jac_func::LatticeTransportODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file From b93bcb16db438ce853245d03be65e1580b6b31ea Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:53:46 -0500 Subject: [PATCH 095/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 95ab370012..0d4f16eacf 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -31,7 +31,8 @@ struct LatticeTransportODEf{R,S,T} end end work_vert_ps = zeros(lrs.num_verts) - enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (and enumerates it, since it always appear in this form). + # 1 if ps are constant across the graph, 0 else + enum_v_ps_idx_types = enumerate(map(vp -> length(vp) == 1, vert_ps)) enum_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. new{R,S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) end From 5fe0ab227aaf298a6d21e095e86bbbffbc564035 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:56:09 -0500 Subject: [PATCH 096/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- .../spatial_ODE_systems.jl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0d4f16eacf..11d94ef7cf 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -140,11 +140,15 @@ end # Defines the forcing functor's effect on the (spatial) ODE system. function (f_func::LatticeTransportODEf)(du, u, p, t) # Updates for non-spatial reactions. - for vert_i in 1:(f_func.num_verts) # Loops through each vertex of the lattice. Applies the (non-spatial) ODEFunction to the species in that vertex. - f_func.ofunc((@view du[get_indexes(vert_i, f_func.num_species)]), # Get the indexes of the i'th vertex (current one in the loop) in the u vector. Uses this to create a view of the vector to which the new species concentrations are written. - (@view u[get_indexes(vert_i, f_func.num_species)]), # Same as above, but reads the current species concentrations. - view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, f_func.enum_v_ps_idx_types), # Gets a vector with the values of the (vertex) parameters in the current vertex. - t) # Time. + for vert_i in 1:(f_func.num_verts) + # gets the indices of species at vertex i + idxs = get_indexes(vert_i, f_func.num_species) + + # vector of vertex ps at vert_i + vert_i_ps = view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, f_func.enum_v_ps_idx_types) + + # evaluate reaction contributions to du at vert_i + f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) end # Updates for spatial reactions. From 2b5b6bfc85eb941b57743e4f2207158b8e424919 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:58:12 -0500 Subject: [PATCH 097/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- .../spatial_ODE_systems.jl | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 11d94ef7cf..fd4b1d2bc2 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -151,17 +151,18 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) end - # Updates for spatial reactions. - for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) # Loops through all species with transportation. Here: s_idx is its index among the species with transportations. s is its index among all species (in the species(::ReactionSystem) vector). rates is its rates values (vector length 1 if uniform, else same length as the number of edges). - for vert_i in 1:(f_func.num_verts) # Loops through all vertexes. - du[get_index(vert_i, s, f_func.num_species)] -= f_func.leaving_rates[s_idx, vert_i] * - u[get_index(vert_i, s, - f_func.num_species)] # Finds the leaving rate of this species in this vertex. Updates the du vector at that vertex/species combination with the corresponding rate (leaving rate times concentration). + # s_idx is species index among transport species, s is index among all species + # rates are the species' transport rates + for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) + # rate for leaving vert_i + for vert_i in 1:(f_func.num_verts) + idx = get_index(vert_i, s, f_func.num_species) + du[idx] -= f_func.leaving_rates[s_idx, vert_i] * u[idx] end - for (e_idx, e) in f_func.enum_edges # Loops through all edges. - du[get_index(e.dst, s, f_func.num_species)] += get_component_value(rates, e_idx) * - u[get_index(e.src, s, - f_func.num_species)] # For the destination of this edge, we want to add the influx term to du. This is ["rates" value for this edge]*[the species concentration in the source vertex]. + # add rates for entering a given vertex via an incoming edge + for (e_idx, e) in f_func.enum_edges + idx = get_index(e.dst, s, f_func.num_species) + du[idx] += get_component_value(rates, e_idx) * u[idx] end end end From d5d8873b3d6f3ef26f7f120d48c1b4fbd25820f6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 16:59:00 -0500 Subject: [PATCH 098/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index fd4b1d2bc2..417ee8aa04 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -171,12 +171,11 @@ end function (jac_func::LatticeTransportODEjac)(J, u, p, t) J .= 0.0 - # Updates for non-spatial reactions. - for vert_i in 1:(jac_func.num_verts) # Loops through all vertexes and applies the (non-spatial) Jacobian to the species in that vertex. - jac_func.ofunc.jac((@view J[get_indexes(vert_i, jac_func.num_species), - get_indexes(vert_i, jac_func.num_species)]), - (@view u[get_indexes(vert_i, jac_func.num_species)]), - view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types), t) # These inputs are the same as when f_func.ofunc was applied in the previous block. + # Update the Jacobian from reaction terms + for vert_i in 1:(jac_func.num_verts) + idxs = get_indexes(vert_i, jac_func.num_species) + vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types) + jac_func.ofunc.jac((@view J[idxs, idxs], (@view u[idxs]), vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). From 079591738f887cd99e9c2a792056fdfd84035760 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 17:07:07 -0500 Subject: [PATCH 099/121] up --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 417ee8aa04..4276f5385a 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -73,14 +73,16 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, jac = true, sparse = jac, kwargs...) is_transport_system(lrs) || error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") - # Converts potential symmaps to varmaps. + # Converts potential symmaps to varmaps (parameter conversion is more involved since the vertex and edge parameters may be given in a tuple, or in a common vector). 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) # Parameters can be given in Tuple form (where the first element is the vertex parameters and the second the edge parameters). In this case, we have to covert each element separately. + p_in = (p_in isa Tuple{<:Any,<:Any}) ? (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) - vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. These elements are length 1 vectors (if the parameter is uniform), or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). + # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. + # These elements are length 1 vectors (if the parameter is uniform), or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse) # Builds the ODEFunction. From 520bf8988b5d92f67fe14ed0b322735bef4b4ac3 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 17:08:16 -0500 Subject: [PATCH 100/121] fix --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 4276f5385a..43fafe5b40 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -177,7 +177,7 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types) - jac_func.ofunc.jac((@view J[idxs, idxs], (@view u[idxs]), vert_ps, t) + jac_func.ofunc.jac((@view J[idxs, idxs], (@view u[idxs]), vert_ps, t)) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). From 9ab64379f239bf35b8d239438c26501e7b63a8ff Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 17:31:44 -0500 Subject: [PATCH 101/121] fix --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 43fafe5b40..df01eb494b 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -13,7 +13,7 @@ struct LatticeTransportODEf{R,S,T} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector.""" work_vert_ps::Vector{S} """For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} + enum_v_ps_idx_types::Base.Iterators.Enumerate{Vector{Bool}} """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" transport_rates::Vector{Pair{Int64, Vector{S}}} """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" @@ -51,7 +51,7 @@ struct LatticeTransportODEjac{R,S,T} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector.""" work_vert_ps::Vector{S} """For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enum_v_ps_idx_types::Base.Iterators.Enumerate{BitVector} + enum_v_ps_idx_types::Base.Iterators.Enumerate{Vector{Bool}} """Whether the Jacobian is sparse or not.""" sparse::Bool """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" @@ -59,7 +59,7 @@ struct LatticeTransportODEjac{R,S,T} function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} work_vert_ps = zeros(lrs.num_verts) - enum_v_ps_idx_types = enumerate(length.(vert_ps) .== 1) # Creates a Boolean vector whether each vertex parameter need expanding or (an enumerates it, since it always appear in this form). + enum_v_ps_idx_types = enumerate(map(vp -> length(vp) == 1, vert_ps)) jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). new{R,S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) end @@ -177,7 +177,7 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types) - jac_func.ofunc.jac((@view J[idxs, idxs], (@view u[idxs]), vert_ps, t)) + jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). From 2e3e2d4f482920a087af643698579af05cc44f6f Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 20:38:52 -0500 Subject: [PATCH 102/121] ifx --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index df01eb494b..aacd824553 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -163,8 +163,9 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) end # add rates for entering a given vertex via an incoming edge for (e_idx, e) in f_func.enum_edges - idx = get_index(e.dst, s, f_func.num_species) - du[idx] += get_component_value(rates, e_idx) * u[idx] + idx_dst = get_index(e.dst, s, f_func.num_species) + idx_src = get_index(e.src, s, f_func.num_species) + du[idx_dst] += get_component_value(rates, e_idx) * u[idx_src] end end end From fc47179cebec077dfb681206f288bac54dea2c1e Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 15 Nov 2023 22:30:35 -0500 Subject: [PATCH 103/121] update --- .../spatial_ODE_systems.jl | 119 +++++++++++------- .../spatial_reactions.jl | 2 +- src/spatial_reaction_systems/utility.jl | 9 ++ 3 files changed, 82 insertions(+), 48 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index aacd824553..47e9156b55 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -13,15 +13,17 @@ struct LatticeTransportODEf{R,S,T} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector.""" work_vert_ps::Vector{S} """For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enum_v_ps_idx_types::Base.Iterators.Enumerate{Vector{Bool}} + v_ps_idx_types::Vector{Bool} """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" transport_rates::Vector{Pair{Int64, Vector{S}}} """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" leaving_rates::Matrix{S} """An (enumerate'ed) iterator over all the edges of the lattice.""" - enum_edges::T + edges::Graphs.SimpleGraphs.SimpleEdgeIter{SimpleDiGraph{Int64}} + """The edge parameters used to create the spatial ODEProblem. Currently unused, but will be needed to support changing these (e.g. due to events). Contain one vector for each edge parameter (length one if uniform, else one value for each edge).""" + edge_ps::Vector{Vector{T}} - function LatticeTransportODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, lrs::LatticeReactionSystem) where {R,S} + function LatticeTransportODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {R,S,T} leaving_rates = zeros(length(transport_rates), lrs.num_verts) for (s_idx, trpair) in enumerate(transport_rates) rates = last(trpair) @@ -31,37 +33,38 @@ struct LatticeTransportODEf{R,S,T} end end work_vert_ps = zeros(lrs.num_verts) - # 1 if ps are constant across the graph, 0 else - enum_v_ps_idx_types = enumerate(map(vp -> length(vp) == 1, vert_ps)) - enum_edges = deepcopy(enumerate(edges(lrs.lattice))) # Creates an iterator over all the edges. Again, this is always used in the enumerated form. - new{R,S,typeof(enum_edges)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, transport_rates, leaving_rates, enum_edges) + # 1 if ps are constant across the graph, 0 else. + v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) + eds = edges(lrs.lattice) + new{R,S,T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end end # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEjac{R,S,T} +struct LatticeTransportODEjac{Q,R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::R + ofunc::Q """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{S}} + vert_ps::Vector{Vector{R}} """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector.""" - work_vert_ps::Vector{S} + work_vert_ps::Vector{R} """For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" - enum_v_ps_idx_types::Base.Iterators.Enumerate{Vector{Bool}} + v_ps_idx_types::Vector{Bool} """Whether the Jacobian is sparse or not.""" sparse::Bool """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" - jac_values::T + jac_transport::S + """The edge parameters used to create the spatial ODEProblem. Currently unused, but will be needed to support changing these (e.g. due to events). Contain one vector for each edge parameter (length one if uniform, else one value for each edge).""" + edge_ps::Vector{Vector{T}} - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_prototype::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, sparse::Bool) where {R,S} + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, edge_ps::Vector{Vector{T}}, sparse::Bool) where {R,S,T} work_vert_ps = zeros(lrs.num_verts) - enum_v_ps_idx_types = enumerate(map(vp -> length(vp) == 1, vert_ps)) - jac_values = sparse ? jac_prototype.nzval : Matrix(jac_prototype) # Retrieves the diffusion values (form depending on Jacobian sparsity). - new{R,S,typeof(jac_values)}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, enum_v_ps_idx_types, sparse, jac_values) + v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) + new{R,S,typeof(jac_transport),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) end end @@ -70,7 +73,10 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, kwargs...) + jac = true, sparse = jac, + name = nameof(lrs), include_zero_odes = true, + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), + remove_conserved = false, checks = false, kwargs...) is_transport_system(lrs) || error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") # Converts potential symmaps to varmaps (parameter conversion is more involved since the vertex and edge parameters may be given in a tuple, or in a common vector). @@ -85,26 +91,49 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. - ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse) # Builds the ODEFunction. - return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) # Creates a normal ODEProblem. + ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) + return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) end # Builds an ODEFunction for a spatial ODEProblem. function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, - edge_ps::Vector{Vector{T}}, use_jac::Bool, sparse::Bool) where {T} - # Prepares (non-spatial) ODE functions and list of spatially moving species and their rates. - ofunc = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = false) # Creates the (non-spatial) ODEFunction corresponding to the (non-spatial) reaction network. - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs); jac = use_jac, sparse = true) # Creates the same function, but sparse. Could insert so this is only computed for sparse cases. - transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) # Creates a map (Vector{Pair}), mapping each species that is transported to a vector with its transportation rate. If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. - transport_rates = Pair{Int64, Vector{T}}[findfirst(isequal(spat_rates[1]), species(lrs)) => spat_rates[2] - for spat_rates in transport_rates_speciesmap] # Remakes "transport_rates_speciesmap". Rates are identical, but the species are represented as their index (in the species(::ReactionSystem) vector). In "transport_rates_speciesmap" they instead were Symbolics. Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). - - f = LatticeTransportODEf(ofunc, vert_ps, transport_rates, lrs) - jac_prototype = (use_jac || sparse) ? - build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, - lrs; set_nonzero = use_jac) : nothing # Computes the Jacobian prototype (nothing if `jac=false`). - jac = use_jac ? LatticeTransportODEjac(ofunc, vert_ps, lrs, jac_prototype, sparse) : nothing # (Potentially) Creates a functor for the ODE Jacobian function (incorporating spatial and non-spatial reactions). - return ODEFunction(f; jac = jac, jac_prototype = (sparse ? jac_prototype : nothing)) # Creates the ODEFunction used in the ODEProblem. + edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, + name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} + println() + remove_conserved && error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") + + # Creates a map, taking (the index in species(lrs) each species (with transportation) to its transportation rate (uniform or one value for each edge). + transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) + + # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). + if jac + ofunc_dense = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = true, sparse = false) # Always used for build_jac_prototype. + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = true, sparse = true) # Always used for LatticeTransportODEjac. + jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + if sparse + f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) + jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, true) + jac_prototype = jac_vals + else + f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) + J = LatticeTransportODEjac(ofunc_dense, vert_ps, lrs, jac_vals, edge_ps, false) + jac_prototype = nothing + end + else + if sparse + ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = false, sparse = true) + f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) + jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) + else + ofunc_dense = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = false, sparse = false) + f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) + jac_prototype = nothing + end + J = nothing + end + + return ODEFunction(f; jac = J, jac_prototype = jac_prototype) end # Builds a jacobian prototype. If requested, populate it with the Jacobian's (constant) values as well. @@ -120,7 +149,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, ns_j_idxs = ns_jac_prototype_idxs[2] # List the indexes of all non-zero Jacobian terms. - non_spat_terms = [[get_index(vert, s_i, lrs.num_species), get_index(vert, s_j, lrs.num_species)] for vert in 1:(lrs.num_verts) for (s_i, s_j) in zip(ns_i_idxs,ns_j_idxs)] # Indexes of elements due to non-spatial dynamics. + non_spat_terms = [[get_index(vert, s_i, lrs.num_species), get_index(vert, s_j, lrs.num_species)] for vert in 1:(lrs.num_verts) for (s_i, s_j) in zip(ns_i_idxs,ns_j_idxs)] # Indexes of elements due to non-spatial dynamics. trans_only_leaving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.src, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_only_species] # Indexes due to terms for a species leaves its current vertex (but does not have non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already be accounted for. trans_arriving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.dst, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_species] # Indexes due to terms for species arriving into a new vertex. all_terms = [non_spat_terms; trans_only_leaving_terms; trans_arriving_terms] @@ -130,7 +159,7 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Set element values. if set_nonzero - for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) # Loops through all species with transportation and all edges along which the can be transported. + for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.src, s, lrs.num_species)] -= get_component_value(rates, e_idx) # Term due to species leaving source vertex. jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.dst, s, lrs.num_species)] += get_component_value(rates, e_idx) # Term due to species arriving to destination vertex. end @@ -147,7 +176,7 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) idxs = get_indexes(vert_i, f_func.num_species) # vector of vertex ps at vert_i - vert_i_ps = view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, f_func.enum_v_ps_idx_types) + vert_i_ps = view_vert_ps_vector!(f_func.work_vert_ps, p, vert_i, enumerate(f_func.v_ps_idx_types)) # evaluate reaction contributions to du at vert_i f_func.ofunc((@view du[idxs]), (@view u[idxs]), vert_i_ps, t) @@ -156,13 +185,13 @@ function (f_func::LatticeTransportODEf)(du, u, p, t) # s_idx is species index among transport species, s is index among all species # rates are the species' transport rates for (s_idx, (s, rates)) in enumerate(f_func.transport_rates) - # rate for leaving vert_i + # Rate for leaving vert_i for vert_i in 1:(f_func.num_verts) idx = get_index(vert_i, s, f_func.num_species) du[idx] -= f_func.leaving_rates[s_idx, vert_i] * u[idx] end - # add rates for entering a given vertex via an incoming edge - for (e_idx, e) in f_func.enum_edges + # Add rates for entering a given vertex via an incoming edge + for (e_idx, e) in enumerate(f_func.edges) idx_dst = get_index(e.dst, s, f_func.num_species) idx_src = get_index(e.src, s, f_func.num_species) du[idx_dst] += get_component_value(rates, e_idx) * u[idx_src] @@ -177,14 +206,10 @@ function (jac_func::LatticeTransportODEjac)(J, u, p, t) # Update the Jacobian from reaction terms for vert_i in 1:(jac_func.num_verts) idxs = get_indexes(vert_i, jac_func.num_species) - vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, jac_func.enum_v_ps_idx_types) + vert_ps = view_vert_ps_vector!(jac_func.work_vert_ps, p, vert_i, enumerate(jac_func.v_ps_idx_types)) jac_func.ofunc.jac((@view J[idxs, idxs]), (@view u[idxs]), vert_ps, t) end # Updates for the spatial reactions (adds the Jacobian values from the diffusion reactions). - add_spat_J_vals!(J, jac_func) -end - -# Updates the jacobian matrix with the diffusion values. Separate for spatial and non-spatial cases. -add_spat_J_vals!(J::SparseMatrixCSC, jac_func::LatticeTransportODEjac) = (J.nzval .+= jac_func.jac_values) -add_spat_J_vals!(J::Matrix, jac_func::LatticeTransportODEjac) = (J .+= jac_func.jac_values) \ No newline at end of file + J .+= jac_func.jac_transport +end \ 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 d599383e39..720d145444 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -105,7 +105,7 @@ function find_parameters_in_rate!(parameters, rateex::ExprValues) push!(parameters, rateex) end elseif rateex isa Expr - # note, this (correctly) skips $(...) expressions + # Note, this (correctly) skips $(...) expressions for i in 2:length(rateex.args) find_parameters_in_rate!(parameters, rateex.args[i]) end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index 50614e4575..b0efc5ed72 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -131,6 +131,15 @@ function compute_transport_rates(rate_law::Num, for p in relevant_parameters)) for idxE in 1:num_edges] end +# Creates a map, taking each species (with transportation) to its transportation rate. +# The species is represented by its index (in species(lrs). +# If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. +# Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). +function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where T + transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) + return Pair{Int64, Vector{T}}[speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap] +end + ### Accessing State & Parameter Array Values ### # Gets the index in the u array of species s in vertex vert (when their are num_species species). From 617d89f104775d68c16f84226c46c9e0eb296a72 Mon Sep 17 00:00:00 2001 From: Torkel Date: Fri, 17 Nov 2023 11:56:06 -0500 Subject: [PATCH 104/121] Error for species in TR rate added and tested --- .../spatial_reactions.jl | 29 +++++++++++++++---- .../lattice_reaction_systems.jl | 10 +++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 720d145444..7abe2eac11 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -29,6 +29,9 @@ struct TransportReaction <: AbstractSpatialReaction # Creates a diffusion reaction. function TransportReaction(rate, species) + if any(!ModelingToolkit.isparameter(var) for var in ModelingToolkit.get_variables(rate)) + error("TransportReaction rate contain variables: $(filter(var -> !ModelingToolkit.isparameter(var), ModelingToolkit.get_variables(rate))). The rate must consist of parameters only.") + end new(rate, species.val) end end @@ -76,15 +79,29 @@ 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; 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 have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") + # 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). + if !any(isequal(tr.species), species(rs)) + error("Currently, species used in TransportReactions must have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") + end + # 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)))).") + if !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)))).") + end + # Checks that the species does not exist in the system with different metadata. - any([isequal(tr.species, s) && !isequivalent(tr.species, s) 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) && !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.") + if any([isequal(tr.species, s) && !isequivalent(tr.species, s) 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.") + end + if 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.") + end + # 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.") + if 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 end equivalent_metadata(p1, p2) = isempty(setdiff(p1.metadata, p2.metadata, [Catalyst.EdgeParameter => true])) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index dc2e53224c..ae2c7d0829 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -242,6 +242,16 @@ end ### Tests Error generation ### +# Test creation of TransportReaction with non-parameters in rate. +# Tests that it works even when rate is highly nested. +let + @variables t + @species X(t) Y(t) + @parameters D1 D2 D3 + @test_throws ErrorException TransportReaction(D1 + D2*(D3 + Y), X) + @test_throws ErrorException TransportReaction(Y, X) +end + # Network where diffusion species is not declared in non-spatial network. let rs = @reaction_network begin From b3b229b398ed80b695be91f3c077dba27eceac9b Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:17:55 -0500 Subject: [PATCH 105/121] Update src/spatial_reaction_systems/spatial_reactions.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_reactions.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 7abe2eac11..23639a6edf 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -30,7 +30,7 @@ struct TransportReaction <: AbstractSpatialReaction # Creates a diffusion reaction. function TransportReaction(rate, species) if any(!ModelingToolkit.isparameter(var) for var in ModelingToolkit.get_variables(rate)) - error("TransportReaction rate contain variables: $(filter(var -> !ModelingToolkit.isparameter(var), ModelingToolkit.get_variables(rate))). The rate must consist of parameters only.") + error("TransportReaction rate contains variables: $(filter(var -> !ModelingToolkit.isparameter(var), ModelingToolkit.get_variables(rate))). The rate must consist of parameters only.") end new(rate, species.val) end From 89f77808fc3d1400a80d817f50ba0cf271c8ccf1 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:18:20 -0500 Subject: [PATCH 106/121] Update test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl Co-authored-by: Sam Isaacson --- test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 404d71c387..45347a0655 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -263,7 +263,7 @@ let 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)) + @test all(isapprox.(ss_1, ss_2)) end ### Test Transport Reaction Types ### From 4903bbeefc58a3b28a581ee133dfffa2390c0a14 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:18:29 -0500 Subject: [PATCH 107/121] Update test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl Co-authored-by: Sam Isaacson --- test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 45347a0655..9b542078dc 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -284,7 +284,7 @@ let 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)) + @test all(isapprox.(ss_1, ss_2)) end # Tries non-trivial diffusion rates. From 53970074a46ae63671d51fe019bd9b4cb2a1ef46 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:19:33 -0500 Subject: [PATCH 108/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 47e9156b55..dc99ce1d06 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -73,7 +73,7 @@ end # Creates an ODEProblem from a LatticeReactionSystem. function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; - jac = true, sparse = jac, + jac = false, sparse = false, name = nameof(lrs), include_zero_odes = true, combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), remove_conserved = false, checks = false, kwargs...) From be39cdd8d215399cbc9beab5112d76c20cc4c9b8 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:19:50 -0500 Subject: [PATCH 109/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index dc99ce1d06..33c5691397 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -99,7 +99,6 @@ end function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} - println() remove_conserved && error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") # Creates a map, taking (the index in species(lrs) each species (with transportation) to its transportation rate (uniform or one value for each edge). From 499a748e0cf7ddc119652fcc649f4589c211df15 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:23:29 -0500 Subject: [PATCH 110/121] Update test/spatial_reaction_systems/lattice_reaction_systems.jl Co-authored-by: Sam Isaacson --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index ae2c7d0829..d22edfee4f 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -15,11 +15,6 @@ let 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] @unpack X, p = rs d = edge_parameters(lrs)[1] From e058f474415abe5db35941548c907d588e293375 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 16:23:40 -0500 Subject: [PATCH 111/121] Update test/spatial_reaction_systems/lattice_reaction_systems.jl Co-authored-by: Sam Isaacson --- test/spatial_reaction_systems/lattice_reaction_systems.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index d22edfee4f..80172f5db6 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -106,11 +106,6 @@ let 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] @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs dZ, dV, dW = edge_parameters(lrs)[2:end] From 121f2c2c58cf2b08fde0f37e05712239b8aad0c6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Mon, 20 Nov 2023 16:41:55 -0500 Subject: [PATCH 112/121] Auto stash before merge of "lattice_reaction_system_may_2023" and "origin/lattice_reaction_system_may_2023" --- .../lattice_reaction_systems.jl | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/test/spatial_reaction_systems/lattice_reaction_systems.jl b/test/spatial_reaction_systems/lattice_reaction_systems.jl index 80172f5db6..e151dbde40 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -15,7 +15,6 @@ let tr = @transport_reaction d X lrs = LatticeReactionSystem(rs, [tr], grid) - @unpack X, p = rs d = edge_parameters(lrs)[1] @test issetequal(species(lrs), [X]) @@ -47,12 +46,6 @@ let 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)) == [:pX, :pY, :dY] - @test ModelingToolkit.getname.(edge_parameters(lrs)) == [:dX] - @unpack X, Y, pX, pY, dX, dY = rs @test issetequal(species(lrs), [X, Y]) @test issetequal(spatial_species(lrs), [X, Y]) @@ -71,12 +64,6 @@ let 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)) == [] - @unpack dX, p, X, Y, pX, pY = rs @test issetequal(species(lrs), [X, Y]) @test issetequal(spatial_species(lrs), [X]) @@ -106,7 +93,6 @@ let tr_5 = TransportReaction(dW, W) lrs = LatticeReactionSystem(rs, [tr_1, tr_2, tr_3, tr_4, tr_5], grid) - @unpack pX, pY, pZ, pV, dX, dY, X, Y, Z, V = rs dZ, dV, dW = edge_parameters(lrs)[2:end] @test issetequal(species(lrs), [W, X, Y, Z, V]) @@ -158,10 +144,10 @@ let 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]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(spatial_species(tr_2), [Y]) + @test issetequal(parameters(tr_1), [dX]) + @test issetequal(parameters(tr_2), [dY1, dY2]) end ### Tests Spatial Reactions Generation ### From 26634d3ab92959c1f33d0d7ee8aa21ef44d5f776 Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Mon, 20 Nov 2023 17:00:09 -0500 Subject: [PATCH 113/121] Update src/spatial_reaction_systems/spatial_ODE_systems.jl Co-authored-by: Sam Isaacson --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 33c5691397..7b4909af8c 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -159,8 +159,14 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, # Set element values. if set_nonzero for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) - jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.src, s, lrs.num_species)] -= get_component_value(rates, e_idx) # Term due to species leaving source vertex. - jac_prototype[get_index(e.src, s, lrs.num_species), get_index(e.dst, s, lrs.num_species)] += get_component_value(rates, e_idx) # Term due to species arriving to destination vertex. + srcidx = get_index(e.src, s, lrs.num_species) + dstidx = get_index(e.dst, s, lrs.num_species) + val = get_component_value(rates, e_idx) + # Term due to species leaving source vertex. + jac_prototype[srcidx, srcidx] -= val + + # Term due to species arriving to destination vertex. + jac_prototype[srcidx, dstidx] += val end end From 2a81fca716c18558d3e4793c27120dc4b653c81d Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 Nov 2023 10:53:58 -0500 Subject: [PATCH 114/121] up --- .../lattice_reaction_systems.jl | 45 +++- .../spatial_ODE_systems.jl | 176 +++++++++---- .../spatial_reactions.jl | 32 ++- src/spatial_reaction_systems/utility.jl | 246 +++++++++++++----- .../lattice_reaction_systems.jl | 4 +- .../lattice_reaction_systems_ODEs.jl | 2 +- 6 files changed, 367 insertions(+), 138 deletions(-) diff --git a/src/spatial_reaction_systems/lattice_reaction_systems.jl b/src/spatial_reaction_systems/lattice_reaction_systems.jl index 867bd7ab07..79cb5d4fd0 100644 --- a/src/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/src/spatial_reaction_systems/lattice_reaction_systems.jl @@ -1,6 +1,7 @@ ### Lattice Reaction Network Structure ### # Describes a spatial reaction network over a graph. -struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding this part messes up show, disabling me from creating LRSs +# Adding the "<: MT.AbstractTimeDependentSystem" part messes up show, disabling me from creating LRSs. +struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Input values. """The reaction system within each compartment.""" rs::ReactionSystem{S} @@ -20,27 +21,49 @@ struct LatticeReactionSystem{S,T} # <: MT.AbstractTimeDependentSystem # Adding t init_digraph::Bool """Species that may move spatially.""" spat_species::Vector{BasicSymbolic{Real}} - """All parameters related to the lattice reaction system (both with spatial and non-spatial effects).""" + """ + All parameters related to the lattice reaction system + (both with spatial and non-spatial effects). + """ parameters::Vector{BasicSymbolic{Real}} - """Parameters which values are tied to vertexes (adjacencies), e.g. (possibly) have a unique value at each vertex of the system.""" + """ + Parameters which values are tied to vertexes (adjacencies), + e.g. (possibly) have a unique value at each vertex of the system. + """ vertex_parameters::Vector{BasicSymbolic{Real}} - """Parameters which values are tied to edges (adjacencies), e.g. (possibly) have a unique value at each edge of the system.""" + """ + Parameters which values are tied to edges (adjacencies), + e.g. (possibly) have a unique value at each edge of the system. + """ edge_parameters::Vector{BasicSymbolic{Real}} - function LatticeReactionSystem(rs::ReactionSystem{S}, - spatial_reactions::Vector{T}, + function LatticeReactionSystem(rs::ReactionSystem{S}, spatial_reactions::Vector{T}, lattice::DiGraph; init_digraph = true) where {S, T} - (T <: AbstractSpatialReaction) || error("The second argument must be a vector of AbstractSpatialReaction subtypes.") # There probably some better way to ascertain that T has that type. Not sure how. + # There probably some better way to ascertain that T has that type. Not sure how. + if !(T <: AbstractSpatialReaction) + error("The second argument must be a vector of AbstractSpatialReaction subtypes.") + end - spat_species = (isempty(spatial_reactions) ? Vector{BasicSymbolic{Real}}[] : unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions]))) + if isempty(spatial_reactions) + spat_species = Vector{BasicSymbolic{Real}}[] + else + spat_species = unique(reduce(vcat, [spatial_species(sr) for sr in spatial_reactions])) + end + num_species = length(unique([species(rs); spat_species])) rs_edge_parameters = filter(isedgeparameter, parameters(rs)) - srs_edge_parameters = (isempty(spatial_reactions) ? Vector{BasicSymbolic{Real}}[] : setdiff(reduce(vcat, [parameters(sr) for sr in spatial_reactions]), parameters(rs))) + if isempty(spatial_reactions) + srs_edge_parameters = Vector{BasicSymbolic{Real}}[] + else + srs_edge_parameters = setdiff(reduce(vcat, [parameters(sr) for sr in spatial_reactions]), parameters(rs)) + end edge_parameters = unique([rs_edge_parameters; srs_edge_parameters]) vertex_parameters = filter(!isedgeparameter, parameters(rs)) - ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] # Ensures that the order begins similarly to in the non-spatial ReactionSystem. + # Ensures the parameter order begins similarly to in the non-spatial ReactionSystem. + ps = [parameters(rs); setdiff([edge_parameters; vertex_parameters], parameters(rs))] 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, ps, vertex_parameters, edge_parameters) + return new{S,T}(rs, spatial_reactions, lattice, nv(lattice), ne(lattice), num_species, + init_digraph, spat_species, ps, vertex_parameters, edge_parameters) end end function LatticeReactionSystem(rs, srs, lat::SimpleGraph) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 7b4909af8c..0f182154eb 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,29 +1,53 @@ ### Spatial ODE Functor Structures ### # Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. -struct LatticeTransportODEf{R,S,T} +struct LatticeTransportODEf{Q,R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" - ofunc::R + ofunc::Q """The number of vertices.""" num_verts::Int64 """The number of species.""" num_species::Int64 """The values of the parameters which values are tied to vertexes.""" - vert_ps::Vector{Vector{S}} - """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length num_verts vector. To avoid re-allocation they are written to this vector.""" - work_vert_ps::Vector{S} - """For each parameter in vert_ps, its value is a vector with length either num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" + vert_ps::Vector{Vector{R}} + """ + Temporary vector. For parameters which values are identical across the lattice, + at some point these have to be converted of a length num_verts vector. + To avoid re-allocation they are written to this vector. + """ + work_vert_ps::Vector{R} + """ + For each parameter in vert_ps, its value is a vector with length either num_verts or 1. + To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. + This check is done once, and the value stored to this array. + This field (specifically) is an enumerate over that array. + """ v_ps_idx_types::Vector{Bool} - """A vector of pairs, with a value for each species with transportation. The first value is the species index (in the species(::ReactionSystem) vector), and the second is a vector with its transport rate values. If the transport rate is uniform (across all edges), that value is the only value in the vector. Else, there is one value for each edge in the lattice.""" - transport_rates::Vector{Pair{Int64, Vector{S}}} - """A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. Each value is the total rate at which that species leaves that vertex (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D).""" - leaving_rates::Matrix{S} + """ + A vector of pairs, with a value for each species with transportation. + The first value is the species index (in the species(::ReactionSystem) vector), + and the second is a vector with its transport rate values. + If the transport rate is uniform (across all edges), that value is the only value in the vector. + Else, there is one value for each edge in the lattice. + """ + transport_rates::Vector{Pair{Int64, Vector{R}}} + """ + A matrix, NxM, where N is the number of species with transportation and M the number of vertexes. + Each value is the total rate at which that species leaves that vertex + (e.g. for a species with constant diffusion rate D, in a vertex with n neighbours, this value is n*D). + """ + leaving_rates::Matrix{R} """An (enumerate'ed) iterator over all the edges of the lattice.""" - edges::Graphs.SimpleGraphs.SimpleEdgeIter{SimpleDiGraph{Int64}} - """The edge parameters used to create the spatial ODEProblem. Currently unused, but will be needed to support changing these (e.g. due to events). Contain one vector for each edge parameter (length one if uniform, else one value for each edge).""" + edges::S + """ + The edge parameters used to create the spatial ODEProblem. Currently unused, + but will be needed to support changing these (e.g. due to events). + Contain one vector for each edge parameter (length one if uniform, else one value for each edge). + """ edge_ps::Vector{Vector{T}} - function LatticeTransportODEf(ofunc::R, vert_ps::Vector{Vector{S}}, transport_rates::Vector{Pair{Int64, Vector{S}}}, edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {R,S,T} + function LatticeTransportODEf(ofunc::Q, vert_ps::Vector{Vector{R}}, transport_rates::Vector{Pair{Int64, Vector{R}}}, + edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where {Q,R,T} leaving_rates = zeros(length(transport_rates), lrs.num_verts) for (s_idx, trpair) in enumerate(transport_rates) rates = last(trpair) @@ -36,7 +60,8 @@ struct LatticeTransportODEf{R,S,T} # 1 if ps are constant across the graph, 0 else. v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) eds = edges(lrs.lattice) - new{R,S,T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) + new{Q,R,typeof(eds),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, + v_ps_idx_types, transport_rates, leaving_rates, eds, edge_ps) end end @@ -50,21 +75,37 @@ struct LatticeTransportODEjac{Q,R,S,T} num_species::Int64 """The values of the parameters which values are tied to vertexes.""" vert_ps::Vector{Vector{R}} - """Temporary vector. For parameters which values are identical across the lattice, at some point these have to be converted of a length(num_verts) vector. To avoid re-allocation they are written to this vector.""" + """ + Temporary vector. For parameters which values are identical across the lattice, + at some point these have to be converted of a length(num_verts) vector. + To avoid re-allocation they are written to this vector. + """ work_vert_ps::Vector{R} - """For each parameter in vert_ps, it either have length num_verts or 1. To know whenever a parameter's value need expanding to the work_vert_ps array, its length needs checking. This check is done once, and the value stored to this array. This field (specifically) is an enumerate over that array.""" + """ + For each parameter in vert_ps, it either have length num_verts or 1. + To know whenever a parameter's value need expanding to the work_vert_ps array, + its length needs checking. This check is done once, and the value stored to this array. + This field (specifically) is an enumerate over that array. + """ v_ps_idx_types::Vector{Bool} """Whether the Jacobian is sparse or not.""" sparse::Bool """The transport rates. Can be a dense matrix (for non-sparse) or as the "nzval" field if sparse.""" jac_transport::S - """The edge parameters used to create the spatial ODEProblem. Currently unused, but will be needed to support changing these (e.g. due to events). Contain one vector for each edge parameter (length one if uniform, else one value for each edge).""" + """ + The edge parameters used to create the spatial ODEProblem. Currently unused, + but will be needed to support changing these (e.g. due to events). + Contain one vector for each edge parameter (length one if uniform, else one value for each edge). + """ edge_ps::Vector{Vector{T}} - function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, edge_ps::Vector{Vector{T}}, sparse::Bool) where {R,S,T} + function LatticeTransportODEjac(ofunc::R, vert_ps::Vector{Vector{S}}, lrs::LatticeReactionSystem, + jac_transport::Union{Nothing, SparseMatrixCSC{Float64, Int64}}, + edge_ps::Vector{Vector{T}}, sparse::Bool) where {R,S,T} work_vert_ps = zeros(lrs.num_verts) v_ps_idx_types = map(vp -> length(vp) == 1, vert_ps) - new{R,S,typeof(jac_transport),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) + new{R,S,typeof(jac_transport),T}(ofunc, lrs.num_verts, lrs.num_species, vert_ps, + work_vert_ps, v_ps_idx_types, sparse, jac_transport, edge_ps) end end @@ -77,21 +118,28 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, name = nameof(lrs), include_zero_odes = true, combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), remove_conserved = false, checks = false, kwargs...) - is_transport_system(lrs) || error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") + if !is_transport_system(lrs) + error("Currently lattice ODE simulations are only supported when all spatial reactions are TransportReactions.") + end - # Converts potential symmaps to varmaps (parameter conversion is more involved since the vertex and edge parameters may be given in a tuple, or in a common vector). + # Converts potential symmaps to varmaps + # Parameter conversion complicated since the vertex and edge parameters may be given in a tuple, or in a common vector. 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) + p_in = (p_in isa Tuple{<:Any,<:Any}) ? + (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : + symmap_to_varmap(lrs, p_in) # Converts u0 and p to their internal forms. # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. - # These elements are length 1 vectors (if the parameter is uniform), or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). + # These elements are length 1 vectors (if the parameter is uniform), + # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) # Creates ODEProblem. - ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) + ofun = build_odefunction(lrs, vert_ps, edge_ps, jac, sparse, name, include_zero_odes, + combinatoric_ratelaws, remove_conserved, checks) return ODEProblem(ofun, u0, tspan, vert_ps, args...; kwargs...) end @@ -99,15 +147,22 @@ end function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T}}, edge_ps::Vector{Vector{T}}, jac::Bool, sparse::Bool, name, include_zero_odes, combinatoric_ratelaws, remove_conserved, checks) where {T} - remove_conserved && error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") + if remove_conserved + error("Removal of conserved quantities is currently not supported for `LatticeReactionSystem`s") + end - # Creates a map, taking (the index in species(lrs) each species (with transportation) to its transportation rate (uniform or one value for each edge). + # Creates a map, taking (the index in species(lrs) each species (with transportation) + # to its transportation rate (uniform or one value for each edge). transport_rates = make_sidxs_to_transrate_map(vert_ps, edge_ps, lrs) # Prepares the Jacobian and forcing functions (depending on jacobian and sparsity selection). + osys = convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks) if jac - ofunc_dense = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = true, sparse = false) # Always used for build_jac_prototype. - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = true, sparse = true) # Always used for LatticeTransportODEjac. + # `build_jac_prototype` currently assumes a sparse (non-spatial) Jacobian. Hence compute this. + # `LatticeTransportODEjac` currently assumes a dense (non-spatial) Jacobian. Hence compute this. + # Long term we could write separate version of these functions for generic input. + ofunc_dense = ODEFunction(osys; jac = true, sparse = false) + ofunc_sparse = ODEFunction(osys; jac = true, sparse = true) jac_vals = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = true) if sparse f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) @@ -121,11 +176,11 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} end else if sparse - ofunc_sparse = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = false, sparse = true) + ofunc_sparse = ODEFunction(osys; jac = false, sparse = true) f = LatticeTransportODEf(ofunc_sparse, vert_ps, transport_rates, edge_ps, lrs) jac_prototype = build_jac_prototype(ofunc_sparse.jac_prototype, transport_rates, lrs; set_nonzero = false) else - ofunc_dense = ODEFunction(convert(ODESystem, lrs.rs; name, combinatoric_ratelaws, include_zero_odes, checks); jac = false, sparse = false) + ofunc_dense = ODEFunction(osys; jac = false, sparse = false) f = LatticeTransportODEf(ofunc_dense, vert_ps, transport_rates, edge_ps, lrs) jac_prototype = nothing end @@ -136,8 +191,8 @@ function build_odefunction(lrs::LatticeReactionSystem, vert_ps::Vector{Vector{T} 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}, trans_rates, lrs::LatticeReactionSystem; - set_nonzero = false) +function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, trans_rates, + lrs::LatticeReactionSystem; set_nonzero = false) # Finds the indexes of the transport species, and the species with transport only (and no non-spatial dynamics). trans_species = first.(trans_rates) trans_only_species = filter(s_idx -> !Base.isstored(ns_jac_prototype, s_idx, s_idx), trans_species) @@ -147,26 +202,55 @@ function build_jac_prototype(ns_jac_prototype::SparseMatrixCSC{Float64, Int64}, ns_i_idxs = ns_jac_prototype_idxs[1] ns_j_idxs = ns_jac_prototype_idxs[2] - # List the indexes of all non-zero Jacobian terms. - non_spat_terms = [[get_index(vert, s_i, lrs.num_species), get_index(vert, s_j, lrs.num_species)] for vert in 1:(lrs.num_verts) for (s_i, s_j) in zip(ns_i_idxs,ns_j_idxs)] # Indexes of elements due to non-spatial dynamics. - trans_only_leaving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.src, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_only_species] # Indexes due to terms for a species leaves its current vertex (but does not have non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already be accounted for. - trans_arriving_terms = [[get_index(e.src, s_idx, lrs.num_species), get_index(e.dst, s_idx, lrs.num_species)] for e in edges(lrs.lattice) for s_idx in trans_species] # Indexes due to terms for species arriving into a new vertex. - all_terms = [non_spat_terms; trans_only_leaving_terms; trans_arriving_terms] + # Prepares vectors to store i and j indexes of Jacobian entries. + idx = 1 + num_entries = lrs.num_verts * length(ns_i_idxs) + + lrs.num_edges * (length(trans_only_species) + length(trans_species)) + i_idxs = Vector{Int}(undef, num_entries) + j_idxs = Vector{Int}(undef, num_entries) + + # Indexes of elements due to non-spatial dynamics. + for vert in 1:lrs.num_verts + for n in 1:length(ns_i_idxs) + i_idxs[idx] = get_index(vert, ns_i_idxs[n], lrs.num_species) + j_idxs[idx] = get_index(vert, ns_j_idxs[n], lrs.num_species) + idx += 1 + end + end + + # Indexes of elements due to spatial dynamics. + for e in edges(lrs.lattice) + # Indexes due to terms for a species leaves its current vertex (but does not have + # non-spatial dynamics). If the non-spatial Jacobian is fully dense, these would already + # be accounted for. + for s_idx in trans_only_species + i_idxs[idx] = get_index(e.src, s_idx, lrs.num_species) + j_idxs[idx] = i_idxs[idx] + idx += 1 + end + # Indexes due to terms for species arriving into a new vertex. + for s_idx in trans_species + i_idxs[idx] = get_index(e.src, s_idx, lrs.num_species) + j_idxs[idx] = get_index(e.dst, s_idx, lrs.num_species) + idx += 1 + end + end - # Creates a jacobian prototype with 0 values in all positions). - jac_prototype = sparse(first.(all_terms), last.(all_terms), fill(0.0, length(all_terms))) + # Create sparse jacobian prototype with 0-valued entries. + jac_prototype = sparse(i_idxs, j_idxs, zeros(num_entries)) # Set element values. if set_nonzero for (s, rates) in trans_rates, (e_idx, e) in enumerate(edges(lrs.lattice)) - srcidx = get_index(e.src, s, lrs.num_species) - dstidx = get_index(e.dst, s, lrs.num_species) - val = get_component_value(rates, e_idx) - # Term due to species leaving source vertex. - jac_prototype[srcidx, srcidx] -= val + idx_src = get_index(e.src, s, lrs.num_species) + idx_dst = get_index(e.dst, s, lrs.num_species) + val = get_component_value(rates, e_idx) + + # Term due to species leaving source vertex. + jac_prototype[idx_src, idx_src] -= val - # Term due to species arriving to destination vertex. - jac_prototype[srcidx, dstidx] += val + # Term due to species arriving to destination vertex. + jac_prototype[idx_src, idx_dst] += val end end diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index 23639a6edf..dff7a9df5c 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -71,23 +71,24 @@ function make_transport_reaction(rateex, species) end # Gets the parameters in a transport reaction. -ModelingToolkit.parameters(tr::TransportReaction) = convert(Vector{BasicSymbolic{Real}}, Symbolics.get_variables(tr.rate)) +ModelingToolkit.parameters(tr::TransportReaction) = Symbolics.get_variables(tr.rate) # Gets the species in a transport reaction. -# species(tr::TransportReaction) = [tr.species] # Currently these two are identical. This can be added back in once we have complicated spatial reactions where the two cases are not identical. 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; 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). + # (ODE simulation code becomes difficult if this is not required, + # as non-spatial jacobian and f function generated from rs is of wrong size). if !any(isequal(tr.species), species(rs)) error("Currently, species used in TransportReactions must have previously been declared within the non-spatial ReactionSystem. This is not the case for $(tr.species).") end # Checks that the rate does not depend on species. - if !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)))).") + rate_vars = ModelingToolkit.getname.(Symbolics.get_variables(tr.rate)) + if !isempty(intersect(ModelingToolkit.getname.(species(rs)), rate_vars)) + error("The following species were used in rates of a transport reactions: $(setdiff(ModelingToolkit.getname.(species(rs)), rate_vars)).") end # Checks that the species does not exist in the system with different metadata. @@ -105,13 +106,32 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti end equivalent_metadata(p1, p2) = isempty(setdiff(p1.metadata, p2.metadata, [Catalyst.EdgeParameter => true])) -# Since MTK's "isequal" does not worry about metadata, we have to use a special function that accounts for this (important because whether something is an edge parameter is defined here). +# Since MTK's "isequal" ignores metadata, we have to use a special function that accounts for this. +# This is important because whether something is an edge parameter is defined in metadata. function isequivalent(sym1, sym2) !isequal(sym1, sym2) && (return false) (sym1.metadata != sym2.metadata) && (return false) return true end +# Implements custom `==`. +""" + ==(rx1::TransportReaction, rx2::TransportReaction) + +Tests whether two [`TransportReaction`](@ref)s are identical. +""" +function (==)(tr1::TransportReaction, tr2::TransportReaction) + isequal(tr1.rate, tr2.rate) || return false + isequal(tr1.species, tr2.species) || return false + return true +end + +# Implements custom `hash`. +function hash(tr::TransportReaction, h::UInt) + h = Base.hash(tr.rate, h) + Base.hash(tr.species, h) +end + ### Utility ### # Loops through a rate and extract all parameters. function find_parameters_in_rate!(parameters, rateex::ExprValues) diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index b0efc5ed72..d0a02b3357 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -2,64 +2,114 @@ # Defines _symbol_to_var, but where the system is a LRS. 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))) # Checks if sym is a parameter. + # Checks if sym is a parameter. + 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))) # Checks if sym is a species. + + # Checks if sym is a species. + 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 (a vector on the form [species 1 at vertex 1, species 2 at vertex 1, ..., species 1 at vertex 2, ...]). +# From u0 input, extracts their values and store them in the internal format. +# Internal format: a vector on the form [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]). function lattice_process_u0(u0_in, u0_syms, num_verts) - u0 = lattice_process_input(u0_in, u0_syms, num_verts) # u0 values can be given in various forms. This converts it to a Vector{Vector{}} form (Vector containing one vector for each species. Second vector has one value if species uniform across lattice, else one value for each vertex). - check_vector_lengths(u0, length(u0_syms), num_verts) # Perform various error checks on the (by the user provided) initial conditions. - expand_component_values(u0, num_verts) # Converts the Vector{Vector{}} format to a single Vector (with one values for each species and vertex). + # u0 values can be given in various forms. This converts it to a Vector{Vector{}} form. + # Top-level vector: Contains one vector for each species. + # Second-level vector: contain one value if species uniform across lattice, else one value for each vertex). + u0 = lattice_process_input(u0_in, u0_syms, num_verts) + + # Perform various error checks on the (by the user provided) initial conditions. + check_vector_lengths(u0, length(u0_syms), num_verts) + + # Converts the Vector{Vector{}} format to a single Vector (with one values for each species and vertex). + expand_component_values(u0, num_verts) end -# From p input, splits it into diffusion parameters and compartment parameters, and store these in the desired internal format. +# From p input, splits it into diffusion parameters and compartment parameters. +# Store these in the desired internal format. function lattice_process_p(p_in, p_vertex_syms, p_edge_syms, lrs::LatticeReactionSystem) - vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) # If the user provided parameters as a single map (mixing vertex and edge parameters) , these are split into two separate vectors. - vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. - lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) # If the lattice was defined as an undirected graph (with N edges), but then provides N/2 values for some edge parameter, we presume they want to expand that parameters value so it has the same value in both directions. - check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) # Perform various error checks on the (by the user provided) vertex parameters. - check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) # Perform various error checks on the (by the user provided) edge parameters. + # If the user provided parameters as a single map (mixing vertex and edge parameters): + # Split into two separate vectors. + vert_ps_in, edge_ps_in = split_parameters(p_in, p_vertex_syms, p_edge_syms) + + # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. + vert_ps = lattice_process_input(vert_ps_in, p_vertex_syms, lrs.num_verts) + + # Parameter values can be given in various forms. This converts it to the Vector{Vector{}} form. + edge_ps = lattice_process_input(edge_ps_in, p_edge_syms, lrs.num_edges) + + # If the lattice defined as (N edge) undirected graph, and we provides N/2 values for some edge parameter: + # Presume they want to expand that parameters value so it has the same value in both directions. + lrs.init_digraph || duplicate_trans_params!(edge_ps, lrs) + + # Perform various error checks on the (by the user provided) vertex and edge parameters. + check_vector_lengths(vert_ps, length(p_vertex_syms), lrs.num_verts) + check_vector_lengths(edge_ps, length(p_edge_syms), lrs.num_edges) + return vert_ps, edge_ps end # Splits parameters into those for the vertexes and those for the edges. -split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps # If they are already split, return that. -function split_parameters(ps::Vector{<:Number}, args...) # Providing parameters to a spatial reaction system as a single vector of values (e.g. [1.0, 4.0, 0.1]) is not allowed, Either use tuple (e.g. ([1.0, 4.0], [0.1])) or map format (e.g. [A => 1.0, B => 4.0, D => 0.1]). + +# If they are already split, return that. +split_parameters(ps::Tuple{<:Any, <:Any}, args...) = ps +# Providing parameters to a spatial reaction system as a single vector of values (e.g. [1.0, 4.0, 0.1]) is not allowed. +# Either use tuple (e.g. ([1.0, 4.0], [0.1])) or map format (e.g. [A => 1.0, B => 4.0, D => 0.1]). +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) # Splitting is only done for Vectors of Pairs (where the first value is a Symbols, and the second a value). - vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] # The input vertex parameters. - edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] # The input edge parameters. - (sum(length.([vert_ps_in, edge_ps_in])) != length(ps)) && error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") # Error check, in case some input parameters where neither recognised as vertex or edge parameters. +# Splitting is only done for Vectors of Pairs (where the first value is a Symbols, and the second a value). +function split_parameters(ps::Vector{<: Pair}, p_vertex_syms::Vector, p_edge_syms::Vector) + vert_ps_in = [p for p in ps if any(isequal(p[1]), p_vertex_syms)] + edge_ps_in = [p for p in ps if any(isequal(p[1]), p_edge_syms)] + + # Error check, in case some input parameters where neither recognised as vertex or edge parameters. + if (sum(length.([vert_ps_in, edge_ps_in])) != length(ps)) + error("These input parameters are not recognised: $(setdiff(first.(ps), vcat(first.([vert_ps_in, edge_ps_in]))))") + end + return vert_ps_in, edge_ps_in end -# Input is allowed on the following forms (after potential Symbol maps have been converted to Symbolic maps: - # - A vector of values, where the i'th value corresponds to the value of the i'th: initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). - # - A vector of vectors of values. The same as previously, but here the species/parameter can have different values across the spatial structure. - # - A map of symbols to their values. These can either be a single value (if uniform across the spatial structure) or a vector (with different values for each vertex/edge). These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). - # - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, indicating the value of each species at each vertex. -# The lattice_process_input function takes input initial conditions/vertex parameters/edge parameters of whichever form the user have used, and converts them to the Vector{Vector{}} form used internally. E.g. for parameters the top vector contain one vector for each parameter (using the same order as in parameters(::ReactionSystem)). If a parameter is uniformly-values across the spatial structure, its vector has a single value. Else, it has a number of values corresponding to the number of vertexes/edges (for edge/vertex parameters). Initial conditions works similarly. +# Input may have the following forms (after potential Symbol maps to Symbolic maps conversions): + # - A vector of values, where the i'th value corresponds to the value of the i'th + # initial condition value (for u0_in), vertex parameter value (for vert_ps_in), or edge parameter value (for edge_ps_in). + # - A vector of vectors of values. The same as previously, + # but here the species/parameter can have different values across the spatial structure. + # - A map of Symbols to values. These can either be a single value (if uniform across the spatial structure) + # or a vector (with different values for each vertex/edge). + # These can be mixed (e.g. [X => 1.0, Y => [1.0, 2.0, 3.0, 4.0]] is allowed). + # - A matrix. E.g. for initial conditions you can have a num_species * num_vertex matrix, + # indicating the value of each species at each vertex. -# If the input is given in a map form, the vector needs sorting and the first value removed. The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. +# The lattice_process_input function takes input initial conditions/vertex parameters/edge parameters +# of whichever form the user have used, and converts them to the Vector{Vector{}} form used internally. +# E.g. for parameters the top-level vector contain one vector for each parameter (same order as in parameters(::ReactionSystem)). +# If a parameter is uniformly-values across the spatial structure, its vector has a single value. +# Else, it has a number of values corresponding to the number of vertexes/edges (for edge/vertex parameters). +# Initial conditions works similarly. + +# If the input is given in a map form, the vector needs sorting and the first value removed. +# The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. 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 -# If the input is a matrix: Processes the input and gives it in a form where it is a vector of vectors (some of which may have a single value). Sends it back to lattice_process_input for reprocessing. +# If the input is a matrix: Processes the input and gives it in a form where it is a vector of vectors +# (some of which may have a single value). Sends it back to lattice_process_input for reprocessing. function lattice_process_input(input::Matrix{<:Number}, args...) lattice_process_input([vec(input[i, :]) for i in 1:size(input, 1)], args...) end +# Possibly we want to support this type of input at some point. function lattice_process_input(input::Array{<:Number, 3}, args...) - error("3 dimensional array parameter input currently not supported.") # Supposedly we want to support this type of input at some point. + error("3 dimensional array parameter input currently not supported.") end -# If the input is a Vector containing both vectors and single values, converts it to the Vector{<:Vector} form. Technically this last lattice_process_input is probably not needed. +# If the input is a Vector containing both vectors and single values, converts it to the Vector{<:Vector} form. +# Technically this last lattice_process_input is probably not needed. 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], @@ -68,31 +118,51 @@ end # If the input is of the correct form already, return it. 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. Error check if e.g. the user does not provide values for all species/parameters, or for one, provides a vector of values, but that has the wrong length (e.g providing 7 values for one species, but there are 8 vertexes). +# Checks that a value vector have the right length, as well as that of all its sub vectors. +# Error check if e.g. the user does not provide values for all species/parameters, +# or for one: provides a vector of values, but that has the wrong length +# (e.g providing 7 values for one species, but there are 8 vertexes). 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.") + if (length(input)!=n_syms) + error("Missing values for some initial conditions/parameters. Expected $n_syms values, got $(length(input)).") + end + if !isempty(setdiff(unique(length.(input)), [1, n_locations])) + error("Some inputs where given values of inappropriate length.") + end end -# For transport parameters, if the lattice was given as an undirected graph of size n, this is converted to a directed graph of size 2n. +# For transport parameters, if the lattice was given as an undirected graph of size n: +# this is converted to a directed graph of size 2n. # If transport parameters are given with n values, we want to use 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 the correct value in each position. +# 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 the correct value in each position. function duplicate_trans_params!(edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) cum_adjacency_counts = [0;cumsum(length.(lrs.lattice.fadjlist[1:end-1]))] - for idx in 1:length(edge_ps) # Loops through all edge parameter. - (2length(edge_ps[idx]) == lrs.num_edges) || continue # If the edge parameter already have one value for each directed edge, it is fine and we can continue. + for idx in 1:length(edge_ps) + # If the edge parameter already values for each directed edge, we can continue. + (2length(edge_ps[idx]) == lrs.num_edges) || continue # - # This entire thing depends on the fact that, in the edges(lattice) iterator, the edges are sorted by (1) Their source node, (2) Their destination node. - new_vals = Vector{Float64}(undef,lrs.num_edges) # A vector where we will put the edge parameters new values. Has the correct length (the number of directed edges in the lattice). - 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. + # This entire thing depends on the fact that, in the edges(lattice) iterator, the edges are sorted by: + # (1) Their source node + # (2) Their destination node. + + # A vector where we will put the edge parameters new values. + # Has the correct length (the number of directed edges in the lattice). + new_vals = Vector{Float64}(undef,lrs.num_edges) + # As we loop through the edges of the di-graph, this keeps track of each edge's index in the original graph. + original_edge_count = 0 for edge in edges(lrs.lattice) # For each edge. - (edge.src < edge.dst) ? (original_edge_count += 1) : continue # The digraph conversion 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. + # The digraph conversion only adds edges so that src > dst. + (edge.src < edge.dst) ? (original_edge_count += 1) : continue + # For original edge i -> j, finds the index of i -> j in DiGraph. + 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 j -> i in DiGraph. + idx_bwd = cum_adjacency_counts[edge.dst] + findfirst(isequal(edge.src),lrs.lattice.fadjlist[edge.dst]) new_vals[idx_fwd] = edge_ps[idx][original_edge_count] new_vals[idx_bwd] = edge_ps[idx][original_edge_count] end - edge_ps[idx] = new_vals # Replaces the edge parameters values with the updated value vector. + # Replaces the edge parameters values with the updated value vector. + edge_ps[idx] = new_vals end end @@ -104,31 +174,47 @@ function param_dict(vert_ps, edge_ps, lrs) vals_to_dict(edge_parameters(lrs), edge_ps)) end -# Computes the transport rates and stores them in a desired format (a Dictionary from species index to rates across all edges). +# Computes the transport rates and stores them in a desired format +# (a Dictionary from species index to rates across all edges). function compute_all_transport_rates(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{Float64}}, lrs::LatticeReactionSystem) - param_value_dict = param_dict(vert_ps, edge_ps, lrs) # Creates a dict, allowing us to access the values of wll parameters. - unsorted_rates = [s => Symbolics.value.(compute_transport_rates(get_transport_rate_law(s, lrs), param_value_dict, lrs.num_edges)) for s in spatial_species(lrs)] # For all species with transportation, compute their transportation rate (across all edges). This is a vector, pairing each species to these rates. - return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem), + # Creates a dict, allowing us to access the values of wll parameters. + p_val_dict = param_dict(vert_ps, edge_ps, lrs) + + # For all species with transportation, compute their transportation rate (across all edges). + # This is a vector, pairing each species to these rates. + unsorted_rates = [s => compute_transport_rates(get_transport_rate_law(s, lrs), p_val_dict, lrs.num_edges) + for s in spatial_species(lrs)] + + # Sorts all the species => rate pairs according to their species index in species(::ReactionSystem). + return sort(unsorted_rates; by=rate -> findfirst(isequal(rate[1]), species(lrs))) end -# For a species, retrieves the symbolic expression for its transportation rate (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). +# For a species, retrieves the symbolic expression for its transportation rate +# (likely only a single parameter, such as `D`, but could be e.g. L*D, where L and D are parameters). +# We could allows several transportation reactions for one species and simply sum them though, easy change. function get_transport_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. + (length(rates) > 1) && error("Species $s have more than one diffusion reaction.") return rates[1].rate end -# For the numeric expression describing the rate of transport (likely only a single parameter, such as `D`), and the values of all our parameters, computes the transport rate(s). If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. Else a vector with each value corresponding to the rate at one specific edge. +# For the numeric expression describing the rate of transport (likely only a single parameter, e.g. `D`), +# and the values of all our parameters, computes the transport rate(s). +# If all parameters the rate depend on are uniform all edges, this becomes a length 1 vector. +# Else a vector with each value corresponding to the rate at one specific edge. function compute_transport_rates(rate_law::Num, - param_value_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) - relevant_parameters = Symbolics.get_variables(rate_law) # Extracts the parameters that this rate depend on. - if all(length(param_value_dict[P]) == 1 for P in relevant_parameters) # If all these parameters are spatially uniform. - return [ - substitute(rate_law, - Dict(p => param_value_dict[p][1] for p in relevant_parameters)), # Computes a length 1 vector with this value. - ] + p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) + relevant_ps = Symbolics.get_variables(rate_law) + + # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. + if all(length(p_val_dict[P]) == 1 for P in relevant_ps) + rates = [substitute(rate_law, Dict(p => p_val_dict[p][1] for p in relevant_ps))] + + # If at least on parameter the rate depends on have a value varying across all edges, + # we have to compute one rate value for each edge. + else + rates = [substitute(rate_law, Dict(p => get_component_value(p_val_dict[p], idxE) for p in relevant_ps)) + for idxE in 1:num_edges] end - return [substitute(rate_law, # If at least on parameter the rate depends on have a value varying across all edges, we have to compute one rate value for each edge. - Dict(p => get_component_value(param_value_dict[p], idxE) - for p in relevant_parameters)) for idxE in 1:num_edges] + return Symbolics.value.(rates) end # Creates a map, taking each species (with transportation) to its transportation rate. @@ -137,7 +223,9 @@ end # Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where T transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) - return Pair{Int64, Vector{T}}[speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap] + return Pair{Int64, Vector{T}}[ + speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap + ] end ### Accessing State & Parameter Array Values ### @@ -147,29 +235,37 @@ get_index(vert::Int64, s::Int64, num_species::Int64) = (vert - 1) * num_species # Gets the indexes in the u array of all species in vertex vert (when their are num_species species). get_indexes(vert::Int64, num_species::Int64) = ((vert - 1) * num_species + 1):(vert * num_species) -# 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. Here: - # - values is the vector with the values of teh component across all locations (where the internal vectors may or may not be of size 1). - # - component_idx is the initial condition species/vertex parameter/edge parameters's index. This is predominantly used for parameters, for initial conditions, it is only used once (at initialisation) to re-process the input vector. - # - location_idx is the index of the vertex or edge for which we wish to access a initial condition or parameter values +# For vectors of length 1 or n, we want to get value idx (or the one value, if length is 1). +# This function gets that. Here: +# - values is the vector with the values of the component across all locations +# (where the internal vectors may or may not be of size 1). +# - component_idx is the initial condition species/vertex parameter/edge parameters's index. +# This is predominantly used for parameters, for initial conditions, +# it is only used once (at initialisation) to re-process the input vector. +# - location_idx is the index of the vertex or edge for which we wish to access a initial condition or parameter values. # The first two function takes the full value vector, and call the function of at the components specific index. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, location_idx::Int64) get_component_value(values[component_idx], location_idx) end +# Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. +# This is stored in location_types. function get_component_value(values::Vector{<:Vector}, component_idx::Int64, - location_idx::Int64, location_types::Vector{Bool}) # Sometimes we have pre-computed, for each component, whether it's vector is length 1 or not. This is stored in location_types. + location_idx::Int64, location_types::Vector{Bool}) get_component_value(values[component_idx], location_idx, location_types[component_idx]) end # For a components value (which is a vector of either length 1 or some other length), retrieves its value. function get_component_value(values::Vector{<:Number}, location_idx::Int64) get_component_value(values, location_idx, length(values) == 1) end +# Again, the location type (length of the value vector) may be pre-computed. function get_component_value(values::Vector{<:Number}, location_idx::Int64, location_type::Bool) - location_type ? values[1] : values[location_idx] # Again, the location type (length of teh value vector) may be pre-computed. + location_type ? values[1] : values[location_idx] end -# Converts a vector of vectors to a long vector. These are used when the initial condition is converted to a single vector (from vector of vector form). +# Converts a vector of vectors to a long vector. +# These are used when the initial condition is converted to a single vector (from vector of vector form). function expand_component_values(values::Vector{<:Vector}, n) vcat([get_component_value.(values, comp) for comp in 1:n]...) end @@ -177,15 +273,21 @@ 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 vert_ps vector at a given location. Provides a work vector to which the converted vector is written. +# Creates a view of the vert_ps vector at a given location. +# Provides a work vector to which the converted vector is written. function view_vert_ps_vector!(work_vert_ps, vert_ps, comp, enumerated_vert_ps_idx_types) - for (idx,loc_type) in enumerated_vert_ps_idx_types # Loops through all parameters. - work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) # If the parameter is uniform across the spatial structure, it will have a length-1 value vector (which value we write to the work vector). Else, we extract it value at the specific location. + # Loops through all parameters. + for (idx,loc_type) in enumerated_vert_ps_idx_types + # If the parameter is uniform across the spatial structure, it will have a length-1 value vector + # (which value we write to the work vector). + # Else, we extract it value at the specific location. + work_vert_ps[idx] = (loc_type ? vert_ps[idx][1] : vert_ps[idx][comp]) end return work_vert_ps end -# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form (currently only used in Spatial Jump systems). +# Expands a u0/p information stored in Vector{Vector{}} for to Matrix form +# (currently only 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 e151dbde40..00ded50494 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems.jl @@ -211,8 +211,8 @@ let tr_macro_2 = @transport_reaction $(rate2) Y # tr_macro_3 = @transport_reaction dZ $species3 # Currently does not work, something with meta programming. - @test_broken isequal(tr_1, tr_macro_1) - @test_broken isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. + @test isequal(tr_1, tr_macro_1) + @test isequal(tr_2, tr_macro_2) # Unsure why these fails, since for components equality hold: `isequal(tr_1.species, tr_macro_1.species)` and `isequal(tr_1.rate, tr_macro_1.rate)` are both true. # @test isequal(tr_3, tr_macro_3) end diff --git a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl index 9b542078dc..7355f34ef4 100644 --- a/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl +++ b/test/spatial_reaction_systems/lattice_reaction_systems_ODEs.jl @@ -124,7 +124,7 @@ let D_vals = [0.2, 0.2, 0.3, 0.3] u0 = [:X => [1.0, 2.0, 3.0], :Y => 1.0] ps = [:pX => [2.0, 2.5, 3.0], :pY => 0.5, :d => 0.1, :D => D_vals] - oprob = ODEProblem(lrs, u0, (0.0, 0.0), ps) + oprob = ODEProblem(lrs, u0, (0.0, 0.0), ps; jac=true, sparse=true) # Creates manual f and jac functions. function f_manual!(du, u, p, t) From 4341962d4c16a700031b5b66176abd01b0e997df Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 Nov 2023 11:12:09 -0500 Subject: [PATCH 115/121] reduce more lines. --- src/spatial_reaction_systems/spatial_ODE_systems.jl | 6 +++--- src/spatial_reaction_systems/spatial_reactions.jl | 3 ++- src/spatial_reaction_systems/utility.jl | 12 ++++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/spatial_reaction_systems/spatial_ODE_systems.jl b/src/spatial_reaction_systems/spatial_ODE_systems.jl index 0f182154eb..f0d71383ba 100644 --- a/src/spatial_reaction_systems/spatial_ODE_systems.jl +++ b/src/spatial_reaction_systems/spatial_ODE_systems.jl @@ -1,6 +1,6 @@ ### Spatial ODE Functor Structures ### -# Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. +# Functor with information for the forcing function of a spatial ODE with spatial movement on a lattice. struct LatticeTransportODEf{Q,R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::Q @@ -65,7 +65,7 @@ struct LatticeTransportODEf{Q,R,S,T} end end -# Functor structure containing the information for the forcing function of a spatial ODE with spatial movement on a lattice. +# Functor with information for the Jacobian function of a spatial ODE with spatial movement on a lattice. struct LatticeTransportODEjac{Q,R,S,T} """The ODEFunction of the (non-spatial) reaction system which generated this function.""" ofunc::Q @@ -123,7 +123,7 @@ function DiffEqBase.ODEProblem(lrs::LatticeReactionSystem, u0_in, tspan, end # Converts potential symmaps to varmaps - # Parameter conversion complicated since the vertex and edge parameters may be given in a tuple, or in a common vector. + # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. u0_in = symmap_to_varmap(lrs, u0_in) p_in = (p_in isa Tuple{<:Any,<:Any}) ? (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : diff --git a/src/spatial_reaction_systems/spatial_reactions.jl b/src/spatial_reaction_systems/spatial_reactions.jl index dff7a9df5c..c1a01a3888 100644 --- a/src/spatial_reaction_systems/spatial_reactions.jl +++ b/src/spatial_reaction_systems/spatial_reactions.jl @@ -95,7 +95,8 @@ function check_spatial_reaction_validity(rs::ReactionSystem, tr::TransportReacti if any([isequal(tr.species, s) && !isequivalent(tr.species, s) 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.") end - if 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)]) + if 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.") end diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index d0a02b3357..c630d5e59c 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -95,7 +95,9 @@ end # If the input is given in a map form, the vector needs sorting and the first value removed. # The creates a Vector{Vector{Value}} or Vector{value} form, which is then again sent to lattice_process_input for reprocessing. 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)).") + if !isempty(setdiff(first.(input), syms)) + error("Some input symbols are not recognised: $(setdiff(first.(input), syms)).") + end sorted_input = sort(input; by = p -> findfirst(isequal(p[1]), syms)) return lattice_process_input(last.(sorted_input), syms, args...) end @@ -148,7 +150,7 @@ function duplicate_trans_params!(edge_ps::Vector{Vector{Float64}}, lrs::LatticeR # A vector where we will put the edge parameters new values. # Has the correct length (the number of directed edges in the lattice). - new_vals = Vector{Float64}(undef,lrs.num_edges) + new_vals = Vector{Float64}(undef, lrs.num_edges) # As we loop through the edges of the di-graph, this keeps track of each edge's index in the original graph. original_edge_count = 0 for edge in edges(lrs.lattice) # For each edge. @@ -219,9 +221,11 @@ end # Creates a map, taking each species (with transportation) to its transportation rate. # The species is represented by its index (in species(lrs). -# If the rate is uniform across all edges, the vector will be length 1 (with this value), else there will be a separate value for each edge. +# If the rate is uniform across all edges, the vector will be length 1 (with this value), +# else there will be a separate value for each edge. # Pair{Int64, Vector{T}}[] is required in case vector is empty (otherwise it becomes Any[], causing type error later). -function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, lrs::LatticeReactionSystem) where T +function make_sidxs_to_transrate_map(vert_ps::Vector{Vector{Float64}}, edge_ps::Vector{Vector{T}}, + lrs::LatticeReactionSystem) where T transport_rates_speciesmap = compute_all_transport_rates(vert_ps, edge_ps, lrs) return Pair{Int64, Vector{T}}[ speciesmap(lrs.rs)[spat_rates[1]] => spat_rates[2] for spat_rates in transport_rates_speciesmap From 621d664f98d650855f6f0a2b543f019b96a9bf56 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 Nov 2023 12:15:15 -0500 Subject: [PATCH 116/121] use generated rate law function --- Project.toml | 1 + src/Catalyst.jl | 4 ++++ src/spatial_reaction_systems/utility.jl | 13 +++++++------ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Project.toml b/Project.toml index 6300c1427e..e982285872 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" Requires = "ae029012-a4dd-5104-9daa-d747884805df" +RuntimeGeneratedFunctions = "7e49a35a-f44a-4d26-94aa-eba1b4ca6b47" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" diff --git a/src/Catalyst.jl b/src/Catalyst.jl index 33b95ebaab..f10795d6e9 100644 --- a/src/Catalyst.jl +++ b/src/Catalyst.jl @@ -16,6 +16,10 @@ const MT = ModelingToolkit using Unitful @reexport using ModelingToolkit using Symbolics + +using RuntimeGeneratedFunctions +RuntimeGeneratedFunctions.init(@__MODULE__) + import Symbolics: BasicSymbolic import SymbolicUtils using ModelingToolkit: Symbolic, value, istree, get_states, get_ps, get_iv, get_systems, diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index c630d5e59c..b987b2032f 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -204,16 +204,17 @@ end # Else a vector with each value corresponding to the rate at one specific edge. function compute_transport_rates(rate_law::Num, p_val_dict::Dict{SymbolicUtils.BasicSymbolic{Real}, Vector{Float64}}, num_edges::Int64) - relevant_ps = Symbolics.get_variables(rate_law) - - # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. - if all(length(p_val_dict[P]) == 1 for P in relevant_ps) - rates = [substitute(rate_law, Dict(p => p_val_dict[p][1] for p in relevant_ps))] + # Finds parameters involved in rate and create a function evaluating teh rate law. + relevant_ps = Symbolics.get_variables(rate_law) + rate_law_func = drop_expr(@RuntimeGeneratedFunction(build_function(rate_law, relevant_ps...))) + # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. + if all(length(p_val_dict[P]) == 1 for P in relevant_ps) + rates = [rate_law_func([p_val_dict[p][1] for p in relevant_ps]...)] # If at least on parameter the rate depends on have a value varying across all edges, # we have to compute one rate value for each edge. else - rates = [substitute(rate_law, Dict(p => get_component_value(p_val_dict[p], idxE) for p in relevant_ps)) + rates = [rate_law_func([get_component_value(p_val_dict[p], idxE) for p in relevant_ps]...) for idxE in 1:num_edges] end return Symbolics.value.(rates) From efa91d030d1080b61448764f0ff7648a01f13e6a Mon Sep 17 00:00:00 2001 From: Torkel Loman Date: Tue, 21 Nov 2023 14:58:47 -0500 Subject: [PATCH 117/121] Update HISTORY.md Co-authored-by: Sam Isaacson --- HISTORY.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index f2187b0ab4..069e74ae5d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -18,7 +18,9 @@ ``` this value will be used across the entire system. If their values are instead vectors, different values are used across the spatial system. Here ```julia - u0 = [:X => [1.0, 0.0, 0.0, ...]] + X0 = zeros(25) + X0[1] = 1.0 + u0 = [:X => X0] ``` X's value will be `1.0` in the first vertex, but `0.0` in the remaining one (the system have 25 vertexes in total). SInce th parameters `p` and `d` are part of the non-spatial reaction network, their values are tied to vertexes. However, if the `D` parameter (which governs diffusion between vertexes) is given several values, these will instead correspond to the specific edges (and transportation along those edges.) From 1f9386dd245fbc8ce4f7ff2b1a74d85985981c08 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 Nov 2023 15:08:53 -0500 Subject: [PATCH 118/121] up --- Project.toml | 1 + src/spatial_reaction_systems/utility.jl | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index e982285872..c43b053c36 100644 --- a/Project.toml +++ b/Project.toml @@ -41,6 +41,7 @@ ModelingToolkit = "8.66" Parameters = "0.12" Reexport = "0.2, 1.0" Requires = "1.0" +RuntimeGeneratedFunctions = "0.5.12" SymbolicUtils = "1.0.3" Symbolics = "5.0.3" Unitful = "1.12.4" diff --git a/src/spatial_reaction_systems/utility.jl b/src/spatial_reaction_systems/utility.jl index b987b2032f..8e6543a2b3 100644 --- a/src/spatial_reaction_systems/utility.jl +++ b/src/spatial_reaction_systems/utility.jl @@ -210,14 +210,13 @@ function compute_transport_rates(rate_law::Num, # If all these parameters are spatially uniform. `rates` becomes a vector with 1 value. if all(length(p_val_dict[P]) == 1 for P in relevant_ps) - rates = [rate_law_func([p_val_dict[p][1] for p in relevant_ps]...)] + return [rate_law_func([p_val_dict[p][1] for p in relevant_ps]...)] # If at least on parameter the rate depends on have a value varying across all edges, # we have to compute one rate value for each edge. else - rates = [rate_law_func([get_component_value(p_val_dict[p], idxE) for p in relevant_ps]...) + return [rate_law_func([get_component_value(p_val_dict[p], idxE) for p in relevant_ps]...) for idxE in 1:num_edges] end - return Symbolics.value.(rates) end # Creates a map, taking each species (with transportation) to its transportation rate. From 820efb0f56e50f94cdac2584ed999d113d7298b6 Mon Sep 17 00:00:00 2001 From: Torkel Date: Tue, 21 Nov 2023 16:29:39 -0500 Subject: [PATCH 119/121] use SteadyStateDiffEq 2 syntax --- test/miscellaneous_tests/nonlinear_solve.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/miscellaneous_tests/nonlinear_solve.jl b/test/miscellaneous_tests/nonlinear_solve.jl index f0e3a0b806..f3336e9f32 100644 --- a/test/miscellaneous_tests/nonlinear_solve.jl +++ b/test/miscellaneous_tests/nonlinear_solve.jl @@ -26,7 +26,7 @@ let # Solves it using standard algorithm and simulation based algorithm. sol1 = solve(nl_prob; abstol=1e-12, reltol=1e-12).u - sol2 = solve(nl_prob, DynamicSS(Rosenbrock23(); abstol=1e-12, reltol=1e-12); abstol=1e-12, reltol=1e-12).u + sol2 = solve(nl_prob, DynamicSS(Rosenbrock23()); abstol=1e-12, reltol=1e-12).u # Tests solutions are correct. @test isapprox(sol1[1], p[1] / p[2]; atol=1e-10) From 9f6c1af47dd9bb3b358bc79d075cf7e55b39a762 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 Nov 2023 13:45:23 -0500 Subject: [PATCH 120/121] update remaining tests for SteadyStateDiffEq v2 --- test/miscellaneous_tests/nonlinear_solve.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/miscellaneous_tests/nonlinear_solve.jl b/test/miscellaneous_tests/nonlinear_solve.jl index f3336e9f32..c2f74c462d 100644 --- a/test/miscellaneous_tests/nonlinear_solve.jl +++ b/test/miscellaneous_tests/nonlinear_solve.jl @@ -55,7 +55,7 @@ let # Solves it using standard algorithm and simulation based algorithm. sol1 = solve(nl_prob; abstol=1e-12, reltol=1e-12).u - sol2 = solve(nl_prob, DynamicSS(Rosenbrock23(); abstol=1e-12, reltol=1e-12); abstol=1e-12, reltol=1e-12).u + sol2 = solve(nl_prob, DynamicSS(Rosenbrock23()); abstol=1e-12, reltol=1e-12).u # Computes NonlinearFunction (manually and automatically). nfunc = NonlinearFunction(convert(NonlinearSystem, steady_state_network_2)) @@ -91,7 +91,7 @@ let # Solves it using standard algorithm and simulation based algorithm. sol1 = solve(nl_prob_1; abstol=1e-12, reltol=1e-12) - sol2 = solve(nl_prob_2, DynamicSS(Rosenbrock23(); abstol=1e-12, reltol=1e-12); abstol=1e-12, reltol=1e-12) + sol2 = solve(nl_prob_2, DynamicSS(Rosenbrock23()); abstol=1e-12, reltol=1e-12) # Checks output using NonlinearFunction. nfunc = NonlinearFunction(convert(NonlinearSystem, steady_state_network_3)) From 5e220f7bbe2c88047b29e4ef423cf75d6a289d45 Mon Sep 17 00:00:00 2001 From: Torkel Date: Wed, 22 Nov 2023 14:41:30 -0500 Subject: [PATCH 121/121] remove old SteadyStateDiffEq file (with similar test, but old syntax) --- .../solve_steady_state_problems.jl | 78 ------------------- test/runtests.jl | 1 - 2 files changed, 79 deletions(-) delete mode 100644 test/model_simulation/solve_steady_state_problems.jl diff --git a/test/model_simulation/solve_steady_state_problems.jl b/test/model_simulation/solve_steady_state_problems.jl deleted file mode 100644 index 55c44b6115..0000000000 --- a/test/model_simulation/solve_steady_state_problems.jl +++ /dev/null @@ -1,78 +0,0 @@ -### Fetch Packages and Reaction Networks ### - -# Fetch packages. -using Catalyst, OrdinaryDiffEq, Random, SteadyStateDiffEq, Test - -# Sets rnd number. -using StableRNGs -rng = StableRNG(12345) - -# Fetch test networks. -include("../test_networks.jl") - -### Compares Solution to Known Steady States ### - -# Simple network. -let - steady_state_network_1 = @reaction_network begin - (k1, k2), ∅ ↔ X1 - (k3, k4), ∅ ↔ 3X2 - (k5, k6), ∅ ↔ X3 + X4 - end - - for factor in [1e-1, 1e0, 1e1] - u0 = factor * rand(rng, length(states(steady_state_network_1))) - u0[4] = u0[3] - p = 0.01 .+ factor * rand(rng, length(parameters(steady_state_network_1))) - prob = SteadyStateProblem(steady_state_network_1, u0, p) - sol = solve(prob, SSRootfind()).u - (minimum(sol[1:1]) > 1e-2) && (@test abs.(sol[1] - p[1] / p[2]) < 1e-8) - (minimum(sol[2:2]) > 1e-2) && - (@test abs.(sol[2]^3 / factorial(3) - p[3] / p[4]) < 1e-8) - (minimum(sol[3:4]) > 1e-2) && (@test abs.(sol[3] * sol[4] - p[5] / p[6]) < 1e-8) - end -end - -# These are disabled due to problem in SteadyStateProblem solution exactness. We do not recommend this method for finding steady states. - -# steady_state_network_2 = @reaction_network begin -# v / 10 + hill(X, v, K, n), ∅ → X -# d, X → ∅ -# end - -# for factor in [1e-1, 1e1, 1e1], repeat in 1:3 -# u0_small = factor * rand(rng, length(get_states(steady_state_network_2))) / 100 -# u0_large = factor * rand(rng, length(get_states(steady_state_network_2))) * 100 -# p = factor * rand(rng, length(get_ps(steady_state_network_2))) -# p[3] = round(p[3]) + 1 -# sol1 = solve(SteadyStateProblem(steady_state_network_2, u0_small, p), SSRootfind()).u[1] -# sol2 = solve(SteadyStateProblem(steady_state_network_2, u0_large, p), SSRootfind()).u[1] -# diff1 = abs(p[1] / 10 + p[1] * (sol1^p[3]) / (sol1^p[3] + p[2]^p[3]) - p[4] * sol1) -# diff2 = abs(p[1] / 10 + p[1] * (sol2^p[3]) / (sol2^p[3] + p[2]^p[3]) - p[4] * sol2) -# @test (diff1 < 1e-8) || (diff2 < 1e-8) -# end - -### For a couple of networks, test that the steady state solution is identical to the long term ODE solution. ### - -# steady_state_test_networks = [ -# reaction_networks_standard[8], -# reaction_networks_standard[10], -# reaction_networks_weird[1], -# ] -# for network in steady_state_test_networks, factor in [1e-1, 1e0, 1e1] -# u0 = factor * rand(rng, length(get_states(network))) -# p = factor * rand(rng, length(get_ps(network))) -# sol_ode = solve(ODEProblem(network, u0, (0.0, 1000000), p), Rosenbrock23()) -# sol_ss = solve(SteadyStateProblem(network, u0, p), SSRootfind()) -# -# @test all(abs.(sol_ode.u[end] .- sol_ss.u) .< 1e-4) -# end - -### No parameter test ### - -# no_param_network = @reaction_network begin (0.6, 3.2), ∅ ↔ X end -# for factor in [1e0, 1e1, 1e2] -# u0 = factor * rand(rng, length(get_states(no_param_network))) -# sol_ss = solve(SteadyStateProblem(no_param_network, u0), SSRootfind(), abstol = 1e-11) -# @test abs.(sol_ss.u[1] - 0.6 / 3.2) < 1e-8 -# end diff --git a/test/runtests.jl b/test/runtests.jl index e41d70fec7..0685c2fc81 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -39,7 +39,6 @@ using SafeTestsets @time @safetestset "U0 and Parameters Input Variants" begin include("model_simulation/u0_n_parameter_inputs.jl") end @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 ### Tests Spatial Network Simulations. ### @time @safetestset "PDE Systems Simulations" begin include("spatial_reaction_systems/simulate_PDEs.jl") end