Skip to content

Commit 692a52e

Browse files
Merge pull request #489 from SciML/docs
Restructure tutorial section of the documentation
2 parents 14baeb9 + 41ab866 commit 692a52e

File tree

7 files changed

+448
-279
lines changed

7 files changed

+448
-279
lines changed

docs/make.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ makedocs(
1010
canonical="https://mtk.sciml.ai/stable/"),
1111
pages=[
1212
"Home" => "index.md",
13-
"highlevel.md",
13+
"Tutorials" => Any[
14+
"tutorials/ode_modeling.md",
15+
"tutorials/higher_order.md",
16+
"tutorials/nonlinear.md",
17+
"tutorials/modelingtoolkitize.md",
18+
"tutorials/auto_parallel.md"
19+
],
1420
"Systems" => Any[
1521
"systems/AbstractSystem.md",
1622
"systems/ODESystem.md",
@@ -22,6 +28,7 @@ makedocs(
2228
"systems/PDESystem.md",
2329
"systems/DependencyGraphs.md"
2430
],
31+
"highlevel.md",
2532
"IR.md"
2633
]
2734
)

docs/src/highlevel.md

Lines changed: 35 additions & 278 deletions
Original file line numberDiff line numberDiff line change
@@ -5,194 +5,14 @@ manner. It is designed as a semi-DSL for easily building large complex models
55
and manipulating the models to generate optimal forms to be used in numerical
66
methods.
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
19514
Base.:~(::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]
29745
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
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

Comments
 (0)