Skip to content

Commit dce78c4

Browse files
committed
init
1 parent 06aed2e commit dce78c4

File tree

2 files changed

+54
-0
lines changed

2 files changed

+54
-0
lines changed

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pages = Any[
3434
"model_simulation/sde_simulation_performance.md",
3535
"Examples" => Any[
3636
"model_simulation/examples/periodic_events_simulation.md",
37+
"model_simulation/examples/activation_time_distribution_measurement.md",
3738
"model_simulation/examples/interactive_brusselator_simulation.md"
3839
]
3940
],
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# [Measuring the Distribution of System Activation Times](@id activation_time_distribution_measurement)
2+
In this example we will consider a model which, while initially inactive, activates in response to an input. The model is *stochastic*, causing the activation times to be *random*. By combining events, callbacks, and stochastic ensemble simulations, we will measure the probability distribution of the activation times (so called[*first passage times](https://en.wikipedia.org/wiki/First-hitting-time_model)).
3+
4+
Our model will be a version of the [simple self-activation loop](@ref basic_CRN_library_self_activation) (the ensemble simulations of we have [considered previously](@ref ensemble_simulations_monte_carlo)). Here, we will consider the activation threshold parameter ($K$) to be activated by an input (at time $t = 0$). Before the input, $K$ is very large (essentially keeping the system inactive). After the input, it is reduced to a lower value (which permits the activation of the system). We will model this using two additional parameters ($Kᵢ$ and $Kₐ$, describing the pre and post-activation values of $K$, respectively). Initially, $K$ will [default to](@ref dsl_advanced_options_default_vals) $Kᵢ$. Next, at the activation time ($t = 0$), a discrete event will change $K$'s value to $Kᵢ$.
5+
```@example activation_time_distribution_measurement
6+
sa_model = @reaction_network begin
7+
@parameters Kᵢ Kₐ K = Kᵢ
8+
@discrete_events [0.0] => [K ~ Kₐ]
9+
v0 + hill(X,v,K,n), 0 --> X
10+
deg, X --> 0
11+
end
12+
```
13+
Next, to perform stochastic simulations of the system we will create an `SDEProblem`. Here, we will need to assign parameter values to $Kᵢ$ and $Kₐ$, but not to $K$ (as its value is controlled by its default and the event). Also note that we start the simulation at a time $t < 0$. This ensures that by the input time ($t = 0$), the system have (more or less) reached its (inactive state) steady state distribution. It also means that the activation time can be measured exactly as the system time (as this is the time from the input at $t = 0$).
14+
```@example activation_time_distribution_measurement
15+
u0 = [:X => 10.0]
16+
tspan = (-200.0, 2000.0)
17+
ps = [:v0 => 0.1, :v => 2.5, :Kᵢ => 1000.0, :Kₐ => 40.0, :n => 3.0, :deg => 0.01]
18+
sprob = SDEProblem(sa_model, u0, tspan, ps)
19+
nothing # hide
20+
```
21+
We can now create a simple `EnsembleProblem` and perform an ensemble simulation (as described [here](@ref ensemble_simulations)). Please note that the system contain an event which modifies its parameters, hence we must add the `safetycopy = true` argument to `EnsembleProblem` (else, subsequent simulations would start with $K = Kₐ$).
22+
```@example activation_time_distribution_measurement
23+
using Plots, StochasticDiffEq
24+
eprob = EnsembleProblem(sprob; safetycopy = true)
25+
esol = solve(eprob, ImplicitEM(); trajectories = 10)
26+
plot(esol)
27+
```
28+
Here we see how, after the input time, the system (randomly) switches from the inactive state to the active one (several examples of this, bistability-based, activation have been studied in the literature, both in models and experiments).
29+
30+
Next, we wish to measure the distribution of these activation times. First we will create a [callback](@ref https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/) which terminates the simulation once it have reached a threshold. This both ensures that we do not have to expend unnecessary computer time on the simulation after its activation, and also enables us to measure the activation time as the final time point of the simulation. Here we will use a [discrete callback](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#SciMLBase.DiscreteCallback) (for an ODE simulation, a [continuous callback](https://docs.sciml.ai/DiffEqDocs/stable/features/callback_functions/#ContinuousCallback) would instead have been appropriate, however, these combine less well with stochastic models). From looking at the previous simulations, $X = 100$ seems like a good threshold between the inactive and active state, so we will use this as our activation threshold. We use the `terminate!` function to terminate the simulation once the activation threshold has been reached.
31+
```@example activation_time_distribution_measurement
32+
condition(u, t, integrator) = integrator[:X] > 100.0
33+
affect!(integrator) = terminate!(integrator)
34+
callback = DiscreteCallback(condition, affect!)
35+
nothing # hide
36+
```
37+
Next, we will perform our ensemble simulation. By default, for each simulation, these save the full trajectory. Here, however, we are only interested in teh activation time. To do this, we can utilise an [*output function*](https://docs.sciml.ai/DiffEqDocs/dev/features/ensemble/#Building-a-Problem). This will be called at the end of each single simulation and determine what to save (it can also be used to potentially rerun individual simulations, however, we will not use this feature here). Here we create an output function which saves only the simulation final time point. We also make it throw a warning if the simulation reached the final time point (which indicates a simulation that never activated, the occurrences of such simulation would cause us to underestimate the activation times). Finally, just like previously, we must set `safetycopy = true`.
38+
```@example activation_time_distribution_measurement
39+
function output_func(sol, i)
40+
(sol.t[end] == tspan[2]) && @warn "A simulation did not activate during the given time span."
41+
return (sol.t[end], false)
42+
end
43+
eprob = EnsembleProblem(sprob; output_func, safetycopy = true)
44+
esol = solve(eprob, STrapezoid(); trajectories = 250, callback)
45+
nothing # hide
46+
```
47+
Finally, we can plot the distribution of activation times directly. For this, we will use the StatsPlots.jl package's `density` function (essentially a smoothed histogram). The input to `density` is the activation times (which our output function will save to `esol.u`).
48+
49+
```@example activation_time_distribution_measurement
50+
using StatsPlots
51+
density(esol.u; label = "Activation times")
52+
```
53+
Here we that the activation times take some form of long tail distribution (for non-trivial models, it is generally not possible to identify the activation times as any known statistical distribution).

0 commit comments

Comments
 (0)