Skip to content

Commit 9bf734d

Browse files
committed
Simplify callback interface, fix references
1 parent 2ac2d23 commit 9bf734d

File tree

3 files changed

+23
-89
lines changed

3 files changed

+23
-89
lines changed

docs/src/basics/Events.md

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -416,12 +416,12 @@ in between. To do this, we create two continuous callbacks:
416416
using Setfield
417417
furnace_disable = ModelingToolkit.SymbolicContinuousCallback(
418418
[temp ~ furnace_off_threshold],
419-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c
419+
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i
420420
@set! x.furnace_on = false
421421
end)
422422
furnace_enable = ModelingToolkit.SymbolicContinuousCallback(
423423
[temp ~ furnace_on_threshold],
424-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i, c
424+
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i
425425
@set! x.furnace_on = true
426426
end)
427427
```
@@ -454,18 +454,16 @@ In our example, each event merely changes whether the furnace is on or off. Acco
454454
evaluate this before calling our function to fill out all of the numerical values, then apply them back to the system
455455
once our affect function returns. Furthermore, it will check that it is possible to do this assignment.
456456

457-
The function given to `ImperativeAffect` needs to have one of four signatures, checked in this order:
458-
459-
- `f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTuple` if the function needs the low-level integrator,
460-
- `f(modified::NamedTuple, observed::NamedTuple, ctx)::NamedTuple` if the function needs the user-defined context,
461-
- `f(modified::NamedTuple, observed::NamedTuple)::NamedTuple` if the function also reads observed values from the system,
462-
- `f(modified::NamedTuple)::NamedTuple` if the function only writes values (unknowns or parameters) to the system.
463-
The `do` block in the example implicitly constructs said function inline. For exposition, we use the full version (e.g. `x, o, i, c`) but this could be simplified to merely `x`.
457+
The function given to `ImperativeAffect` needs to have the signature:
464458

459+
```julia
460+
f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTuple
461+
```
465462
The function `f` will be called with `observed` and `modified` `NamedTuple`s that are derived from their respective `NamedTuple` definitions.
466463
In our example, if `furnace_on` is `false`, then the value of the `x` that's passed in as `modified` will be `(furnace_on = false)`.
467464
The modified values should be passed out in the same format: to set `furnace_on` to `true` we need to return a tuple `(furnace_on = true)`.
468-
We use Setfield to do this in the example, recreating the result tuple before returning it.
465+
The examples does this with Setfield, recreating the result tuple before returning it; the returned tuple may optionally be missing values as
466+
well, in which case those values will not be written back to the problem.
469467

470468
Accordingly, we can now interpret the `ImperativeAffect` definitions to mean that when `temp = furnace_off_threshold` we
471469
will write `furnace_on = false` back to the system, and when `temp = furnace_on_threshold` we will write `furnace_on = true` back
@@ -543,7 +541,7 @@ In our encoder, we interpret this as occlusion or nonocclusion of the sensor, up
543541

544542
```@example events
545543
qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0],
546-
ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, i, c
544+
ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i
547545
@set! x.hA = x.qA
548546
@set! x.hB = o.qB
549547
@set! x.qA = 1
@@ -567,7 +565,7 @@ Instead, we can use right root finding:
567565

568566
```@example events
569567
qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0],
570-
ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA, theta)) do x, o, i, c
568+
ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA, theta)) do x, o, c, i
571569
@set! x.hA = o.qA
572570
@set! x.hB = x.qB
573571
@set! x.qB = clamp(sign(cos(100 * o.theta - π / 2)), 0.0, 1.0)

src/systems/callbacks.jl

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,11 @@ end
7676
7777
`ImperativeAffect` is a helper for writing affect functions that will compute observed values and
7878
ensure that modified values are correctly written back into the system. The affect function `f` needs to have
79-
one of four signatures:
80-
* `f(modified::NamedTuple)::NamedTuple` if the function only writes values (unknowns or parameters) to the system,
81-
* `f(modified::NamedTuple, observed::NamedTuple)::NamedTuple` if the function also reads observed values from the system,
82-
* `f(modified::NamedTuple, observed::NamedTuple, ctx)::NamedTuple` if the function needs the user-defined context,
83-
* `f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTuple` if the function needs the low-level integrator.
84-
These will be checked in reverse order (that is, the four-argument version first, than the 3, etc).
79+
the signature
80+
81+
```
82+
f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTuple
83+
```
8584
8685
The function `f` will be called with `observed` and `modified` `NamedTuple`s that are derived from their respective `NamedTuple` definitions.
8786
Each declaration`NamedTuple` should map an expression to a symbol; for example if we pass `observed=(; x = a + b)` this will alias the result of executing `a+b` in the system as `x`
@@ -1046,7 +1045,7 @@ function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs.
10461045
Implementation sketch:
10471046
generate observed function (oop), should save to a component array under obs_syms
10481047
do the same stuff as the normal FA for pars_syms
1049-
call the affect method - test if it's OOP or IP using applicable
1048+
call the affect method
10501049
unpack and apply the resulting values
10511050
=#
10521051
function check_dups(syms, exprs) # = (syms_dedup, exprs_dedup)
@@ -1135,22 +1134,10 @@ function compile_user_affect(affect::ImperativeAffect, cb, sys, dvs, ps; kwargs.
11351134
integ.u, integ.p, integ.t))
11361135

11371136
# let the user do their thing
1138-
modvals = if applicable(
1139-
user_affect, upd_component_array, obs_component_array, ctx, integ)
1140-
user_affect(upd_component_array, obs_component_array, ctx, integ)
1141-
elseif applicable(user_affect, upd_component_array, obs_component_array, ctx)
1142-
user_affect(upd_component_array, obs_component_array, ctx)
1143-
elseif applicable(user_affect, upd_component_array, obs_component_array)
1144-
user_affect(upd_component_array, obs_component_array)
1145-
elseif applicable(user_affect, upd_component_array)
1146-
user_affect(upd_component_array)
1147-
else
1148-
@error "User affect function $user_affect needs to implement one of the supported ImperativeAffect callback forms; see the ImperativeAffect docstring for more details"
1149-
user_affect(upd_component_array, obs_component_array, integ, ctx) # this WILL error but it'll give a more sensible message
1150-
end
1151-
1137+
upd_vals = user_affect(upd_component_array, obs_component_array, ctx, integ)
1138+
11521139
# write the new values back to the integrator
1153-
_generated_writeback(integ, upd_funs, modvals)
1140+
_generated_writeback(integ, upd_funs, upd_vals)
11541141

11551142
for idx in save_idxs
11561143
SciMLBase.save_discretes!(integ, idx)

test/symbolic_events.jl

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,60 +1027,9 @@ end
10271027

10281028
furnace_off = ModelingToolkit.SymbolicContinuousCallback(
10291029
[temp ~ furnace_off_threshold],
1030-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i
1031-
@set! x.furnace_on = false
1032-
end)
1033-
furnace_enable = ModelingToolkit.SymbolicContinuousCallback(
1034-
[temp ~ furnace_on_threshold],
1035-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, i
1036-
@set! x.furnace_on = true
1037-
end)
1038-
@named sys = ODESystem(
1039-
eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable])
1040-
ss = structural_simplify(sys)
1041-
prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0))
1042-
sol = solve(prob, Tsit5(); dtmax = 0.01)
1043-
@test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49)
1044-
1045-
furnace_off = ModelingToolkit.SymbolicContinuousCallback(
1046-
[temp ~ furnace_off_threshold],
1047-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o
1048-
@set! x.furnace_on = false
1049-
end)
1050-
furnace_enable = ModelingToolkit.SymbolicContinuousCallback(
1051-
[temp ~ furnace_on_threshold],
1052-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o
1053-
@set! x.furnace_on = true
1054-
end)
1055-
@named sys = ODESystem(
1056-
eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable])
1057-
ss = structural_simplify(sys)
1058-
prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0))
1059-
sol = solve(prob, Tsit5(); dtmax = 0.01)
1060-
@test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49)
1061-
1062-
furnace_off = ModelingToolkit.SymbolicContinuousCallback(
1063-
[temp ~ furnace_off_threshold],
1064-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x
1065-
@set! x.furnace_on = false
1066-
end)
1067-
furnace_enable = ModelingToolkit.SymbolicContinuousCallback(
1068-
[temp ~ furnace_on_threshold],
1069-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x
1070-
@set! x.furnace_on = true
1071-
end)
1072-
@named sys = ODESystem(
1073-
eqs, t, [temp], params; continuous_events = [furnace_off, furnace_enable])
1074-
ss = structural_simplify(sys)
1075-
prob = ODEProblem(ss, [temp => 0.0, furnace_on => true], (0.0, 100.0))
1076-
sol = solve(prob, Tsit5(); dtmax = 0.01)
1077-
@test all(sol[temp][sol.t .> 1.0] .<= 0.79) && all(sol[temp][sol.t .> 1.0] .>= 0.49)
1078-
1079-
furnace_off = ModelingToolkit.SymbolicContinuousCallback(
1080-
[temp ~ furnace_off_threshold],
1081-
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x
1030+
ModelingToolkit.ImperativeAffect(modified = (; furnace_on)) do x, o, c, i
10821031
@set! x.furnace_on = false
1083-
end; initialize = ModelingToolkit.ImperativeAffect(modified = (; temp)) do x
1032+
end; initialize = ModelingToolkit.ImperativeAffect(modified = (; temp)) do x, o, c, i
10841033
@set! x.temp = 0.2
10851034
end)
10861035
furnace_enable = ModelingToolkit.SymbolicContinuousCallback(
@@ -1180,7 +1129,7 @@ end
11801129
end
11811130
end
11821131
qAevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta) ~ 0],
1183-
ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, i, c
1132+
ModelingToolkit.ImperativeAffect((; qA, hA, hB, cnt), (; qB)) do x, o, c, i
11841133
@set! x.hA = x.qA
11851134
@set! x.hB = o.qB
11861135
@set! x.qA = 1
@@ -1196,7 +1145,7 @@ end
11961145
x
11971146
end; rootfind = SciMLBase.RightRootFind)
11981147
qBevt = ModelingToolkit.SymbolicContinuousCallback([cos(100 * theta - π / 2) ~ 0],
1199-
ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, i, c
1148+
ModelingToolkit.ImperativeAffect((; qB, hA, hB, cnt), (; qA)) do x, o, c, i
12001149
@set! x.hA = o.qA
12011150
@set! x.hB = x.qB
12021151
@set! x.qB = 1

0 commit comments

Comments
 (0)