Skip to content

Commit

Permalink
improve option error handling, more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TorkelE committed Jan 19, 2025
1 parent 7ec7383 commit 5f35480
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 21 deletions.
20 changes: 11 additions & 9 deletions src/dsl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,8 @@ end
# the `default_noise_scaling` reaction metadata, otherwise, returns an empty vector.
function read_default_noise_scaling_option(options)
if haskey(options, :default_noise_scaling)
if (length(options[:default_noise_scaling].args) != 3)
error("@default_noise_scaling should only have a single expression as its input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
end
(length(options[:default_noise_scaling].args) != 3) &&
error("@default_noise_scaling should only have a single expression as its input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
return :([:noise_scaling => $(options[:default_noise_scaling].args[3])])
end
return :([])
Expand All @@ -649,11 +648,14 @@ end
# of the compound species, and also the expression that crates them.
function read_compound_options(options)
# If the compound option is used, retrieve a list of compound species and the option line
# that creates them (used to declare them as compounds at the end).
# that creates them (used to declare them as compounds at the end). Due to some expression
# handling, in the case of a single compound we must change to the `@compound` macro.
if haskey(options, :compounds)
cmpexpr_init = options[:compounds]
cmpexpr_init.args[3] = option_block_form(get_block_option(cmpexpr_init))
cmps_declared = [find_varinfo_in_declaration(arg.args[2])[1]
for arg in cmpexpr_init.args[3].args]
(length(cmps_declared) == 1) && (cmpexpr_init.args[1] = Symbol("@compound"))
else # If option is not used, return empty vectors and expressions.
cmpexpr_init = :()
cmps_declared = Union{Symbol, Expr}[]
Expand All @@ -667,7 +669,7 @@ function read_events_option(options, event_type::Symbol)
if event_type [:continuous_events, :discrete_events]
error("Trying to read an unsupported event type.")
end
events_input = haskey(options, event_type) ? options[event_type].args[3] :
events_input = haskey(options, event_type) ? get_block_option(options[event_type]) :
striplines(:(begin end))
events_input = option_block_form(events_input)

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

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

Expand Down
10 changes: 10 additions & 0 deletions src/expression_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ end

### Catalyst-specific Expressions Manipulation ###

# Many option inputs can be on a form `@option input` or `@option begin ... end`. In both these
# cases we want to retrieve the third argument in the option expression. Further more, we wish
# to throw an error if there is more inputs (suggesting e.g. multiple inputs on a single line).
# Note that there are only some options for which we wish to make this check.
function get_block_option(expr)
(length(expr.args) > 3) &&
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.")
return expr.args[3]
end

# Some options takes input on form that is either `@option ...` or `@option begin ... end`.
# This transforms input of the latter form to the former (with only one line in the `begin ... end` block)
function option_block_form(expr)
Expand Down
40 changes: 34 additions & 6 deletions test/dsl/dsl_basic_model_construction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -471,8 +471,8 @@ let
@test isequal((@reaction k, 0 --> X), (@reaction k, 0 X))
end

# Test that symbols with special meanings, or that are forbidden, are handled properly.
let
# Test that symbols with special meanings are handled properly.
let
test_network = @reaction_network begin t * k, X -->end
@test length(species(test_network)) == 1
@test length(parameters(test_network)) == 1
Expand All @@ -491,11 +491,39 @@ let
@test length(species(test_network)) == 1
@test length(parameters(test_network)) == 0
@test reactions(test_network)[1].rate ==
end

@test_throws LoadError @eval @reaction im, 0 --> B
@test_throws LoadError @eval @reaction nothing, 0 --> B
@test_throws LoadError @eval @reaction k, 0 --> im
@test_throws LoadError @eval @reaction k, 0 --> nothing
# Check that forbidden symbols correctly generates errors.
let
# @reaction macro, symbols that cannot be in the rate.
@test_throws Exception @eval @reaction im, 0 --> X
@test_throws Exception @eval @reaction nothing, 0 --> X
@test_throws Exception @eval @reaction Γ, 0 --> X
@test_throws Exception @eval @reaction ∅, 0 --> X

# @reaction macro, symbols that cannot be a reactant.
@test_throws Exception @eval @reaction 1, 0 --> im
@test_throws Exception @eval @reaction 1, 0 --> nothing
@test_throws Exception @eval @reaction 1, 0 --> Γ
@test_throws Exception @eval @reaction 1, 0 -->
@test_throws Exception @eval @reaction 1, 0 --> pi
@test_throws Exception @eval @reaction 1, 0 --> π
@test_throws Exception @eval @reaction 1, 0 --> t

# @reaction_network macro, symbols that cannot be in the rate.
@test_throws Exception @eval @reaction_network begin im, 0 --> X end
@test_throws Exception @eval @reaction_network begin nothing, 0 --> X end
@test_throws Exception @eval @reaction_network begin Γ, 0 --> X end
@test_throws Exception @eval @reaction_network begin ∅, 0 --> X end

# @reaction_network macro, symbols that cannot be a reactant.
@test_throws Exception @eval @reaction_network begin 1, 0 --> im end
@test_throws Exception @eval @reaction_network begin 1, 0 --> nothing end
@test_throws Exception @eval @reaction_network begin 1, 0 --> Γ end
@test_throws Exception @eval @reaction_network begin 1, 0 -->end
@test_throws Exception @eval @reaction_network begin 1, 0 --> pi end
@test_throws Exception @eval @reaction_network begin 1, 0 --> π end
@test_throws Exception @eval @reaction_network begin 1, 0 --> t end

# Checks that non-supported arrow type usage yields error.
@test_throws Exception @eval @reaction_network begin
Expand Down
112 changes: 106 additions & 6 deletions test/dsl/dsl_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1144,12 +1144,112 @@ end

### Other DSL Option Tests ###

# Test that various options can be provided in block and single line form.
# Also checks that the single line form takes maximally one argument.
let
# The `@equations` option.
rn11 = @reaction_network rn1 begin
@equations D(V) ~ 1 - V
end
rn12 = @reaction_network rn1 begin
@equations begin
D(V) ~ 1 - V
end
end
@test isequal(rn11, rn12)
@test_throws Exception @eval @reaction_network begin
@equations D(V) ~ 1 - V D(W) ~ 1 - W
end

# The `@observables` option.
rn21 = @reaction_network rn1 begin
@species X(t)
@observables X2 ~ 2X
end
rn22 = @reaction_network rn1 begin
@species X(t)
@observables begin
X2 ~ 2X
end
end
@test isequal(rn21, rn22)
@test_throws Exception @eval @reaction_network begin
@species X(t)
@observables X2 ~ 2X X3 ~ 3X
end

# The `@compounds` option.
rn31 = @reaction_network rn1 begin
@species X(t)
@compounds X2 ~ 2X
end
rn32 = @reaction_network rn1 begin
@species X(t)
@compounds begin
X2 ~ 2X
end
end
@test isequal(rn31, rn32)
@test_throws Exception @eval @reaction_network begin
@species X(t)
@compounds X2 ~ 2X X3 ~ 3X
end

# The `@differentials` option.
rn41 = @reaction_network rn1 begin
@differentials D = Differential(t)
end
rn42 = @reaction_network rn1 begin
@differentials begin
D = Differential(t)
end
end
@test isequal(rn41, rn42)
@test_throws Exception @eval @reaction_network begin
@differentials D = Differential(t) Δ = Differential(t)
end

# The `@continuous_events` option.
rn51 = @reaction_network rn1 begin
@species X(t)
@continuous_events [X ~ 3.0] => [X ~ X - 1]
end
rn52 = @reaction_network rn1 begin
@species X(t)
@continuous_events begin
[X ~ 3.0] => [X ~ X - 1]
end
end
@test isequal(rn51, rn52)
@test_throws Exception @eval @reaction_network begin
@species X(t)
@continuous_events [X ~ 3.0] => [X ~ X - 1] [X ~ 1.0] => [X ~ X + 1]
end

# The `@discrete_events` option.
rn61 = @reaction_network rn1 begin
@species X(t)
@discrete_events [X > 3.0] => [X ~ X - 1]
end
rn62 = @reaction_network rn1 begin
@species X(t)
@discrete_events begin
[X > 3.0] => [X ~ X - 1]
end
end
@test isequal(rn61, rn62)
@test_throws Exception @eval @reaction_network begin
@species X(t)
@discrete_events [X > 3.0] => [X ~ X - 1] [X < 1.0] => [X ~ X + 1]
end
end

# test combinatoric_ratelaws DSL option
let
rn = @reaction_network begin
@combinatoric_ratelaws false
(k1,k2), 2A <--> B
end
end
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn)
@test combinatoric_ratelaw == false
rl = oderatelaw(reactions(rn)[1]; combinatoric_ratelaw)
Expand All @@ -1159,7 +1259,7 @@ let
rn2 = @reaction_network begin
@combinatoric_ratelaws true
(k1,k2), 2A <--> B
end
end
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn2)
@test combinatoric_ratelaw == true
rl = oderatelaw(reactions(rn2)[1]; combinatoric_ratelaw)
Expand All @@ -1170,7 +1270,7 @@ let
rn3 = @reaction_network begin
@combinatoric_ratelaws $crl
(k1,k2), 2A <--> B
end
end
combinatoric_ratelaw = Catalyst.get_combinatoric_ratelaws(rn3)
@test combinatoric_ratelaw == crl
rl = oderatelaw(reactions(rn3)[1]; combinatoric_ratelaw)
Expand Down Expand Up @@ -1256,10 +1356,10 @@ let
end
end

### test that @no_infer properly throws errors when undeclared variables are written ###

import Catalyst: UndeclaredSymbolicError
# test that @require_declaration properly throws errors when undeclared variables are written.
let
import Catalyst: UndeclaredSymbolicError

# Test error when species are inferred
@test_throws UndeclaredSymbolicError @macroexpand @reaction_network begin
@require_declaration
Expand Down

0 comments on commit 5f35480

Please sign in to comment.