Skip to content

Commit f977ec9

Browse files
committed
init
1 parent 8cbc581 commit f977ec9

File tree

9 files changed

+342
-60
lines changed

9 files changed

+342
-60
lines changed

docs/src/api/catalyst_api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ substoichmat
178178
prodstoichmat
179179
netstoichmat
180180
reactionrates
181+
get_metadata_dict
182+
has_metadata
183+
get_metadata
181184
```
182185

183186
## [Functions to extend or modify a network](@id api_network_extension_and_modification)

src/Catalyst.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export ODEProblem,
6767
SteadyStateProblem
6868

6969
# reaction_network macro
70-
const ExprValues = Union{Expr, Symbol, Float64, Int}
70+
const ExprValues = Union{Expr, Symbol, Float64, Int, Bool}
7171
include("expression_utils.jl")
7272
include("reaction_network.jl")
7373
export @reaction_network, @add_reactions, @reaction, @species
@@ -80,6 +80,7 @@ export mm, mmr, hill, hillr, hillar
8080
include("networkapi.jl")
8181
export species, nonspecies, reactionparams, reactions, speciesmap, paramsmap
8282
export numspecies, numreactions, numreactionparams, setdefaults!, symmap_to_varmap
83+
export get_metadata_dict, has_metadata, get_metadata
8384
export make_empty_network, addspecies!, addparam!, addreaction!, reactionparamsmap
8485
export dependants, dependents, substoichmat, prodstoichmat, netstoichmat
8586
export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants

src/expression_utils.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ function tup_leng(ex::ExprValues)
44
return 1
55
end
66

7-
#Gets the ith element in a expression tuple, or returns the input itself if it is not an expression tuple (probably a Symbol/Numerical).
7+
# Gets the ith element in a expression tuple, or returns the input itself if it is not an expression tuple
8+
# (probably a Symbol/Numerical).
89
function get_tup_arg(ex::ExprValues, i::Int)
910
(tup_leng(ex) == 1) && (return ex)
1011
return ex.args[i]

src/reaction_network.jl

Lines changed: 87 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,17 @@ struct ReactionStruct
293293
substrates::Vector{ReactantStruct}
294294
products::Vector{ReactantStruct}
295295
rate::ExprValues
296-
only_use_rate::Bool
296+
metadata::Expr
297297

298-
function ReactionStruct(sub_line::ExprValues, prod_line::ExprValues, rate::ExprValues,
299-
only_use_rate::Bool)
298+
function ReactionStruct(sub_line::ExprValues, prod_line::ExprValues, rate::ExprValues,
299+
metadata_line::ExprValues)
300300
sub = recursive_find_reactants!(sub_line, 1, Vector{ReactantStruct}(undef, 0))
301301
prod = recursive_find_reactants!(prod_line, 1, Vector{ReactantStruct}(undef, 0))
302-
new(sub, prod, rate, only_use_rate)
302+
metadata = extract_metadata(metadata_line)
303+
new(sub, prod, rate, metadata)
303304
end
304305
end
305-
306+
306307
### Functions rephrasing the macro input as a ReactionSystem structure. ###
307308

308309
function forbidden_variable_check(v)
@@ -565,8 +566,8 @@ function get_rxexprs(rxstruct)
565566
prod_init = isempty(rxstruct.products) ? nothing : :([])
566567
prod_stoich_init = deepcopy(prod_init)
567568
reaction_func = :(Reaction($(recursive_expand_functions!(rxstruct.rate)), $subs_init,
568-
$prod_init, $subs_stoich_init, $prod_stoich_init,
569-
only_use_rate = $(rxstruct.only_use_rate)))
569+
$prod_init, $subs_stoich_init, $prod_stoich_init,
570+
metadata = Dict($(rxstruct.metadata)),))
570571
for sub in rxstruct.substrates
571572
push!(reaction_func.args[3].args, sub.reactant)
572573
push!(reaction_func.args[5].args, sub.stoichiometry)
@@ -580,70 +581,82 @@ end
580581

581582
### Functions for extracting the reactions from a DSL expression, and putting them ReactionStruct vector. ###
582583

583-
# Reads a line and creates the corresponding ReactionStruct.
584+
# Reads a single line and creates the corresponding ReactionStruct.
584585
function get_reaction(line)
585-
(rate, r_line) = line.args
586-
(r_line.head == :-->) && (r_line = Expr(:call, :, r_line.args[1], r_line.args[2]))
587-
588-
arrow = r_line.args[1]
589-
in(arrow, double_arrows) && error("Double arrows not allowed for single reactions.")
590-
591-
only_use_rate = in(arrow, pure_rate_arrows)
592-
if in(arrow, fwd_arrows)
593-
rs = create_ReactionStruct(r_line.args[2], r_line.args[3], rate, only_use_rate)
594-
elseif in(arrow, bwd_arrows)
595-
rs = create_ReactionStruct(r_line.args[3], r_line.args[2], rate, only_use_rate)
596-
else
597-
throw("Malformed reaction, invalid arrow type used in: $(MacroTools.striplines(line))")
586+
reaction = get_reactions([line])
587+
if (length(reaction) != 1)
588+
error("Malformed reaction. @reaction macro only creates a single reaction. E.g. double arrows, such as `<-->` are not supported.")
598589
end
599-
600-
rs
590+
return reaction[1]
601591
end
592+
602593
# Generates a vector containing a number of reaction structures, each containing the information about one reaction.
603594
function get_reactions(exprs::Vector{Expr}, reactions = Vector{ReactionStruct}(undef, 0))
604595
for line in exprs
605-
(rate, r_line) = line.args
606-
(r_line.head == :-->) && (r_line = Expr(:call, :, r_line.args[1], r_line.args[2]))
596+
# Reads core reaction information.
597+
arrow, rate, reaction, metadata = read_reaction_line(line)
607598

608-
arrow = r_line.args[1]
609-
only_use_rate = in(arrow, pure_rate_arrows)
599+
# Checks the type of arrow used, and creates the corresponding reaction(s). Returns them in an array.
610600
if in(arrow, double_arrows)
611-
(typeof(rate) == Expr && rate.head == :tuple) ||
601+
if typeof(rate) != Expr || rate.head != :tuple
612602
error("Error: Must provide a tuple of reaction rates when declaring a bi-directional reaction.")
613-
push_reactions!(reactions, r_line.args[2], r_line.args[3], rate.args[1],
614-
only_use_rate)
615-
push_reactions!(reactions, r_line.args[3], r_line.args[2], rate.args[2],
616-
only_use_rate)
603+
end
604+
push_reactions!(reactions, reaction.args[2], reaction.args[3], rate.args[1], metadata.args[1], arrow)
605+
push_reactions!(reactions, reaction.args[3], reaction.args[2], rate.args[2], metadata.args[2], arrow)
617606
elseif in(arrow, fwd_arrows)
618-
push_reactions!(reactions, r_line.args[2], r_line.args[3], rate, only_use_rate)
607+
push_reactions!(reactions, reaction.args[2], reaction.args[3], rate, metadata, arrow)
619608
elseif in(arrow, bwd_arrows)
620-
push_reactions!(reactions, r_line.args[3], r_line.args[2], rate, only_use_rate)
609+
push_reactions!(reactions, reaction.args[3], reaction.args[2], rate, metadata, arrow)
621610
else
622611
throw("Malformed reaction, invalid arrow type used in: $(MacroTools.striplines(line))")
623612
end
624613
end
625-
reactions
626-
end
614+
return reactions
615+
end
616+
617+
# Extracts the rate, reaction, and metadata fields (the last one optional) from a reaction line.
618+
function read_reaction_line(line::Expr)
619+
# Handles rate, reaction, and arrow.
620+
# Special routine required for the`-->` case, which creates different expression from all other cases.
621+
rate = line.args[1]
622+
reaction = line.args[2]
623+
(reaction.head == :-->) && (reaction = Expr(:call, :, reaction.args[1], reaction.args[2]))
624+
arrow = reaction.args[1]
625+
626+
# Handles metadata. If not provided, empty metadata is created.
627+
if length(line.args) == 2
628+
metadata = in(arrow, double_arrows) ? :(([], [])) : :([])
629+
elseif length(line.args) == 3
630+
metadata = line.args[3]
631+
else
632+
error("The following reaction line: \"$line\" was malformed. It should have a form \"rate, reaction\" or a form \"rate, reaction, metadata\". It has neither.")
633+
end
627634

628-
# Creates a ReactionStruct from the information in a single line.
629-
function create_ReactionStruct(sub_line::ExprValues, prod_line::ExprValues,
630-
rate::ExprValues, only_use_rate::Bool)
631-
all(==(1), (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate))) ||
632-
error("Malformed reaction, line appears to be defining multiple reactions incorrectly: rate=$rate, subs=$sub_line, prods=$prod_line.")
633-
ReactionStruct(get_tup_arg(sub_line, 1), get_tup_arg(prod_line, 1),
634-
get_tup_arg(rate, 1), only_use_rate)
635+
return arrow, rate, reaction, metadata
635636
end
636637

637-
#Takes a reaction line and creates reactions from it and pushes those to the reaction array. Used to create multiple reactions from, for instance, 1.0, (X,Y) --> 0.
638-
function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues,
639-
prod_line::ExprValues, rate::ExprValues, only_use_rate::Bool)
640-
lengs = (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate))
641-
for i in 1:maximum(lengs)
642-
(count(lengs .== 1) + count(lengs .== maximum(lengs)) < 3) &&
643-
(throw("Malformed reaction, rate=$rate, subs=$sub_line, prods=$prod_line."))
644-
push!(reactions,
645-
ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i),
646-
get_tup_arg(rate, i), only_use_rate))
638+
# Takes a reaction line and creates reaction(s) from it and pushes those to the reaction array.
639+
# Used to create multiple reactions from, for instance, `k, (X,Y) --> 0`.
640+
# Handles metadata, e.g. `1.0, Z --> 0, [noisescaling=η]`.
641+
function push_reactions!(reactions::Vector{ReactionStruct}, sub_line::ExprValues, prod_line::ExprValues,
642+
rate::ExprValues, metadata::ExprValues, arrow::Symbol)
643+
# The rates, substrates, products, and metadata may be in a tupple form (e.g. `k, (X,Y) --> 0`).
644+
# This finds the length of these tuples (or 1 if not in tuple forms). Errors if lengs inconsistent.
645+
lengs = (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate), tup_leng(metadata))
646+
if any(!(leng in [1, maximum(lengs)]) for leng in lengs)
647+
throw("Malformed reaction, rate=$rate, subs=$sub_line, prods=$prod_line, metadata=$metadata.")
648+
end
649+
650+
# Loops through each reaction encoded by the reaction composites. Adds the reaction to the reactions vector.
651+
for i in 1:maximum(lengs)
652+
# If the `only_use_rate` metadata was not provided, this has to be infered from the arrow used.
653+
metadata_i = get_tup_arg(metadata, i)
654+
if all([arg.args[1] != :only_use_rate for arg in metadata_i.args])
655+
push!(metadata_i.args, :(only_use_rate = $(in(arrow, pure_rate_arrows))))
656+
end
657+
658+
push!(reactions, ReactionStruct(get_tup_arg(sub_line, i), get_tup_arg(prod_line, i),
659+
get_tup_arg(rate, i), metadata_i))
647660
end
648661
end
649662

@@ -687,6 +700,17 @@ function recursive_find_reactants!(ex::ExprValues, mult::ExprValues,
687700
reactants
688701
end
689702

703+
# Finds the metadata from a metadata expresion (`[key=val, ...]`) and returns as a Vector{Pair{Symbol,ExprValues}}.
704+
function extract_metadata(metadata_line::Expr)
705+
metadata = :([])
706+
for arg in metadata_line.args
707+
(arg.head != :(=)) && error("Malformatted metadata line: $metadata_line. Each entry in the vector should contain a `=`.")
708+
(arg.args[1] isa Symbol) || error("Malformatted metadata entry: $arg. Entries left-hand-side should be a single symbol.")
709+
push!(metadata.args, :($(QuoteNode(arg.args[1])) => $(arg.args[2])))
710+
end
711+
return metadata
712+
end
713+
690714
### DSL Options Handling ###
691715
# Most options handled in previous sections, when code re-organised, these should ideally be moved to the same place.
692716

@@ -888,3 +912,13 @@ end
888912
# end
889913
# return defaults
890914
# end
915+
916+
917+
# # Creates a ReactionStruct from the information in a single line.
918+
# function create_ReactionStruct(sub_line::ExprValues, prod_line::ExprValues,
919+
# rate::ExprValues, only_use_rate::Bool)
920+
# all(==(1), (tup_leng(sub_line), tup_leng(prod_line), tup_leng(rate))) ||
921+
# error("Malformed reaction, line appears to be defining multiple reactions incorrectly: rate=$rate, subs=$sub_line, prods=$prod_line.")
922+
# ReactionStruct(get_tup_arg(sub_line, 1), get_tup_arg(prod_line, 1),
923+
# get_tup_arg(rate, 1), only_use_rate)
924+
# end

src/reactionsystem.jl

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Notes:
9898
- The three-argument form assumes all reactant and product stoichiometric coefficients
9999
are one.
100100
"""
101-
struct Reaction{S, T}
101+
struct Reaction{R, S, T}
102102
"""The rate function (excluding mass action terms)."""
103103
rate::Any
104104
"""Reaction substrates."""
@@ -116,6 +116,11 @@ struct Reaction{S, T}
116116
`true` if `rate` represents the full reaction rate law.
117117
"""
118118
only_use_rate::Bool
119+
"""
120+
Contain additional data, such whenever the reaction have a specific noise-scaling expression for
121+
the chemical Langevin equation.
122+
"""
123+
metadata::Dict{Symbol, R}
119124
end
120125

121126
"""
@@ -126,7 +131,8 @@ Test if a species is valid as a reactant (i.e. a species variable or a constant
126131
isvalidreactant(s) = MT.isparameter(s) ? isconstant(s) : (isspecies(s) && !isconstant(s))
127132

128133
function Reaction(rate, subs, prods, substoich, prodstoich;
129-
netstoich = nothing, only_use_rate = false,
134+
netstoich = nothing, metadata = Dict{Symbol,Any}(),
135+
only_use_rate = (haskey(metadata, :only_use_rate) && Bool(metadata[:only_use_rate])),
130136
kwargs...)
131137
(isnothing(prods) && isnothing(subs)) &&
132138
throw(ArgumentError("A reaction requires a non-nothing substrate or product vector."))
@@ -181,7 +187,9 @@ function Reaction(rate, subs, prods, substoich, prodstoich;
181187
convert.(stoich_type, netstoich) : netstoich
182188
end
183189

184-
Reaction(value(rate), subs, prods, substoich′, prodstoich′, ns, only_use_rate)
190+
haskey(metadata, :only_use_rate) && delete!(metadata, :only_use_rate)
191+
192+
Reaction(value(rate), subs, prods, substoich′, prodstoich′, ns, only_use_rate, metadata)
185193
end
186194

187195
# three argument constructor assumes stoichiometric coefs are one and integers
@@ -241,7 +249,7 @@ function ModelingToolkit.namespace_equation(rx::Reaction, name; kw...)
241249
ns = similar(rx.netstoich)
242250
map!(n -> f(n[1]) => f(n[2]), ns, rx.netstoich)
243251
end
244-
Reaction(rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate)
252+
Reaction(rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata)
245253
end
246254

247255
netstoich_stoichtype(::Vector{Pair{S, T}}) where {S, T} = T
@@ -830,6 +838,67 @@ function get_indep_sts(rs::ReactionSystem, remove_conserved = false)
830838
indepsts, filter(isspecies, indepsts)
831839
end
832840

841+
######################## Other accessors ##############################
842+
843+
"""
844+
get_metadata_dict(reaction::Reaction)
845+
846+
Retrives the `ImmutableDict` containing all of the metadata associated with a specific reaction.
847+
848+
Arguments:
849+
- `reaction`: The reaction for which we wish to retrive all metadata.
850+
851+
Example:
852+
```julia
853+
reaction = @reaction k, 0 --> X, [noise_scaling=0.0]
854+
get_metadata_dict(reaction)
855+
```
856+
"""
857+
function get_metadata_dict(reaction::Reaction)
858+
return reaction.metadata
859+
end
860+
861+
"""
862+
has_metadata(reaction::Reaction, md_key::Symbol)
863+
864+
Checks if a `Reaction` have a certain metadata field. If it does, returns `true` (else returns `false`).
865+
866+
Arguments:
867+
- `reaction`: The reaction for which we wish to check if a specific metadata field exist.
868+
- `md_key`: The metadata for which we wish to check existence of.
869+
870+
Example:
871+
```julia
872+
reaction = @reaction k, 0 --> X, [noise_scaling=0.0]
873+
has_metadata(reaction, :noise_scaling)
874+
```
875+
"""
876+
function has_metadata(reaction::Reaction, md_key::Symbol)
877+
return haskey(get_metadata_dict(reaction), md_key)
878+
end
879+
880+
"""
881+
get_metadata(reaction::Reaction, md_key::Symbol)
882+
883+
Retrives a certain metadata value from a `Reaction`. If the metadata does not exists, throws an error.
884+
885+
Arguments:
886+
- `reaction`: The reaction for which we wish to retrive a specific metadata value.
887+
- `md_key`: The metadata for which we wish to retrive.
888+
889+
Example:
890+
```julia
891+
reaction = @reaction k, 0 --> X, [noise_scaling=0.0]
892+
get_metadata(reaction, :noise_scaling)
893+
```
894+
"""
895+
function get_metadata(reaction::Reaction, md_key::Symbol)
896+
if !has_metadata(reaction, md_key)
897+
error("The reaction does not have a metadata field $md_key. It does have the following metadata fields: $(keys(get_metadata_dict(reaction))).")
898+
end
899+
return get_metadata_dict(reaction)[md_key]
900+
end
901+
833902
######################## Conversion to ODEs/SDEs/jump, etc ##############################
834903

835904
"""

src/registered_functions.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ function expand_registered_functions(expr)
134134
end
135135
# If applied to a Reaction, return a reaction with its rate modified.
136136
function expand_registered_functions(rx::Reaction)
137-
Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich, rx.prodstoich, rx.netstoich, rx.only_use_rate)
137+
Reaction(expand_registered_functions(rx.rate), rx.substrates, rx.products, rx.substoich,
138+
rx.prodstoich, rx.netstoich, rx.only_use_rate, rx.metadata)
138139
end
139140
# If applied to a Equation, returns it with it applied to lhs and rhs
140141
function expand_registered_functions(eq::Equation)

0 commit comments

Comments
 (0)