Skip to content

Commit 50acd56

Browse files
authored
Merge pull request #856 from SciML/update_possible_Reaction_inputs
Update `Reaction` constructor
2 parents 14481ef + af9f801 commit 50acd56

File tree

3 files changed

+141
-69
lines changed

3 files changed

+141
-69
lines changed

src/reaction.jl

Lines changed: 59 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ function metadata_only_use_rate_check(metadata)
7272
return Bool(metadata[only_use_rate_idx][2])
7373
end
7474

75+
# Used to promote a vector to the appropriate type. Takes the `vec == Any[]` case into account by
76+
# returning an empty vector of the appropriate type.
77+
function promote_reaction_vector(vec, type)
78+
isempty(vec) && (return type[])
79+
type[value(v) for v in vec]
80+
end
81+
7582
# calculates the net stoichiometry of a reaction as a vector of pairs (sub,substoich)
7683
function get_netstoich(subs, prods, sstoich, pstoich)
7784
# stoichiometry as a Dictionary
@@ -85,9 +92,6 @@ function get_netstoich(subs, prods, sstoich, pstoich)
8592
[el for el in nsdict if !_iszero(el[2])]
8693
end
8794

88-
# Get the net stoichiometries' type.
89-
netstoich_stoichtype(::Vector{Pair{S, T}}) where {S, T} = T
90-
9195
### Reaction Structure ###
9296

9397
"""
@@ -134,19 +138,19 @@ Notes:
134138
- The three-argument form assumes all reactant and product stoichiometric coefficients
135139
are one.
136140
"""
137-
struct Reaction{S, T}
141+
struct Reaction{T}
138142
"""The rate function (excluding mass action terms)."""
139143
rate::Any
140144
"""Reaction substrates."""
141-
substrates::Vector
145+
substrates::Vector{Any}
142146
"""Reaction products."""
143-
products::Vector
147+
products::Vector{Any}
144148
"""The stoichiometric coefficients of the reactants."""
145-
substoich::Vector{T}
149+
substoich::Vector
146150
"""The stoichiometric coefficients of the products."""
147-
prodstoich::Vector{T}
151+
prodstoich::Vector
148152
"""The net stoichiometric coefficients of all species changed by the reaction."""
149-
netstoich::Vector{Pair{S, T}}
153+
netstoich::Vector{Pair{Any, T}}
150154
"""
151155
`false` (default) if `rate` should be multiplied by mass action terms to give the rate law.
152156
`true` if `rate` represents the full reaction rate law.
@@ -160,83 +164,74 @@ struct Reaction{S, T}
160164
end
161165

162166
# Five-argument constructor accepting rate, substrates, and products, and their stoichiometries.
163-
function Reaction(rate, subs, prods, substoich, prodstoich;
164-
netstoich = nothing, metadata = Pair{Symbol, Any}[],
165-
only_use_rate = metadata_only_use_rate_check(metadata), kwargs...)
166-
(isnothing(prods) && isnothing(subs)) &&
167-
throw(ArgumentError("A reaction requires a non-nothing substrate or product vector."))
168-
(isnothing(prodstoich) && isnothing(substoich)) &&
169-
throw(ArgumentError("Both substrate and product stochiometry inputs cannot be nothing."))
170-
171-
if isnothing(subs)
172-
prodtype = typeof(value(first(prods)))
173-
subs = Vector{prodtype}()
174-
!isnothing(substoich) &&
175-
throw(ArgumentError("If substrates are nothing, substrate stoichiometries have to be so too."))
176-
substoich = typeof(prodstoich)()
177-
else
178-
subs = value.(subs)
179-
end
167+
function Reaction(rate, subs::Vector, prods::Vector, substoich::Vector{S}, prodstoich::Vector{T};
168+
netstoich = [], metadata = Pair{Symbol, Any}[],
169+
only_use_rate = metadata_only_use_rate_check(metadata), kwargs...) where {S,T}
170+
# Error checks.
171+
isempty(subs) && isempty(prods) &&
172+
throw(ArgumentError("A reaction requires either a non-empty substrate or product vector."))
173+
length(subs) != length(substoich) &&
174+
throw(ArgumentError("The substrate vector ($(subs)) and the substrate stoichiometry vector ($(substoich)) must have equal length."))
175+
length(prods) != length(prodstoich) &&
176+
throw(ArgumentError("The product vector ($(prods)) and the product stoichiometry vector ($(prodstoich)) must have equal length."))
180177
allunique(subs) ||
181178
throw(ArgumentError("Substrates can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated substrates instead."))
182-
S = eltype(substoich)
183-
184-
if isnothing(prods)
185-
prods = Vector{eltype(subs)}()
186-
!isnothing(prodstoich) &&
187-
throw(ArgumentError("If products are nothing, product stoichiometries have to be so too."))
188-
prodstoich = typeof(substoich)()
189-
else
190-
prods = value.(prods)
191-
end
192179
allunique(prods) ||
193180
throw(ArgumentError("Products can not be repeated in the list provided to `Reaction`, please modify the stoichiometry for any repeated products instead."))
194-
T = eltype(prodstoich)
195181

196-
# try to get a common type for stoichiometry, using Any if have Syms
182+
# Ensures everything have uniform and correct types.
183+
subs = promote_reaction_vector(subs, BasicSymbolic{Real})
184+
prods = promote_reaction_vector(prods, BasicSymbolic{Real})
197185
stoich_type = promote_type(S, T)
198-
if stoich_type <: Num
199-
stoich_type = Any
200-
substoich′ = Any[value(s) for s in substoich]
201-
prodstoich′ = Any[value(p) for p in prodstoich]
202-
else
203-
substoich′ = (S == stoich_type) ? substoich : convert.(stoich_type, substoich)
204-
prodstoich′ = (T == stoich_type) ? prodstoich : convert.(stoich_type, prodstoich)
205-
end
186+
(stoich_type <: Num) && (stoich_type = Any)
187+
substoich = promote_reaction_vector(substoich, stoich_type)
188+
prodstoich = promote_reaction_vector(prodstoich, stoich_type)
206189

190+
# Checks that all reactants are valid.
207191
if !(all(isvalidreactant, subs) && all(isvalidreactant, prods))
208192
badsts = union(filter(!isvalidreactant, subs), filter(!isvalidreactant, prods))
209-
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"""))
193+
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"))
210194
end
211-
212-
ns = if netstoich === nothing
213-
get_netstoich(subs, prods, substoich′, prodstoich′)
214-
else
215-
(netstoich_stoichtype(netstoich) != stoich_type) ?
216-
convert.(stoich_type, netstoich) : netstoich
195+
196+
# Computes the net stoichiometries.
197+
if isempty(netstoich)
198+
netstoich = get_netstoich(subs, prods, substoich, prodstoich)
199+
elseif typeof(netstoich) != Vector{Pair{BasicSymbolic{Real}, stoich_type}}
200+
netstoich = Pair{BasicSymbolic{Real}, stoich_type}[
201+
value(ns[1]) => convert(stoich_type, ns[2]) for ns in netstoich]
217202
end
218203

219-
# Check that all metadata entries are unique. (cannot use `in` since some entries may be symbolics).
204+
# Handles metadata (check that all entries are unique, remove potential `only_use_rate`
205+
# entries, and converts to the required type.
220206
if !allunique(entry[1] for entry in metadata)
221207
error("Repeated entries for the same metadata encountered in the following metadata set: $([entry[1] for entry in metadata]).")
222208
end
223-
224-
# Deletes potential `:only_use_rate => ` entries from the metadata.
225209
if any(:only_use_rate == entry[1] for entry in metadata)
226210
deleteat!(metadata, findfirst(:only_use_rate == entry[1] for entry in metadata))
227211
end
228-
229-
# Ensures metadata have the correct type.
230212
metadata = convert(Vector{Pair{Symbol, Any}}, metadata)
231213

232-
Reaction(value(rate), subs, prods, substoich, prodstoich′, ns, only_use_rate, metadata)
214+
Reaction{stoich_type}(value(rate), subs, prods, substoich, prodstoich, netstoich, only_use_rate, metadata)
233215
end
234216

235-
# Three argument constructor assumes stoichiometric coefs are one and integers.
236-
function Reaction(rate, subs, prods; kwargs...)
237-
sstoich = isnothing(subs) ? nothing : ones(Int, length(subs))
238-
pstoich = isnothing(prods) ? nothing : ones(Int, length(prods))
239-
Reaction(rate, subs, prods, sstoich, pstoich; kwargs...)
217+
# Three-argument constructor. Handles the case where no stoichiometries is given
218+
# (by assuming that all stoichiometries are `1`).
219+
function Reaction(rate, subs::Vector, prods::Vector; kwargs...)
220+
Reaction(rate, subs, prods, ones(Int, length(subs)), ones(Int, length(prods)); kwargs...)
221+
end
222+
223+
# Handles cases where `nothing` is given (instead of an empty vector).
224+
function Reaction(rate, subs::Vector, prods::Nothing, substoich::Vector, prodstoich::Nothing; kwargs...)
225+
Reaction(rate, subs, Int64[], substoich, Int64[]; kwargs...)
226+
end
227+
function Reaction(rate, subs::Nothing, prods::Vector, substoich::Nothing, prodstoich::Vector; kwargs...)
228+
Reaction(rate, Int64[], prods, Int64[], prodstoich; kwargs...)
229+
end
230+
function Reaction(rate, subs::Vector, prods::Nothing; kwargs...)
231+
Reaction(rate, subs, Int64[]; kwargs...)
232+
end
233+
function Reaction(rate, subs::Nothing, prods::Vector; kwargs...)
234+
Reaction(rate, Int64[], prods; kwargs...)
240235
end
241236

242237
# Union type for `Reaction`s and `Equation`s.

test/reactionsystem_core/reaction.jl

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,94 @@ let
3636
@test issetequal(get_symbolics(rx4), [X, t, Y, η1, η2])
3737
end
3838

39+
### Reaction Constructor Tests ###
40+
41+
# Sets the default `t` to use.
42+
t = default_t()
43+
44+
# Checks that `Reaction`s can be sucesfully created using various complicated inputs.
45+
# Checks that the `Reaction`s have the correct type, and the correct net stoichiometries are generated.
46+
let
47+
# Declare symbolic variables.
48+
@parameters k n1 n2::Int32 x [isconstantspecies=true]
49+
@species X(t) Y(t) Z(t)
50+
@variables A(t)
51+
52+
# Creates `Reaction`s.
53+
rx1 = Reaction(k*A, [X], [])
54+
rx2 = Reaction(k*A, [x], [Y], [1.5], [1])
55+
rx3 = Reaction(k*A, [x, X], [], [n1 + n2, n2], [])
56+
rx4 = Reaction(k*A, [X, Y], [X, Y, Z], [2//3, 3], [1//3, 1, 2])
57+
rx5 = Reaction(k*A, [X, Y], [X, Y, Z], [2, 3], [1, n1, n2])
58+
rx6 = Reaction(k*A, [X], [x], [n1], [1])
59+
60+
# Check `Reaction` types.
61+
@test rx1 isa Reaction{Int64}
62+
@test rx2 isa Reaction{Float64}
63+
@test rx3 isa Reaction{Any}
64+
@test rx4 isa Reaction{Rational{Int64}}
65+
@test rx5 isa Reaction{Any}
66+
@test rx6 isa Reaction{Any}
67+
68+
# Check `Reaction` net stoichiometries.
69+
issetequal(rx1.netstoich, [X => -1])
70+
issetequal(rx2.netstoich, [x => -1.5, Y => 1.0])
71+
issetequal(rx3.netstoich, [x => -n1 - n2, X => -n2])
72+
issetequal(rx4.netstoich, [X => -1//3, Y => -2//1, Z => 2//1])
73+
issetequal(rx5.netstoich, [X => -1, Y => n1 - 3, Z => n2])
74+
issetequal(rx6.netstoich, [X => -n1, x => 1])
75+
end
76+
77+
# Tests that various `Reaction` constructors gives identical inputs.
78+
let
79+
# Declare symbolic variables.
80+
@parameters k n1 n2::Int32
81+
@species X(t) Y(t) Z(t)
82+
@variables A(t)
83+
84+
# Tests that the three-argument constructor generates correct result.
85+
@test Reaction(k*A, [X], [Y, Z]) == Reaction(k*A, [X], [Y, Z], [1], [1, 1])
86+
87+
# Tests that `[]` and `nothing` can be used interchangeably.
88+
@test Reaction(k*A, [X, Z], nothing) == Reaction(k*A, [X, Z], [])
89+
@test Reaction(k*A, nothing, [Y, Z]) == Reaction(k*A, [], [Y, Z])
90+
@test Reaction(k*A, [X, Z], nothing, [n1 + n2, 2], nothing) == Reaction(k*A, [X, Z], [], [n1 + n2, 2], [])
91+
@test Reaction(k*A, nothing, [Y, Z], nothing, [n1 + n2, 2]) == Reaction(k*A, [], [Y, Z], [], [n1 + n2, 2])
92+
end
93+
94+
# Tests that various incorrect inputs yields errors.
95+
let
96+
# Declare symbolic variables.
97+
@parameters k n1 n2::Int32
98+
@species X(t) Y(t) Z(t)
99+
@variables A(t)
100+
101+
# Neither substrates nor products.
102+
@test_throws ArgumentError Reaction(k*A, [], [])
103+
104+
# Substrate vector not of equal length to substrate stoichiometry vector.
105+
@test_throws ArgumentError Reaction(k*A, [X, X, Z], [], [1, 2], [])
106+
107+
# Product vector not of equal length to product stoichiometry vector.
108+
@test_throws ArgumentError Reaction(k*A, [], [X, X, Z], [], [1, 2])
109+
110+
# Repeated substrates.
111+
@test_throws ArgumentError Reaction(k*A, [X, X, Z], [])
112+
113+
# Repeated products.
114+
@test_throws ArgumentError Reaction(k*A, [], [Y, Z, Z])
115+
116+
# Non-valid reactants (parameter or variable).
117+
@test_throws ArgumentError Reaction(k*A, [], [A])
118+
@test_throws ArgumentError Reaction(k*A, [], [k])
119+
end
120+
39121
### Tests Metadata ###
40122

41123
# Tests creation.
42124
# Tests basic accessor functions.
43125
# Tests that repeated metadata entries are not permitted.
44126
let
45-
@variables t
46127
@parameters k
47128
@species X(t) X2(t)
48129

@@ -60,7 +141,6 @@ end
60141

61142
# Tests accessors for system without metadata.
62143
let
63-
@variables t
64144
@parameters k
65145
@species X(t) X2(t)
66146

@@ -77,7 +157,6 @@ end
77157
# Tests basic accessor functions.
78158
# Tests various metadata types.
79159
let
80-
@variables t
81160
@parameters k
82161
@species X(t) X2(t)
83162

@@ -109,7 +188,6 @@ end
109188

110189
# Tests the noise scaling metadata.
111190
let
112-
@variables t
113191
@parameters k η
114192
@species X(t) X2(t)
115193

test/reactionsystem_core/reactionsystem.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ end
140140
### Check ODE, SDE, and Jump Functions ###
141141

142142
# Test by evaluating drift and diffusion terms.
143-
144143
let
145144
u = rnd_u0(rs, rng)
146145
p = rnd_ps(rs, rng)

0 commit comments

Comments
 (0)