Skip to content

Commit 5f35480

Browse files
committed
improve option error handling, more tests
1 parent 7ec7383 commit 5f35480

File tree

4 files changed

+161
-21
lines changed

4 files changed

+161
-21
lines changed

src/dsl.jl

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,8 @@ end
637637
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
638638
function read_default_noise_scaling_option(options)
639639
if haskey(options, :default_noise_scaling)
640-
if (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-
end
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])\"")
643642
return :([:noise_scaling => $(options[:default_noise_scaling].args[3])])
644643
end
645644
return :([])
@@ -649,11 +648,14 @@ end
649648
# of the compound species, and also the expression that crates them.
650649
function read_compound_options(options)
651650
# If the compound option is used, retrieve a list of compound species and the option line
652-
# that creates them (used to declare them as compounds at the end).
651+
# that creates them (used to declare them as compounds at the end). Due to some expression
652+
# handling, in the case of a single compound we must change to the `@compound` macro.
653653
if haskey(options, :compounds)
654654
cmpexpr_init = options[:compounds]
655+
cmpexpr_init.args[3] = option_block_form(get_block_option(cmpexpr_init))
655656
cmps_declared = [find_varinfo_in_declaration(arg.args[2])[1]
656657
for arg in cmpexpr_init.args[3].args]
658+
(length(cmps_declared) == 1) && (cmpexpr_init.args[1] = Symbol("@compound"))
657659
else # If option is not used, return empty vectors and expressions.
658660
cmpexpr_init = :()
659661
cmps_declared = Union{Symbol, Expr}[]
@@ -667,7 +669,7 @@ function read_events_option(options, event_type::Symbol)
667669
if event_type [:continuous_events, :discrete_events]
668670
error("Trying to read an unsupported event type.")
669671
end
670-
events_input = haskey(options, event_type) ? options[event_type].args[3] :
672+
events_input = haskey(options, event_type) ? get_block_option(options[event_type]) :
671673
striplines(:(begin end))
672674
events_input = option_block_form(events_input)
673675

@@ -701,7 +703,7 @@ end
701703
function read_equations_options!(diffsexpr, options, syms_unavailable, tiv; requiredec = false)
702704
# Prepares the equations. First, extracts equations from provided option (converting to block form if required).
703705
# Next, uses MTK's `parse_equations!` function to split input into a vector with the equations.
704-
eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end)
706+
eqs_input = haskey(options, :equations) ? get_block_option(options[:equations]) : :(begin end)
705707
eqs_input = option_block_form(eqs_input)
706708
equations = Expr[]
707709
ModelingToolkit.parse_equations!(Expr(:block), equations,
@@ -759,8 +761,8 @@ function read_differentials_option(options)
759761
# Creates the differential expression.
760762
# If differentials was provided as options, this is used as the initial expression.
761763
# If the default differential (D(...)) was used in equations, this is added to the expression.
762-
diffsexpr = (haskey(options, :differentials) ? options[:differentials].args[3] :
763-
striplines(:(begin end)))
764+
diffsexpr = (haskey(options, :differentials) ?
765+
get_block_option(options[:differentials]) : striplines(:(begin end)))
764766
diffsexpr = option_block_form(diffsexpr)
765767

766768
# Goes through all differentials, checking that they are correctly formatted. Adds their
@@ -794,7 +796,7 @@ function read_observed_options(options, all_ivs, us_declared, all_syms; required
794796
if haskey(options, :observables)
795797
# Gets list of observable equations and prepares variable declaration expression.
796798
# (`options[:observables]` includes `@observables`, `.args[3]` removes this part)
797-
obs_eqs = make_obs_eqs(options[:observables].args[3])
799+
obs_eqs = make_obs_eqs(get_block_option(options[:observables]))
798800
obsexpr = Expr(:block, :(@variables))
799801
obs_syms = :([])
800802

src/expression_utils.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ end
3434

3535
### Catalyst-specific Expressions Manipulation ###
3636

37+
# Many option inputs can be on a form `@option input` or `@option begin ... end`. In both these
38+
# cases we want to retrieve the third argument in the option expression. Further more, we wish
39+
# to throw an error if there is more inputs (suggesting e.g. multiple inputs on a single line).
40+
# Note that there are only some options for which we wish to make this check.
41+
function get_block_option(expr)
42+
(length(expr.args) > 3) &&
43+
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.")
44+
return expr.args[3]
45+
end
46+
3747
# Some options takes input on form that is either `@option ...` or `@option begin ... end`.
3848
# This transforms input of the latter form to the former (with only one line in the `begin ... end` block)
3949
function option_block_form(expr)

test/dsl/dsl_basic_model_construction.jl

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,8 @@ let
471471
@test isequal((@reaction k, 0 --> X), (@reaction k, 0 X))
472472
end
473473

474-
# Test that symbols with special meanings, or that are forbidden, are handled properly.
475-
let
474+
# Test that symbols with special meanings are handled properly.
475+
let
476476
test_network = @reaction_network begin t * k, X -->end
477477
@test length(species(test_network)) == 1
478478
@test length(parameters(test_network)) == 1
@@ -491,11 +491,39 @@ let
491491
@test length(species(test_network)) == 1
492492
@test length(parameters(test_network)) == 0
493493
@test reactions(test_network)[1].rate ==
494+
end
494495

495-
@test_throws LoadError @eval @reaction im, 0 --> B
496-
@test_throws LoadError @eval @reaction nothing, 0 --> B
497-
@test_throws LoadError @eval @reaction k, 0 --> im
498-
@test_throws LoadError @eval @reaction k, 0 --> nothing
496+
# Check that forbidden symbols correctly generates errors.
497+
let
498+
# @reaction macro, symbols that cannot be in the rate.
499+
@test_throws Exception @eval @reaction im, 0 --> X
500+
@test_throws Exception @eval @reaction nothing, 0 --> X
501+
@test_throws Exception @eval @reaction Γ, 0 --> X
502+
@test_throws Exception @eval @reaction ∅, 0 --> X
503+
504+
# @reaction macro, symbols that cannot be a reactant.
505+
@test_throws Exception @eval @reaction 1, 0 --> im
506+
@test_throws Exception @eval @reaction 1, 0 --> nothing
507+
@test_throws Exception @eval @reaction 1, 0 --> Γ
508+
@test_throws Exception @eval @reaction 1, 0 -->
509+
@test_throws Exception @eval @reaction 1, 0 --> pi
510+
@test_throws Exception @eval @reaction 1, 0 --> π
511+
@test_throws Exception @eval @reaction 1, 0 --> t
512+
513+
# @reaction_network macro, symbols that cannot be in the rate.
514+
@test_throws Exception @eval @reaction_network begin im, 0 --> X end
515+
@test_throws Exception @eval @reaction_network begin nothing, 0 --> X end
516+
@test_throws Exception @eval @reaction_network begin Γ, 0 --> X end
517+
@test_throws Exception @eval @reaction_network begin ∅, 0 --> X end
518+
519+
# @reaction_network macro, symbols that cannot be a reactant.
520+
@test_throws Exception @eval @reaction_network begin 1, 0 --> im end
521+
@test_throws Exception @eval @reaction_network begin 1, 0 --> nothing end
522+
@test_throws Exception @eval @reaction_network begin 1, 0 --> Γ end
523+
@test_throws Exception @eval @reaction_network begin 1, 0 -->end
524+
@test_throws Exception @eval @reaction_network begin 1, 0 --> pi end
525+
@test_throws Exception @eval @reaction_network begin 1, 0 --> π end
526+
@test_throws Exception @eval @reaction_network begin 1, 0 --> t end
499527

500528
# Checks that non-supported arrow type usage yields error.
501529
@test_throws Exception @eval @reaction_network begin

test/dsl/dsl_options.jl

Lines changed: 106 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,12 +1144,112 @@ end
11441144

11451145
### Other DSL Option Tests ###
11461146

1147+
# Test that various options can be provided in block and single line form.
1148+
# Also checks that the single line form takes maximally one argument.
1149+
let
1150+
# The `@equations` option.
1151+
rn11 = @reaction_network rn1 begin
1152+
@equations D(V) ~ 1 - V
1153+
end
1154+
rn12 = @reaction_network rn1 begin
1155+
@equations begin
1156+
D(V) ~ 1 - V
1157+
end
1158+
end
1159+
@test isequal(rn11, rn12)
1160+
@test_throws Exception @eval @reaction_network begin
1161+
@equations D(V) ~ 1 - V D(W) ~ 1 - W
1162+
end
1163+
1164+
# The `@observables` option.
1165+
rn21 = @reaction_network rn1 begin
1166+
@species X(t)
1167+
@observables X2 ~ 2X
1168+
end
1169+
rn22 = @reaction_network rn1 begin
1170+
@species X(t)
1171+
@observables begin
1172+
X2 ~ 2X
1173+
end
1174+
end
1175+
@test isequal(rn21, rn22)
1176+
@test_throws Exception @eval @reaction_network begin
1177+
@species X(t)
1178+
@observables X2 ~ 2X X3 ~ 3X
1179+
end
1180+
1181+
# The `@compounds` option.
1182+
rn31 = @reaction_network rn1 begin
1183+
@species X(t)
1184+
@compounds X2 ~ 2X
1185+
end
1186+
rn32 = @reaction_network rn1 begin
1187+
@species X(t)
1188+
@compounds begin
1189+
X2 ~ 2X
1190+
end
1191+
end
1192+
@test isequal(rn31, rn32)
1193+
@test_throws Exception @eval @reaction_network begin
1194+
@species X(t)
1195+
@compounds X2 ~ 2X X3 ~ 3X
1196+
end
1197+
1198+
# The `@differentials` option.
1199+
rn41 = @reaction_network rn1 begin
1200+
@differentials D = Differential(t)
1201+
end
1202+
rn42 = @reaction_network rn1 begin
1203+
@differentials begin
1204+
D = Differential(t)
1205+
end
1206+
end
1207+
@test isequal(rn41, rn42)
1208+
@test_throws Exception @eval @reaction_network begin
1209+
@differentials D = Differential(t) Δ = Differential(t)
1210+
end
1211+
1212+
# The `@continuous_events` option.
1213+
rn51 = @reaction_network rn1 begin
1214+
@species X(t)
1215+
@continuous_events [X ~ 3.0] => [X ~ X - 1]
1216+
end
1217+
rn52 = @reaction_network rn1 begin
1218+
@species X(t)
1219+
@continuous_events begin
1220+
[X ~ 3.0] => [X ~ X - 1]
1221+
end
1222+
end
1223+
@test isequal(rn51, rn52)
1224+
@test_throws Exception @eval @reaction_network begin
1225+
@species X(t)
1226+
@continuous_events [X ~ 3.0] => [X ~ X - 1] [X ~ 1.0] => [X ~ X + 1]
1227+
end
1228+
1229+
# The `@discrete_events` option.
1230+
rn61 = @reaction_network rn1 begin
1231+
@species X(t)
1232+
@discrete_events [X > 3.0] => [X ~ X - 1]
1233+
end
1234+
rn62 = @reaction_network rn1 begin
1235+
@species X(t)
1236+
@discrete_events begin
1237+
[X > 3.0] => [X ~ X - 1]
1238+
end
1239+
end
1240+
@test isequal(rn61, rn62)
1241+
@test_throws Exception @eval @reaction_network begin
1242+
@species X(t)
1243+
@discrete_events [X > 3.0] => [X ~ X - 1] [X < 1.0] => [X ~ X + 1]
1244+
end
1245+
end
1246+
11471247
# test combinatoric_ratelaws DSL option
11481248
let
11491249
rn = @reaction_network begin
11501250
@combinatoric_ratelaws false
11511251
(k1,k2), 2A <--> B
1152-
end
1252+
end
11531253
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn)
11541254
@test combinatoric_ratelaw == false
11551255
rl = oderatelaw(reactions(rn)[1]; combinatoric_ratelaw)
@@ -1159,7 +1259,7 @@ let
11591259
rn2 = @reaction_network begin
11601260
@combinatoric_ratelaws true
11611261
(k1,k2), 2A <--> B
1162-
end
1262+
end
11631263
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn2)
11641264
@test combinatoric_ratelaw == true
11651265
rl = oderatelaw(reactions(rn2)[1]; combinatoric_ratelaw)
@@ -1170,7 +1270,7 @@ let
11701270
rn3 = @reaction_network begin
11711271
@combinatoric_ratelaws $crl
11721272
(k1,k2), 2A <--> B
1173-
end
1273+
end
11741274
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn3)
11751275
@test combinatoric_ratelaw == crl
11761276
rl = oderatelaw(reactions(rn3)[1]; combinatoric_ratelaw)
@@ -1256,10 +1356,10 @@ let
12561356
end
12571357
end
12581358

1259-
### test that @no_infer properly throws errors when undeclared variables are written ###
1260-
1261-
import Catalyst: UndeclaredSymbolicError
1359+
# test that @require_declaration properly throws errors when undeclared variables are written.
12621360
let
1361+
import Catalyst: UndeclaredSymbolicError
1362+
12631363
# Test error when species are inferred
12641364
@test_throws UndeclaredSymbolicError @macroexpand @reaction_network begin
12651365
@require_declaration

0 commit comments

Comments
 (0)