Skip to content

Commit 94fb749

Browse files
committed
docs(refactor): refactor "Getting Started" section with @mtkmodel
- All sections, except building hierarchal models, use `@mtkmodel`. - "Non-DSL way of defining an ODESystem" section is added.
1 parent 278698b commit 94fb749

File tree

1 file changed

+184
-37
lines changed

1 file changed

+184
-37
lines changed

docs/src/tutorials/ode_modeling.md

Lines changed: 184 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,34 @@ 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-
```@example ode
12+
```@example first-mtkmodel
1313
using ModelingToolkit
1414
15-
@variables t x(t) # independent and dependent variables
16-
@parameters τ # parameters
17-
@constants h = 1 # constants have an assigned value
18-
D = Differential(t) # define an operator for the differentiation w.r.t. time
19-
20-
# your first ODE, consisting of a single equation, the equality indicated by ~
21-
@named fol = ODESystem([D(x) ~ (h - x) / τ])
15+
@variables t
16+
D = Differential(t)
17+
18+
@mtkmodel FOL begin
19+
@parameters begin
20+
τ # parameters
21+
end
22+
@variables begin
23+
x(t) # dependent variables
24+
end
25+
@structural_parameters begin
26+
h = 1
27+
end
28+
@equations begin
29+
D(x) ~ (h - x) / τ
30+
end
31+
end
2232
2333
using DifferentialEquations: solve
2434
25-
prob = ODEProblem(fol, [x => 0.0], (0.0, 10.0), [τ => 3.0])
26-
# parameter `τ` can be assigned a value, but constant `h` cannot
35+
@named fol = FOL()
36+
fol = complete(fol)
37+
38+
prob = ODEProblem(fol, [fol.x => 0.0], (0.0, 10.0), [fol.τ => 3.0])
39+
# parameter `τ` can be assigned a value, but structural parameter `h` cannot'.
2740
sol = solve(prob)
2841
2942
using Plots
@@ -43,48 +56,110 @@ first-order lag element:
4356

4457
Here, ``t`` is the independent variable (time), ``x(t)`` is the (scalar) state
4558
variable, ``f(t)`` is an external forcing function, and ``\tau`` is a
46-
parameter. In MTK, this system can be modelled as follows. For simplicity, we
47-
first set the forcing function to a time-independent value.
59+
parameter.
60+
In MTK, this system can be modelled as follows. For simplicity, we
61+
first set the forcing function to a time-independent value ``h``. And the
62+
independent variable ``t`` is automatically added by ``@mtkmodel``.
4863

4964
```@example ode2
5065
using ModelingToolkit
5166
52-
@variables t x(t) # independent and dependent variables
53-
@parameters τ # parameters
54-
@constants h = 1 # constants
55-
D = Differential(t) # define an operator for the differentiation w.r.t. time
67+
@variables t
68+
D = Differential(t)
69+
70+
@mtkmodel FOL begin
71+
@parameters begin
72+
τ # parameters
73+
end
74+
@variables begin
75+
x(t) # dependent variables
76+
end
77+
@structural_parameters begin
78+
h = 1
79+
end
80+
@equations begin
81+
D(x) ~ (h - x) / τ
82+
end
83+
end
5684
57-
# your first ODE, consisting of a single equation, indicated by ~
58-
@named fol_model = ODESystem(D(x) ~ (h - x) / τ)
85+
@named fol_incomplete = FOL()
86+
fol = complete(fol_incomplete)
5987
```
6088

6189
Note that equations in MTK use the tilde character (`~`) as equality sign.
62-
Also note that the `@named` macro simply ensures that the symbolic name
63-
matches the name in the REPL. If omitted, you can directly set the `name` keyword.
90+
91+
`@named` creates an instance of `FOL` named as `fol`. Before creating an
92+
ODEProblem with `fol` run `complete`. Once the system is complete, it will no
93+
longer namespace its subsystems or variables. This is necessary to correctly pass
94+
the intial values of states and parameters to the ODEProblem.
95+
96+
```julia
97+
julia> fol_incomplete.x
98+
fol_incomplete₊x(t)
99+
100+
julia> fol.x
101+
x(t)
102+
```
64103

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

67106
```@example ode2
68107
using DifferentialEquations
69108
using Plots
70109
71-
prob = ODEProblem(fol_model, [x => 0.0], (0.0, 10.0), [τ => 3.0])
110+
prob = ODEProblem(fol, [fol.x => 0.0], (0.0, 10.0), [fol.τ => 3.0])
72111
plot(solve(prob))
73112
```
74113

75114
The initial state and the parameter values are specified using a mapping
76115
from the actual symbolic elements to their values, represented as an array
77116
of `Pair`s, which are constructed using the `=>` operator.
78117

118+
## Non-DSL way of defining an ODESystem
119+
120+
Using `@mtkmodel` is the preferred way of defining ODEs with MTK. However, let us
121+
look at how we can define the same system without `@mtkmodel`. This is useful for
122+
defining PDESystem etc.
123+
124+
```@example first-mtkmodel
125+
@variables t x(t) # independent and dependent variables
126+
@parameters τ # parameters
127+
@constants h = 1 # constants
128+
D = Differential(t) # define an operator for the differentiation w.r.t. time
129+
130+
# your first ODE, consisting of a single equation, indicated by ~
131+
@named fol_model = ODESystem(D(x) ~ (h - x) / τ)
132+
```
133+
79134
## Algebraic relations and structural simplification
80135

81136
You could separate the calculation of the right-hand side, by introducing an
82137
intermediate variable `RHS`:
83138

84139
```@example ode2
85-
@variables RHS(t)
86-
@named fol_separate = ODESystem([RHS ~ (h - x) / τ,
87-
D(x) ~ RHS])
140+
using ModelingToolkit
141+
142+
@mtkmodel FOL begin
143+
@parameters begin
144+
τ # parameters
145+
end
146+
@variables begin
147+
x(t) # dependent variables
148+
RHS(t)
149+
end
150+
@structural_parameters begin
151+
h = 1
152+
end
153+
begin
154+
D = Differential(t)
155+
end
156+
@equations begin
157+
RHS ~ (h - x) / τ
158+
D(x) ~ RHS
159+
end
160+
end
161+
162+
@named fol_separate = FOL()
88163
```
89164

90165
To directly solve this system, you would have to create a Differential-Algebraic
@@ -94,12 +169,12 @@ transformed into the single ODE we used in the first example above. MTK achieves
94169
this by structural simplification:
95170

96171
```@example ode2
97-
fol_simplified = structural_simplify(fol_separate)
172+
fol_simplified = structural_simplify(complete(fol_separate))
98173
equations(fol_simplified)
99174
```
100175

101176
```@example ode2
102-
equations(fol_simplified) == equations(fol_model)
177+
equations(fol_simplified) == equations(fol)
103178
```
104179

105180
You can extract the equations from a system using `equations` (and, in the same
@@ -114,9 +189,12 @@ along with the state variable. Note that this has to be requested explicitly,
114189
through:
115190

116191
```@example ode2
117-
prob = ODEProblem(fol_simplified, [x => 0.0], (0.0, 10.0), [τ => 3.0])
192+
prob = ODEProblem(fol_simplified,
193+
[fol_simplified.x => 0.0],
194+
(0.0, 10.0),
195+
[fol_simplified.τ => 3.0])
118196
sol = solve(prob)
119-
plot(sol, vars = [x, RHS])
197+
plot(sol, vars = [fol_simplified.x, fol_simplified.RHS])
120198
```
121199

122200
By default, `structural_simplify` also replaces symbolic `constants` with
@@ -136,8 +214,27 @@ What if the forcing function (the “external input”) ``f(t)`` is not constant
136214
Obviously, one could use an explicit, symbolic function of time:
137215

138216
```@example ode2
139-
@variables f(t)
140-
@named fol_variable_f = ODESystem([f ~ sin(t), D(x) ~ (f - x) / τ])
217+
@mtkmodel FOL begin
218+
@parameters begin
219+
τ # parameters
220+
end
221+
@variables begin
222+
x(t) # dependent variables
223+
f(t)
224+
end
225+
@structural_parameters begin
226+
h = 1
227+
end
228+
begin
229+
D = Differential(t)
230+
end
231+
@equations begin
232+
f ~ sin(t)
233+
D(x) ~ (f - x) / τ
234+
end
235+
end
236+
237+
@named fol_variable_f = FOL()
141238
```
142239

143240
But often this function might not be available in an explicit form.
@@ -154,11 +251,35 @@ value_vector = randn(10)
154251
f_fun(t) = t >= 10 ? value_vector[end] : value_vector[Int(floor(t)) + 1]
155252
@register_symbolic f_fun(t)
156253
157-
@named fol_external_f = ODESystem([f ~ f_fun(t), D(x) ~ (f - x) / τ])
158-
prob = ODEProblem(structural_simplify(fol_external_f), [x => 0.0], (0.0, 10.0), [τ => 0.75])
254+
@mtkmodel FOLExternalFunction begin
255+
@parameters begin
256+
τ # parameters
257+
end
258+
@variables begin
259+
x(t) # dependent variables
260+
f(t)
261+
end
262+
@structural_parameters begin
263+
h = 1
264+
end
265+
begin
266+
D = Differential(t)
267+
end
268+
@equations begin
269+
f ~ f_fun(t)
270+
D(x) ~ (f - x) / τ
271+
end
272+
end
273+
274+
@named fol_external_f = FOLExternalFunction()
275+
fol_external_f = complete(fol_external_f)
276+
prob = ODEProblem(structural_simplify(fol_external_f),
277+
[fol_external_f.x => 0.0],
278+
(0.0, 10.0),
279+
[fol_external_f.τ => 0.75])
159280
160281
sol = solve(prob)
161-
plot(sol, vars = [x, f])
282+
plot(sol, vars = [fol_external_f.x, fol_external_f.f])
162283
```
163284

164285
## Building component-based, hierarchical models
@@ -235,19 +356,43 @@ plot(solve(prob))
235356

236357
More on this topic may be found in [Composing Models and Building Reusable Components](@ref acausal).
237358

238-
## Defaults
359+
## Inital Guess
239360

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

243364
```@example ode2
244-
function unitstep_fol_factory(; name)
365+
@mtkmodel UnitstepFOLFactory begin
366+
@parameters begin
367+
τ = 1.0
368+
end
369+
@variables begin
370+
x(t) = 0.0
371+
end
372+
@equations begin
373+
D(x) ~ (1 - x) / τ
374+
end
375+
end
376+
```
377+
378+
While defining the model `UnitstepFOLFactory`, an initial guess of 0.0 is assigned to `x(t)` and 1.0 to `τ`.
379+
Additionaly, these initial guesses can be modified while creating instances of `UnitstepFOLFactory` by passing arguements.
380+
381+
```@example ode2
382+
@named fol = UnitstepFOLFactory(; x = 0.1)
383+
sol = ODEProblem(fol, [], (0.0, 5.0), []) |> solve
384+
```
385+
386+
In non-DSL definitions, one can pass `defaults` dictionary to set the initial guess of the symbolic variables.
387+
388+
```@example ode3
389+
using ModelingToolkit
390+
391+
function UnitstepFOLFactory(; name)
245392
@parameters τ
246393
@variables t x(t)
247394
ODESystem(D(x) ~ (1 - x) / τ; name, defaults = Dict(x => 0.0, τ => 1.0))
248395
end
249-
250-
ODEProblem(unitstep_fol_factory(name = :fol), [], (0.0, 5.0), []) |> solve
251396
```
252397

253398
Note that the defaults can be functions of the other variables, which is then
@@ -316,6 +461,8 @@ Where to go next?
316461

317462
- Not sure how MTK relates to similar tools and packages? Read
318463
[Comparison of ModelingToolkit vs Equation-Based and Block Modeling Languages](@ref).
464+
- For a more detailed explanation of `@mtkmodel` checkout
465+
[Defining components with `@mtkmodel` and connectors with `@connectors`](@ref mtkmodel_connector)
319466
- Depending on what you want to do with MTK, have a look at some of the other
320467
**Symbolic Modeling Tutorials**.
321468
- If you want to automatically convert an existing function to a symbolic

0 commit comments

Comments
 (0)