Skip to content

Commit 50f8f82

Browse files
committed
save progress
1 parent fba3f5e commit 50f8f82

File tree

2 files changed

+68
-47
lines changed

2 files changed

+68
-47
lines changed

src/chemistry_functionality.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ function make_compound(expr)
8686
# Cannot extract directly using e.g. "getfield.(composition, :reactant)" because then
8787
# we get something like :([:C, :O]), rather than :([C, O]).
8888
composition = Catalyst.recursive_find_reactants!(expr.args[3], 1,
89-
Vector{ReactantInternal}(undef, 0))
90-
components = :([]) # Becomes something like :([C, O]).
91-
coefficients = :([]) # Becomes something like :([1, 2]).
89+
Vector{DSLReactant}(undef, 0))
90+
components = :([]) # Becomes something like :([C, O]).
91+
coefficients = :([]) # Becomes something like :([1, 2]).
9292
for comp in composition
9393
push!(components.args, comp.reactant)
9494
push!(coefficients.args, comp.stoichiometry)

src/dsl.jl

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,36 @@ end
4747

4848
### `@reaction_network` and `@network_component` Macros ###
4949

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+
5071
"""
5172
@reaction_network
5273
5374
Macro for generating chemical reaction network models. Outputs a [`ReactionSystem`](@ref) structure,
5475
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
5677
DSL" (where DSL = domain-specific language), as it implements a DSL for creating chemical reaction
5778
network models.
58-
79+
5980
The `@reaction_network` macro, and the `ReactionSystem`s it generates, are central to Catalyst
6081
and its functionality. Catalyst is described in more detail in its documentation. The
6182
`reaction_network` DSL in particular is described in more detail [here](@ref dsl_description).
@@ -83,7 +104,7 @@ sa_loop = @reaction_network begin
83104
d, X --> 0
84105
end
85106
```
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
87108
either no substrates or no products in a reaction.
88109
89110
Options:
@@ -101,7 +122,7 @@ end
101122
```
102123
103124
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
105126
systems can be created through the alternative `@network_component` macro).
106127
- `ReactionSystem`s created through `@reaction_network`, by default, have a random name. Specific
107128
names can be designated as a first argument (before `begin`, e.g. `rn = @reaction_network name begin ...`).
@@ -131,7 +152,7 @@ end
131152

132153
# Ideally, it would have been possible to combine the @reaction_network and @network_component macros.
133154
# 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
135156
# same input, but passes `complete = false` to `make_rs_expr`.
136157
"""
137158
@network_component
@@ -181,35 +202,35 @@ end
181202
### Internal DSL Structures ###
182203

183204
# Internal structure containing information about one reactant in one reaction.
184-
struct ReactantInternal
205+
struct DSLReactant
185206
reactant::Union{Symbol, Expr}
186207
stoichiometry::ExprValues
187208
end
188209

189210
# Internal structure containing information about one Reaction. Contain all its substrates and
190211
# 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}
194215
rate::ExprValues
195216
metadata::Expr
196217
rxexpr::Expr
197218

198-
function ReactionInternal(sub_line::ExprValues, prod_line::ExprValues,
219+
function DSLReaction(sub_line::ExprValues, prod_line::ExprValues,
199220
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))
202223
metadata = extract_metadata(metadata_line)
203224
new(sub, prod, rate, metadata, rx_line)
204225
end
205226
end
206227

207228
# 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
210231
# stoichiometry is updated and new reactants added.
211232
function recursive_find_reactants!(ex::ExprValues, mult::ExprValues,
212-
reactants::Vector{ReactantInternal})
233+
reactants::Vector{DSLReactant})
213234
# We have reached the end of the expression tree and can finalise and return the reactants.
214235
if typeof(ex) != Expr || (ex.head == :escape) || (ex.head == :ref)
215236
# 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,
219240
if any(ex == reactant.reactant for reactant in reactants)
220241
idx = findfirst(r.reactant == ex for r in reactants)
221242
new_mult = processmult(+, mult, reactants[idx].stoichiometry)
222-
reactants[idx] = ReactantInternal(ex, new_mult)
243+
reactants[idx] = DSLReactant(ex, new_mult)
223244

224245
# If the expression corresponds to a new reactant, add it to the list.
225246
else
226-
push!(reactants, ReactantInternal(ex, mult))
247+
push!(reactants, DSLReactant(ex, mult))
227248
end
228249

229250
# 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,
245266
else
246267
throw("Malformed reaction, bad operator: $(ex.args[1]) found in stoichiometry expression $ex.")
247268
end
248-
return reactants
269+
reactants
249270
end
250271

251272
# Helper function for updating the multiplicity throughout recursion (handles e.g. parametric
@@ -273,13 +294,12 @@ function extract_metadata(metadata_line::Expr)
273294
return metadata
274295
end
275296

276-
277-
297+
### Specialised Error for @require_declaration Option ###
278298
struct UndeclaredSymbolicError <: Exception
279299
msg::String
280300
end
281301

282-
function Base.showerror(io::IO, err::UndeclaredSymbolicError)
302+
function Base.showerror(io::IO, err::UndeclaredSymbolicError)
283303
print(io, "UndeclaredSymbolicError: ")
284304
print(io, err.msg)
285305
end
@@ -302,7 +322,7 @@ function make_reaction_system(ex::Expr, name)
302322
end
303323
options = Dict(Symbol(String(arg.args[1])[2:end]) => arg for arg in option_lines)
304324

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
306326
# they might declare parameters/species/variables).
307327
compound_expr_init, compound_species = read_compound_options(options)
308328
species_declared = [extract_syms(options, :species); compound_species]
@@ -334,7 +354,7 @@ function make_reaction_system(ex::Expr, name)
334354
combinatoric_ratelaws = read_combinatoric_ratelaws_option(options)
335355

336356
# 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))
338358
error("@reaction_network input contain $(length(ex.args) - sum(length.([reaction_lines,option_lines]))) malformed lines.")
339359
end
340360
if any(!in(opt_in, option_keys) for opt_in in keys(options))
@@ -387,7 +407,7 @@ end
387407
# Generates a vector of reaction structures, each containing the information about one reaction.
388408
function get_reactions(exprs::Vector{Expr})
389409
# Declares an array to which we add all found reactions.
390-
reactions = Vector{ReactionInternal}(undef, 0)
410+
reactions = Vector{DSLReaction}(undef, 0)
391411

392412
# Loops through each line of reactions. Extracts and adds each lines's reactions to `reactions`.
393413
for line in exprs
@@ -396,9 +416,11 @@ function get_reactions(exprs::Vector{Expr})
396416

397417
# Checks which type of line is used, and calls `push_reactions!` on the processed line.
398418
if in(arrow, double_arrows)
399-
if typeof(rate) != Expr || rate.head != :tuple
419+
(typeof(rate) != Expr || rate.head != :tuple) &&
400420
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+
402424
push_reactions!(reactions, reaction.args[2], reaction.args[3],
403425
rate.args[1], metadata.args[1], arrow, line)
404426
push_reactions!(reactions, reaction.args[3], reaction.args[2],
@@ -418,7 +440,7 @@ end
418440

419441
# Extracts the rate, reaction, and metadata fields (the last one optional) from a reaction line.
420442
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
422444
# creates an expression different what the other arrows creates.
423445
rate = line.args[1]
424446
reaction = line.args[2]
@@ -429,7 +451,7 @@ function read_reaction_line(line::Expr)
429451

430452
# Handles metadata. If not provided, empty metadata is created.
431453
if length(line.args) == 2
432-
metadata = in(arrow, double_arrows) ? :(([], [])) : :([])
454+
in(arrow, double_arrows) ? :(([], [])) : :([])
433455
elseif length(line.args) == 3
434456
metadata = line.args[3]
435457
else
@@ -441,8 +463,8 @@ end
441463

442464
# Takes a reaction line and creates reaction(s) from it and pushes those to the reaction vector.
443465
# 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)
446468
# The rates, substrates, products, and metadata may be in a tuple form (e.g. `k, (X,Y) --> 0`).
447469
# This finds these tuples' lengths (or 1 for non-tuple forms). Inconsistent lengths yield error.
448470
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,
451473
throw("Malformed reaction, rate=$rate, subs=$subs, prods=$prods, metadata=$metadata.")
452474
end
453475

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`.
456478
for i in 1:maxl
457479
# If the `only_use_rate` metadata was not provided, this must be inferred from the arrow.
458480
metadata_i = get_tup_arg(metadata, i)
@@ -461,8 +483,8 @@ function push_reactions!(reactions::Vector{ReactionInternal}, subs::ExprValues,
461483
end
462484

463485
# 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))
466488
end
467489
end
468490

@@ -515,7 +537,7 @@ end
515537
# Function called by extract_species_and_parameters, recursively loops through an
516538
# expression and find symbols (adding them to the push_symbols vector).
517539
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.
519541
if expr isa Symbol
520542
if !(expr in forbidden_symbols_skip) && !(expr in excluded_syms)
521543
push!(push_symbols, expr)
@@ -530,7 +552,7 @@ end
530552

531553
### DSL Output Expression Builders ###
532554

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
534556
# `@species ...` (or `@variables ..`) expression which would declare these.
535557
# If `key = :variables`, does this for variables (and not species).
536558
function get_usexpr(us_extracted, options, key = :species; ivs = (DEFAULT_IV_SYM,))
@@ -584,7 +606,7 @@ function scalarize_macro(expr_init, name)
584606
return expr, namesym
585607
end
586608

587-
# From the system reactions (as `ReactionInternal`s) and equations (as expressions),
609+
# From the system reactions (as `DSLReaction`s) and equations (as expressions),
588610
# creates the expressions that evalutes to the reaction (+ equations) vector.
589611
function make_rxsexprs(reactions, equations)
590612
rxsexprs = :(Catalyst.CatalystEqType[])
@@ -593,9 +615,9 @@ function make_rxsexprs(reactions, equations)
593615
return rxsexprs
594616
end
595617

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
597619
# of the correponding reaction.
598-
function get_rxexpr(rx::ReactionInternal)
620+
function get_rxexpr(rx::DSLReaction)
599621
# Initiates the `Reaction` expression.
600622
rate = recursive_expand_functions!(rx.rate)
601623
rx_constructor = :(Reaction($rate, [], [], [], []; metadata = $(rx.metadata)))
@@ -639,7 +661,7 @@ end
639661
# more generic to account for other default reaction metadata. Practically, this will likely
640662
# be the only relevant reaction metadata to have a default value via the DSL. If another becomes
641663
# 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
643665
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
644666
function read_default_noise_scaling_option(options)
645667
if haskey(options, :default_noise_scaling)
@@ -803,7 +825,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted; req
803825
throw(UndeclaredSymbolicError(
804826
"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."))
805827
end
806-
isempty(ivs) ||
828+
if !isempty(ivs)
807829
error("An observable ($obs_name) was given independent variable(s). These should not be given, as they are inferred automatically.")
808830
end
809831
if !isnothing(defaults)
@@ -813,8 +835,7 @@ function read_observed_options(options, species_n_vars_declared, ivs_sorted; req
813835
error("A forbidden symbol ($(obs_eq.args[2])) was used as an observable name.")
814836
end
815837
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.")
818839
end
819840
if ((obs_name in species_n_vars_declared) || is_escaped_expr(obs_eq.args[2])) &&
820841
!isnothing(metadata)
@@ -958,7 +979,7 @@ function make_reaction(ex::Expr)
958979
end
959980
end
960981

961-
# Reads a single line and creates the corresponding ReactionInternal.
982+
# Reads a single line and creates the corresponding DSLReaction.
962983
function get_reaction(line)
963984
reaction = get_reactions([line])
964985
if (length(reaction) != 1)

0 commit comments

Comments
 (0)