You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/src/features/callback_functions.md
+39-41Lines changed: 39 additions & 41 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,12 +10,12 @@ types can be used to build libraries of extension behavior.
10
10
The callback types are defined as follows. There are three primitive callback types: the `ContinuousCallback`, `DiscreteCallback` and the `VectorContinuousCallback`:
11
11
12
12
- The [`ContinuousCallback`](@ref) is applied when a given continuous *condition function* hits zero. This hitting can happen even within
13
-
an integration step and the solver must be able to detect it and adjust the integration step accordingly. This type of callback implements
14
-
what is known in other problemsolving environments as an *Event*.
13
+
an integration step, and the solver must be able to detect it and adjust the integration step accordingly. This type of callback implements
14
+
what is known in other problem-solving environments as an *Event*.
15
15
- The [`DiscreteCallback`](@ref) is applied when its *condition function* is `true`, but the condition is only evaluated at the end of every
16
16
integration step.
17
-
- The [`VectorContinuousCallback`](@ref) works like a vector of `ContinuousCallbacks` and lets the user specify a vector of continuous callbacks
18
-
each with simultanious rootfinding equations. The effect that is applied is the effect corresponding to the first/earliest condition that is
17
+
- The [`VectorContinuousCallback`](@ref) works like a vector of `ContinuousCallbacks` and lets the user specify a vector of continuous callbacks,
18
+
each with simultaneous rootfinding equations. The effect that is applied is the effect corresponding to the first (earliest) condition that is
19
19
satisfied. A `VectorContinuousCallback` is more efficient than a `CallbackSet` of `ContinuousCallback`s as the number of callbacks grows. As
20
20
such, it's a slightly more involved definition which gives better scaling.
21
21
@@ -58,7 +58,7 @@ or a `CallbackSet`.
58
58
### Note About Saving
59
59
60
60
When a callback is supplied, the default saving behavior is turned off. This is
61
-
because otherwise events would "double save" one of the values. To re-enable
61
+
because otherwise, events would “double save” one of the values. To re-enable
62
62
the standard saving behavior, one must have the first `save_positions` value
63
63
be true for at least one callback.
64
64
@@ -343,10 +343,8 @@ end
343
343
```
344
344
345
345
All we have to do in order to specify the event is to have a function which
346
-
should always be positive with an event occurring at 0. For now at least
347
-
that's how it's specified. If a generalization is needed we can talk about
348
-
this (but it needs to be "root-findable"). For here it's clear that we just
349
-
want to check if the ball's height ever hits zero:
346
+
should always be positive, with an event occurring at 0.
347
+
We thus want to check if the ball's height ever hits zero:
350
348
351
349
```@example callback4
352
350
function condition(u,t,integrator) # Event when event_f(u,t) == 0
@@ -358,7 +356,7 @@ Notice that here we used the values `u` instead of the value from the `integrato
358
356
This is because the values `u,t` will be appropriately modified at the interpolation
359
357
points, allowing for the rootfinding behavior to occur.
360
358
361
-
Now we have to say what to do when the event occurs. In this case we just
359
+
Now we have to say what to do when the event occurs. In this case, we just
362
360
flip the velocity (the second variable)
363
361
364
362
```@example callback4
@@ -387,8 +385,8 @@ using Plots; plot(sol)
387
385
388
386
As you can see from the resulting image, DifferentialEquations.jl is smart enough
389
387
to use the interpolation to hone in on the time of the event and apply the event
390
-
back at the correct time. Thus one does not have to worry about the adaptive timestepping
391
-
"overshooting" the event as this is handled for you. Notice that the event macro
388
+
back at the correct time. Thus, one does not have to worry about the adaptive timestepping
389
+
“overshooting” the event, as this is handled for you. Notice that the event macro
392
390
will save the value(s) at the discontinuity.
393
391
394
392
The callback is robust to having multiple discontinuities occur. For example,
@@ -428,9 +426,9 @@ plot(sol)
428
426
429
427
Notice that at the end, the ball is not at `0.0` like the condition would let
430
428
you believe, but instead it's at `4.329177480185359e-16`. From the printing
431
-
inside of the affect function, we can see that this is the value it had at the
429
+
inside the affect function, we can see that this is the value it had at the
432
430
event time `t=0.4517539514526232`. Why did the event handling not make it exactly
433
-
zero? If you instead would have run the simulation to
431
+
zero? If you instead had run the simulation to
434
432
`nextfloat(0.4517539514526232) = 0.45175395145262326`, we would see that the
435
433
value of `u[1] = -1.2647055847076505e-15`. You can see this by changing the
436
434
`rootfind` argument of the callback:
@@ -444,10 +442,10 @@ sol = solve(prob, Tsit5(), callback=floor_event)
444
442
sol[end] # [-1.2647055847076505e-15, 0.0]
445
443
```
446
444
447
-
What this means is that there is not 64-bit floatingpoint number `t` such that
445
+
What this means is that there is no 64-bit floating-point number `t` such that
448
446
the condition is zero! By default, if there is no `t` such that `condition=0`,
449
-
then rootfinder defaults to choosing the floatingpoint number exactly before
450
-
the exactly before the event (`LeftRootFind`). This way manifold constraints are
447
+
then the rootfinder defaults to choosing the floating-point number exactly before
448
+
the event (`LeftRootFind`). This way manifold constraints are
451
449
preserved by default (i.e. the ball never goes below the floor). However, if you
452
450
require that the condition is exactly satisfied after the event, you will want
453
451
to add such a change to the `affect!` function. For example, the error correction
@@ -468,7 +466,7 @@ sol = solve(prob, Tsit5(), callback=floor_event)
468
466
sol[end] # [0.0,0.0]
469
467
```
470
468
471
-
and now the sticky behavior is perfect to the floatingpoint.
469
+
and now the sticky behavior is perfect to the floating-point.
472
470
473
471
#### Handling Accumulation Points
474
472
@@ -496,12 +494,12 @@ sol = solve(prob, Tsit5(), callback=floor_event)
496
494
plot(sol)
497
495
```
498
496
499
-
From the readout we can see the ball only bounced 8 times before it went below
500
-
the floor, what happened? What happened is floatingpoint error. Because one
501
-
cannot guarantee that floatingpoint numbers exist to make the `condition=0`,
497
+
From the readout, we can see the ball only bounced 8 times before it went below
498
+
the floor, what happened? What happened is floating-point error. Because one
499
+
cannot guarantee that floating-point numbers exist to make the `condition=0`,
502
500
a heuristic is used to ensure that a zero is not accidentally detected at
503
-
`nextfloat(t)` after the simulation restarts (otherwise it would repeatly find
504
-
the same event!). However, sooner or later the ability to detect minute floating
501
+
`nextfloat(t)` after the simulation restarts (otherwise it would repeatedly find
502
+
the same event!). However, sooner or later, the ability to detect minute floating
505
503
point differences will crash, and what should be infinitely many bounces finally
506
504
misses a bounce.
507
505
@@ -510,26 +508,26 @@ This leads to two questions:
510
508
1. How can you improve the accuracy of an accumulation calculation?
511
509
2. How can you make it gracefully continue?
512
510
513
-
For (1), note that floatingpoint accuracy is dependent on the current `dt`. If
511
+
For (1), note that floating-point accuracy is dependent on the current `dt`. If
514
512
you know that an accumulation point is coming, one can use `set_proposed_dt!`
515
513
to shrink the `dt` value and help find the next bounce point. You can use
516
514
`t - tprev` to know the length of the previous interval for this calculation.
517
515
For this example, we can set the proposed `dt` to `(t - tprev)/10` to ensure
518
-
an everincreasing accuracy of the check.
516
+
an ever-increasing accuracy of the check.
519
517
520
518
However, at some point we will hit machine epsilon, the value where
521
519
`t + eps(t) == t`, so we cannot measure infinitely many bounces and instead will
522
-
be limited by the floatingpoint accuracy of our number representation. Using
520
+
be limited by the floating-point accuracy of our number representation. Using
523
521
alternative number types like
524
522
[ArbFloats.jl](https://github.com/JuliaArbTypes/ArbFloats.jl) can allow for this
525
-
to be done at very high accuracy, but still not infinite. Thus what we need to
523
+
to be done at very high accuracy, but still not infinite. Thus, what we need to
526
524
do is determine a tolerance after which we assume the accumulation has been
527
-
reached and define the exit behavior. In this case we will say when the
525
+
reached and define the exit behavior. In this case, we will say when the
528
526
`dt<1e-12`, we are almost at the edge of Float64 accuracy
529
527
(`eps(1.0) = 2.220446049250313e-16`), so we will change the position and
530
528
velocity to exactly zero.
531
529
532
-
With these floatingpoint corrections in mind, the accumulation calculations
530
+
With these floating-point corrections in mind, the accumulation calculation
533
531
looks as follows:
534
532
535
533
```@example callback4
@@ -557,7 +555,7 @@ sol = solve(prob, Tsit5(), callback=floor_event)
557
555
plot(sol)
558
556
```
559
557
560
-
With this corrected version, we see that after 41 bounces the accumulation
558
+
With this corrected version, we see that after 41 bounces, the accumulation
561
559
point is reached at `t = 1.355261854357056`. To really see the accumulation,
562
560
let's zoom in:
563
561
@@ -573,11 +571,11 @@ I think Zeno would be proud of our solution.
573
571
574
572
### Example 2: Terminating an Integration
575
573
576
-
In many cases you might want to terminate an integration when some condition is
574
+
Often, you might want to terminate an integration when some condition is
577
575
satisfied. To terminate an integration, use `terminate!(integrator)` as the `affect!`
578
576
in a callback.
579
577
580
-
In this example we will solve the differential equation:
578
+
In this example, we will solve the differential equation:
It is evident that `out[2]` will be zero when `u[3]` (x-coordinate) is either `0.0` or `10.0`. And when that happens, we flip the velocity with some coefficient of restitution (`0.9`).
0 commit comments