Skip to content

Commit 962837b

Browse files
committed
more examples
1 parent 2ac0307 commit 962837b

File tree

5 files changed

+52
-119
lines changed

5 files changed

+52
-119
lines changed

docs/Project.toml

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
1111
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
1212
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1313
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
14+
StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
15+
StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544"
1416
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
1517
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
1618
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

docs/src/basics/Validation.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ModelingToolkit.jl provides extensive functionality for model validation and uni
66

77
Units may assigned with the following syntax.
88

9-
```julia
9+
```@example validation
1010
using ModelingToolkit, Unitful
1111
@variables t [unit = u"s"] x(t) [unit = u"m"] g(t) w(t) [unit = "Hz"]
1212
@@ -45,21 +45,25 @@ ModelingToolkit.get_unit
4545

4646
Example usage below. Note that `ModelingToolkit` does not force unit conversions to preferred units in the event of nonstandard combinations -- it merely checks that the equations are consistent.
4747

48-
```julia
48+
```@example validation
4949
using ModelingToolkit, Unitful
5050
@parameters τ [unit = u"ms"]
5151
@variables t [unit = u"ms"] E(t) [unit = u"kJ"] P(t) [unit = u"MW"]
5252
D = Differential(t)
5353
eqs = eqs = [D(E) ~ P - E/τ,
5454
0 ~ P ]
55-
ModelingToolkit.validate(eqs) #Returns true
56-
ModelingToolkit.validate(eqs[1]) #Returns true
57-
ModelingToolkit.get_unit(eqs[1].rhs) #Returns u"kJ ms^-1"
55+
ModelingToolkit.validate(eqs)
56+
```
57+
```@example validation
58+
ModelingToolkit.validate(eqs[1])
59+
```
60+
```@example validation
61+
ModelingToolkit.get_unit(eqs[1].rhs)
5862
```
5963

6064
An example of an inconsistent system: at present, `ModelingToolkit` requires that the units of all terms in an equation or sum to be equal-valued (`ModelingToolkit.equivalent(u1,u2)`), rather that simply dimensionally consistent. In the future, the validation stage may be upgraded to support the insertion of conversion factors into the equations.
6165

62-
```julia
66+
```@example validation
6367
using ModelingToolkit, Unitful
6468
@parameters τ [unit = u"ms"]
6569
@variables t [unit = u"ms"] E(t) [unit = u"J"] P(t) [unit = u"MW"]

docs/src/tutorials/ode_modeling.md

+32-73
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ illustrate the basic user-facing functionality.
99
A much deeper tutorial with forcing functions and sparse Jacobians is below.
1010
But if you want to just see some code and run, here's an example:
1111

12-
```julia
12+
```@example ode
1313
using ModelingToolkit
1414
15+
1516
@variables t x(t) # independent and dependent variables
1617
@parameters τ # parameters
1718
@constants h = 1 # constants have an assigned value
@@ -21,15 +22,14 @@ D = Differential(t) # define an operator for the differentiation w.r.t. time
2122
@named fol = ODESystem([ D(x) ~ (h - x)/τ])
2223
2324
using DifferentialEquations: solve
24-
using Plots: plot
2525
2626
prob = ODEProblem(fol, [x => 0.0], (0.0,10.0), [τ => 3.0])
2727
# parameter `τ` can be assigned a value, but constant `h` cannot
2828
sol = solve(prob)
29+
30+
using Plots
2931
plot(sol)
3032
```
31-
32-
![Simulation result of first-order lag element, with right-hand side](https://user-images.githubusercontent.com/13935112/111958369-703f2200-8aed-11eb-8bb4-0abe9652e850.png)
3333
Now let's start digging into MTK!
3434

3535
## Your very first ODE
@@ -46,7 +46,7 @@ variable, ``f(t)`` is an external forcing function, and ``\tau`` is a
4646
parameter. In MTK, this system can be modelled as follows. For simplicity, we
4747
first set the forcing function to a time-independent value.
4848

49-
```julia
49+
```@example ode2
5050
using ModelingToolkit
5151
5252
@variables t x(t) # independent and dependent variables
@@ -56,11 +56,6 @@ D = Differential(t) # define an operator for the differentiation w.r.t. time
5656
5757
# your first ODE, consisting of a single equation, indicated by ~
5858
@named fol_model = ODESystem(D(x) ~ (h - x)/τ)
59-
# Model fol_model with 1 equations
60-
# States (1):
61-
# x(t)
62-
# Parameters (1):
63-
# τ
6459
```
6560

6661
Note that equations in MTK use the tilde character (`~`) as equality sign.
@@ -69,16 +64,14 @@ matches the name in the REPL. If omitted, you can directly set the `name` keywor
6964

7065
After construction of the ODE, you can solve it using [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/):
7166

72-
```julia
67+
```@example ode2
7368
using DifferentialEquations
7469
using Plots
7570
7671
prob = ODEProblem(fol_model, [x => 0.0], (0.0,10.0), [τ => 3.0])
7772
plot(solve(prob))
7873
```
7974

80-
![Simulation result of first-order lag element](https://user-images.githubusercontent.com/13935112/111958369-703f2200-8aed-11eb-8bb4-0abe9652e850.png)
81-
8275
The initial state and the parameter values are specified using a mapping
8376
from the actual symbolic elements to their values, represented as an array
8477
of `Pair`s, which are constructed using the `=>` operator.
@@ -88,16 +81,10 @@ of `Pair`s, which are constructed using the `=>` operator.
8881
You could separate the calculation of the right-hand side, by introducing an
8982
intermediate variable `RHS`:
9083

91-
```julia
84+
```@example ode2
9285
@variables RHS(t)
9386
@named fol_separate = ODESystem([ RHS ~ (h - x)/τ,
9487
D(x) ~ RHS ])
95-
# Model fol_separate with 2 equations
96-
# States (2):
97-
# x(t)
98-
# RHS(t)
99-
# Parameters (1):
100-
# τ
10188
```
10289

10390
To directly solve this system, you would have to create a Differential-Algebraic
@@ -106,15 +93,13 @@ additional algebraic equation now. However, this DAE system can obviously be
10693
transformed into the single ODE we used in the first example above. MTK achieves
10794
this by structural simplification:
10895

109-
```julia
96+
```@example ode2
11097
fol_simplified = structural_simplify(fol_separate)
111-
11298
equations(fol_simplified)
113-
# 1-element Array{Equation,1}:
114-
# Differential(t)(x(t)) ~ (τ^-1)*(h - x(t))
99+
```
115100

101+
```@example ode2
116102
equations(fol_simplified) == equations(fol_model)
117-
# true
118103
```
119104

120105
You can extract the equations from a system using `equations` (and, in the same
@@ -128,7 +113,7 @@ in a simulation result. The intermediate variable `RHS` therefore can be plotted
128113
along with the state variable. Note that this has to be requested explicitly,
129114
through:
130115

131-
```julia
116+
```@example ode2
132117
prob = ODEProblem(fol_simplified, [x => 0.0], (0.0,10.0), [τ => 3.0])
133118
sol = solve(prob)
134119
plot(sol, vars=[x, RHS])
@@ -140,8 +125,6 @@ when using `parameters` (e.g., solution of linear equations by dividing out
140125
the constant's value, which cannot be done for parameters, since they may
141126
be zero).
142127

143-
![Simulation result of first-order lag element, with right-hand side](https://user-images.githubusercontent.com/13935112/111958403-7e8d3e00-8aed-11eb-9d18-08b5180a59f9.png)
144-
145128
Note that the indexing of the solution similarly works via the names, and so
146129
`sol[x]` gives the time-series for `x`, `sol[x,2:10]` gives the 2nd through 10th
147130
values of `x` matching `sol.t`, etc. Note that this works even for variables
@@ -152,7 +135,7 @@ which have been eliminated, and thus `sol[RHS]` retrieves the values of `RHS`.
152135
What if the forcing function (the “external input”) ``f(t)`` is not constant?
153136
Obviously, one could use an explicit, symbolic function of time:
154137

155-
```julia
138+
```@example ode2
156139
@variables f(t)
157140
@named fol_variable_f = ODESystem([f ~ sin(t), D(x) ~ (f - x)/τ])
158141
```
@@ -166,7 +149,7 @@ So, you could, for example, interpolate given the time-series using
166149
we illustrate this option by a simple lookup ("zero-order hold") of a vector
167150
of random values:
168151

169-
```julia
152+
```@example ode2
170153
value_vector = randn(10)
171154
f_fun(t) = t >= 10 ? value_vector[end] : value_vector[Int(floor(t))+1]
172155
@register_symbolic f_fun(t)
@@ -178,15 +161,13 @@ sol = solve(prob)
178161
plot(sol, vars=[x,f])
179162
```
180163

181-
![Simulation result of first-order lag element, step-wise forcing function](https://user-images.githubusercontent.com/13935112/111958424-83ea8880-8aed-11eb-8f42-489f4b44c3bc.png)
182-
183164
## Building component-based, hierarchical models
184165

185166
Working with simple one-equation systems is already fun, but composing more
186167
complex systems from simple ones is even more fun. Best practice for such a
187168
“modeling framework” could be to use factory functions for model components:
188169

189-
```julia
170+
```@example ode2
190171
function fol_factory(separate=false;name)
191172
@parameters τ
192173
@variables t x(t) f(t) RHS(t)
@@ -202,7 +183,7 @@ end
202183
Such a factory can then be used to instantiate the same component multiple times,
203184
but allows for customization:
204185

205-
```julia
186+
```@example ode2
206187
@named fol_1 = fol_factory()
207188
@named fol_2 = fol_factory(true) # has observable RHS
208189
```
@@ -212,21 +193,11 @@ Now, these two components can be used as subsystems of a parent system, i.e.
212193
one level higher in the model hierarchy. The connections between the components
213194
again are just algebraic relations:
214195

215-
```julia
196+
```@example ode2
216197
connections = [ fol_1.f ~ 1.5,
217198
fol_2.f ~ fol_1.x ]
218199
219200
connected = compose(ODESystem(connections,name=:connected), fol_1, fol_2)
220-
# Model connected with 5 equations
221-
# States (5):
222-
# fol_1₊f(t)
223-
# fol_2₊f(t)
224-
# fol_1₊x(t)
225-
# fol_2₊x(t)
226-
# fol_2₊RHS(t)
227-
# Parameters (2):
228-
# fol_1₊τ
229-
# fol_2₊τ
230201
```
231202

232203
All equations, variables, and parameters are collected, but the structure of the
@@ -236,26 +207,12 @@ hierarchical model is still preserved. This means you can still get information
236207
variables and connection equations from the system using structural
237208
simplification:
238209

239-
```julia
210+
```@example ode2
240211
connected_simp = structural_simplify(connected)
241-
# Model connected with 2 equations
242-
# States (2):
243-
# fol_1₊x(t)
244-
# fol_2₊x(t)
245-
# Parameters (2):
246-
# fol_1₊τ
247-
# fol_2₊τ
248-
# Incidence matrix:
249-
# [1, 1] = ×
250-
# [2, 1] = ×
251-
# [2, 2] = ×
252-
# [1, 3] = ×
253-
# [2, 4] = ×
212+
```
254213

214+
```@example ode2
255215
full_equations(connected_simp)
256-
# 2-element Array{Equation,1}:
257-
# Differential(t)(fol_1₊x(t)) ~ (fol_1₊τ^-1)*(1.5 - fol_1₊x(t))
258-
# Differential(t)(fol_2₊x(t)) ~ (fol_2₊τ^-1)*(fol_1₊x(t) - fol_2₊x(t))
259216
```
260217
As expected, only the two state-derivative equations remain,
261218
as if you had manually eliminated as many variables as possible from the equations.
@@ -264,7 +221,7 @@ As mentioned above, the hierarchical structure is preserved. So, the
264221
initial state and the parameter values can be specified accordingly when
265222
building the `ODEProblem`:
266223

267-
```julia
224+
```@example ode2
268225
u0 = [ fol_1.x => -0.5,
269226
fol_2.x => 1.0 ]
270227
@@ -275,16 +232,14 @@ prob = ODEProblem(connected_simp, u0, (0.0,10.0), p)
275232
plot(solve(prob))
276233
```
277234

278-
![Simulation of connected system (two first-order lag elements in series)](https://user-images.githubusercontent.com/13935112/111958439-877e0f80-8aed-11eb-9074-9d35458459a4.png)
279-
280235
More on this topic may be found in [Composing Models and Building Reusable Components](@ref acausal).
281236

282237
## Defaults
283238

284239
It is often a good idea to specify reasonable values for the initial state and the
285240
parameters of a model component. Then, these do not have to be explicitly specified when constructing the `ODEProblem`.
286241

287-
```julia
242+
```@example ode2
288243
function unitstep_fol_factory(;name)
289244
@parameters τ
290245
@variables t x(t)
@@ -311,21 +266,25 @@ By default, analytical derivatives and sparse matrices, e.g. for the Jacobian, t
311266
matrix of first partial derivatives, are not used. Let's benchmark this (`prob`
312267
still is the problem using the `connected_simp` system above):
313268

314-
```julia
269+
```@example ode2
315270
using BenchmarkTools
316-
317-
@btime solve($prob, Rodas4());
318-
# 251.300 μs (873 allocations: 31.18 KiB)
271+
@btime solve(prob, Rodas4());
272+
nothing # hide
319273
```
320274

321275
Now have MTK provide sparse, analytical derivatives to the solver. This has to
322276
be specified during the construction of the `ODEProblem`:
323277

324-
```julia
325-
prob_an = ODEProblem(connected_simp, u0, (0.0,10.0), p; jac=true, sparse=true)
278+
```@example ode2
279+
prob_an = ODEProblem(connected_simp, u0, (0.0,10.0), p; jac=true)
280+
@btime solve($prob_an, Rodas4());
281+
nothing # hide
282+
```
326283

284+
```@example ode2
285+
prob_an = ODEProblem(connected_simp, u0, (0.0,10.0), p; jac=true, sparse=true)
327286
@btime solve($prob_an, Rodas4());
328-
# 142.899 μs (1297 allocations: 83.96 KiB)
287+
nothing # hide
329288
```
330289

331290
The speedup is significant. For this small dense model (3 of 4 entries are

0 commit comments

Comments
 (0)