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
that accepts the function to call, a named tuple of both the names of and symbolic values representing
440
+
values in the system to be modified, a named tuple of the values that are merely observed (that is, used from
441
+
the system but not modified), and a context that's passed to the affect function.
442
+
443
+
In our example, each event merely changes whether the furnace is on or off. Accordingly, we pass a `modified` tuple
444
+
`(; furnace_on)` (creating a `NamedTuple` equivalent to `(furnace_on = furnace_on)`). `ImperativeAffect` will then
445
+
evaluate this before calling our function to fill out all of the numerical values, then apply them back to the system
446
+
once our affect function returns. Furthermore, it will check that it is possible to do this assignment.
447
+
448
+
The function given to `ImperativeAffect` needs to have one of four signatures, checked in this order:
449
+
*`f(modified::NamedTuple, observed::NamedTuple, ctx, integrator)::NamedTuple` if the function needs the low-level integrator,
450
+
*`f(modified::NamedTuple, observed::NamedTuple, ctx)::NamedTuple` if the function needs the user-defined context,
451
+
*`f(modified::NamedTuple, observed::NamedTuple)::NamedTuple` if the function also reads observed values from the system,
452
+
*`f(modified::NamedTuple)::NamedTuple` if the function only writes values (unknowns or parameters) to the system.
453
+
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`.
454
+
455
+
The function `f` will be called with `observed` and `modified``NamedTuple`s that are derived from their respective `NamedTuple` definitions.
456
+
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)`.
457
+
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)`.
458
+
We use Setfield to do this in the example, recreating the result tuple before returning it.
459
+
460
+
Accordingly, we can now interpret the `ImperativeAffect` definitions to mean that when `temp = furnace_off_threshold` we
461
+
will write `furnace_on = false` back to the system, and when `temp = furnace_on_threshold` we will write `furnace_on = true` back
hline!([sol.ps[furnace_off_threshold], sol.ps[furnace_on_threshold]], l = (:black, 1), primary = false)
472
+
```
473
+
474
+
Here we see exactly the desired hysteresis. The heater starts on until the temperature hits
475
+
`furnace_off_threshold`. The temperature then bleeds away until `furnace_on_threshold` at which
476
+
point the furnace turns on again until `furnace_off_threshold` and so on and so forth. The controller
477
+
is effectively regulating the temperature of the plant.
478
+
479
+
### [Quadrature Encoder](@id quadrature)
480
+
For a more complex application we'll look at modeling a quadrature encoder attached to a shaft spinning at a constant speed.
481
+
Traditionally, a quadrature encoder is built out of a code wheel that interrupts the sensors at constant intervals and two sensors slightly out of phase with one another.
482
+
A state machine can take the pattern of pulses produced by the two sensors and determine the number of steps that the shaft has spun. The state machine takes the new value
483
+
from each sensor and the old values and decodes them into the direction that the wheel has spun in this step.
0 commit comments