@@ -5,194 +5,14 @@ manner. It is designed as a semi-DSL for easily building large complex models
5
5
and manipulating the models to generate optimal forms to be used in numerical
6
6
methods.
7
7
8
- ## Examples
9
-
10
- ### Example 1: Symbolically Building an ODEProblem for DifferentialEquations.jl
11
-
12
- Let's build an ODE. First, we define some variables. In a differential equation
13
- system, we need to differentiate between our (dependent) variables
14
- and parameters. Therefore, we label them as follows:
15
-
16
- ``` julia
17
- using ModelingToolkit
18
-
19
- @parameters t σ ρ β
20
- @variables x (t) y (t) z (t)
21
- @derivatives D' ~ t
22
- ```
23
-
24
- Then we build the system:
25
-
26
- ``` julia
27
- eqs = [D (x) ~ σ* (y- x),
28
- D (y) ~ x* (ρ- z)- y,
29
- D (z) ~ x* y - β* z]
30
- ```
31
-
32
- Each operation builds an ` Operation ` type, and thus ` eqs ` is an array of
33
- ` Operation ` and ` Variable ` s. This holds a tree of the full system that can be
34
- analyzed by other programs. We can turn this into a ` ODESystem ` via:
35
-
36
- ``` julia
37
- sys = ODESystem (eqs)
38
- ```
39
-
40
- This ` ODESystem ` can then be used to generate an ` ODEProblem ` by supplying the
41
- constructor with a map from the states of the system to their initial condition
42
- values and from the parameters of the system to their values. For example:
43
-
44
- ``` julia
45
- u0 = [x => 1.0
46
- y => 0.0
47
- z => 0.0 ]
48
-
49
- p = [σ => 10.0
50
- ρ => 28.0
51
- β => 8 / 3 ]
52
- tspan = (0.0 ,100.0 )
53
- prob = ODEProblem (sys,u0,tspan,p;jac= true ,sparse= true )
54
- ```
55
-
56
- Note that the additional ` jac=true ` tells the system to symbolically generate
57
- an optimized Jacobian function to enhance the differential equation solvers,
58
- and ` sparse ` tells it to build the ODEProblem with all of the enhancements
59
- setup for sparse Jacobians.
60
-
61
- ### Example 2: Building a Component-Based ODEProblem
62
-
63
- In addition, we can then use ModelingToolkit to compose multiple ODE subsystems.
64
- Let's define two interacting Lorenz equations:
65
-
66
- ``` julia
67
- lorenz1 = ODESystem (eqs,name= :lorenz1 )
68
- lorenz2 = ODESystem (eqs,name= :lorenz2 )
69
-
70
- @variables α
71
- @parameters γ
72
- connections = [0 ~ lorenz1. x + lorenz2. y + sin (α* γ)]
73
- connected = ODESystem (connections,[α],[γ],systems= [lorenz1,lorenz2])
74
- ```
75
-
76
- which is now a differential-algebraic equation (DAE) of 7 variables, which has
77
- two independent Lorenz systems and an algebraic equation that determines ` α `
78
- such that an implicit constraint holds. We can then define the resulting
79
- ` ODEProblem ` and send it over to DifferentialEquations.jl.
80
-
81
- ### Example 3: Building Nonlinear Systems to Solve with NLsolve.jl
82
-
83
- In this example we will go one step deeper and showcase the direct function
84
- generation capabilities in ModelingToolkit.jl to build nonlinear systems.
85
- Let's say we wanted to solve for the steady state of the previous ODE. This is
86
- the nonlinear system defined by where the derivatives are zero. We use (unknown)
87
- variables for our nonlinear system.
88
-
89
- ``` julia
90
- using ModelingToolkit
91
-
92
- @variables x y z
93
- @parameters σ ρ β
94
-
95
- # Define a nonlinear system
96
- eqs = [0 ~ σ* (y- x),
97
- 0 ~ x* (ρ- z)- y,
98
- 0 ~ x* y - β* z]
99
- ns = NonlinearSystem (eqs, [x,y,z], [σ,ρ,β])
100
- nlsys_func = generate_function (ns)[2 ] # second is the inplace version
101
- ```
102
-
103
- which generates:
104
-
105
- ``` julia
106
- (var"##MTIIPVar#405" , u, p)-> begin
107
- @inbounds begin
108
- @inbounds begin
109
- let (x, y, z, σ, ρ, β) = (u[1 ], u[2 ], u[3 ], p[1 ], p[2 ], p[3 ])
110
- var"##MTIIPVar#405" [1 ] = (* )(σ, (- )(y, x))
111
- var"##MTIIPVar#405" [2 ] = (- )((* )(x, (- )(ρ, z)), y)
112
- var"##MTIIPVar#405" [3 ] = (- )((* )(x, y), (* )(β, z))
113
- end
114
- end
115
- end
116
- nothing
117
- end
118
- ```
119
-
120
- We can use this to build a nonlinear function for use with NLsolve.jl:
121
-
122
- ``` julia
123
- f = eval (nlsys_func)
124
- du = zeros (3 ); u = ones (3 )
125
- params = (10.0 ,26.0 ,2.33 )
126
- f (du,u,params)
127
- du
128
-
129
- #=
130
- 3-element Array{Float64,1}:
131
- 0.0
132
- 24.0
133
- -1.33
134
- =#
135
- ```
136
-
137
- We can similarly ask to generate the in-place Jacobian function:
138
-
139
- ``` julia
140
- j_func = generate_jacobian (ns)[2 ] # second is in-place
141
- j! = eval (j_func)
142
- ```
143
-
144
- which gives:
145
-
146
- ``` julia
147
- :((var"##MTIIPVar#582" , u, p)-> begin
148
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:70 =#
149
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:71 =#
150
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:71 =# @inbounds begin
151
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:72 =#
152
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:53 =# @inbounds begin
153
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:53 =#
154
- let (x, y, z, σ, ρ, β) = (u[1 ], u[2 ], u[3 ], p[1 ], p[2 ], p[3 ])
155
- var"##MTIIPVar#582" [1 ] = (* )(σ, - 1 )
156
- var"##MTIIPVar#582" [2 ] = (- )(ρ, z)
157
- var"##MTIIPVar#582" [3 ] = y
158
- var"##MTIIPVar#582" [4 ] = σ
159
- var"##MTIIPVar#582" [5 ] = - 1
160
- var"##MTIIPVar#582" [6 ] = x
161
- var"##MTIIPVar#582" [7 ] = 0
162
- var"##MTIIPVar#582" [8 ] = (* )(x, - 1 )
163
- var"##MTIIPVar#582" [9 ] = (* )(- 1 , β)
164
- end
165
- end
166
- end
167
- #= C:\Users\accou\.julia\dev\ModelingToolkit\src\utils.jl:74 =#
168
- nothing
169
- end )
170
- ```
171
-
172
- Now, we can call ` nlsolve ` by enclosing our parameters into the functions:
173
-
174
- ``` julia
175
- using NLsolve
176
- nlsolve ((out, x) -> f (out, x, params), (out, x) -> j! (out, x, params), ones (3 ))
177
- ```
178
-
179
- If one would like the generated function to be a Julia function instead of an expression, and allow this
180
- function to be used from within the same world-age, one simply needs to pass ` Val{false} ` to tell it to
181
- generate the function, i.e.:
182
-
183
- ``` julia
184
- nlsys_func = generate_function (ns, [x,y,z], [σ,ρ,β], expression= Val{false })[2 ]
185
- ```
186
-
187
- which uses GeneralizedGenerated.jl to build the same world-age function on the fly without eval.
188
-
189
8
## High-Level API Documentation
190
9
191
10
``` @docs
192
11
@parameters
193
12
@variables
194
13
@derivatives
195
14
Base.:~(::Expression, ::Expression)
15
+ modelingtoolkitize
196
16
```
197
17
198
18
## Additional High-Level Explanations and Tips
@@ -211,112 +31,49 @@ NonlinearSystem(eqs)
211
31
212
32
### Direct Tracing
213
33
214
- Because the ModelingToolkit ` Expression ` types obey Julia semantics, one can
215
- directly transform existing Julia functions into ModelingToolkit symbolic
216
- representations of the function by simply inputting the symbolic values into
217
- the function and using what is returned. For example, let's take the following
218
- numerical PDE discretization :
34
+ Because ModelingToolkit expressions respect Julia semantics, one way
35
+ to generate symbolic expressions is to simply place ModelingToolkit
36
+ variables as inputs into existing Julia code. For example, the following
37
+ uses the standard Julia function for the Lorenz equations to generate
38
+ the symbolic expression for the Lorenz equations :
219
39
220
40
``` julia
221
- using ModelingToolkit, LinearAlgebra, SparseArrays
222
-
223
- # Define the constants for the PDE
224
- const α₂ = 1.0
225
- const α₃ = 1.0
226
- const β₁ = 1.0
227
- const β₂ = 1.0
228
- const β₃ = 1.0
229
- const r₁ = 1.0
230
- const r₂ = 1.0
231
- const _DD = 100.0
232
- const γ₁ = 0.1
233
- const γ₂ = 0.1
234
- const γ₃ = 0.1
235
- const N = 8
236
- const X = reshape ([i for i in 1 : N for j in 1 : N],N,N)
237
- const Y = reshape ([j for i in 1 : N for j in 1 : N],N,N)
238
- const α₁ = 1.0 .* (X.>= 4 * N/ 5 )
239
-
240
- const Mx = Tridiagonal ([1.0 for i in 1 : N- 1 ],[- 2.0 for i in 1 : N],[1.0 for i in 1 : N- 1 ])
241
- const My = copy (Mx)
242
- Mx[2 ,1 ] = 2.0
243
- Mx[end - 1 ,end ] = 2.0
244
- My[1 ,2 ] = 2.0
245
- My[end ,end - 1 ] = 2.0
246
-
247
- # Define the discretized PDE as an ODE function
248
- function f! (du,u,p,t)
249
- A = @view u[:,:,1 ]
250
- B = @view u[:,:,2 ]
251
- C = @view u[:,:,3 ]
252
- dA = @view du[:,:,1 ]
253
- dB = @view du[:,:,2 ]
254
- dC = @view du[:,:,3 ]
255
- mul! (MyA,My,A)
256
- mul! (AMx,A,Mx)
257
- @. DA = _DD* (MyA + AMx)
258
- @. dA = DA + α₁ - β₁* A - r₁* A* B + r₂* C
259
- @. dB = α₂ - β₂* B - r₁* A* B + r₂* C
260
- @. dC = α₃ - β₃* C + r₁* A* B - r₂* C
261
- end
262
- ```
263
-
264
- We can then define the corresponding arrays as ModelingToolkit variables:
265
-
266
- ``` julia
267
- # Define the initial condition as normal arrays
268
- @variables du[1 : N,1 : N,1 : 3 ] u[1 : N,1 : N,1 : 3 ] MyA[1 : N,1 : N] AMx[1 : N,1 : N] DA[1 : N,1 : N]
269
- f! (du,u,nothing ,0.0 )
270
- ```
271
-
272
- The output, here the in-place modified ` du ` , is a symbolic representation of
273
- each output of the function. We can then utilize this in the ModelingToolkit
274
- functionality. For example, let's compute the sparse Jacobian function and
275
- compile a fast multithreaded version:
276
-
277
- ``` julia
278
- jac = sparse (ModelingToolkit. jacobian (vec (du),vec (u),simplify= false ))
279
- multithreadedjac = eval (ModelingToolkit. build_function (vec (jac),u,multithread= true )[2 ])
280
- ```
281
-
282
- ### modelingtoolkitize
283
-
284
- For some ` DEProblem ` types, automatic tracing functionality is already included
285
- via the ` modelingtoolkitize ` function. Take, for example, the Robertson ODE
286
- defined as an ` ODEProblem ` for DifferentialEquations.jl:
287
-
288
- ``` julia
289
- using DifferentialEquations
290
- function rober (du,u,p,t)
291
- y₁,y₂,y₃ = u
292
- k₁,k₂,k₃ = p
293
- du[1 ] = - k₁* y₁+ k₃* y₂* y₃
294
- du[2 ] = k₁* y₁- k₂* y₂^ 2 - k₃* y₂* y₃
295
- du[3 ] = k₂* y₂^ 2
296
- nothing
41
+ function lorenz (du,u,p,t)
42
+ du[1 ] = 10.0 (u[2 ]- u[1 ])
43
+ du[2 ] = u[1 ]* (28.0 - u[3 ]) - u[2 ]
44
+ du[3 ] = u[1 ]* u[2 ] - (8 / 3 )* u[3 ]
297
45
end
298
- prob = ODEProblem (rober,[1.0 ,0.0 ,0.0 ],(0.0 ,1e5 ),(0.04 ,3e7 ,1e4 ))
46
+ @variables t u[1 : 3 ](t) du[1 : 3 ](t)
47
+ @parameters p[1 : 3 ]
48
+ lorenz (du,u,p,t)
49
+ du
299
50
```
300
51
301
- If we want to get a symbolic representation, we can simply call ` modelingtoolkitize `
302
- on the ` prob ` , which will return an ` ODESystem ` :
303
-
304
52
``` julia
305
- sys = modelingtoolkitize (prob)
306
- ```
53
+ 3 - element Array{Operation,1 }:
54
+ 10.0 * (u₂ (t) - u₁ (t))
55
+ u₁ (t) * (28.0 - u₃ (t)) - u₂ (t)
56
+ u₁ (t) * u₂ (t) - 2.6666666666666665 * u₃ (t)
57
+ ```
307
58
308
- Using this, we can symbolically build the Jacobian and then rebuild the ODEProblem :
59
+ Or similarly :
309
60
310
- ``` julia
311
- jac = eval (ModelingToolkit. generate_jacobian (sys)[2 ])
312
-
313
- f = ODEFunction (rober, jac= jac)
314
- prob_jac = ODEProblem (f,[1.0 ,0.0 ,0.0 ],(0.0 ,1e5 ),(0.04 ,3e7 ,1e4 ))
315
- ```
61
+ ``` julia
62
+ @variables t x (t) y (t) z (t) dx (t) dy (t) dz (t)
63
+ @parameters σ ρ β
64
+ du = [dx,dy,dz]
65
+ u = [x,y,z]
66
+ p = [σ,ρ,β]
67
+ lorenz (du,u,p,t)
68
+ du
69
+ ```
316
70
317
- ``` @docs
318
- modelingtoolkitize
319
- ```
71
+ ``` julia
72
+ 3 - element Array{Operation,1 }:
73
+ 10.0 * (y (t) - x (t))
74
+ x (t) * (28.0 - z (t)) - y (t)
75
+ x (t) * y (t) - 2.6666666666666665 * z (t)
76
+ ```
320
77
321
78
### Intermediate Calculations
322
79
0 commit comments