Skip to content

Commit 58d8ddd

Browse files
Reorganize the documentation
This comes after some hefty discussions with users, customers, etc. along with surveys, analytics, and all of that. We'll be rolling out a slightly perturbed style throughout all of the packages, starting with the biggies, being ModelingToolkit and DifferentialEquations. This comes from some discussions with folks who found the documentation of the following styles to be quite good: - https://pandas.pydata.org/docs/index.html - https://matplotlib.org/stable/index.html - https://www.mathworks.com/help/matlab/ordinary-differential-equations.html?s_tid=CRUX_topnav One noticeable aspect is the separation of tutorials from examples. While these all do something slightly different of course, the general mantra is that tutorials are more beginner explanations of core functionality while examples are just "cool shit" you want to show, which maybe has less text. The latter is mostly useful for showing people what you can do, or giving people starter code. The former is more about a human-level explanation. We had been traditionally mixing the two, but it's good to split them so they can serve their respective functions better. Additionally, for each package we are designating one tutorial to be on top as the "Getting Started" tutorial. The SciML docs as a whole have a "Getting Started" section which we use as a landing page that orients people to the whole ecosystem (https://docs.sciml.ai/Overview/stable/). It's in-depth, multipage, etc. and similar in style to the Pandas or SciPy ones. This then leaves room for the "Getting Started" section of a package to simply be about the package itself. It should have a section at the end that contextualizes, like is seen right now in DifferentialEquations.jl (https://docs.sciml.ai/DiffEqDocs/v7.6/tutorials/ode_example/#Additional-Features-and-Analysis-Tools), but its real focus is "99% of people who use this package should know at least these things". Our Google Analytics shows that >90% of all users read the first tutorial (and about 20% only read the first tutorial in DifferentialEquations, but seem to keep coming back to it!). Thus we basically want a page that will serve as something we "know" or can assume every reader has read. This is why it's then elevated from being the first tutorial to being a separate highlighted "Getting Started" page (which should then also include installation instructions and links to other tutorials / videos). For ModelingToolkit and DifferentialEquations, and many of our other packages, the first tutorial has already been written as a "Getting Started" type of tutorial, while the other tutorials then dig into specific features. So this PR, like the ones that will happen to other package, elevates the first tutorial to this "Getting Started" status. That is not to say everything is completed. The matplotlib and MATLAB documentation examples show that associating a plot with each example and putting them in a tiled view is really nice and accessible. We cannot do that with Documenter.jl, so for now we will at least get the examples curated while we beg for some new way to distinguish them from the rest of the documentation. Additionally, I have taken the following notes for documentation improvements, which I'll put here but not actually do yet in this restructuring PR: - Make as many of the comparisons and explanations into tables. This reduces text and improves readability - Format examples using more than just headers. Use some bolding, italics. Plots in grids, etc. - Move plots up, add plots to tutorials - Make a table introducing all of the modules being used at the top - Get Documenter.jl to change its default font size from being high school cartoon to something more professional - Allow more white space in the pages. Space to digest is more important than keeping pages short - Don't go off template: change the Documenter template because being uniform is more important than being better
1 parent 0d8d1f6 commit 58d8ddd

File tree

6 files changed

+245
-24
lines changed

6 files changed

+245
-24
lines changed

docs/pages.jl

+21-12
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
pages = [
22
"Home" => "index.md",
3-
"Symbolic Modeling Tutorials" => Any["tutorials/ode_modeling.md",
4-
"tutorials/spring_mass.md",
5-
"tutorials/acausal_components.md",
6-
"tutorials/higher_order.md",
7-
"tutorials/tearing_parallelism.md",
8-
"tutorials/nonlinear.md",
9-
"tutorials/optimization.md",
10-
"tutorials/stochastic_diffeq.md",
11-
"tutorials/parameter_identifiability.md"],
12-
"ModelingToolkitize Tutorials" => Any["mtkitize_tutorials/modelingtoolkitize.md",
13-
"mtkitize_tutorials/modelingtoolkitize_index_reduction.md",
14-
"mtkitize_tutorials/sparse_jacobians.md"],
3+
"tutorials/ode_modeling.md",
4+
"Tutorials" => Any[
5+
"tutorials/acausal_components.md",
6+
"tutorials/nonlinear.md",
7+
"tutorials/optimization.md",
8+
"mtkitize_tutorials/modelingtoolkitize.md",
9+
"tutorials/stochastic_diffeq.md",
10+
"tutorials/parameter_identifiability.md"
11+
],
12+
"Examples" => Any[
13+
"Basic Examples" => Any[
14+
"tutorials/higher_order.md",
15+
"tutorials/spring_mass.md",
16+
"mtkitize_tutorials/modelingtoolkitize_index_reduction.md",
17+
],
18+
"Advanced Examples" => Any[
19+
"tutorials/tearing_parallelism.md",
20+
"mtkitize_tutorials/sparse_jacobians.md",
21+
"examples/symbolicnumeric_differential.md",
22+
],
23+
],
1524
"Basics" => Any["basics/AbstractSystem.md",
1625
"basics/ContextualVariables.md",
1726
"basics/Variable_metadata.md",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# [Symbolic-Numerical Perturbation Theory for ODEs](@id perturb_diff)
2+
3+
## Prelims
4+
5+
In the previous tutorial, [Mixed Symbolic-Numeric Perturbation Theory](), we discussed how to solve algebraic equations using **Symbolics.jl**. Here, our goal is to extend the method to differential equations. First, we import the following helper functions that were introduced in [Mixed Symbolic/Numerical Methods for Perturbation Theory - Algebraic Equations](@ref perturb_alg):
6+
7+
```@example perturb
8+
using Symbolics, SymbolicUtils
9+
10+
def_taylor(x, ps) = sum([a*x^i for (i,a) in enumerate(ps)])
11+
def_taylor(x, ps, p₀) = p₀ + def_taylor(x, ps)
12+
13+
function collect_powers(eq, x, ns; max_power=100)
14+
eq = substitute(expand(eq), Dict(x^j => 0 for j=last(ns)+1:max_power))
15+
16+
eqs = []
17+
for i in ns
18+
powers = Dict(x^j => (i==j ? 1 : 0) for j=1:last(ns))
19+
push!(eqs, substitute(eq, powers))
20+
end
21+
eqs
22+
end
23+
24+
function solve_coef(eqs, ps)
25+
vals = Dict()
26+
27+
for i = 1:length(ps)
28+
eq = substitute(eqs[i], vals)
29+
vals[ps[i]] = Symbolics.solve_for(eq ~ 0, ps[i])
30+
end
31+
vals
32+
end
33+
```
34+
35+
## The Trajectory of a Ball!
36+
37+
In the first two examples, we applied the perturbation method to algebraic problems. However, the main power of the perturbation method is to solve differential equations (usually ODEs, but also occasionally PDEs). Surprisingly, the main procedure developed to solve algebraic problems works well for differential equations. In fact, we will use the same two helper functions, `collect_powers` and `solve_coef`. The main difference is in the way we expand the dependent variables. For algebraic problems, the coefficients of $\epsilon$ are constants; whereas, for differential equations, they are functions of the dependent variable (usually time).
38+
39+
As the first ODE example, we have chosen a simple and well-behaved problem, which is a variation of a standard first-year physics problem: what is the trajectory of an object (say, a ball or a rocket) thrown vertically at velocity $v$ from the surface of a planet? Assuming a constant acceleration of gravity, $g$, every burgeoning physicist knows the answer: $x(t) = x(0) + vt - \frac{1}{2}gt^2$. However, what happens if $g$ is not constant? Specifically, $g$ is inversely proportional to the distant from the center of the planet. If $v$ is large and the projectile travels a large fraction of the radius of the planet, the assumption of constant gravity does not hold anymore. However, unless $v$ is large compared to the escape velocity, the correction is usually small. After simplifications and change of variables to dimensionless, the problem becomes
40+
41+
$$
42+
\ddot{x}(t) = -\frac{1}{(1 + \epsilon x(t))^2}
43+
\,,
44+
$$
45+
46+
with the initial conditions $x(0) = 0$, and $\dot{x}(0) = 1$. Note that for $\epsilon = 0$, this equation transforms back to the standard one. Let's start with defining the variables
47+
48+
```@example perturb
49+
n = 2
50+
@variables ϵ t y[0:n](t) ∂∂y[0:n]
51+
```
52+
53+
Next, we define $x$.
54+
55+
```@example perturb
56+
x = def_taylor(ϵ, y[2:end], y[1])
57+
```
58+
59+
We need the second derivative of `x`. It may seem that we can do this using `Differential(t)`; however, this operation needs to wait for a few steps because we need to manipulate the differentials as separate variables. Instead, we define dummy variables `∂∂y` as the placeholder for the second derivatives and define
60+
61+
```@example perturb
62+
∂∂x = def_taylor(ϵ, ∂∂y[2:end], ∂∂y[1])
63+
```
64+
65+
as the second derivative of `x`. After rearrangement, our governing equation is $\ddot{x}(t)(1 + \epsilon x(t))^{-2} + 1 = 0$, or
66+
67+
```@example perturb
68+
eq = ∂∂x * (1 + ϵ*x)^2 + 1
69+
```
70+
71+
The next two steps are the same as the ones for algebraic equations (note that we pass `0:n` to `collect_powers` because the zeroth order term is needed here)
72+
73+
```@example perturb
74+
eqs = collect_powers(eq, ϵ, 0:n)
75+
```
76+
77+
and,
78+
79+
```@example perturb
80+
vals = solve_coef(eqs, ∂∂y)
81+
```
82+
83+
Our system of ODEs is forming. Now is the time to convert `∂∂`s to the correct **Symbolics.jl** form by substitution:
84+
85+
```@example perturb
86+
D = Differential(t)
87+
subs = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
88+
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
89+
```
90+
91+
We are nearly there! From this point on, the rest is standard ODE solving procedures. Potentially we can use a symbolic ODE solver to find a closed form solution to this problem. However, **Symbolics.jl** currently does not support this functionality. Instead, we solve the problem numerically. We form an `ODESystem`, lower the order (convert second derivatives to first), generate an `ODEProblem` (after passing the correct initial conditions), and, finally, solve it.
92+
93+
```@example perturb
94+
using ModelingToolkit, DifferentialEquations
95+
96+
sys = ODESystem(eqs, t)
97+
sys = ode_order_lowering(sys)
98+
states(sys)
99+
```
100+
101+
```@example perturb
102+
# the initial conditions
103+
# everything is zero except the initial velocity
104+
u0 = zeros(2n+2)
105+
u0[3] = 1.0 # y₀ˍt
106+
107+
prob = ODEProblem(sys, u0, (0, 3.0))
108+
sol = solve(prob; dtmax=0.01)
109+
```
110+
111+
Finally, we calculate the solution to the problem as a function of `ϵ` by substituting the solution to the ODE system back into the defining equation for `x`. Note that `𝜀` is a number, compared to `ϵ`, which is a symbolic variable.
112+
113+
```@example perturb
114+
X = 𝜀 -> sum([𝜀^(i-1) * sol[y[i]] for i in eachindex(y)])
115+
```
116+
117+
Using `X`, we can plot the trajectory for a range of $𝜀$s.
118+
119+
```@example perturb
120+
using Plots
121+
122+
plot(sol.t, hcat([X(𝜀) for 𝜀 = 0.0:0.1:0.5]...))
123+
```
124+
125+
As expected, as `𝜀` becomes larger (meaning the gravity is less with altitude), the object goes higher and stays up for a longer duration. Of course, we could have solved the problem directly using as ODE solver. One of the benefits of the perturbation method is that we need to run the ODE solver only once and then can just calculate the answer for different values of `𝜀`; whereas, if we had used the direct method, we would need to run the solver once for each value of `𝜀`.
126+
127+
## A Weakly Nonlinear Oscillator
128+
129+
For the next example, we have chosen a simple example from a very important class of problems, the nonlinear oscillators. As we will see, perturbation theory has difficulty providing a good solution to this problem, but the process is instructive. This example closely follows the chapter 7.6 of *Nonlinear Dynamics and Chaos* by Steven Strogatz.
130+
131+
The goal is to solve $\ddot{x} + 2\epsilon\dot{x} + x = 0$, where the dot signifies time-derivatives and the initial conditions are $x(0) = 0$ and $\dot{x}(0) = 1$. If $\epsilon = 0$, the problem reduces to the simple linear harmonic oscillator with the exact solution $x(t) = \sin(t)$. We follow the same steps as the previous example.
132+
133+
```@example perturb
134+
n = 2
135+
@variables ϵ t y[0:n](t) ∂y[0:n] ∂∂y[0:n]
136+
x = def_taylor(ϵ, y[2:end], y[1])
137+
∂x = def_taylor(ϵ, ∂y[2:end], ∂y[1])
138+
∂∂x = def_taylor(ϵ, ∂∂y[2:end], ∂∂y[1])
139+
```
140+
141+
This time we also need the first derivative terms. Continuing,
142+
143+
```@example perturb
144+
eq = ∂∂x + 2*ϵ*∂x + x
145+
eqs = collect_powers(eq, ϵ, 0:n)
146+
vals = solve_coef(eqs, ∂∂y)
147+
```
148+
149+
Next, we need to replace ``s and `∂∂`s with their **Symbolics.jl** counterparts:
150+
151+
```@example perturb
152+
D = Differential(t)
153+
subs1 = Dict(∂y[i] => D(y[i]) for i in eachindex(y))
154+
subs2 = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
155+
subs = subs1 ∪ subs2
156+
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
157+
```
158+
159+
We continue with converting 'eqs' to an `ODEProblem`, solving it, and finally plot the results against the exact solution to the original problem, which is $x(t, \epsilon) = (1 - \epsilon)^{-1/2} e^{-\epsilon t} \sin((1- \epsilon^2)^{1/2}t)$,
160+
161+
```@example perturb
162+
sys = ODESystem(eqs, t)
163+
sys = ode_order_lowering(sys)
164+
```
165+
166+
```@example perturb
167+
# the initial conditions
168+
u0 = zeros(2n+2)
169+
u0[3] = 1.0 # y₀ˍt
170+
171+
prob = ODEProblem(sys, u0, (0, 50.0))
172+
sol = solve(prob; dtmax=0.01)
173+
174+
X = 𝜀 -> sum([𝜀^(i-1) * sol[y[i]] for i in eachindex(y)])
175+
T = sol.t
176+
Y = 𝜀 -> exp.(-𝜀*T) .* sin.(sqrt(1 - 𝜀^2)*T) / sqrt(1 - 𝜀^2) # exact solution
177+
178+
plot(sol.t, [Y(0.1), X(0.1)])
179+
```
180+
181+
The figure is similar to Figure 7.6.2 in *Nonlinear Dynamics and Chaos*. The two curves fit well for the first couple of cycles, but then the perturbation method curve diverges from the true solution. The main reason is that the problem has two or more time-scales that introduce secular terms in the solution. One solution is to explicitly account for the two time scales and use an analytic method called *two-timing*.

docs/src/mtkitize_tutorials/modelingtoolkitize.md

+33-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1-
# Automatically Accelerating ODEProblem Code
1+
# Modelingtoolkitize: Automatically Translating Numerical to Symbolic Code
22

3-
For some `DEProblem` types, automatic tracing functionality is already included
4-
via the `modelingtoolkitize` function. Take, for example, the Robertson ODE
3+
## What is `modelingtoolkitize`?
4+
5+
From the other tutorials you will have learned that ModelingToolkit is a symbolic library
6+
with all kinds of goodies, such as the ability to derive analytical expressions for things
7+
like Jacobians, determine the sparsity of a set of equations, perform index reduction,
8+
tearing, and other transformations to improve both stability and performance. All of these
9+
are good things, but all of these require that one has defined the problem in a symbolic
10+
way.
11+
12+
**But what happens if one wants to use ModelingToolkit functionality on code that is already
13+
written for DifferentialEquations.jl, NonlinearSolve.jl, Optimization.jl, or beyond?**
14+
15+
`modelingtoolktize` is a function in ModelingToolkit which takes a numerically-defined
16+
`SciMLProblem` and transforms it into its symbolic ModelingToolkit equivalent. By doing
17+
so, ModelingToolkit analysis passes and transformations can be run as intermediate steps
18+
to improve a simulation code before it's passed to the solver.
19+
20+
!!! note
21+
`modelingtoolkitize` does have some limitations, i.e. not all codes that work with the
22+
numerical solvers will work with `modelingtoolkitize`. Namely, it requires the ability
23+
to trace the equations with Symbolics.jl `Num` types. Generally a code which is
24+
compatible with forward-mode automatic differentiation is compatible with
25+
`modelingtoolkitize`.
26+
27+
!!! warn
28+
`modelingtoolkitize` expressions cannot keep control flow structures (loops), and thus
29+
equations with long loops will be translated into large expressions which can increase
30+
the compile time of the equations and reduce the SIMD vectorization achieved by LLVM.
31+
32+
## Example Usage: Generating an Analytical Jacobian Expression for an ODE Code
33+
34+
Take, for example, the Robertson ODE
535
defined as an `ODEProblem` for DifferentialEquations.jl:
636

737
```@example mtkize

docs/src/tutorials/acausal_components.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# [Acausal Component-Based Modeling the RC Circuit](@id acausal)
1+
# [Acausal Component-Based Modeling](@id acausal)
22

33
In this tutorial we will build a hierarchical acausal component-based model of
44
the RC circuit. The RC circuit is a simple example where we connect a resistor
@@ -14,7 +14,7 @@ equalities before solving. Let's see this in action.
1414
This tutorial teaches how to build the entire RC circuit from scratch.
1515
However, to simulate electrical components with more ease, check out the
1616
[ModelingToolkitStandardLibrary.jl](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/)
17-
which includes a
17+
which includes a
1818
[tutorial for simulating RC circuits with pre-built components](https://docs.sciml.ai/ModelingToolkitStandardLibrary/stable/tutorials/rc_circuit/)
1919

2020
## Copy-Paste Example

docs/src/tutorials/higher_order.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ ModelingToolkit has a system for transformations of mathematical
44
systems. These transformations allow for symbolically changing
55
the representation of the model to problems that are easier to
66
numerically solve. One simple to demonstrate transformation is the
7-
`ode_order_lowering` transformation that sends an Nth order ODE
7+
`structural_simplify` with does a lot of tricks, one being the
8+
transformation that sends an Nth order ODE
89
to a 1st order ODE.
910

1011
To see this, let's define a second order riff on the Lorenz equations.
@@ -33,7 +34,7 @@ Now let's transform this into the `ODESystem` of first order components.
3334
We do this by simply calling `ode_order_lowering`:
3435

3536
```@example orderlowering
36-
sys = ode_order_lowering(sys)
37+
sys = structural_simplify(sys)
3738
```
3839

3940
Now we can directly numerically solve the lowered system. Note that,

docs/src/tutorials/ode_modeling.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Composing Ordinary Differential Equations
1+
# Getting Started with ModelingToolkit.jl
22

33
This is an introductory example for the usage of ModelingToolkit (MTK).
44
It illustrates the basic user-facing functionality by means of some
@@ -14,7 +14,7 @@ But if you want to just see some code and run, here's an example:
1414
using ModelingToolkit
1515

1616
@variables t x(t) # independent and dependent variables
17-
@parameters τ # parameters
17+
@parameters τ # parameters
1818
@constants h = 1 # constants have an assigned value
1919
D = Differential(t) # define an operator for the differentiation w.r.t. time
2020

@@ -44,7 +44,7 @@ first-order lag element:
4444
```
4545

4646
Here, ``t`` is the independent variable (time), ``x(t)`` is the (scalar) state
47-
variable, ``f(t)`` is an external forcing function, and ``\tau`` is a
47+
variable, ``f(t)`` is an external forcing function, and ``\tau`` is a
4848
parameter. In MTK, this system can be modelled as follows. For simplicity, we
4949
first set the forcing function to a time-independent value.
5050

@@ -137,10 +137,10 @@ plot(sol, vars=[x, RHS])
137137
```
138138

139139
By default, `structural_simplify` also replaces symbolic `constants` with
140-
their default values. This allows additional simplifications not possible
140+
their default values. This allows additional simplifications not possible
141141
if using `parameters` (eg, solution of linear equations by dividing out
142142
the constant's value, which cannot be done for parameters since they may
143-
be zero).
143+
be zero).
144144

145145
![Simulation result of first-order lag element, with right-hand side](https://user-images.githubusercontent.com/13935112/111958403-7e8d3e00-8aed-11eb-9d18-08b5180a59f9.png)
146146

0 commit comments

Comments
 (0)