Skip to content

Commit 7139625

Browse files
committed
up
1 parent f6906b6 commit 7139625

File tree

5 files changed

+221
-55
lines changed

5 files changed

+221
-55
lines changed

docs/src/catalyst_functionality/chemistry_related_functionality.md

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ using Catalyst
1515
```
1616
Next, we create the `CO2` compound species:
1717
```@example chem1
18-
@compound CO2(t) = C + 2O
18+
@compound CO2 ~ C + 2O
1919
```
20-
Here, the compound is the first argument to the macro, followed by its component. The `(t)` indicates that `CO2` is a time dependant species. Components with non-unitary stoichiometries have this value written before the component (generally, the rules for designating the components of a compound are identical to those of designating the substrates or products of a reaction). The created compound, `CO2`, is a species in every sense, and can be used wherever e.g. `C` can be used:
20+
Here, the compound is the first argument to the macro, followed by its component (with the left-hand and right-hand sides separated by a `~` sign). While non-compound species (such as `C` and `O`) have their independent variable (in this case `t`) designated, independent variables as not designated for compounds (but instead directly inferred from their components). Components with non-unitary stoichiometries have this value written before the component (generally, the rules for designating the components of a compound are identical to those of designating the substrates or products of a reaction). The created compound, `CO2`, is a species in every sense, and can be used wherever e.g. `C` can be used:
2121
```@example chem1
2222
isspecies(CO2)
2323
```
@@ -40,16 +40,16 @@ iscompound(CO2)
4040
Compound components that are also compounds are allowed, e.g. we can create a carbonic acid compound (H₂CO₃) that consists of CO₂ and H₂O:
4141
```@example chem1
4242
@species H(t)
43-
@compound H2O(t) = 2H + O
44-
@compound H2CO3(t) = CO2 + H2O
43+
@compound H2O ~ 2H + O
44+
@compound H2CO3 ~ CO2 + H2O
4545
```
4646

4747
When multiple compounds are created, they can be created simultaneously using the `@compounds` macro, e.g. the previous code-block can be re-written as:
4848
```@example chem1
4949
@species H(t)
5050
@compounds begin
51-
H2O(t) = 2H + O
52-
H2CO3(t) = CO2 + H2O
51+
H2O ~ 2H + O
52+
H2CO3 ~ CO2 + H2O
5353
end
5454
```
5555

@@ -59,9 +59,9 @@ It is also possible to declare species as compound species within the `@reaction
5959
rn = @reaction_network begin
6060
@species C(t) H(t) O(t)
6161
@compounds begin
62-
C2O(t) = C + 2O
63-
H2O(t) = 2H + O
64-
H2CO3(t) = CO2 + H2O
62+
C2O ~ C + 2O
63+
H2O ~ 2H + O
64+
H2CO3 ~ CO2 + H2O
6565
end
6666
(k1,k2), H2O+ CO2 <--> H2CO3
6767
end
@@ -70,15 +70,29 @@ When creating compound species using the DSL, it is important to note that *ever
7070
```julia
7171
rn = @reaction_network begin
7272
@compounds begin
73-
C2O(t) = C + 2O
74-
H2O(t) = 2H + O
75-
H2CO3(t) = CO2 + H2O
73+
C2O ~ C + 2O
74+
H2O ~ 2H + O
75+
H2CO3 ~ CO2 + H2O
7676
end
7777
(k1,k2), H2O+ CO2 <--> H2CO3
7878
end
7979
```
8080
as the components `C`, `H`, and `O` are not declared as a species anywhere. Please also note that only `@compounds` can be used as an option in the DSL, not `@compound`.
8181

82+
#### Designating metadata and default values for compounds
83+
Just like for normal species, it is possible to designate metadata and default values for compounds. Metadata is provided after the compound name, but separated from it by a `,`:
84+
```@example chem1
85+
@compound CO2, [unit="mol"] ~ C + 2O
86+
```
87+
Default values are designated using `=`, and provided directly after the compound name. If default values are given, the left-hand side must be grouped using `()`:
88+
```@example chem1
89+
@compound (CO2 = 2.0) ~ C + 2O
90+
```
91+
If both default values and meta data are provided, the metadata is provided after teh default value:
92+
```@example chem1
93+
@compound (CO2 = 2.0, [unit="mol"]) ~ C + 2O
94+
```
95+
8296
## Balancing chemical reactions
8397
One use of defining a species as a compound is that they can be used to balance reactions to that the number of components are the same on both sides. Catalyst provides the `balance_reaction` function, which takes a reaction, and returns a balanced version. E.g. let us consider a reaction when carbon dioxide is formed from carbon and oxide `C + O --> CO2`. Here, `balance_reaction` enables us to find coefficients creating a balanced reaction (in this case, where the number of carbon and oxygen atoms are the same on both sides). To demonstrate, we first created the unbalanced reactions:
8498
```@example chem1
@@ -96,10 +110,10 @@ using Catalyst # hide
96110
@variables t
97111
@species N(t) H(t) O(t)
98112
@compounds begin
99-
NH3(t) = N + 3H
100-
O2(t) = 2O
101-
NO(t) = N + O
102-
H2O(t) = 2H + O
113+
NH3 ~ N + 3H
114+
O2 ~ 2O
115+
NO ~ N + O
116+
H2O ~ 2H + O
103117
end
104118
unbalanced_reaction = @reaction k, $NH3 + $O2 --> $NO + $H2O
105119
```

src/chemistry_functionality.jl

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,28 @@ macro compound(expr)
2828
make_compound(MacroTools.striplines(expr))
2929
end
3030

31+
# Declares compound error messages:
32+
const COMPOUND_CREATION_ERROR_BASE = "Malformed input to @compound. Should use form like e.g. \"@compound CO2 ~ C + 2O\"."
33+
const COMPOUND_CREATION_ERROR_BAD_SEPARATOR = "Malformed input to @compound. Left-hand side (the compound) and the right-hand side (the components) should be separated by a \"~\" (e.g. \"@compound CO2 ~ C + 2O\"). If the left hand side contains metadata or default values, this should be enclosed by \"()\" (e.g. \"@compound (CO2 = 1.0, [output=true]) ~ C + 2O\")."
34+
const COMPOUND_CREATION_ERROR_DEPENDENT_VAR_GIVEN = "Left hand side of @compound is malformed. Does the compound depend on a independent variable (e.g. \"CO2(t)\")? If so, that should not be the case, these are inferred from the compounds."
35+
3136
# Function managing the @compound macro.
3237
function make_compound(expr)
3338
# Error checks.
34-
!(expr isa Expr) || (expr.head != :(=)) && error("Malformed expression. Compounds should be declared using a \"=\".")
35-
(length(expr.args) != 2) && error("Malformed expression. Compounds should be consists of two expression, separated by a \"=\".")
36-
((expr.args[1] isa Symbol) || (expr.args[1].head != :call)) && error("Malformed expression. There must be a single compound which depend on an independent variable, e.g. \"CO2(t)\".")
39+
(expr isa Expr) || error(COMPOUND_CREATION_ERROR_BASE)
40+
((expr.head == :call) && (expr.args[1] == :~) && (length(expr.args) == 3)) || error(COMPOUND_CREATION_ERROR_BAD_SEPARATOR)
41+
if !(expr.args[2] isa Symbol)
42+
# Checks if a dependent variable was given (e.g. CO2(t)).
43+
(expr.args[2].head == :call) && error(COMPOUND_CREATION_ERROR_DEPENDENT_VAR_GIVEN)
44+
((expr.args[2].args[1] isa Expr) && expr.args[2].args[1].head == :call) && error(COMPOUND_CREATION_ERROR_DEPENDENT_VAR_GIVEN)
45+
((expr.args[2].args[1] isa Expr) && (expr.args[2].args[1].args[1] isa Expr) && expr.args[2].args[1].args[1].head == :call) && error(COMPOUND_CREATION_ERROR_DEPENDENT_VAR_GIVEN)
46+
end
3747

3848
# Extracts the composite species name, and a Vector{ReactantStruct} of its components.
39-
species_expr = expr.args[1] # E.g. :(CO2(t))
40-
species_name = expr.args[1].args[1] # E.g. :CO2
41-
composition = Catalyst.recursive_find_reactants!(expr.args[2].args[1], 1, Vector{ReactantStruct}(undef, 0))
49+
species_expr = expr.args[2] # E.g. :(CO2(t))
50+
species_expr = insert_independent_variable(species_expr, :t) # Add independent variable (e.g. goes from `CO2` to `CO2(t)`).
51+
species_name = find_varname_in_declaration(expr.args[2]) # E.g. :CO2
52+
composition = Catalyst.recursive_find_reactants!(expr.args[3], 1, Vector{ReactantStruct}(undef, 0))
4253

4354
# 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]))
4455
components = :([]) # Becomes something like :([C, O]).
@@ -102,9 +113,13 @@ function make_compounds(expr)
102113
# 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).
103114
# Creates an output vector, and loops through all compounds, adding them to it.
104115
push!(compound_declarations.args, :($(Expr(:escape, :([])))))
116+
compound_syms = :([])
105117
for arg in expr.args
106-
push!(compound_declarations.args[end].args[1].args, arg.args[1].args[1])
118+
push!(compound_syms.args, find_varname_in_declaration(arg.args[2]))
107119
end
120+
push!(compound_declarations.args, :($(Expr(:escape, :($(compound_syms))))))
121+
122+
# Returns output that.
108123
return compound_declarations
109124
end
110125

src/expression_utils.jl

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,73 @@ function get_tup_arg(ex::ExprValues, i::Int)
1010
return ex.args[i]
1111
end
1212

13+
# In variable/species/parameters on the forms like:
14+
# X
15+
# X = 1.0
16+
# X, [metadata=true]
17+
# X = 1.0, [metadata=true]
18+
# X(t)
19+
# X(t) = 1.0
20+
# X(t), [metadata=true]
21+
# X(t) = 1.0, [metadata=true]
22+
# Finds the variable name (here, X).
23+
# Currently does not support e.g. "X, [metadata=true]" (when metadata does not have a comma before).
24+
function find_varname_in_declaration(expr)
25+
(expr isa Symbol) && (return expr) # Case: X
26+
(expr.head == :call) && (return ex5.args[1]) # Case: X(t)
27+
if expr.head == :(=)
28+
(expr.args[1] isa Symbol) && (return expr.args[1]) # Case: X = 1.0
29+
(expr.args[1].head == :call) && (return expr.args[1].args[1]) # Case: X(t) = 1.0
30+
end
31+
if expr.head == :tuple
32+
(expr.args[1] isa Symbol) && (return expr.args[1]) # Case: X, [metadata=true]
33+
(expr.args[1].head == :call) && (return expr.args[1].args[1]) # Case: X(t), [metadata=true]
34+
if (expr.args[1].head == :(=))
35+
(expr.args[1].args[1] isa Symbol) && (return expr.args[1].args[1]) # Case: X = 1.0, [metadata=true]
36+
(expr.args[1].args[1].head == :call) && (return expr.args[1].args[1].args[1]) # Case: X(t) = 1.0, [metadata=true]
37+
end
38+
end
39+
error("Unable to detect the variable declared in expression: $expr.")
40+
end
41+
42+
# Converts an expression of the form:
43+
# X
44+
# X = 1.0
45+
# X, [metadata=true]
46+
# X = 1.0, [metadata=true]
47+
# To the form:
48+
# X(t)
49+
# X(t) = 1.0
50+
# X(t), [metadata=true]
51+
# X(t) = 1.0, [metadata=true]
52+
# (In this example the independent variable t was inserted).
53+
function insert_independent_variable(expr_in, iv)
54+
# If expr is a symbol, just attach the iv. If not we have to create a new expr and mutate it.
55+
# Because Symbols (a possible input) cannot be mutated, this function cannot mutate the input (would have been easier if Expr input was guaranteed).
56+
(expr_in isa Symbol) && (return :($(expr_in)($iv)))
57+
expr = deepcopy(expr_in)
58+
59+
if expr.head == :(=) # Case: :(X = 1.0)
60+
expr.args[1] = :($(expr.args[1])($iv))
61+
elseif expr.head == :tuple
62+
if expr.args[1] isa Symbol # Case: :(X, [metadata=true])
63+
expr.args[1] = :($(expr.args[1])($iv))
64+
elseif (expr.args[1].head == :(=)) && (expr.args[1].args[1] isa Symbol) # Case: :(X = 1.0, [metadata=true])
65+
expr.args[1].args[1] = :($(expr.args[1].args[1])($iv))
66+
end
67+
end
68+
(expr == expr_in) && error("Failed to add independent variable $(iv) to expression: $expr_in")
69+
return expr
70+
end
71+
72+
73+
74+
75+
76+
77+
78+
### Old Stuff ###
79+
1380
#This will be called whenever a function stored in funcdict is called.
1481
# function replace_names(expr, old_names, new_names)
1582
# mapping = Dict(zip(old_names, new_names))

src/reaction_network.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,8 @@ function read_compound_options(opts)
471471
# If the compound option is used retrive a list of compound species (need to be added to teh reaction system's species), and the option that creates them (used to declare them as compounds at the end).
472472
if haskey(opts, :compounds)
473473
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.
474+
# Find compound species names, and append the independent variable.
475+
compound_species = [find_varname_in_declaration(arg.args[2]) for arg in compound_expr.args[3].args]
475476
else # If option is not used, return empty vectors and expressions.
476477
compound_expr = :()
477478
compound_species = Union{Symbol, Expr}[]

0 commit comments

Comments
 (0)