Skip to content

Commit a86c550

Browse files
Merge branch 'SciML:master' into testdocs
2 parents c7ce86d + 5554c89 commit a86c550

File tree

6 files changed

+32
-26
lines changed

6 files changed

+32
-26
lines changed

HISTORY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Breaking updates and feature summaries across releases
22

33
## Catalyst unreleased (master branch)
4-
4+
- Due to changes in upstream packages, the structural identifiability extension is currently broken.
55

66
## Catalyst 14.4.1
77
- Support for user-defined functions on the RHS when providing coupled equations

docs/src/inverse_problems/structural_identifiability.md

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# [Structural Identifiability Analysis](@id structural_identifiability)
2+
!!! note
3+
Due to recent changes in upstream packages, this Catalyst feature is currently broken on Catalyst v14.
4+
25
During parameter fitting, parameter values are inferred from data. Parameter identifiability refers to whether inferring parameter values for a given model is mathematically feasible. Ideally, parameter fitting should always be accompanied with an identifiability analysis of the problem.
36

47
Identifiability can be divided into *structural* and *practical* identifiability[^1]. Structural identifiability considers only the mathematical model, and which parameters are and are not inherently identifiable due to model structure. Practical identifiability also considers the available data, and determines what system quantities can be inferred from it. In the idealised case of an infinite amount of non-noisy data, practical identifiability converges to structural identifiability. Generally, structural identifiability is assessed before parameters are fitted, while practical identifiability is assessed afterwards.
@@ -31,7 +34,7 @@ Global identifiability can be assessed using the `assess_identifiability` functi
3134
- Unidentifiable.
3235

3336
To it, we provide our `ReactionSystem` model and a list of quantities that we are able to measure. Here, we consider a Goodwind oscillator (a simple 3-component model, where the three species $M$, $E$, and $P$ are produced and degraded, which may exhibit oscillations)[^2]. Let us say that we are able to measure the concentration of $M$, we then designate this using the `measured_quantities` argument. We can now assess identifiability in the following way:
34-
```@example si1
37+
```julia
3538
using Catalyst, Logging, StructuralIdentifiability
3639
gwo = @reaction_network begin
3740
(pₘ/(1+P), dₘ), 0 <--> M
@@ -43,14 +46,14 @@ assess_identifiability(gwo; measured_quantities = [:M], loglevel = Logging.Error
4346
From the output, we find that `E(t)`, `pₑ`, and `pₚ` (the trajectory of $E$, and the production rates of $E$ and $P$, respectively) are non-identifiable. Next, `dₑ` and `dₚ` (the degradation rates of $E$ and $P$, respectively) are locally identifiable. Finally, `P(t)`, `M(t)`, `pₘ`, and `dₘ` (the trajectories of `P` and `M`, and the production and degradation rate of `M`, respectively) are all globally identifiable. We note that we also imported the Logging.jl package, and provided the `loglevel = Logging.Error` input argument. StructuralIdentifiability functions generally provide a large number of output messages. Hence, we will use this argument (which requires the Logging package) throughout this tutorial to decrease the amount of printed text.
4447

4548
Next, we also assess identifiability in the case where we can measure all three species concentrations:
46-
```@example si1
49+
```julia
4750
assess_identifiability(gwo; measured_quantities = [:M, :P, :E], loglevel = Logging.Error)
4851
```
4952
in which case all species trajectories and parameters become identifiable.
5053

5154
### [Indicating known parameters](@id structural_identifiability_gi_known_ps)
5255
In the previous case we assumed that all parameters are unknown, however, this is not necessarily true. If there are parameters with known values, we can supply these using the `known_p` argument. Providing this additional information might also make other, previously unidentifiable, parameters identifiable. Let us consider the previous example, where we measure the concentration of $M$ only, but now assume we also know the production rate of $E$ ($pₑ$):
53-
```@example si1
56+
```julia
5457
assess_identifiability(gwo; measured_quantities = [:M], known_p = [:pₑ], loglevel = Logging.Error)
5558
```
5659
Not only does this turn the previously non-identifiable `pₑ` (globally) identifiable (which is obvious, given that its value is now known), but this additional information improve identifiability for several other network components.
@@ -59,45 +62,45 @@ To, in a similar manner, indicate that certain initial conditions are known is a
5962

6063
### [Providing non-trivial measured quantities](@id structural_identifiability_gi_nontrivial_mq)
6164
Sometimes, ones may not have measurements of species, but rather some combinations of species (or possibly parameters). To account for this, `measured_quantities` accepts any algebraic expression (and not just single species). To form such expressions, species and parameters have to first be `@unpack`'ed from the model. Say that we have a model where an enzyme ($E$) is converted between an active and inactive form, which in turns activates the production of a product, $P$:
62-
```@example si1
65+
```julia
6366
rs = @reaction_network begin
6467
(kA,kD), Eᵢ <--> Eₐ
6568
(Eₐ, d), 0 <-->P
6669
end
6770
```
6871
If we can measure the total amount of $E$ ($=Eᵢ+Eₐ$), as well as the amount of $P$, we can use the following to assess identifiability:
69-
```@example si1
72+
```julia
7073
@unpack Eᵢ, Eₐ = rs
7174
assess_identifiability(rs; measured_quantities = [Eᵢ + Eₐ, :P], loglevel = Logging.Error)
7275
nothing # hide
7376
```
7477

7578
### [Assessing identifiability for specified quantities only](@id structural_identifiability_gi_ftc)
7679
By default, StructuralIdentifiability assesses identifiability for all parameters and variables. It is, however, possible to designate precisely which quantities you want to check using the `funcs_to_check` option. This both includes selecting a smaller subset of parameters and variables to check, or defining customised expressions. Let us consider the Goodwind from previously, and say that we would like to check whether the production parameters ($pₘ$, $pₑ$, and $pₚ$) and the total amount of the three species ($P + M + E$) are identifiable quantities. Here, we would first unpack these (allowing us to form algebraic expressions) and then use the following code:
77-
```@example si1
80+
```julia
7881
@unpack pₘ, pₑ, pₚ, M, E, P = gwo
7982
assess_identifiability(gwo; measured_quantities = [:M], funcs_to_check = [pₘ, pₑ, pₚ, M + E + P], loglevel = Logging.Error)
8083
nothing # hide
8184
```
8285

8386
### [Probability of correctness](@id structural_identifiability_gi_probs)
8487
The identifiability methods used can, in theory, produce erroneous results. However, it is possible to adjust the lower bound for the probability of correctness using the argument `prob_threshold` (by default set to `0.99`, that is, at least a $99\%$ chance of correctness). We can e.g. increase the bound through:
85-
```@example si1
88+
```julia
8689
assess_identifiability(gwo; measured_quantities=[:M], prob_threshold = 0.999, loglevel = Logging.Error)
8790
nothing # hide
8891
```
8992
giving a minimum bound of $99.9\%$ chance of correctness. In practise, the bounds used by StructuralIdentifiability are very conservative, which means that while the minimum guaranteed probability of correctness in the default case is $99\%$, in practise it is much higher. While increasing the value of `prob_threshold` increases the certainty of correctness, it will also increase the time required to assess identifiability.
9093

9194
## [Local identifiability analysis](@id structural_identifiability_lit)
9295
Local identifiability can be assessed through the `assess_local_identifiability` function. While this is already determined by `assess_identifiability`, assessing local identifiability only has the advantage that it is easier to compute. Hence, there might be models where global identifiability analysis fails (or takes a prohibitively long time), where instead `assess_local_identifiability` can be used. This function takes the same inputs as `assess_identifiability` and returns, for each quantity, `true` if it is locally identifiable (or `false` if it is not). Here, for the Goodwind oscillator, we assesses it for local identifiability only:
93-
```@example si1
96+
```julia
9497
assess_local_identifiability(gwo; measured_quantities = [:M], loglevel = Logging.Error)
9598
```
9699
We note that the results are consistent with those produced by `assess_identifiability` (with globally or locally identifiable quantities here all being assessed as at least locally identifiable).
97100

98101
## [Finding identifiable functions](@id structural_identifiability_identifiabile_funcs)
99102
Finally, StructuralIdentifiability provides the `find_identifiable_functions` function. Rather than determining the identifiability of each parameter and unknown of the model, it finds a set of identifiable functions, such as any other identifiable expression of the model can be generated by these. Let us again consider the Goodwind oscillator, using the `find_identifiable_functions` function we find that identifiability can be reduced to five globally identifiable expressions:
100-
```@example si1
103+
```julia
101104
find_identifiable_functions(gwo; measured_quantities = [:M], loglevel = Logging.Error)
102105
```
103106
Again, these results are consistent with those produced by `assess_identifiability`. There, `pₑ` and `pₚ` where found to be globally identifiable. Here, they correspond directly to identifiable expressions. The remaining four parameters (`pₘ`, `dₘ`, `dₑ`, and `dₚ`) occur as part of more complicated composite expressions.
@@ -106,19 +109,19 @@ Again, these results are consistent with those produced by `assess_identifiabili
106109

107110
## [Creating StructuralIdentifiability compatible ODE models from Catalyst `ReactionSystem`s](@id structural_identifiability_si_odes)
108111
While the functionality described above covers the vast majority of analysis that user might want to perform, the StructuralIdentifiability package supports several additional features. While these does not have inherent Catalyst support, we do provide the `make_si_ode` function to simplify their use. Similar to the previous functions, it takes a `ReactionSystem`, lists of measured quantities, and known parameter values. The output is a [ODE of the standard form supported by StructuralIdentifiability](https://docs.sciml.ai/StructuralIdentifiability/stable/tutorials/creating_ode/#Defining-the-model-using-@ODEmodel-macro). It can be created using the following syntax:
109-
```@example si1
112+
```julia
110113
si_ode = make_si_ode(gwo; measured_quantities = [:M])
111114
nothing # hide
112115
```
113116
and then used as input to various StructuralIdentifiability functions. In the following example we use StructuralIdentifiability's `print_for_DAISY` function, printing the model as an expression that can be used by the [DAISY](https://daisy.dei.unipd.it/) software for identifiability analysis[^3].
114-
```@example si1
117+
```julia
115118
print_for_DAISY(si_ode)
116119
nothing # hide
117120
```
118121

119122
## [Notes on systems with conservation laws](@id structural_identifiability_conslaws)
120123
Several reaction network models, such as
121-
```@example si3
124+
```julia
122125
using Catalyst, Logging, StructuralIdentifiability # hide
123126
rs = @reaction_network begin
124127
(k1,k2), X1 <--> X2

docs/src/model_creation/reactionsystem_content_accessing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# [Accessing model properties](@id model_accessing)
2-
Catalyst is based around the creation, analysis, and simulation of chemical reaction network models. Catalyst stores these models in [`ReactionSystem](@ref) structures. This page describes some basic functions for accessing the content of these structures. This includes retrieving lists of species, parameters, or reactions that a model consists of. An extensive list of relevant functions for working with `ReactionSystem` models can be found in Catalyst's [API](@ref api).
2+
Catalyst is based around the creation, analysis, and simulation of chemical reaction network models. Catalyst stores these models in [`ReactionSystem`](@ref) structures. This page describes some basic functions for accessing the content of these structures. This includes retrieving lists of species, parameters, or reactions that a model consists of. An extensive list of relevant functions for working with `ReactionSystem` models can be found in Catalyst's [API](@ref api).
33

44
!!! warning
55
Generally, a field of a Julia structure can be accessed through `struct.fieldname`. E.g. a simulation's time vector can be retrieved using `simulation.t`. While Catalyst `ReactionSystem`s are structures, one should *never* access their fields using this approach, but rather using the accessor functions described below and in the [API](@ref api_accessor_functions) (direct accessing of fields can yield unexpected behaviours). E.g. to retrieve the species of a `ReactionsSystem` `rs`, use `Catalyst.get_species(rs)`, *not* `rs.species`. The reason is that, as shown [below](@ref model_accessing_symbolic_variables), Catalyst (and more generally any [ModelingToolkit]https://github.com/SciML/ModelingToolkit.jl system types) reserves this type of accessing for accessing symbolic variables stored in the system. I.e. `rs.X` refers to the `X` symbolic variable, not a field in `rs` named "X".

test/extensions/bifurcation_kit.jl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,32 +56,35 @@ end
5656
# Checks that the same bifurcation problem is created as for BifurcationKit.
5757
# Checks with Symbolics as bifurcation and plot vars.
5858
# Tries setting `jac=false`.
59+
# Note: Only one parameter used, as tests technically depended on internal parameter ordering
60+
# (Potentially the test should also be removed as it tests internal parameter stuff, however, test
61+
# was written a while ago when I paid less attention to this kind of stuff.)
5962
let
6063
# Creates BifurcationProblem via Catalyst.
6164
bistable_switch = @reaction_network begin
62-
0.1 + hill(X,v,K,n), 0 --> X
63-
d, X --> 0
65+
0.1 + hill(X,5.0,K,3), 0 --> X
66+
1.0, X --> 0
6467
end
65-
@unpack X, v, K, n, d = bistable_switch
68+
@unpack X, K = bistable_switch
6669
u0_guess = [X => 1.0]
67-
p_start = [v => 5.0, K => 2.5, n => 3, d => 1.0]
70+
p_start = [K => 2.5]
6871
bprob = BifurcationProblem(bistable_switch, u0_guess, p_start, K; jac=false, plot_var=X)
6972

7073
# Creates BifurcationProblem via BifurcationKit.
7174
function bistable_switch_BK(u, p)
7275
X, = u
73-
v, K, n, d = p
74-
return [0.1 + v*(X^n)/(X^n + K^n) - d*X]
76+
K, = p
77+
return [0.1 + 5.0*(X^3)/(X^3 + K^3) - 1.0*X]
7578
end
76-
bprob_BK = BifurcationProblem(bistable_switch_BK, [1.0], [5.0, 2.5, 3, 1.0], (@lens _[1]); record_from_solution = (x, p) -> x[1])
79+
bprob_BK = BifurcationProblem(bistable_switch_BK, [1.0], [2.5], (@lens _[1]); record_from_solution = (x, p) -> x[1])
7780

7881
# Check the same function have been generated.
7982
bprob.u0 == bprob_BK.u0
8083
bprob.params == bprob_BK.params
8184
for repeat = 1:20
8285
u0 = rand(rng, 1)
83-
p = rand(rng, 4)
84-
@test bprob_BK.VF.F(u0, p) == bprob.VF.F(u0, p)
86+
p = rand(rng, 1)
87+
@test bprob_BK.VF.F(u0, p) bprob.VF.F(u0, p)
8588
end
8689
end
8790

test/extensions/homotopy_continuation.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,12 @@ let
9696
v*(0.1/v + hill(X,1,K,n)), 0 --> X
9797
d, X --> 0
9898
end
99-
ps = [:v => 5.0, :K => 2.5, :n => 3, :d => 1.0]
99+
ps = Dict([:v => 5.0, :K => 2.5, :n => 3, :d => 1.0])
100100
sss = hc_steady_states(rs, ps; filter_negative = false, show_progress = false, seed = 0x000004d1)
101101

102102
@test length(sss) == 4
103103
for ss in sss
104-
@test f_eval(rs,sss[1], last.(ps), 0.0)[1] 0.0 atol = 1e-12
104+
@test ps[:v]*(0.1/ps[:v] + ss[1]^ps[:n]/(ss[1]^ps[:n] + ps[:K]^ps[:n])) - ps[:d]*ss[1] 0.0 atol = 1e-12
105105
end
106106

107107
ps = [:v => 5.0, :K => 2.5, :n => 2.7, :d => 1.0]

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ using SafeTestsets, Test
6060
# Tests extensions.
6161
@time @safetestset "BifurcationKit Extension" begin include("extensions/bifurcation_kit.jl") end
6262
@time @safetestset "HomotopyContinuation Extension" begin include("extensions/homotopy_continuation.jl") end
63-
@time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end
63+
# @time @safetestset "Structural Identifiability Extension" begin include("extensions/structural_identifiability.jl") end
6464

6565
# Tests stability computation (uses HomotopyContinuation extension).
6666
@time @safetestset "Steady State Stability Computations" begin include("miscellaneous_tests/stability_computation.jl") end

0 commit comments

Comments
 (0)