Skip to content

Commit b0c027d

Browse files
committed
@compounds in the macro
1 parent 99237cf commit b0c027d

File tree

4 files changed

+165
-15
lines changed

4 files changed

+165
-15
lines changed

src/chemistry_functionality.jl

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ function make_compound(expr)
3939
species_expr = expr.args[1] # E.g. :(CO2(t))
4040
species_name = expr.args[1].args[1] # E.g. :CO2
4141
composition = Catalyst.recursive_find_reactants!(expr.args[2].args[1], 1, Vector{ReactantStruct}(undef, 0))
42-
components = :([]) # Extra step required here to get something like :([C, O]), rather than :([:C, :O])
43-
foreach(comp -> push!(components.args, comp.reactant), composition) # E.g. [C, O]
44-
coefficients = getfield.(composition, :stoichiometry) # E.g. [1, 2]
42+
43+
# Loops through all components, add the component and the coefficients to the corresponding vectors (cannot extract directly using e.g. "getfield.(composition, :reactant)" because then we get something like :([:C, :O]), rather than :([C, O]))
44+
components = :([]) # Becomes something like :([C, O]).
45+
coefficients = :([]) # Becomes something like :([1, 2]).
46+
for comp in composition
47+
push!(components.args, comp.reactant)
48+
push!(coefficients.args, comp.stoichiometry)
49+
end
4550

4651
# Creates the found expressions that will create the compound species.
4752
# The `Expr(:escape, :(...))` is required so that teh expressions are evaluated in the scope the users use the macro in (to e.g. detect already exiting species).
@@ -50,6 +55,14 @@ function make_compound(expr)
5055
components_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata($species_name, Catalyst.CompoundComponents, $components))) # E.g. `CO2 = ModelingToolkit.setmetadata(CO2, Catalyst.CompoundSpecies, [C, O])`
5156
coefficients_designation_expr = Expr(:escape, :($species_name = ModelingToolkit.setmetadata($species_name, Catalyst.CompoundCoefficients, $coefficients))) # E.g. `CO2 = ModelingToolkit.setmetadata(CO2, Catalyst.CompoundSpecies, [1, 2])`
5257

58+
println(
59+
return quote
60+
$species_declaration_expr
61+
$compound_designation_expr
62+
$components_designation_expr
63+
$coefficients_designation_expr
64+
end)
65+
5366
# Returns the rephrased expression.
5467
return quote
5568
$species_declaration_expr
@@ -85,9 +98,22 @@ end
8598

8699
# Function managing the @compound macro.
87100
function make_compounds(expr)
88-
# Creates a separate compound call for each compound.
89-
compound_calls = [make_compound(line) for line in expr.args] # Crates a vector containing the quote for each compound.
90-
return Expr(:block, vcat([c_call.args for c_call in compound_calls]...)...) # Combines the quotes to a single one. Don't want the double "...". But had problems getting past them for various metaprogramming reasons :(.)
101+
# Creates an empty block containing the output call.
102+
compound_declarations = Expr(:block)
103+
104+
# Creates a compound creation set of lines (4 in total) for each line. Loops through all 4x[Number of compounds] liens and add them to compound_declarations.
105+
compound_calls = [Catalyst.make_compound(line) for line in expr.args]
106+
for compound_call in compound_calls, line in MacroTools.striplines(compound_call).args
107+
push!(compound_declarations.args, line)
108+
end
109+
110+
# The output of the macros should be a vector with the compounds (same as for e.g. "@species begin ... end", also require for things to work in the DSL).
111+
# Creates an output vector, and loops through all compounds, adding them to it.
112+
push!(compound_declarations.args, :($(Expr(:escape, :([])))))
113+
for arg in expr.args
114+
push!(compound_declarations.args[end].args[1].args, arg.args[1].args[1])
115+
end
116+
return compound_declarations
91117
end
92118

93119
## Compound Getters ###

src/reaction_network.jl

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ const forbidden_variables_error = let
123123
end
124124

125125
# Declares the keys used for various options.
126-
const option_keys = (:species, :parameters, :variables, :ivs)
126+
const option_keys = (:species, :parameters, :variables, :ivs, :compounds)
127127

128128
### The @species macro, basically a copy of the @variables macro. ###
129129
macro species(ex...)
@@ -358,9 +358,12 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem)))
358358
options = Dict(map(arg -> Symbol(String(arg.args[1])[2:end]) => arg,
359359
option_lines))
360360

361+
# Reads compounds options.
362+
compound_expr, compound_species = read_compound_options(options)
363+
361364
# Parses reactions, species, and parameters.
362365
reactions = get_reactions(reaction_lines)
363-
species_declared = extract_syms(options, :species)
366+
species_declared = [extract_syms(options, :species); compound_species]
364367
parameters_declared = extract_syms(options, :parameters)
365368
variables = extract_syms(options, :variables)
366369

@@ -399,6 +402,7 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem)))
399402
ps, pssym = scalarize_macro(!isempty(parameters), pexprs, "ps")
400403
vars, varssym = scalarize_macro(!isempty(variables), vexprs, "vars")
401404
sps, spssym = scalarize_macro(!isempty(species), sexprs, "specs")
405+
comps, compssym = scalarize_macro(!isempty(compound_species), compound_expr, "comps")
402406
rxexprs = :(Catalyst.CatalystEqType[])
403407
for reaction in reactions
404408
push!(rxexprs.args, get_rxexprs(reaction))
@@ -410,8 +414,9 @@ function make_reaction_system(ex::Expr; name = :(gensym(:ReactionSystem)))
410414
$ivexpr
411415
$vars
412416
$sps
417+
$comps
413418

414-
Catalyst.make_ReactionSystem_internal($rxexprs, $tiv, union($spssym, $varssym),
419+
Catalyst.make_ReactionSystem_internal($rxexprs, $tiv, union($spssym, $varssym, $compssym),
415420
$pssym; name = $name,
416421
spatial_ivs = $sivs)
417422
end
@@ -461,6 +466,19 @@ function esc_dollars!(ex)
461466
ex
462467
end
463468

469+
# When compound species are declared using the "@compound begin ... end" option, get a list of the compound species, and also the expression that crates them.
470+
function read_compound_options(opts)
471+
# If the compound option is used retrive a list of compound species, and the option that creeates them
472+
if haskey(opts, :compounds)
473+
compound_expr = opts[:compounds]
474+
compound_species = [arg.args[1].args[1] for arg in compound_expr.args[3].args] # Loops through where in the "@compound begin ... end" the compound species names are.
475+
else
476+
compound_expr = :()
477+
compound_species = Union{Symbol, Expr}[]
478+
end
479+
return compound_expr, compound_species
480+
end
481+
464482
# When the user have used the @species (or @parameters) options, extract species (or
465483
# parameters) from its input.
466484
function extract_syms(opts, vartype::Symbol)

test/miscellaneous_tests/compound_macro.jl

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
using Catalyst, Test
22

3-
# Test base funcationality in two cases.
3+
# Test base functionality in two cases.
44
let
55
@variables t
6-
@species C(t) H(t) + O(t)
6+
@species C(t) H(t) O(t)
77
@compound C6H12O2(t) = 6C + 12H + 2O
88

99
@test iscompound(C6H12O2)
@@ -102,14 +102,23 @@ let
102102
@species H(t)
103103

104104
alpha = 2
105-
@compound H2_1(t) = alpha*H
106-
@compound H2_2(t) = 2H
105+
h = H
106+
@compound H2_1(t) = 2*H
107+
@compound H2_2(t) = alpha*H
108+
@compound H2_3(t) = 2*h
109+
@compound H2_4(t) = alpha*2H
107110

108111
@test iscompound(H2_1)
109112
@test iscompound(H2_2)
113+
@test iscompound(H2_2)
114+
@test iscompound(H2_4)
110115

111116
@test isequal(components(H2_1),components(H2_2))
117+
@test isequal(components(H2_2),components(H2_3))
118+
@test isequal(components(H2_3),components(H2_4))
112119
@test isequal(coefficients(H2_1),coefficients(H2_2))
120+
@test isequal(coefficients(H2_2),coefficients(H2_3))
121+
@test isequal(coefficients(H2_3),coefficients(H2_4))
113122
end
114123

115124
let
@@ -185,4 +194,79 @@ let
185194
@test isequal(components(comp),components(comp_alt))
186195
@test isequal(coefficients(comp), coefficients(comp_alt))
187196
@test isequal(component_coefficients(comp), component_coefficients(comp_alt))
197+
end
198+
199+
### Compounds in DSL ###
200+
201+
# Checks with a single compound.
202+
# Checks using @unpack.
203+
# Check where compounds and components does not occur in reactions.
204+
let
205+
rn = @reaction_network begin
206+
@species C(t) O(t)
207+
@compounds begin
208+
CO2(t) = C + 2O
209+
end
210+
end
211+
@unpack C, O, CO2 = rn
212+
213+
@test length(species(rn)) == 3
214+
@test iscompound(CO2)
215+
@test isequal([C, O], components(CO2))
216+
@test isequal([1, 2], coefficients(CO2))
217+
@test isequal([C => 1, O => 2], component_coefficients(CO2))
218+
end
219+
220+
# Test using multiple compounds.
221+
# Test using rn. notation to fetch species.
222+
let
223+
rn = complete(@reaction_network begin
224+
@species C(t) O(t) H(t)
225+
@compounds begin
226+
CH4(t) = C + 4H
227+
O2(t) = 2O
228+
CO2(t) = C + 2O
229+
H2O(t) = 2H + O
230+
end
231+
k, CH4 + O2 --> CO2 + H2O
232+
end)
233+
species(rn)
234+
235+
@test length(species(rn)) == 7
236+
@test isequal([rn.C, rn.H], components(rn.CH4))
237+
@test isequal([1, 4], coefficients(rn.CH4))
238+
@test isequal([rn.C => 1, rn.H => 4], component_coefficients(rn.CH4))
239+
@test isequal([rn.O], components(rn.O2))
240+
@test isequal([2], coefficients(rn.O2))
241+
@test isequal([rn.O => 2], component_coefficients(rn.O2))
242+
@test isequal([rn.C, rn.O], components(rn.CO2))
243+
@test isequal([1, 2], coefficients(rn.CO2))
244+
@test isequal([rn.C => 1, rn.O => 2], component_coefficients(rn.CO2))
245+
@test isequal([rn.H, rn.O], components(rn.H2O))
246+
@test isequal([2, 1], coefficients(rn.H2O))
247+
@test isequal([rn.H => 2, rn.O => 1], component_coefficients(rn.H2O))
248+
end
249+
250+
# Tests using compounds of compounds.
251+
# Tests where species are part of reactions and not declared using "@species".
252+
let
253+
rn = @reaction_network begin
254+
@compounds begin
255+
SO2(t) = S + 2O
256+
S2O4(t) = 2SO2
257+
end
258+
dS, S --> 0
259+
dO, O --> 0
260+
end
261+
species(rn)
262+
@unpack S, O, SO2, S2O4 = rn
263+
264+
@test length(species(rn)) == 4
265+
266+
@test isequal([S, O], components(SO2))
267+
@test isequal([1, 2], coefficients(SO2))
268+
@test isequal([S => 1, O => 2], component_coefficients(SO2))
269+
@test isequal([SO2], components(S2O4))
270+
@test isequal([2], coefficients(S2O4))
271+
@test isequal([SO2 => 2], component_coefficients(S2O4))
188272
end

test/miscellaneous_tests/reaction_balancing.jl

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ let
88
@compounds begin
99
H2(t) = 2H
1010
O2(t) = 2O
11-
H2O(t) = 2H + 1O2
11+
H2O(t) = 2H + 1O
1212
end
1313

1414
rx = Reaction(k,[H2,O2],[H2O])
@@ -29,7 +29,7 @@ end
2929
let
3030
@variables t
3131
@parameters k
32-
@species C(t) H(t) = O(t)
32+
@species C(t) H(t) O(t)
3333
@compound O2(t) = 2O
3434
@compound CO2(t) = 1C + 2O
3535
@compound H2O(t) = 2H + 1O
@@ -416,3 +416,25 @@ let
416416
rx = Reaction(1.0, [CO, H2], [COH2])
417417
@test_throws Catalyst.COMPOUND_OF_COMPOUND_ERROR balance_reaction(rx)
418418
end
419+
420+
# Checks that balancing work for a reaction from a reaction_network.
421+
let
422+
rn = complete(@reaction_network begin
423+
@species C(t) H(t) O(t)
424+
@compounds begin
425+
O2(t) = 2O
426+
CO2(t) = 1C + 2O
427+
H2O(t) = 2H + 1O
428+
C6H12O6(t) = 6C + 12H + 6O
429+
end
430+
k, CO2 + H2O --> C6H12O6 + O2
431+
end)
432+
433+
brxs = balance_reaction(reactions(rn)[1])[1]
434+
435+
@test isequal(rn.k, brxs.rate)
436+
@test isequal([rn.CO2, rn.H2O], brxs.substrates)
437+
@test isequal([rn.C6H12O6, rn.O2], brxs.products)
438+
@test isequal([6, 6], brxs.substoich)
439+
@test isequal([1, 6], brxs.prodstoich)
440+
end

0 commit comments

Comments
 (0)