Skip to content

Commit 1a5f21d

Browse files
committed
add 4 test blocks
1 parent 1fee862 commit 1a5f21d

File tree

1 file changed

+216
-1
lines changed

1 file changed

+216
-1
lines changed

test/simulation_and_solving/hybrid_models.jl

Lines changed: 216 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,219 @@ end
109109
# end
110110
# Xsamp /= Nsims
111111
# @test abs(Xsamp - Xf(0.2, p) < 0.05 * Xf(0.2, p))
112-
# end
112+
# end
113+
114+
# Checks that a disjoint hybrid model (i.e. where the Jump and ODE parts do not interact) gives the
115+
# same results as simulating the two parts separately.
116+
let
117+
# Creates the disjoint ODE/Jump models, and the hybrid model combining both.
118+
rn_ode = @reaction_network begin
119+
A, ∅ X
120+
1, 2X + Y 3X
121+
B, X Y
122+
1, X
123+
end
124+
rn_jump = @reaction_network begin
125+
(p,d), 0 <--> V
126+
(k1,k2), V + W <--> VW
127+
end
128+
rn_hybrid = @reaction_network begin
129+
A, ∅ X, [physical_scale = PhysicalScale.ODE]
130+
1, 2X + Y 3X, [physical_scale = PhysicalScale.ODE]
131+
B, X Y, [physical_scale = PhysicalScale.ODE]
132+
1, X ∅, [physical_scale = PhysicalScale.ODE]
133+
(p,d), 0 <--> V
134+
(k1,k2), V + W <--> VW
135+
end
136+
137+
# Sets simulation conditions and creates problems corresponding to the different models.
138+
u0_ode = [:X => 1.0, :Y => 0.0]
139+
u0_jump = [:V => 1, :W => 20, :VW => 0.0]
140+
u0_hybrid = (u0_ode..., u0_jump...)
141+
ps_ode = [:A => 1.0, :B => 4.0]
142+
ps_jump = [:p => 2.0, :d => 0.1, :k1 => 0.2, :k2 => 1.0]
143+
ps_hybrid = [ps_ode; ps_jump]
144+
tspan = (0.0, 1000.0)
145+
ode_prob = ODEProblem(rn_ode, u0_ode, tspan, ps_ode)
146+
jump_prob = JumpProblem(JumpInputs(rn_jump, u0_jump, tspan, ps_jump); save_positions = (false,false))
147+
hybrid_prob = JumpProblem(JumpInputs(rn_hybrid, u0_hybrid, tspan, ps_hybrid); save_positions = (false,false))
148+
149+
# Performs simulations. Checks that ODE parts are identical. Check that jump parts have similar statistics.
150+
ode_sol = solve(ode_prob, Rosenbrock23(); saveat = 1.0, abstol = 1e-10, reltol = 1e-10)
151+
jump_sol = solve(jump_prob, SSAStepper(); saveat = 1.0)
152+
hybrid_sol = solve(hybrid_prob, Rosenbrock23(); saveat = 1.0, abstol = 1e-10, reltol = 1e-10)
153+
@test ode_sol[:Y] hybrid_sol[:Y] atol = 1e-4 rtol = 1e-4
154+
@test mean(jump_sol[:V]) mean(hybrid_sol[:V]) atol = 1e-1 rtol = 1e-1
155+
@test mean(jump_sol[:W]) mean(hybrid_sol[:W]) atol = 1e-1 rtol = 1e-1
156+
@test mean(jump_sol[:VW]) mean(hybrid_sol[:VW]) atol = 1e-1 rtol = 1e-1
157+
end
158+
159+
### Other Tests ###
160+
161+
# Checks that the full pipeline of symbolic accessing/updating problems/integrators/solutions
162+
# of hybrid models work.
163+
# CURRENTLY NOT WORKING DUE TO `remake` ISSUE WITH HYBRID MODELS.
164+
let
165+
# Creates model and initial problem (with defaults for one species and one parameter).
166+
rn_hybrid = @reaction_network begin
167+
@species X(t) = 1.0
168+
@parameters p = 1.0
169+
p, 0 --> X, [physical_scale = PhysicalScale.ODE]
170+
(kB,kD), 2X <--> X2
171+
k, X2 --> Y2, [physical_scale = PhysicalScale.ODE]
172+
(kB,kD), 2Y <--> Y2
173+
d, Y --> 0, [physical_scale = PhysicalScale.ODE]
174+
end
175+
u0 = [:X2 => 0.0, :Y => 0.0, :Y2 => 0.0]
176+
ps = [:kB => 2.0, :kD => 0.4, :k => 1.0, :d => 0.5]
177+
prob = JumpProblem(JumpInputs(rn_hybrid, u0, (0.0, 5.0), ps))
178+
179+
# Check problem content, updates problem with remake, checks again.
180+
@test prob[:X] == 1.0
181+
@test prob[:X2] == 0.0
182+
@test prob[:Y] == 0.0
183+
@test prob[:Y2] == 0.0
184+
@test prob.ps[:p] == 1.0
185+
@test prob.ps[:kB] == 2.0
186+
@test prob.ps[:kD] == 0.4
187+
@test prob.ps[:k] == 1.0
188+
@test prob.ps[:d] == 0.5
189+
prob = remake(prob; u0 = [:X => 3.0, :X2 => 2.0, :Y => 1.0], p = [:p => 5.0, :dD => 0.8, :k => 2.0])
190+
@test prob[:X] == 3.0
191+
@test prob[:X2] == 2.0
192+
@test prob[:Y] == 1.0
193+
@test prob[:Y2] == 0.0
194+
@test prob.ps[:p] == 5.0
195+
@test prob.ps[:kB] == 2.0
196+
@test prob.ps[:kD] == 0.8
197+
@test prob.ps[:k] == 2.0
198+
@test prob.ps[:d] == 0.5
199+
200+
# Creates, checks, updates, and checks an integrator.
201+
int = init(prob, Tsit5())
202+
@test int[:X] == 3.0
203+
@test int[:X2] == 2.0
204+
@test int[:Y] == 1.0
205+
@test int[:Y2] == 0.0
206+
@test int.ps[:p] == 5.0
207+
@test int.ps[:kB] == 2.0
208+
@test int.ps[:kD] == 0.8
209+
@test int.ps[:k] == 2.0
210+
@test int.ps[:d] == 0.5
211+
@test int[:X] = 4.0
212+
@test int[:X2] = 3.0
213+
@test int[:Y] = 2.0
214+
@test int.ps[:p] = 6.0
215+
@test int.ps[:kB] = 3.0
216+
@test int.ps[:kD] = 0.6
217+
@test int.ps[:k] = 3.0
218+
@test int[:X] == 4.0
219+
@test int[:X2] == 3.0
220+
@test int[:Y] == 2.0
221+
@test int[:Y2] == 0.0
222+
@test int.ps[:p] == 6.0
223+
@test int.ps[:kB] == 3.0
224+
@test int.ps[:kD] == 0.5
225+
@test int.ps[:k] == 3.0
226+
@test int.ps[:d] == 0.5
227+
228+
# Solves and checks values of solution.
229+
sol = solve(prob, Tsit5(); maxiters = 1, verbose = false)
230+
@test sol[:X][1] == 3.0
231+
@test sol[:X2][1] == 2.0
232+
@test sol[:Y][1] == 1.0
233+
@test sol[:Y2][1] == 0.0
234+
@test sol.ps[:p] == 5.0
235+
@test sol.ps[:kB] == 2.0
236+
@test sol.ps[:kD] == 0.8
237+
@test sol.ps[:k] == 2.0
238+
@test sol.ps[:d] == 0.5
239+
end
240+
241+
# Checks that various model options (observables, events, defaults and metadata, differential equations,
242+
# non-default_iv) works for hybrid models.
243+
let
244+
# Creates the model (X species is pure jump, Y is pure ODE, and Z1,Z2 are mixed).
245+
# Hybrid species have non-constant rates containing the two other species.
246+
rn = @reaction_network begin
247+
@ivs τ
248+
@differentials Δ = Differential(τ)
249+
@parameters X0 Y0
250+
@species X(τ) = X0 [description = "A jump only species"] Y(τ) = Y0 [description = "An ODE only species"]
251+
@observables Ztot ~ Z1 + Z2
252+
@discrete_events [1.0] => [Z1 ~ Z1 + 1.0]
253+
@continuous_events [Y ~ 1.0] => [Y ~ 5.0]
254+
@equations Δ(V) ~ Z1 + X^2 - V
255+
(p,d), 0 <--> X
256+
d, Y --> 0, [physical_scale = PhysicalScale.ODE]
257+
(k*X, k*Y), Z1 <--> Z2, ([physical_scale = PhysicalScale.ODE], [physical_scale = PhysicalScale.Jump])
258+
end
259+
260+
# Simulates the model.
261+
u0 = [:Z1 => 1.0, :Z2 => 2.0, :V => 1.5]
262+
ps = [:p => 2.0, :d => 0.5, :k => 1.0, :X0 => 0.1, :Y0 => 4.0]
263+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 10.0), ps))
264+
sol = solve(prob, Tsit5(); tstops = [1.0 - eps(), 1.0 + eps()])
265+
266+
# Tests that the model contain the correct stuff.
267+
@test ModelingToolkit.getdescription(rn.X) == "A jump only species"
268+
@test ModelingToolkit.getdescription(rn.Y) == "An ODE only species"
269+
@test sol[:X][1] == sol.ps[:X0] == 0.1
270+
@test sol[:Y][1] == sol.ps[:Y0] == 4.0
271+
@test sol[:V][1] == 1.5
272+
@test sol[:Ztot] sol[rn.Z1 + rn.Z2]
273+
@test minimum(sol[:Y]) 1.0
274+
@test maximum(sol[:Y]) 5.0 atol = 1e-1 rtol = 1e-1
275+
@test all(isequal([rn.τ], Symbolics.arguments(Symbolics.value(u))) for u in unknowns(rn))
276+
@test sol(1.0 - eps(); idxs = :Z1) + 1 sol(1.0 + eps(); idxs = :Z1)
277+
end
278+
279+
# Checks the types of species when various combinations of default/non-default types are used.
280+
let
281+
# Creates model and parameter set. Model have one pure ODE and one pure Jump species.
282+
rn = @reaction_network begin
283+
d, X --> 0
284+
d, Y --> 0, [physical_scale = PhysicalScale.ODE]
285+
end
286+
ps = [:d => 0.5]
287+
288+
# Checks case: X Int64 (default), Y Float64 (default). Everything is converted to Float64.
289+
u0 = (:X => 1, :Y => 1.0)
290+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 1.0), ps))
291+
int = init(prob, Tsit5())
292+
sol = solve(prob, Tsit5())
293+
@test typeof(prob[:X]) == typeof(int[:X]) == typeof(sol[:X][end]) == Float64
294+
@test typeof(prob[:Y]) == typeof(int[:Y]) == typeof(sol[:Y][end]) == Float64
295+
296+
# Checks case: X Int32 (non-default), Y Float64 (default). Everything is converted to Float64.
297+
u0 = (:X => Int32(1), :Y => 1.0)
298+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 1.0), ps))
299+
int = init(prob, Tsit5())
300+
sol = solve(prob, Tsit5())
301+
@test typeof(prob[:X]) == typeof(int[:X]) == typeof(sol[:X][end]) == Float64
302+
@test typeof(prob[:Y]) == typeof(int[:Y]) == typeof(sol[:Y][end]) == Float64
303+
304+
# Checks case: X Int64 (default), Y Float32 (non-default). Everything is converted to Float64.
305+
u0 = (:X => 1, :Y => 1.0f0)
306+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 1.0), ps))
307+
int = init(prob, Tsit5())
308+
sol = solve(prob, Tsit5())
309+
@test typeof(prob[:X]) == typeof(int[:X]) == typeof(sol[:X][end]) == Float64
310+
@test typeof(prob[:Y]) == typeof(int[:Y]) == typeof(sol[:Y][end]) == Float64
311+
312+
# Checks case: X Int32 (non-default), Y Float32 (non-default). Everything is converted to Float64.
313+
u0 = (:X => Int32(1), :Y => 1.0f0)
314+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 1.0), ps))
315+
int = init(prob, Tsit5())
316+
sol = solve(prob, Tsit5())
317+
@test typeof(prob[:X]) == typeof(int[:X]) == typeof(sol[:X][end]) == Float64
318+
@test typeof(prob[:Y]) == typeof(int[:Y]) == typeof(sol[:Y][end]) == Float64
319+
320+
# Checks case: X Float32 (non-default float), Y Float32 (non-default). Everything is converted to Float32.
321+
u0 = (:X => 1.0f0, :Y => 1.0f0)
322+
prob = JumpProblem(JumpInputs(rn, u0, (0.0, 1.0), ps))
323+
int = init(prob, Tsit5())
324+
sol = solve(prob, Tsit5())
325+
@test typeof(prob[:X]) == typeof(int[:X]) == typeof(sol[:X][end]) == Float32
326+
@test typeof(prob[:Y]) == typeof(int[:Y]) == typeof(sol[:Y][end]) == Float32
327+
end

0 commit comments

Comments
 (0)