Skip to content

Commit ba1e715

Browse files
authored
Merge pull request #1186 from SciML/mtk_update
fixes for MTK changes
2 parents 093c64a + 2fdb32d commit ba1e715

32 files changed

+582
-368
lines changed

Project.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ BifurcationKit = "0.4.4"
4949
CairoMakie = "0.12, 0.13"
5050
Combinatorics = "1.0.2"
5151
DataStructures = "0.18"
52-
DiffEqBase = "6.159.0"
52+
DiffEqBase = "6.165.0"
5353
DocStringExtensions = "0.8, 0.9"
5454
DynamicPolynomials = "0.5, 0.6"
5555
DynamicQuantities = "0.13.2, 1"
@@ -61,17 +61,17 @@ LaTeXStrings = "1.3.0"
6161
Latexify = "0.16.6"
6262
MacroTools = "0.5.5"
6363
Makie = "0.22.1"
64-
ModelingToolkit = "< 9.60"
64+
ModelingToolkit = "9.69"
6565
NetworkLayout = "0.4.7"
6666
Parameters = "0.12"
6767
Reexport = "0.2, 1.0"
6868
Requires = "1.0"
6969
RuntimeGeneratedFunctions = "0.5.12"
70-
SciMLBase = "2.57.2"
70+
SciMLBase = "2.77"
7171
Setfield = "1"
7272
StructuralIdentifiability = "0.5.11"
73-
SymbolicUtils = "3.20.0"
74-
Symbolics = "6.22"
73+
SymbolicUtils = "3.20"
74+
Symbolics = "6.31.1"
7575
Unitful = "1.12.4"
7676
julia = "1.10"
7777

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ IncompleteLU = "0.2"
6363
JumpProcesses = "9.13.2"
6464
Latexify = "0.16.5"
6565
LinearSolve = "2.30, 3"
66-
ModelingToolkit = "< 9.60"
66+
ModelingToolkit = "9.69"
6767
NetworkLayout = "0.4"
6868
NonlinearSolve = "3.12, 4"
6969
Optim = "1.9"

docs/src/api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ direct access to the corresponding internal fields of the `ReactionSystem`)
124124
entries in `get_species(rn)` correspond to the first `length(get_species(rn))`
125125
components in `get_unknowns(rn)`.
126126
* `ModelingToolkit.get_ps(rn)` is a vector that collects all the parameters
127-
defined *within* reactions in `rn`.
127+
defined *within* reactions in `rn`. This includes initialisation parameters (which
128+
are added to the system by ModelingToolkit, and not the user).
128129
* `ModelingToolkit.get_eqs(rn)` is a vector that collects all the
129130
[`Reaction`](@ref)s and `Symbolics.Equation` defined within `rn`, ordering all
130131
`Reaction`s before `Equation`s.
Lines changed: 34 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# [Fitting Parameters for an Oscillatory System](@id parameter_estimation)
2-
In this example we will use [Optimization.jl](https://github.com/SciML/Optimization.jl) to fit the parameters of an oscillatory system (the Brusselator) to data. Here, special consideration is taken to avoid reaching a local minimum. Instead of fitting the entire time series directly, we will start with fitting parameter values for the first period, and then use those as an initial guess for fitting the next (and then these to find the next one, and so on). Using this procedure is advantageous for oscillatory systems, and enables us to reach the global optimum.
2+
In this example we will use [Optimization.jl](https://github.com/SciML/Optimization.jl) to fit the parameters of an oscillatory system (the [Brusselator](@ref basic_CRN_library_brusselator)) to data. Here, special consideration is taken to avoid reaching a local minimum. Instead of fitting the entire time series directly, we will start with fitting parameter values for the first period, and then use those as an initial guess for fitting the next (and then these to find the next one, and so on). Using this procedure is advantageous for oscillatory systems, and enables us to reach the global optimum. For more information on fitting ODE parameters to data, please see [the main documentation page](@ref optimization_parameter_fitting) on this topic.
33

44
First, we fetch the required packages.
55
```@example pe_osc_example
@@ -18,18 +18,18 @@ brusselator = @reaction_network begin
1818
B, X --> Y
1919
1, X --> ∅
2020
end
21-
p_real = [:A => 1., :B => 2.]
21+
p_real = [:A => 1.0, :B => 2.0]
2222
nothing # hide
2323
```
2424

2525
We simulate our model, and from the simulation generate sampled data points
2626
(to which we add noise). We will use this data to fit the parameters of our model.
2727
```@example pe_osc_example
2828
u0 = [:X => 1.0, :Y => 1.0]
29-
tspan = (0.0, 30.0)
29+
tend = 30.0
3030
31-
sample_times = range(tspan[1]; stop = tspan[2], length = 100)
32-
prob = ODEProblem(brusselator, u0, tspan, p_real)
31+
sample_times = range(0.0; stop = tend, length = 100)
32+
prob = ODEProblem(brusselator, u0, tend, p_real)
3333
sol_real = solve(prob, Rosenbrock23(); tstops = sample_times)
3434
sample_vals = Array(sol_real(sample_times))
3535
sample_vals .*= (1 .+ .1 * rand(Float64, size(sample_vals)) .- .05)
@@ -48,20 +48,25 @@ scatter!(sample_times, sample_vals'; color = [:blue :red], legend = nothing)
4848
Next, we create a function to fit the parameters using the `ADAM` optimizer. For
4949
a given initial estimate of the parameter values, `pinit`, this function will
5050
fit parameter values, `p`, to our data samples. We use `tend` to indicate the
51-
time interval over which we fit the model.
51+
time interval over which we fit the model. We use an out of place [`set_p` function](@ref simulation_structure_interfacing_functions)
52+
to update the parameter set in each iteration. We also provide the `set_p`, `prob`,
53+
`sample_times`, and `sample_vals` variables as parameters to our optimization problem.
5254
```@example pe_osc_example
53-
function optimise_p(pinit, tend)
54-
function loss(p, _)
55+
set_p = ModelingToolkit.setp_oop(prob, [:A, :B])
56+
function optimize_p(pinit, tend,
57+
set_p = set_p, prob = prob, sample_times = sample_times, sample_vals = sample_vals)
58+
function loss(p, (set_p, prob, sample_times, sample_vals))
59+
p = set_p(prob, p)
5560
newtimes = filter(<=(tend), sample_times)
56-
newprob = remake(prob; tspan = (0.0, tend), p = p)
57-
sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes))
61+
newprob = remake(prob; p)
62+
sol = Array(solve(newprob, Rosenbrock23(); saveat = newtimes, verbose = false, maxiters = 10000))
5863
loss = sum(abs2, sol .- sample_vals[:, 1:size(sol,2)])
5964
return loss
6065
end
6166
6267
# optimize for the parameters that minimize the loss
6368
optf = OptimizationFunction(loss, Optimization.AutoZygote())
64-
optprob = OptimizationProblem(optf, pinit)
69+
optprob = OptimizationProblem(optf, pinit, (set_p, prob, sample_times, sample_vals))
6570
sol = solve(optprob, ADAM(0.1); maxiters = 100)
6671
6772
# return the parameters we found
@@ -72,45 +77,36 @@ nothing # hide
7277

7378
Next, we will fit a parameter set to the data on the interval `(0, 10)`.
7479
```@example pe_osc_example
75-
p_estimate = optimise_p([5.0, 5.0], 10.0)
80+
p_estimate = optimize_p([5.0, 5.0], 10.0)
7681
```
7782

7883
We can compare this to the real solution, as well as the sample data
7984
```@example pe_osc_example
80-
newprob = remake(prob; tspan = (0., 10.), p = p_estimate)
81-
sol_estimate = solve(newprob, Rosenbrock23())
82-
plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2)
83-
scatter!(sample_times, sample_vals'; color = [:blue :red],
84-
label = ["Samples of X" "Samples of Y"], alpha = 0.4)
85-
plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash,
86-
label = ["X estimated" "Y estimated"], xlimit = tspan)
85+
function plot_opt_fit(p, tend)
86+
p = set_p(prob, p)
87+
newprob = remake(prob; tspan = tend, p)
88+
sol_estimate = solve(newprob, Rosenbrock23())
89+
plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2)
90+
scatter!(sample_times, sample_vals'; color = [:blue :red],
91+
label = ["Samples of X" "Samples of Y"], alpha = 0.4)
92+
plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash,
93+
label = ["X estimated" "Y estimated"], xlimit = (0.0, tend))
94+
end
95+
plot_opt_fit(p_estimate, 10.0)
8796
```
8897

8998
Next, we use this parameter estimate as the input to the next iteration of our
9099
fitting process, this time on the interval `(0, 20)`.
91100
```@example pe_osc_example
92-
p_estimate = optimise_p(p_estimate, 20.)
93-
newprob = remake(prob; tspan = (0., 20.), p = p_estimate)
94-
sol_estimate = solve(newprob, Rosenbrock23())
95-
plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2)
96-
scatter!(sample_times, sample_vals'; color = [:blue :red],
97-
label = ["Samples of X" "Samples of Y"], alpha = 0.4)
98-
plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash,
99-
label = ["X estimated" "Y estimated"], xlimit = tspan)
101+
p_estimate = optimize_p(p_estimate, 20.0)
102+
plot_opt_fit(p_estimate, 20.0)
100103
```
101104

102105
Finally, we use this estimate as the input to fit a parameter set on the full
103106
time interval of the sampled data.
104107
```@example pe_osc_example
105-
p_estimate = optimise_p(p_estimate, 30.0)
106-
107-
newprob = remake(prob; tspan = (0., 30.0), p = p_estimate)
108-
sol_estimate = solve(newprob, Rosenbrock23())
109-
plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2)
110-
scatter!(sample_times, sample_vals'; color = [:blue :red],
111-
label = ["Samples of X" "Samples of Y"], alpha = 0.4)
112-
plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash,
113-
label = ["X estimated" "Y estimated"], xlimit = tspan)
108+
p_estimate = optimize_p(p_estimate, 30.0)
109+
plot_opt_fit(p_estimate, 30.0)
114110
```
115111

116112
The final parameter estimate is then
@@ -126,13 +122,6 @@ specifically, we chose our initial interval to be smaller than a full cycle of
126122
the oscillation. If we had chosen to fit a parameter set on the full interval
127123
immediately we would have obtained poor fit and an inaccurate estimate for the parameters.
128124
```@example pe_osc_example
129-
p_estimate = optimise_p([5.0,5.0], 30.0)
130-
131-
newprob = remake(prob; tspan = (0.0,30.0), p = p_estimate)
132-
sol_estimate = solve(newprob, Rosenbrock23())
133-
plot(sol_real; color = [:blue :red], label = ["X real" "Y real"], linealpha = 0.2)
134-
scatter!(sample_times,sample_vals'; color = [:blue :red],
135-
label = ["Samples of X" "Samples of Y"], alpha = 0.4)
136-
plot!(sol_estimate; color = [:darkblue :darkred], linestyle = :dash,
137-
label = ["X estimated" "Y estimated"], xlimit = tspan)
125+
p_estimate = optimize_p([5.0,5.0], 30.0)
126+
plot_opt_fit(p_estimate, 30.0)
138127
```

docs/src/inverse_problems/optimization_ode_param_fitting.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,11 @@ We can now fit our model to data and plot the results:
148148
```@example optimization_paramfit_1
149149
optprob_S_P = OptimizationProblem(objective_function_S_P, p_guess)
150150
optsol_S_P = solve(optprob_S_P, NLopt.LN_NELDERMEAD())
151-
oprob_fitted_S_P = remake(oprob_base; p = optsol_S_P.u)
151+
p = Pair.([:kB, :kD, :kP], optsol_S_P.u)
152+
oprob_fitted_S_P = remake(oprob_base; p)
152153
fitted_sol_S_P = solve(oprob_fitted_S_P)
153-
plot!(fitted_sol_S_P; idxs=[:S, :P], label="Fitted solution", linestyle = :dash, lw = 6, color = [:lightblue :pink])
154-
plot!(plt2, fitted_sol_S_P; idxs=[:S, :P], label="Fitted solution", linestyle = :dash, lw = 6, color = [:lightblue :pink]) # hide
154+
plot!(fitted_sol_S_P; idxs = [:S, :P], label = "Fitted solution", linestyle = :dash, lw = 6, color = [:lightblue :pink])
155+
plot!(plt2, fitted_sol_S_P; idxs = [:S, :P], label = "Fitted solution", linestyle = :dash, lw = 6, color = [:lightblue :pink]) # hide
155156
Catalyst.PNG(plot(plt2; fmt = :png, dpi = 200)) # hide
156157
```
157158

docs/src/model_creation/compositional_modeling.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ reactions *only* within a given system (i.e. ignoring subsystems), we can use
9595
Catalyst.get_species(rn)
9696
```
9797
```@example ex1
98-
ModelingToolkit.get_ps(rn)
98+
Catalyst.get_ps(rn)
9999
```
100100
```@example ex1
101101
Catalyst.get_rxs(rn)
@@ -110,7 +110,6 @@ parameters(rn)
110110
```@example ex1
111111
reactions(rn) # or equations(rn)
112112
```
113-
114113
If we want to collapse `rn` down to a single system with no subsystems we can use
115114
```@example ex1
116115
flatrn = Catalyst.flatten(rn)

docs/src/model_creation/constraint_equations.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,19 @@ eq = [D(V) ~ λ * V]
4343
@named osys = ODESystem(eq, t)
4444
4545
# build the ReactionSystem with no protein initially
46-
rn = @reaction_network begin
46+
rn = @network_component begin
4747
@species P(t) = 0.0
4848
$V, 0 --> P
4949
1.0, P --> 0
5050
end
5151
```
5252
Notice, here we interpolated the variable `V` with `$V` to ensure we use the
53-
same symbolic unknown variable in the `rn` as we used in building `osys`. See the
54-
doc section on [interpolation of variables](@ref
55-
dsl_advanced_options_symbolics_and_DSL_interpolation) for more information.
53+
same symbolic unknown variable in the `rn` as we used in building `osys`. See
54+
the doc section on [interpolation of variables](@ref
55+
dsl_advanced_options_symbolics_and_DSL_interpolation) for more information. We
56+
also use `@network_component` instead of `@reaction_network` as when merging
57+
systems together Catalyst requires that the systems have not been marked as
58+
`complete` (which indicates to Catalyst that a system is finalized).
5659

5760
We can now merge the two systems into one complete `ReactionSystem` model using
5861
[`ModelingToolkit.extend`](@ref):

docs/src/model_creation/examples/hodgkin_huxley_equation.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,26 @@ We observe three action potentials due to the steady applied current.
138138
As an illustration of how one can construct models from individual components,
139139
we now separately construct and compose the model components.
140140

141-
We start by defining systems to model each ionic current:
141+
We start by defining systems to model each ionic current. Note we now use
142+
`@network_component` instead of `@reaction_network` as we want the models to be
143+
composable and not marked as finalized.
142144
```@example hh1
143-
IKmodel = @reaction_network IKmodel begin
145+
IKmodel = @network_component IKmodel begin
144146
@parameters ḡK = 36.0 EK = -82.0
145147
@variables V(t) Iₖ(t)
146148
(αₙ(V), βₙ(V)), n′ <--> n
147149
@equations Iₖ ~ ḡK*n^4*(V-EK)
148150
end
149151
150-
INamodel = @reaction_network INamodel begin
152+
INamodel = @network_component INamodel begin
151153
@parameters ḡNa = 120.0 ENa = 45.0
152154
@variables V(t) Iₙₐ(t)
153155
(αₘ(V), βₘ(V)), m′ <--> m
154156
(αₕ(V), βₕ(V)), h′ <--> h
155157
@equations Iₙₐ ~ ḡNa*m^3*h*(V-ENa)
156158
end
157159
158-
ILmodel = @reaction_network ILmodel begin
160+
ILmodel = @network_component ILmodel begin
159161
@parameters ḡL = .3 EL = -59.0
160162
@variables V(t) Iₗ(t)
161163
@equations Iₗ ~ ḡL*(V-EL)
@@ -165,7 +167,7 @@ nothing # hide
165167

166168
We next define the voltage dynamics with unspecified values for the currents
167169
```@example hh1
168-
hhmodel2 = @reaction_network hhmodel2 begin
170+
hhmodel2 = @network_component hhmodel2 begin
169171
@parameters C = 1.0 I₀ = 0.0
170172
@variables V(t) Iₖ(t) Iₙₐ(t) Iₗ(t)
171173
@equations D(V) ~ -1/C * (Iₖ + Iₙₐ + Iₗ) + Iapp(t,I₀)

docs/src/model_creation/reactionsystem_content_accessing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ Similarly, `parameters` retrieves five different parameters. Here, we note that
225225
parameters(rs)
226226
```
227227

228-
If we wish to retrieve the species (or parameters) that are specifically contained in the top-level system (and not only indirectly through its subsystems), we can use the `Catalyst.get_species` (or `Catalyst.get_ps`) functions:
228+
If we wish to retrieve the species (or parameters) that are specifically contained in the top-level system (and not only indirectly through its subsystems), we can use the `Catalyst.get_species` (or `ModelingToolkit.getps`) functions:
229229
```@example model_accessing_hierarchical
230230
Catalyst.get_species(rs)
231231
```
232232
```@example model_accessing_hierarchical
233-
Catalyst.get_ps(rs)
233+
ModelingToolkit.get_ps(rs)
234234
```
235235
Here, our top-level model contains a single parameter (`kₜ`), and two the two versions of the `Xᵢ` species. These are all the symbolic variables that occur in the transportation reaction (`@kₜ, $(nucleus_sys.Xᵢ) --> $(cytoplasm_sys.Xᵢ)`), which is the only reaction of the top-level system. We can apply these functions to the systems as well. However, when we do so, the systems' names are not prepended:
236236
```@example model_accessing_hierarchical

docs/src/steady_state_functionality/nonlinear_solve.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,24 @@ end
6767
```
6868
It has an infinite number of steady states. To make steady state finding possible, information of the system's conserved quantities (here $C = X1 + X2$) must be provided. Since these can be computed from system initial conditions (`u0`, i.e. those provided when performing ODE simulations), designating an `u0` is often the best way. There are two ways to do this. First, one can perform [forward ODE simulation-based steady state finding](@ref steady_state_solving_simulation), using the initial condition as the initial `u` guess. Alternatively, any conserved quantities can be eliminated when the `NonlinearProblem` is created. This feature is supported by [Catalyst's conservation law finding and elimination feature](@ref conservation_laws).
6969

70+
!!! warn
71+
For Catalyst versions >14.4.1, handling of conservation laws in `NonlinearProblem`s through the `remove_conserved = true` argument has been temporarily disabled. This is due to an upstream update in ModelingToolkit.jl. We aim to re-enable this as soon as possible. Currently, to find steady states of these systems, either use [homotopy continuation](@ref homotopy_continuation), the [simulation based approach](@ref steady_state_solving_simulation), or temporarily downgrade Catalyst to version 14.4.1. The remaining code of this section is left on display (and the text with it), but is not run dynamically, and cannot be run without generating an error.
72+
7073
To eliminate conservation laws we simply provide the `remove_conserved = true` argument to `NonlinearProblem`:
71-
```@example steady_state_solving_claws
74+
```julia
7275
p = [:k1 => 2.0, :k2 => 3.0]
7376
u_guess = [:X1 => 3.0, :X2 => 1.0]
7477
nl_prob = NonlinearProblem(two_state_model, u_guess, p; remove_conserved = true)
7578
nothing # hide
7679
```
7780
here it is important that the quantities used in `u_guess` correspond to the conserved quantities we wish to use. E.g. here the conserved quantity $X1 + X2 = 3.0 + 1.0 = 4$ holds for the initial condition, and will hence also hold in the computed steady state as well. We can now find the steady states using `solve` like before:
78-
<!-- ```@example steady_state_solving_claws
81+
```julia
7982
sol = solve(nl_prob)
80-
``` -->
83+
```
8184
We note that the output only provides a single value. The reason is that the actual system solved only contains a single equation (the other being eliminated with the conserved quantity). To find the values of $X1$ and $X2$ we can [directly query the solution object for these species' values, using the species themselves as inputs](@ref simulation_structure_interfacing_solutions):
82-
<!--```@example steady_state_solving_claws
85+
```julia
8386
sol[[:X1, :X2]]
84-
```-->
87+
```
8588

8689
## [Finding steady states through ODE simulations](@id steady_state_solving_simulation)
8790
The `NonlinearProblem`s generated by Catalyst corresponds to ODEs. A common method of solving these is to simulate the ODE from an initial condition until a steady state is reached. Here we do so for the dimerisation system considered in the previous section. First, we declare our model, initial condition, and parameter values.

0 commit comments

Comments
 (0)