Skip to content

Commit 2902101

Browse files
committed
doc fix, combinatorial_ratelaw error handling
1 parent 292da20 commit 2902101

File tree

4 files changed

+94
-75
lines changed

4 files changed

+94
-75
lines changed

docs/src/model_creation/dsl_advanced.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ nothing # hide
575575
When using interpolation, expressions like `2$spec` won't work; the multiplication symbol must be explicitly included like `2*$spec`.
576576

577577
## [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:
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 dsl_description_reactions) for writing and interpreting reactions (however, bi-directional reactions are not permitted). E.g. here we create a simple dimerisation reaction:
579579
```@example dsl_advanced_reaction_macro
580580
using Catalyst # hide
581581
rx_dimerisation = @reaction kD, 2X --> X2

src/dsl.jl

Lines changed: 73 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,21 @@ 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+
632647
# When compound species are declared using the "@compound begin ... end" option, get a list
633648
# of the compound species, and also the expression that crates them.
634649
function read_compound_option(options)
@@ -648,30 +663,38 @@ function read_compound_option(options)
648663
return cmpexpr_init, cmps_declared
649664
end
650665

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)
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)
660675

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])
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
692+
693+
# Adds the correctly formatted event to the event creation expression.
694+
push!(events_expr.args, arg)
672695
end
673696

674-
return diffsexpr, diffs_declared
697+
return events_expr
675698
end
676699

677700
# Reads the variables options. Outputs a list of the variables inferred from the equations,
@@ -732,6 +755,32 @@ function find_D_call(expr)
732755
end
733756
end
734757

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+
735784
# Reads the observables options. Outputs an expression for creating the observable variables,
736785
# a vector containing the observable equations, and a list of all observable symbols (this
737786
# list contains both those declared separately or inferred from the `@observables` option` input`).
@@ -761,8 +810,8 @@ function read_observed_option(options, all_ivs, us_declared, all_syms; requirede
761810
error("An observable ($obs_name) uses a name that already have been already been declared or inferred as another model property.")
762811
(obs_name in us_declared) && is_escaped_expr(obs_eq.args[2]) &&
763812
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.")
764-
((obs_name in us_declared) || is_escaped_expr(obs_eq.args[2])) &&
765-
!isnothing(metadata) && error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the observable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.")
813+
((obs_name in us_declared) || is_escaped_expr(obs_eq.args[2])) && !isnothing(metadata) &&
814+
error("Metadata was provided to observable $obs_name in the `@observables` macro. However, the observable was also declared separately (using either @species or @variables). When this is done, metadata should instead be provided within the original @species or @variable declaration.")
766815

767816
# This bits adds the observables to the @variables vector which is given as output.
768817
# For Observables that have already been declared using @species/@variables,
@@ -815,60 +864,11 @@ function make_obs_eqs(observables_expr)
815864
return obs_eqs
816865
end
817866

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-
867867
# Reads the combinatorial ratelaw options, which determines if a combinatorial rate law should
868868
# be used or not. If not provides, uses the default (true).
869869
function read_combinatoric_ratelaws_option(options)
870870
return haskey(options, :combinatoric_ratelaws) ?
871-
options[:combinatoric_ratelaws].args[end] : true
871+
get_block_option(options[:combinatoric_ratelaws]) : true
872872
end
873873

874874
### `@reaction` Macro & its Internals ###

src/expression_utils.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ end
3939
# Note that there are only some options for which we wish to make this check.
4040
function get_block_option(expr)
4141
(length(expr.args) > 3) &&
42-
error("An option input ($expr) is misformatted. Potentially, it has multiple inputs on a single lines, and these should be split across multiple lines using a `begin ... end` block.")
42+
error("An option input ($expr) is missformatted. Potentially, it has multiple inputs on a single lines, and these should be split across multiple lines using a `begin ... end` block.")
43+
(length(expr.args) < 3) &&
44+
error("An option input ($expr) is missformatted. It seems that it has no inputs, which is expected.")
4345
return expr.args[3]
4446
end
4547

test/dsl/dsl_options.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,7 @@ end
12611261

12621262
# test combinatoric_ratelaws DSL option
12631263
let
1264+
# Test for `@combinatoric_ratelaws false`.
12641265
rn = @reaction_network begin
12651266
@combinatoric_ratelaws false
12661267
(k1,k2), 2A <--> B
@@ -1271,6 +1272,7 @@ let
12711272
@unpack k1, A = rn
12721273
@test isequal(rl, k1*A^2)
12731274

1275+
# Test for `@combinatoric_ratelaws true`.
12741276
rn2 = @reaction_network begin
12751277
@combinatoric_ratelaws true
12761278
(k1,k2), 2A <--> B
@@ -1281,6 +1283,7 @@ let
12811283
@unpack k1, A = rn2
12821284
@test isequal(rl, k1*A^2/2)
12831285

1286+
# Test for interpolation into `@combinatoric_ratelaws`.
12841287
crl = false
12851288
rn3 = @reaction_network begin
12861289
@combinatoric_ratelaws $crl
@@ -1291,6 +1294,20 @@ let
12911294
rl = oderatelaw(reactions(rn3)[1]; combinatoric_ratelaw)
12921295
@unpack k1, A = rn3
12931296
@test isequal(rl, k1*A^2)
1297+
1298+
# Test for erroneous inputs (to few, to many, wrong type).
1299+
@test_throws Exception @eval @reaction_network begin
1300+
@combinatoric_ratelaws
1301+
d, 3X --> 0
1302+
end
1303+
@test_throws Exception @eval @reaction_network begin
1304+
@combinatoric_ratelaws false false
1305+
d, 3X --> 0
1306+
end
1307+
@test_throws Exception @eval @reaction_network begin
1308+
@combinatoric_ratelaws "false"
1309+
d, 3X --> 0
1310+
end
12941311
end
12951312

12961313
# Test whether user-defined functions are properly expanded in equations.

0 commit comments

Comments
 (0)