@@ -5,194 +5,14 @@ manner. It is designed as a semi-DSL for easily building large complex models
55and manipulating the models to generate optimal forms to be used in numerical
66methods.
77
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-
1898## High-Level API Documentation
1909
19110``` @docs
19211@parameters
19312@variables
19413@derivatives
19514Base.:~(::Expression, ::Expression)
15+ modelingtoolkitize
19616```
19717
19818## Additional High-Level Explanations and Tips
@@ -211,112 +31,49 @@ NonlinearSystem(eqs)
21131
21232### Direct Tracing
21333
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 :
21939
22040``` 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 ]
29745end
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
29950```
30051
301- If we want to get a symbolic representation, we can simply call ` modelingtoolkitize `
302- on the ` prob ` , which will return an ` ODESystem ` :
303-
30452``` 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+ ```
30758
308- Using this, we can symbolically build the Jacobian and then rebuild the ODEProblem :
59+ Or similarly :
30960
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+ ```
31670
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+ ```
32077
32178### Intermediate Calculations
32279
0 commit comments