Skip to content

Commit 29e3dd6

Browse files
committed
initial finish
1 parent fba0ae0 commit 29e3dd6

File tree

5 files changed

+244
-246
lines changed

5 files changed

+244
-246
lines changed

src/chemistry_functionality.jl

Lines changed: 127 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,49 @@ Symbolics.option_to_metadata_type(::Val{:iscompound}) = CompoundSpecies
77
Symbolics.option_to_metadata_type(::Val{:components}) = CompoundComponents
88
Symbolics.option_to_metadata_type(::Val{:coefficients}) = CompoundCoefficients
99

10+
## Compound Getters ###
11+
12+
"""
13+
iscompound(s)
14+
15+
Returns `true` if the input is a compound species (else false).
16+
"""
17+
iscompound(s::Num) = iscompound(MT.value(s))
18+
function iscompound(s)
19+
MT.getmetadata(s, CompoundSpecies, false)
20+
end
21+
22+
"""
23+
components(s)
24+
25+
Returns a vector with a list of all the components of a compound species (created using e.g. the @compound macro).
26+
"""
27+
components(s::Num) = components(MT.value(s))
28+
function components(s)
29+
MT.getmetadata(s, CompoundComponents)
30+
end
31+
32+
"""
33+
coefficients(s)
34+
35+
Returns a vector with a list of all the stoichiometric coefficients of the components of a compound species (created using e.g. the @compound macro).
36+
"""
37+
coefficients(s::Num) = coefficients(MT.value(s))
38+
function coefficients(s)
39+
MT.getmetadata(s, CompoundCoefficients)
40+
end
41+
42+
"""
43+
component_coefficients(s)
44+
45+
Returns a Vector{Pari{Symbol,Int64}}, listing a compounds species (created using e.g. the @compound macro) all the coefficients and their stoichiometric coefficients.
46+
"""
47+
component_coefficients(s::Num) = component_coefficients(MT.value(s))
48+
function component_coefficients(s)
49+
return [c => co for (c, co) in zip(components(s), coefficients(s))]
50+
end
51+
52+
1053
### Create @compound Macro(s) ###
1154

1255
"""
@@ -146,135 +189,9 @@ function make_compounds(expr)
146189
return compound_declarations
147190
end
148191

149-
## Compound Getters ###
150-
151-
"""
152-
iscompound(s)
153-
154-
Returns `true` if the input is a compound species (else false).
155-
"""
156-
iscompound(s::Num) = iscompound(MT.value(s))
157-
function iscompound(s)
158-
MT.getmetadata(s, CompoundSpecies, false)
159-
end
160-
161-
"""
162-
components(s)
163-
164-
Returns a vector with a list of all the components of a compound species (created using e.g. the @compound macro).
165-
"""
166-
components(s::Num) = components(MT.value(s))
167-
function components(s)
168-
MT.getmetadata(s, CompoundComponents)
169-
end
170-
171-
"""
172-
coefficients(s)
173-
174-
Returns a vector with a list of all the stoichiometric coefficients of the components of a compound species (created using e.g. the @compound macro).
175-
"""
176-
coefficients(s::Num) = coefficients(MT.value(s))
177-
function coefficients(s)
178-
MT.getmetadata(s, CompoundCoefficients)
179-
end
180-
181-
"""
182-
component_coefficients(s)
183-
184-
Returns a Vector{Pari{Symbol,Int64}}, listing a compounds species (created using e.g. the @compound macro) all the coefficients and their stoichiometric coefficients.
185-
"""
186-
component_coefficients(s::Num) = component_coefficients(MT.value(s))
187-
function component_coefficients(s)
188-
return [c => co for (c, co) in zip(components(s), coefficients(s))]
189-
end
190-
191192

192193
### Reaction Balancing Functionality ###
193194

194-
# Reaction balancing error.
195-
const COMPOUND_OF_COMPOUND_ERROR = ErrorException("Reaction balancing does not currently work for reactions involving compounds of compounds.")
196-
197-
# Note this does not correctly handle compounds of compounds currently.
198-
# Internal function used by "balance_reaction" (via "get_balanced_stoich").
199-
function create_matrix(reaction::Catalyst.Reaction)
200-
@unpack substrates, products = reaction
201-
unique_atoms = [] # Array to store unique atoms
202-
n_atoms = 0
203-
ncompounds = length(substrates) + length(products)
204-
A = zeros(Int, 0, ncompounds)
205-
206-
coeffsign = 1
207-
jbase = 0
208-
for compounds in (substrates, products)
209-
for (j2, compound) in enumerate(compounds)
210-
j = jbase + j2
211-
212-
if iscompound(compound)
213-
atoms = components(compound)
214-
any(iscompound, atoms) && throw(COMPOUND_OF_COMPOUND_ERROR)
215-
coeffs = coefficients(compound)
216-
(atoms == nothing || coeffs == nothing) && continue
217-
else
218-
# If not a compound, assume coefficient of 1
219-
atoms = [compound]
220-
coeffs = [1]
221-
end
222-
223-
for (atom,coeff) in zip(atoms, coeffs)
224-
# Extract atom and coefficient from the pair
225-
i = findfirst(x -> isequal(x, atom), unique_atoms)
226-
if i === nothing
227-
# Add the atom to the atoms array if it's not already present
228-
push!(unique_atoms, atom)
229-
n_atoms += 1
230-
A = [A; zeros(Int, 1, ncompounds)]
231-
i = n_atoms
232-
end
233-
234-
# Adjust coefficient based on whether the compound is a product or substrate
235-
coeff *= coeffsign
236-
237-
A[i, j] = coeff
238-
end
239-
end
240-
241-
# update for iterating through products
242-
coeffsign = -1
243-
jbase = length(substrates)
244-
end
245-
246-
return A
247-
end
248-
249-
# Internal function used by "balance_reaction".
250-
function get_balanced_stoich(reaction::Reaction)
251-
# Create the reaction matrix A that is m atoms by n compounds
252-
A = create_matrix(reaction)
253-
254-
# get an integer nullspace basis
255-
X = ModelingToolkit.nullspace(A)
256-
nullity = size(X, 2)
257-
258-
stoichvecs = Vector{Vector{Int64}}()
259-
for (j, col) in enumerate(eachcol(X))
260-
signs = unique(col)
261-
262-
# If there is only one basis vector and the signs are not all the same this means
263-
# we have a solution that would require moving at least one substrate to be a
264-
# product (or vice-versa). We therefore do not return anything in this case.
265-
# If there are multiple basis vectors we don't currently determine if we can
266-
# construct a linear combination giving a solution, so we just return them.
267-
if (nullity > 1) || (all(>=(0), signs) || all(<=(0), signs))
268-
coefs = abs.(col)
269-
common_divisor = reduce(gcd, coefs)
270-
coefs .= div.(coefs, common_divisor)
271-
push!(stoichvecs, coefs)
272-
end
273-
end
274-
275-
return stoichvecs
276-
end
277-
278195
"""
279196
balance_reaction(reaction::Reaction)
280197
@@ -345,3 +262,87 @@ function balance_reaction(reaction::Reaction)
345262
(length(balancedrxs) > 1) && (@warn "Infinite balanced reactions from ($reaction) are possible, returning a basis for them. Note that we do not check if they preserve the set of substrates and products from the original reaction.")
346263
return balancedrxs
347264
end
265+
266+
# Internal function used by "balance_reaction".
267+
function get_balanced_stoich(reaction::Reaction)
268+
# Create the reaction matrix A that is m atoms by n compounds
269+
A = create_matrix(reaction)
270+
271+
# get an integer nullspace basis
272+
X = ModelingToolkit.nullspace(A)
273+
nullity = size(X, 2)
274+
275+
stoichvecs = Vector{Vector{Int64}}()
276+
for (j, col) in enumerate(eachcol(X))
277+
signs = unique(col)
278+
279+
# If there is only one basis vector and the signs are not all the same this means
280+
# we have a solution that would require moving at least one substrate to be a
281+
# product (or vice-versa). We therefore do not return anything in this case.
282+
# If there are multiple basis vectors we don't currently determine if we can
283+
# construct a linear combination giving a solution, so we just return them.
284+
if (nullity > 1) || (all(>=(0), signs) || all(<=(0), signs))
285+
coefs = abs.(col)
286+
common_divisor = reduce(gcd, coefs)
287+
coefs .= div.(coefs, common_divisor)
288+
push!(stoichvecs, coefs)
289+
end
290+
end
291+
292+
return stoichvecs
293+
end
294+
295+
# Reaction balancing error.
296+
const COMPOUND_OF_COMPOUND_ERROR = ErrorException("Reaction balancing does not currently work for reactions involving compounds of compounds.")
297+
298+
# Note this does not correctly handle compounds of compounds currently.
299+
# Internal function used by "balance_reaction" (via "get_balanced_stoich").
300+
function create_matrix(reaction::Catalyst.Reaction)
301+
@unpack substrates, products = reaction
302+
unique_atoms = [] # Array to store unique atoms
303+
n_atoms = 0
304+
ncompounds = length(substrates) + length(products)
305+
A = zeros(Int, 0, ncompounds)
306+
307+
coeffsign = 1
308+
jbase = 0
309+
for compounds in (substrates, products)
310+
for (j2, compound) in enumerate(compounds)
311+
j = jbase + j2
312+
313+
if iscompound(compound)
314+
atoms = components(compound)
315+
any(iscompound, atoms) && throw(COMPOUND_OF_COMPOUND_ERROR)
316+
coeffs = coefficients(compound)
317+
(atoms == nothing || coeffs == nothing) && continue
318+
else
319+
# If not a compound, assume coefficient of 1
320+
atoms = [compound]
321+
coeffs = [1]
322+
end
323+
324+
for (atom,coeff) in zip(atoms, coeffs)
325+
# Extract atom and coefficient from the pair
326+
i = findfirst(x -> isequal(x, atom), unique_atoms)
327+
if i === nothing
328+
# Add the atom to the atoms array if it's not already present
329+
push!(unique_atoms, atom)
330+
n_atoms += 1
331+
A = [A; zeros(Int, 1, ncompounds)]
332+
i = n_atoms
333+
end
334+
335+
# Adjust coefficient based on whether the compound is a product or substrate
336+
coeff *= coeffsign
337+
338+
A[i, j] = coeff
339+
end
340+
end
341+
342+
# update for iterating through products
343+
coeffsign = -1
344+
jbase = length(substrates)
345+
end
346+
347+
return A
348+
end

src/dsl.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ macro network_component(name::Symbol = gensym(:ReactionSystem))
207207
:(ReactionSystem(Reaction[], t, [], []; name = $(QuoteNode(name)))))
208208
end
209209

210+
210211
### Internal DSL Structures ###
211212

212213
# Structure containing information about one reactant in one reaction.
@@ -574,6 +575,22 @@ function get_rxexprs(rxstruct)
574575
reaction_func
575576
end
576577

578+
# takes a ModelingToolkit declaration macro like @parameters and returns an expression
579+
# that calls the macro and then scalarizes all the symbols created into a vector of Nums
580+
function scalarize_macro(nonempty, ex, name)
581+
namesym = gensym(name)
582+
if nonempty
583+
symvec = gensym()
584+
ex = quote
585+
$symvec = $ex
586+
$namesym = reduce(vcat, Symbolics.scalarize($symvec))
587+
end
588+
else
589+
ex = :($namesym = Num[])
590+
end
591+
ex, namesym
592+
end
593+
577594

578595
### DSL Option Handling ###
579596

0 commit comments

Comments
 (0)