47
47
48
48
# ## `@reaction_network` and `@network_component` Macros ###
49
49
50
+ """ @reaction_network
51
+
52
+ Macro for generating chemical reaction network models (Catalyst `ReactionSystem`s). See the
53
+ [Catalyst documentation](https://catalyst.sciml.ai) for more details on the domain specific
54
+ language (DSL) that the macro implements, and for how `ReactionSystem`s can be used to generate
55
+ and simulate mathematical models of chemical systems.
56
+
57
+ Returns:
58
+ - A Catalyst `ReactionSystem`, i.e. a symbolic model for the reaction network. The returned
59
+ system is marked `complete`. To obtain a `ReactionSystem` that is not marked complete, for
60
+ example to then use in compositional modeling, see the otherwise equivalent `@network_component` macro.
61
+
62
+ Options:
63
+ - `@species S1(t) S2(t) ...`, defines a collection of species.
64
+ - `@variables V1(t) V2(t) ...`, defines non-species variables (for example, that evolve via a coupled ODE).
65
+ - ... - naming a network ...
66
+
67
+ Examples: some examples illustrating various use cases, including begin/end blocks, naming, interpolation, and mixes of the options.
68
+
69
+ """
70
+
50
71
"""
51
72
@reaction_network
52
73
53
74
Macro for generating chemical reaction network models. Outputs a [`ReactionSystem`](@ref) structure,
54
75
which stores all information of the model. Next, it can be used as input to various simulations, or
55
- other tools for model analysis. The `@reaction_network` macro is sometimes called the "Catalyst
76
+ other tools for model analysis. The `@reaction_network` macro is sometimes called the "Catalyst
56
77
DSL" (where DSL = domain-specific language), as it implements a DSL for creating chemical reaction
57
78
network models.
58
-
79
+
59
80
The `@reaction_network` macro, and the `ReactionSystem`s it generates, are central to Catalyst
60
81
and its functionality. Catalyst is described in more detail in its documentation. The
61
82
`reaction_network` DSL in particular is described in more detail [here](@ref dsl_description).
@@ -83,7 +104,7 @@ sa_loop = @reaction_network begin
83
104
d, X --> 0
84
105
end
85
106
```
86
- This model also contains production and degradation reactions, where `0` denotes that there are
107
+ This model also contains production and degradation reactions, where `0` denotes that there are
87
108
either no substrates or no products in a reaction.
88
109
89
110
Options:
101
122
```
102
123
103
124
Notes:
104
- - `ReactionSystem`s created through `@reaction_network` are considered complete (non-complete
125
+ - `ReactionSystem`s created through `@reaction_network` are considered complete (non-complete
105
126
systems can be created through the alternative `@network_component` macro).
106
127
- `ReactionSystem`s created through `@reaction_network`, by default, have a random name. Specific
107
128
names can be designated as a first argument (before `begin`, e.g. `rn = @reaction_network name begin ...`).
131
152
132
153
# Ideally, it would have been possible to combine the @reaction_network and @network_component macros.
133
154
# However, this issue: https://github.com/JuliaLang/julia/issues/37691 causes problem with interpolations
134
- # if we make the @reaction_network macro call the @network_component macro. Instead, these uses the
155
+ # if we make the @reaction_network macro call the @network_component macro. Instead, these uses the
135
156
# same input, but passes `complete = false` to `make_rs_expr`.
136
157
"""
137
158
@network_component
@@ -181,35 +202,35 @@ end
181
202
# ## Internal DSL Structures ###
182
203
183
204
# Internal structure containing information about one reactant in one reaction.
184
- struct ReactantInternal
205
+ struct DSLReactant
185
206
reactant:: Union{Symbol, Expr}
186
207
stoichiometry:: ExprValues
187
208
end
188
209
189
210
# Internal structure containing information about one Reaction. Contain all its substrates and
190
211
# products as well as its rate and potential metadata. Uses a specialized constructor.
191
- struct ReactionInternal
192
- substrates:: Vector{ReactantInternal }
193
- products:: Vector{ReactantInternal }
212
+ struct DSLReaction
213
+ substrates:: Vector{DSLReactant }
214
+ products:: Vector{DSLReactant }
194
215
rate:: ExprValues
195
216
metadata:: Expr
196
217
rxexpr:: Expr
197
218
198
- function ReactionInternal (sub_line:: ExprValues , prod_line:: ExprValues ,
219
+ function DSLReaction (sub_line:: ExprValues , prod_line:: ExprValues ,
199
220
rate:: ExprValues , metadata_line:: ExprValues )
200
- subs = recursive_find_reactants! (sub_line, 1 , Vector {ReactantInternal } (undef, 0 ))
201
- prods = recursive_find_reactants! (prod_line, 1 , Vector {ReactantInternal } (undef, 0 ))
221
+ subs = recursive_find_reactants! (sub_line, 1 , Vector {DSLReactant } (undef, 0 ))
222
+ prods = recursive_find_reactants! (prod_line, 1 , Vector {DSLReactant } (undef, 0 ))
202
223
metadata = extract_metadata (metadata_line)
203
224
new (sub, prod, rate, metadata, rx_line)
204
225
end
205
226
end
206
227
207
228
# Recursive function that loops through the reaction line and finds the reactants and their
208
- # stoichiometry. Recursion makes it able to handle weird cases like 2(X + Y + 3(Z + XY)). The
209
- # reactants are stored in the `reactants` vector. As the expression tree is parsed, the
229
+ # stoichiometry. Recursion makes it able to handle weird cases like 2(X + Y + 3(Z + XY)). The
230
+ # reactants are stored in the `reactants` vector. As the expression tree is parsed, the
210
231
# stoichiometry is updated and new reactants added.
211
232
function recursive_find_reactants! (ex:: ExprValues , mult:: ExprValues ,
212
- reactants:: Vector{ReactantInternal } )
233
+ reactants:: Vector{DSLReactant } )
213
234
# We have reached the end of the expression tree and can finalise and return the reactants.
214
235
if typeof (ex) != Expr || (ex. head == :escape ) || (ex. head == :ref )
215
236
# The final bit of the expression is not a relevant reactant, no additions are required.
@@ -219,11 +240,11 @@ function recursive_find_reactants!(ex::ExprValues, mult::ExprValues,
219
240
if any (ex == reactant. reactant for reactant in reactants)
220
241
idx = findfirst (r. reactant == ex for r in reactants)
221
242
new_mult = processmult (+ , mult, reactants[idx]. stoichiometry)
222
- reactants[idx] = ReactantInternal (ex, new_mult)
243
+ reactants[idx] = DSLReactant (ex, new_mult)
223
244
224
245
# If the expression corresponds to a new reactant, add it to the list.
225
246
else
226
- push! (reactants, ReactantInternal (ex, mult))
247
+ push! (reactants, DSLReactant (ex, mult))
227
248
end
228
249
229
250
# If we have encountered a multiplication (i.e. a stoichiometry and a set of reactants).
@@ -245,7 +266,7 @@ function recursive_find_reactants!(ex::ExprValues, mult::ExprValues,
245
266
else
246
267
throw (" Malformed reaction, bad operator: $(ex. args[1 ]) found in stoichiometry expression $ex ." )
247
268
end
248
- return reactants
269
+ reactants
249
270
end
250
271
251
272
# Helper function for updating the multiplicity throughout recursion (handles e.g. parametric
@@ -273,13 +294,12 @@ function extract_metadata(metadata_line::Expr)
273
294
return metadata
274
295
end
275
296
276
-
277
-
297
+ # ## Specialised Error for @require_declaration Option ###
278
298
struct UndeclaredSymbolicError <: Exception
279
299
msg:: String
280
300
end
281
301
282
- function Base. showerror (io:: IO , err:: UndeclaredSymbolicError )
302
+ function Base. showerror (io:: IO , err:: UndeclaredSymbolicError )
283
303
print (io, " UndeclaredSymbolicError: " )
284
304
print (io, err. msg)
285
305
end
@@ -302,7 +322,7 @@ function make_reaction_system(ex::Expr, name)
302
322
end
303
323
options = Dict (Symbol (String (arg. args[1 ])[2 : end ]) => arg for arg in option_lines)
304
324
305
- # Reads options (round 1, options which must be read before the reactions, e.g. because
325
+ # Reads options (round 1, options which must be read before the reactions, e.g. because
306
326
# they might declare parameters/species/variables).
307
327
compound_expr_init, compound_species = read_compound_options (options)
308
328
species_declared = [extract_syms (options, :species ); compound_species]
@@ -334,7 +354,7 @@ function make_reaction_system(ex::Expr, name)
334
354
combinatoric_ratelaws = read_combinatoric_ratelaws_option (options)
335
355
336
356
# Checks for input errors.
337
- if (sum (length .( [reaction_lines, option_lines]) ) != length (ex. args))
357
+ if (sum (length, [reaction_lines, option_lines]) != length (ex. args))
338
358
error (" @reaction_network input contain $(length (ex. args) - sum (length .([reaction_lines,option_lines]))) malformed lines." )
339
359
end
340
360
if any (! in (opt_in, option_keys) for opt_in in keys (options))
387
407
# Generates a vector of reaction structures, each containing the information about one reaction.
388
408
function get_reactions (exprs:: Vector{Expr} )
389
409
# Declares an array to which we add all found reactions.
390
- reactions = Vector {ReactionInternal } (undef, 0 )
410
+ reactions = Vector {DSLReaction } (undef, 0 )
391
411
392
412
# Loops through each line of reactions. Extracts and adds each lines's reactions to `reactions`.
393
413
for line in exprs
@@ -396,9 +416,11 @@ function get_reactions(exprs::Vector{Expr})
396
416
397
417
# Checks which type of line is used, and calls `push_reactions!` on the processed line.
398
418
if in (arrow, double_arrows)
399
- if typeof (rate) != Expr || rate. head != :tuple
419
+ ( typeof (rate) != Expr || rate. head != :tuple ) &&
400
420
error (" Error: Must provide a tuple of reaction rates when declaring a bi-directional reaction." )
401
- end
421
+ (typeof (metadata) != Expr || metadata. head != :tuple ) &&
422
+ error (" Error: Must provide a tuple of reaction metadata when declaring a bi-directional reaction." )
423
+
402
424
push_reactions! (reactions, reaction. args[2 ], reaction. args[3 ],
403
425
rate. args[1 ], metadata. args[1 ], arrow, line)
404
426
push_reactions! (reactions, reaction. args[3 ], reaction. args[2 ],
418
440
419
441
# Extracts the rate, reaction, and metadata fields (the last one optional) from a reaction line.
420
442
function read_reaction_line (line:: Expr )
421
- # Handles rate, reaction, and arrow. Special routine required for the`-->` case, which
443
+ # Handles rate, reaction, and arrow. Special routine required for the`-->` case, which
422
444
# creates an expression different what the other arrows creates.
423
445
rate = line. args[1 ]
424
446
reaction = line. args[2 ]
@@ -429,7 +451,7 @@ function read_reaction_line(line::Expr)
429
451
430
452
# Handles metadata. If not provided, empty metadata is created.
431
453
if length (line. args) == 2
432
- metadata = in (arrow, double_arrows) ? :(([], [])) : :([])
454
+ in (arrow, double_arrows) ? :(([], [])) : :([])
433
455
elseif length (line. args) == 3
434
456
metadata = line. args[3 ]
435
457
else
441
463
442
464
# Takes a reaction line and creates reaction(s) from it and pushes those to the reaction vector.
443
465
# Used to create multiple reactions from bundled reactions (like `k, (X,Y) --> 0`).
444
- function push_reactions! (reactions:: Vector{ReactionInternal } , subs:: ExprValues ,
445
- prods:: ExprValues , rate:: ExprValues , metadata:: ExprValues , arrow:: Symbol )
466
+ function push_reactions! (reactions:: Vector{DSLReaction } , subs:: ExprValues ,
467
+ prods:: ExprValues , rate:: ExprValues , metadata:: ExprValues , arrow:: Symbol , line :: Expr )
446
468
# The rates, substrates, products, and metadata may be in a tuple form (e.g. `k, (X,Y) --> 0`).
447
469
# This finds these tuples' lengths (or 1 for non-tuple forms). Inconsistent lengths yield error.
448
470
lengs = (tup_leng (subs), tup_leng (prods), tup_leng (rate), tup_leng (metadata))
@@ -451,8 +473,8 @@ function push_reactions!(reactions::Vector{ReactionInternal}, subs::ExprValues,
451
473
throw (" Malformed reaction, rate=$rate , subs=$subs , prods=$prods , metadata=$metadata ." )
452
474
end
453
475
454
- # Loops through each reaction encoded by the reaction's different components.
455
- # Creates a `ReactionInternal ` representation and adds it to `reactions`.
476
+ # Loops through each reaction encoded by the reaction's different components.
477
+ # Creates a `DSLReaction ` representation and adds it to `reactions`.
456
478
for i in 1 : maxl
457
479
# If the `only_use_rate` metadata was not provided, this must be inferred from the arrow.
458
480
metadata_i = get_tup_arg (metadata, i)
@@ -461,8 +483,8 @@ function push_reactions!(reactions::Vector{ReactionInternal}, subs::ExprValues,
461
483
end
462
484
463
485
# Extracts substrates, products, and rates for the i'th reaction.
464
- subs_i, prods_i, rate_i = get_tup_arg .([ subs, prods, rate] , i)
465
- push! (reactions, ReactionInternal (subs_i, prods_i, rate_i, metadata_i))
486
+ subs_i, prods_i, rate_i = get_tup_arg .(( subs, prods, rate) , i)
487
+ push! (reactions, DSLReaction (subs_i, prods_i, rate_i, metadata_i))
466
488
end
467
489
end
468
490
515
537
# Function called by extract_species_and_parameters, recursively loops through an
516
538
# expression and find symbols (adding them to the push_symbols vector).
517
539
function add_syms_from_expr! (push_symbols:: AbstractSet , expr:: ExprValues , excluded_syms)
518
- # If we have encountered a Symbol in the recursion, we can try extracting it.
540
+ # If we have encountered a Symbol in the recursion, we can try extracting it.
519
541
if expr isa Symbol
520
542
if ! (expr in forbidden_symbols_skip) && ! (expr in excluded_syms)
521
543
push! (push_symbols, expr)
530
552
531
553
# ## DSL Output Expression Builders ###
532
554
533
- # Given the extracted species (or variables) and the option dictionary, creates the
555
+ # Given the extracted species (or variables) and the option dictionary, creates the
534
556
# `@species ...` (or `@variables ..`) expression which would declare these.
535
557
# If `key = :variables`, does this for variables (and not species).
536
558
function get_usexpr (us_extracted, options, key = :species ; ivs = (DEFAULT_IV_SYM,))
@@ -584,7 +606,7 @@ function scalarize_macro(expr_init, name)
584
606
return expr, namesym
585
607
end
586
608
587
- # From the system reactions (as `ReactionInternal `s) and equations (as expressions),
609
+ # From the system reactions (as `DSLReaction `s) and equations (as expressions),
588
610
# creates the expressions that evalutes to the reaction (+ equations) vector.
589
611
function make_rxsexprs (reactions, equations)
590
612
rxsexprs = :(Catalyst. CatalystEqType[])
@@ -593,9 +615,9 @@ function make_rxsexprs(reactions, equations)
593
615
return rxsexprs
594
616
end
595
617
596
- # From a `ReactionInternal ` struct, creates the expression which evaluates to the creation
618
+ # From a `DSLReaction ` struct, creates the expression which evaluates to the creation
597
619
# of the correponding reaction.
598
- function get_rxexpr (rx:: ReactionInternal )
620
+ function get_rxexpr (rx:: DSLReaction )
599
621
# Initiates the `Reaction` expression.
600
622
rate = recursive_expand_functions! (rx. rate)
601
623
rx_constructor = :(Reaction ($ rate, [], [], [], []; metadata = $ (rx. metadata)))
639
661
# more generic to account for other default reaction metadata. Practically, this will likely
640
662
# be the only relevant reaction metadata to have a default value via the DSL. If another becomes
641
663
# relevant, the code can be rewritten to take this into account.
642
- # Checks if the `@default_noise_scaling` option is used. If so, uses it as the default value of
664
+ # Checks if the `@default_noise_scaling` option is used. If so, uses it as the default value of
643
665
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
644
666
function read_default_noise_scaling_option (options)
645
667
if haskey (options, :default_noise_scaling )
@@ -803,7 +825,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted; req
803
825
throw (UndeclaredSymbolicError (
804
826
" An undeclared variable ($obs_name ) was declared as an observable in the following observable equation: \" $obs_eq \" . Since the flag @require_declaration is set, all variables must be declared with the @species, @parameters, or @variables macros." ))
805
827
end
806
- isempty (ivs) ||
828
+ if ! isempty (ivs)
807
829
error (" An observable ($obs_name ) was given independent variable(s). These should not be given, as they are inferred automatically." )
808
830
end
809
831
if ! isnothing (defaults)
@@ -813,8 +835,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted; req
813
835
error (" A forbidden symbol ($(obs_eq. args[2 ]) ) was used as an observable name." )
814
836
end
815
837
if (obs_name in species_n_vars_declared) && is_escaped_expr (obs_eq. args[2 ])
816
- println (" HERE" )
817
- error (" An interpolated observable have been used, which has also been explicitly declared within the system using either @species or @variables. This is not permitted." )
838
+ error (" An interpolated observable have been used, which has also been ereqxplicitly declared within the system using either @species or @variables. This is not permitted." )
818
839
end
819
840
if ((obs_name in species_n_vars_declared) || is_escaped_expr (obs_eq. args[2 ])) &&
820
841
! isnothing (metadata)
@@ -958,7 +979,7 @@ function make_reaction(ex::Expr)
958
979
end
959
980
end
960
981
961
- # Reads a single line and creates the corresponding ReactionInternal .
982
+ # Reads a single line and creates the corresponding DSLReaction .
962
983
function get_reaction (line)
963
984
reaction = get_reactions ([line])
964
985
if (length (reaction) != 1 )
0 commit comments