-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a restore flag to the Event constructor. #180
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## dev #180 +/- ##
==========================================
- Coverage 96.28% 96.27% -0.02%
==========================================
Files 36 36
Lines 1722 1824 +102
==========================================
+ Hits 1658 1756 +98
- Misses 64 68 +4 ☔ View full report in Codecov by Sentry. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like if we want to allow alternative semantics for restoring events it could get messy if we do it through the Event class heirarchy.
Please let me know if I've misunderstood what's going on.
R/event.R
Outdated
@@ -1,6 +1,7 @@ | |||
#' @title EventBase Class | |||
#' @description Common functionality shared between simple and targeted events. | |||
#' @importFrom R6 R6Class | |||
#' @keywords internal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also have @noRd
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really know what the best practice is for this. A user probably doesn't need to care about this type, so I didn't want to pollute the API index with it, but then the Event
/TargetedEvent
types link to it, so we still want documentation to be generated somewhere, which I'm not sure noRd
would do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, noRd vs internal seems to be a discussion. But for rOpenSci and tidyverse, noRd seems to win out for some reason.
I would be open to internal, but only after we document a method for compiling dev docs locally and ideally set up a workflow for generating web-based dev docs too.
For example, in our SIRS model, it may be tempting the model a time-varying parameter by running half of the simulation with one value and then resuming it with a different value. While this would probably work, it would be brittle and hard to compose. As more time-varying parameters are introduced to the model, the simulation would need to be saved and restored each time a value changes. | ||
For example, in our SIRS model, it may be tempting to model a time-varying parameter by running half of the simulation with one value and then resuming it with a different value. While this would probably work, it would be brittle and hard to compose. As more time-varying parameters are introduced to the model, the simulation would need to be saved and restored each time a value changes. | ||
|
||
### Saving and restoring events {#events} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, these docs suggest that StaticEvent's purpose is for alternative save/restore semantics for the Event
s?
Do we really want to tie restore semantics to the implementation? That means we'd have to make new classes for every resource and restore semantics combination.
Surely we would want to specify restore semantics as arguments for simulation_loop
. You could imagine something like this being more flexible (and not require changing the class of all the events):
e <- Event$new()
e$schedule(9)
state <- simulation_loop(timesteps = 5, events = list(e))
e <- Event$new()
e$schedule(14)
e$add_listener(function(t) cat("Triggered at timestep", t))
simulation_loop(timesteps = 30, events = list(e), state = state, restore_events='merge')
# restore_events could be 'overwrite' or NULL
Though I suspect for some cases, we won't be able to anticipate the best strategy for restoring events (see #178 (review))
Sorry, this wasn't clear to me before.
d2cfca1
to
a8aa9b9
Compare
So I've thought a bit more about this. I don't think I like a global restore strategy as an argument on I've implemented instead as a per- event <- Event$new(restore = FALSE)
event$schedule(14) The I guess at some point we can add mixed restore strategies, such as The one caveat is that the way this is typically used in The code looks something like: e <- Event$new()
e$schedule(times[[1]] - 1)
e$add_listener(function(t) {
index <- which(times == t)
e$schedule(times[[index + 1]])
}) If you resume the simulation at a point later than the first timepoint then the scheduled execution is ignored (it is in the past), and the subsequent executions we expect are never scheduled. For this to work, the initialization would have to know at which timestep the simulation is resumed and use the first value that is greater (or equal) than that. A simpler and probably better way of expressing this by scheduling all the time points upfront, as follows: e <- Event$new(restore = FALSE)
e$schedule(times - 1)
e$add_listener(function(t) {
index <- which(times == t)
# Don't call e$schedule here
}) Anything in |
By default, when restoring the simulation state all previous schedule on events are cleared and restored from the saved state. This goes against use cases that wish to resume a simulation with different intervention schedules to compare effects. In those use cases, a different initialization sequence is used when creating the simulation, and we do not want that to be cleared and overwritten. The new `restore` flag, when set to false, overrides this default behaviour and the state of an Event is (mostly) unaffected by a restore. Thanks to this, a new event schedule, that is unrelated to the schedule of the original run, can be configured.
See mrc-ide/malariasimulation@1d2c662 for how this can be used in malariasimulation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some discussion. We've decided it's fine to have a small amount of restoring logic in the Event constructor.
But once we have a clearer idea on alternative restoration strategies, it's worth moving this logic into primitive building functions. Ideally primitive building functions would be in R space to make external implementations easier.
I changed a function signature in mrc-ide#180, but had not regenerated the `.Rd` files.
I changed a function signature in #180, but had not regenerated the `.Rd` files.
By default, when restoring the simulation state all previous schedules on events are cleared and restored from the saved state. This goes against use cases that wish to resume a simulation with different intervention schedules to compare effects. In those use cases, a different initialization sequence is used when creating the simulation, and we do not want that to be cleared and overwritten.
The new
restore
flag, when set to false, overrides this default behaviour and the state of an Event is (mostly) unaffected by a restore. Thanks to this, a new event schedule, that is unrelated to the schedule of the original run, can be configured.Unlike other event types, which have aschedule
method, theStaticEvent
follows a fixed list of times at which it triggers, defined at initialization. This follows a common pattern found in malariasimulation of using a vector of times at which an intervention happens, where the vector is part of the simulation parameters.When triggering, listeners on a static event receive as an additional argument the index into the timestep list matching the current timestep. This can be used to look up parameters associated with the current invocation.Below is an example of aStaticEvent
being used to model a mass drug administration campaign:The main benefit of StaticEvent is to enable a schedule to be reliably modified when resuming a simulation. TheEvent
andTargetedEvent
classes save their schedules in the checkpoint and restore them when loading from a previous simulation state. As a consequence of this, resuming a simulation with a different schedule for an event may not work as intended, even if the scheduled time is in the future.For example in the code below, the simulation is first initialized with an event scheduled att=10
and is run for only 5 steps, hence the event does not yet trigger. On the second run, the event is seemingly scheduled fort=15
. However, by resuming the simulation, the event's schedule is overwritten with the saved state, clearing the newly scheduled time. The event is triggered att=10
only.StaticEvent
provides alternative semantics, allowing their schedule to be modified reliably when resuming the simulation. Static events don't save or restore their schedule, instead it is only dependent on their initialization, which can be modified when resuming. In the modified example below, the event triggers att=15
as intended.~~