|
| 1 | +### JumpProblem ### |
| 2 | + |
| 3 | +# Builds a spatial DiscreteProblem from a Lattice Reaction System. |
| 4 | +function DiffEqBase.DiscreteProblem(lrs::LatticeReactionSystem, u0_in, tspan, p_in = DiffEqBase.NullParameters(), args...; kwargs...) |
| 5 | + if !is_transport_system(lrs) |
| 6 | + error("Currently lattice Jump simulations only supported when all spatial reactions are transport reactions.") |
| 7 | + end |
| 8 | + |
| 9 | + # Converts potential symmaps to varmaps |
| 10 | + # Vertex and edge parameters may be given in a tuple, or in a common vector, making parameter case complicated. |
| 11 | + u0_in = symmap_to_varmap(lrs, u0_in) |
| 12 | + p_in = (p_in isa Tuple{<:Any,<:Any}) ? |
| 13 | + (symmap_to_varmap(lrs, p_in[1]),symmap_to_varmap(lrs, p_in[2])) : |
| 14 | + symmap_to_varmap(lrs, p_in) |
| 15 | + |
| 16 | + # Converts u0 and p to their internal forms. |
| 17 | + # u0 is [spec 1 at vert 1, spec 2 at vert 1, ..., spec 1 at vert 2, ...]. |
| 18 | + u0 = lattice_process_u0(u0_in, species(lrs), lrs.num_verts) |
| 19 | + # Both vert_ps and edge_ps becomes vectors of vectors. Each have 1 element for each parameter. |
| 20 | + # These elements are length 1 vectors (if the parameter is uniform), |
| 21 | + # or length num_verts/nE, with unique values for each vertex/edge (for vert_ps/edge_ps, respectively). |
| 22 | + vert_ps, edge_ps = lattice_process_p(p_in, vertex_parameters(lrs), edge_parameters(lrs), lrs) |
| 23 | + |
| 24 | + # Returns a DiscreteProblem. |
| 25 | + # Previously, a Tuple was used for (vert_ps, edge_ps), but this was converted to a Vector internally. |
| 26 | + return DiscreteProblem(u0, tspan, [vert_ps, edge_ps], args...; kwargs...) |
| 27 | +end |
| 28 | + |
| 29 | +# Builds a spatial JumpProblem from a DiscreteProblem containg a Lattice Reaction System. |
| 30 | +function JumpProcesses.JumpProblem(lrs::LatticeReactionSystem, dprob, aggregator, args...; name = nameof(lrs.rs), |
| 31 | + combinatoric_ratelaws = get_combinatoric_ratelaws(lrs.rs), kwargs...) |
| 32 | + # Error checks. |
| 33 | + if !isnothing(dprob.f.sys) |
| 34 | + error("Unexpected `DiscreteProblem` passed into `JumpProblem`. Was a `LatticeReactionSystem` used as input to the initial `DiscreteProblem`?") |
| 35 | + end |
| 36 | + |
| 37 | + # Computes hopping constants and mass action jumps (requires some internal juggling). |
| 38 | + # Currently, JumpProcesses requires uniform vertex parameters (hence `p=first.(dprob.p[1])`). |
| 39 | + # Currently, the resulting JumpProblem does not depend on parameters (no way to incorporate these). |
| 40 | + # Hence the parameters of this one does nto actually matter. If at some point JumpProcess can |
| 41 | + # handle parameters this can be updated and improved. |
| 42 | + # The non-spatial DiscreteProblem have a u0 matrix with entries for all combinations of species and vertexes. |
| 43 | + hopping_constants = make_hopping_constants(dprob, lrs) |
| 44 | + sma_jumps = make_spatial_majumps(dprob, lrs) |
| 45 | + non_spat_dprob = DiscreteProblem(reshape(dprob.u0, lrs.num_species, lrs.num_verts), dprob.tspan, first.(dprob.p[1])) |
| 46 | + |
| 47 | + return JumpProblem(non_spat_dprob, aggregator, sma_jumps; |
| 48 | + hopping_constants, spatial_system = lrs.lattice, name, kwargs...) |
| 49 | +end |
| 50 | + |
| 51 | +# Creates the hopping constants from a discrete problem and a lattice reaction system. |
| 52 | +function make_hopping_constants(dprob::DiscreteProblem, lrs::LatticeReactionSystem) |
| 53 | + # Creates the all_diff_rates vector, containing for each species, its transport rate across all edges. |
| 54 | + # If transport rate is uniform for one species, the vector have a single element, else one for each edge. |
| 55 | + spatial_rates_dict = Dict(compute_all_transport_rates(dprob.p[1], dprob.p[2], lrs)) |
| 56 | + all_diff_rates = [haskey(spatial_rates_dict, s) ? spatial_rates_dict[s] : [0.0] for s in species(lrs)] |
| 57 | + |
| 58 | + # Creates the hopping constant Matrix. It contains one element for each combination of species and vertex. |
| 59 | + # Each element is a Vector, containing the outgoing hopping rates for that species, from that vertex, on that edge. |
| 60 | + hopping_constants = [Vector{Float64}(undef, length(lrs.lattice.fadjlist[j])) |
| 61 | + for i in 1:(lrs.num_species), j in 1:(lrs.num_verts)] |
| 62 | + |
| 63 | + # For each edge, finds each position in `hopping_constants`. |
| 64 | + for (e_idx, e) in enumerate(edges(lrs.lattice)) |
| 65 | + dst_idx = findfirst(isequal(e.dst), lrs.lattice.fadjlist[e.src]) |
| 66 | + # For each species, sets that hopping rate. |
| 67 | + for s_idx in 1:(lrs.num_species) |
| 68 | + hopping_constants[s_idx, e.src][dst_idx] = get_component_value(all_diff_rates[s_idx], e_idx) |
| 69 | + end |
| 70 | + end |
| 71 | + |
| 72 | + return hopping_constants |
| 73 | +end |
| 74 | + |
| 75 | +# Creates a SpatialMassActionJump struct from a (spatial) DiscreteProblem and a LatticeReactionSystem. |
| 76 | +# Could implementation a version which, if all reaction's rates are uniform, returns a MassActionJump. |
| 77 | +# Not sure if there is any form of performance improvement from that though. Possibly is not the case. |
| 78 | +function make_spatial_majumps(dprob, lrs::LatticeReactionSystem) |
| 79 | + # Creates a vector, storing which reactions have spatial components. |
| 80 | + is_spatials = [Catalyst.has_spatial_vertex_component(rx.rate, lrs; vert_ps = dprob.p[1]) for rx in reactions(lrs.rs)] |
| 81 | + |
| 82 | + # Creates templates for the rates (uniform and spatial) and the stoichiometries. |
| 83 | + # We cannot fetch reactant_stoich and net_stoich from a (non-spatial) MassActionJump. |
| 84 | + # The reason is that we need to re-order the reactions so that uniform appears first, and spatial next. |
| 85 | + u_rates = Vector{Float64}(undef, length(reactions(lrs.rs)) - count(is_spatials)) |
| 86 | + s_rates = Matrix{Float64}(undef, count(is_spatials), lrs.num_verts) |
| 87 | + reactant_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) |
| 88 | + net_stoich = Vector{Vector{Pair{Int64, Int64}}}(undef, length(reactions(lrs.rs))) |
| 89 | + |
| 90 | + # Loops through reactions with non-spatial rates, computes their rates and stoichiometries. |
| 91 | + cur_rx = 1; |
| 92 | + for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) |
| 93 | + is_spat && continue |
| 94 | + u_rates[cur_rx] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1])[1] |
| 95 | + substoich_map = Pair.(rx.substrates, rx.substoich) |
| 96 | + reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) |
| 97 | + net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) |
| 98 | + cur_rx += 1 |
| 99 | + end |
| 100 | + # Loops through reactions with spatial rates, computes their rates and stoichiometries. |
| 101 | + for (is_spat, rx) in zip(is_spatials, reactions(lrs.rs)) |
| 102 | + is_spat || continue |
| 103 | + s_rates[cur_rx-length(u_rates),:] = compute_vertex_value(rx.rate, lrs; vert_ps = dprob.p[1]) |
| 104 | + substoich_map = Pair.(rx.substrates, rx.substoich) |
| 105 | + reactant_stoich[cur_rx] = int_map(substoich_map, lrs.rs) |
| 106 | + net_stoich[cur_rx] = int_map(rx.netstoich, lrs.rs) |
| 107 | + cur_rx += 1 |
| 108 | + end |
| 109 | + # SpatialMassActionJump expects empty rate containers to be nothing. |
| 110 | + isempty(u_rates) && (u_rates = nothing) |
| 111 | + (count(is_spatials)==0) && (s_rates = nothing) |
| 112 | + |
| 113 | + return SpatialMassActionJump(u_rates, s_rates, reactant_stoich, net_stoich) |
| 114 | +end |
| 115 | + |
| 116 | +### Extra ### |
| 117 | + |
| 118 | +# Temporary. Awaiting implementation in SII, or proper implementation withinCatalyst (with more general functionality). |
| 119 | +function int_map(map_in, sys) where {T,S} |
| 120 | + return [ModelingToolkit.variable_index(sys, pair[1]) => pair[2] for pair in map_in] |
| 121 | +end |
| 122 | + |
| 123 | +# Currently unused. If we want to create certain types of MassActionJumps (instead of SpatialMassActionJumps) we can take this one back. |
| 124 | +# Creates the (non-spatial) mass action jumps from a (non-spatial) DiscreteProblem (and its Reaction System of origin). |
| 125 | +# function make_majumps(non_spat_dprob, rs::ReactionSystem) |
| 126 | +# # Computes various required inputs for assembling the mass action jumps. |
| 127 | +# js = convert(JumpSystem, rs) |
| 128 | +# statetoid = Dict(ModelingToolkit.value(state) => i for (i, state) in enumerate(states(rs))) |
| 129 | +# eqs = equations(js) |
| 130 | +# invttype = non_spat_dprob.tspan[1] === nothing ? Float64 : typeof(1 / non_spat_dprob.tspan[2]) |
| 131 | +# |
| 132 | +# # Assembles the non-spatial mass action jumps. |
| 133 | +# p = (non_spat_dprob.p isa DiffEqBase.NullParameters || non_spat_dprob.p === nothing) ? Num[] : non_spat_dprob.p |
| 134 | +# majpmapper = ModelingToolkit.JumpSysMajParamMapper(js, p; jseqs = eqs, rateconsttype = invttype) |
| 135 | +# return ModelingToolkit.assemble_maj(eqs.x[1], statetoid, majpmapper) |
| 136 | +# end |
0 commit comments