Skip to content

Commit 86f05e6

Browse files
committed
docs: add documentation for the symbolic affect changes
1 parent ff96602 commit 86f05e6

File tree

2 files changed

+77
-15
lines changed

2 files changed

+77
-15
lines changed

docs/src/basics/Events.md

Lines changed: 75 additions & 14 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 (changed in V10)
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
@@ -233,7 +294,7 @@ 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)
@@ -275,7 +336,7 @@ cells, modeled by setting `α = 0.0`
275336
@parameters tkill
276337
277338
# we reset the first event to just occur at tinject
278-
injection = (t == tinject) => [N ~ N + M]
339+
injection = (t == tinject) => [N ~ Pre(N) + M]
279340
280341
# at time tkill we turn off production of cells
281342
killing = (t == tkill) => [α ~ 0.0]
@@ -298,15 +359,15 @@ A preset-time event is triggered at specific set times, which can be
298359
passed in a vector like
299360

300361
```julia
301-
discrete_events = [[1.0, 4.0] => [v ~ -v]]
362+
discrete_events = [[1.0, 4.0] => [v ~ -Pre(v)]]
302363
```
303364

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

306367
As such, our last example with treatment and killing could instead be modeled by
307368

308369
```@example events
309-
injection = [10.0] => [N ~ N + M]
370+
injection = [10.0] => [N ~ Pre(N) + M]
310371
killing = [20.0] => [α ~ 0.0]
311372
312373
p = [α => 100.0, M => 50]
@@ -325,7 +386,7 @@ specify a periodic interval, pass the interval as the condition for the event.
325386
For example,
326387

327388
```julia
328-
discrete_events = [1.0 => [v ~ -v]]
389+
discrete_events = [1.0 => [v ~ -Pre(v)]]
329390
```
330391

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

336397
```julia
337-
discrete_events = [[2.0] => [v ~ -v]]
398+
discrete_events = [[2.0] => [v ~ -Pre(v)]]
338399
```
339400

340-
## Saving discrete values
401+
## [Saving discrete values](@id save_discretes)
341402

342403
Time-dependent parameters which are updated in callbacks are termed as discrete variables.
343404
ModelingToolkit enables automatically saving the timeseries of these discrete variables,
@@ -349,7 +410,7 @@ example:
349410
@parameters c(t)
350411
351412
@mtkbuild sys = ODESystem(
352-
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
413+
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]])
353414
354415
prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
355416
sol = solve(prob, Tsit5())
@@ -370,7 +431,7 @@ this change:
370431
@parameters c
371432
372433
@mtkbuild sys = ODESystem(
373-
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ c + 1]])
434+
D(x) ~ c * cos(x), t, [x], [c]; discrete_events = [1.0 => [c ~ Pre(c) + 1]])
374435
375436
prob = ODEProblem(sys, [x => 0.0], (0.0, 2pi), [c => 1.0])
376437
sol = solve(prob, Tsit5())

test/odesystem.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ModelingToolkit, StaticArrays, LinearAlgebra
2-
using ModelingToolkit: get_metadata, MTKParameters
2+
using ModelingToolkit: get_metadata, MTKParameters, SymbolicDiscreteCallback,
3+
SymbolicContinuousCallback
34
using SymbolicIndexingInterface
45
using OrdinaryDiffEq, Sundials
56
using DiffEqBase, SparseArrays

0 commit comments

Comments
 (0)