Skip to content

Commit a05e746

Browse files
committed
Merge branch 'master' into careful-event-affects
2 parents e750219 + b52bce7 commit a05e746

36 files changed

+1560
-432
lines changed

.github/workflows/Documentation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- uses: actions/checkout@v4
2121
- uses: julia-actions/setup-julia@latest
2222
with:
23-
version: '1'
23+
version: 'lts'
2424
- run: sudo apt-get update && sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev
2525
- name: Install dependencies
2626
run: DISPLAY=:0 xvfb-run -s '-screen 0 1024x768x24' julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'

Project.toml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ModelingToolkit"
22
uuid = "961ee093-0014-501f-94e3-6117800e7a78"
33
authors = ["Yingbo Ma <[email protected]>", "Chris Rackauckas <[email protected]> and contributors"]
4-
version = "9.47.0"
4+
version = "9.50.0"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"
@@ -21,6 +21,7 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
2121
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
2222
DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf"
2323
DynamicQuantities = "06fc5a27-2a28-4c7c-a15d-362465fb6821"
24+
EnumX = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
2425
ExprTools = "e2ba6199-217a-4e67-a87a-7c52f15ade04"
2526
Expronicon = "6b7a57c9-7cc1-4fdf-b7f5-e857abae3636"
2627
FindFirstFunctions = "64ca27bc-2ba2-4a57-88aa-44e436879224"
@@ -94,6 +95,7 @@ Distributions = "0.23, 0.24, 0.25"
9495
DocStringExtensions = "0.7, 0.8, 0.9"
9596
DomainSets = "0.6, 0.7"
9697
DynamicQuantities = "^0.11.2, 0.12, 0.13, 1"
98+
EnumX = "1.0.4"
9799
ExprTools = "0.1.10"
98100
Expronicon = "0.8"
99101
FindFirstFunctions = "1"
@@ -111,7 +113,7 @@ Libdl = "1"
111113
LinearAlgebra = "1"
112114
MLStyle = "0.4.17"
113115
NaNMath = "0.3, 1"
114-
NonlinearSolve = "3.14"
116+
NonlinearSolve = "3.14, 4"
115117
OffsetArrays = "1"
116118
OrderedCollections = "1"
117119
OrdinaryDiffEq = "6.82.0"
@@ -121,17 +123,17 @@ REPL = "1"
121123
RecursiveArrayTools = "3.26"
122124
Reexport = "0.2, 1"
123125
RuntimeGeneratedFunctions = "0.5.9"
124-
SciMLBase = "2.56.1"
126+
SciMLBase = "2.57.1"
125127
SciMLStructures = "1.0"
126128
Serialization = "1"
127129
Setfield = "0.7, 0.8, 1"
128-
SimpleNonlinearSolve = "0.1.0, 1"
130+
SimpleNonlinearSolve = "0.1.0, 1, 2"
129131
SparseArrays = "1"
130132
SpecialFunctions = "0.7, 0.8, 0.9, 0.10, 1.0, 2"
131133
StaticArrays = "0.10, 0.11, 0.12, 1.0"
132134
SymbolicIndexingInterface = "0.3.31"
133135
SymbolicUtils = "3.7"
134-
Symbolics = "6.15.2"
136+
Symbolics = "6.15.4"
135137
URIs = "1"
136138
UnPack = "0.1, 1.0"
137139
Unitful = "1.1"

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Distributions = "0.25"
3131
Documenter = "1"
3232
DynamicQuantities = "^0.11.2, 0.12, 1"
3333
ModelingToolkit = "8.33, 9"
34-
NonlinearSolve = "3"
34+
NonlinearSolve = "3, 4"
3535
Optim = "1.7"
3636
Optimization = "3.9"
3737
OptimizationOptimJL = "0.1"

docs/src/basics/AbstractSystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Optionally, a system could have:
6363
- `get_defaults(sys)`: A `Dict` that maps variables into their default values
6464
for the current-level system.
6565
- `get_noiseeqs(sys)`: Noise equations of the current-level system.
66+
- `get_description(sys)`: A string that describes what a system represents.
6667
- `get_metadata(sys)`: Any metadata about the system or its origin to be used by downstream packages.
6768

6869
Note that if you know a system is an `AbstractTimeDependentSystem` you could use `get_iv` to get the

docs/src/basics/MTKLanguage.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ julia> ModelingToolkit.getdefault(model_c1.v)
147147
2.0
148148
```
149149

150-
#### `@extend` begin block
150+
#### `@extend` statement
151151

152-
Partial systems can be extended in a higher system in two ways:
152+
One or more partial systems can be extended in a higher system with `@extend` statements. This can be done in two ways:
153153

154154
- `@extend PartialSystem(var1 = value1)`
155155

@@ -313,7 +313,8 @@ end
313313
- `:components`: The list of sub-components in the form of [[name, sub_component_name],...].
314314
- `:constants`: Dictionary of constants mapped to its metadata.
315315
- `:defaults`: Dictionary of variables and default values specified in the `@defaults`.
316-
- `:extend`: The list of extended unknowns, name given to the base system, and name of the base system.
316+
- `:extend`: The list of extended unknowns, parameters and components, name given to the base system, and name of the base system.
317+
When multiple extend statements are present, latter two are returned as lists.
317318
- `:structural_parameters`: Dictionary of structural parameters mapped to their metadata.
318319
- `:parameters`: Dictionary of symbolic parameters mapped to their metadata. For
319320
parameter arrays, length is added to the metadata as `:size`.

docs/src/examples/perturbation.md

Lines changed: 53 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,104 @@
11
# [Symbolic-Numeric Perturbation Theory for ODEs](@id perturb_diff)
22

3-
## Prelims
3+
In the [Mixed Symbolic-Numeric Perturbation Theory tutorial](https://symbolics.juliasymbolics.org/stable/tutorials/perturbation/), we discussed how to solve algebraic equations using **Symbolics.jl**. Here we extend the method to differential equations. The procedure is similar, but the Taylor series coefficients now become functions of an independent variable (usually time).
44

5-
In the previous tutorial, [Mixed Symbolic-Numeric Perturbation Theory](https://symbolics.juliasymbolics.org/stable/examples/perturbation/), 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](https://symbolics.juliasymbolics.org/stable/examples/perturbation/):
5+
## Free fall in a varying gravitational field
66

7-
```@example perturbation
8-
using Symbolics
9-
10-
def_taylor(x, ps) = sum([a * x^(i - 1) for (i, a) in enumerate(ps)])
11-
12-
function collect_powers(eq, x, ns; max_power = 100)
13-
eq = substitute(expand(eq), Dict(x^j => 0 for j in (last(ns) + 1):max_power))
14-
15-
eqs = []
16-
for i in ns
17-
powers = Dict(x^j => (i == j ? 1 : 0) for j in 1:last(ns))
18-
e = substitute(eq, powers)
19-
20-
# manually remove zeroth order from higher orders
21-
if 0 in ns && i != 0
22-
e = e - eqs[1]
23-
end
24-
25-
push!(eqs, e)
26-
end
27-
eqs
28-
end
29-
30-
function solve_coef(eqs, ps)
31-
vals = Dict()
32-
33-
for i in 1:length(ps)
34-
eq = substitute(eqs[i], vals)
35-
vals[ps[i]] = Symbolics.symbolic_linear_solve(eq ~ 0, ps[i])
36-
end
37-
vals
38-
end
39-
```
40-
41-
## The Trajectory of a Ball!
42-
43-
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).
44-
45-
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
7+
Our first ODE example is a well-known physics problem: what is the altitude $x(t)$ of an object (say, a ball or a rocket) thrown vertically with initial velocity $ẋ(0)$ from the surface of a planet with mass $M$ and radius $R$? According to Newton's second law and law of gravity, it is the solution of the ODE
468

479
```math
48-
\ddot{x}(t) = -\frac{1}{(1 + \epsilon x(t))^2}
49-
```
50-
51-
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
52-
53-
```@example perturbation
54-
using ModelingToolkit: t_nounits as t, D_nounits as D
55-
order = 2
56-
n = order + 1
57-
@variables ϵ (y(t))[1:n] (∂∂y(t))[1:n]
10+
ẍ = -\frac{GM}{(R+x)^2} = -\frac{GM}{R^2} \frac{1}{\left(1+ϵ\frac{x}{R}\right)^2}.
5811
```
5912

60-
Next, we define $x$.
61-
62-
```@example perturbation
63-
x = def_taylor(ϵ, y)
64-
```
13+
In the last equality, we introduced a perturbative expansion parameter $ϵ$. When $ϵ=1$, we recover the original problem. When $ϵ=0$, the problem reduces to the trivial problem $ẍ = -g$ with constant gravitational acceleration $g = GM/R^2$ and solution $x(t) = x(0) + ẋ(0) t - \frac{1}{2} g t^2$. This is a good setup for perturbation theory.
6514

66-
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
15+
To make the problem dimensionless, we redefine $x \leftarrow x / R$ and $t \leftarrow t / \sqrt{R^3/GM}$. Then the ODE becomes
6716

6817
```@example perturbation
69-
∂∂x = def_taylor(ϵ, ∂∂y)
70-
```
71-
72-
as the second derivative of `x`. After rearrangement, our governing equation is $\ddot{x}(t)(1 + \epsilon x(t))^{-2} + 1 = 0$, or
73-
74-
```@example perturbation
75-
eq = ∂∂x * (1 + ϵ * x)^2 + 1
76-
```
77-
78-
The next two steps are the same as the ones for algebraic equations (note that we pass `1:n` to `collect_powers` because the zeroth order term is needed here)
79-
80-
```@example perturbation
81-
eqs = collect_powers(eq, ϵ, 0:order)
18+
using ModelingToolkit
19+
using ModelingToolkit: t_nounits as t, D_nounits as D
20+
@variables ϵ x(t)
21+
eq = D(D(x)) ~ -(1 + ϵ * x)^(-2)
8222
```
8323

84-
and,
24+
Next, expand $x(t)$ in a series up to second order in $ϵ$:
8525

8626
```@example perturbation
87-
vals = solve_coef(eqs, ∂∂y)
27+
using Symbolics
28+
@variables y(t)[0:2] # coefficients
29+
x_series = series(y, ϵ)
8830
```
8931

90-
Our system of ODEs is forming. Now is the time to convert `∂∂`s to the correct **Symbolics.jl** form by substitution:
32+
Insert this into the equation and collect perturbed equations to each order:
9133

9234
```@example perturbation
93-
subs = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
94-
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
35+
eq_pert = substitute(eq, x => x_series)
36+
eqs_pert = taylor_coeff(eq_pert, ϵ, 0:2)
9537
```
9638

97-
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.
98-
99-
```@example perturbation
100-
using ModelingToolkit, DifferentialEquations
101-
102-
@mtkbuild sys = ODESystem(eqs, t)
103-
unknowns(sys)
104-
```
39+
!!! note
40+
41+
The 0-th order equation can be solved analytically, but ModelingToolkit does currently not feature automatic analytical solution of ODEs, so we proceed with solving it numerically.
42+
These are the ODEs we want to solve. Now construct an `ODESystem`, which automatically inserts dummy derivatives for the velocities:
10543

10644
```@example perturbation
107-
# the initial conditions
108-
# everything is zero except the initial velocity
109-
u0 = Dict([unknowns(sys) .=> 0; D(y[1]) => 1])
110-
111-
prob = ODEProblem(sys, u0, (0, 3.0))
112-
sol = solve(prob; dtmax = 0.01);
45+
@mtkbuild sys = ODESystem(eqs_pert, t)
11346
```
11447

115-
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.
48+
To solve the `ODESystem`, we generate an `ODEProblem` with initial conditions $x(0) = 0$, and $ẋ(0) = 1$, and solve it:
11649

11750
```@example perturbation
118-
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
51+
using DifferentialEquations
52+
u0 = Dict([unknowns(sys) .=> 0.0; D(y[0]) => 1.0]) # nonzero initial velocity
53+
prob = ODEProblem(sys, u0, (0.0, 3.0))
54+
sol = solve(prob)
11955
```
12056

121-
Using `X`, we can plot the trajectory for a range of $𝜀$s.
57+
This is the solution for the coefficients in the series for $x(t)$ and their derivatives. Finally, we calculate the solution to the original problem by summing the series for different $ϵ$:
12258

12359
```@example perturbation
12460
using Plots
125-
126-
plot(sol.t, hcat([X(𝜀) for 𝜀 in 0.0:0.1:0.5]...))
61+
p = plot()
62+
for ϵᵢ in 0.0:0.1:1.0
63+
plot!(p, sol, idxs = substitute(x_series, ϵ => ϵᵢ), label = "ϵ = $ϵᵢ")
64+
end
65+
p
12766
```
12867

129-
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 `𝜀`.
68+
This makes sense: for larger $ϵ$, gravity weakens with altitude, and the trajectory goes higher for a fixed initial velocity.
13069

131-
## A Weakly Nonlinear Oscillator
70+
An advantage of the perturbative method is that we run the ODE solver only once and calculate trajectories for several $ϵ$ for free. Had we solved the full unperturbed ODE directly, we would need to do repeat it for every $ϵ$.
13271

133-
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.
72+
## Weakly nonlinear oscillator
13473

135-
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.
136-
137-
```@example perturbation
138-
order = 2
139-
n = order + 1
140-
@variables ϵ (y(t))[1:n] (∂y)[1:n] (∂∂y)[1:n]
141-
x = def_taylor(ϵ, y)
142-
∂x = def_taylor(ϵ, ∂y)
143-
∂∂x = def_taylor(ϵ, ∂∂y)
144-
```
74+
Our second example applies perturbation theory to nonlinear oscillators -- a very important class of problems. As we will see, perturbation theory has difficulty providing a good solution to this problem, but the process is nevertheless instructive. This example closely follows chapter 7.6 of *Nonlinear Dynamics and Chaos* by Steven Strogatz.
14575

146-
This time we also need the first derivative terms. Continuing,
76+
The goal is to solve the ODE
14777

14878
```@example perturbation
149-
eq = ∂∂x + 2 * ϵ * ∂x + x
150-
eqs = collect_powers(eq, ϵ, 0:n)
151-
vals = solve_coef(eqs, ∂∂y)
79+
eq = D(D(x)) + 2 * ϵ * D(x) + x ~ 0
15280
```
15381

154-
Next, we need to replace ``s and `∂∂`s with their **Symbolics.jl** counterparts:
82+
with initial conditions $x(0) = 0$ and $ẋ(0) = 1$. With $ϵ = 0$, the problem reduces to the simple linear harmonic oscillator with the exact solution $x(t) = \sin(t)$.
15583

156-
```@example perturbation
157-
subs1 = Dict(∂y[i] => D(y[i]) for i in eachindex(y))
158-
subs2 = Dict(∂∂y[i] => D(D(y[i])) for i in eachindex(y))
159-
subs = subs1 ∪ subs2
160-
eqs = [substitute(first(v), subs) ~ substitute(last(v), subs) for v in vals]
161-
```
162-
163-
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)$,
84+
We follow the same steps as in the previous example to construct the `ODESystem`:
16485

16586
```@example perturbation
166-
@mtkbuild sys = ODESystem(eqs, t)
87+
eq_pert = substitute(eq, x => x_series)
88+
eqs_pert = taylor_coeff(eq_pert, ϵ, 0:2)
89+
@mtkbuild sys = ODESystem(eqs_pert, t)
16790
```
16891

169-
```@example perturbation
170-
# the initial conditions
171-
u0 = Dict([unknowns(sys) .=> 0; D(y[1]) => 1])
172-
173-
prob = ODEProblem(sys, u0, (0, 50.0))
174-
sol = solve(prob; dtmax = 0.01)
92+
We solve and plot it as in the previous example, and compare the solution with $ϵ=0.1$ to the exact solution $x(t, ϵ) = e^{-ϵ t} \sin(\sqrt{(1-ϵ^2)}\,t) / \sqrt{1-ϵ^2}$ of the unperturbed equation:
17593

176-
X = 𝜀 -> sum([𝜀^(i - 1) * sol[yi] for (i, yi) in enumerate(y)])
177-
T = sol.t
178-
Y = 𝜀 -> exp.(-𝜀 * T) .* sin.(sqrt(1 - 𝜀^2) * T) / sqrt(1 - 𝜀^2) # exact solution
94+
```@example perturbation
95+
u0 = Dict([unknowns(sys) .=> 0.0; D(y[0]) => 1.0]) # nonzero initial velocity
96+
prob = ODEProblem(sys, u0, (0.0, 50.0))
97+
sol = solve(prob)
98+
plot(sol, idxs = substitute(x_series, ϵ => 0.1); label = "Perturbative (ϵ=0.1)")
17999
180-
plot(sol.t, [Y(0.1), X(0.1)])
100+
x_exact(t, ϵ) = exp(-ϵ * t) * sin(√(1 - ϵ^2) * t) / √(1 - ϵ^2)
101+
plot!(sol.t, x_exact.(sol.t, 0.1); label = "Exact (ϵ=0.1)")
181102
```
182103

183-
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*.
104+
This 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 perturbative solution diverges from the exact 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*, but this is outside the scope of this example.

docs/src/tutorials/stochastic_diffeq.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ where the magnitude of the noise scales with (0.3 times) the magnitude of each o
2323

2424
```math
2525
\begin{aligned}
26-
\frac{dx}{dt} &= (\sigma (y-x)) &+ 0.1x\frac{dB}{dt} \\
27-
\frac{dy}{dt} &= (x(\rho-z) - y) &+ 0.1y\frac{dB}{dt} \\
28-
\frac{dz}{dt} &= (xy - \beta z) &+ 0.1z\frac{dB}{dt} \\
26+
\frac{dx}{dt} &= (\sigma (y-x)) &+ 0.3x\frac{dB}{dt} \\
27+
\frac{dy}{dt} &= (x(\rho-z) - y) &+ 0.3y\frac{dB}{dt} \\
28+
\frac{dz}{dt} &= (xy - \beta z) &+ 0.3z\frac{dB}{dt} \\
2929
\end{aligned}
3030
```
3131

ext/MTKBifurcationKitExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ struct ObservableRecordFromSolution{S, T}
5959
end
6060
end
6161
# Functor function that computes the value.
62-
function (orfs::ObservableRecordFromSolution)(x, p)
62+
function (orfs::ObservableRecordFromSolution)(x, p; k...)
6363
# Updates the state values (in subs_vals).
6464
for state_idx in 1:(orfs.state_end_idxs)
6565
orfs.subs_vals[state_idx] = orfs.subs_vals[state_idx][1] => x[state_idx]

0 commit comments

Comments
 (0)