Skip to content

Commit 292da20

Browse files
committed
minor upodates after read through
1 parent 5f35480 commit 292da20

File tree

5 files changed

+174
-149
lines changed

5 files changed

+174
-149
lines changed

docs/src/model_creation/dsl_advanced.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -500,34 +500,6 @@ Catalyst.getdescription(rx)
500500

501501
A list of all available reaction metadata can be found [in the api](@ref api_rx_metadata).
502502

503-
## [Declaring individual reaction using the `@reaction` macro](@id dsl_advanced_options_reaction_macro)
504-
Catalyst exports a macro `@reaction` which can be used to generate a singular [`Reaction`](@ref) object of the same type which is stored within the [`ReactionSystem`](@ref) structure (which in turn can be generated by `@reaction_network `). In the following example, we create a simple [SIR model](@ref basic_CRN_library_sir). Next, we instead create its individual reaction components using the `@reaction` macro. Finally, we confirm that these are identical to those stored in the initial model (using the [`reactions`](@ref) function).
505-
```@example dsl_advanced_reaction_macro
506-
using Catalyst # hide
507-
sir_model = @reaction_network begin
508-
α, S + I --> 2I
509-
β, I --> R
510-
end
511-
infection_rx = @reaction α, S + I --> 2I
512-
recovery_rx = @reaction β, I --> R
513-
issetequal(reactions(sir_model), [infection_rx, recovery_rx])
514-
```
515-
516-
Here, the `@reaction` macro is followed by a single line consisting of three parts:
517-
- A rate (at which the reaction occurs).
518-
- Any number of substrates (which are consumed by the reaction).
519-
- Any number of products (which are produced by the reaction).
520-
The rules of writing and interpreting this line are [identical to those for `@reaction_network`](@ref dsl_description_reactions) (however, bi-directional reactions are not permitted).
521-
522-
Generally, the `@reaction` macro provides a more concise notation to the [`Reaction`](@ref) constructor. One of its primary uses is for [creating models programmatically](@ref programmatic_CRN_construction). When doing so, it can often be useful to use [*interpolation*](@ref dsl_advanced_options_symbolics_and_DSL_interpolation) of symbolic variables declared previously:
523-
```@example dsl_advanced_reaction_macro
524-
t = default_t()
525-
@parameters k b
526-
@species A(t)
527-
ex = k*A^2 + t
528-
rx = @reaction b*$ex*$A, $A --> C
529-
```
530-
531503
## [Working with symbolic variables and the DSL](@id dsl_advanced_options_symbolics_and_DSL)
532504
We have previously described how Catalyst represents its models symbolically (enabling e.g. symbolic differentiation of expressions stored in models). While Catalyst utilises this for many internal operation, these symbolic representations can also be accessed and harnessed by the user. Primarily, doing so is much easier during programmatic (as opposed to DSL-based) modelling. Indeed, the section on [programmatic modelling](@ref programmatic_CRN_construction) goes into more details about symbolic representation in models, and how these can be used. It is, however, also ways to utilise these methods during DSL-based modelling. Below we briefly describe two methods for doing so.
533505

@@ -602,6 +574,45 @@ nothing # hide
602574
!!! note
603575
When using interpolation, expressions like `2$spec` won't work; the multiplication symbol must be explicitly included like `2*$spec`.
604576

577+
## [Creating individual reaction using the `@reaction` macro](@id dsl_advanced_options_reaction_macro)
578+
Catalyst exports a macro `@reaction`, which can be used to generate a singular [`Reaction`](@ref) object of the same type which is stored within the [`ReactionSystem`](@ref) structure (which in turn can be generated by `@reaction_network`). Generally, `@reaction` follows [identical rules to those of `@reaction_network`](@ref (@ref dsl_description_reactions)) for writing and interpreting reactions (however, bi-directional reactions are not permitted). E.g. here we create a simple dimerisation reaction:
579+
```@example dsl_advanced_reaction_macro
580+
using Catalyst # hide
581+
rx_dimerisation = @reaction kD, 2X --> X2
582+
```
583+
Here, `@reaction` is followed by a single line consisting of three parts:
584+
- A rate (at which the reaction occurs).
585+
- Any number of substrates (which are consumed by the reaction).
586+
- Any number of products (which are produced by the reaction).
587+
588+
In the next example, we first create a simple [SIR model](@ref basic_CRN_library_sir). Next, we instead create its individual reaction components using the `@reaction` macro. Finally, we confirm that these are identical to those stored in the initial model (using the [`reactions`](@ref) function).
589+
```@example dsl_advanced_reaction_macro
590+
sir_model = @reaction_network begin
591+
α, S + I --> 2I
592+
β, I --> R
593+
end
594+
infection_rx = @reaction α, S + I --> 2I
595+
recovery_rx = @reaction β, I --> R
596+
sir_rxs = [infection_rx, recovery_rx]
597+
issetequal(reactions(sir_model), sir_rxs)
598+
```
599+
One of the primary uses of the `@reaction` macro is to provide some of the convenience of the DSL to [*programmatic modelling](@ref programmatic_CRN_construction). E.g. here we can combine our reactions to create a `ReactionSystem` directly, and also confirm that this is identical to the model created through the DSL:
600+
```@example dsl_advanced_reaction_macro
601+
sir_programmatic = complete(ReactionSystem(sir_rxs, default_t(); name = :sir))
602+
sir_programmatic == sir_model
603+
```
604+
605+
During programmatic modelling, it can be good to keep in mind that already declared symbolic variables can be [*interpolated*](@ref dsl_advanced_options_symbolics_and_DSL_interpolation). E.g. here we create two production reactions both depending on the same Michaelis-Menten function:
606+
```@example dsl_advanced_reaction_macro
607+
t = default_t()
608+
@species X(t)
609+
@parameters v K
610+
mm_term = Catalyst.mm(X, v, K)
611+
rx1 = @reaction $mm_term, 0 --> P1
612+
rx2 = @reaction $mm_term, 0 --> P2
613+
nothing # hide
614+
```
615+
605616
## [Disabling mass action for reactions](@id dsl_advanced_options_disable_ma)
606617

607618
As [described previously](@ref math_models_in_catalyst_rre_odes), Catalyst uses *mass action kinetics* to generate ODEs from reactions. Here, each reaction generates a term for each of its reactants, which consists of the reaction's rate, substrates, and the reactant's stoichiometry. E.g. the following reaction:

src/dsl.jl

Lines changed: 87 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ function make_reaction_system(ex::Expr, name)
288288
ps_declared = extract_syms(options, :parameters)
289289
vs_declared = extract_syms(options, :variables)
290290
tiv, sivs, ivs, ivsexpr = read_ivs_option(options)
291-
cmpexpr_init, cmps_declared = read_compound_options(options)
291+
cmpexpr_init, cmps_declared = read_compound_option(options)
292292
diffsexpr, diffs_declared = read_differentials_option(options)
293293
syms_declared = collect(Iterators.flatten((cmps_declared, sps_declared, ps_declared,
294294
vs_declared, ivs, diffs_declared)))
@@ -301,15 +301,15 @@ function make_reaction_system(ex::Expr, name)
301301
requiredec = haskey(options, :require_declaration)
302302
reactions = get_reactions(reaction_lines)
303303
sps_inferred, ps_pre_inferred = extract_sps_and_ps(reactions, syms_declared; requiredec)
304-
vs_inferred, diffs_inferred, equations = read_equations_options!(diffsexpr, options,
304+
vs_inferred, diffs_inferred, equations = read_equations_option!(diffsexpr, options,
305305
union(syms_declared, sps_inferred), tiv; requiredec)
306306
ps_inferred = setdiff(ps_pre_inferred, vs_inferred, diffs_inferred)
307307
syms_inferred = union(sps_inferred, ps_inferred, vs_inferred, diffs_inferred)
308308
all_syms = union(syms_declared, syms_inferred)
309+
obsexpr, obs_eqs, obs_syms = read_observed_option(options, ivs,
310+
union(sps_declared, vs_declared), all_syms; requiredec)
309311

310312
# Read options not related to the declaration or inference of symbols.
311-
obsexpr, obs_eqs, obs_syms = read_observed_options(options, ivs,
312-
union(sps_declared, vs_declared), all_syms; requiredec)
313313
continuous_events_expr = read_events_option(options, :continuous_events)
314314
discrete_events_expr = read_events_option(options, :discrete_events)
315315
default_reaction_metadata = read_default_noise_scaling_option(options)
@@ -489,7 +489,7 @@ function extract_sps_and_ps(reactions, excluded_syms; requiredec = false)
489489
end
490490
end
491491

492-
return collect(species), collect(parameters)
492+
collect(species), collect(parameters)
493493
end
494494

495495
# Function called by extract_sps_and_ps, recursively loops through an
@@ -524,7 +524,7 @@ function get_usexpr(us_extracted, options, key = :species; ivs = (DEFAULT_IV_SYM
524524
for u in us_extracted
525525
u isa Symbol && push!(usexpr.args, Expr(:call, u, ivs...))
526526
end
527-
return usexpr
527+
usexpr
528528
end
529529

530530
# Given the parameters that were extracted from the reactions, and the options dictionary,
@@ -538,7 +538,7 @@ function get_psexpr(parameters_extracted, options)
538538
:(@parameters)
539539
end
540540
foreach(p -> push!(pexprs.args, p), parameters_extracted)
541-
return pexprs
541+
pexprs
542542
end
543543

544544
# Takes a ModelingToolkit declaration macro (like @parameters ...) and return and expression:
@@ -629,24 +629,9 @@ function read_ivs_option(options)
629629
return tiv, sivs, ivs, ivsexpr
630630
end
631631

632-
# Returns the `default_reaction_metadata` output. Technically Catalyst's code could have been made
633-
# more generic to account for other default reaction metadata. Practically, this will likely
634-
# be the only relevant reaction metadata to have a default value via the DSL. If another becomes
635-
# relevant, the code can be rewritten to take this into account.
636-
# Checks if the `@default_noise_scaling` option is used. If so, uses it as the default value of
637-
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
638-
function read_default_noise_scaling_option(options)
639-
if haskey(options, :default_noise_scaling)
640-
(length(options[:default_noise_scaling].args) != 3) &&
641-
error("@default_noise_scaling should only have a single expression as its input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
642-
return :([:noise_scaling => $(options[:default_noise_scaling].args[3])])
643-
end
644-
return :([])
645-
end
646-
647632
# When compound species are declared using the "@compound begin ... end" option, get a list
648633
# of the compound species, and also the expression that crates them.
649-
function read_compound_options(options)
634+
function read_compound_option(options)
650635
# If the compound option is used, retrieve a list of compound species and the option line
651636
# that creates them (used to declare them as compounds at the end). Due to some expression
652637
# handling, in the case of a single compound we must change to the `@compound` macro.
@@ -663,44 +648,36 @@ function read_compound_options(options)
663648
return cmpexpr_init, cmps_declared
664649
end
665650

666-
# Read the events (continuous or discrete) provided as options to the DSL. Returns an expression which evaluates to these.
667-
function read_events_option(options, event_type::Symbol)
668-
# Prepares the events, if required to, converts them to block form.
669-
if event_type [:continuous_events, :discrete_events]
670-
error("Trying to read an unsupported event type.")
671-
end
672-
events_input = haskey(options, event_type) ? get_block_option(options[event_type]) :
673-
striplines(:(begin end))
674-
events_input = option_block_form(events_input)
675-
676-
# Goes through the events, checks for errors, and adds them to the output vector.
677-
events_expr = :([])
678-
for arg in events_input.args
679-
# Formatting error checks.
680-
# NOTE: Maybe we should move these deeper into the system (rather than the DSL), throwing errors more generally?
681-
if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) ||
682-
(length(arg.args) != 3)
683-
error("Events should be on form `condition => affect`, separated by a `=>`. This appears not to be the case for: $(arg).")
684-
end
685-
if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) &&
686-
(event_type == :continuous_events)
687-
error("The condition part of continuous events (the left-hand side) must be a vector. This is not the case for: $(arg).")
688-
end
689-
if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect)
690-
error("The affect part of all events (the right-hand side) must be a vector. This is not the case for: $(arg).")
691-
end
651+
# Creates an expression declaring differentials. Here, `tiv` is the time independent variables,
652+
# which is used by the default differential (if it is used).
653+
function read_differentials_option(options)
654+
# Creates the differential expression.
655+
# If differentials was provided as options, this is used as the initial expression.
656+
# If the default differential (D(...)) was used in equations, this is added to the expression.
657+
diffsexpr = (haskey(options, :differentials) ?
658+
get_block_option(options[:differentials]) : striplines(:(begin end)))
659+
diffsexpr = option_block_form(diffsexpr)
692660

693-
# Adds the correctly formatted event to the event creation expression.
694-
push!(events_expr.args, arg)
661+
# Goes through all differentials, checking that they are correctly formatted. Adds their
662+
# symbol to the list of declared differential symbols.
663+
diffs_declared = Union{Symbol, Expr}[]
664+
for dexpr in diffsexpr.args
665+
(dexpr.head != :(=)) &&
666+
error("Differential declaration must have form like D = Differential(t), instead \"$(dexpr)\" was given.")
667+
(dexpr.args[1] isa Symbol) ||
668+
error("Differential left-hand side must be a single symbol, instead \"$(dexpr.args[1])\" was given.")
669+
in(dexpr.args[1], forbidden_symbols_error) &&
670+
error("A forbidden symbol ($(dexpr.args[1])) was used as a differential name.")
671+
push!(diffs_declared, dexpr.args[1])
695672
end
696673

697-
return events_expr
674+
return diffsexpr, diffs_declared
698675
end
699676

700677
# Reads the variables options. Outputs a list of the variables inferred from the equations,
701678
# as well as the equation vector. If the default differential was used, updates the `diffsexpr`
702679
# expression so that this declares this as well.
703-
function read_equations_options!(diffsexpr, options, syms_unavailable, tiv; requiredec = false)
680+
function read_equations_option!(diffsexpr, options, syms_unavailable, tiv; requiredec = false)
704681
# Prepares the equations. First, extracts equations from provided option (converting to block form if required).
705682
# Next, uses MTK's `parse_equations!` function to split input into a vector with the equations.
706683
eqs_input = haskey(options, :equations) ? get_block_option(options[:equations]) : :(begin end)
@@ -755,43 +732,10 @@ function find_D_call(expr)
755732
end
756733
end
757734

758-
# Creates an expression declaring differentials. Here, `tiv` is the time independent variables,
759-
# which is used by the default differential (if it is used).
760-
function read_differentials_option(options)
761-
# Creates the differential expression.
762-
# If differentials was provided as options, this is used as the initial expression.
763-
# If the default differential (D(...)) was used in equations, this is added to the expression.
764-
diffsexpr = (haskey(options, :differentials) ?
765-
get_block_option(options[:differentials]) : striplines(:(begin end)))
766-
diffsexpr = option_block_form(diffsexpr)
767-
768-
# Goes through all differentials, checking that they are correctly formatted. Adds their
769-
# symbol to the list of declared differential symbols.
770-
diffs_declared = Union{Symbol, Expr}[]
771-
for dexpr in diffsexpr.args
772-
(dexpr.head != :(=)) &&
773-
error("Differential declaration must have form like D = Differential(t), instead \"$(dexpr)\" was given.")
774-
(dexpr.args[1] isa Symbol) ||
775-
error("Differential left-hand side must be a single symbol, instead \"$(dexpr.args[1])\" was given.")
776-
in(dexpr.args[1], forbidden_symbols_error) &&
777-
error("A forbidden symbol ($(dexpr.args[1])) was used as a differential name.")
778-
push!(diffs_declared, dexpr.args[1])
779-
end
780-
781-
return diffsexpr, diffs_declared
782-
end
783-
784-
# Reads the combinatorial ratelaw options, which determines if a combinatorial rate law should
785-
# be used or not. If not provides, uses the default (true).
786-
function read_combinatoric_ratelaws_option(options)
787-
return haskey(options, :combinatoric_ratelaws) ?
788-
options[:combinatoric_ratelaws].args[end] : true
789-
end
790-
791735
# Reads the observables options. Outputs an expression for creating the observable variables,
792736
# a vector containing the observable equations, and a list of all observable symbols (this
793737
# list contains both those declared separately or inferred from the `@observables` option` input`).
794-
function read_observed_options(options, all_ivs, us_declared, all_syms; requiredec = false)
738+
function read_observed_option(options, all_ivs, us_declared, all_syms; requiredec = false)
795739
syms_unavailable = setdiff(all_syms, us_declared)
796740
if haskey(options, :observables)
797741
# Gets list of observable equations and prepares variable declaration expression.
@@ -871,6 +815,62 @@ function make_obs_eqs(observables_expr)
871815
return obs_eqs
872816
end
873817

818+
# Read the events (continuous or discrete) provided as options to the DSL. Returns an expression which evaluates to these.
819+
function read_events_option(options, event_type::Symbol)
820+
# Prepares the events, if required to, converts them to block form.
821+
if event_type [:continuous_events, :discrete_events]
822+
error("Trying to read an unsupported event type.")
823+
end
824+
events_input = haskey(options, event_type) ? get_block_option(options[event_type]) :
825+
striplines(:(begin end))
826+
events_input = option_block_form(events_input)
827+
828+
# Goes through the events, checks for errors, and adds them to the output vector.
829+
events_expr = :([])
830+
for arg in events_input.args
831+
# Formatting error checks.
832+
# NOTE: Maybe we should move these deeper into the system (rather than the DSL), throwing errors more generally?
833+
if (arg isa Expr) && (arg.head != :call) || (arg.args[1] != :(=>)) ||
834+
(length(arg.args) != 3)
835+
error("Events should be on form `condition => affect`, separated by a `=>`. This appears not to be the case for: $(arg).")
836+
end
837+
if (arg isa Expr) && (arg.args[2] isa Expr) && (arg.args[2].head != :vect) &&
838+
(event_type == :continuous_events)
839+
error("The condition part of continuous events (the left-hand side) must be a vector. This is not the case for: $(arg).")
840+
end
841+
if (arg isa Expr) && (arg.args[3] isa Expr) && (arg.args[3].head != :vect)
842+
error("The affect part of all events (the right-hand side) must be a vector. This is not the case for: $(arg).")
843+
end
844+
845+
# Adds the correctly formatted event to the event creation expression.
846+
push!(events_expr.args, arg)
847+
end
848+
849+
return events_expr
850+
end
851+
852+
# Returns the `default_reaction_metadata` output. Technically Catalyst's code could have been made
853+
# more generic to account for other default reaction metadata. Practically, this will likely
854+
# be the only relevant reaction metadata to have a default value via the DSL. If another becomes
855+
# relevant, the code can be rewritten to take this into account.
856+
# Checks if the `@default_noise_scaling` option is used. If so, uses it as the default value of
857+
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
858+
function read_default_noise_scaling_option(options)
859+
if haskey(options, :default_noise_scaling)
860+
(length(options[:default_noise_scaling].args) != 3) &&
861+
error("@default_noise_scaling should only have a single expression as its input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
862+
return :([:noise_scaling => $(options[:default_noise_scaling].args[3])])
863+
end
864+
return :([])
865+
end
866+
867+
# Reads the combinatorial ratelaw options, which determines if a combinatorial rate law should
868+
# be used or not. If not provides, uses the default (true).
869+
function read_combinatoric_ratelaws_option(options)
870+
return haskey(options, :combinatoric_ratelaws) ?
871+
options[:combinatoric_ratelaws].args[end] : true
872+
end
873+
874874
### `@reaction` Macro & its Internals ###
875875

876876
"""

src/expression_utils.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ function esc_dollars!(ex)
1313
ex.args[i] = esc_dollars!(ex.args[i])
1414
end
1515
end
16-
1716
ex
1817
end
1918

0 commit comments

Comments
 (0)