Skip to content
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

Release 0.1.12 #182

Merged
merged 4 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: individual
Title: Framework for Specifying and Simulating Individual Based Models
Version: 0.1.11
Version: 0.1.12
Authors@R: c(
person(
given = "Giovanni",
Expand All @@ -27,6 +27,7 @@ Authors@R: c(
given = "Paul",
family = "Liétar",
role = c('aut'),
comment = c(ORCID = "0009-0000-3813-6227"),
email = 'paul.lietar13@imperial.ac.uk'
),
person(
Expand Down Expand Up @@ -64,7 +65,7 @@ Suggests:
testthat (>= 2.1.0),
xml2,
bench
RoxygenNote: 7.2.1.9000
RoxygenNote: 7.2.3
VignetteBuilder: knitr
LinkingTo:
Rcpp,
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# individual 0.1.12

* Simulation state can be saved and restored, allowing the simulation to be resumed.

# individual 0.1.11

* Optimised rendering memory usage and speed
Expand Down
36 changes: 26 additions & 10 deletions R/RcppExports.R
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,16 @@ create_targeted_event <- function(size) {
.Call(`_individual_create_targeted_event`, size)
}

event_tick <- function(event) {
invisible(.Call(`_individual_event_tick`, event))
event_base_tick <- function(event) {
invisible(.Call(`_individual_event_base_tick`, event))
}

event_base_get_timestep <- function(event) {
.Call(`_individual_event_base_get_timestep`, event)
}

event_base_should_trigger <- function(event) {
.Call(`_individual_event_base_should_trigger`, event)
}

event_schedule <- function(event, delays) {
Expand All @@ -185,6 +193,14 @@ event_clear_schedule <- function(event) {
invisible(.Call(`_individual_event_clear_schedule`, event))
}

event_checkpoint <- function(event) {
.Call(`_individual_event_checkpoint`, event)
}

event_restore <- function(event, time, schedule) {
invisible(.Call(`_individual_event_restore`, event, time, schedule))
}

targeted_event_clear_schedule_vector <- function(event, target) {
invisible(.Call(`_individual_targeted_event_clear_schedule_vector`, event, target))
}
Expand Down Expand Up @@ -229,14 +245,6 @@ targeted_event_schedule_multi_delay_vector <- function(event, target, delay) {
invisible(.Call(`_individual_targeted_event_schedule_multi_delay_vector`, event, target, delay))
}

event_get_timestep <- function(event) {
.Call(`_individual_event_get_timestep`, event)
}

event_should_trigger <- function(event) {
.Call(`_individual_event_should_trigger`, event)
}

targeted_event_get_target <- function(event) {
.Call(`_individual_targeted_event_get_target`, event)
}
Expand All @@ -245,6 +253,14 @@ targeted_event_resize <- function(event) {
invisible(.Call(`_individual_targeted_event_resize`, event))
}

targeted_event_checkpoint <- function(event) {
.Call(`_individual_targeted_event_checkpoint`, event)
}

targeted_event_restore <- function(event, time, state) {
invisible(.Call(`_individual_targeted_event_restore`, event, time, state))
}

process_listener <- function(event, listener) {
invisible(.Call(`_individual_process_listener`, event, listener))
}
Expand Down
21 changes: 19 additions & 2 deletions R/categorical_variable.R
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ CategoricalVariable <- R6Class(
#' @description return a character vector of possible values.
#' Note that the order of the returned vector may not be the same order
#' that was given when the variable was intitialized, due to the underlying
#' unordered storage type.
#' unordered storage type.
get_categories = function() {
categorical_variable_get_categories(self$.variable)
},
Expand Down Expand Up @@ -94,6 +94,23 @@ CategoricalVariable <- R6Class(
size = function() variable_get_size(self$.variable),

.update = function() variable_update(self$.variable),
.resize = function() variable_resize(self$.variable)
.resize = function() variable_resize(self$.variable),

.checkpoint = function() {
categories <- self$get_categories()
values <- lapply(categories, function(c) self$get_index_of(c)$to_vector())
names(values) <- categories
values
},

.restore = function(values) {
stopifnot(names(values) == self$get_categories())
stopifnot(sum(sapply(values, length)) == categorical_variable_get_size(self$.variable))

for (c in names(values)) {
self$queue_update(c, values[[c]])
}
self$.update()
}
)
)
9 changes: 8 additions & 1 deletion R/double_variable.R
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,13 @@ DoubleVariable <- R6Class(
size = function() variable_get_size(self$.variable),

.update = function() variable_update(self$.variable),
.resize = function() variable_resize(self$.variable)
.resize = function() variable_resize(self$.variable),

.checkpoint = function() self$get_values(),
.restore = function(values) {
stopifnot(length(values) == variable_get_size(self$.variable))
self$queue_update(values)
self$.update()
}
)
)
65 changes: 41 additions & 24 deletions R/event.R
Original file line number Diff line number Diff line change
@@ -1,61 +1,78 @@
#' @title Event Class
#' @description Describes a general event in the simulation.
#' @title EventBase Class
#' @description Common functionality shared between simple and targeted events.
#' @importFrom R6 R6Class
#' @export
Event <- R6Class(
'Event',
EventBase <- R6Class(
'EventBase',
public = list(

.event = NULL,
.listeners = list(),

#' @description Initialise an Event.
initialize = function() {
self$.event <- create_event()
},

#' @description Add an event listener.
#' @param listener the function to be executed on the event, which takes a single
#' argument giving the time step when this event is triggered.
add_listener = function(listener) {
self$.listeners <- c(self$.listeners, listener)
},

#' @description Schedule this event to occur in the future.
#' @param delay the number of time steps to wait before triggering the event,
#' can be a scalar or a vector of values for events that should be triggered
#' multiple times.
schedule = function(delay) event_schedule(self$.event, delay),
.timestep = function() event_base_get_timestep(self$.event),

#' @description Stop a future event from triggering.
clear_schedule = function() event_clear_schedule(self$.event),

.tick = function() event_tick(self$.event),
.tick = function() event_base_tick(self$.event),

.process = function() {
for (listener in self$.listeners) {
if (event_should_trigger(self$.event)) {
if (event_base_should_trigger(self$.event)) {
if (inherits(listener, "externalptr")) {
self$.process_listener_cpp(listener)
} else {
self$.process_listener(listener)
}
}
}
}
)
)

#' @title Event Class
#' @description Describes a general event in the simulation.
#' @importFrom R6 R6Class
#' @export
Event <- R6Class(
'Event',
inherit=EventBase,
public = list(
#' @description Initialise an Event.
initialize = function() {
self$.event <- create_event()
},

#' @description Schedule this event to occur in the future.
#' @param delay the number of time steps to wait before triggering the event,
#' can be a scalar or a vector of values for events that should be triggered
#' multiple times.
schedule = function(delay) event_schedule(self$.event, delay),

#' @description Stop a future event from triggering.
clear_schedule = function() event_clear_schedule(self$.event),

.process_listener = function(listener) {
listener(event_get_timestep(self$.event))
listener(self$.timestep())
},

.process_listener_cpp = function(listener){
.process_listener_cpp = function(listener) {
process_listener(
event = self$.event,
listener = listener
)
},

# NOTE: intentionally empty
.resize = function() {}
.resize = function() {},

.checkpoint = function() {
event_checkpoint(self$.event)
},
.restore = function(time, schedule) {
event_restore(self$.event, time, schedule)
}
)
)
9 changes: 8 additions & 1 deletion R/integer_variable.R
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ IntegerVariable <- R6Class(
size = function() variable_get_size(self$.variable),

.update = function() variable_update(self$.variable),
.resize = function() variable_resize(self$.variable)
.resize = function() variable_resize(self$.variable),

.checkpoint = function() self$get_values(),
.restore = function(values) {
stopifnot(length(values) == variable_get_size(self$.variable))
self$queue_update(values)
self$.update()
}
)
)
9 changes: 8 additions & 1 deletion R/ragged_double.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ RaggedDouble <- R6Class(
size = function() variable_get_size(self$.variable),

.update = function() variable_update(self$.variable),
.resize = function() variable_resize(self$.variable)
.resize = function() variable_resize(self$.variable),

.checkpoint = function() self$get_values(),
.restore = function(values) {
stopifnot(length(values) == variable_get_size(self$.variable))
self$queue_update(values)
self$.update()
}
)
)
9 changes: 8 additions & 1 deletion R/ragged_integer.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ RaggedInteger <- R6Class(
size = function() variable_get_size(self$.variable),

.update = function() variable_update(self$.variable),
.resize = function() variable_resize(self$.variable)
.resize = function() variable_resize(self$.variable),

.checkpoint = function() self$get_values(),
.restore = function(values) {
stopifnot(length(values) == variable_get_size(self$.variable))
self$queue_update(values)
self$.update()
}
)
)
70 changes: 67 additions & 3 deletions R/simulation.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#' @param variables a list of Variables
#' @param events a list of Events
#' @param processes a list of processes to execute on each timestep
#' @param timesteps the number of timesteps to simulate
#' @param timesteps the end timestep of the simulation. If `state` is not NULL, timesteps must be greater than `state$timestep`
#' @param state a checkpoint from which to resume the simulation
#' @param restore_random_state if TRUE, restore R's global random number generator's state from the checkpoint.
#' @examples
#' population <- 4
#' timesteps <- 5
Expand Down Expand Up @@ -35,12 +37,23 @@
variables = list(),
events = list(),
processes = list(),
timesteps
timesteps,
state = NULL,
restore_random_state = FALSE
) {
if (timesteps <= 0) {
stop('End timestep must be > 0')
}
for (t in seq_len(timesteps)) {

start <- 1
if (!is.null(state)) {
start <- restore_state(state, variables, events, restore_random_state)
if (start > timesteps) {
stop("Restored state is already longer than timesteps")
}
}

for (t in seq(start, timesteps)) {
for (process in processes) {
execute_any_process(process, t)
}
Expand All @@ -60,6 +73,57 @@
event$.tick()
}
}

invisible(checkpoint_state(timesteps, variables, events))
}

#' @title Save the simulation state
#' @description Save the simulation state in an R object, allowing it to be
#' resumed later using \code{\link[individual]{restore_state}}.
#' @param timesteps <- the number of time steps that have already been simulated
#' @param variables the list of Variables
#' @param events the list of Events
checkpoint_state <- function(timesteps, variables, events) {
random_state <- .GlobalEnv$.Random.seed
list(
variables=lapply(variables, function(v) v$.checkpoint()),
events=lapply(events, function(e) e$.checkpoint()),
timesteps=timesteps,
random_state=random_state
)
}

#' @title Restore the simulation state
#' @description Restore the simulation state from a previous checkpoint.
#' The state of passed events and variables is overwritten to match the state they
#' had when the simulation was checkpointed. Returns the time step at which the
#' simulation should resume.
#' @param state the simulation state to restore, as returned by \code{\link[individual]{restore_state}}.
#' @param variables the list of Variables
#' @param events the list of Events
#' @param restore_random_state if TRUE, restore R's global random number generator's state from the checkpoint.
restore_state <- function(state, variables, events, restore_random_state) {
timesteps <- state$timesteps + 1

if (length(variables) != length(state$variables)) {
stop("Checkpoint's variables do not match simulation's")
}
for (i in seq_along(variables)) {
variables[[i]]$.restore(state$variables[[i]])
}

if (length(events) != length(state$events)) {
stop("Checkpoint's events do not match simulation's")

Check warning on line 116 in R/simulation.R

View check run for this annotation

Codecov / codecov/patch

R/simulation.R#L116

Added line #L116 was not covered by tests
}
for (i in seq_along(events)) {
events[[i]]$.restore(timesteps, state$events[[i]])
}

if (restore_random_state) {
.GlobalEnv$.Random.seed <- state$random_state
}

timesteps
}

#' @title Execute a C++ or R process in the simulation
Expand Down
Loading
Loading