Skip to content

Commit c4868e3

Browse files
Add @mtkbuild greatly simplify the tutorials
Good thing is that the tutorials are much simpler. Bad thing is that there's an issue with completing a structurally simplified model which only allows for using the flattened variables, and thus requires using: ```julia u0 = [ rc_model.capacitor₊v => 0.0, ] ``` by the user, which is not nice as it should be: ```julia u0 = [ rc_model.capacitor.v => 0.0, ] ``` which is something we should probably fix ASAP
1 parent 3034c9f commit c4868e3

File tree

6 files changed

+196
-126
lines changed

6 files changed

+196
-126
lines changed

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pages = [
55
"tutorials/nonlinear.md",
66
"tutorials/optimization.md",
77
"tutorials/modelingtoolkitize.md",
8+
"tutorials/programmatically_generating.md",
89
"tutorials/stochastic_diffeq.md",
910
"tutorials/parameter_identifiability.md",
1011
"tutorials/domain_connections.md"],

docs/src/tutorials/acausal_components.md

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ using ModelingToolkit, Plots, DifferentialEquations
2424
2525
@variables t
2626
@connector Pin begin
27-
v(t) = 1.0
28-
i(t) = 1.0, [connect = Flow]
27+
v(t)
28+
i(t), [connect = Flow]
2929
end
3030
3131
@mtkmodel Ground begin
@@ -43,8 +43,8 @@ end
4343
n = Pin()
4444
end
4545
@variables begin
46-
v(t) = 1.0
47-
i(t) = 1.0
46+
v(t)
47+
i(t)
4848
end
4949
@equations begin
5050
v ~ p.v - n.v
@@ -56,7 +56,7 @@ end
5656
@mtkmodel Resistor begin
5757
@extend v, i = oneport = OnePort()
5858
@parameters begin
59-
R = 1.0
59+
R = 1.0 # Sets the default resistance
6060
end
6161
@equations begin
6262
v ~ i * R
@@ -100,13 +100,11 @@ end
100100
end
101101
end
102102
103-
@named rc_model = RCModel(resistor.R = 2.0)
104-
rc_model = complete(rc_model)
105-
sys = structural_simplify(rc_model)
103+
@mtkbuild rc_model = RCModel(resistor.R = 2.0)
106104
u0 = [
107-
rc_model.capacitor.v => 0.0,
105+
rc_model.capacitorv => 0.0,
108106
]
109-
prob = ODAEProblem(sys, u0, (0, 10.0))
107+
prob = ODAEProblem(rc_model, u0, (0, 10.0))
110108
sol = solve(prob, Tsit5())
111109
plot(sol)
112110
```
@@ -131,8 +129,8 @@ default, variables are equal in a connection.
131129

132130
```@example acausal
133131
@connector Pin begin
134-
v(t) = 1.0
135-
i(t) = 1.0, [connect = Flow]
132+
v(t)
133+
i(t), [connect = Flow]
136134
end
137135
```
138136

@@ -175,8 +173,8 @@ pin.
175173
n = Pin()
176174
end
177175
@variables begin
178-
v(t) = 1.0
179-
i(t) = 1.0
176+
v(t)
177+
i(t)
180178
end
181179
@equations begin
182180
v ~ p.v - n.v
@@ -276,7 +274,7 @@ We can create a RCModel component with `@named`. And using `subcomponent_name.pa
276274
the parameters or defaults values of variables of subcomponents.
277275

278276
```@example acausal
279-
@named rc_model = RCModel(resistor.R = 2.0)
277+
@mtkbuild rc_model = RCModel(resistor.R = 2.0)
280278
```
281279

282280
This model is acausal because we have not specified anything about the causality of the model. We have
@@ -300,26 +298,15 @@ and the parameters are:
300298
parameters(rc_model)
301299
```
302300

303-
## Simplifying and Solving this System
304-
305-
This system could be solved directly as a DAE using [one of the DAE solvers
306-
from DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/).
307-
However, let's take a second to symbolically simplify the system before doing the
308-
solve. Although we can use ODE solvers that handle mass matrices to solve the
309-
above system directly, we want to run the `structural_simplify` function first,
310-
as it eliminates many unnecessary variables to build the leanest numerical
311-
representation of the system. Let's see what it does here:
301+
The observed equations are:
312302

313303
```@example acausal
314-
sys = structural_simplify(rc_model)
315-
equations(sys)
304+
observed(rc_model)
316305
```
317306

318-
```@example acausal
319-
states(sys)
320-
```
307+
## Solving this System
321308

322-
After structural simplification, we are left with a system of only two equations
309+
We are left with a system of only two equations
323310
with two state variables. One of the equations is a differential equation,
324311
while the other is an algebraic equation. We can then give the values for the
325312
initial conditions of our states, and solve the system by converting it to
@@ -328,21 +315,21 @@ DAE solver](https://docs.sciml.ai/DiffEqDocs/stable/solvers/dae_solve/#OrdinaryD
328315
This is done as follows:
329316

330317
```@example acausal
331-
u0 = [rc_model.capacitor.v => 0.0
332-
rc_model.capacitor.p.i => 0.0]
318+
u0 = [rc_model.capacitorv => 0.0
319+
rc_model.capacitor₊p₊i => 0.0]
333320
334321
prob = ODEProblem(sys, u0, (0, 10.0))
335322
sol = solve(prob, Rodas4())
336323
plot(sol)
337324
```
338325

339-
Since we have run `structural_simplify`, MTK can numerically solve all the
326+
MTK can numerically solve all the
340327
unreduced algebraic equations using the `ODAEProblem` (note the
341328
letter `A`):
342329

343330
```@example acausal
344331
u0 = [
345-
rc_model.capacitor.v => 0.0,
332+
rc_model.capacitorv => 0.0,
346333
]
347334
prob = ODAEProblem(sys, u0, (0, 10.0))
348335
sol = solve(prob, Rodas4())
@@ -369,11 +356,11 @@ The solution object can be accessed via its symbols. For example, let's retrieve
369356
the voltage of the resistor over time:
370357

371358
```@example acausal
372-
sol[rc_model.resistor.v]
359+
sol[rc_model.resistorv]
373360
```
374361

375362
or we can plot the timeseries of the resistor's voltage:
376363

377364
```@example acausal
378-
plot(sol, idxs = [rc_model.resistor.v])
365+
plot(sol, idxs = [rc_model.resistorv])
379366
```

docs/src/tutorials/ode_modeling.md

Lines changed: 56 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# Getting Started with ModelingToolkit.jl
22

3-
This is an introductory tutorial for ModelingToolkit (MTK).
4-
Some examples of Ordinary Differential Equations (ODE) are used to
5-
illustrate the basic user-facing functionality.
3+
This is an introductory tutorial for ModelingToolkit (MTK). We will demonstrate
4+
the basics of the package by demonstrating how to define and simulate simple
5+
Ordinary Differential Equation (ODE) systems.
6+
7+
## Installing ModelingToolkit
8+
9+
To install ModelingToolkit, use the Julia package manager. This can be done as follows:
10+
11+
```julia
12+
using Pkg
13+
Pkg.add("ModelingToolkit")
14+
```
615

716
## Copy-Pastable Simplified Example
817

@@ -22,21 +31,14 @@ D = Differential(t)
2231
@variables begin
2332
x(t) # dependent variables
2433
end
25-
@structural_parameters begin
26-
h = 1
27-
end
2834
@equations begin
29-
D(x) ~ (h - x) / τ
35+
D(x) ~ (1 - x) / τ
3036
end
3137
end
3238
3339
using DifferentialEquations: solve
34-
35-
@named fol = FOL()
36-
fol = complete(fol)
37-
40+
@mtkbuild fol = FOL()
3841
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'.
4042
sol = solve(prob)
4143
4244
using Plots
@@ -58,7 +60,7 @@ Here, ``t`` is the independent variable (time), ``x(t)`` is the (scalar) state
5860
variable, ``f(t)`` is an external forcing function, and ``\tau`` is a
5961
parameter.
6062
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
63+
first set the forcing function to a time-independent value ``1``. And the
6264
independent variable ``t`` is automatically added by ``@mtkmodel``.
6365

6466
```@example ode2
@@ -74,32 +76,17 @@ D = Differential(t)
7476
@variables begin
7577
x(t) # dependent variables
7678
end
77-
@structural_parameters begin
78-
h = 1
79-
end
8079
@equations begin
81-
D(x) ~ (h - x) / τ
80+
D(x) ~ (1 - x) / τ
8281
end
8382
end
8483
85-
@named fol_incomplete = FOL()
86-
fol = complete(fol_incomplete)
84+
@mtkbuild fol = FOL()
8785
```
8886

8987
Note that equations in MTK use the tilde character (`~`) as equality sign.
9088

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-
```
89+
`@mtkbuild` creates an instance of `FOL` named as `fol`.
10390

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

@@ -115,22 +102,6 @@ The initial state and the parameter values are specified using a mapping
115102
from the actual symbolic elements to their values, represented as an array
116103
of `Pair`s, which are constructed using the `=>` operator.
117104

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-
134105
## Algebraic relations and structural simplification
135106

136107
You could separate the calculation of the right-hand side, by introducing an
@@ -147,46 +118,40 @@ using ModelingToolkit
147118
x(t) # dependent variables
148119
RHS(t)
149120
end
150-
@structural_parameters begin
151-
h = 1
152-
end
153121
begin
154122
D = Differential(t)
155123
end
156124
@equations begin
157-
RHS ~ (h - x) / τ
125+
RHS ~ (1 - x) / τ
158126
D(x) ~ RHS
159127
end
160128
end
161129
162-
@named fol_separate = FOL()
130+
@mtkbuild fol = FOL()
163131
```
164132

165-
To directly solve this system, you would have to create a Differential-Algebraic
166-
Equation (DAE) problem, since besides the differential equation, there is an
167-
additional algebraic equation now. However, this DAE system can obviously be
168-
transformed into the single ODE we used in the first example above. MTK achieves
169-
this by structural simplification:
133+
You can look at the equations by using the command `equations`:
170134

171135
```@example ode2
172-
fol_simplified = structural_simplify(complete(fol_separate))
173-
equations(fol_simplified)
136+
equations(fol)
174137
```
175138

139+
Notice that there is only one equation in this system, `Differential(t)(x(t)) ~ RHS(t)`.
140+
The other equation was removed from the system and was transformed into an `observed`
141+
variable. Observed equations are variables which can be computed on-demand but are not
142+
necessary for the solution of the system, and thus MTK tracks it separately. One can
143+
check the observed equations via the `observed` function:
144+
176145
```@example ode2
177-
equations(fol_simplified) == equations(fol)
146+
observed(fol)
178147
```
179148

180-
You can extract the equations from a system using `equations` (and, in the same
181-
way, `states` and `parameters`). The simplified equation is exactly the same
182-
as the original one, so the simulation performance will also be the same.
183-
However, there is one difference. MTK does keep track of the eliminated
184-
algebraic variables as "observables" (see
185-
[Observables and Variable Elimination](@ref)).
186-
That means, MTK still knows how to calculate them out of the information available
149+
For more information on this process, see [Observables and Variable Elimination](@ref).
150+
151+
MTK still knows how to calculate them out of the information available
187152
in a simulation result. The intermediate variable `RHS` therefore can be plotted
188-
along with the state variable. Note that this has to be requested explicitly,
189-
through:
153+
along with the state variable. Note that this has to be requested explicitly
154+
like as follows:
190155

191156
```@example ode2
192157
prob = ODEProblem(fol_simplified,
@@ -197,16 +162,26 @@ sol = solve(prob)
197162
plot(sol, vars = [fol_simplified.x, fol_simplified.RHS])
198163
```
199164

200-
By default, `structural_simplify` also replaces symbolic `constants` with
201-
their default values. This allows additional simplifications not possible
202-
when using `parameters` (e.g., solution of linear equations by dividing out
203-
the constant's value, which cannot be done for parameters, since they may
204-
be zero).
165+
## Named Indexing of Solutions
166+
167+
Note that the indexing of the solution similarly works via the names, and so to get
168+
the time series for `x`, one would do:
169+
170+
```@example ode2
171+
sol[fol.x]
172+
```
173+
174+
or to get the second value in the time series for `x`:
175+
176+
```@example ode2
177+
sol[fol.x,2]
178+
```
205179

206-
Note that the indexing of the solution similarly works via the names, and so
207-
`sol[x]` gives the time-series for `x`, `sol[x,2:10]` gives the 2nd through 10th
208-
values of `x` matching `sol.t`, etc. Note that this works even for variables
209-
which have been eliminated, and thus `sol[RHS]` retrieves the values of `RHS`.
180+
Similarly, the time series for `RHS` can be retrieved using the same indexing:
181+
182+
```@example ode2
183+
sol[fol.RHS]
184+
```
210185

211186
## Specifying a time-variable forcing function
212187

@@ -222,9 +197,6 @@ Obviously, one could use an explicit, symbolic function of time:
222197
x(t) # dependent variables
223198
f(t)
224199
end
225-
@structural_parameters begin
226-
h = 1
227-
end
228200
begin
229201
D = Differential(t)
230202
end
@@ -445,14 +417,9 @@ using the structural information. For more information, see the
445417

446418
Here are some notes that may be helpful during your initial steps with MTK:
447419

448-
- Sometimes, the symbolic engine within MTK cannot correctly identify the
449-
independent variable (e.g. time) out of all variables. In such a case, you
450-
usually get an error that some variable(s) is "missing from variable map". In
451-
most cases, it is then sufficient to specify the independent variable as second
452-
argument to `ODESystem`, e.g. `ODESystem(eqs, t)`.
453-
- A completely macro-free usage of MTK is possible and is discussed in a
454-
separate tutorial. This is for package developers, since the macros are only
455-
essential for automatic symbolic naming for modelers.
420+
- The `@mtkmodel` macro is for high-level usage of MTK. However, in many cases you
421+
may need to programmatically generate `ODESystem`s. If that's the case, check out
422+
the [Programmatically Generating and Scripting ODESystems Tutorial](@ref programmatically).
456423
- Vector-valued parameters and variables are possible. A cleaner, more
457424
consistent treatment of these is still a work in progress, however. Once finished,
458425
this introductory tutorial will also cover this feature.

0 commit comments

Comments
 (0)