Skip to content

Commit e907fb9

Browse files
Merge pull request #3563 from SciML/v10
BREAKING: v10 release
2 parents 250e7be + 7bceb7a commit e907fb9

File tree

163 files changed

+8879
-12541
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

163 files changed

+8879
-12541
lines changed

.github/workflows/Documentation.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- master
7+
- v10
78
tags: '*'
89
pull_request:
910

.github/workflows/FormatCheck.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- 'master'
7+
- v10
78
tags: '*'
89
pull_request:
910

.github/workflows/Tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches:
66
- master
77
- 'release-'
8+
- v10
89
paths-ignore:
910
- 'docs/**'
1011
push:

NEWS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
# ModelingToolkit v10 Release Notes
2+
3+
### Callbacks
4+
5+
Callback semantics have changed.
6+
7+
- There is a new `Pre` operator that is used to specify which values are before the callback.
8+
For example, the affect `A ~ A + 1` should now be written as `A ~ Pre(A) + 1`. This is
9+
**required** to be specified - `A ~ A + 1` will now be interpreted as an equation to be
10+
satisfied after the callback (and will thus error since it is unsatisfiable).
11+
12+
- All parameters that are changed by a callback must be declared as discrete parameters to
13+
the callback constructor, using the `discrete_parameters` keyword argument.
14+
15+
```julia
16+
event = SymbolicDiscreteCallback(
17+
[t == 1] => [p ~ Pre(p) + 1], discrete_parameters = [p])
18+
```
19+
120
# ModelingToolkit v9 Release Notes
221

322
### Upgrade guide

Project.toml

Lines changed: 12 additions & 3 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.80.1"
4+
version = "10.0.0"
55

66
[deps]
77
ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b"
@@ -31,6 +31,7 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
3131
FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e"
3232
FunctionWrappersWrappers = "77dc65aa-8811-40c2-897b-53d922fa7daf"
3333
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
34+
ImplicitDiscreteSolve = "3263718b-31ed-49cf-8a0f-35a466e8af96"
3435
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
3536
JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899"
3637
JumpProcesses = "ccbc3e58-028d-4f4c-8cd5-9ae44345cda5"
@@ -43,6 +44,7 @@ NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
4344
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
4445
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
4546
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
47+
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
4648
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
4749
RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd"
4850
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
@@ -87,7 +89,7 @@ BifurcationKit = "0.4"
8789
BlockArrays = "1.1"
8890
BoundaryValueDiffEqAscher = "1.6.0"
8991
BoundaryValueDiffEqMIRK = "1.7.0"
90-
CasADi = "1.0.6"
92+
CasADi = "1.0.7"
9193
ChainRulesCore = "1"
9294
Combinatorics = "1"
9395
CommonSolve = "0.2.4"
@@ -115,6 +117,7 @@ ForwardDiff = "0.10.3"
115117
FunctionWrappers = "1.1"
116118
FunctionWrappersWrappers = "0.1"
117119
Graphs = "1.5.2"
120+
ImplicitDiscreteSolve = "0.1.2"
118121
InfiniteOpt = "0.5"
119122
InteractiveUtils = "1"
120123
JuliaFormatter = "1.0.47, 2"
@@ -176,6 +179,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
176179
ModelingToolkitStandardLibrary = "16a59e39-deab-5bd0-87e4-056b12336739"
177180
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
178181
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
182+
OptimizationBase = "bca83a33-5cc9-4baa-983d-23429ab6bcbb"
179183
OptimizationMOI = "fd9f6733-72f4-499f-8506-86b2bdd0dea1"
180184
OptimizationOptimJL = "36348300-93cb-4f02-beb5-3c3902f8871e"
181185
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
@@ -195,5 +199,10 @@ StochasticDiffEq = "789caeaf-c7a9-5a7d-9973-96adeb23e2a0"
195199
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
196200
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
197201

202+
[sources]
203+
ModelingToolkitStandardLibrary = { url = "https://github.com/SciML/ModelingToolkitStandardLibrary.jl/", rev = "mtk-v10" }
204+
OptimizationBase = { url = "https://github.com/AayushSabharwal/OptimizationBase.jl", rev = "as/mtk-v10" }
205+
OptimizationMOI = { url = "https://github.com/AayushSabharwal/Optimization.jl", subdir = "lib/OptimizationMOI", rev = "as/mtk-v10" }
206+
198207
[targets]
199-
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging"]
208+
test = ["AmplNLWriter", "BenchmarkTools", "BoundaryValueDiffEqMIRK", "BoundaryValueDiffEqAscher", "ControlSystemsBase", "DataInterpolations", "DelayDiffEq", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "OrdinaryDiffEq", "OrdinaryDiffEqCore", "OrdinaryDiffEqDefault", "REPL", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq", "Pkg", "JET", "OrdinaryDiffEqNonlinearSolve", "Logging", "OptimizationBase"]

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Documenter = "1"
4040
DynamicQuantities = "^0.11.2, 0.12, 1"
4141
FMI = "0.14"
4242
FMIZoo = "1"
43-
ModelingToolkit = "8.33, 9"
43+
ModelingToolkit = "10"
4444
ModelingToolkitStandardLibrary = "2.19"
4545
NonlinearSolve = "3, 4"
4646
Optim = "1.7"

docs/src/basics/Composition.md

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,42 +135,34 @@ sys.y = u * 1.1
135135
In a hierarchical system, variables of the subsystem get namespaced by the name of the system they are in. This prevents naming clashes, but also enforces that every unknown and parameter is local to the subsystem it is used in. In some cases it might be desirable to have variables and parameters that are shared between subsystems, or even global. This can be accomplished as follows.
136136

137137
```julia
138-
@parameters a b c d e f
138+
@parameters a b c d
139139

140140
# a is a local variable
141141
b = ParentScope(b) # b is a variable that belongs to one level up in the hierarchy
142142
c = ParentScope(ParentScope(c)) # ParentScope can be nested
143-
d = DelayParentScope(d) # skips one level before applying ParentScope
144-
e = DelayParentScope(e, 2) # second argument allows skipping N levels
145-
f = GlobalScope(f)
143+
d = GlobalScope(d)
146144

147-
p = [a, b, c, d, e, f]
145+
p = [a, b, c, d]
148146

149147
level0 = ODESystem(Equation[], t, [], p; name = :level0)
150148
level1 = ODESystem(Equation[], t, [], []; name = :level1) level0
151149
parameters(level1)
152150
#level0₊a
153151
#b
154152
#c
155-
#level0₊d
156-
#level0₊e
157-
#f
153+
#d
158154
level2 = ODESystem(Equation[], t, [], []; name = :level2) level1
159155
parameters(level2)
160156
#level1₊level0₊a
161157
#level1₊b
162158
#c
163-
#level0₊d
164-
#level1₊level0₊e
165-
#f
159+
#d
166160
level3 = ODESystem(Equation[], t, [], []; name = :level3) level2
167161
parameters(level3)
168162
#level2₊level1₊level0₊a
169163
#level2₊level1₊b
170164
#level2₊c
171-
#level2₊level0₊d
172-
#level1₊level0₊e
173-
#f
165+
#d
174166
```
175167

176168
## Structural Simplify

docs/src/basics/Events.md

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,67 @@ the event occurs). These can both be specified symbolically, but a more [general
2525
functional affect](@ref func_affects) representation is also allowed, as described
2626
below.
2727

28+
## Symbolic Callback Semantics
29+
30+
In callbacks, there is a distinction between values of the unknowns and parameters
31+
*before* the callback, and the desired values *after* the callback. In MTK, this
32+
is provided by the `Pre` operator. For example, if we would like to add 1 to an
33+
unknown `x` in a callback, the equation would look like the following:
34+
35+
```julia
36+
x ~ Pre(x) + 1
37+
```
38+
39+
Non `Pre`-d values will be interpreted as values *after* the callback. As such,
40+
writing
41+
42+
```julia
43+
x ~ x + 1
44+
```
45+
46+
will be interpreted as an algebraic equation to be satisfied after the callback.
47+
Since this equation obviously cannot be satisfied, an error will result.
48+
49+
Callbacks must maintain the consistency of DAEs, meaning that they must satisfy
50+
all the algebraic equations of the system after their update. However, the affect
51+
equations often do not fully specify which unknowns/parameters should be modified
52+
to maintain consistency. To make this clear, MTK uses the following rules:
53+
54+
1. All unknowns are treated as modifiable by the callback. In order to enforce that an unknown `x` remains the same, one can add `x ~ Pre(x)` to the affect equations.
55+
2. All parameters are treated as un-modifiable, *unless* they are declared as `discrete_parameters` to the callback. In order to be a discrete parameter, the parameter must be time-dependent (the terminology *discretes* here means [discrete variables](@ref save_discretes)).
56+
57+
For example, consider the following system.
58+
59+
```julia
60+
@variables x(t) y(t)
61+
@parameters p(t)
62+
@mtkbuild sys = ODESystem([x * y ~ p, D(x) ~ 0], t)
63+
event = [t == 1] => [x ~ Pre(x) + 1]
64+
```
65+
66+
By default what will happen is that `x` will increase by 1, `p` will remain constant,
67+
and `y` will change in order to compensate the increase in `x`. But what if we
68+
wanted to keep `y` constant and change `p` instead? We could use the callback
69+
constructor as follows:
70+
71+
```julia
72+
event = SymbolicDiscreteCallback(
73+
[t == 1] => [x ~ Pre(x) + 1, y ~ Pre(y)], discrete_parameters = [p])
74+
```
75+
76+
This way, we enforce that `y` will remain the same, and `p` will change.
77+
78+
!!! warning
79+
80+
Symbolic affects come with the guarantee that the state after the callback
81+
will be consistent. However, when using [general functional affects](@ref func_affects)
82+
or [imperative affects](@ref imp_affects) one must be more careful. In
83+
particular, one can pass in `reinitializealg` as a keyword arg to the
84+
callback constructor to re-initialize the system. This will default to
85+
`SciMLBase.NoInit()` in the case of symbolic affects and `SciMLBase.CheckInit()`
86+
in the case of functional affects. This keyword should *not* be provided
87+
if the affect is purely symbolic.
88+
2889
## Continuous Events
2990

3091
The basic purely symbolic continuous event interface to encode *one* continuous
@@ -91,7 +152,7 @@ like this
91152
@variables x(t)=1 v(t)=0
92153
93154
root_eqs = [x ~ 0] # the event happens at the ground x(t) = 0
94-
affect = [v ~ -v] # the effect is that the velocity changes sign
155+
affect = [v ~ -Pre(v)] # the effect is that the velocity changes sign
95156
96157
@mtkbuild ball = ODESystem([D(x) ~ v
97158
D(v) ~ -9.8], t; continuous_events = root_eqs => affect) # equation => affect
@@ -110,8 +171,8 @@ Multiple events? No problem! This example models a bouncing ball in 2D that is e
110171
```@example events
111172
@variables x(t)=1 y(t)=0 vx(t)=0 vy(t)=2
112173
113-
continuous_events = [[x ~ 0] => [vx ~ -vx]
114-
[y ~ -1.5, y ~ 1.5] => [vy ~ -vy]]
174+
continuous_events = [[x ~ 0] => [vx ~ -Pre(vx)]
175+
[y ~ -1.5, y ~ 1.5] => [vy ~ -Pre(vy)]]
115176
116177
@mtkbuild ball = ODESystem(
117178
[
@@ -204,7 +265,7 @@ bb_sol = solve(bb_prob, Tsit5())
204265
plot(bb_sol)
205266
```
206267

207-
## Discrete events support
268+
## Discrete Events
208269

209270
In addition to continuous events, discrete events are also supported. The
210271
general interface to represent a collection of discrete events is
@@ -227,13 +288,13 @@ Suppose we have a population of `N(t)` cells that can grow and die, and at time
227288
`t1` we want to inject `M` more cells into the population. We can model this by
228289

229290
```@example events
230-
@parameters M tinject α
291+
@parameters M tinject α(t)
231292
@variables N(t)
232293
Dₜ = Differential(t)
233294
eqs = [Dₜ(N) ~ α - N]
234295
235296
# at time tinject we inject M cells
236-
injection = (t == tinject) => [N ~ N + M]
297+
injection = (t == tinject) => [N ~ Pre(N) + M]
237298
238299
u0 = [N => 0.0]
239300
tspan = (0.0, 20.0)
@@ -255,7 +316,7 @@ its steady-state value (which is 100). We can encode this by modifying the event
255316
to
256317

257318
```@example events
258-
injection = ((t == tinject) & (N < 50)) => [N ~ N + M]
319+
injection = ((t == tinject) & (N < 50)) => [N ~ Pre(N) + M]
259320
260321
@mtkbuild osys = ODESystem(eqs, t, [N], [M, tinject, α]; discrete_events = injection)
261322
oprob = ODEProblem(osys, u0, tspan, p)
@@ -269,16 +330,18 @@ event time, the event condition now returns false. Here we used logical and,
269330
cannot be used within symbolic expressions.
270331

271332
Let's now also add a drug at time `tkill` that turns off production of new
272-
cells, modeled by setting `α = 0.0`
333+
cells, modeled by setting `α = 0.0`. Since this is a parameter we must explicitly
334+
set it as `discrete_parameters`.
273335

274336
```@example events
275337
@parameters tkill
276338
277339
# we reset the first event to just occur at tinject
278-
injection = (t == tinject) => [N ~ N + M]
340+
injection = (t == tinject) => [N ~ Pre(N) + M]
279341
280342
# at time tkill we turn off production of cells
281-
killing = (t == tkill) => [α ~ 0.0]
343+
killing = ModelingToolkit.SymbolicDiscreteCallback(
344+
(t == tkill) => [α ~ 0.0]; discrete_parameters = α, iv = t)
282345
283346
tspan = (0.0, 30.0)
284347
p = [α => 100.0, tinject => 10.0, M => 50, tkill => 20.0]
@@ -298,16 +361,17 @@ A preset-time event is triggered at specific set times, which can be
298361
passed in a vector like
299362

300363
```julia
301-
discrete_events = [[1.0, 4.0] => [v ~ -v]]
364+
discrete_events = [[1.0, 4.0] => [v ~ -Pre(v)]]
302365
```
303366

304367
This will change the sign of `v` *only* at `t = 1.0` and `t = 4.0`.
305368

306369
As such, our last example with treatment and killing could instead be modeled by
307370

308371
```@example events
309-
injection = [10.0] => [N ~ N + M]
310-
killing = [20.0] => [α ~ 0.0]
372+
injection = [10.0] => [N ~ Pre(N) + M]
373+
killing = ModelingToolkit.SymbolicDiscreteCallback(
374+
[20.0] => [α ~ 0.0], discrete_parameters = α, iv = t)
311375
312376
p = [α => 100.0, M => 50]
313377
@mtkbuild osys = ODESystem(eqs, t, [N], [α, M];
@@ -325,7 +389,7 @@ specify a periodic interval, pass the interval as the condition for the event.
325389
For example,
326390

327391
```julia
328-
discrete_events = [1.0 => [v ~ -v]]
392+
discrete_events = [1.0 => [v ~ -Pre(v)]]
329393
```
330394

331395
will change the sign of `v` at `t = 1.0`, `2.0`, ...
@@ -334,10 +398,10 @@ Finally, we note that to specify an event at precisely one time, say 2.0 below,
334398
one must still use a vector
335399

336400
```julia
337-
discrete_events = [[2.0] => [v ~ -v]]
401+
discrete_events = [[2.0] => [v ~ -Pre(v)]]
338402
```
339403

340-
## Saving discrete values
404+
## [Saving discrete values](@id save_discretes)
341405

342406
Time-dependent parameters which are updated in callbacks are termed as discrete variables.
343407
ModelingToolkit enables automatically saving the timeseries of these discrete variables,
@@ -348,8 +412,10 @@ example:
348412
@variables x(t)
349413
@parameters c(t)
350414
415+
ev = ModelingToolkit.SymbolicDiscreteCallback(
416+
1.0 => [c ~ Pre(c) + 1], discrete_parameters = c, iv = t)
351417
@mtkbuild sys = ODESystem(
352-
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
418+
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [ev])
353419
354420
prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
355421
sol = solve(prob, Tsit5())
@@ -362,15 +428,15 @@ The solution object can also be interpolated with the discrete variables
362428
sol([1.0, 2.0], idxs = [c, c * cos(x)])
363429
```
364430

365-
Note that only time-dependent parameters will be saved. If we repeat the above example with
366-
this change:
431+
Note that only time-dependent parameters that are explicitly passed as `discrete_parameters`
432+
will be saved. If we repeat the above example with `c` not a `discrete_parameter`:
367433

368434
```@example events
369435
@variables x(t)
370-
@parameters c
436+
@parameters c(t)
371437
372438
@mtkbuild sys = ODESystem(
373-
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
439+
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]])
374440
375441
prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
376442
sol = solve(prob, Tsit5())

0 commit comments

Comments
 (0)