Skip to content

Commit 55e0a51

Browse files
committed
Merge remote-tracking branch 'origin/master' into expand_hills
2 parents 1e3bb24 + f5b92ab commit 55e0a51

24 files changed

+590
-112
lines changed

HISTORY.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@
7676
functions (as a parameter) in a model. For more details on how to use these,
7777
please read:
7878
https://docs.sciml.ai/Catalyst/stable/model_creation/functional_parameters/.
79+
- We have introduced a restriction on bundling of reactions in the DSL. Now,
80+
bundling is not permitted if multiple rates are provided but only one set each
81+
of substrates/products. E.g. this model:
82+
```julia
83+
@reaction_network begin
84+
(k1,k2), X --> Y
85+
end
86+
```
87+
will now throw an error. The reason that users attempting to write bi-directional
88+
reactions but typing `-->` instead of `<-->` would get a wrong model. We decided that
89+
this kind of bundling was unlikely to be used, and throwing errors for people who
90+
made the typo was more important.
91+
92+
If you use this type of bundling and it indeed is useful to you, please raise and issue
93+
and we will see if we can sort something out.
7994
- Scoped species/variables/parameters are now treated similar to the latest MTK
8095
releases ( 9.49).
8196
- A tutorial on making interactive plot displays using Makie has been added.

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ BifurcationKit = "0.4.4"
5252
CairoMakie = "0.12, 0.13"
5353
Catalyst = "14.4"
5454
DataFrames = "1.6"
55+
DataInterpolations = "7.2, 8"
5556
DiffEqBase = "6.159.0"
5657
Distributions = "0.25"
57-
DataInterpolations = "7.2"
5858
Documenter = "1.4.1"
5959
DynamicalSystems = "3.3"
6060
GlobalSensitivity = "2.6"

docs/pages.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ pages = Any[
2323
"model_creation/examples/basic_CRN_library.md",
2424
"model_creation/examples/programmatic_generative_linear_pathway.md",
2525
"model_creation/examples/hodgkin_huxley_equation.md",
26-
"model_creation/examples/smoluchowski_coagulation_equation.md"
26+
"model_creation/examples/smoluchowski_coagulation_equation.md",
27+
"model_creation/examples/noise_modelling_approaches.md",
2728
]
2829
],
2930
"Model simulation and visualization" => Any[
@@ -35,6 +36,7 @@ pages = Any[
3536
"model_simulation/sde_simulation_performance.md",
3637
"Examples" => Any[
3738
"model_simulation/examples/periodic_events_simulation.md",
39+
"model_simulation/examples/activation_time_distribution_measurement.md",
3840
"model_simulation/examples/interactive_brusselator_simulation.md"
3941
]
4042
],
@@ -43,7 +45,11 @@ pages = Any[
4345
"steady_state_functionality/nonlinear_solve.md",
4446
"steady_state_functionality/steady_state_stability_computation.md",
4547
"steady_state_functionality/bifurcation_diagrams.md",
46-
"steady_state_functionality/dynamical_systems.md"
48+
"steady_state_functionality/dynamical_systems.md",
49+
"Examples" => Any[
50+
"steady_state_functionality/examples/bifurcationkit_periodic_orbits.md"
51+
"steady_state_functionality/examples/bifurcationkit_codim2.md"
52+
]
4753
],
4854
"Inverse problems" => Any[
4955
"inverse_problems/petab_ode_param_fitting.md",

docs/src/faqs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ conserved constant(s), `Γ`, are updated. As an example consider the following
339339
using Catalyst, NonlinearSolve
340340
rn = @reaction_network begin
341341
(k₁,k₂), X₁ <--> X₂
342-
(k₃,k₄), X₁ + X₂ --> 2X₃
342+
(k₃,k₄), X₁ + X₂ <--> 2X₃
343343
end
344344
u0 = [:X₁ => 1.0, :X₂ => 2.0, :X₃ => 3.0]
345345
ps = [:k₁ => 0.1, :k₂ => 0.2, :k₃ => 0.3, :k₄ => 0.4]

docs/src/model_creation/dsl_basics.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ end
188188
```
189189
However, like for the above model, bundling reactions too zealously can reduce (rather than improve) a model's readability.
190190

191+
The one exception to reaction bundling is that we do not permit the user to provide multiple rates but only set one set each for the substrates and products. I.e.
192+
```julia
193+
rn = @reaction_network begin
194+
(k1,k2), X --> Y
195+
end
196+
```
197+
is not permitted (due to this notation's similarity to a bidirectional reaction). However, if multiples are provided for substrates and/or products, like `(k1,k2), (X1,X2) --> Y`, then bundling works.
198+
199+
191200
## [Non-constant reaction rates](@id dsl_description_nonconstant_rates)
192201
So far we have assumed that all reaction rates are constant (being either a number of a parameter). Non-constant rates that depend on one (or several) species are also possible. More generally, the rate can be any valid expression of parameters and species.
193202

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# [Approaches for modelling system noise](@id noise_modelling_approaches)
2+
Catalyst's primary tools for modelling stochasticity include the creation of `SDEProblem`s or `JumpProblem`s from reaction network models. However, other approaches for incorporating model noise exist, some of which will be discussed here. We will first consider *intrinsic* and *extrinsic* noise. These are well-established terms, both of which we will describe below (however, to our knowledge, no generally agreed-upon definition of these terms exists)[^1]. Finally, we will demonstrate a third approach, the utilisation of a noisy input process to an otherwise deterministic system. This approach is infrequently used, however, as it is encountered in the literature, we will demonstrate it here as well.
3+
4+
We note that these approaches can all be combined. E.g. an intrinsic noise model (using an SDE) can be combined with extrinsic noise (using randomised parameter values), while also feeding a noisy input process into the system.
5+
6+
!!! note
7+
Here we use intrinsic and extrinsic noise as descriptions of two of our modelling approaches. It should be noted that while these are established terminologies for noisy biological systems[^1], our use of these terms to describe different approaches for modelling noise is only inspired by this terminology, and nothing that is established in the field. Please consider the [references](@ref noise_modelling_approaches_references) for more information on intrinsic and extrinsic noise.
8+
9+
## [The repressilator model](@id noise_modelling_approaches_model_intro)
10+
For this tutorial we will use the oscillating [repressilator](@ref basic_CRN_library_repressilator) model.
11+
```@example noise_modelling_approaches
12+
using Catalyst
13+
repressilator = @reaction_network begin
14+
    hillr(Z,v,K,n), ∅ --> X
15+
    hillr(X,v,K,n), ∅ --> Y
16+
    hillr(Y,v,K,n), ∅ --> Z
17+
d, (X, Y, Z) --> ∅
18+
end
19+
```
20+
21+
## [Using intrinsic noise](@id noise_modelling_approaches_model_intrinsic)
22+
Generally, intrinsic noise is randomness inherent to a system itself. This means that it cannot be controlled for, or filtered out by, experimental settings. Low-copy number cellular systems, were reaction occurs due to the encounters of molecules due to random diffusion, is an example of intrinsic noise. In practise, this can be modelled exactly through [SDE](@ref simulation_intro_SDEs) (chemical Langevin equations) or [jump](@ref simulation_intro_jumps) (stochastic chemical kinetics) simulations.
23+
24+
In Catalyst, intrinsic noise is accounted for whenever an `SDEProblem` or `JumpProblem` is created and simulated. Here we will model intrinsic noise through SDEs, which means creating an `SDEProblem` using the standard approach.
25+
```@example noise_modelling_approaches
26+
u0 = [:X => 45.0, :Y => 20.0, :Z => 20.0]
27+
tend = 200.0
28+
ps = [:v => 10.0, :K => 20.0, :n => 3, :d => 0.1]
29+
sprob = SDEProblem(repressilator, u0, tend, ps)
30+
nothing # hide
31+
```
32+
Next, to illustrate the system's noisiness, we will perform multiple simulations. We do this by [creating an `EnsembleProblem`](@ref ensemble_simulations_monte_carlo). From it, we perform, and plot, 4 simulations.
33+
```@example noise_modelling_approaches
34+
using StochasticDiffEq, Plots
35+
eprob_intrinsic = EnsembleProblem(sprob)
36+
sol_intrinsic = solve(eprob_intrinsic, ImplicitEM(); trajectories = 4)
37+
plot(sol_intrinsic; idxs = :X)
38+
```
39+
Here, each simulation is performed from the same system using the same settings. Despite this, due to the noise, the individual trajectories are different.
40+
41+
## [Using extrinsic noise](@id noise_modelling_approaches_model_extrinsic)
42+
Next, we consider extrinsic noise. This is randomness caused by stochasticity external to, yet affecting, a system. Examples could be different bacteria experiencing different microenvironments or cells being in different parts of the cell cycle. This is noise which (in theory) can be controlled for experimentally (e.g. by ensuring a uniform environment). Whenever a specific source of noise is intrinsic and extrinsic to a system may depend on how one defines the system itself (this is a reason why giving an exact definition of these terms is difficult).
43+
44+
In Catalyst we can model extrinsic noise by letting the model parameters be probability distributions. Here, at the beginning of each simulation, random parameter values are drawn from their distributions. Let us imagine that our repressilator circuit was inserted into a bacterial population. Here, while each bacteria would have the same circuit, their individual number of e.g. ribosomes (which will be random) might affect the production rates (which while constant within each bacteria, might differ between the individuals).
45+
46+
Again we will perform ensemble simulation. Instead of creating an `SDEProblem`, we will create an `ODEProblem`, as well as a [problem function](@ref ensemble_simulations_varying_conditions) which draws random parameter values for each simulation. Here we have implemented the parameter's probability distributions as [normal distributions](https://en.wikipedia.org/wiki/Normal_distribution) using the [Distributions.jl](https://github.com/JuliaStats/Distributions.jl) package.
47+
```@example noise_modelling_approaches
48+
using Distributions
49+
p_dists = Dict([:v => Normal(10.0, 2.0), :K => Normal(20.0, 5.0), :n => Normal(3, 0.2), :d => Normal(0.1, 0.02)])
50+
function prob_func(prob, i, repeat)
51+
p = [par => rand(p_dists[par]) for par in keys(p_dists)]
52+
return remake(prob; p)
53+
end
54+
nothing # hide
55+
```
56+
Next, we again perform 4 simulations. While the individual trajectories are performed using deterministic simulations, the randomised parameter values create heterogeneity across the ensemble.
57+
```@example noise_modelling_approaches
58+
using OrdinaryDiffEqDefault
59+
oprob = ODEProblem(repressilator, u0, tend, ps)
60+
eprob_extrinsic = EnsembleProblem(oprob; prob_func)
61+
sol_extrinsic = solve(eprob_extrinsic; trajectories = 4)
62+
plot(sol_extrinsic; idxs = :X)
63+
```
64+
We note that a similar approach can be used to also randomise the initial conditions. In a very detailed model, the parameter values could fluctuate during a single simulation, something which could be implemented using the approach from the next section.
65+
66+
## [Using a noisy input process](@id noise_modelling_approaches_model_input_noise)
67+
Finally, we will consider the case where we have a deterministic system, but which is exposed to a noisy input process. One example could be a [light sensitive system, where the amount of experienced sunlight is stochastic due to e.g. variable cloud cover](@ref functional_parameters_circ_rhythm). Practically, this can be considered as extrinsic noise, however, we will generate the noise using a different approach from in the previous section. Here, we pre-simulate a random process in time, which we then feed into the system as a functional, time-dependent, parameter. A more detailed description of functional parameters can be found [here](@ref time_dependent_parameters).
68+
69+
We assume that our repressilator has an input, which corresponds to the $K$ value that controls $X$'s production. First we create a function, `make_K_series`, which creates a randomised time series representing $K$'s value over time.
70+
```@example noise_modelling_approaches
71+
using DataInterpolations
72+
function make_K_series(; K_mean = 20.0, n = 500, θ = 0.01)
73+
t_samples = range(0.0, stop = tend, length = n)
74+
K_series = fill(K_mean, n)
75+
for i = 2:n
76+
K_series[i] = K_series[i-1] + (rand() - 0.5) - θ*(K_series[i-1] - K_mean)
77+
    end
78+
    return LinearInterpolation(K_series, t_samples)
79+
end
80+
plot(make_K_series())
81+
```
82+
Next, we create an updated repressilator model, where the input $K$ value is modelled as a time-dependent parameter.
83+
```@example noise_modelling_approaches
84+
@parameters (K_in::typeof(make_K_series()))(..)
85+
K_in = K_in(default_t())
86+
repressilator_Kin = @reaction_network begin
87+
    hillr(Z,v,$K_in,n), ∅ --> X
88+
    hillr(X,v,K,n), ∅ --> Y
89+
    hillr(Y,v,K,n), ∅ --> Z
90+
d, (X, Y, Z) --> ∅
91+
end
92+
nothing # hide
93+
```
94+
Finally, we will again perform ensemble simulations of our model. This time, at the beginning of each simulation, we will use `make_K_series` to generate a new $K$, and set this as the `K_in` parameter's value.
95+
```@example noise_modelling_approaches
96+
function prob_func_Kin(prob, i, repeat)
97+
p = [ps; :K_in => make_K_series()]    
98+
    return ODEProblem(repressilator_Kin, prob.u0, prob.tspan, p)
99+
end
100+
oprob = ODEProblem(repressilator_Kin, u0, tend, [ps; :K_in => make_K_series()])
101+
eprob_inputnoise = EnsembleProblem(oprob; prob_func = prob_func_Kin)
102+
sol_inputnoise = solve(eprob_inputnoise; trajectories = 4)
103+
plot(sol_inputnoise; idxs = :X)
104+
```
105+
Like in the previous two cases, this generates heterogeneous trajectories across our ensemble.
106+
107+
108+
## [Investigating the mean of noisy oscillations](@id noise_modelling_approaches_model_noisy_oscillation_mean)
109+
Finally, we will observe an interesting phenomenon for ensembles of stochastic oscillators. First, we create ensemble simulations with a larger number of trajectories.
110+
```@example noise_modelling_approaches
111+
sol_intrinsic = solve(eprob_intrinsic, ImplicitEM(); trajectories = 200)
112+
sol_extrinsic = solve(eprob_extrinsic; trajectories = 200)
113+
nothing # hide
114+
```
115+
Next, we can use the `EnsembleSummary` interface to plot each ensemble's mean activity (as well as 5% and 95% quantiles) over time:
116+
```@example noise_modelling_approaches
117+
e_sumary_intrinsic = EnsembleAnalysis.EnsembleSummary(sol_intrinsic, 0.0:1.0:tend)
118+
e_sumary_extrinsic = EnsembleAnalysis.EnsembleSummary(sol_extrinsic, 0.0:1.0:tend)
119+
plot(e_sumary_intrinsic; label = "Intrinsic noise", idxs = 1)
120+
plot!(e_sumary_extrinsic; label = "Extrinsic noise", idxs = 1)
121+
```
122+
Here we can see that, over time, the systems' mean $X$ activity reaches a constant level around $30$.
123+
124+
This is a well-known phenomenon (especially in circadian biology[^2]). Here, as stochastic oscillators evolve from a common initial condition the mean behaves as a damped oscillator. This can be caused by two different phenomena:
125+
- The individual trajectories are themselves damped.
126+
- The individual trajectories's phases get de-synchronised.
127+
However, if we only observe the mean behaviour (and not the single trajectories), we cannot know which of these cases we are encountering. Here, by checking the single-trajectory plots from the previous sections, we note that this is due to trajectory de-synchronisation. Stochastic oscillators have often been cited as a reason for the importance to study cellular systems at the *single-cell* level, and not just in bulk.
128+
129+
---
130+
## [References](@id noise_modelling_approaches_references)
131+
[^1]: [Michael B. Elowitz, Arnold J. Levine, Eric D. Siggia, Peter S. Swain, *Stochastic Gene Expression in a Single Cell*, Science (2002).](https://www.science.org/doi/10.1126/science.1070919)
132+
[^2]: [Qiong Yang, Bernardo F. Pando, Guogang Dong, Susan S. Golden, Alexander van Oudenaarden, *Circadian Gating of the Cell Cycle Revealed in Single Cyanobacterial Cells*, Science (2010).](https://www.science.org/doi/10.1126/science.1181759)

docs/src/model_creation/functional_parameters.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ plot(sol)
3434
!!! note
3535
For this simple example, $(2 + t)/(1 + t)$ could have been used directly as a reaction rate (or written as a normal function), technically making the functional parameter approach unnecessary. However, here we used this function as a simple example of how discrete data can be made continuous using DataInterpolations, and then have its values inserted using a (functional) parameter.
3636

37+
3738
## [Inserting a customised, time-dependent, input](@id functional_parameters_circ_rhythm)
3839
Let us now go through everything again, but providing some more details. Let us first consider the input parameter. We have previously described how a [time-dependent rate can model a circadian rhythm](@ref dsl_description_nonconstant_rates_time). For real applications, due to e.g. clouds, sunlight is not a perfect sine wave. Here, a common solution is to take real sunlight data from some location and use in the model. Here, we will create synthetic (noisy) data as our light input:
3940
```@example functional_parameters_circ_rhythm

0 commit comments

Comments
 (0)