Skip to content

Commit fd56e7a

Browse files
committed
save progress (init)
1 parent 1cfa932 commit fd56e7a

File tree

7 files changed

+2025
-2013
lines changed

7 files changed

+2025
-2013
lines changed

src/Catalyst.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ function __init__()
6969
end
7070
end
7171

72+
# Declare constants.
73+
const CatalystEqType = Union{Reaction, Equation}
74+
7275
# base system type and features
7376
include("reactionsystem.jl")
7477
export isspecies
@@ -80,7 +83,7 @@ export ODEProblem,
8083
# reaction_network macro
8184
const ExprValues = Union{Expr, Symbol, Float64, Int, Bool}
8285
include("expression_utils.jl")
83-
include("reaction_network.jl")
86+
include("dsl.jl")
8487
export @reaction_network, @network_component, @reaction, @species
8588

8689
# registers CRN specific functions using Symbolics.jl
File renamed without changes.

src/networkapi.jl renamed to src/network_analysis.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11

2+
3+
4+
25
######### Accessors: #########
36

47
function filter_nonrxsys(network)

src/reaction_structure.jl

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
### Species Structure ###
2+
3+
# Catalyst specific symbolics to support SBML
4+
struct ParameterConstantSpecies end
5+
struct VariableBCSpecies end
6+
struct VariableSpecies end
7+
Symbolics.option_to_metadata_type(::Val{:isconstantspecies}) = ParameterConstantSpecies
8+
Symbolics.option_to_metadata_type(::Val{:isbcspecies}) = VariableBCSpecies
9+
Symbolics.option_to_metadata_type(::Val{:isspecies}) = VariableSpecies
10+
11+
"""
12+
Catalyst.isconstant(s)
13+
14+
Tests if the given symbolic variable corresponds to a constant species.
15+
"""
16+
isconstant(s::Num) = isconstant(MT.value(s))
17+
function isconstant(s)
18+
MT.getmetadata(s, ParameterConstantSpecies, false)
19+
end
20+
21+
"""
22+
Catalyst.isbc(s)
23+
24+
Tests if the given symbolic variable corresponds to a boundary condition species.
25+
"""
26+
isbc(s::Num) = isbc(MT.value(s))
27+
function isbc(s)
28+
MT.getmetadata(s, VariableBCSpecies, false)
29+
end
30+
31+
"""
32+
isspecies(s)
33+
34+
Tests if the given symbolic variable corresponds to a chemical species.
35+
"""
36+
isspecies(s::Num) = isspecies(MT.value(s))
37+
function isspecies(s)
38+
MT.getmetadata(s, VariableSpecies, false)
39+
end
40+
41+
"""
42+
tospecies(s)
43+
44+
Convert the given symbolic variable to be a species by adding the isspecies metadata.
45+
46+
Notes:
47+
- Will error if passed a parameter.
48+
"""
49+
function tospecies(s)
50+
MT.isparameter(s) &&
51+
error("Parameters can not be converted to species. Please pass a variable.")
52+
MT.setmetadata(s, VariableSpecies, true)
53+
end
54+
55+
# true for species which shouldn't change from the reactions, including non-species
56+
# variables
57+
drop_dynamics(s) = isconstant(s) || isbc(s) || (!isspecies(s))
58+
59+
60+
### Reaction Structure ###
61+
62+
63+
"""
64+
$(TYPEDEF)
65+
66+
One chemical reaction.
67+
68+
# Fields
69+
$(FIELDS)
70+
71+
# Examples
72+
73+
```julia
74+
using Catalyst
75+
t = default_t()
76+
@parameters k[1:20]
77+
@species A(t) B(t) C(t) D(t)
78+
rxs = [Reaction(k[1], nothing, [A]), # 0 -> A
79+
Reaction(k[2], [B], nothing), # B -> 0
80+
Reaction(k[3],[A],[C]), # A -> C
81+
Reaction(k[4], [C], [A,B]), # C -> A + B
82+
Reaction(k[5], [C], [A], [1], [2]), # C -> A + A
83+
Reaction(k[6], [A,B], [C]), # A + B -> C
84+
Reaction(k[7], [B], [A], [2], [1]), # 2B -> A
85+
Reaction(k[8], [A,B], [A,C]), # A + B -> A + C
86+
Reaction(k[9], [A,B], [C,D]), # A + B -> C + D
87+
Reaction(k[10], [A], [C,D], [2], [1,1]), # 2A -> C + D
88+
Reaction(k[11], [A], [A,B], [2], [1,1]), # 2A -> A + B
89+
Reaction(k[12], [A,B,C], [C,D], [1,3,4], [2, 3]), # A+3B+4C -> 2C + 3D
90+
Reaction(k[13], [A,B], nothing, [3,1], nothing), # 3A+B -> 0
91+
Reaction(k[14], nothing, [A], nothing, [2]), # 0 -> 2A
92+
Reaction(k[15]*A/(2+A), [A], nothing; only_use_rate=true), # A -> 0 with custom rate
93+
Reaction(k[16], [A], [B]; only_use_rate=true), # A -> B with custom rate.
94+
Reaction(k[17]*A*exp(B), [C], [D], [2], [1]), # 2C -> D with non constant rate.
95+
Reaction(k[18]*B, nothing, [B], nothing, [2]), # 0 -> 2B with non constant rate.
96+
Reaction(k[19]*t, [A], [B]), # A -> B with non constant rate.
97+
Reaction(k[20]*t*A, [B,C], [D],[2,1],[2]) # 2A +B -> 2C with non constant rate.
98+
]
99+
```
100+
101+
Notes:
102+
- `nothing` can be used to indicate a reaction that has no reactants or no products.
103+
In this case the corresponding stoichiometry vector should also be set to `nothing`.
104+
- The three-argument form assumes all reactant and product stoichiometric coefficients
105+
are one.
106+
"""
107+
struct Reaction{S, T}
108+
"""The rate function (excluding mass action terms)."""
109+
rate::Any
110+
"""Reaction substrates."""
111+
substrates::Vector
112+
"""Reaction products."""
113+
products::Vector
114+
"""The stoichiometric coefficients of the reactants."""
115+
substoich::Vector{T}
116+
"""The stoichiometric coefficients of the products."""
117+
prodstoich::Vector{T}
118+
"""The net stoichiometric coefficients of all species changed by the reaction."""
119+
netstoich::Vector{Pair{S, T}}
120+
"""
121+
`false` (default) if `rate` should be multiplied by mass action terms to give the rate law.
122+
`true` if `rate` represents the full reaction rate law.
123+
"""
124+
only_use_rate::Bool
125+
"""
126+
Contain additional data, such whenever the reaction have a specific noise-scaling expression for
127+
the chemical Langevin equation.
128+
"""
129+
metadata::Vector{Pair{Symbol, Any}}
130+
end
131+
132+
"""
133+
isvalidreactant(s)
134+
135+
Test if a species is valid as a reactant (i.e. a species variable or a constant parameter).
136+
"""
137+
isvalidreactant(s) = MT.isparameter(s) ? isconstant(s) : (isspecies(s) && !isconstant(s))
138+
139+
function Reaction(rate, subs, prods, substoich, prodstoich;
140+
netstoich = nothing, metadata = Pair{Symbol, Any}[],
141+
only_use_rate = metadata_only_use_rate_check(metadata), kwargs...)
142+
(isnothing(prods) && isnothing(subs)) &&
143+
throw(ArgumentError("A reaction requires a non-nothing substrate or product vector."))
144+
(isnothing(prodstoich) && isnothing(substoich)) &&
145+
throw(ArgumentError("Both substrate and product stochiometry inputs cannot be nothing."))
146+
147+
if isnothing(subs)
148+
prodtype = typeof(value(first(prods)))
149+
subs = Vector{prodtype}()
150+
!isnothing(substoich) &&
151+
throw(ArgumentError("If substrates are nothing, substrate stoichiometries have to be so too."))
152+
substoich = typeof(prodstoich)()
153+
else
154+
subs = value.(subs)
155+
end
156+
allunique(subs) ||
157+
throw(ArgumentError("Substrates can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated substrates instead."))
158+
S = eltype(substoich)
159+
160+
if isnothing(prods)
161+
prods = Vector{eltype(subs)}()
162+
!isnothing(prodstoich) &&
163+
throw(ArgumentError("If products are nothing, product stoichiometries have to be so too."))
164+
prodstoich = typeof(substoich)()
165+
else
166+
prods = value.(prods)
167+
end
168+
allunique(prods) ||
169+
throw(ArgumentError("Products can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated products instead."))
170+
T = eltype(prodstoich)
171+
172+
# try to get a common type for stoichiometry, using Any if have Syms
173+
stoich_type = promote_type(S, T)
174+
if stoich_type <: Num
175+
stoich_type = Any
176+
substoich′ = Any[value(s) for s in substoich]
177+
prodstoich′ = Any[value(p) for p in prodstoich]
178+
else
179+
substoich′ = (S == stoich_type) ? substoich : convert.(stoich_type, substoich)
180+
prodstoich′ = (T == stoich_type) ? prodstoich : convert.(stoich_type, prodstoich)
181+
end
182+
183+
if !(all(isvalidreactant, subs) && all(isvalidreactant, prods))
184+
badsts = union(filter(!isvalidreactant, subs), filter(!isvalidreactant, prods))
185+
throw(ArgumentError("""To be a valid substrate or product, non-constant species must be declared via @species, while constant species must be parameters with the isconstantspecies metadata. The following reactants do not follow this convention:\n $badsts"""))
186+
end
187+
188+
ns = if netstoich === nothing
189+
get_netstoich(subs, prods, substoich′, prodstoich′)
190+
else
191+
(netstoich_stoichtype(netstoich) != stoich_type) ?
192+
convert.(stoich_type, netstoich) : netstoich
193+
end
194+
195+
# Check that all metadata entries are unique. (cannot use `in` since some entries may be symbolics).
196+
if !allunique(entry[1] for entry in metadata)
197+
error("Repeated entries for the same metadata encountered in the following metadata set: $([entry[1] for entry in metadata]).")
198+
end
199+
200+
# Deletes potential `:only_use_rate => ` entries from the metadata.
201+
if any(:only_use_rate == entry[1] for entry in metadata)
202+
deleteat!(metadata, findfirst(:only_use_rate == entry[1] for entry in metadata))
203+
end
204+
205+
# Ensures metadata have the correct type.
206+
metadata = convert(Vector{Pair{Symbol, Any}}, metadata)
207+
208+
Reaction(value(rate), subs, prods, substoich′, prodstoich′, ns, only_use_rate, metadata)
209+
end
210+
211+
# Checks if a metadata input has an entry :only_use_rate => true
212+
function metadata_only_use_rate_check(metadata)
213+
only_use_rate_idx = findfirst(:only_use_rate == entry[1] for entry in metadata)
214+
isnothing(only_use_rate_idx) && return false
215+
return Bool(metadata[only_use_rate_idx][2])
216+
end
217+
218+
# three argument constructor assumes stoichiometric coefs are one and integers
219+
function Reaction(rate, subs, prods; kwargs...)
220+
sstoich = isnothing(subs) ? nothing : ones(Int, length(subs))
221+
pstoich = isnothing(prods) ? nothing : ones(Int, length(prods))
222+
Reaction(rate, subs, prods, sstoich, pstoich; kwargs...)
223+
end
224+
225+
function print_rxside(io::IO, specs, stoich)
226+
# reactants/substrates
227+
if isempty(specs)
228+
print(io, "")
229+
else
230+
for (i, spec) in enumerate(specs)
231+
prspec = (MT.isparameter(spec) || (MT.operation(spec) == getindex)) ?
232+
spec : MT.operation(spec)
233+
if isequal(stoich[i], one(stoich[i]))
234+
print(io, prspec)
235+
elseif istree(stoich[i])
236+
print(io, "(", stoich[i], ")*", prspec)
237+
else
238+
print(io, stoich[i], "*", prspec)
239+
end
240+
241+
(i < length(specs)) && print(io, " + ")
242+
end
243+
end
244+
nothing
245+
end
246+
247+
function Base.show(io::IO, rx::Reaction)
248+
print(io, rx.rate, ", ")
249+
print_rxside(io, rx.substrates, rx.substoich)
250+
arrow = rx.only_use_rate ? "" : "-->"
251+
print(io, " ", arrow, " ")
252+
print_rxside(io, rx.products, rx.prodstoich)
253+
end
254+
255+
function apply_if_nonempty(f, v)
256+
isempty(v) && return v
257+
s = similar(v)
258+
map!(f, s, v)
259+
s
260+
end
261+
262+
function ModelingToolkit.namespace_equation(rx::Reaction, name; kw...)
263+
f = Base.Fix2(namespace_expr, name)
264+
rate = f(rx.rate)
265+
subs = apply_if_nonempty(f, rx.substrates)
266+
prods = apply_if_nonempty(f, rx.products)
267+
substoich = apply_if_nonempty(f, rx.substoich)
268+
prodstoich = apply_if_nonempty(f, rx.prodstoich)
269+
netstoich = if isempty(rx.netstoich)
270+
rx.netstoich
271+
else
272+
ns = similar(rx.netstoich)
273+
map!(n -> f(n[1]) => f(n[2]), ns, rx.netstoich)
274+
end
275+
Reaction(rate, subs, prods, substoich, prodstoich, netstoich, rx.only_use_rate, rx.metadata)
276+
end
277+
278+
netstoich_stoichtype(::Vector{Pair{S, T}}) where {S, T} = T
279+
280+
# calculates the net stoichiometry of a reaction as a vector of pairs (sub,substoich)
281+
function get_netstoich(subs, prods, sstoich, pstoich)
282+
# stoichiometry as a Dictionary
283+
nsdict = Dict{Any, eltype(sstoich)}(sub => -sstoich[i] for (i, sub) in enumerate(subs))
284+
for (i, p) in enumerate(prods)
285+
coef = pstoich[i]
286+
@inbounds nsdict[p] = haskey(nsdict, p) ? nsdict[p] + coef : coef
287+
end
288+
289+
# stoichiometry as a vector
290+
[el for el in nsdict if !_iszero(el[2])]
291+
end
292+
293+
"""
294+
isbcbalanced(rx::Reaction)
295+
296+
True if any BC species in `rx` appears as a substrate and product with the same
297+
stoichiometry.
298+
"""
299+
function isbcbalanced(rx::Reaction)
300+
# any substrate BC must be a product with the same stoichiometry
301+
for (sidx, sub) in enumerate(rx.substrates)
302+
if isbc(sub)
303+
pidx = findfirst(Base.Fix1(isequal, sub), rx.products)
304+
(pidx === nothing) && return false
305+
isequal(rx.prodstoich[pidx], rx.substoich[sidx]) || return false
306+
end
307+
end
308+
309+
for prod in rx.products
310+
if isbc(prod)
311+
any(Base.Fix1(isequal, prod), rx.substrates) || return false
312+
end
313+
end
314+
315+
true
316+
end
317+
318+
### Reaction Acessor Functions ###
319+
320+
# Overwrites functions in ModelingToolkit to give the correct input.
321+
ModelingToolkit.is_diff_equation(rx::Reaction) = false
322+
ModelingToolkit.is_alg_equation(rx::Reaction) = false

0 commit comments

Comments
 (0)