diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..6319e186 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +FROM rocker/r-base:latest + +RUN \ + echo 'options(repos=c(CRAN="https://cloud.r-project.org"))' >> ~/.Rprofile && \ + Rscript --vanilla -e 'getOption("repos")' + +# Adding Git +RUN apt-get update && apt-get install -y --no-install-recommends git + +# Adding R packages +RUN \ + wget https://github.com/jgm/pandoc/releases/download/3.2.1/pandoc-3.2.1-1-amd64.deb && \ + dpkg -i pandoc-3.2.1-1-amd64.deb + +RUN install2.r cpp11 rmarkdown roxygen2 tinytest data.table netplot \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..d2974f1d --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "epiworldR", + "build": { + "dockerfile": "Dockerfile" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "gcc -v", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + "remoteUser": "root" +} diff --git a/.github/workflows/r.yml b/.github/workflows/r.yml index 58ac8b27..15336ff2 100644 --- a/.github/workflows/r.yml +++ b/.github/workflows/r.yml @@ -36,7 +36,7 @@ jobs: R_KEEP_PKG_SOURCE: yes steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 @@ -69,7 +69,7 @@ jobs: error-on: '"warning"' # Upload the built package as an artifact - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ matrix.config.os == 'ubuntu-latest' && matrix.config.r == 'release' }} with: name: ${{ matrix.config.os }}-pkg diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index a52fc3db..eb3d24ec 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -7,7 +7,8 @@ "/usr/local/include", "/usr/lib/R/site-library/cpp11/include", "/usr/lib/R/site-library/Rcpp/include", - "/usr/share/R/include" + "/usr/share/R/include", + "inst/include/epiworld" ], "intelliSenseMode": "linux-gcc-x64", "compilerPath": "/usr/bin/gcc", diff --git a/DESCRIPTION b/DESCRIPTION index 9c882d09..d67fb3b2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: epiworldR Type: Package Title: Fast Agent-Based Epi Models -Version: 0.1-1 -Date: 2024-04-07 +Version: 0.3-2 +Date: 2024-06-12 Authors@R: c( person("Derek", "Meyer", role=c("aut","cre"), email="derekmeyer37@gmail.com", comment = c(ORCID = "0009-0005-1350-6988")), @@ -21,7 +21,7 @@ URL: https://github.com/UofUEpiBio/epiworldR, https://uofuepibio.github.io/epiworldR-workshop/ BugReports: https://github.com/UofUEpiBio/epiworldR/issues License: MIT + file LICENSE -RoxygenNote: 7.3.1 +RoxygenNote: 7.3.2 Encoding: UTF-8 Roxygen: list(markdown = TRUE) LinkingTo: cpp11 diff --git a/Makefile b/Makefile index 21139d01..7dd0d23e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ # Capture the current value of the version of the package in DESCRIPTION VERSION := $(shell grep Version DESCRIPTION | sed -e 's/Version: //') + build: cd .. && R CMD build epiworldR diff --git a/NAMESPACE b/NAMESPACE index 887ea275..17f23b74 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,8 +1,8 @@ # Generated by roxygen2: do not edit by hand S3method("[",epiworld_agents) +S3method("[",epiworld_entities) S3method(add_tool,epiworld_model) -S3method(add_tool_n,epiworld_model) S3method(add_virus,epiworld_model) S3method(add_virus,epiworld_seir) S3method(add_virus,epiworld_seirconn) @@ -12,14 +12,10 @@ S3method(add_virus,epiworld_sir) S3method(add_virus,epiworld_sirconn) S3method(add_virus,epiworld_sird) S3method(add_virus,epiworld_sirdconn) -S3method(add_virus_n,epiworld_model) -S3method(add_virus_n,epiworld_seir) -S3method(add_virus_n,epiworld_seirconn) -S3method(add_virus_n,epiworld_sir) -S3method(add_virus_n,epiworld_sirconn) S3method(agents_from_edgelist,epiworld_model) S3method(agents_smallworld,epiworld_model) S3method(as.array,epiworld_hist_transition) +S3method(get_agents,epiworld_model) S3method(get_hist_tool,epiworld_model) S3method(get_hist_total,epiworld_model) S3method(get_hist_transition_matrix,epiworld_model) @@ -48,16 +44,23 @@ S3method(plot,epiworld_seir) S3method(plot,epiworld_seirconn) S3method(plot,epiworld_seird) S3method(plot,epiworld_seirdconn) +S3method(plot,epiworld_seirmixing) S3method(plot,epiworld_sir) S3method(plot,epiworld_sirconn) S3method(plot,epiworld_sird) S3method(plot,epiworld_sirdconn) +S3method(plot,epiworld_sirmixing) S3method(plot,epiworld_sis) S3method(plot,epiworld_sisd) S3method(plot,epiworld_surv) +S3method(plot_epi,epiworld_hist) +S3method(plot_epi,epiworld_hist_virus) +S3method(plot_epi,epiworld_model) S3method(print,epiworld_agent) S3method(print,epiworld_agents) S3method(print,epiworld_agents_tools) +S3method(print,epiworld_entities) +S3method(print,epiworld_entity) S3method(print,epiworld_globalevent) S3method(print,epiworld_model) S3method(print,epiworld_saver) @@ -82,14 +85,17 @@ export(ModelSEIR) export(ModelSEIRCONN) export(ModelSEIRD) export(ModelSEIRDCONN) +export(ModelSEIRMixing) export(ModelSIR) export(ModelSIRCONN) export(ModelSIRD) export(ModelSIRDCONN) export(ModelSIRLogit) +export(ModelSIRMixing) export(ModelSIS) export(ModelSISD) export(ModelSURV) +export(add_entity) export(add_globalevent) export(add_tool) export(add_tool_agent) @@ -101,10 +107,22 @@ export(agents_from_edgelist) export(agents_smallworld) export(change_state) export(clone_model) +export(distribute_entity_randomly) +export(distribute_entity_to_set) +export(distribute_tool_randomly) +export(distribute_tool_to_set) +export(distribute_virus_randomly) +export(distribute_virus_set) +export(entity) +export(entity_add_agent) +export(entity_get_agents) export(get_agents) export(get_agents_data_ncols) export(get_agents_states) export(get_agents_tools) +export(get_entities) +export(get_entity_name) +export(get_entity_size) export(get_generation_time) export(get_hist_tool) export(get_hist_total) @@ -138,12 +156,14 @@ export(globalevent_tool_logit) export(has_tool) export(has_virus) export(initial_states) +export(load_agents_entities_ties) export(make_saver) export(plot_generation_time) export(plot_incidence) export(plot_reproductive_number) export(queuing_off) export(queuing_on) +export(rm_entity) export(rm_tool) export(rm_virus) export(run) @@ -153,6 +173,9 @@ export(set_agents_data) export(set_death_reduction) export(set_death_reduction_fun) export(set_death_reduction_ptr) +export(set_distribution_entity) +export(set_distribution_tool) +export(set_distribution_virus) export(set_incubation) export(set_incubation_fun) export(set_incubation_ptr) diff --git a/NEWS.md b/NEWS.md index c949aca9..2f15b86e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,22 @@ +# epiworldR 0.3-2 (dev) + +* Starting version 0.3-0, `epiworldR` is versioned using the same version as the C++ library, `epiworld`. + +* Adds the new mixing models `ModelSIRMixing` and `ModelSEIRMixing`. + +* Ports the `Entity` class. Entities are used to group agents within a model. + +* Refactors `add_tool`, `add_virus`, and `add_entity` simplifying syntax. Now, + these functions only receive the model and object. Prevalence is + specified in the object itself. `add_tool_n` and `add_virus_n` are now + deprecated. + +* `globalaction_*` are now defunct. Use `globalevent_*` instead. + +* New functions to specify how viruses, tools, and entities are distributed + among agents: `distribute_viruses`, `distribute_tools`, and `distribute_entities`. + + # epiworldR 0.1-0` * Force model to update agents' states when running a simulation. diff --git a/R/ModelSEIRCONN.R b/R/ModelSEIRCONN.R index c5a41cea..3e01bff1 100644 --- a/R/ModelSEIRCONN.R +++ b/R/ModelSEIRCONN.R @@ -39,8 +39,8 @@ #' plot(model_seirconn) #' #' # Adding the flu -#' flu <- virus("Flu", .9, 1/7) -#' add_virus(model_seirconn, flu, .001) +#' flu <- virus("Flu", .9, 1/7, prevalence = 0.001, as_proportion = TRUE) +#' add_virus(model_seirconn, flu) #' #' #' # Running and printing #' run(model_seirconn, ndays = 100, seed = 1912) diff --git a/R/ModelSEIRDCONN.R b/R/ModelSEIRDCONN.R index 44aed7ba..42290d5a 100644 --- a/R/ModelSEIRDCONN.R +++ b/R/ModelSEIRDCONN.R @@ -47,8 +47,12 @@ #' plot(model_seirdconn) #' #' # Adding the flu -#' flu <- virus("Flu", prob_infecting = .3, recovery_rate = 1/7, prob_death = 0.001) -#' add_virus(model = model_seirdconn, virus = flu, proportion = .001) +#' flu <- virus( +#' "Flu", prob_infecting = .3, recovery_rate = 1/7, +#' prob_death = 0.001, +#' prevalence = 0.001, as_proportion = TRUE +#' ) +#' add_virus(model = model_seirdconn, virus = flu) #' #' #' # Running and printing #' run(model_seirdconn, ndays = 100, seed = 1912) diff --git a/R/ModelSEIRMixing.R b/R/ModelSEIRMixing.R new file mode 100644 index 00000000..1dee4eac --- /dev/null +++ b/R/ModelSEIRMixing.R @@ -0,0 +1,91 @@ +#' Susceptible Exposed Infected Removed model (SEIR) with mixing +#' @param name String. Name of the virus +#' @param prevalence Double. Initial proportion of individuals with the virus. +#' @param contact_rate Numeric scalar. Average number of contacts per step. +#' @param transmission_rate Numeric scalar between 0 and 1. Probability of +#' transmission. +#' @param incubation_days Numeric scalar. Average number of days in the +#' incubation period. +#' @param recovery_rate Numeric scalar between 0 and 1. Probability of recovery. +#' @param x Object of class SIRCONN. +#' @param ... Currently ignore. +#' @param n Number of individuals in the population. +#' @param contact_matrix Matrix of contact rates between individuals. +#' @export +#' @family Models +#' @details +#' The `contact_matrix` is a matrix of contact rates between entities. The +#' matrix should be of size `n x n`, where `n` is the number of entities. +#' This is a row-stochastic matrix, i.e., the sum of each row should be 1. +#' +#' The [initial_states] function allows the user to set the initial state of the +#' model. In particular, the user can specify how many of the non-infected +#' agents have been removed at the beginning of the simulation. +#' @returns +#' - The `ModelSEIRMixing`function returns a model of class [epiworld_model]. +#' @aliases epiworld_seirmixing +#' +#' @examples +#' +#' # Start off creating three entities. +#' # Individuals will be distribured randomly between the three. +#' e1 <- entity("Population 1", 3e3, as_proportion = FALSE) +#' e2 <- entity("Population 2", 3e3, as_proportion = FALSE) +#' e3 <- entity("Population 3", 3e3, as_proportion = FALSE) +#' +#' # Row-stochastic matrix (rowsums 1) +#' cmatrix <- c( +#' c(0.9, 0.05, 0.05), +#' c(0.1, 0.8, 0.1), +#' c(0.1, 0.2, 0.7) +#' ) |> matrix(byrow = TRUE, nrow = 3) +#' +#' N <- 9e3 +#' +#' flu_model <- ModelSEIRMixing( +#' name = "Flu", +#' n = N, +#' prevalence = 1 / N, +#' contact_rate = 20, +#' transmission_rate = 0.1, +#' recovery_rate = 1 / 7, +#' incubation_days = 7, +#' contact_matrix = cmatrix +#' ) +#' +#' # Adding the entities to the model +#' flu_model |> +#' add_entity(e1) |> +#' add_entity(e2) |> +#' add_entity(e3) +#' +#' set.seed(331) +#' run(flu_model, ndays = 100) +#' summary(flu_model) +#' plot_incidence(flu_model) +#' +#' @seealso epiworld-methods +ModelSEIRMixing <- function( + name, n, prevalence, contact_rate, transmission_rate, + incubation_days, recovery_rate, contact_matrix +) { + + structure( + ModelSEIRMixing_cpp( + name, n, prevalence, contact_rate, + transmission_rate, incubation_days, + recovery_rate, as.vector(contact_matrix) + ), + class = c("epiworld_seirmixing", "epiworld_model") + ) + +} + +#' @rdname ModelSEIRMixing +#' @export +#' @returns The `plot` function returns a plot of the SEIRMixing model of class +#' [epiworld_model]. +#' @param main Title of the plot +plot.epiworld_seirmixing <- function(x, main = get_name(x), ...) { # col = NULL + plot_epi(x, main = main, ...) +} diff --git a/R/ModelSIRMixing.R b/R/ModelSIRMixing.R new file mode 100644 index 00000000..d10b5c29 --- /dev/null +++ b/R/ModelSIRMixing.R @@ -0,0 +1,88 @@ +#' Susceptible Infected Removed model (SIR) with mixing +#' @param name String. Name of the virus +#' @param prevalence Double. Initial proportion of individuals with the virus. +#' @param contact_rate Numeric scalar. Average number of contacts per step. +#' @param transmission_rate Numeric scalar between 0 and 1. Probability of +#' transmission. +#' @param recovery_rate Numeric scalar between 0 and 1. Probability of recovery. +#' @param x Object of class SIRCONN. +#' @param ... Currently ignore. +#' @param n Number of individuals in the population. +#' @param contact_matrix Matrix of contact rates between individuals. +#' @export +#' @family Models +#' @details +#' The `contact_matrix` is a matrix of contact rates between entities. The +#' matrix should be of size `n x n`, where `n` is the number of entities. +#' This is a row-stochastic matrix, i.e., the sum of each row should be 1. +#' +#' The [initial_states] function allows the user to set the initial state of the +#' model. In particular, the user can specify how many of the non-infected +#' agents have been removed at the beginning of the simulation. +#' @returns +#' - The `ModelSIRMixing`function returns a model of class [epiworld_model]. +#' @aliases epiworld_sirmixing +#' +#' @examples +#' # From the vignette +#' +#' # Start off creating three entities. +#' # Individuals will be distribured randomly between the three. +#' e1 <- entity("Population 1", 3e3, as_proportion = FALSE) +#' e2 <- entity("Population 2", 3e3, as_proportion = FALSE) +#' e3 <- entity("Population 3", 3e3, as_proportion = FALSE) +#' +#' # Row-stochastic matrix (rowsums 1) +#' cmatrix <- c( +#' c(0.9, 0.05, 0.05), +#' c(0.1, 0.8, 0.1), +#' c(0.1, 0.2, 0.7) +#' ) |> matrix(byrow = TRUE, nrow = 3) +#' +#' N <- 9e3 +#' +#' flu_model <- ModelSIRMixing( +#' name = "Flu", +#' n = N, +#' prevalence = 1 / N, +#' contact_rate = 20, +#' transmission_rate = 0.1, +#' recovery_rate = 1 / 7, +#' contact_matrix = cmatrix +#' ) +#' +#' # Adding the entities to the model +#' flu_model |> +#' add_entity(e1) |> +#' add_entity(e2) |> +#' add_entity(e3) +#' +#' set.seed(331) +#' run(flu_model, ndays = 100) +#' summary(flu_model) +#' plot_incidence(flu_model) +#' +#' @seealso epiworld-methods +ModelSIRMixing <- function( + name, n, prevalence, contact_rate, transmission_rate, recovery_rate, + contact_matrix +) { + + structure( + ModelSIRMixing_cpp( + name, n, prevalence, contact_rate, + transmission_rate, recovery_rate, as.vector(contact_matrix) + ), + class = c("epiworld_sirmixing", "epiworld_model") + ) + +} + +#' @rdname ModelSIRMixing +#' @export +#' @returns The `plot` function returns a plot of the SIRMixing model of class +#' [epiworld_model]. +#' @param main Title of the plot +plot.epiworld_sirmixing <- function(x, main = get_name(x), ...) { # col = NULL + plot_epi(x, main = main, ...) +} diff --git a/R/agents-methods.R b/R/agents-methods.R index d71148c5..2c1bd5fb 100644 --- a/R/agents-methods.R +++ b/R/agents-methods.R @@ -37,7 +37,12 @@ #' #' x[0] # Print information about the first agent. Substitute the agent of #' # interest's position where '0' is. -get_agents <- function(model) { +#' @name agents +get_agents <- function(model, ...) UseMethod("get_agents") + +#' @export +#' @rdname agents +get_agents.epiworld_model <- function(model, ...) { res <- get_agents_cpp(model) @@ -52,7 +57,7 @@ get_agents <- function(model) { #' @param x An object of class [epiworld_agents] #' @param i Index (id) of the agent (from 0 to `n-1`) #' @export -#' @rdname get_agents +#' @rdname agents #' @return #' - The `[` method returns an object of class [epiworld_agent]. #' @aliases epiworld_agent @@ -82,7 +87,7 @@ get_agents <- function(model) { #' @returns #' - The `print` function returns information about each individual agent of #' class [epiworld_agent]. -#' @rdname get_agents +#' @rdname agents print.epiworld_agent <- function(x, compressed = FALSE, ...) { invisible(print_agent_cpp(x, attr(x, "model"), compressed)) @@ -91,7 +96,7 @@ print.epiworld_agent <- function(x, compressed = FALSE, ...) { #' @export #' @param max_print Integer scalar. Maximum number of agents to print. -#' @rdname get_agents +#' @rdname agents print.epiworld_agents <- function(x, compressed = TRUE, max_print = 10, ...) { model <- attr(x, "model") @@ -113,7 +118,7 @@ print.epiworld_agents <- function(x, compressed = TRUE, max_print = 10, ...) { #' @export #' @returns #' - The `get_state` function returns the state of the [epiworld_agents] object. -#' @rdname get_agents +#' @rdname agents get_state <- function(x) { get_state_agent_cpp(x) } diff --git a/R/agents.R b/R/agents.R index 9d2e9ca3..f4b4f8a1 100644 --- a/R/agents.R +++ b/R/agents.R @@ -19,7 +19,6 @@ stopifnot_agent <- function(x) { #' @param d,directed Logical scalar. Whether the graph is directed or not. #' @param p Probability of rewiring. #' @export -#' @aliases agents #' @return #' - The 'agents_smallworld' function returns a model with the agents #' loaded. diff --git a/R/cpp11.R b/R/cpp11.R index 0b5eb2c2..b1276fdb 100644 --- a/R/cpp11.R +++ b/R/cpp11.R @@ -104,6 +104,62 @@ get_today_total_cpp <- function(model) { .Call(`_epiworldR_get_today_total_cpp`, model) } +get_entities_cpp <- function(model) { + .Call(`_epiworldR_get_entities_cpp`, model) +} + +get_entity_cpp <- function(entities, idx) { + .Call(`_epiworldR_get_entity_cpp`, entities, idx) +} + +entity_cpp <- function(name, preval, as_proportion, to_unassigned) { + .Call(`_epiworldR_entity_cpp`, name, preval, as_proportion, to_unassigned) +} + +get_entity_size_cpp <- function(entity) { + .Call(`_epiworldR_get_entity_size_cpp`, entity) +} + +entity_add_agent_cpp <- function(entity, agent, model) { + .Call(`_epiworldR_entity_add_agent_cpp`, entity, agent, model) +} + +get_entity_name_cpp <- function(entity) { + .Call(`_epiworldR_get_entity_name_cpp`, entity) +} + +add_entity_cpp <- function(model, entity) { + .Call(`_epiworldR_add_entity_cpp`, model, entity) +} + +rm_entity_cpp <- function(model, entity_pos) { + .Call(`_epiworldR_rm_entity_cpp`, model, entity_pos) +} + +load_agents_entities_ties_cpp <- function(model, agents_ids, entities_ids) { + .Call(`_epiworldR_load_agents_entities_ties_cpp`, model, agents_ids, entities_ids) +} + +entity_get_agents_cpp <- function(entity) { + .Call(`_epiworldR_entity_get_agents_cpp`, entity) +} + +print_entity_cpp <- function(entity) { + .Call(`_epiworldR_print_entity_cpp`, entity) +} + +set_distribution_entity_cpp <- function(entity, fun) { + .Call(`_epiworldR_set_distribution_entity_cpp`, entity, fun) +} + +distribute_entity_randomly_cpp <- function(prevalence, as_proportion, to_unassigned) { + .Call(`_epiworldR_distribute_entity_randomly_cpp`, prevalence, as_proportion, to_unassigned) +} + +distribute_entity_to_set_cpp <- function(agents_ids) { + .Call(`_epiworldR_distribute_entity_to_set_cpp`, agents_ids) +} + ModelSURV_cpp <- function(name, prevalence, efficacy_vax, latent_period, prob_symptoms, prop_vaccinated, prop_vax_redux_transm, infect_period, prop_vax_redux_infect, surveillance_prob, transmission_rate, prob_death, prob_noreinfect) { .Call(`_epiworldR_ModelSURV_cpp`, name, prevalence, efficacy_vax, latent_period, prob_symptoms, prop_vaccinated, prop_vax_redux_transm, infect_period, prop_vax_redux_infect, surveillance_prob, transmission_rate, prob_death, prob_noreinfect) } @@ -156,6 +212,14 @@ ModelDiffNet_cpp <- function(name, prevalence, prob_adopt, normalize_exposure, d .Call(`_epiworldR_ModelDiffNet_cpp`, name, prevalence, prob_adopt, normalize_exposure, data, data_ncols, data_cols, params) } +ModelSIRMixing_cpp <- function(name, n, prevalence, contact_rate, transmission_rate, recovery_rate, contact_matrix) { + .Call(`_epiworldR_ModelSIRMixing_cpp`, name, n, prevalence, contact_rate, transmission_rate, recovery_rate, contact_matrix) +} + +ModelSEIRMixing_cpp <- function(name, n, prevalence, contact_rate, transmission_rate, incubation_days, recovery_rate, contact_matrix) { + .Call(`_epiworldR_ModelSEIRMixing_cpp`, name, n, prevalence, contact_rate, transmission_rate, incubation_days, recovery_rate, contact_matrix) +} + print_cpp <- function(m, lite) { .Call(`_epiworldR_print_cpp`, m, lite) } @@ -264,16 +328,12 @@ clone_model_cpp <- function(model) { .Call(`_epiworldR_clone_model_cpp`, model) } -tool_cpp <- function(name, susceptibility_reduction, transmission_reduction, recovery_enhancer, death_reduction) { - .Call(`_epiworldR_tool_cpp`, name, susceptibility_reduction, transmission_reduction, recovery_enhancer, death_reduction) -} - -add_tool_cpp <- function(m, t, preval) { - .Call(`_epiworldR_add_tool_cpp`, m, t, preval) +tool_cpp <- function(name, prevalence, as_proportion, susceptibility_reduction, transmission_reduction, recovery_enhancer, death_reduction) { + .Call(`_epiworldR_tool_cpp`, name, prevalence, as_proportion, susceptibility_reduction, transmission_reduction, recovery_enhancer, death_reduction) } -add_tool_n_cpp <- function(m, t, preval) { - .Call(`_epiworldR_add_tool_n_cpp`, m, t, preval) +add_tool_cpp <- function(m, t) { + .Call(`_epiworldR_add_tool_cpp`, m, t) } rm_tool_cpp <- function(m, tool_pos) { @@ -352,20 +412,28 @@ print_agent_tools_cpp <- function(tools) { .Call(`_epiworldR_print_agent_tools_cpp`, tools) } -virus_cpp <- function(name, prob_infecting, prob_recovery, prob_death, post_immunity, incubation) { - .Call(`_epiworldR_virus_cpp`, name, prob_infecting, prob_recovery, prob_death, post_immunity, incubation) +set_distribution_tool_cpp <- function(tool, distfun) { + .Call(`_epiworldR_set_distribution_tool_cpp`, tool, distfun) } -virus_set_state_cpp <- function(v, init, end, removed) { - .Call(`_epiworldR_virus_set_state_cpp`, v, init, end, removed) +distribute_tool_randomly_cpp <- function(prevalence, as_proportion) { + .Call(`_epiworldR_distribute_tool_randomly_cpp`, prevalence, as_proportion) } -add_virus_cpp <- function(m, v, preval) { - .Call(`_epiworldR_add_virus_cpp`, m, v, preval) +distribute_tool_to_set_cpp <- function(agents_ids) { + .Call(`_epiworldR_distribute_tool_to_set_cpp`, agents_ids) } -add_virus_n_cpp <- function(m, v, preval) { - .Call(`_epiworldR_add_virus_n_cpp`, m, v, preval) +virus_cpp <- function(name, prevalence, as_proportion, prob_infecting, prob_recovery, prob_death, post_immunity, incubation) { + .Call(`_epiworldR_virus_cpp`, name, prevalence, as_proportion, prob_infecting, prob_recovery, prob_death, post_immunity, incubation) +} + +virus_set_state_cpp <- function(v, init, end, removed) { + .Call(`_epiworldR_virus_set_state_cpp`, v, init, end, removed) +} + +add_virus_cpp <- function(m, v) { + .Call(`_epiworldR_add_virus_cpp`, m, v) } rm_virus_cpp <- function(m, virus_pos) { @@ -435,3 +503,15 @@ get_name_virus_cpp <- function(virus) { set_name_virus_cpp <- function(virus, name) { .Call(`_epiworldR_set_name_virus_cpp`, virus, name) } + +set_distribution_virus_cpp <- function(virus, dist) { + .Call(`_epiworldR_set_distribution_virus_cpp`, virus, dist) +} + +distribute_virus_randomly_cpp <- function(prevalence, as_proportion) { + .Call(`_epiworldR_distribute_virus_randomly_cpp`, prevalence, as_proportion) +} + +distribute_virus_to_set_cpp <- function(agents_ids) { + .Call(`_epiworldR_distribute_virus_to_set_cpp`, agents_ids) +} diff --git a/R/entity.R b/R/entity.R new file mode 100644 index 00000000..e10c4227 --- /dev/null +++ b/R/entity.R @@ -0,0 +1,280 @@ + +stopifnot_entity <- function(entity) { + if (!inherits(entity, "epiworld_entity")) { + stop("Argument 'entity' must be an entity object.") + } +} + +stopifnot_entity_distfun <- function(distfun) { + if (!inherits(distfun, "epiworld_distribution_entity")) { + stop("Argument 'distfun' must be a distribution function.") + } +} + +#' Get entities +#' +#' Entities in `epiworld` are objects that can contain agents. +#' @param model Model object of class `epiworld_model`. +#' +#' @details +#' Epiworld entities are especially useful for mixing models, particularly +#' [ModelSIRMixing] and [ModelSEIRMixing]. +#' +#' @name entities +#' @export +#' @examples +#' # Creating a mixing model +#' mymodel <- ModelSIRMixing( +#' name = "My model", +#' n = 10000, +#' prevalence = .001, +#' contact_rate = 10, +#' transmission_rate = .1, +#' recovery_rate = 1/7, +#' contact_matrix = matrix(c(.9, .1, .1, .9), 2, 2) +#' ) +#' +#' ent1 <- entity("First", 5000, FALSE) +#' ent2 <- entity("Second", 5000, FALSE) +#' +#' mymodel |> +#' add_entity(ent1) |> +#' add_entity(ent2) +#' +#' run(mymodel, ndays = 100, seed = 1912) +#' +#' summary(mymodel) +get_entities <- function(model) { + + stopifnot_model(model) + structure( + lapply( + get_entities_cpp(model), \(e) { + structure( + e, + class = c("epiworld_entity"), + model = model + ) + } + ), + class = c("epiworld_entities") + ) +} + +#' @export +print.epiworld_entities <- function(x, ...) { + cat("A collection of ", length(x), " entities.\n") + invisible(x) +} + +#' @export +#' @rdname entities +#' @param x Object of class `epiworld_entities`. +#' @param i Integer index. +`[.epiworld_entities` <- function(x, i) { + + stopifnot_entity(x) + + if (i > get_entity_size(x)) { + stop("Index out of bounds.") + } + + structure( + get_entity_cpp(x, i), + class = c("epiworld_entity"), + model = x$model + ) + +} + +#' @export +#' @param name Character scalar. Name of the entity. +#' @param prevalence Numeric scalar. Prevalence of the entity. +#' @param as_proportion Logical scalar. If `TRUE`, `prevalence` is interpreted +#' as a proportion. +#' @param to_unassigned Logical scalar. If `TRUE`, the entity is added to the +#' unassigned pool. +#' @return +#' - The function `entity` creates an entity object. +#' @rdname entities +entity <- function(name, prevalence, as_proportion, to_unassigned = TRUE) { + + structure( + entity_cpp( + name, + as.double(prevalence), + as.logical(as_proportion), + as.logical(to_unassigned) + ), + class = "epiworld_entity" + ) + +} + +#' @export +#' @rdname entities +#' @param entity Entity object of class `epiworld_entity`. +#' @return +#' - The function `get_entity_size` returns the number of agents in the entity. +get_entity_size <- function(entity) { + stopifnot_entity(entity) + get_entity_size_cpp(entity) +} + +#' @export +#' @rdname entities +#' @return +#' - The function `get_entity_name` returns the name of the entity. +get_entity_name <- function(entity) { + stopifnot_entity(entity) + get_entity_name_cpp(entity) +} + +#' @export +#' @rdname entities +#' @param agent Agent object of class `epiworld_agent`. +#' @return +#' - The function `entity_add_agent` adds an agent to the entity. +entity_add_agent <- function( + entity, + agent, + model = attr(entity, "model") + ) { + + stopifnot_entity(entity) + stopifnot_agent(agent) + entity_add_agent_cpp(entity, agent, model) + + invisible(entity) + +} + +#' @export +#' @rdname entities +#' @param id Integer scalar. Entity id to remove (starting from zero). +#' @return +#' - The function `rm_entity` removes an entity from the model. +rm_entity <- function(model, id) { + + stopifnot_model(model) + rm_entity_cpp(model, entity) + + invisible(model) +} + +#' @export +#' @rdname entities +add_entity <- function( + model, + entity +) { + + stopifnot_model(model) + stopifnot_entity(entity) + add_entity_cpp( + model, + entity + ) + + invisible(model) + +} + +#' @export +#' @rdname entities +#' @param agents_id Integer vector. +#' @param entities_id Integer vector. +#' @return +#' - The function `load_agents_entities_ties` loads agents into entities. +load_agents_entities_ties <- function( + model, + agents_id, + entities_id +) { + + stopifnot_model(model) + if (!inherits(agents_id, "integer")) { + stop("Argument 'agents_id' must be an integer.") + } + + if (!inherits(entities_id, "integer")) { + stop("Argument 'entities_id' must be an integer.") + } + + load_agents_entities_ties_cpp(model, agents_id, entities_id) + + invisible(model) + +} + +#' @export +#' @rdname entities +#' @return +#' - The function `entity_get_agents` returns an integer vector with the agents +#' in the entity (ids). +entity_get_agents <- function(entity) { + + stopifnot_entity(entity) + entity_get_agents_cpp(entity) + +} + +#' @export +print.epiworld_entity <- function(x, ...) { + print_entity_cpp(x) + invisible(x) +} + +#' @export +#' @param prevalence Numeric scalar. Prevalence of the entity. +#' @param as_proportion Logical scalar. If `TRUE`, `prevalence` is interpreted +#' as a proportion. +#' @rdname entities +distribute_entity_randomly <- function( + prevalence, + as_proportion, + to_unassigned = TRUE +) { + + structure( + distribute_entity_randomly_cpp( + as.double(prevalence), + as.logical(as_proportion), + as.logical(to_unassigned) + ), + class = "epiworld_distribution_entity" + ) + +} + +#' @export +#' @param agents_ids Integer vector. Ids of the agents to distribute. +#' @rdname entities +distribute_entity_to_set <- function( + agents_ids +) { + + structure( + distribute_entity_to_set_cpp( + as.integer(agents_ids) + ), + class = "epiworld_distribution_entity" + ) + +} + +#' @export +#' @rdname entities +#' @param distfun Distribution function object of class `epiworld_distribution_entity`. +set_distribution_entity <- function( + entity, + distfun +) { + + stopifnot_entity(entity) + stopifnot_entity_distfun(distfun) + set_distribution_entity_cpp(entity, distfun) + + invisible(entity) + +} \ No newline at end of file diff --git a/R/functions-renamed.R b/R/functions-renamed.R index 2726e6a6..61556bed 100644 --- a/R/functions-renamed.R +++ b/R/functions-renamed.R @@ -1,7 +1,48 @@ -#' Deprecated functions in epiworldR +#' Deprecated and removed functions in epiworldR #' @description #' Starting version 0.0-4, epiworld changed how it refered to "actions." #' Following more traditional ABMs, actions are now called "events." +#' #' @param ... Arguments to be passed to the new function. +#' @param model Model object of class `epiworld_model`. +#' @param tool Tool object of class `epiworld_tool`. +#' @param virus Virus object of class `epiworld_virus`. #' @name epiworldR-deprecated -NULL \ No newline at end of file +NULL + +#' @param n Deprecated. +#' @export +#' @rdname epiworldR-deprecated +add_tool_n <- function(model, tool, n) { + + .Deprecated(new = "add_tool") + + set_distribution_tool( + tool, + distfun = distribute_tool_randomly( + prevalence = n, + as_proportion = TRUE + ) + ) + + add_tool(model, tool) + +} + +#' @export +#' @rdname epiworldR-deprecated +add_virus_n <- function(model, virus, n) { + + .Deprecated(new = "add_virus") + + set_distribution_virus( + virus = virus, + distfun = distribute_virus_randomly( + prevalence = n, + as_proportion = TRUE + ) + ) + + add_virus(model, virus) + +} \ No newline at end of file diff --git a/R/global-actions.R b/R/global-actions.R index 163b7fe8..2504e204 100644 --- a/R/global-actions.R +++ b/R/global-actions.R @@ -23,6 +23,8 @@ #' # Creating a tool #' epitool <- tool( #' name = "Vaccine", +#' prevalence = 0, +#' as_proportion = FALSE, #' susceptibility_reduction = .9, #' transmission_reduction = .5, #' recovery_enhancer = .5, @@ -89,12 +91,10 @@ globalevent_tool <- function( #' @rdname epiworldR-deprecated globalaction_tool <- function(...) { - .Deprecated( + .Defunct( new = "globalevent_tool" - ) + ) - globalevent_tool(...) - } #' @export @@ -132,7 +132,7 @@ globalevent_tool_logit <- function( #' @rdname epiworldR-deprecated globalaction_tool_logit <- function(...) { - .Deprecated( + .Defunct( new = "globalevent_tool_logit" ) @@ -170,7 +170,8 @@ globalevent_set_params <- function( #' @rdname epiworldR-deprecated globalaction_set_params <- function(...) { - .Deprecated( + + .Defunct( new = "globalevent_set_params" ) @@ -232,7 +233,7 @@ globalevent_fun <- function( #' @rdname epiworldR-deprecated globalaction_fun <- function(...) { - .Deprecated( + .Defunct( new = "globalevent_fun" ) @@ -274,7 +275,7 @@ print.epiworld_globalevent <- function(x, ...) { add_globalevent <- function(model, action) { if (length(attr(action, "tool"))) - add_tool_n(model, attr(action, "tool"), 0) + add_tool(model, attr(action, "tool")) invisible(add_globalevent_cpp(model, action)) diff --git a/R/model-methods.R b/R/model-methods.R index 19991701..61210d0d 100644 --- a/R/model-methods.R +++ b/R/model-methods.R @@ -96,7 +96,7 @@ stopifnot_model <- function(model) { #' get_virus(model_sirconn, 0) # Returns information about the first virus in #' # the model (index begins at 0). #' -#' add_tool(model_sirconn, tool("Vaccine", .9, .9, .5, 1), proportion = .5) +#' add_tool(model_sirconn, tool("Vaccine", .9, .9, .5, 1, prevalence = 0.5, as_prop = TRUE)) #' get_tool(model_sirconn, 0) # Returns information about the first tool in the #' # model. In this case, there are no tools so an #' # error message will occur. diff --git a/R/plot_epi.R b/R/plot_epi.R index 0922951f..36234322 100644 --- a/R/plot_epi.R +++ b/R/plot_epi.R @@ -32,6 +32,7 @@ find_scale <- function(x) { #' @importFrom graphics legend plot_epi <- function(x, main = "", counts_scale, ...) UseMethod("plot_epi") +#' @export plot_epi.epiworld_model <- function( x, main = "", counts_scale, @@ -47,6 +48,7 @@ plot_epi.epiworld_model <- function( } +#' @export plot_epi.epiworld_hist_virus <- function( x, main = "", counts_scale, @@ -62,6 +64,7 @@ plot_epi.epiworld_hist_virus <- function( } +#' @export plot_epi.epiworld_hist <- function( x, main = "", counts_scale, diff --git a/R/tool.R b/R/tool.R index 97d0cb8d..3cf628ad 100644 --- a/R/tool.R +++ b/R/tool.R @@ -28,6 +28,8 @@ #' #' epitool <- tool( #' name = "Vaccine", +#' prevalence = 0.5, +#' as_proportion = TRUE, #' susceptibility_reduction = .9, #' transmission_reduction = .5, #' recovery_enhancer = .5, @@ -38,14 +40,16 @@ #' #' set_name_tool(epitool, 'Pfizer') # Assigning name to the tool #' get_name_tool(epitool) # Returning the name of the tool -#' add_tool(model_sirconn, epitool, .5) +#' add_tool(model_sirconn, epitool) #' run(model_sirconn, ndays = 100, seed = 1912) #' model_sirconn #' plot(model_sirconn) #' #' # To declare a certain number of individuals with the tool #' rm_tool(model_sirconn, 0) # Removing epitool from the model -#' add_tool_n(model_sirconn, epitool, 5500) +#' # Setting prevalence to 0.1 +#' set_distribution_tool(epitool, distribute_tool_randomly(0.1, TRUE)) +#' add_tool(model_sirconn, epitool) #' run(model_sirconn, ndays = 100, seed = 1912) #' #' # Adjusting probabilities due to tool @@ -53,6 +57,9 @@ #' set_transmission_reduction(epitool, 0.2) # Transmission reduction #' set_recovery_enhancer(epitool, 0.15) # Probability increase of recovery #' set_death_reduction(epitool, 0.05) # Probability reduction of death +#' +#' rm_tool(model_sirconn, 0) +#' add_tool(model_sirconn, epitool) #' run(model_sirconn, ndays = 100, seed = 1912) # Run model to view changes #' #' @export @@ -61,6 +68,8 @@ #' @aliases epiworld_tool tool <- function( name, + prevalence, + as_proportion, susceptibility_reduction, transmission_reduction, recovery_enhancer, @@ -70,6 +79,8 @@ tool <- function( structure( tool_cpp( name, + prevalence, + as_proportion, susceptibility_reduction, transmission_reduction, recovery_enhancer, @@ -106,6 +117,16 @@ stopifnot_tfun <- function(tfun) { } } +stopifnot_tool_distfun <- function(tool_distfun) { + if (!inherits(tool_distfun, "epiworld_tool_distfun")) { + stop( + "The -tool_distfun- object must be of class \"epiworld_tool_distfun\". ", + "The object passed to the function is of class(es): ", + paste(class(tool_distfun), collapse = ", ") + ) + } +} + #' @export #' @details #' The name of the `epiworld_tool` object can be manipulated with the functions @@ -132,35 +153,36 @@ get_name_tool <- function(tool) { #' @export #' @param tool An object of class `epiworld_tool` -#' @param proportion In the case of `add_tool`, a proportion, otherwise, an integer. +#' @param proportion Deprecated. #' @details #' The `add_tool` function adds the specified tool to the model of class #' [epiworld_model] with specified proportion. #' @rdname tool -add_tool <- function(model, tool, proportion) UseMethod("add_tool") +add_tool <- function(model, tool, proportion) { + + if (!missing(proportion)) { -#' @export -add_tool.epiworld_model <- function(model, tool, proportion) { + warning( + "The 'proportion' argument is deprecated. ", + "Use 'set_distribution_tool' instead." + ) - stopifnot_tool(tool) - add_tool_cpp(model, tool, as.double(proportion)) - invisible(model) + set_distribution_tool( + tool, + distribute_tool_randomly(proportion, TRUE) + ) -} + } -#' @export -#' @rdname tool -#' @returns -#' - The `add_tool_n` function adds the specified tool to the model of class -#' [epiworld_model] with specified count n. -#' @param n A positive integer. Number of agents to initially have the tool. -add_tool_n <- function(model, tool, n) UseMethod("add_tool_n") + UseMethod("add_tool") + +} #' @export -add_tool_n.epiworld_model <- function(model, tool, n) { +add_tool.epiworld_model <- function(model, tool, proportion) { stopifnot_tool(tool) - add_tool_n_cpp(model, tool, as.integer(n)) + add_tool_cpp(model, tool) invisible(model) } @@ -204,13 +226,15 @@ rm_tool <- function(model, tool_pos) { #' # Creating a tool #' mask_wearing <- tool( #' name = "Mask", +#' prevalence = 0.5, +#' as_proportion = TRUE, #' susceptibility_reduction = 0.0, #' transmission_reduction = 0.3, # Only transmission #' recovery_enhancer = 0.0, #' death_reduction = 0.0 #' ) #' -#' add_tool(sir, mask_wearing, .5) +#' add_tool(sir, mask_wearing) #' #' run(sir, ndays = 50, seed = 11) #' hist_0 <- get_hist_total(sir) @@ -343,7 +367,7 @@ set_susceptibility_reduction_fun <- function(tool, model, tfun) { set_transmission_reduction <- function(tool, prob) { stopifnot_tool(tool) - set_transmission_reduction_cpp(tool, as.double(prob)) + invisible(set_transmission_reduction_cpp(tool, as.double(prob))) } @@ -353,7 +377,7 @@ set_transmission_reduction_ptr <- function(tool, model, param) { stopifnot_tool(tool) stopifnot_model(model) - set_transmission_reduction_ptr_cpp(tool, model, param) + invisible(set_transmission_reduction_ptr_cpp(tool, model, param)) } @@ -364,7 +388,7 @@ set_transmission_reduction_fun <- function(tool, model, tfun) { stopifnot_tool(tool) stopifnot_model(model) stopifnot_tfun(tfun) - set_transmission_reduction_fun_cpp(tool, model, tfun) + invisible(set_transmission_reduction_fun_cpp(tool, model, tfun)) } # Recovery enhancer ------------------------------------------------------------ @@ -377,7 +401,7 @@ set_transmission_reduction_fun <- function(tool, model, tfun) { set_recovery_enhancer <- function(tool, prob) { stopifnot_tool(tool) - set_recovery_enhancer_cpp(tool, as.double(prob)) + invisible(set_recovery_enhancer_cpp(tool, as.double(prob))) } @@ -387,7 +411,7 @@ set_recovery_enhancer_ptr <- function(tool, model, param) { stopifnot_tool(tool) stopifnot_model(model) - set_recovery_enhancer_ptr_cpp(tool, model, param) + invisible(set_recovery_enhancer_ptr_cpp(tool, model, param)) } @@ -398,7 +422,7 @@ set_recovery_enhancer_fun <- function(tool, model, tfun) { stopifnot_tool(tool) stopifnot_model(model) stopifnot_tfun(tfun) - set_recovery_enhancer_fun_cpp(tool, model, tfun) + invisible(set_recovery_enhancer_fun_cpp(tool, model, tfun)) } @@ -412,7 +436,7 @@ set_recovery_enhancer_fun <- function(tool, model, tfun) { set_death_reduction <- function(tool, prob) { stopifnot_tool(tool) - set_death_reduction_cpp(tool, as.double(prob)) + invisible(set_death_reduction_cpp(tool, as.double(prob))) } @@ -422,7 +446,7 @@ set_death_reduction_ptr <- function(tool, model, param) { stopifnot_tool(tool) stopifnot_model(model) - set_death_reduction_ptr_cpp(tool, model, param) + invisible(set_death_reduction_ptr_cpp(tool, model, param)) } @@ -433,7 +457,7 @@ set_death_reduction_fun <- function(tool, model, tfun) { stopifnot_tool(tool) stopifnot_model(model) stopifnot_tfun(tfun) - set_death_reduction_fun_cpp(tool, model, tfun) + invisible(set_death_reduction_fun_cpp(tool, model, tfun)) } @@ -475,5 +499,67 @@ print.epiworld_agents_tools <- function(x, max_print = 10, ...) { } +#' @export +#' @details +#' The `set_distribution_tool` function assigns a distribution function to the +#' specified tool of class [epiworld_tool]. The distribution function can be +#' created using the functions [distribute_tool_randomly()] and +#' [distribute_tool_to_set()]. +#' @param distfun An object of class `epiworld_tool_distfun`. +#' @rdname tool +set_distribution_tool <- function(tool, distfun) { + stopifnot_tool(tool) + stopifnot_tool_distfun(distfun) + invisible(set_distribution_tool_cpp(tool = tool, distfun = distfun)) + +} + +#' @export +#' @rdname tool +#' @details +#' The `distribute_tool_randomly` function creates a distribution function that +#' randomly assigns the tool to a proportion of the population. +#' @param as_proportion Logical scalar. If `TRUE`, `prevalence` is interpreted +#' as a proportion of the total number of agents in the model. +#' @param prevalence Numeric scalar. Prevalence of the tool. +#' @return +#' - The `distribute_tool_randomly` function returns a distribution function of +#' class `epiworld_tool_distfun`. +distribute_tool_randomly <- function( + prevalence, + as_proportion +) { + + structure( + distribute_tool_randomly_cpp( + as.double(prevalence), + as.logical(as_proportion) + ), + class = "epiworld_tool_distfun" + ) + +} + +#' @export +#' @rdname tool +#' @details +#' The `distribute_tool_to_set` function creates a distribution function that +#' assigns the tool to a set of agents. +#' @param agents_ids Integer vector. Indices of the agents to which the tool +#' will be assigned. +#' @return +#' - The `distribute_tool_to_set` function returns a distribution function of +#' class `epiworld_tool_distfun`. +distribute_tool_to_set <- function( + agents_ids +) { + + structure( + distribute_tool_to_set_cpp( + agents_ids + ), + class = "epiworld_tool_distfun" + ) +} \ No newline at end of file diff --git a/R/virus.R b/R/virus.R index e49463db..0bcfd472 100644 --- a/R/virus.R +++ b/R/virus.R @@ -30,10 +30,12 @@ #' recovery_rate = 0.99 #' ) #' -#' delta <- virus("Delta Variant", 0, .5, .2, .01) +#' delta <- virus( +#' "Delta Variant", 0, .5, .2, .01, prevalence = 0.3, as_proportion = TRUE +#' ) #' #' # Adding virus and setting/getting virus name -#' add_virus(mseirconn, delta, .3) +#' add_virus(mseirconn, delta) #' set_name_virus(delta, "COVID-19 Strain") #' get_name_virus(delta) #' @@ -41,8 +43,8 @@ #' mseirconn #' #' rm_virus(mseirconn, 0) # Removing the first virus from the model object -#' add_virus_n(mseirconn, delta, 100) # Setting initial count of delta virus -#' # to n = 100 +#' set_distribution_virus(delta, distribute_virus_randomly(100, as_proportion = FALSE)) +#' add_virus(mseirconn, delta) #' #' # Setting parameters for the delta virus manually #' set_prob_infecting(delta, 0.5) @@ -54,12 +56,16 @@ #' # 1: Infected #' # 2: Recovered #' # 3: Dead -#' delta2 <- virus("Delta Variant 2", 0, .5, .2, .01) +#' delta2 <- virus( +#' "Delta Variant 2", 0, .5, .2, .01, prevalence = 0, as_proportion = TRUE +#' ) #' virus_set_state(delta2, 1, 2, 3) #' @export #' @aliases epiworld_virus virus <- function( name, + prevalence, + as_proportion, prob_infecting, recovery_rate = 0.5, prob_death = 0.0, @@ -70,6 +76,8 @@ virus <- function( structure( virus_cpp( name, + prevalence, + as_proportion, prob_infecting, recovery_rate, prob_death, @@ -106,6 +114,17 @@ stopifnot_vfun <- function(vfun) { } } +stopifnot_virus_distfun <- function(virus_distfun) { + if (!inherits(virus_distfun, "epiworld_virus_distfun")) { + stop( + "The -virus_distfun- object must be of class \"epiworld_virus_distfun\". ", + "The object passed to the function is of class(es): ", + paste(class(virus_distfun), collapse = ", ") + ) + } +} + + #' @export #' @details #' The name of the `epiworld_virus` object can be manipulated with the functions @@ -135,18 +154,35 @@ get_name_virus <- function(virus) { #' @rdname virus #' @param model An object of class `epiworld_model`. #' @param virus An object of class `epiworld_virus` -#' @param proportion In the case of `add_virus`, a proportion, otherwise, an integer. +#' @param proportion Deprecated. #' @returns #' - The `add_virus` function does not return a value, instead it adds the #' virus of choice to the model object of class [epiworld_model]. -add_virus <- function(model, virus, proportion) UseMethod("add_virus") +add_virus <- function(model, virus, proportion) { + + if (!missing(proportion)) { + + warning( + "The argument 'proportion' is deprecated and will be removed in ", + "the next version." + ) + + set_distribution_virus( + virus=virus, + distfun=distribute_virus_randomly(proportion, as_proportion = TRUE) + ) + + } + + UseMethod("add_virus") + +} #' @export add_virus.epiworld_model <- function(model, virus, proportion) { stopifnot_virus(virus) - - add_virus_cpp(model, virus, proportion) + add_virus_cpp(model, virus) invisible(model) } @@ -156,7 +192,7 @@ add_virus.epiworld_sir <- function(model, virus, proportion) { stopifnot_virus(virus) virus_set_state(virus, init = 1, end = 2, removed = 2) - invisible(add_virus_cpp(model, virus, proportion)) + invisible(add_virus_cpp(model, virus)) } @@ -165,7 +201,7 @@ add_virus.epiworld_sird <- function(model, virus, proportion) { stopifnot_virus(virus) virus_set_state(virus, init = 1, end = 2, removed = 3) - invisible(add_virus_cpp(model, virus, proportion)) + invisible(add_virus_cpp(model, virus)) } @@ -173,7 +209,7 @@ add_virus.epiworld_sird <- function(model, virus, proportion) { add_virus.epiworld_sirconn <- function(model, virus, proportion) { stopifnot_virus(virus) - add_virus.epiworld_sir(model, virus, proportion) + add_virus.epiworld_sir(model, virus) } @@ -181,7 +217,7 @@ add_virus.epiworld_sirconn <- function(model, virus, proportion) { add_virus.epiworld_sirdconn <- function(model, virus, proportion) { stopifnot_virus(virus) - add_virus.epiworld_sird(model, virus, proportion) + add_virus.epiworld_sird(model, virus) } @@ -191,7 +227,7 @@ add_virus.epiworld_seir <- function(model, virus, proportion) { stopifnot_virus(virus) virus_set_state(virus, init = 1, end = 3, removed = 3) - invisible(add_virus_cpp(model, virus, proportion)) + invisible(add_virus_cpp(model, virus)) } @@ -200,7 +236,7 @@ add_virus.epiworld_seird <- function(model, virus, proportion) { stopifnot_virus(virus) virus_set_state(virus, init = 1, end = 3, removed = 4) - invisible(add_virus_cpp(model, virus, proportion)) + invisible(add_virus_cpp(model, virus)) } @@ -208,7 +244,7 @@ add_virus.epiworld_seird <- function(model, virus, proportion) { add_virus.epiworld_seirconn <- function(model, virus, proportion) { stopifnot_virus(virus) - add_virus.epiworld_seir(model, virus, proportion) + add_virus.epiworld_seir(model, virus) } @@ -216,58 +252,7 @@ add_virus.epiworld_seirconn <- function(model, virus, proportion) { add_virus.epiworld_seirdconn <- function(model, virus, proportion) { stopifnot_virus(virus) - add_virus.epiworld_seird(model, virus, proportion) - -} - -#' @export -#' @rdname virus -#' @returns -#' - The `add_virus_n` function does not return a value, but instead adds a -#' specified number of agents with the virus of choice to the model object -#' of class [epiworld_model]. -#' @param n A positive integer. Initial count of agents to have the virus. -add_virus_n <- function(model, virus, n) UseMethod("add_virus_n") - -#' @export -add_virus_n.epiworld_model <- function(model, virus, n) { - - stopifnot_virus(virus) - invisible(add_virus_n_cpp(model, virus, n)) - -} - -#' @export -add_virus_n.epiworld_sir <- function(model, virus, n) { - - stopifnot_virus(virus) - virus_set_state(virus, init = 1, end = 2, removed = 2) - invisible(add_virus_n_cpp(model, virus, n)) - -} - -#' @export -add_virus_n.epiworld_sirconn <- function(model, virus, n) { - - stopifnot_virus(virus) - add_virus_n.epiworld_sir(model, virus, n) - -} - -#' @export -add_virus_n.epiworld_seir <- function(model, virus, n) { - - stopifnot_virus(virus) - virus_set_state(virus, init = 1, end = 3, removed = 3) - invisible(add_virus_n_cpp(model, virus, n)) - -} - -#' @export -add_virus_n.epiworld_seirconn <- function(model, virus, n) { - - stopifnot_virus(virus) - add_virus_n.epiworld_seir(model, virus, n) + add_virus.epiworld_seird(model, virus) } @@ -536,5 +521,53 @@ set_incubation_fun <- function(virus, model, vfun) { } +#' @export +#' @rdname virus +#' @param distfun An object of class `epiworld_distribution_virus`. +set_distribution_virus <- function(virus, distfun) { + + stopifnot_virus(virus) + stopifnot_virus_distfun(distfun) + invisible(set_distribution_virus_cpp(virus, distfun)) + +} + +#' @export +#' @rdname virus +#' @details The `distribute_virus_randomly` function is a factory function +#' used to randomly distribute the virus in the model. The prevalence can be set +#' as a proportion or as a number of agents. The resulting function can then be +#' passed to `set_distribution_virus`. +#' @param prevalence Numeric scalar. Prevalence of the virus. +#' @param as_proportion Logical scalar. If `TRUE`, the prevalence is set as a +#' proportion of the total number of agents in the model. +#' @return +#' - The `distribute_virus_randomly` function returns a function that can be +#' used to distribute the virus in the model. +distribute_virus_randomly <- function( + prevalence, + as_proportion +) { + + structure( + distribute_virus_randomly_cpp( + as.double(prevalence), + as.logical(as_proportion) + ), + class = "epiworld_virus_distfun" + ) +} +#' @export +#' @rdname virus +#' @param agents_ids Integer vector. Indices of the agents that will receive the +#' virus. +distribute_virus_set <- function(agents_ids) { + + structure( + distribute_virus_to_set_cpp(as.vector(agents_ids)), + class = "epiworld_virus_distfun" + ) + +} \ No newline at end of file diff --git a/README.Rmd b/README.Rmd index 8807652f..ba6d4105 100644 --- a/README.Rmd +++ b/README.Rmd @@ -20,7 +20,7 @@ knitr::opts_chunk$set( [![R-CMD-check](https://github.com/UofUEpiBio/epiworldR/actions/workflows/r.yml/badge.svg)](https://github.com/UofUEpiBio/epiworldR/actions/workflows/r.yml) [![CRANlogs downloads](https://cranlogs.r-pkg.org/badges/grand-total/epiworldR)](https://cran.r-project.org/package=epiworldR) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/UofUEpiBio/epiworldR/blob/master/LICENSE.md) -[![codecov](https://codecov.io/gh/UofUEpiBio/epiworldR/graph/badge.svg?token=ZB8FVLI7GN)](https://codecov.io/gh/UofUEpiBio/epiworldR) +[![codecov](https://codecov.io/gh/UofUEpiBio/epiworldR/graph/badge.svg?token=ZB8FVLI7GN)](https://app.codecov.io/gh/UofUEpiBio/epiworldR) This R package is a wrapper of the C++ library [epiworld](https://github.com/UofUEpiBio/epiworld){target="_blank"}. It provides a general framework for modeling disease transmission using [agent-based models](https://en.wikipedia.org/w/index.php?title=Agent-based_model&oldid=1153634802){target="_blank"}. Some of the main features include: @@ -36,6 +36,16 @@ From the package's description: > A flexible framework for Agent-Based Models (ABM), the epiworldR package provides methods for prototyping disease outbreaks and transmission models using a C++ backend, making it very fast. It supports multiple epidemiological models, including the Susceptible-Infected-Susceptible (SIS), Susceptible-Infected-Removed (SIR), Susceptible-Exposed-Infected-Removed (SEIR), and others, involving arbitrary mitigation policies and multiple-disease models. Users can specify infectiousness/susceptibility rates as a function of agents’ features, providing great complexity for the model dynamics. Furthermore, epiworldR is ideal for simulation studies featuring large populations. +Current available models: + +```{r print-models, echo=FALSE, results='asis'} +models <- list.files(path="R/", pattern = "Model.*.R", full.names = FALSE) |> + gsub(pattern = "(Model.*)\\.R", replacement = "\\1") + +sprintf("%i. `%s`\n", 1:length(models), models) |> + cat() +``` + ## Installation You can install the development version of epiworldR from [GitHub](https://github.com/) with: @@ -52,10 +62,10 @@ install.packages("epiworldR") # Examples -This R package includes several popular epidemiological models including +This R package includes several popular epidemiological models, including [SIS](https://en.wikipedia.org/w/index.php?title=Compartmental_models_in_epidemiology&oldid=1155757336#Variations_on_the_basic_SIR_model){target="_blank"}, [SIR](https://en.wikipedia.org/w/index.php?title=Compartmental_models_in_epidemiology&oldid=1155757336#The_SIR_model){target="_blank"}, and -[SEIR](https://en.wikipedia.org/w/index.php?title=Compartmental_models_in_epidemiology&oldid=1155757336#The_SEIR_model){target="_blank"} using either a fully connected graph (similar to a compartmental model) or a user-defined network. Here are some examples: +[SEIR](https://en.wikipedia.org/w/index.php?title=Compartmental_models_in_epidemiology&oldid=1155757336#The_SEIR_model){target="_blank"} using either a fully connected graph (similar to a compartmental model) or a user-defined network. ## SIR model using a random graph @@ -81,41 +91,50 @@ sir Visualizing the outputs -```{r} +```{r sir-figures} summary(sir) plot(sir) +plot_incidence(sir) ``` ## SEIR model with a fully connected graph The SEIR model is similar to the SIR model but includes an exposed state. Here, we simulate a population of 10,000 agents with a 0.01 prevalence, a 0.6 transmission rate, a 0.5 recovery rate, and 7 days-incubation period. The population is fully connected, meaning agents can transmit the disease to any other agent: -```{r} +```{r seir-conn} model_seirconn <- ModelSEIRCONN( name = "COVID-19", prevalence = 0.01, n = 10000, - contact_rate = 4, + contact_rate = 10, incubation_days = 7, - transmission_rate = 0.6, - recovery_rate = 0.5 -) |> add_virus(virus("COVID-19", 0.01, 0.6, 0.5, 7), .5) + transmission_rate = 0.1, + recovery_rate = 1/7 +) |> add_virus( + virus( + name = "COVID-19", + prevalence = 0.01, + as_proportion = TRUE, + prob_infecting = 0.01, + recovery_rate = 0.6, + prob_death = 0.5, + incubation = 7 + )) set.seed(132) run(model_seirconn, ndays = 100) -model_seirconn +summary(model_seirconn) ``` Computing some key statistics -```{r} +```{r seir-conn-figures} plot(model_seirconn) repnum <- get_reproductive_number(model_seirconn) head(plot(repnum)) -plot_incidence(model_seirconn) head(plot_generation_time(model_seirconn)) ``` @@ -132,7 +151,7 @@ in a small-world network. Each agent is connected to eight other agents. One percent of the population has the virus, with an 80% chance of transmission. Infected individuals recover at a 0.3 rate: -```{r} +```{r logit-model} # Simulating a population of 100,000 agents set.seed(2223) n <- 100000 @@ -212,7 +231,7 @@ nplot(x, edge.curvature = 0, edge.color = "gray", skip.vertex=TRUE) `epiworldR` supports running multiple simulations using the `run_multiple` function. The following code simulates 50 SIR models with 1000 agents each. Each agent is connected to ten other agents. One percent of the population has the virus, with a 90% chance of transmission. Infected individuals recover at a 0.1 rate. The results are saved in a `data.frame`: -```{r} +```{r multiple-example} model_sir <- ModelSIRCONN( name = "COVID-19", prevalence = 0.01, @@ -255,9 +274,9 @@ citation("epiworldR") # Existing Alternatives -Several alternatives to `epiworldR` exist and provide researchers with a range of options, each with its own unique features and strengths, enabling the exploration and analysis of infectious disease dynamics through agent-based modeling. Below is a manually curated table of existing alternatives including ABM [@ABM], abmR [@abmR], cystiSim [@cystiSim], villager [@villager], and RNetLogo [@RNetLogo]. +Several alternatives to `epiworldR` exist and provide researchers with a range of options, each with its own unique features and strengths, enabling the exploration and analysis of infectious disease dynamics through agent-based modeling. Below is a manually curated table of existing alternatives, including ABM [@ABM], abmR [@abmR], cystiSim [@cystiSim], villager [@villager], and RNetLogo [@RNetLogo]. -| Package | Multiple Viruses | Multiple Tools | Multiple Runs | Global Actions | Built-In Epi Models | Dependencies | Activity | +| Package | Multiple Viruses | Multiple Tools | Multiple Runs | Global Actions | Built-In Epi Models | Dependencies | Activity | |:--------|:--------|:--------|:--------|:--------|---------|:--------|:--------| | [**epiworldR**](https://cran.r-project.org/package=epiworldR) | yes | yes | yes | yes | yes | [![status](https://tinyverse.netlify.com/badge/epiworldR)](https://CRAN.R-project.org/package=epiworldR) | [![Activity](https://img.shields.io/github/last-commit/UofUEpiBio/epiworldR)](https://github.com/UofUEpiBio/epiworldR) | | [**ABM**](https://cran.r-project.org/package=ABM) | \- | \- | \- | yes | yes | [![status](https://tinyverse.netlify.com/badge/ABM)](https://CRAN.R-project.org/package=ABM) | [![Activity](https://img.shields.io/github/last-commit/junlingm/ABM)](https://github.com/junlingm/ABM) | @@ -277,4 +296,4 @@ You may want to check out other R packages for agent-based modeling: [`ABM`](htt ## Code of Conduct -Please note that the epiworldR project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. +The epiworldR project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. diff --git a/README.md b/README.md index 210b70a3..270a5067 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ status](https://www.r-pkg.org/badges/version/epiworldR)](https://CRAN.R-project. downloads](https://cranlogs.r-pkg.org/badges/grand-total/epiworldR)](https://cran.r-project.org/package=epiworldR) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/UofUEpiBio/epiworldR/blob/master/LICENSE.md) -[![codecov](https://codecov.io/gh/UofUEpiBio/epiworldR/graph/badge.svg?token=ZB8FVLI7GN)](https://codecov.io/gh/UofUEpiBio/epiworldR) +[![codecov](https://codecov.io/gh/UofUEpiBio/epiworldR/graph/badge.svg?token=ZB8FVLI7GN)](https://app.codecov.io/gh/UofUEpiBio/epiworldR) This R package is a wrapper of the C++ library @@ -43,6 +43,24 @@ From the package’s description: > Furthermore, epiworldR is ideal for simulation studies featuring large > populations. +Current available models: + +1. `ModelDiffNet` +2. `ModelSEIR` +3. `ModelSEIRCONN` +4. `ModelSEIRD` +5. `ModelSEIRDCONN` +6. `ModelSEIRMixing` +7. `ModelSIR` +8. `ModelSIRCONN` +9. `ModelSIRD` +10. `ModelSIRDCONN` +11. `ModelSIRLogit` +12. `ModelSIRMixing` +13. `ModelSIS` +14. `ModelSISD` +15. `ModelSURV` + ## Installation You can install the development version of epiworldR from @@ -60,16 +78,15 @@ install.packages("epiworldR") # Examples -This R package includes several popular epidemiological models including -SIS, SIR, and SEIR using either a fully connected graph (similar -to a compartmental model) or a user-defined network. Here are some -examples: +to a compartmental model) or a user-defined network. ## SIR model using a random graph @@ -97,9 +114,6 @@ sir <- ModelSIR( #> |Running the model... #> |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done. #> | done. -``` - -``` r sir #> ________________________________________________________________________________ @@ -123,15 +137,15 @@ summary(sir) #> Number of entities : 0 #> Days (duration) : 50 (of 50) #> Number of viruses : 1 -#> Last run elapsed t : 165.00ms -#> Last run speed : 30.13 million agents x day / second +#> Last run elapsed t : 147.00ms +#> Last run speed : 33.83 million agents x day / second #> Rewiring : off #> #> Global events: #> (none) #> #> Virus(es): -#> - COVID-19 (baseline prevalence: 1.00%) +#> - COVID-19 #> #> Tool(s): #> (none) @@ -149,13 +163,16 @@ summary(sir) #> - Susceptible 0.91 0.09 0.00 #> - Infected 0.00 0.70 0.30 #> - Recovered 0.00 0.00 1.00 +plot(sir) ``` + + ``` r -plot(sir) +plot_incidence(sir) ``` - + ## SEIR model with a fully connected graph @@ -170,11 +187,20 @@ model_seirconn <- ModelSEIRCONN( name = "COVID-19", prevalence = 0.01, n = 10000, - contact_rate = 4, + contact_rate = 10, incubation_days = 7, - transmission_rate = 0.6, - recovery_rate = 0.5 -) |> add_virus(virus("COVID-19", 0.01, 0.6, 0.5, 7), .5) + transmission_rate = 0.1, + recovery_rate = 1/7 +) |> add_virus( + virus( + name = "COVID-19", + prevalence = 0.01, + as_proportion = TRUE, + prob_infecting = 0.01, + recovery_rate = 0.6, + prob_death = 0.5, + incubation = 7 + )) set.seed(132) run(model_seirconn, ndays = 100) @@ -182,15 +208,48 @@ run(model_seirconn, ndays = 100) #> Running the model... #> ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done. #> done. -``` - -``` r -model_seirconn +summary(model_seirconn) +#> ________________________________________________________________________________ #> ________________________________________________________________________________ -#> Susceptible-Exposed-Infected-Removed (SEIR) (connected) -#> It features 10000 agents, 2 virus(es), and 0 tool(s). -#> The model has 4 states. -#> The final distribution is: 634 Susceptible, 5 Exposed, 0 Infected, and 9361 Recovered. +#> SIMULATION STUDY +#> +#> Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected) +#> Population size : 10000 +#> Agents' data : (none) +#> Number of entities : 0 +#> Days (duration) : 100 (of 100) +#> Number of viruses : 2 +#> Last run elapsed t : 80.00ms +#> Last run speed : 12.38 million agents x day / second +#> Rewiring : off +#> +#> Global events: +#> - Update infected individuals (runs daily) +#> +#> Virus(es): +#> - COVID-19 +#> - COVID-19 +#> +#> Tool(s): +#> (none) +#> +#> Model parameters: +#> - Avg. Incubation days : 7.0000 +#> - Contact rate : 10.0000 +#> - Prob. Recovery : 0.1429 +#> - Prob. Transmission : 0.1000 +#> +#> Distribution of the population at time 100: +#> - (0) Susceptible : 9800 -> 13 +#> - (1) Exposed : 200 -> 0 +#> - (2) Infected : 0 -> 1 +#> - (3) Recovered : 0 -> 9986 +#> +#> Transition Probabilities: +#> - Susceptible 0.94 0.06 0.00 0.00 +#> - Exposed 0.00 0.86 0.14 0.00 +#> - Infected 0.00 0.00 0.85 0.15 +#> - Recovered 0.00 0.00 0.00 1.00 ``` Computing some key statistics @@ -199,7 +258,7 @@ Computing some key statistics plot(model_seirconn) ``` - + ``` r @@ -208,36 +267,27 @@ repnum <- get_reproductive_number(model_seirconn) head(plot(repnum)) ``` - + - #> virus_id virus date avg n sd lb ub - #> 1 0 COVID-19 0 2.762500 80 2.082135 1.0 7.025 - #> 2 0 COVID-19 2 3.250000 24 2.862805 0.0 9.850 - #> 3 0 COVID-19 3 3.294118 17 2.663755 0.4 9.400 - #> 4 0 COVID-19 4 2.666667 18 2.351470 0.0 7.875 - #> 5 0 COVID-19 5 1.878788 33 1.745666 0.0 5.800 - #> 6 0 COVID-19 6 1.794118 34 1.533058 0.0 4.350 + #> virus_id virus date avg n sd lb ub + #> 1 0 COVID-19 0 5.615385 91 4.832228 1.0 17.0 + #> 2 0 COVID-19 2 5.000000 9 3.605551 0.2 10.4 + #> 3 0 COVID-19 3 6.000000 13 5.049752 0.0 13.0 + #> 4 0 COVID-19 4 4.592593 27 3.885469 0.0 12.7 + #> 5 0 COVID-19 5 4.846154 26 4.920913 0.0 14.5 + #> 6 0 COVID-19 6 4.236842 38 3.241906 0.0 12.0 -``` r - -plot_incidence(model_seirconn) -``` - - - -``` r -head(plot_generation_time(model_seirconn)) -``` + head(plot_generation_time(model_seirconn)) - + - #> date avg n sd ci_lower ci_upper virus virus_id - #> 1 2 8.400000 20 6.227274 2.475 24.150 COVID-19 0 - #> 2 3 8.750000 16 6.547264 2.375 21.750 COVID-19 0 - #> 3 4 7.625000 16 5.302515 2.375 19.000 COVID-19 0 - #> 4 5 5.888889 27 3.178453 2.000 12.700 COVID-19 0 - #> 5 6 10.148148 27 5.586410 2.000 21.400 COVID-19 0 - #> 6 7 8.458333 24 6.064717 2.000 20.825 COVID-19 0 + #> date avg n sd ci_lower ci_upper virus virus_id + #> 1 2 7.125000 8 2.474874 2.7 9.825 COVID-19 0 + #> 2 3 8.090909 11 7.203534 2.0 23.750 COVID-19 0 + #> 3 4 6.708333 24 4.338695 2.0 16.425 COVID-19 0 + #> 4 5 7.428571 21 4.738897 2.0 15.500 COVID-19 0 + #> 5 6 7.628571 35 4.173345 2.0 15.300 COVID-19 0 + #> 6 7 6.921053 38 4.675304 2.0 16.150 COVID-19 0 ## SIR Logit @@ -288,13 +338,10 @@ run(model_logit, 50) #> |Running the model... #> |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done. #> | done. -``` - -``` r plot(model_logit) ``` - + ``` r @@ -307,9 +354,6 @@ rn <- get_reproductive_number(model_logit) ) |> prop.table())[,2] #> 0 1 #> 0.12984 0.14201 -``` - -``` r # Looking into the agents get_agents(model_logit) @@ -351,9 +395,6 @@ sir <- ModelSIR( #> |Running the model... #> |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done. #> | done. -``` - -``` r # Transmission network net <- get_transmissions(sir) @@ -362,9 +403,6 @@ net <- get_transmissions(sir) library(epiworldR) library(netplot) #> Loading required package: grid -``` - -``` r x <- igraph::graph_from_edgelist( as.matrix(net[,2:3]) + 1 ) @@ -403,9 +441,6 @@ run_multiple(model_sir, ndays = 100, nsims = 50, saver = saver, nthread = 2) #> _________________________________________________________________________ #> ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done. #> done. -``` - -``` r # Retrieving the results ans <- run_multiple_get_results(model_sir) @@ -418,9 +453,6 @@ head(ans$total_hist) #> 4 1 1 1 Susceptible 977 #> 5 1 1 1 Infected 22 #> 6 1 1 1 Recovered 1 -``` - -``` r head(ans$reproductive) #> sim_num virus_id virus source source_exposure_date rt #> 1 1 0 COVID-19 976 9 0 @@ -429,14 +461,11 @@ head(ans$reproductive) #> 4 1 0 COVID-19 314 9 0 #> 5 1 0 COVID-19 41 9 0 #> 6 1 0 COVID-19 32 9 0 -``` - -``` r plot(ans$reproductive) ``` - + # Tutorials @@ -461,7 +490,7 @@ citation("epiworldR") #> And the actual R package: #> #> Meyer D, Vega Yon G (2024). _epiworldR: Fast Agent-Based Epi Models_. -#> R package version 0.2-1, . +#> R package version 0.3-2, . #> #> To see these entries in BibTeX format, use 'print(, #> bibtex=TRUE)', 'toBibtex(.)', or set @@ -474,17 +503,17 @@ Several alternatives to `epiworldR` exist and provide researchers with a range of options, each with its own unique features and strengths, enabling the exploration and analysis of infectious disease dynamics through agent-based modeling. Below is a manually curated table of -existing alternatives including ABM \[@ABM\], abmR \[@abmR\], cystiSim +existing alternatives, including ABM \[@ABM\], abmR \[@abmR\], cystiSim \[@cystiSim\], villager \[@villager\], and RNetLogo \[@RNetLogo\]. -| Package | Multiple Viruses | Multiple Tools | Multiple Runs | Global Actions | Built-In Epi Models | Dependencies | Activity | -|:--------------------------------------------------------------|:-----------------|:---------------|:--------------|:---------------|---------------------|:---------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------| -| [**epiworldR**](https://cran.r-project.org/package=epiworldR) | yes | yes | yes | yes | yes | [![status](https://tinyverse.netlify.com/badge/epiworldR)](https://CRAN.R-project.org/package=epiworldR) | [![Activity](https://img.shields.io/github/last-commit/UofUEpiBio/epiworldR)](https://github.com/UofUEpiBio/epiworldR) | -| [**ABM**](https://cran.r-project.org/package=ABM) | \- | \- | \- | yes | yes | [![status](https://tinyverse.netlify.com/badge/ABM)](https://CRAN.R-project.org/package=ABM) | [![Activity](https://img.shields.io/github/last-commit/junlingm/ABM)](https://github.com/junlingm/ABM) | -| [**abmR**](https://cran.r-project.org/package=abmR) | \- | \- | yes | \- | \- | [![status](https://tinyverse.netlify.com/badge/abmR)](https://CRAN.R-project.org/package=abmR) | [![Activity](https://img.shields.io/github/last-commit/bgoch5/abmR)](https://github.com/bgoch5/abmR) | -| [**cystiSim**](https://cran.r-project.org/package=cystiSim) | \- | yes | yes | \- | \- | [![status](https://tinyverse.netlify.com/badge/cystiSim)](https://CRAN.R-project.org/package=cystiSim) | [![Activity](https://img.shields.io/github/last-commit/brechtdv/cystiSim)](https://github.com/brechtdv/cystiSim) | -| [**villager**](https://cran.r-project.org/package=villager) | \- | \- | \- | yes | \- | [![status](https://tinyverse.netlify.com/badge/villager)](https://CRAN.R-project.org/package=villager) | [![Activity](https://img.shields.io/github/last-commit/zizroc/villager)](https://github.com/zizroc/villager) | -| [**RNetLogo**](https://cran.r-project.org/package=RNetLogo) | \- | yes | yes | yes | \- | [![status](https://tinyverse.netlify.com/badge/RNetLogo)](https://CRAN.R-project.org/package=RNetLogo) | [![Activity](https://img.shields.io/github/last-commit/cran/RNetLogo)](https://github.com/cran/RNetLogo) | +| Package | Multiple Viruses | Multiple Tools | Multiple Runs | Global Actions | Built-In Epi Models | Dependencies | Activity | +|:---|:---|:---|:---|:---|----|:---|:---| +| [**epiworldR**](https://cran.r-project.org/package=epiworldR) | yes | yes | yes | yes | yes | [![status](https://tinyverse.netlify.com/badge/epiworldR)](https://CRAN.R-project.org/package=epiworldR) | [![Activity](https://img.shields.io/github/last-commit/UofUEpiBio/epiworldR)](https://github.com/UofUEpiBio/epiworldR) | +| [**ABM**](https://cran.r-project.org/package=ABM) | \- | \- | \- | yes | yes | [![status](https://tinyverse.netlify.com/badge/ABM)](https://CRAN.R-project.org/package=ABM) | [![Activity](https://img.shields.io/github/last-commit/junlingm/ABM)](https://github.com/junlingm/ABM) | +| [**abmR**](https://cran.r-project.org/package=abmR) | \- | \- | yes | \- | \- | [![status](https://tinyverse.netlify.com/badge/abmR)](https://CRAN.R-project.org/package=abmR) | [![Activity](https://img.shields.io/github/last-commit/bgoch5/abmR)](https://github.com/bgoch5/abmR) | +| [**cystiSim**](https://cran.r-project.org/package=cystiSim) | \- | yes | yes | \- | \- | [![status](https://tinyverse.netlify.com/badge/cystiSim)](https://CRAN.R-project.org/package=cystiSim) | [![Activity](https://img.shields.io/github/last-commit/brechtdv/cystiSim)](https://github.com/brechtdv/cystiSim) | +| [**villager**](https://cran.r-project.org/package=villager) | \- | \- | \- | yes | \- | [![status](https://tinyverse.netlify.com/badge/villager)](https://CRAN.R-project.org/package=villager) | [![Activity](https://img.shields.io/github/last-commit/zizroc/villager)](https://github.com/zizroc/villager) | +| [**RNetLogo**](https://cran.r-project.org/package=RNetLogo) | \- | yes | yes | yes | \- | [![status](https://tinyverse.netlify.com/badge/RNetLogo)](https://CRAN.R-project.org/package=RNetLogo) | [![Activity](https://img.shields.io/github/last-commit/cran/RNetLogo)](https://github.com/cran/RNetLogo) | # Other ABM R packages @@ -502,7 +531,6 @@ target="_blank">RNetLogo. ## Code of Conduct -Please note that the epiworldR project is released with a [Contributor -Code of +The epiworldR project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. diff --git a/docker/Makefile b/docker/Makefile index dffd5d55..b161464e 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -1,2 +1,7 @@ all: - docker build -t uofuepibio/epiworldr:debug -f Dockerfile . \ No newline at end of file + docker build -t uofuepibio/epiworldr:debug -f Dockerfile . + +podman: + podman build -t uofuepibio/epiworldr:debug -f Dockerfile . + +.PHONY: all podman \ No newline at end of file diff --git a/inst/include/epiworld/agent-bones.hpp b/inst/include/epiworld/agent-bones.hpp index 1e83c069..c4e75dea 100644 --- a/inst/include/epiworld/agent-bones.hpp +++ b/inst/include/epiworld/agent-bones.hpp @@ -102,9 +102,9 @@ class Agent { std::vector< ToolPtr > tools; epiworld_fast_uint n_tools = 0u; - std::vector< Agent * > sampled_agents; + std::vector< Agent * > sampled_agents = {}; size_t sampled_agents_n = 0u; - std::vector< size_t > sampled_agents_left; + std::vector< size_t > sampled_agents_left = {}; size_t sampled_agents_left_n = 0u; int date_last_build_sample = -99; @@ -218,6 +218,7 @@ class Agent { int get_id() const; ///< Id of the individual VirusPtr & get_virus(); + const VirusPtr & get_virus() const; ToolPtr & get_tool(int i); Tools get_tools(); @@ -263,6 +264,8 @@ class Agent { bool has_virus(epiworld_fast_uint t) const; bool has_virus(std::string name) const; bool has_virus(const Virus & v) const; + bool has_entity(epiworld_fast_uint t) const; + bool has_entity(std::string name) const; void print(Model * model, bool compressed = false) const; diff --git a/inst/include/epiworld/agent-events-meat.hpp b/inst/include/epiworld/agent-events-meat.hpp index 8da043fb..cfa9c275 100644 --- a/inst/include/epiworld/agent-events-meat.hpp +++ b/inst/include/epiworld/agent-events-meat.hpp @@ -192,7 +192,7 @@ inline void default_add_entity(Event & a, Model *) if (p->get_n_entities() > e->size()) // Slower search through the agent { for (size_t i = 0u; i < e->size(); ++i) - if(e->operator[](i)->get_id() == p->get_id()) + if(static_cast(e->operator[](i)) == p->get_id()) throw std::logic_error("An entity cannot be reassigned to an agent."); } else // Slower search through the entity @@ -253,11 +253,15 @@ inline void default_rm_entity(Event & a, Model * m) // When we move the end entity to the new location, the // moved entity needs to reflect the change, i.e., where the // entity will now be located in the agent - size_t agent_location_in_last_entity = p->entities_locations[p->n_entities]; - Entity * last_entity = &m->get_entities()[p->entities[p->n_entities]]; ///< Last entity of the agent + size_t agent_location_in_last_entity = + p->entities_locations[p->n_entities]; + + Entity * last_entity = + &m->get_entity(p->entities[p->n_entities]); ///< Last entity of the agent // The end entity will be located where the removed was - last_entity->agents_location[agent_location_in_last_entity] = idx_entity_in_agent; + last_entity->agents_location[agent_location_in_last_entity] = + idx_entity_in_agent; // We now make the swap std::swap( @@ -274,10 +278,13 @@ inline void default_rm_entity(Event & a, Model * m) // moved agent needs to reflect the change, i.e., where the // agent will now be located in the entity size_t entity_location_in_last_agent = e->agents_location[e->n_agents]; - Agent * last_agent = &m->get_agents()[e->agents[e->n_agents]]; ///< Last agent of the entity + + Agent * last_agent = + &m->get_agents()[e->agents[e->n_agents]]; ///< Last agent of the entity // The end entity will be located where the removed was - last_agent->entities_locations[entity_location_in_last_agent] = idx_agent_in_entity; + last_agent->entities_locations[entity_location_in_last_agent] = + idx_agent_in_entity; // We now make the swap std::swap( diff --git a/inst/include/epiworld/agent-meat.hpp b/inst/include/epiworld/agent-meat.hpp index a5006b5d..27d4785e 100644 --- a/inst/include/epiworld/agent-meat.hpp +++ b/inst/include/epiworld/agent-meat.hpp @@ -242,7 +242,7 @@ inline void Agent::add_entity( -1, -1 ); - // default_add_entity(a, model); /* passing model makes nothing */ + default_add_entity(a, model); /* passing model makes nothing */ } @@ -332,12 +332,19 @@ inline void Agent::rm_entity( "There is entity to remove here!" ); - CHECK_COALESCE_(state_new, model->entities[entity_idx].state_post, state); - CHECK_COALESCE_(queue, model->entities[entity_idx].queue_post, Queue::NoOne); + CHECK_COALESCE_(state_new, model->get_entity(entity_idx).state_post, state); + CHECK_COALESCE_(queue, model->get_entity(entity_idx).queue_post, Queue::NoOne); model->events_add( - this, nullptr, nullptr, model->entities[entity_idx], state_new, queue, - default_rm_entity, entities_locations[entity_idx], entity_idx + this, + nullptr, + nullptr, + &model->get_entity(entity_idx), + state_new, + queue, + default_rm_entity, + entities_locations[entity_idx], + entity_idx ); } @@ -354,22 +361,35 @@ inline void Agent::rm_entity( int entity_idx = -1; for (size_t i = 0u; i < n_entities; ++i) { - if (entities[i] == entity->get_id()) + if (static_cast(entities[i]) == entity.get_id()) + { entity_idx = i; + break; + } } if (entity_idx == -1) throw std::logic_error( - "The agent " + std::to_string(id) + " is not associated with entity \"" + - entity.get_name() + "\"." + std::string("The agent ") + + std::to_string(id) + + std::string(" is not associated with entity \"") + + entity.get_name() + + std::string("\".") ); CHECK_COALESCE_(state_new, entity.state_post, state); CHECK_COALESCE_(queue, entity.queue_post, Queue::NoOne); model->events_add( - this, nullptr, nullptr, entities[entity_idx], state_new, queue, - default_rm_entity, entities_locations[entity_idx], entity_idx + this, + nullptr, + nullptr, + &model->entities[entity.get_id()], + state_new, + queue, + default_rm_entity, + entities_locations[entity_idx], + entity_idx ); } @@ -435,6 +455,11 @@ inline VirusPtr & Agent::get_virus() { return virus; } +template +inline const VirusPtr & Agent::get_virus() const { + return virus; +} + template inline Tools Agent::get_tools() { @@ -610,6 +635,10 @@ inline void Agent::reset() this->tools.clear(); n_tools = 0u; + this->entities.clear(); + this->entities_locations.clear(); + this->n_entities = 0u; + this->state = 0u; this->state_prev = 0u; @@ -677,6 +706,30 @@ inline bool Agent::has_virus(const Virus & virus) const } +template +inline bool Agent::has_entity(epiworld_fast_uint t) const +{ + + for (auto & entity : entities) + if (entity == t) + return true; + + return false; + +} + +template +inline bool Agent::has_entity(std::string name) const +{ + + for (auto & entity : entities) + if (model->get_entity(entity).get_name() == name) + return true; + + return false; + +} + template inline void Agent::print( Model * model, @@ -788,19 +841,25 @@ inline const Entities_const Agent::get_entities() const template inline const Entity & Agent::get_entity(size_t i) const { + if (n_entities == 0) + throw std::range_error("Agent id " + std::to_string(id) + " has no entities."); + if (i >= n_entities) throw std::range_error("Trying to get to an agent's entity outside of the range."); - return model->entities[entities[i]]; + return model->get_entity(entities[i]); } template inline Entity & Agent::get_entity(size_t i) { + if (n_entities == 0) + throw std::range_error("Agent id " + std::to_string(id) + " has no entities."); + if (i >= n_entities) throw std::range_error("Trying to get to an agent's entity outside of the range."); - return model->entities[entities[i]]; + return model->get_entity(entities[i]); } template diff --git a/inst/include/epiworld/agentssample-bones.hpp b/inst/include/epiworld/agentssample-bones.hpp index c8462c58..e0d1285a 100644 --- a/inst/include/epiworld/agentssample-bones.hpp +++ b/inst/include/epiworld/agentssample-bones.hpp @@ -30,10 +30,10 @@ class AgentsSample { size_t sample_size = 0u; - std::vector< Agent* > * agents = nullptr; ///< Pointer to sample of agents + std::vector< Agent* >* agents = nullptr; ///< Pointer to sample of agents size_t * agents_n = nullptr; ///< Size of sample of agents - std::vector< size_t > * agents_left = nullptr; ///< Pointer to agents left (iota) + std::vector< size_t >* agents_left = nullptr; ///< Pointer to agents left (iota) size_t * agents_left_n = nullptr; ///< Size of agents left Model * model = nullptr; ///< Extracts runif() and (if the case) population. @@ -49,7 +49,7 @@ class AgentsSample { public: // Not available (for now) - AgentsSample() = delete; ///< Default constructor + AgentsSample() = delete; ///< Default constructor AgentsSample(const AgentsSample & a) = delete; ///< Copy constructor AgentsSample(AgentsSample && a) = delete; ///< Move constructor @@ -60,13 +60,17 @@ class AgentsSample { ); AgentsSample( - Model * model, Entity & entity_, size_t n, + Model * model, + Entity & entity_, + size_t n, std::vector< size_t > states_ = {}, bool truncate = false ); AgentsSample( - Model * model, Agent & agent_, size_t n, + Model * model, + Agent & agent_, + size_t n, std::vector< size_t > states_ = {}, bool truncate = false ); @@ -187,9 +191,6 @@ inline AgentsSample::AgentsSample( agents = &agent_.sampled_agents; agents_n = &agent_.sampled_agents_n; - agents_left = &agent_.sampled_agents_left; - agents_left_n = &agent_.sampled_agents_left_n; - // Computing the cumulative sum of counts across entities size_t agents_in_entities = 0; Entities entities_a = agent->get_entities(); @@ -228,31 +229,37 @@ inline AgentsSample::AgentsSample( agents->resize(n); size_t i_obs = 0u; - for (size_t i = 0u; i < agents_in_entities; ++i) + for (size_t i = 0u; i < sample_size; ++i) { + + // Sampling a single agent from the set of entities int jth = std::floor(model->runif() * agents_in_entities); for (size_t e = 0u; e < cum_agents_count.size(); ++e) { + // Are we in the limit? if (jth <= cum_agents_count[e]) { size_t agent_idx = 0u; if (e == 0) // From the first group - agent_idx = entities_a[e][jth]; + agent_idx = entities_a[e][jth]->get_id(); else - agent_idx = entities_a[e][jth - cum_agents_count[e - 1]]; + agent_idx = entities_a[e][jth - cum_agents_count[e - 1]]->get_id(); - // Getting the state - size_t state = model->population[agent_idx].get_state(); // Checking if states was specified if (states.size()) { + + // Getting the state + size_t state = model->population[agent_idx].get_state(); + if (std::find(states.begin(), states.end(), state) != states.end()) continue; + } - agents->operator[](i_obs++) = agent_idx; + agents->operator[](i_obs++) = &(model->population[agent_idx]); break; } diff --git a/inst/include/epiworld/config.hpp b/inst/include/epiworld/config.hpp index 852243d9..d9251061 100644 --- a/inst/include/epiworld/config.hpp +++ b/inst/include/epiworld/config.hpp @@ -75,7 +75,7 @@ using MixerFun = std::function*,VirusPtr,Model template using MutFun = std::function*,Virus&,Model*)>; -template +template using PostRecoveryFun = std::function*,Virus&,Model*)>; template @@ -90,25 +90,25 @@ using GlobalFun = std::function*)>; template struct Event; -template -using ActionFun = std::function&,Model*)>; +template +using EventFun = std::function&,Model*)>; /** * @brief Decides how to distribute viruses at initialization */ -template +template using VirusToAgentFun = std::function&,Model*)>; /** * @brief Decides how to distribute tools at initialization */ -template +template using ToolToAgentFun = std::function&,Model*)>; /** * @brief Decides how to distribute entities at initialization */ -template +template using EntityToAgentFun = std::function&,Model*)>; /** @@ -116,7 +116,7 @@ using EntityToAgentFun = std::function&,Model*)>; * * @tparam TSeq */ -template +template struct Event { Agent * agent; VirusPtr virus; @@ -124,7 +124,7 @@ struct Event { Entity * entity; epiworld_fast_int new_state; epiworld_fast_int queue; - ActionFun call; + EventFun call; int idx_agent; int idx_object; public: @@ -151,7 +151,7 @@ struct Event { Entity * entity_, epiworld_fast_int new_state_, epiworld_fast_int queue_, - ActionFun call_, + EventFun call_, int idx_agent_, int idx_object_ ) : agent(agent_), virus(virus_), tool(tool_), entity(entity_), diff --git a/inst/include/epiworld/entity-bones.hpp b/inst/include/epiworld/entity-bones.hpp index 6e2ac160..b36190f5 100644 --- a/inst/include/epiworld/entity-bones.hpp +++ b/inst/include/epiworld/entity-bones.hpp @@ -24,8 +24,6 @@ class Entity { friend void default_add_entity(Event & a, Model * m); friend void default_rm_entity(Event & a, Model * m); private: - - Model * model; int id = -1; std::vector< size_t > agents; ///< Vector of agents @@ -49,7 +47,7 @@ class Entity { ///@} int max_capacity = -1; - std::string entity_name = "Unknown entity"; + std::string entity_name = "Unnamed entity"; std::vector< epiworld_double > location = {0.0}; ///< An arbitrary vector for location @@ -59,18 +57,30 @@ class Entity { epiworld_fast_int queue_init = 0; ///< Change of state when added to agent. epiworld_fast_int queue_post = 0; ///< Change of state when removed from agent. + EntityToAgentFun dist_fun = nullptr; + public: - // Entity() = delete; - // Entity(Entity & e) = delete; - // Entity(const Entity & e); - // Entity(Entity && e); - Entity(std::string name) : entity_name(name) {}; - // Entity & operator=(const Entity & e); + /** + * @brief Constructs an Entity object. + * + * This constructor initializes an Entity object with the specified parameters. + * + * @param name The name of the entity. + * @param fun A function pointer to a function that maps the entity to an agent. + */ + Entity( + std::string name, + EntityToAgentFun fun = nullptr + ) : + entity_name(name), + dist_fun(fun) + {}; + void add_agent(Agent & p, Model * model); void add_agent(Agent * p, Model * model); - void rm_agent(size_t idx); + void rm_agent(size_t idx, Model * model); size_t size() const noexcept; void set_location(std::vector< epiworld_double > loc); std::vector< epiworld_double > & get_location(); @@ -81,7 +91,7 @@ class Entity { typename std::vector< Agent * >::const_iterator begin() const; typename std::vector< Agent * >::const_iterator end() const; - Agent * operator[](size_t i); + size_t operator[](size_t i); int get_id() const noexcept; const std::string & get_name() const noexcept; @@ -96,6 +106,20 @@ class Entity { bool operator==(const Entity & other) const; bool operator!=(const Entity & other) const {return !operator==(other);}; + /** + * @name Entity distribution + * + * @details These functions are used for distributing agents among entities. + * The idea is to have a flexible way of distributing agents among entities. + + */ + void distribute(Model * model); + + std::vector< size_t > & get_agents(); + + void print() const; + void set_distribution(EntityToAgentFun fun); + }; diff --git a/inst/include/epiworld/entity-distribute-meat.hpp b/inst/include/epiworld/entity-distribute-meat.hpp new file mode 100644 index 00000000..d589a368 --- /dev/null +++ b/inst/include/epiworld/entity-distribute-meat.hpp @@ -0,0 +1,157 @@ +#ifndef EPIWORLD_ENTITY_DISTRIBUTE_MEAT_HPP +#define EPIWORLD_ENTITY_DISTRIBUTE_MEAT_HPP + + +template +/** + * Distributes an entity to unassigned agents in the model. + * + * @param prevalence The proportion of agents to distribute the entity to. + * @param as_proportion Flag indicating whether the prevalence is a proportion + * @param to_unassigned Flag indicating whether to distribute the entity only + * to unassigned agents. + * @return An EntityToAgentFun object that distributes the entity to unassigned + * agents. + */ +inline EntityToAgentFun distribute_entity_randomly( + epiworld_double prevalence, + bool as_proportion, + bool to_unassigned +) +{ + + return [prevalence, as_proportion, to_unassigned]( + Entity & e, Model * m + ) -> void { + + + // Preparing the sampling space + std::vector< size_t > idx; + if (to_unassigned) + { + for (const auto & a: m->get_agents()) + if (a.get_n_entities() == 0) + idx.push_back(a.get_id()); + } + else + { + + for (const auto & a: m->get_agents()) + idx.push_back(a.get_id()); + + } + + size_t n = idx.size(); + + // Figuring out how many to sample + int n_to_sample; + if (as_proportion) + { + n_to_sample = static_cast(std::floor(prevalence * n)); + if (n_to_sample > static_cast(n)) + --n_to_sample; + + } else + { + n_to_sample = static_cast(prevalence); + if (n_to_sample > static_cast(n)) + throw std::range_error("There are only " + std::to_string(n) + + " individuals in the population. Cannot add the entity to " + + std::to_string(n_to_sample)); + } + + int n_left = n; + for (int i = 0; i < n_to_sample; ++i) + { + int loc = static_cast( + floor(m->runif() * n_left--) + ); + + // Correcting for possible overflow + if ((loc > 0) && (loc >= n_left)) + loc = n_left - 1; + + m->get_agent(idx[loc]).add_entity(e, m); + + std::swap(idx[loc], idx[n_left]); + + } + + }; + +} + +template +/** + * Distributes an entity to a range of agents. + * + * @param from The starting index of the range. + * @param to The ending index of the range. + * @param to_unassigned Flag indicating whether to distribute the entity only + * to unassigned agents. + * @return A lambda function that distributes the entity to the specified range + * of agents. + */ +inline EntityToAgentFun distribute_entity_to_range( + int from, + int to, + bool to_unassigned = false + ) { + + if (to_unassigned) + { + + return [from, to](Entity & e, Model * m) -> void { + + auto & agents = m->get_agents(); + for (size_t i = from; i < to; ++i) + { + if (agents[i].get_n_entities() == 0) + e.add_agent(&agents[i], m); + else + throw std::logic_error( + "Agent " + std::to_string(i) + " already has an entity." + ); + } + + return; + + }; + + } + else + { + + return [from, to](Entity & e, Model * m) -> void { + + auto & agents = m->get_agents(); + for (size_t i = from; i < to; ++i) + { + e.add_agent(&agents[i], m); + } + + return; + + }; + + } +} + + +template +inline EntityToAgentFun distribute_entity_to_set( + std::vector< size_t > & idx + ) { + + return [idx](Entity & e, Model * m) -> void { + + for (const auto & i: idx) + { + e.add_agent(&m->get_agent(i), m); + } + + }; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/entity-meat.hpp b/inst/include/epiworld/entity-meat.hpp index 4c5a30d4..7c1200f6 100644 --- a/inst/include/epiworld/entity-meat.hpp +++ b/inst/include/epiworld/entity-meat.hpp @@ -23,7 +23,7 @@ inline void Entity::add_agent( } template -inline void Entity::rm_agent(size_t idx) +inline void Entity::rm_agent(size_t idx, Model * model) { if (idx >= n_agents) throw std::out_of_range( @@ -31,7 +31,7 @@ inline void Entity::rm_agent(size_t idx) " out of " + std::to_string(n_agents) ); - model->population[idx].rm_entity(*this); + model->get_agents()[agents[idx]].rm_entity(*this, model); return; } @@ -89,12 +89,15 @@ inline typename std::vector< Agent * >::const_iterator Entity::end() } template -inline Agent * Entity::operator[](size_t i) +size_t Entity::operator[](size_t i) { if (n_agents <= i) - throw std::logic_error("There are not that many agents in this entity."); + throw std::logic_error( + "There are not that many agents in this entity. " + + std::to_string(n_agents) + " <= " + std::to_string(i) + ); - return &model->get_agents()[i]; + return i; } template @@ -164,6 +167,13 @@ inline void Entity::reset() sampled_agents_n = 0u; sampled_agents_left.clear(); sampled_agents_left_n = 0u; + + this->agents.clear(); + this->n_agents = 0u; + this->agents_location.clear(); + + return; + } template @@ -216,4 +226,41 @@ inline bool Entity::operator==(const Entity & other) const } +template +inline void Entity::distribute(Model * model) +{ + + if (dist_fun) + { + + dist_fun(*this, model); + + } + +} + +template +inline std::vector< size_t > & Entity::get_agents() +{ + return agents; +} + +template +inline void Entity::print() const +{ + + printf_epiworld( + "Entity '%s' (id %i) with %i agents.\n", + this->entity_name.c_str(), + static_cast(id), + static_cast(n_agents) + ); +} + +template +inline void Entity::set_distribution(EntityToAgentFun fun) +{ + dist_fun = fun; +} + #endif \ No newline at end of file diff --git a/inst/include/epiworld/epiworld-macros.hpp b/inst/include/epiworld/epiworld-macros.hpp index 0fdf87b3..149a4961 100644 --- a/inst/include/epiworld/epiworld-macros.hpp +++ b/inst/include/epiworld/epiworld-macros.hpp @@ -101,4 +101,11 @@ [](epiworld::Model* m) -> void +#define EPI_NEW_ENTITYTOAGENTFUN(funname,tseq) inline void \ + (funname)(epiworld::Entity & e, epiworld::Model * m) + +#define EPI_NEW_ENTITYTOAGENTFUN_LAMBDA(funname,tseq) \ + epiworld::EntityToAgentFun funname = \ + [](epiworld::Entity & e, epiworld::Model * m) -> void + #endif diff --git a/inst/include/epiworld/epiworld.hpp b/inst/include/epiworld/epiworld.hpp index 5d7d7bdc..7e8f6624 100644 --- a/inst/include/epiworld/epiworld.hpp +++ b/inst/include/epiworld/epiworld.hpp @@ -16,11 +16,10 @@ #ifndef EPIWORLD_HPP #define EPIWORLD_HPP - /* Versioning */ #define EPIWORLD_VERSION_MAJOR 0 -#define EPIWORLD_VERSION_MINOR 1 -#define EPIWORLD_VERSION_PATCH 1 +#define EPIWORLD_VERSION_MINOR 3 +#define EPIWORLD_VERSION_PATCH 2 static const int epiworld_version_major = EPIWORLD_VERSION_MAJOR; static const int epiworld_version_minor = EPIWORLD_VERSION_MINOR; @@ -61,14 +60,17 @@ namespace epiworld { #include "viruses-bones.hpp" #include "virus-bones.hpp" + #include "virus-distribute-meat.hpp" #include "virus-meat.hpp" #include "tools-bones.hpp" #include "tool-bones.hpp" + #include "tool-distribute-meat.hpp" #include "tool-meat.hpp" #include "entity-bones.hpp" + #include "entity-distribute-meat.hpp" #include "entity-meat.hpp" #include "entities-bones.hpp" @@ -79,6 +81,9 @@ namespace epiworld { #include "agentssample-bones.hpp" + #include "groupsampler-bones.hpp" + #include "groupsampler-meat.hpp" + #include "models/models.hpp" } diff --git a/inst/include/epiworld/groupsampler-bones.hpp b/inst/include/epiworld/groupsampler-bones.hpp new file mode 100644 index 00000000..88ec690a --- /dev/null +++ b/inst/include/epiworld/groupsampler-bones.hpp @@ -0,0 +1,59 @@ +#ifndef GROUPSAMPLER_BONES_HPP +#define GROUPSAMPLER_BONES_HPP + +/** + * @brief Weighted sampling of groups + */ +template +class GroupSampler { + +private: + + std::vector< double > contact_matrix; ///< Contact matrix between groups + std::vector< size_t > group_sizes; ///< Sizes of the groups + std::vector< double > cumulate; ///< Cumulative sum of the contact matrix (row-major for faster access) + + /** + * @brief Get the index of the contact matrix + * + * The matrix is a vector stored in column-major order. + * + * @param i Index of the row + * @param j Index of the column + * @return Index of the contact matrix + */ + inline int idx(const int i, const int j, bool rowmajor = false) const + { + + if (rowmajor) + return i * group_sizes.size() + j; + + return j * group_sizes.size() + i; + + } + +public: + + GroupSampler() {}; + + GroupSampler( + const std::vector< double > & contact_matrix_, + const std::vector< size_t > & group_sizes_, + bool normalize = true + ); + + int sample_1( + Model * model, + const int origin_group + ); + + void sample_n( + Model * model, + std::vector< int > & sample, + const int origin_group, + const int nsamples + ); + +}; + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/groupsampler-meat.hpp b/inst/include/epiworld/groupsampler-meat.hpp new file mode 100644 index 00000000..6e960965 --- /dev/null +++ b/inst/include/epiworld/groupsampler-meat.hpp @@ -0,0 +1,84 @@ +#ifndef GROUPSAMPLER_MEAT_HPP +#define GROUPSAMPLER_MEAT_HPP + +template +inline GroupSampler::GroupSampler( + const std::vector< double > & contact_matrix_, + const std::vector< size_t > & group_sizes_, + bool normalize + ): contact_matrix(contact_matrix_), group_sizes(group_sizes_) { + + + this->cumulate.resize(contact_matrix.size()); + std::fill(cumulate.begin(), cumulate.end(), 0.0); + + // Cumulative sum + for (size_t j = 0; j < group_sizes.size(); ++j) + { + for (size_t i = 0; i < group_sizes.size(); ++i) + cumulate[idx(i, j, true)] += + cumulate[idx(i, j - 1, true)] + + contact_matrix[idx(i, j)]; + } + + if (normalize) + { + for (size_t i = 0; i < group_sizes.size(); ++i) + { + double sum = 0.0; + for (size_t j = 0; j < group_sizes.size(); ++j) + sum += contact_matrix[idx(i, j, true)]; + for (size_t j = 0; j < group_sizes.size(); ++j) + contact_matrix[idx(i, j, true)] /= sum; + } + } + + }; + +template +int GroupSampler::sample_1( + Model * model, + const int origin_group + ) +{ + + // Random number + double r = model->runif(); + + // Finding the group + size_t j = 0; + while (r > cumulate[idx(origin_group, j, true)]) + ++j; + + // Adjusting the prob + r = r - (j == 0 ? 0.0 : cumulate[idx(origin_group, j - 1, true)]); + + int res = static_cast( + std::floor(r * group_sizes[j]) + ); + + // Making sure we are not picking outside of the group + if (res >= static_cast(group_sizes[j])) + res = static_cast(group_sizes[j]) - 1; + + return model->get_entities()[j][res]->get_id(); + +} + +template +void GroupSampler::sample_n( + Model * model, + std::vector< int > & sample, + const int origin_group, + const int nsamples +) +{ + + for (int i = 0; i < nsamples; ++i) + sample[i] = sample_1(model, origin_group); + + return; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/model-bones.hpp b/inst/include/epiworld/model-bones.hpp index 3b51e2a8..4029f12a 100644 --- a/inst/include/epiworld/model-bones.hpp +++ b/inst/include/epiworld/model-bones.hpp @@ -135,14 +135,7 @@ class Model { bool directed = false; std::vector< VirusPtr > viruses = {}; - std::vector< epiworld_double > prevalence_virus = {}; ///< Initial prevalence_virus of each virus - std::vector< bool > prevalence_virus_as_proportion = {}; - std::vector< VirusToAgentFun > viruses_dist_funs = {}; - std::vector< ToolPtr > tools = {}; - std::vector< epiworld_double > prevalence_tool = {}; - std::vector< bool > prevalence_tool_as_proportion = {}; - std::vector< ToolToAgentFun > tools_dist_funs = {}; std::vector< Entity > entities = {}; std::vector< Entity > entities_backup = {}; @@ -183,7 +176,7 @@ class Model { void dist_tools(); void dist_virus(); - // void dist_entities(); + void dist_entities(); std::chrono::time_point time_start; std::chrono::time_point time_end; @@ -227,7 +220,7 @@ class Model { Entity * entity_, epiworld_fast_int new_state_, epiworld_fast_int queue_, - ActionFun call_, + EventFun call_, int idx_agent_, int idx_object_ ); @@ -258,6 +251,7 @@ class Model { std::vector array_double_tmp; std::vector * > array_virus_tmp; + std::vector< int > array_int_tmp; Model(); Model(const Model & m); @@ -337,16 +331,12 @@ class Model { * indicating number of individuals. */ ///@{ - void add_virus(Virus & v, epiworld_double preval); - void add_virus_n(Virus & v, epiworld_fast_uint preval); - void add_virus_fun(Virus & v, VirusToAgentFun fun); - void add_tool(Tool & t, epiworld_double preval); - void add_tool_n(Tool & t, epiworld_fast_uint preval); - void add_tool_fun(Tool & t, ToolToAgentFun fun); + void add_virus(Virus & v); + void add_tool(Tool & t); void add_entity(Entity e); void rm_virus(size_t virus_pos); void rm_tool(size_t tool_pos); - void rm_entity(size_t entity_pos); + void rm_entity(size_t entity_id); ///@} /** @@ -360,6 +350,20 @@ class Model { * @param skip How many rows to skip. */ void load_agents_entities_ties(std::string fn, int skip); + + /** + * @brief Associate agents-entities from data + */ + void load_agents_entities_ties( + const std::vector & agents_ids, + const std::vector & entities_ids + ); + + void load_agents_entities_ties( + const int * agents_id, + const int * entities_id, + size_t n + ); /** * @name Accessing population of the model @@ -391,6 +395,8 @@ class Model { std::vector< Agent > & get_agents(); ///< Returns a reference to the vector of agents. + Agent & get_agent(size_t i); + std::vector< epiworld_fast_uint > get_agents_states() const; ///< Returns a vector with the states of the agents. std::vector< Viruses_const > get_agents_viruses() const; ///< Returns a const vector with the viruses of the agents. @@ -399,6 +405,8 @@ class Model { std::vector< Entity > & get_entities(); + Entity & get_entity(size_t entity_id, int * entity_pos = nullptr); + Model & agents_smallworld( epiworld_fast_uint n = 1000, epiworld_fast_uint k = 5, @@ -525,8 +533,6 @@ class Model { virtual void reset(); const Model & print(bool lite = false) const; - Model && clone() const; - /** * @name Manage state (states) in the model * @@ -685,8 +691,6 @@ class Model { ///@} const std::vector< VirusPtr > & get_viruses() const; - const std::vector< epiworld_double > & get_prevalence_virus() const; - const std::vector< bool > & get_prevalence_virus_as_proportion() const; const std::vector< ToolPtr > & get_tools() const; Virus & get_virus(size_t id); Tool & get_tool(size_t id); diff --git a/inst/include/epiworld/model-meat-print.hpp b/inst/include/epiworld/model-meat-print.hpp index f17f0c00..1a66a85d 100644 --- a/inst/include/epiworld/model-meat-print.hpp +++ b/inst/include/epiworld/model-meat-print.hpp @@ -143,6 +143,8 @@ inline const Model & Model::print(bool lite) const for (size_t i = 0u; i < n_viruses_model; ++i) { + + const auto & virus = viruses[i]; if ((n_viruses_model > 10) && (i >= 10)) { printf_epiworld(" ...and %i more viruses...\n", @@ -155,32 +157,16 @@ inline const Model & Model::print(bool lite) const if (i < n_viruses_model) { - if (prevalence_virus_as_proportion[i]) - { - - printf_epiworld( - " - %s (baseline prevalence: %.2f%%)\n", - viruses[i]->get_name().c_str(), - prevalence_virus[i] * 100.00 - ); - - } - else - { - - printf_epiworld( - " - %s (baseline prevalence: %i seeds)\n", - viruses[i]->get_name().c_str(), - static_cast(prevalence_virus[i]) - ); - - } + printf_epiworld( + " - %s\n", + virus->get_name().c_str() + ); } else { printf_epiworld( " - %s (originated in the model...)\n", - viruses[i]->get_name().c_str() + virus->get_name().c_str() ); } @@ -204,6 +190,7 @@ inline const Model & Model::print(bool lite) const size_t n_tools_model = tools.size(); for (size_t i = 0u; i < tools.size(); ++i) { + const auto & tool = tools[i]; if ((n_tools_model > 10) && (i >= 10)) { @@ -216,32 +203,17 @@ inline const Model & Model::print(bool lite) const if (i < n_tools_model) { - if (prevalence_tool_as_proportion[i]) - { - - printf_epiworld( - " - %s (baseline prevalence: %.2f%%)\n", - tools[i]->get_name().c_str(), - prevalence_tool[i] * 100.0 - ); - - } - else - { - - printf_epiworld( - " - %s (baseline prevalence: %i seeds)\n", - tools[i]->get_name().c_str(), - static_cast(prevalence_tool[i]) - ); + printf_epiworld( + " - %s\n", + tool->get_name().c_str() + ); - } } else { printf_epiworld( " - %s (originated in the model...)\n", - tools[i]->get_name().c_str() + tool->get_name().c_str() ); } diff --git a/inst/include/epiworld/model-meat.hpp b/inst/include/epiworld/model-meat.hpp index 63bdc3e2..d8d4e978 100644 --- a/inst/include/epiworld/model-meat.hpp +++ b/inst/include/epiworld/model-meat.hpp @@ -157,7 +157,7 @@ inline void Model::events_add( Entity * entity_, epiworld_fast_int new_state_, epiworld_fast_int queue_, - ActionFun call_, + EventFun call_, int idx_agent_, int idx_object_ ) { @@ -166,7 +166,7 @@ inline void Model::events_add( #ifdef EPI_DEBUG if (nactions == 0) - throw std::logic_error("Actions cannot be zero!!"); + throw std::logic_error("Events cannot be zero!!"); #endif if (nactions > events.size()) @@ -380,18 +380,9 @@ inline Model::Model(const Model & model) : population_backup(model.population_backup), directed(model.directed), viruses(model.viruses), - prevalence_virus(model.prevalence_virus), - prevalence_virus_as_proportion(model.prevalence_virus_as_proportion), - viruses_dist_funs(model.viruses_dist_funs), tools(model.tools), - prevalence_tool(model.prevalence_tool), - prevalence_tool_as_proportion(model.prevalence_tool_as_proportion), - tools_dist_funs(model.tools_dist_funs), entities(model.entities), entities_backup(model.entities_backup), - // prevalence_entity(model.prevalence_entity), - // prevalence_entity_as_proportion(model.prevalence_entity_as_proportion), - // entities_dist_funs(model.entities_dist_funs), rewire_fun(model.rewire_fun), rewire_prop(model.rewire_prop), parameters(model.parameters), @@ -407,7 +398,8 @@ inline Model::Model(const Model & model) : queue(model.queue), use_queuing(model.use_queuing), array_double_tmp(model.array_double_tmp.size()), - array_virus_tmp(model.array_virus_tmp.size()) + array_virus_tmp(model.array_virus_tmp.size()), + array_int_tmp(model.array_int_tmp.size()) { @@ -419,13 +411,6 @@ inline Model::Model(const Model & model) : for (auto & p : population_backup) p.model = this; - for (auto & e : entities) - e.model = this; - - if (entities_backup.size() != 0u) - for (auto & e : entities_backup) - e.model = this; - // Pointing to the right place. This needs // to be done afterwards since the state zero is set as a function // of the population. @@ -454,20 +439,11 @@ inline Model::Model(Model && model) : directed(std::move(model.directed)), // Virus viruses(std::move(model.viruses)), - prevalence_virus(std::move(model.prevalence_virus)), - prevalence_virus_as_proportion(std::move(model.prevalence_virus_as_proportion)), - viruses_dist_funs(std::move(model.viruses_dist_funs)), // Tools tools(std::move(model.tools)), - prevalence_tool(std::move(model.prevalence_tool)), - prevalence_tool_as_proportion(std::move(model.prevalence_tool_as_proportion)), - tools_dist_funs(std::move(model.tools_dist_funs)), // Entities entities(std::move(model.entities)), entities_backup(std::move(model.entities_backup)), - // prevalence_entity(std::move(model.prevalence_entity)), - // prevalence_entity_as_proportion(std::move(model.prevalence_entity_as_proportion)), - // entities_dist_funs(std::move(model.entities_dist_funs)), // Pseudo-RNG engine(std::move(model.engine)), runifd(std::move(model.runifd)), @@ -492,7 +468,8 @@ inline Model::Model(Model && model) : queue(std::move(model.queue)), use_queuing(model.use_queuing), array_double_tmp(model.array_double_tmp.size()), - array_virus_tmp(model.array_virus_tmp.size()) + array_virus_tmp(model.array_virus_tmp.size()), + array_int_tmp(model.array_int_tmp.size()) { db.model = this; @@ -532,20 +509,11 @@ inline Model & Model::operator=(const Model & m) directed = m.directed; viruses = m.viruses; - prevalence_virus = m.prevalence_virus; - prevalence_virus_as_proportion = m.prevalence_virus_as_proportion; - viruses_dist_funs = m.viruses_dist_funs; tools = m.tools; - prevalence_tool = m.prevalence_tool; - prevalence_tool_as_proportion = m.prevalence_tool_as_proportion; - tools_dist_funs = m.tools_dist_funs; entities = m.entities; entities_backup = m.entities_backup; - // prevalence_entity = m.prevalence_entity; - // prevalence_entity_as_proportion = m.prevalence_entity_as_proportion; - // entities_dist_funs = m.entities_dist_funs; rewire_fun = m.rewire_fun; rewire_prop = m.rewire_prop; @@ -586,6 +554,7 @@ inline Model & Model::operator=(const Model & m) )); array_virus_tmp.resize(1024u); + array_int_tmp.resize(1024u * 1024); return *this; @@ -603,6 +572,12 @@ inline std::vector> & Model::get_agents() return population; } +template +inline Agent & Model::get_agent(size_t i) +{ + return population[i]; +} + template inline std::vector< epiworld_fast_uint > Model::get_agents_states() const { @@ -644,6 +619,25 @@ inline std::vector> & Model::get_entities() return entities; } +template +inline Entity & Model::get_entity(size_t i, int * entity_pos) +{ + + for (size_t j = 0u; j < entities.size(); ++j) + if (entities[j].get_id() == static_cast(i)) + { + + if (entity_pos) + *entity_pos = j; + + return entities[j]; + + } + + throw std::range_error("The entity with id " + std::to_string(i) + " was not found."); + +} + template inline Model & Model::agents_smallworld( epiworld_fast_uint n, @@ -741,62 +735,10 @@ template inline void Model::dist_virus() { - // Starting first infection - int n = size(); - std::vector< size_t > idx(n, 0u); - std::iota(idx.begin(), idx.end(), 0); - int n_left = idx.size(); - - for (size_t v = 0u; v < viruses.size(); ++v) + for (auto & v: viruses) { - if (viruses_dist_funs[v]) - { - - viruses_dist_funs[v](*viruses[v], this); - - } else { - - // Picking how many - int nsampled; - if (prevalence_virus_as_proportion[v]) - { - nsampled = static_cast(std::floor(prevalence_virus[v] * size())); - } - else - { - nsampled = static_cast(prevalence_virus[v]); - } - - if (nsampled > static_cast(size())) - throw std::range_error("There are only " + std::to_string(size()) + - " individuals in the population. Cannot add the virus to " + std::to_string(nsampled)); - - - VirusPtr virus = viruses[v]; - - while (nsampled > 0) - { - - int loc = static_cast(floor(runif() * (n_left--))); - - Agent & agent = population[idx[loc]]; - - // Adding action - agent.set_virus( - virus, - const_cast * >(this), - virus->state_init, - virus->queue_init - ); - - // Adjusting sample - nsampled--; - std::swap(idx[loc], idx[n_left]); - - } - - } + v->distribute(this); // Apply the events events_run(); @@ -808,55 +750,10 @@ template inline void Model::dist_tools() { - // Starting first infection - int n = size(); - std::vector< size_t > idx(n); - for (epiworld_fast_uint t = 0; t < tools.size(); ++t) + for (auto & tool: tools) { - if (tools_dist_funs[t]) - { - - tools_dist_funs[t](*tools[t], this); - - } else { - - // Picking how many - int nsampled; - if (prevalence_tool_as_proportion[t]) - { - nsampled = static_cast(std::floor(prevalence_tool[t] * size())); - } - else - { - nsampled = static_cast(prevalence_tool[t]); - } - - if (nsampled > static_cast(size())) - throw std::range_error("There are only " + std::to_string(size()) + - " individuals in the population. Cannot add the tool to " + std::to_string(nsampled)); - - ToolPtr tool = tools[t]; - - int n_left = n; - std::iota(idx.begin(), idx.end(), 0); - while (nsampled > 0) - { - int loc = static_cast(floor(runif() * n_left--)); - - population[idx[loc]].add_tool( - tool, - const_cast< Model * >(this), - tool->state_init, tool->queue_init - ); - - nsampled--; - - std::swap(idx[loc], idx[n_left]); - - } - - } + tool->distribute(this); // Apply the events events_run(); @@ -865,62 +762,21 @@ inline void Model::dist_tools() } -// template -// inline void Model::dist_entities() -// { - -// // Starting first infection -// int n = size(); -// std::vector< size_t > idx(n); -// for (epiworld_fast_uint e = 0; e < entities.size(); ++e) -// { - -// if (entities_dist_funs[e]) -// { - -// entities_dist_funs[e](entities[e], this); - -// } else { - -// // Picking how many -// int nsampled; -// if (prevalence_entity_as_proportion[e]) -// { -// nsampled = static_cast(std::floor(prevalence_entity[e] * size())); -// } -// else -// { -// nsampled = static_cast(prevalence_entity[e]); -// } - -// if (nsampled > static_cast(size())) -// throw std::range_error("There are only " + std::to_string(size()) + -// " individuals in the population. Cannot add the entity to " + std::to_string(nsampled)); - -// Entity & entity = entities[e]; - -// int n_left = n; -// std::iota(idx.begin(), idx.end(), 0); -// while (nsampled > 0) -// { -// int loc = static_cast(floor(runif() * n_left--)); - -// population[idx[loc]].add_entity(entity, this, entity.state_init, entity.queue_init); - -// nsampled--; - -// std::swap(idx[loc], idx[n_left]); +template +inline void Model::dist_entities() +{ -// } + for (auto & entity: entities) + { -// } + entity.distribute(this); -// // Apply the events -// events_run(); + // Apply the events + events_run(); -// } + } -// } +} template inline void Model::chrono_start() { @@ -1055,15 +911,11 @@ inline void Model::seed(size_t s) { } template -inline void Model::add_virus(Virus & v, epiworld_double preval) +inline void Model::add_virus( + Virus & v + ) { - if (preval > 1.0) - throw std::range_error("Prevalence of virus cannot be above 1.0"); - - if (preval < 0.0) - throw std::range_error("Prevalence of virus cannot be negative"); - // Checking the state epiworld_fast_int init_, post_, rm_; v.get_state(&init_, &post_, &rm_); @@ -1082,123 +934,45 @@ inline void Model::add_virus(Virus & v, epiworld_double preval) // Adding new virus viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(preval); - prevalence_virus_as_proportion.push_back(true); - viruses_dist_funs.push_back(nullptr); - -} - -template -inline void Model::add_virus_n(Virus & v, epiworld_fast_uint preval) -{ - - // Checking the ids - epiworld_fast_int init_, post_, rm_; - v.get_state(&init_, &post_, &rm_); - - if (init_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -init- state." - ); - else if (post_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -post- state." - ); - - // Setting the id - db.record_virus(v); - - // Adding new virus - viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(preval); - prevalence_virus_as_proportion.push_back(false); - viruses_dist_funs.push_back(nullptr); } template -inline void Model::add_virus_fun(Virus & v, VirusToAgentFun fun) +inline void Model::add_tool(Tool & t) { - // Checking the ids - epiworld_fast_int init_, post_, rm_; - v.get_state(&init_, &post_, &rm_); - - if (init_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -init- state." - ); - else if (post_ == -99) - throw std::logic_error( - "The virus \"" + v.get_name() + "\" has no -post- state." - ); - - // Setting the id - db.record_virus(v); - // v.set_id(viruses.size()); - - // Adding new virus - viruses.push_back(std::make_shared< Virus >(v)); - prevalence_virus.push_back(0.0); - prevalence_virus_as_proportion.push_back(false); - viruses_dist_funs.push_back(fun); - -} - -template -inline void Model::add_tool(Tool & t, epiworld_double preval) -{ - - if (preval > 1.0) - throw std::range_error("Prevalence of tool cannot be above 1.0"); - - if (preval < 0.0) - throw std::range_error("Prevalence of tool cannot be negative"); - + db.record_tool(t); // Adding the tool to the model (and database.) tools.push_back(std::make_shared< Tool >(t)); - prevalence_tool.push_back(preval); - prevalence_tool_as_proportion.push_back(true); - tools_dist_funs.push_back(nullptr); } template -inline void Model::add_tool_n(Tool & t, epiworld_fast_uint preval) +inline void Model::add_entity(Entity e) { - - db.record_tool(t); - tools.push_back(std::make_shared >(t)); - prevalence_tool.push_back(preval); - prevalence_tool_as_proportion.push_back(false); - tools_dist_funs.push_back(nullptr); + e.id = entities.size(); + entities.push_back(e); } template -inline void Model::add_tool_fun(Tool & t, ToolToAgentFun fun) +inline void Model::rm_entity(size_t entity_id) { - - db.record_tool(t); - - tools.push_back(std::make_shared >(t)); - prevalence_tool.push_back(0.0); - prevalence_tool_as_proportion.push_back(false); - tools_dist_funs.push_back(fun); -} + int entity_pos = 0; + auto & entity = this->get_entity(entity_id, &entity_pos); -template -inline void Model::add_entity(Entity e) -{ + // First, resetting the entity + entity.reset(); - e.model = this; - e.id = entities.size(); - entities.push_back(e); + // How should + if (entity_pos != (static_cast(entities.size()) - 1)) + std::swap(entities[entity_pos], entities[entities.size() - 1]); + entities.pop_back(); } template @@ -1217,17 +991,7 @@ inline void Model::rm_virus(size_t virus_pos) // Flipping with the last one std::swap(viruses[virus_pos], viruses[viruses.size() - 1]); - std::swap(viruses_dist_funs[virus_pos], viruses_dist_funs[viruses.size() - 1]); - std::swap(prevalence_virus[virus_pos], prevalence_virus[viruses.size() - 1]); - std::vector::swap( - prevalence_virus_as_proportion[virus_pos], - prevalence_virus_as_proportion[viruses.size() - 1] - ); - viruses.pop_back(); - viruses_dist_funs.pop_back(); - prevalence_virus.pop_back(); - prevalence_virus_as_proportion.pop_back(); return; @@ -1249,8 +1013,6 @@ inline void Model::rm_tool(size_t tool_pos) // Flipping with the last one std::swap(tools[tool_pos], tools[tools.size() - 1]); - std::swap(tools_dist_funs[tool_pos], tools_dist_funs[tools.size() - 1]); - std::swap(prevalence_tool[tool_pos], prevalence_tool[tools.size() - 1]); /* There's an error on windows: https://github.com/UofUEpiBio/epiworldR/actions/runs/4801482395/jobs/8543744180#step:6:84 @@ -1258,20 +1020,8 @@ inline void Model::rm_tool(size_t tool_pos) More clear here: https://stackoverflow.com/questions/58660207/why-doesnt-stdswap-work-on-vectorbool-elements-under-clang-win */ - std::vector::swap( - prevalence_tool_as_proportion[tool_pos], - prevalence_tool_as_proportion[tools.size() - 1] - ); - - // auto old = prevalence_tool_as_proportion[tool_pos]; - // prevalence_tool_as_proportion[tool_pos] = prevalence_tool_as_proportion[tools.size() - 1]; - // prevalence_tool_as_proportion[tools.size() - 1] = old; - tools.pop_back(); - tools_dist_funs.pop_back(); - prevalence_tool.pop_back(); - prevalence_tool_as_proportion.pop_back(); return; @@ -1291,7 +1041,6 @@ inline void Model::load_agents_entities_ties( throw std::logic_error("The file " + fn + " was not found."); int linenum = 0; - std::vector< epiworld_fast_uint > source_; std::vector< std::vector< epiworld_fast_uint > > target_(entities.size()); target_.reserve(1e5); @@ -1332,36 +1081,105 @@ inline void Model::load_agents_entities_ties( } - // // Iterating over entities - // for (size_t e = 0u; e < entities.size(); ++e) - // { + return; - // // This entity will have individuals assigned to it, so we add it - // if (target_[e].size() > 0u) - // { +} - // // Filling in the gaps - // prevalence_entity[e] = static_cast(target_[e].size()); - // prevalence_entity_as_proportion[e] = false; +template +inline void Model::load_agents_entities_ties( + const std::vector< int > & agents_ids, + const std::vector< int > & entities_ids +) { - // // Generating the assignment function - // auto who = target_[e]; - // entities_dist_funs[e] = - // [who](Entity & e, Model* m) -> void { + // Checking the size + if (agents_ids.size() != entities_ids.size()) + throw std::length_error( + std::string("The size of agents_ids (") + + std::to_string(agents_ids.size()) + + std::string(") and entities_ids (") + + std::to_string(entities_ids.size()) + + std::string(") must be the same.") + ); - // for (auto w : who) - // m->population[w].add_entity(e, m, e.state_init, e.queue_init); - - // return; - - // }; + return this->load_agents_entities_ties( + agents_ids.data(), + entities_ids.data(), + agents_ids.size() + ); - // } +} - // } +template +inline void Model::load_agents_entities_ties( + const int * agents_ids, + const int * entities_ids, + size_t n +) { + + auto get_agent = [agents_ids](int i) -> int { + return *(agents_ids + i); + }; + + auto get_entity = [entities_ids](int i) -> int { + return *(entities_ids + i); + }; + + for (size_t i = 0u; i < n; ++i) + { + + if (get_agent(i) < 0) + throw std::length_error( + std::string("agents_ids[") + + std::to_string(i) + + std::string("] = ") + + std::to_string(get_agent(i)) + + std::string(" is negative.") + ); + + if (get_entity(i) < 0) + throw std::length_error( + std::string("entities_ids[") + + std::to_string(i) + + std::string("] = ") + + std::to_string(get_entity(i)) + + std::string(" is negative.") + ); + + int pop_size = static_cast(this->population.size()); + if (get_agent(i) >= pop_size) + throw std::length_error( + std::string("agents_ids[") + + std::to_string(i) + + std::string("] = ") + + std::to_string(get_agent(i)) + + std::string(" is out of range (population size: ") + + std::to_string(pop_size) + + std::string(").") + ); + + int ent_size = static_cast(this->entities.size()); + if (get_entity(i) >= ent_size) + throw std::length_error( + std::string("entities_ids[") + + std::to_string(i) + + std::string("] = ") + + std::to_string(get_entity(i)) + + std::string(" is out of range (entities size: ") + + std::to_string(ent_size) + + std::string(").") + ); + + // Adding the entity to the agent + this->population[get_agent(i)].add_entity( + this->entities[get_entity(i)], + nullptr /* Immediately add it to the agent */ + ); + + } return; + } template @@ -1488,6 +1306,7 @@ inline Model & Model::run( array_virus_tmp.resize(1024); + array_int_tmp.resize(1024 * 1024); // Checking whether the proposed state in/out/removed // are valid @@ -2072,11 +1891,9 @@ inline void Model::reset() { #endif } - else - { - for (auto & e: entities) - e.reset(); - } + + for (auto & e: entities) + e.reset(); current_date = 0; @@ -2089,6 +1906,7 @@ inline void Model::reset() { // Re distributing tools and virus dist_virus(); dist_tools(); + dist_entities(); // Distributing initial state, if specified initial_states_fun(this); @@ -2103,47 +1921,6 @@ inline void Model::reset() { // Too big to keep here #include "model-meat-print.hpp" -template -inline Model && Model::clone() const { - - // Step 1: Regen the individuals and make sure that: - // - Neighbors point to the right place - // - DB is pointing to the right place - Model res(*this); - - // Removing old neighbors - for (auto & p: res.population) - p.neighbors.clear(); - - // Rechecking individuals - for (epiworld_fast_uint p = 0u; p < size(); ++p) - { - // Making room - const Agent & agent_this = population[p]; - Agent & agent_res = res.population[p]; - - // Agent pointing to the right model and agent - agent_res.model = &res; - agent_res.viruses.agent = &agent_res; - agent_res.tools.agent = &agent_res; - - // Readding - std::vector< Agent * > neigh = agent_this.neighbors; - for (epiworld_fast_uint n = 0u; n < neigh.size(); ++n) - { - // Point to the right neighbors - int loc = res.population_ids[neigh[n]->get_id()]; - agent_res.add_neighbor(res.population[loc], true, true); - - } - - } - - return res; - -} - - template inline void Model::add_state( @@ -2543,18 +2320,6 @@ inline const std::vector< VirusPtr > & Model::get_viruses() const return viruses; } -template -inline const std::vector< epiworld_double > & Model::get_prevalence_virus() const -{ - return prevalence_virus; -} - -template -inline const std::vector< bool > & Model::get_prevalence_virus_as_proportion() const -{ - return prevalence_virus_as_proportion; -} - template const std::vector< ToolPtr > & Model::get_tools() const { @@ -2683,18 +2448,6 @@ inline bool Model::operator==(const Model & other) const ) } - - VECT_MATCH( - prevalence_virus, - other.prevalence_virus, - "virus prevalence don't match" - ) - - VECT_MATCH( - prevalence_virus_as_proportion, - other.prevalence_virus_as_proportion, - "virus prevalence as prop don't match" - ) // Tools ------------------------------------------------------------------- EPI_DEBUG_FAIL_AT_TRUE( @@ -2710,18 +2463,6 @@ inline bool Model::operator==(const Model & other) const ) } - - VECT_MATCH( - prevalence_tool, - other.prevalence_tool, - "tools prevalence don't match" - ) - - VECT_MATCH( - prevalence_tool_as_proportion, - other.prevalence_tool_as_proportion, - "tools as prop don't match" - ) VECT_MATCH( entities, diff --git a/inst/include/epiworld/models/diffnet.hpp b/inst/include/epiworld/models/diffnet.hpp index b7ab0724..e0eacc83 100644 --- a/inst/include/epiworld/models/diffnet.hpp +++ b/inst/include/epiworld/models/diffnet.hpp @@ -20,7 +20,7 @@ class ModelDiffNet : public epiworld::Model ModelDiffNet( ModelDiffNet & model, - std::string innovation_name, + const std::string & innovation_name, epiworld_double prevalence, epiworld_double prob_adopt, bool normalize_exposure = true, @@ -31,7 +31,7 @@ class ModelDiffNet : public epiworld::Model ); ModelDiffNet( - std::string innovation_name, + const std::string & innovation_name, epiworld_double prevalence, epiworld_double prob_adopt, bool normalize_exposure = true, @@ -52,7 +52,7 @@ class ModelDiffNet : public epiworld::Model template inline ModelDiffNet::ModelDiffNet( ModelDiffNet & model, - std::string innovation_name, + const std::string & innovation_name, epiworld_double prevalence, epiworld_double prob_adopt, bool normalize_exposure, @@ -164,12 +164,12 @@ inline ModelDiffNet::ModelDiffNet( model.add_param(prob_adopt, parname); // Preparing the virus ------------------------------------------- - epiworld::Virus innovation(innovation_name); + epiworld::Virus innovation(innovation_name, prevalence, true); innovation.set_state(1,1,1); innovation.set_prob_infecting(&model(parname)); - model.add_virus(innovation, prevalence); + model.add_virus(innovation); model.set_name( std::string("Diffusion of Innovations - ") + innovation_name); @@ -180,7 +180,7 @@ inline ModelDiffNet::ModelDiffNet( template inline ModelDiffNet::ModelDiffNet( - std::string innovation_name, + const std::string & innovation_name, epiworld_double prevalence, epiworld_double prob_adopt, bool normalize_exposure, diff --git a/inst/include/epiworld/models/init-functions.hpp b/inst/include/epiworld/models/init-functions.hpp index f63366df..1ac26c99 100644 --- a/inst/include/epiworld/models/init-functions.hpp +++ b/inst/include/epiworld/models/init-functions.hpp @@ -29,16 +29,13 @@ inline std::function*)> create_init_function_sir( // Figuring out information about the viruses double tot = 0.0; - const auto & vpreval = model->get_prevalence_virus(); - const auto & vprop = model->get_prevalence_virus_as_proportion(); double n = static_cast(model->size()); - for (size_t i = 0u; i < model->get_n_viruses(); ++i) + for (const auto & agent: model->get_agents()) { - if (vprop[i]) - tot += vpreval[i]; - else - tot += vpreval[i] / n; + if (agent.get_virus() != nullptr) + tot += 1.0; } + tot /= n; // Putting the total into context double tot_left = 1.0 - tot; @@ -105,16 +102,13 @@ inline std::function*)> create_init_function_sird( // Figuring out information about the viruses double tot = 0.0; - const auto & vpreval = model->get_prevalence_virus(); - const auto & vprop = model->get_prevalence_virus_as_proportion(); double n = static_cast(model->size()); - for (size_t i = 0u; i < model->get_n_viruses(); ++i) + for (const auto & agent: model->get_agents()) { - if (vprop[i]) - tot += vpreval[i]; - else - tot += vpreval[i] / n; + if (agent.get_virus() != nullptr) + tot += 1.0; } + tot /= n; // Putting the total into context double tot_left = 1.0 - tot; @@ -185,16 +179,13 @@ inline std::function*)> create_init_function_seir( // Figuring out information about the viruses double tot = 0.0; - const auto & vpreval = model->get_prevalence_virus(); - const auto & vprop = model->get_prevalence_virus_as_proportion(); double n = static_cast(model->size()); - for (size_t i = 0u; i < model->get_n_viruses(); ++i) + for (const auto & agent: model->get_agents()) { - if (vprop[i]) - tot += vpreval[i]; - else - tot += vpreval[i] / n; + if (agent.get_virus() != nullptr) + tot += 1.0; } + tot /= n; // Putting the total into context double tot_left = 1.0 - tot; @@ -269,16 +260,14 @@ inline std::function*)> create_init_function_seird( // Figuring out information about the viruses double tot = 0.0; - const auto & vpreval = model->get_prevalence_virus(); - const auto & vprop = model->get_prevalence_virus_as_proportion(); double n = static_cast(model->size()); - for (size_t i = 0u; i < model->get_n_viruses(); ++i) + + for (const auto & agent: model->get_agents()) { - if (vprop[i]) - tot += vpreval[i]; - else - tot += vpreval[i] / n; + if (agent.get_virus() != nullptr) + tot += 1.0; } + tot /= n; // Putting the total into context double tot_left = 1.0 - tot; diff --git a/inst/include/epiworld/models/models.hpp b/inst/include/epiworld/models/models.hpp index 72cfc860..a61ae91d 100644 --- a/inst/include/epiworld/models/models.hpp +++ b/inst/include/epiworld/models/models.hpp @@ -19,6 +19,8 @@ namespace epimodels { #include "seirdconnected.hpp" #include "sirlogit.hpp" #include "diffnet.hpp" + #include "seirmixing.hpp" + #include "sirmixing.hpp" } diff --git a/inst/include/epiworld/models/seir.hpp b/inst/include/epiworld/models/seir.hpp index 301796ee..173f3454 100644 --- a/inst/include/epiworld/models/seir.hpp +++ b/inst/include/epiworld/models/seir.hpp @@ -25,7 +25,7 @@ class ModelSEIR : public epiworld::Model ModelSEIR( ModelSEIR & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -33,7 +33,7 @@ class ModelSEIR : public epiworld::Model ); ModelSEIR( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -84,7 +84,7 @@ class ModelSEIR : public epiworld::Model template inline ModelSEIR::ModelSEIR( ModelSEIR & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -104,7 +104,7 @@ inline ModelSEIR::ModelSEIR( model.add_param(recovery_rate, "Recovery rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(ModelSEIR::EXPOSED, ModelSEIR::REMOVED, ModelSEIR::REMOVED); virus.set_prob_infecting(&model("Transmission rate")); @@ -112,7 +112,7 @@ inline ModelSEIR::ModelSEIR( virus.set_prob_recovery(&model("Recovery rate")); // Adding the tool and the virus - model.add_virus(virus, prevalence); + model.add_virus(virus); model.set_name("Susceptible-Exposed-Infected-Removed (SEIR)"); @@ -122,7 +122,7 @@ inline ModelSEIR::ModelSEIR( template inline ModelSEIR::ModelSEIR( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, diff --git a/inst/include/epiworld/models/seirconnected.hpp b/inst/include/epiworld/models/seirconnected.hpp index 7295dbac..cd87e6c3 100644 --- a/inst/include/epiworld/models/seirconnected.hpp +++ b/inst/include/epiworld/models/seirconnected.hpp @@ -20,7 +20,7 @@ class ModelSEIRCONN : public epiworld::Model ModelSEIRCONN( ModelSEIRCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -30,7 +30,7 @@ class ModelSEIRCONN : public epiworld::Model ); ModelSEIRCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -136,7 +136,7 @@ inline Model * ModelSEIRCONN::clone_ptr() template inline ModelSEIRCONN::ModelSEIRCONN( ModelSEIRCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -322,7 +322,7 @@ inline ModelSEIRCONN::ModelSEIRCONN( // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state( ModelSEIRCONN::EXPOSED, ModelSEIRCONN::RECOVERED, @@ -333,7 +333,7 @@ inline ModelSEIRCONN::ModelSEIRCONN( virus.set_prob_recovery(&model("Prob. Recovery")); virus.set_incubation(&model("Avg. Incubation days")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.queuing_off(); // No queuing need @@ -348,7 +348,7 @@ inline ModelSEIRCONN::ModelSEIRCONN( template inline ModelSEIRCONN::ModelSEIRCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, diff --git a/inst/include/epiworld/models/seird.hpp b/inst/include/epiworld/models/seird.hpp index fe608934..c1ddfb64 100644 --- a/inst/include/epiworld/models/seird.hpp +++ b/inst/include/epiworld/models/seird.hpp @@ -31,7 +31,7 @@ class ModelSEIRD : public epiworld::Model */ ModelSEIRD( ModelSEIRD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -50,7 +50,7 @@ class ModelSEIRD : public epiworld::Model * @param death_rate Death rate of the disease. */ ModelSEIRD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -145,7 +145,7 @@ class ModelSEIRD : public epiworld::Model template inline ModelSEIRD::ModelSEIRD( ModelSEIRD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, @@ -168,7 +168,7 @@ inline ModelSEIRD::ModelSEIRD( model.add_param(death_rate, "Death rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(ModelSEIRD::EXPOSED, ModelSEIRD::REMOVED, ModelSEIRD::DECEASED); virus.set_prob_infecting(&model("Transmission rate")); @@ -177,7 +177,7 @@ inline ModelSEIRD::ModelSEIRD( virus.set_prob_recovery(&model("Recovery rate")); // Adding the tool and the virus - model.add_virus(virus, prevalence); + model.add_virus(virus); model.set_name("Susceptible-Exposed-Infected-Removed-Deceased (SEIRD)"); @@ -187,7 +187,7 @@ inline ModelSEIRD::ModelSEIRD( template inline ModelSEIRD::ModelSEIRD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double avg_incubation_days, diff --git a/inst/include/epiworld/models/seirdconnected.hpp b/inst/include/epiworld/models/seirdconnected.hpp index e34f023f..b0976fd3 100644 --- a/inst/include/epiworld/models/seirdconnected.hpp +++ b/inst/include/epiworld/models/seirdconnected.hpp @@ -20,7 +20,7 @@ class ModelSEIRDCONN : public epiworld::Model ModelSEIRDCONN( ModelSEIRDCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -31,7 +31,7 @@ class ModelSEIRDCONN : public epiworld::Model ); ModelSEIRDCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -142,7 +142,7 @@ inline Model * ModelSEIRDCONN::clone_ptr() template inline ModelSEIRDCONN::ModelSEIRDCONN( ModelSEIRDCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -341,7 +341,7 @@ inline ModelSEIRDCONN::ModelSEIRDCONN( // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state( ModelSEIRDCONN::EXPOSED, ModelSEIRDCONN::REMOVED, @@ -352,7 +352,7 @@ inline ModelSEIRDCONN::ModelSEIRDCONN( virus.set_prob_recovery(&model("Prob. Recovery")); virus.set_incubation(&model("Avg. Incubation days")); virus.set_prob_death(&model("Death rate")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.queuing_off(); // No queuing need @@ -367,7 +367,7 @@ inline ModelSEIRDCONN::ModelSEIRDCONN( template inline ModelSEIRDCONN::ModelSEIRDCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, diff --git a/inst/include/epiworld/models/seirmixing.hpp b/inst/include/epiworld/models/seirmixing.hpp new file mode 100644 index 00000000..114e74e9 --- /dev/null +++ b/inst/include/epiworld/models/seirmixing.hpp @@ -0,0 +1,524 @@ +#ifndef EPIWORLD_MODELS_SEIRMIXING_HPP +#define EPIWORLD_MODELS_SEIRMIXING_HPP + +/** + * @file seirentitiesconnected.hpp + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model with mixing + */ +template +class ModelSEIRMixing : public epiworld::Model +{ +private: + std::vector< std::vector< epiworld::Agent * > > infected; + void update_infected(); + std::vector< epiworld::Agent * > sampled_agents; + size_t sample_agents( + epiworld::Agent * agent, + std::vector< epiworld::Agent * > & sampled_agents + ); + double adjusted_contact_rate; + std::vector< double > contact_matrix; + + size_t index(size_t i, size_t j, size_t n) { + return j * n + i; + } + +public: + + static const int SUSCEPTIBLE = 0; + static const int EXPOSED = 1; + static const int INFECTED = 2; + static const int RECOVERED = 3; + + ModelSEIRMixing() {}; + + /** + * @brief Constructs a ModelSEIRMixing object. + * + * @param model A reference to an existing ModelSEIRMixing object. + * @param vname The name of the ModelSEIRMixing object. + * @param n The number of entities in the model. + * @param prevalence The initial prevalence of the disease in the model. + * @param contact_rate The contact rate between entities in the model. + * @param transmission_rate The transmission rate of the disease in the model. + * @param avg_incubation_days The average incubation period of the disease in the model. + * @param recovery_rate The recovery rate of the disease in the model. + * @param contact_matrix The contact matrix between entities in the model. Specified in + * column-major order. + */ + ModelSEIRMixing( + ModelSEIRMixing & model, + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ); + + /** + * @brief Constructs a ModelSEIRMixing object. + * + * @param vname The name of the ModelSEIRMixing object. + * @param n The number of entities in the model. + * @param prevalence The initial prevalence of the disease in the model. + * @param contact_rate The contact rate between entities in the model. + * @param transmission_rate The transmission rate of the disease in the model. + * @param avg_incubation_days The average incubation period of the disease in the model. + * @param recovery_rate The recovery rate of the disease in the model. + * @param contact_matrix The contact matrix between entities in the model. + */ + ModelSEIRMixing( + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ); + + ModelSEIRMixing & run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + + /** + * @brief Set the initial states of the model + * @param proportions_ Double vector with a single element: + * - The proportion of non-infected individuals who have recovered. + */ + ModelSEIRMixing & initial_states( + std::vector< double > proportions_, + std::vector< int > queue_ = {} + ); + + size_t get_n_infected(size_t group) const + { + return infected[group].size(); + } + + void set_contact_matrix(std::vector< double > cmat) + { + contact_matrix = cmat; + return; + }; + +}; + +template +inline void ModelSEIRMixing::update_infected() +{ + + auto & agents = Model::get_agents(); + auto & entities = Model::get_entities(); + + infected.resize(entities.size()); + sampled_agents.resize(agents.size()); + + // Checking contact matrix's rows add to one + size_t nentities = entities.size(); + if (this->contact_matrix.size() != nentities*nentities) + throw std::length_error( + std::string("The contact matrix must be a square matrix of size ") + + std::string("nentities x nentities. ") + + std::to_string(this->contact_matrix.size()) + + std::string(" != ") + std::to_string(nentities*nentities) + + std::string(".") + ); + + for (size_t i = 0u; i < entities.size(); ++i) + { + double sum = 0.0; + for (size_t j = 0u; j < entities.size(); ++j) + { + if (this->contact_matrix[index(i, j, nentities)] < 0.0) + throw std::range_error( + std::string("The contact matrix must be non-negative. ") + + std::to_string(this->contact_matrix[index(i, j, nentities)]) + + std::string(" < 0.") + ); + sum += this->contact_matrix[index(i, j, nentities)]; + } + if (sum < 0.999 || sum > 1.001) + throw std::range_error( + std::string("The contact matrix must have rows that add to one. ") + + std::to_string(sum) + + std::string(" != 1.") + ); + } + + for (size_t i = 0; i < entities.size(); ++i) + { + infected[i].clear(); + infected[i].reserve(agents.size()); + } + + for (auto & a : agents) + { + + if (a.get_state() == ModelSEIRMixing::INFECTED) + { + if (a.get_n_entities() > 0u) + infected[a.get_entity(0u).get_id()].push_back(&a); + } + + } + + // Adjusting contact rate + adjusted_contact_rate = Model::get_param("Contact rate") / + agents.size(); + + return; + +} + +template +inline size_t ModelSEIRMixing::sample_agents( + epiworld::Agent * agent, + std::vector< epiworld::Agent * > & sampled_agents + ) +{ + + size_t agent_group_id = agent->get_entity(0u).get_id(); + size_t ngroups = infected.size(); + + int samp_id = 0; + for (size_t g = 0; g < infected.size(); ++g) + { + + // How many from this entity? + int nsamples = epiworld::Model::rbinom( + infected[g].size(), + adjusted_contact_rate * contact_matrix[ + index(agent_group_id, g, ngroups) + ] + ); + + if (nsamples == 0) + continue; + + // Sampling from the entity + for (int s = 0; s < nsamples; ++s) + { + + // Randomly selecting an agent + int which = epiworld::Model::runif() * infected[g].size(); + + // Correcting overflow error + if (which >= static_cast(infected[g].size())) + which = static_cast(infected[g].size()) - 1; + + auto & a = infected[g][which]; + + // Can't sample itself + if (a->get_id() == agent->get_id()) + continue; + + sampled_agents[samp_id++] = a; + + } + + } + + return samp_id; + +} + +template +inline ModelSEIRMixing & ModelSEIRMixing::run( + epiworld_fast_uint ndays, + int seed +) +{ + + Model::run(ndays, seed); + return *this; + +} + +template +inline void ModelSEIRMixing::reset() +{ + + Model::reset(); + this->update_infected(); + + return; + +} + +template +inline Model * ModelSEIRMixing::clone_ptr() +{ + + ModelSEIRMixing * ptr = new ModelSEIRMixing( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + */ +template +inline ModelSEIRMixing::ModelSEIRMixing( + ModelSEIRMixing & model, + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ) +{ + + // Setting up the contact matrix + this->contact_matrix = contact_matrix; + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + if (p->get_n_entities() == 0) + return; + + // Downcasting to retrieve the sampler attached to the + // class + ModelSEIRMixing * m_down = + dynamic_cast *>(m); + + size_t ndraws = m_down->sample_agents(p, m_down->sampled_agents); + + if (ndraws == 0u) + return; + + + // Drawing from the set + int nviruses_tmp = 0; + for (size_t n = 0u; n < ndraws; ++n) + { + + auto & neighbor = m_down->sampled_agents[n]; + + auto & v = neighbor->get_virus(); + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->set_virus( + *m->array_virus_tmp[which], + m, + ModelSEIRMixing::EXPOSED + ); + + return; + + }; + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSEIRMixing::EXPOSED) + { + + // Getting the virus + auto & v = p->get_virus(); + + // Does the agent become infected? + if (m->runif() < 1.0/(v->get_incubation(m))) + { + + p->change_state(m, ModelSEIRMixing::INFECTED); + return; + + } + + + } else if (state == ModelSEIRMixing::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + const auto & v = p->get_virus(); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in exposed."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + p->rm_virus(m); + + return ; + + } else + throw std::logic_error("This function can only be applied to exposed or infected individuals. (SEIR)") ; + + return; + + }; + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Prob. Transmission"); + model.add_param(recovery_rate, "Prob. Recovery"); + model.add_param(avg_incubation_days, "Avg. Incubation days"); + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Exposed", update_infected); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + // Global function + epiworld::GlobalFun update = [](epiworld::Model * m) -> void + { + + ModelSEIRMixing * m_down = + dynamic_cast *>(m); + + m_down->update_infected(); + + return; + + }; + + model.add_globalevent(update, "Update infected individuals"); + + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname, prevalence, true); + virus.set_state( + ModelSEIRMixing::EXPOSED, + ModelSEIRMixing::RECOVERED, + ModelSEIRMixing::RECOVERED + ); + + virus.set_prob_infecting(&model("Prob. Transmission")); + virus.set_prob_recovery(&model("Prob. Recovery")); + virus.set_incubation(&model("Avg. Incubation days")); + + model.add_virus(virus); + + model.queuing_off(); // No queuing need + + // Adding the empty population + model.agents_empty_graph(n); + + model.set_name("Susceptible-Exposed-Infected-Removed (SEIR) with Mixing"); + + return; + +} + +template +inline ModelSEIRMixing::ModelSEIRMixing( + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double avg_incubation_days, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ) +{ + + this->contact_matrix = contact_matrix; + + ModelSEIRMixing( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + avg_incubation_days, + recovery_rate, + contact_matrix + ); + + return; + +} + +template +inline ModelSEIRMixing & ModelSEIRMixing::initial_states( + std::vector< double > proportions_, + std::vector< int > /* queue_ */ +) +{ + + Model::initial_states_fun = + create_init_function_seir(proportions_) + ; + + return *this; + +} + +#endif diff --git a/inst/include/epiworld/models/sir.hpp b/inst/include/epiworld/models/sir.hpp index ae9ca2e3..5e65daa1 100644 --- a/inst/include/epiworld/models/sir.hpp +++ b/inst/include/epiworld/models/sir.hpp @@ -19,14 +19,14 @@ class ModelSIR : public epiworld::Model ModelSIR( ModelSIR & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate ); ModelSIR( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate @@ -47,7 +47,7 @@ class ModelSIR : public epiworld::Model template inline ModelSIR::ModelSIR( ModelSIR & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate @@ -64,13 +64,13 @@ inline ModelSIR::ModelSIR( model.add_param(transmission_rate, "Transmission rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(1,2,2); virus.set_prob_recovery(&model("Recovery rate")); virus.set_prob_infecting(&model("Transmission rate")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.set_name("Susceptible-Infected-Recovered (SIR)"); @@ -80,7 +80,7 @@ inline ModelSIR::ModelSIR( template inline ModelSIR::ModelSIR( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate diff --git a/inst/include/epiworld/models/sirconnected.hpp b/inst/include/epiworld/models/sirconnected.hpp index 705a309f..c66984a1 100644 --- a/inst/include/epiworld/models/sirconnected.hpp +++ b/inst/include/epiworld/models/sirconnected.hpp @@ -21,7 +21,7 @@ class ModelSIRCONN : public epiworld::Model ModelSIRCONN( ModelSIRCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -30,7 +30,7 @@ class ModelSIRCONN : public epiworld::Model ); ModelSIRCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -143,7 +143,7 @@ inline Model * ModelSIRCONN::clone_ptr() template inline ModelSIRCONN::ModelSIRCONN( ModelSIRCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -305,12 +305,12 @@ inline ModelSIRCONN::ModelSIRCONN( model.add_globalevent(update, "Update infected individuals"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(1, 2, 2); virus.set_prob_infecting(&model("Transmission rate")); virus.set_prob_recovery(&model("Recovery rate")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.queuing_off(); // No queuing need @@ -324,7 +324,7 @@ inline ModelSIRCONN::ModelSIRCONN( template inline ModelSIRCONN::ModelSIRCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, diff --git a/inst/include/epiworld/models/sird.hpp b/inst/include/epiworld/models/sird.hpp index f9f51846..8bdfc05f 100644 --- a/inst/include/epiworld/models/sird.hpp +++ b/inst/include/epiworld/models/sird.hpp @@ -25,7 +25,7 @@ class ModelSIRD : public epiworld::Model ///@{ ModelSIRD( ModelSIRD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -33,7 +33,7 @@ class ModelSIRD : public epiworld::Model ); ModelSIRD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -57,7 +57,7 @@ class ModelSIRD : public epiworld::Model template inline ModelSIRD::ModelSIRD( ModelSIRD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -78,13 +78,13 @@ inline ModelSIRD::ModelSIRD( model.add_param(death_rate, "Death rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(1,2,3); virus.set_prob_recovery(&model("Recovery rate")); virus.set_prob_infecting(&model("Transmission rate")); virus.set_prob_death(&model("Death rate")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.set_name("Susceptible-Infected-Recovered-Deceased (SIRD)"); @@ -94,7 +94,7 @@ inline ModelSIRD::ModelSIRD( template inline ModelSIRD::ModelSIRD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, diff --git a/inst/include/epiworld/models/sirdconnected.hpp b/inst/include/epiworld/models/sirdconnected.hpp index 68f2db0e..094d8dd5 100644 --- a/inst/include/epiworld/models/sirdconnected.hpp +++ b/inst/include/epiworld/models/sirdconnected.hpp @@ -19,7 +19,7 @@ class ModelSIRDCONN : public epiworld::Model ModelSIRDCONN( ModelSIRDCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -29,7 +29,7 @@ class ModelSIRDCONN : public epiworld::Model ); ModelSIRDCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -116,7 +116,7 @@ inline Model * ModelSIRDCONN::clone_ptr() template inline ModelSIRDCONN::ModelSIRDCONN( ModelSIRDCONN & model, - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, @@ -290,13 +290,13 @@ inline ModelSIRDCONN::ModelSIRDCONN( // model.add_param(prob_reinfection, "Prob. Reinfection"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(1, 2, 3); virus.set_prob_infecting(&model("Transmission rate")); virus.set_prob_recovery(&model("Recovery rate")); virus.set_prob_death(&model("Death rate")); - model.add_virus(virus, prevalence); + model.add_virus(virus); model.queuing_off(); // No queuing need @@ -310,7 +310,7 @@ inline ModelSIRDCONN::ModelSIRDCONN( template inline ModelSIRDCONN::ModelSIRDCONN( - std::string vname, + const std::string & vname, epiworld_fast_uint n, epiworld_double prevalence, epiworld_double contact_rate, diff --git a/inst/include/epiworld/models/sirlogit.hpp b/inst/include/epiworld/models/sirlogit.hpp index 9af13166..62090f7a 100644 --- a/inst/include/epiworld/models/sirlogit.hpp +++ b/inst/include/epiworld/models/sirlogit.hpp @@ -54,7 +54,7 @@ class ModelSIRLogit : public epiworld::Model */ ModelSIRLogit( ModelSIRLogit & model, - std::string vname, + const std::string & vname, double * data, size_t ncols, std::vector< double > coefs_infect, @@ -67,7 +67,7 @@ class ModelSIRLogit : public epiworld::Model ); ModelSIRLogit( - std::string vname, + const std::string & vname, double * data, size_t ncols, std::vector< double > coefs_infect, @@ -168,7 +168,7 @@ inline void ModelSIRLogit::reset() template inline ModelSIRLogit::ModelSIRLogit( ModelSIRLogit & model, - std::string vname, + const std::string & vname, double * data, size_t ncols, std::vector< double > coefs_infect, @@ -299,7 +299,7 @@ inline ModelSIRLogit::ModelSIRLogit( // model.add_param(prob_reinfection, "Prob. Reinfection"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state( ModelSIRLogit::INFECTED, ModelSIRLogit::RECOVERED, @@ -311,7 +311,7 @@ inline ModelSIRLogit::ModelSIRLogit( // virus.set_prob - model.add_virus(virus, prevalence); + model.add_virus(virus); model.set_name("Susceptible-Infected-Removed (SIR) (logit)"); @@ -321,7 +321,7 @@ inline ModelSIRLogit::ModelSIRLogit( template inline ModelSIRLogit::ModelSIRLogit( - std::string vname, + const std::string & vname, double * data, size_t ncols, std::vector< double > coefs_infect, diff --git a/inst/include/epiworld/models/sirmixing.hpp b/inst/include/epiworld/models/sirmixing.hpp new file mode 100644 index 00000000..fc05cea8 --- /dev/null +++ b/inst/include/epiworld/models/sirmixing.hpp @@ -0,0 +1,496 @@ +#ifndef EPIWORLD_MODELS_SIRMIXING_HPP +#define EPIWORLD_MODELS_SIRMIXING_HPP + +/** + * @file seirentitiesconnected.hpp + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model with mixing + */ +template +class ModelSIRMixing : public epiworld::Model +{ +private: + std::vector< std::vector< epiworld::Agent * > > infected; + void update_infected_list(); + std::vector< epiworld::Agent * > sampled_agents; + size_t sample_agents( + epiworld::Agent * agent, + std::vector< epiworld::Agent * > & sampled_agents + ); + double adjusted_contact_rate; + std::vector< double > contact_matrix; + + size_t index(size_t i, size_t j, size_t n) { + return j * n + i; + } + +public: + + static const int SUSCEPTIBLE = 0; + static const int INFECTED = 1; + static const int RECOVERED = 2; + + ModelSIRMixing() {}; + + /** + * @brief Constructs a ModelSIRMixing object. + * + * @param model A reference to an existing ModelSIRMixing object. + * @param vname The name of the ModelSIRMixing object. + * @param n The number of entities in the model. + * @param prevalence The initial prevalence of the disease in the model. + * @param contact_rate The contact rate between entities in the model. + * @param transmission_rate The transmission rate of the disease in the model. + * @param recovery_rate The recovery rate of the disease in the model. + * @param contact_matrix The contact matrix between entities in the model. + */ + ModelSIRMixing( + ModelSIRMixing & model, + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ); + + /** + * @brief Constructs a ModelSIRMixing object. + * + * @param vname The name of the ModelSIRMixing object. + * @param n The number of entities in the model. + * @param prevalence The initial prevalence of the disease in the model. + * @param contact_rate The contact rate between entities in the model. + * @param transmission_rate The transmission rate of the disease in the model. + * @param recovery_rate The recovery rate of the disease in the model. + * @param contact_matrix The contact matrix between entities in the model. + */ + ModelSIRMixing( + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ); + + ModelSIRMixing & run( + epiworld_fast_uint ndays, + int seed = -1 + ); + + void reset(); + + Model * clone_ptr(); + + /** + * @brief Set the initial states of the model + * @param proportions_ Double vector with a single element: + * - The proportion of non-infected individuals who have recovered. + */ + ModelSIRMixing & initial_states( + std::vector< double > proportions_, + std::vector< int > queue_ = {} + ); + + size_t get_n_infected(size_t group) const + { + return infected[group].size(); + } + + void set_contact_matrix(std::vector< double > cmat) + { + contact_matrix = cmat; + return; + }; + +}; + +template +inline void ModelSIRMixing::update_infected_list() +{ + + auto & agents = Model::get_agents(); + auto & entities = Model::get_entities(); + + infected.resize(entities.size()); + sampled_agents.resize(agents.size()); + + // Checking contact matrix's rows add to one + size_t nentities = entities.size(); + if (this->contact_matrix.size() != nentities*nentities) + throw std::length_error( + std::string("The contact matrix must be a square matrix of size ") + + std::string("nentities x nentities. ") + + std::to_string(this->contact_matrix.size()) + + std::string(" != ") + std::to_string(nentities*nentities) + + std::string(".") + ); + + for (size_t i = 0u; i < entities.size(); ++i) + { + double sum = 0.0; + for (size_t j = 0u; j < entities.size(); ++j) + { + if (this->contact_matrix[index(i, j, nentities)] < 0.0) + throw std::range_error( + std::string("The contact matrix must be non-negative. ") + + std::to_string(this->contact_matrix[index(i, j, nentities)]) + + std::string(" < 0.") + ); + sum += this->contact_matrix[index(i, j, nentities)]; + } + if (sum < 0.999 || sum > 1.001) + throw std::range_error( + std::string("The contact matrix must have rows that add to one. ") + + std::to_string(sum) + + std::string(" != 1.") + ); + } + + for (size_t i = 0; i < entities.size(); ++i) + { + infected[i].clear(); + infected[i].reserve(agents.size()); + } + + for (auto & a : agents) + { + + if (a.get_state() == ModelSIRMixing::INFECTED) + { + if (a.get_n_entities() > 0u) + infected[a.get_entity(0u).get_id()].push_back(&a); + } + + } + + // Adjusting contact rate + adjusted_contact_rate = Model::get_param("Contact rate") / + agents.size(); + + return; + +} + +template +inline size_t ModelSIRMixing::sample_agents( + epiworld::Agent * agent, + std::vector< epiworld::Agent * > & sampled_agents + ) +{ + + size_t agent_group_id = agent->get_entity(0u).get_id(); + size_t ngroups = infected.size(); + + int samp_id = 0; + for (size_t g = 0; g < infected.size(); ++g) + { + + // How many from this entity? + int nsamples = epiworld::Model::rbinom( + infected[g].size(), + adjusted_contact_rate * contact_matrix[ + index(agent_group_id, g, ngroups) + ] + ); + + if (nsamples == 0) + continue; + + // Sampling from the entity + for (int s = 0; s < nsamples; ++s) + { + + // Randomly selecting an agent + int which = epiworld::Model::runif() * infected[g].size(); + + // Correcting overflow error + if (which >= static_cast(infected[g].size())) + which = static_cast(infected[g].size()) - 1; + + auto & a = infected[g][which]; + + // Can't sample itself + if (a->get_id() == agent->get_id()) + continue; + + sampled_agents[samp_id++] = a; + + } + + } + + return samp_id; + +} + +template +inline ModelSIRMixing & ModelSIRMixing::run( + epiworld_fast_uint ndays, + int seed +) +{ + + Model::run(ndays, seed); + return *this; + +} + +template +inline void ModelSIRMixing::reset() +{ + + Model::reset(); + this->update_infected_list(); + + return; + +} + +template +inline Model * ModelSIRMixing::clone_ptr() +{ + + ModelSIRMixing * ptr = new ModelSIRMixing( + *dynamic_cast*>(this) + ); + + return dynamic_cast< Model *>(ptr); + +} + + +/** + * @brief Template for a Susceptible-Exposed-Infected-Removed (SEIR) model + * + * @param model A Model object where to set up the SIR. + * @param vname std::string Name of the virus + * @param prevalence Initial prevalence (proportion) + * @param contact_rate Average number of contacts (interactions) per step. + * @param transmission_rate Probability of transmission + * @param recovery_rate Probability of recovery + */ +template +inline ModelSIRMixing::ModelSIRMixing( + ModelSIRMixing & model, + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ) +{ + + // Setting up the contact matrix + this->contact_matrix = contact_matrix; + + epiworld::UpdateFun update_susceptible = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void + { + + if (p->get_n_entities() == 0) + return; + + // Downcasting to retrieve the sampler attached to the + // class + ModelSIRMixing * m_down = + dynamic_cast *>(m); + + size_t ndraws = m_down->sample_agents(p, m_down->sampled_agents); + + if (ndraws == 0u) + return; + + + // Drawing from the set + int nviruses_tmp = 0; + for (size_t n = 0u; n < ndraws; ++n) + { + + auto & neighbor = m_down->sampled_agents[n]; + + auto & v = neighbor->get_virus(); + + #ifdef EPI_DEBUG + if (nviruses_tmp >= static_cast(m->array_virus_tmp.size())) + throw std::logic_error("Trying to add an extra element to a temporal array outside of the range."); + #endif + + /* And it is a function of susceptibility_reduction as well */ + m->array_double_tmp[nviruses_tmp] = + (1.0 - p->get_susceptibility_reduction(v, m)) * + v->get_prob_infecting(m) * + (1.0 - neighbor->get_transmission_reduction(v, m)) + ; + + m->array_virus_tmp[nviruses_tmp++] = &(*v); + + } + + // Running the roulette + int which = roulette(nviruses_tmp, m); + + if (which < 0) + return; + + p->set_virus( + *m->array_virus_tmp[which], + m, + ModelSIRMixing::INFECTED + ); + + return; + + }; + + epiworld::UpdateFun update_infected = []( + epiworld::Agent * p, epiworld::Model * m + ) -> void { + + auto state = p->get_state(); + + if (state == ModelSIRMixing::INFECTED) + { + + + // Odd: Die, Even: Recover + epiworld_fast_uint n_events = 0u; + const auto & v = p->get_virus(); + + // Recover + m->array_double_tmp[n_events++] = + 1.0 - (1.0 - v->get_prob_recovery(m)) * (1.0 - p->get_recovery_enhancer(v, m)); + + #ifdef EPI_DEBUG + if (n_events == 0u) + { + printf_epiworld( + "[epi-debug] agent %i has 0 possible events!!\n", + static_cast(p->get_id()) + ); + throw std::logic_error("Zero events in infected."); + } + #else + if (n_events == 0u) + return; + #endif + + + // Running the roulette + int which = roulette(n_events, m); + + if (which < 0) + return; + + // Which roulette happen? + p->rm_virus(m); + + return ; + + } else + throw std::logic_error("This function can only be applied to infected individuals. (SIR)") ; + + return; + + }; + + // Setting up parameters + model.add_param(contact_rate, "Contact rate"); + model.add_param(transmission_rate, "Prob. Transmission"); + model.add_param(recovery_rate, "Prob. Recovery"); + + // state + model.add_state("Susceptible", update_susceptible); + model.add_state("Infected", update_infected); + model.add_state("Recovered"); + + // Global function + epiworld::GlobalFun update = [](epiworld::Model * m) -> void + { + + ModelSIRMixing * m_down = + dynamic_cast *>(m); + + m_down->update_infected_list(); + + return; + + }; + + model.add_globalevent(update, "Update infected individuals"); + + + // Preparing the virus ------------------------------------------- + epiworld::Virus virus(vname, prevalence, true); + virus.set_state( + ModelSIRMixing::INFECTED, + ModelSIRMixing::RECOVERED, + ModelSIRMixing::RECOVERED + ); + + virus.set_prob_infecting(&model("Prob. Transmission")); + virus.set_prob_recovery(&model("Prob. Recovery")); + + model.add_virus(virus); + + model.queuing_off(); // No queuing need + + // Adding the empty population + model.agents_empty_graph(n); + + model.set_name("Susceptible-Infected-Removed (SIR) with Mixing"); + + return; + +} + +template +inline ModelSIRMixing::ModelSIRMixing( + const std::string & vname, + epiworld_fast_uint n, + epiworld_double prevalence, + epiworld_double contact_rate, + epiworld_double transmission_rate, + epiworld_double recovery_rate, + std::vector< double > contact_matrix + ) +{ + + this->contact_matrix = contact_matrix; + + ModelSIRMixing( + *this, + vname, + n, + prevalence, + contact_rate, + transmission_rate, + recovery_rate, + contact_matrix + ); + + return; + +} + +template +inline ModelSIRMixing & ModelSIRMixing::initial_states( + std::vector< double > proportions_, + std::vector< int > /* queue_ */ +) +{ + + Model::initial_states_fun = + create_init_function_sir(proportions_) + ; + + return *this; + +} + +#endif diff --git a/inst/include/epiworld/models/sis.hpp b/inst/include/epiworld/models/sis.hpp index 3736b9e8..e87ee430 100644 --- a/inst/include/epiworld/models/sis.hpp +++ b/inst/include/epiworld/models/sis.hpp @@ -22,14 +22,14 @@ class ModelSIS : public epiworld::Model ModelSIS( ModelSIS & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate ); ModelSIS( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate @@ -40,7 +40,7 @@ class ModelSIS : public epiworld::Model template inline ModelSIS::ModelSIS( ModelSIS & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate @@ -58,14 +58,14 @@ inline ModelSIS::ModelSIS( model.add_param(recovery_rate, "Recovery rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(ModelSIS::INFECTED, ModelSIS::SUSCEPTIBLE, ModelSIS::SUSCEPTIBLE); virus.set_prob_infecting(&model("Transmission rate")); virus.set_prob_recovery(&model("Recovery rate")); virus.set_prob_death(0.0); - model.add_virus(virus, prevalence); + model.add_virus(virus); return; @@ -73,7 +73,7 @@ inline ModelSIS::ModelSIS( template inline ModelSIS::ModelSIS( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate diff --git a/inst/include/epiworld/models/sisd.hpp b/inst/include/epiworld/models/sisd.hpp index 1d3a424b..e7dce9c0 100644 --- a/inst/include/epiworld/models/sisd.hpp +++ b/inst/include/epiworld/models/sisd.hpp @@ -20,7 +20,7 @@ class ModelSISD : public epiworld::Model ModelSISD( ModelSISD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -28,7 +28,7 @@ class ModelSISD : public epiworld::Model ); ModelSISD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -40,7 +40,7 @@ class ModelSISD : public epiworld::Model template inline ModelSISD::ModelSISD( ModelSISD & model, - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, @@ -61,14 +61,14 @@ inline ModelSISD::ModelSISD( model.add_param(death_rate, "Death rate"); // Preparing the virus ------------------------------------------- - epiworld::Virus virus(vname); + epiworld::Virus virus(vname, prevalence, true); virus.set_state(1,0,2); virus.set_prob_infecting(&model("Transmission rate")); virus.set_prob_recovery(&model("Recovery rate")); virus.set_prob_death(0.01); - model.add_virus(virus, prevalence); + model.add_virus(virus); return; @@ -76,7 +76,7 @@ inline ModelSISD::ModelSISD( template inline ModelSISD::ModelSISD( - std::string vname, + const std::string & vname, epiworld_double prevalence, epiworld_double transmission_rate, epiworld_double recovery_rate, diff --git a/inst/include/epiworld/models/surveillance.hpp b/inst/include/epiworld/models/surveillance.hpp index 5afd6abb..6dca7e4e 100644 --- a/inst/include/epiworld/models/surveillance.hpp +++ b/inst/include/epiworld/models/surveillance.hpp @@ -62,7 +62,7 @@ class ModelSURV : public epiworld::Model { ModelSURV( ModelSURV & model, - std::string vname, + const std::string & vname, epiworld_fast_uint prevalence = 50, epiworld_double efficacy_vax = 0.9, epiworld_double latent_period = 3u, @@ -78,7 +78,7 @@ class ModelSURV : public epiworld::Model { ); ModelSURV( - std::string vname, + const std::string & vname, epiworld_fast_uint prevalence = 50, epiworld_double efficacy_vax = 0.9, epiworld_double latent_period = 3u, @@ -99,7 +99,7 @@ class ModelSURV : public epiworld::Model { template inline ModelSURV::ModelSURV( ModelSURV & model, - std::string vname, + const std::string & vname, epiworld_fast_uint prevalence, epiworld_double efficacy_vax, epiworld_double latent_period, @@ -304,14 +304,14 @@ inline ModelSURV::ModelSURV( model.add_param(prob_noreinfect, "Prob. no reinfect"); // Virus ------------------------------------------------------------------ - epiworld::Virus covid("Covid19"); + epiworld::Virus covid("Covid19", prevalence, false); covid.set_state(LATENT, RECOVERED, REMOVED); covid.set_post_immunity(&model("Prob. no reinfect")); covid.set_prob_death(&model("Prob. death")); epiworld::VirusFun ptransmitfun = []( epiworld::Agent * p, - epiworld::Virus & v, + epiworld::Virus &, epiworld::Model * m ) -> epiworld_double { @@ -330,17 +330,17 @@ inline ModelSURV::ModelSURV( covid.set_prob_infecting_fun(ptransmitfun); - model.add_virus_n(covid, prevalence); + model.add_virus(covid); model.set_user_data({"nsampled", "ndetected", "ndetected_asympt", "nasymptomatic"}); model.add_globalevent(surveillance_program, "Surveilance program", -1); // Vaccine tool ----------------------------------------------------------- - epiworld::Tool vax("Vaccine"); + epiworld::Tool vax("Vaccine", prop_vaccinated, true); vax.set_susceptibility_reduction(&model("Vax efficacy")); vax.set_transmission_reduction(&model("Vax redux transmission")); - model.add_tool(vax, prop_vaccinated); + model.add_tool(vax); model.set_name("Surveillance"); @@ -350,7 +350,7 @@ inline ModelSURV::ModelSURV( template inline ModelSURV::ModelSURV( - std::string vname, + const std::string & vname, epiworld_fast_uint prevalence, epiworld_double efficacy_vax, epiworld_double latent_period, diff --git a/inst/include/epiworld/tool-bones.hpp b/inst/include/epiworld/tool-bones.hpp index 8e877eab..475a7522 100644 --- a/inst/include/epiworld/tool-bones.hpp +++ b/inst/include/epiworld/tool-bones.hpp @@ -50,9 +50,15 @@ class Tool { void set_agent(Agent * p, size_t idx); + ToolToAgentFun dist_fun = nullptr; + public: Tool(std::string name = "unknown tool"); - // Tool(TSeq d, std::string name = "unknown tool"); + Tool( + std::string name, + epiworld_double prevalence, + bool as_proportion + ); void set_sequence(TSeq d); void set_sequence(std::shared_ptr d); @@ -107,6 +113,9 @@ class Tool { void print() const; + void distribute(Model * model); + void set_distribution(ToolToAgentFun fun); + }; #endif \ No newline at end of file diff --git a/inst/include/epiworld/tool-distribute-meat.hpp b/inst/include/epiworld/tool-distribute-meat.hpp new file mode 100644 index 00000000..7044f615 --- /dev/null +++ b/inst/include/epiworld/tool-distribute-meat.hpp @@ -0,0 +1,103 @@ +#ifndef TOOL_DISTRIBUTE_MEAT_HPP +#define TOOL_DISTRIBUTE_MEAT_HPP + +/** + * @brief Distributes a tool to a set of agents. + * + * This function takes a vector of agent IDs and returns a lambda function that + * distributes a tool to each agent in the set. + * + * The lambda function takes a reference to a Tool object and a pointer to a + * Model object as parameters. It iterates over the agent IDs and adds the tool + * to each agent using the add_tool() method of the Model object. + * + * @tparam TSeq The sequence type used in the Tool and Model objects. + * @param agents_ids A vector of agent IDs representing the set of agents to + * distribute the tool to. + * @return A lambda function that distributes the tool to the set of agents. + */ +template +inline ToolToAgentFun distribute_tool_to_set( + std::vector< size_t > agents_ids +) { + + return [agents_ids]( + Tool & tool, Model * model + ) -> void + { + // Adding action + for (auto i: agents_ids) + { + model->get_agent(i).add_tool( + tool, + const_cast * >(model) + ); + } + }; + +} + +/** + * Function template to distribute a tool randomly to agents in a model. + * + * @tparam TSeq The sequence type used in the model. + * @param prevalence The prevalence of the tool in the population. + * @param as_proportion Flag indicating whether the prevalence is given as a + * proportion or an absolute value. + * @return A lambda function that distributes the tool randomly to agents in + * the model. + */ +template +inline ToolToAgentFun distribute_tool_randomly( + epiworld_double prevalence, + bool as_proportion = true +) { + + return [prevalence, as_proportion]( + Tool & tool, Model * model + ) -> void { + + // Picking how many + int n_to_distribute; + int n = model->size(); + if (as_proportion) + { + n_to_distribute = static_cast(std::floor(prevalence * n)); + + if (n_to_distribute == n) + n_to_distribute--; + } + else + { + n_to_distribute = static_cast(prevalence); + } + + if (n_to_distribute > n) + throw std::range_error("There are only " + std::to_string(n) + + " individuals in the population. Cannot add the tool to " + std::to_string(n_to_distribute)); + + std::vector< int > idx(n); + std::iota(idx.begin(), idx.end(), 0); + auto & population = model->get_agents(); + for (int i = 0u; i < n_to_distribute; ++i) + { + int loc = static_cast( + floor(model->runif() * n--) + ); + + if ((loc > 0) && (loc == n)) + loc--; + + population[idx[loc]].add_tool( + tool, + const_cast< Model * >(model) + ); + + std::swap(idx[loc], idx[n]); + + } + + }; + +} +#endif \ No newline at end of file diff --git a/inst/include/epiworld/tool-meat.hpp b/inst/include/epiworld/tool-meat.hpp index 604ca2ec..9f8e11a5 100644 --- a/inst/include/epiworld/tool-meat.hpp +++ b/inst/include/epiworld/tool-meat.hpp @@ -86,6 +86,20 @@ inline Tool::Tool(std::string name) set_name(name); } +template +inline Tool::Tool( + std::string name, + epiworld_double prevalence, + bool as_proportion + ) +{ + set_name(name); + + set_distribution( + distribute_tool_randomly(prevalence, as_proportion) + ); +} + // template // inline Tool::Tool(TSeq d, std::string name) { // sequence = std::make_shared(d); @@ -497,4 +511,23 @@ inline void Tool::print() const } +template +inline void Tool::distribute(Model * model) +{ + + if (dist_fun) + { + + dist_fun(*this, model); + + } + +} + +template +inline void Tool::set_distribution(ToolToAgentFun fun) +{ + dist_fun = fun; +} + #endif \ No newline at end of file diff --git a/inst/include/epiworld/virus-bones.hpp b/inst/include/epiworld/virus-bones.hpp index 81a88368..94cbbcaa 100644 --- a/inst/include/epiworld/virus-bones.hpp +++ b/inst/include/epiworld/virus-bones.hpp @@ -54,9 +54,18 @@ class Virus { epiworld_fast_int queue_post = -Queue::Everyone; ///< Change of state when removed from agent. epiworld_fast_int queue_removed = -99; ///< Change of state when agent is removed + // Information about how distribution works + VirusToAgentFun dist_fun = nullptr; + public: Virus(std::string name = "unknown virus"); + Virus( + std::string name, + epiworld_double prevalence, + bool as_proportion + ); + void mutate(Model * model); void set_mutation(MutFun fun); @@ -156,6 +165,15 @@ class Virus { void print() const; + /** + * @brief Get information about the prevalence of the virus + */ + ///@{ + void distribute(Model * model); + void set_distribution(VirusToAgentFun fun); + ///@} + + }; #endif \ No newline at end of file diff --git a/inst/include/epiworld/virus-distribute-meat.hpp b/inst/include/epiworld/virus-distribute-meat.hpp new file mode 100644 index 00000000..61313d1e --- /dev/null +++ b/inst/include/epiworld/virus-distribute-meat.hpp @@ -0,0 +1,117 @@ +#ifndef EPIWORLD_VIRUS_DISTRIBUTE_MEAT_HPP +#define EPIWORLD_VIRUS_DISTRIBUTE_MEAT_HPP + +/** + * Distributes a virus to a set of agents. + * + * This function takes a vector of agent IDs and returns a lambda function that + * can be used to distribute a virus to the specified agents. + * + * @param agents_ids A vector of agent IDs representing the set of agents to + * distribute the virus to. + * + * @return A lambda function that takes a Virus object and a Model object and + * distributes the virus to the specified agents. + */ +template +inline VirusToAgentFun distribute_virus_to_set( + std::vector< size_t > agents_ids +) { + + return [agents_ids]( + Virus & virus, Model * model + ) -> void + { + // Adding action + for (auto i: agents_ids) + { + model->get_agent(i).set_virus( + virus, + const_cast * >(model) + ); + } + }; + +} + +/** + * @brief Distributes a virus randomly to agents. + * + * This function takes a sequence of agents and randomly assigns a virus to + * each agent. + * + * @tparam TSeq The type of the sequence of agents. + * @param agents The sequence of agents to distribute the virus to. + * @return A function object that assigns a virus to each agent randomly. + */ +template +inline VirusToAgentFun distribute_virus_randomly( + epiworld_double prevalence, + bool prevalence_as_proportion = true +) { + + return [prevalence,prevalence_as_proportion]( + Virus & virus, Model * model + ) -> void + { + + // Figuring out how what agents are available + std::vector< size_t > idx; + for (const auto & agent: model->get_agents()) + if (agent.get_virus() == nullptr) + idx.push_back(agent.get_id()); + + // Picking how many + size_t n = model->size(); + int n_available = static_cast(idx.size()); + int n_to_sample; + if (prevalence_as_proportion) + { + n_to_sample = static_cast(std::floor(prevalence * n)); + + if (n_to_sample == static_cast(n)) + n_to_sample--; + } + else + { + n_to_sample = static_cast(prevalence); + } + + if (n_to_sample > n_available) + throw std::range_error( + "There are only " + std::to_string(n_available) + + " individuals with no virus in the population. " + + "Cannot add the virus to " + + std::to_string(n_to_sample) + ); + + auto & population = model->get_agents(); + for (int i = 0; i < n_to_sample; ++i) + { + + int loc = static_cast( + floor(model->runif() * (n_available--)) + ); + + // Correcting for possible overflow + if ((loc > 0) && (loc >= n_available)) + loc = n_available - 1; + + Agent & agent = population[idx[loc]]; + + // Adding action + agent.set_virus( + virus, + const_cast * >(model) + ); + + // Adjusting sample + std::swap(idx[loc], idx[n_available]); + + } + + }; + +} + +#endif \ No newline at end of file diff --git a/inst/include/epiworld/virus-meat.hpp b/inst/include/epiworld/virus-meat.hpp index e27bc91f..703a4adf 100644 --- a/inst/include/epiworld/virus-meat.hpp +++ b/inst/include/epiworld/virus-meat.hpp @@ -56,10 +56,10 @@ inline VirusFun virus_fun_logit( for (auto c: coefs) coefs_f.push_back(static_cast(c)); - VirusFun fun_infect = [coefs_f,vars,logit]( + VirusFun fun_infect = [coefs_f,vars]( Agent * agent, - Virus & virus, - Model * model + Virus &, + Model * ) -> epiworld_double { size_t K = coefs_f.size(); @@ -80,10 +80,27 @@ inline VirusFun virus_fun_logit( } template -inline Virus::Virus(std::string name) { +inline Virus::Virus( + std::string name + ) { set_name(name); } +template +inline Virus::Virus( + std::string name, + epiworld_double prevalence, + bool prevalence_as_proportion + ) { + set_name(name); + set_distribution( + distribute_virus_randomly( + prevalence, + prevalence_as_proportion + ) + ); +} + template inline void Virus::mutate( Model * model @@ -686,4 +703,23 @@ inline void Virus::print() const } +template +inline void Virus::distribute(Model * model) +{ + + if (dist_fun) + { + + dist_fun(*this, model); + + } + +} + +template +inline void Virus::set_distribution(VirusToAgentFun fun) +{ + dist_fun = fun; +} + #endif diff --git a/inst/tinytest/test-mixing.R b/inst/tinytest/test-mixing.R new file mode 100644 index 00000000..172097cc --- /dev/null +++ b/inst/tinytest/test-mixing.R @@ -0,0 +1,64 @@ +library(epiworldR) + +e1 <- entity("Population 1", 3e3, FALSE) +e2 <- entity("Population 2", 3e3, FALSE) +e3 <- entity("Population 3", 3e3, FALSE) + +# Row-stochastic matrix (rowsums 1) +cmatrix <- c( + c(1, 0, 0), + c(0, 1, 0), + c(0, 0, 1) +) |> as.double() |> matrix(byrow = TRUE, nrow = 3) + +N <- 9e3 + +flu_model <- ModelSEIRMixing( + name = "Flu", + n = N, + prevalence = 1 / N, + contact_rate = 40, + transmission_rate = 1.0, + recovery_rate = 1 / 10, + incubation_days = .009, + contact_matrix = cmatrix +) + +# Adding the entities +flu_model |> + add_entity(e1) |> + add_entity(e2) |> + add_entity(e3) + +run(flu_model, ndays = 100, seed = 1233) +summary(flu_model) + + +library(data.table) + +agents_entities <- lapply(get_entities(flu_model), \(e) { + entity_get_agents(e) +}) |> rbindlist() + +transmissions <- get_transmissions(flu_model) |> + data.table() + +# We only need the date and the source +transmissions <- subset( + transmissions, + select = c("date", "target") +) + +# Attaching the entity to the source +transmissions <- merge( + transmissions, + agents_entities, + by.x = "target", by.y = "agent" +) + +# Aggregating by date x entity (counts) +transmissions <- transmissions[, .N, by = .(date, entity)] + +transmissions[, sum(N), by = .(entity)] + +transmissions[, table(entity)] diff --git a/man/ModelDiffNet.Rd b/man/ModelDiffNet.Rd index 1e0aa56d..e8ed956c 100644 --- a/man/ModelDiffNet.Rd +++ b/man/ModelDiffNet.Rd @@ -94,11 +94,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSEIR.Rd b/man/ModelSEIR.Rd index d1b82237..8f8f4958 100644 --- a/man/ModelSEIR.Rd +++ b/man/ModelSEIR.Rd @@ -73,11 +73,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSEIRCONN.Rd b/man/ModelSEIRCONN.Rd index ee80beb9..c885d54c 100644 --- a/man/ModelSEIRCONN.Rd +++ b/man/ModelSEIRCONN.Rd @@ -72,8 +72,8 @@ model_seirconn plot(model_seirconn) # Adding the flu -flu <- virus("Flu", .9, 1/7) -add_virus(model_seirconn, flu, .001) +flu <- virus("Flu", .9, 1/7, prevalence = 0.001, as_proportion = TRUE) +add_virus(model_seirconn, flu) #' # Running and printing run(model_seirconn, ndays = 100, seed = 1912) @@ -89,11 +89,13 @@ Other Models: \code{\link{ModelSEIR}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSEIRD.Rd b/man/ModelSEIRD.Rd index b28700a4..c32c3496 100644 --- a/man/ModelSEIRD.Rd +++ b/man/ModelSEIRD.Rd @@ -84,11 +84,13 @@ Other Models: \code{\link{ModelSEIR}()}, \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSEIRDCONN.Rd b/man/ModelSEIRDCONN.Rd index ad691dd1..bb535114 100644 --- a/man/ModelSEIRDCONN.Rd +++ b/man/ModelSEIRDCONN.Rd @@ -83,8 +83,12 @@ model_seirdconn plot(model_seirdconn) # Adding the flu -flu <- virus("Flu", prob_infecting = .3, recovery_rate = 1/7, prob_death = 0.001) -add_virus(model = model_seirdconn, virus = flu, proportion = .001) +flu <- virus( + "Flu", prob_infecting = .3, recovery_rate = 1/7, + prob_death = 0.001, + prevalence = 0.001, as_proportion = TRUE +) +add_virus(model = model_seirdconn, virus = flu) #' # Running and printing run(model_seirdconn, ndays = 100, seed = 1912) @@ -100,11 +104,13 @@ Other Models: \code{\link{ModelSEIR}()}, \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSEIRMixing.Rd b/man/ModelSEIRMixing.Rd new file mode 100644 index 00000000..5ecaff8c --- /dev/null +++ b/man/ModelSEIRMixing.Rd @@ -0,0 +1,127 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ModelSEIRMixing.R +\name{ModelSEIRMixing} +\alias{ModelSEIRMixing} +\alias{epiworld_seirmixing} +\alias{plot.epiworld_seirmixing} +\title{Susceptible Exposed Infected Removed model (SEIR) with mixing} +\usage{ +ModelSEIRMixing( + name, + n, + prevalence, + contact_rate, + transmission_rate, + incubation_days, + recovery_rate, + contact_matrix +) + +\method{plot}{epiworld_seirmixing}(x, main = get_name(x), ...) +} +\arguments{ +\item{name}{String. Name of the virus} + +\item{n}{Number of individuals in the population.} + +\item{prevalence}{Double. Initial proportion of individuals with the virus.} + +\item{contact_rate}{Numeric scalar. Average number of contacts per step.} + +\item{transmission_rate}{Numeric scalar between 0 and 1. Probability of +transmission.} + +\item{incubation_days}{Numeric scalar. Average number of days in the +incubation period.} + +\item{recovery_rate}{Numeric scalar between 0 and 1. Probability of recovery.} + +\item{contact_matrix}{Matrix of contact rates between individuals.} + +\item{x}{Object of class SIRCONN.} + +\item{main}{Title of the plot} + +\item{...}{Currently ignore.} +} +\value{ +\itemize{ +\item The \code{ModelSEIRMixing}function returns a model of class \link{epiworld_model}. +} + +The \code{plot} function returns a plot of the SEIRMixing model of class +\link{epiworld_model}. +} +\description{ +Susceptible Exposed Infected Removed model (SEIR) with mixing +} +\details{ +The \code{contact_matrix} is a matrix of contact rates between entities. The +matrix should be of size \verb{n x n}, where \code{n} is the number of entities. +This is a row-stochastic matrix, i.e., the sum of each row should be 1. + +The \link{initial_states} function allows the user to set the initial state of the +model. In particular, the user can specify how many of the non-infected +agents have been removed at the beginning of the simulation. +} +\examples{ + +# Start off creating three entities. +# Individuals will be distribured randomly between the three. +e1 <- entity("Population 1", 3e3, as_proportion = FALSE) +e2 <- entity("Population 2", 3e3, as_proportion = FALSE) +e3 <- entity("Population 3", 3e3, as_proportion = FALSE) + +# Row-stochastic matrix (rowsums 1) +cmatrix <- c( + c(0.9, 0.05, 0.05), + c(0.1, 0.8, 0.1), + c(0.1, 0.2, 0.7) +) |> matrix(byrow = TRUE, nrow = 3) + +N <- 9e3 + +flu_model <- ModelSEIRMixing( + name = "Flu", + n = N, + prevalence = 1 / N, + contact_rate = 20, + transmission_rate = 0.1, + recovery_rate = 1 / 7, + incubation_days = 7, + contact_matrix = cmatrix +) + +# Adding the entities to the model +flu_model |> + add_entity(e1) |> + add_entity(e2) |> + add_entity(e3) + +set.seed(331) +run(flu_model, ndays = 100) +summary(flu_model) +plot_incidence(flu_model) + +} +\seealso{ +epiworld-methods + +Other Models: +\code{\link{ModelDiffNet}()}, +\code{\link{ModelSEIR}()}, +\code{\link{ModelSEIRCONN}()}, +\code{\link{ModelSEIRD}()}, +\code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSIR}()}, +\code{\link{ModelSIRCONN}()}, +\code{\link{ModelSIRD}()}, +\code{\link{ModelSIRDCONN}()}, +\code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, +\code{\link{ModelSIS}()}, +\code{\link{ModelSISD}()}, +\code{\link{ModelSURV}()}, +\code{\link{epiworld-data}} +} +\concept{Models} diff --git a/man/ModelSIR.Rd b/man/ModelSIR.Rd index 31edd394..eafb9a3c 100644 --- a/man/ModelSIR.Rd +++ b/man/ModelSIR.Rd @@ -73,10 +73,12 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSIRCONN.Rd b/man/ModelSIRCONN.Rd index d01a1f34..e6e21556 100644 --- a/man/ModelSIRCONN.Rd +++ b/man/ModelSIRCONN.Rd @@ -78,10 +78,12 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSIRD.Rd b/man/ModelSIRD.Rd index 81c9936b..c2b92ca0 100644 --- a/man/ModelSIRD.Rd +++ b/man/ModelSIRD.Rd @@ -81,10 +81,12 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSIRDCONN.Rd b/man/ModelSIRDCONN.Rd index b499ddd1..717d7321 100644 --- a/man/ModelSIRDCONN.Rd +++ b/man/ModelSIRDCONN.Rd @@ -83,10 +83,12 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSIRLogit.Rd b/man/ModelSIRLogit.Rd index 61554e0b..3201c78c 100644 --- a/man/ModelSIRLogit.Rd +++ b/man/ModelSIRLogit.Rd @@ -98,10 +98,12 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, diff --git a/man/ModelSIRMixing.Rd b/man/ModelSIRMixing.Rd new file mode 100644 index 00000000..570d6f55 --- /dev/null +++ b/man/ModelSIRMixing.Rd @@ -0,0 +1,123 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ModelSIRMixing.R +\name{ModelSIRMixing} +\alias{ModelSIRMixing} +\alias{epiworld_sirmixing} +\alias{plot.epiworld_sirmixing} +\title{Susceptible Infected Removed model (SIR) with mixing} +\usage{ +ModelSIRMixing( + name, + n, + prevalence, + contact_rate, + transmission_rate, + recovery_rate, + contact_matrix +) + +\method{plot}{epiworld_sirmixing}(x, main = get_name(x), ...) +} +\arguments{ +\item{name}{String. Name of the virus} + +\item{n}{Number of individuals in the population.} + +\item{prevalence}{Double. Initial proportion of individuals with the virus.} + +\item{contact_rate}{Numeric scalar. Average number of contacts per step.} + +\item{transmission_rate}{Numeric scalar between 0 and 1. Probability of +transmission.} + +\item{recovery_rate}{Numeric scalar between 0 and 1. Probability of recovery.} + +\item{contact_matrix}{Matrix of contact rates between individuals.} + +\item{x}{Object of class SIRCONN.} + +\item{main}{Title of the plot} + +\item{...}{Currently ignore.} +} +\value{ +\itemize{ +\item The \code{ModelSIRMixing}function returns a model of class \link{epiworld_model}. +} + +The \code{plot} function returns a plot of the SIRMixing model of class +\link{epiworld_model}. +} +\description{ +Susceptible Infected Removed model (SIR) with mixing +} +\details{ +The \code{contact_matrix} is a matrix of contact rates between entities. The +matrix should be of size \verb{n x n}, where \code{n} is the number of entities. +This is a row-stochastic matrix, i.e., the sum of each row should be 1. + +The \link{initial_states} function allows the user to set the initial state of the +model. In particular, the user can specify how many of the non-infected +agents have been removed at the beginning of the simulation. +} +\examples{ +# From the vignette + +# Start off creating three entities. +# Individuals will be distribured randomly between the three. +e1 <- entity("Population 1", 3e3, as_proportion = FALSE) +e2 <- entity("Population 2", 3e3, as_proportion = FALSE) +e3 <- entity("Population 3", 3e3, as_proportion = FALSE) + +# Row-stochastic matrix (rowsums 1) +cmatrix <- c( + c(0.9, 0.05, 0.05), + c(0.1, 0.8, 0.1), + c(0.1, 0.2, 0.7) +) |> matrix(byrow = TRUE, nrow = 3) + +N <- 9e3 + +flu_model <- ModelSIRMixing( + name = "Flu", + n = N, + prevalence = 1 / N, + contact_rate = 20, + transmission_rate = 0.1, + recovery_rate = 1 / 7, + contact_matrix = cmatrix +) + +# Adding the entities to the model +flu_model |> + add_entity(e1) |> + add_entity(e2) |> + add_entity(e3) + +set.seed(331) +run(flu_model, ndays = 100) +summary(flu_model) +plot_incidence(flu_model) + +} +\seealso{ +epiworld-methods + +Other Models: +\code{\link{ModelDiffNet}()}, +\code{\link{ModelSEIR}()}, +\code{\link{ModelSEIRCONN}()}, +\code{\link{ModelSEIRD}()}, +\code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, +\code{\link{ModelSIR}()}, +\code{\link{ModelSIRCONN}()}, +\code{\link{ModelSIRD}()}, +\code{\link{ModelSIRDCONN}()}, +\code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIS}()}, +\code{\link{ModelSISD}()}, +\code{\link{ModelSURV}()}, +\code{\link{epiworld-data}} +} +\concept{Models} diff --git a/man/ModelSIS.Rd b/man/ModelSIS.Rd index 0f3058c6..8b0a8994 100644 --- a/man/ModelSIS.Rd +++ b/man/ModelSIS.Rd @@ -69,11 +69,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()}, \code{\link{epiworld-data}} diff --git a/man/ModelSISD.Rd b/man/ModelSISD.Rd index 29ad0a38..4e164ab5 100644 --- a/man/ModelSISD.Rd +++ b/man/ModelSISD.Rd @@ -76,11 +76,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSURV}()}, \code{\link{epiworld-data}} diff --git a/man/ModelSURV.Rd b/man/ModelSURV.Rd index 9fb97e53..90dc9aa8 100644 --- a/man/ModelSURV.Rd +++ b/man/ModelSURV.Rd @@ -118,11 +118,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{epiworld-data}} diff --git a/man/get_agents.Rd b/man/agents.Rd similarity index 95% rename from man/get_agents.Rd rename to man/agents.Rd index a079359c..f5d7e74b 100644 --- a/man/get_agents.Rd +++ b/man/agents.Rd @@ -1,8 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/agents-methods.R -\name{get_agents} +\name{agents} +\alias{agents} \alias{get_agents} \alias{epiworld_agents} +\alias{get_agents.epiworld_model} \alias{[.epiworld_agents} \alias{epiworld_agent} \alias{print.epiworld_agent} @@ -10,7 +12,9 @@ \alias{get_state} \title{Agents in epiworldR} \usage{ -get_agents(model) +get_agents(model, ...) + +\method{get_agents}{epiworld_model}(model, ...) \method{[}{epiworld_agents}(x, i) @@ -23,6 +27,8 @@ get_state(x) \arguments{ \item{model}{An object of class \link{epiworld_model}.} +\item{...}{Ignored} + \item{x}{An object of class \link{epiworld_agents}} \item{i}{Index (id) of the agent (from 0 to \code{n-1})} @@ -30,8 +36,6 @@ get_state(x) \item{compressed}{Logical scalar. When FALSE, it prints detailed information about the agent.} -\item{...}{Ignored} - \item{max_print}{Integer scalar. Maximum number of agents to print.} } \value{ diff --git a/man/agents_smallworld.Rd b/man/agents_smallworld.Rd index afa19178..36717930 100644 --- a/man/agents_smallworld.Rd +++ b/man/agents_smallworld.Rd @@ -2,7 +2,6 @@ % Please edit documentation in R/agents.R, R/tool.R \name{agents_smallworld} \alias{agents_smallworld} -\alias{agents} \alias{agents_from_edgelist} \alias{get_network} \alias{network} diff --git a/man/entities.Rd b/man/entities.Rd new file mode 100644 index 00000000..9f0724d2 --- /dev/null +++ b/man/entities.Rd @@ -0,0 +1,136 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/entity.R +\name{entities} +\alias{entities} +\alias{get_entities} +\alias{[.epiworld_entities} +\alias{entity} +\alias{get_entity_size} +\alias{get_entity_name} +\alias{entity_add_agent} +\alias{rm_entity} +\alias{add_entity} +\alias{load_agents_entities_ties} +\alias{entity_get_agents} +\alias{distribute_entity_randomly} +\alias{distribute_entity_to_set} +\alias{set_distribution_entity} +\title{Get entities} +\usage{ +get_entities(model) + +\method{[}{epiworld_entities}(x, i) + +entity(name, prevalence, as_proportion, to_unassigned = TRUE) + +get_entity_size(entity) + +get_entity_name(entity) + +entity_add_agent(entity, agent, model = attr(entity, "model")) + +rm_entity(model, id) + +add_entity(model, entity) + +load_agents_entities_ties(model, agents_id, entities_id) + +entity_get_agents(entity) + +distribute_entity_randomly(prevalence, as_proportion, to_unassigned = TRUE) + +distribute_entity_to_set(agents_ids) + +set_distribution_entity(entity, distfun) +} +\arguments{ +\item{model}{Model object of class \code{epiworld_model}.} + +\item{x}{Object of class \code{epiworld_entities}.} + +\item{i}{Integer index.} + +\item{name}{Character scalar. Name of the entity.} + +\item{prevalence}{Numeric scalar. Prevalence of the entity.} + +\item{as_proportion}{Logical scalar. If \code{TRUE}, \code{prevalence} is interpreted +as a proportion.} + +\item{to_unassigned}{Logical scalar. If \code{TRUE}, the entity is added to the +unassigned pool.} + +\item{entity}{Entity object of class \code{epiworld_entity}.} + +\item{agent}{Agent object of class \code{epiworld_agent}.} + +\item{id}{Integer scalar. Entity id to remove (starting from zero).} + +\item{agents_id}{Integer vector.} + +\item{entities_id}{Integer vector.} + +\item{agents_ids}{Integer vector. Ids of the agents to distribute.} + +\item{distfun}{Distribution function object of class \code{epiworld_distribution_entity}.} +} +\value{ +\itemize{ +\item The function \code{entity} creates an entity object. +} + +\itemize{ +\item The function \code{get_entity_size} returns the number of agents in the entity. +} + +\itemize{ +\item The function \code{get_entity_name} returns the name of the entity. +} + +\itemize{ +\item The function \code{entity_add_agent} adds an agent to the entity. +} + +\itemize{ +\item The function \code{rm_entity} removes an entity from the model. +} + +\itemize{ +\item The function \code{load_agents_entities_ties} loads agents into entities. +} + +\itemize{ +\item The function \code{entity_get_agents} returns an integer vector with the agents +in the entity (ids). +} +} +\description{ +Entities in \code{epiworld} are objects that can contain agents. +} +\details{ +Epiworld entities are especially useful for mixing models, particularly +\link{ModelSIRMixing} and \link{ModelSEIRMixing}. +} +\examples{ +# Creating a mixing model +mymodel <- ModelSIRMixing( + name = "My model", + n = 10000, + prevalence = .001, + contact_rate = 10, + transmission_rate = .1, + recovery_rate = 1/7, + contact_matrix = matrix(c(.9, .1, .1, .9), 2, 2) +) + +ent1 <- entity("First", 5000, FALSE) +ent2 <- entity("Second", 5000, FALSE) + +mymodel |> + add_entity(ent1) |> + add_entity(ent2) + +run(mymodel, ndays = 100, seed = 1912) + +summary(mymodel) +} diff --git a/man/epiworld-data.Rd b/man/epiworld-data.Rd index 5b592fb3..3390eee0 100644 --- a/man/epiworld-data.Rd +++ b/man/epiworld-data.Rd @@ -240,11 +240,13 @@ Other Models: \code{\link{ModelSEIRCONN}()}, \code{\link{ModelSEIRD}()}, \code{\link{ModelSEIRDCONN}()}, +\code{\link{ModelSEIRMixing}()}, \code{\link{ModelSIR}()}, \code{\link{ModelSIRCONN}()}, \code{\link{ModelSIRD}()}, \code{\link{ModelSIRDCONN}()}, \code{\link{ModelSIRLogit}()}, +\code{\link{ModelSIRMixing}()}, \code{\link{ModelSIS}()}, \code{\link{ModelSISD}()}, \code{\link{ModelSURV}()} diff --git a/man/epiworld-methods.Rd b/man/epiworld-methods.Rd index 0f175377..3d5deab2 100644 --- a/man/epiworld-methods.Rd +++ b/man/epiworld-methods.Rd @@ -270,7 +270,7 @@ get_agents_data_ncols(model_sirconn) # Returns number of columns get_virus(model_sirconn, 0) # Returns information about the first virus in # the model (index begins at 0). -add_tool(model_sirconn, tool("Vaccine", .9, .9, .5, 1), proportion = .5) +add_tool(model_sirconn, tool("Vaccine", .9, .9, .5, 1, prevalence = 0.5, as_prop = TRUE)) get_tool(model_sirconn, 0) # Returns information about the first tool in the # model. In this case, there are no tools so an # error message will occur. diff --git a/man/epiworldR-deprecated.Rd b/man/epiworldR-deprecated.Rd index 15cdec94..e58dff4c 100644 --- a/man/epiworldR-deprecated.Rd +++ b/man/epiworldR-deprecated.Rd @@ -2,12 +2,18 @@ % Please edit documentation in R/functions-renamed.R, R/global-actions.R \name{epiworldR-deprecated} \alias{epiworldR-deprecated} +\alias{add_tool_n} +\alias{add_virus_n} \alias{globalaction_tool} \alias{globalaction_tool_logit} \alias{globalaction_set_params} \alias{globalaction_fun} -\title{Deprecated functions in epiworldR} +\title{Deprecated and removed functions in epiworldR} \usage{ +add_tool_n(model, tool, n) + +add_virus_n(model, virus, n) + globalaction_tool(...) globalaction_tool_logit(...) @@ -17,6 +23,14 @@ globalaction_set_params(...) globalaction_fun(...) } \arguments{ +\item{model}{Model object of class \code{epiworld_model}.} + +\item{tool}{Tool object of class \code{epiworld_tool}.} + +\item{n}{Deprecated.} + +\item{virus}{Virus object of class \code{epiworld_virus}.} + \item{...}{Arguments to be passed to the new function.} } \description{ diff --git a/man/figures/README-logit-model-1.png b/man/figures/README-logit-model-1.png new file mode 100644 index 00000000..3cd4a67d Binary files /dev/null and b/man/figures/README-logit-model-1.png differ diff --git a/man/figures/README-multiple-example-1.png b/man/figures/README-multiple-example-1.png new file mode 100644 index 00000000..819f2583 Binary files /dev/null and b/man/figures/README-multiple-example-1.png differ diff --git a/man/figures/README-pressure-1.png b/man/figures/README-pressure-1.png deleted file mode 100644 index 7bc4c8f7..00000000 Binary files a/man/figures/README-pressure-1.png and /dev/null differ diff --git a/man/figures/README-seir-conn-figures-1.png b/man/figures/README-seir-conn-figures-1.png new file mode 100644 index 00000000..fe7d1ac4 Binary files /dev/null and b/man/figures/README-seir-conn-figures-1.png differ diff --git a/man/figures/README-seir-conn-figures-2.png b/man/figures/README-seir-conn-figures-2.png new file mode 100644 index 00000000..dcdf7d07 Binary files /dev/null and b/man/figures/README-seir-conn-figures-2.png differ diff --git a/man/figures/README-seir-conn-figures-3.png b/man/figures/README-seir-conn-figures-3.png new file mode 100644 index 00000000..516d9201 Binary files /dev/null and b/man/figures/README-seir-conn-figures-3.png differ diff --git a/man/figures/README-sir-figures-1.png b/man/figures/README-sir-figures-1.png new file mode 100644 index 00000000..7203b10e Binary files /dev/null and b/man/figures/README-sir-figures-1.png differ diff --git a/man/figures/README-sir-figures-2.png b/man/figures/README-sir-figures-2.png new file mode 100644 index 00000000..1963afc5 Binary files /dev/null and b/man/figures/README-sir-figures-2.png differ diff --git a/man/figures/README-unnamed-chunk-2-1.png b/man/figures/README-unnamed-chunk-2-1.png deleted file mode 100644 index 4be5db82..00000000 Binary files a/man/figures/README-unnamed-chunk-2-1.png and /dev/null differ diff --git a/man/figures/README-unnamed-chunk-5-1.png b/man/figures/README-unnamed-chunk-5-1.png deleted file mode 100644 index 206831fa..00000000 Binary files a/man/figures/README-unnamed-chunk-5-1.png and /dev/null differ diff --git a/man/global-actions.Rd b/man/global-actions.Rd index 062aedf3..dd4c819e 100644 --- a/man/global-actions.Rd +++ b/man/global-actions.Rd @@ -113,6 +113,8 @@ model_sirconn <- ModelSIRCONN( # Creating a tool epitool <- tool( name = "Vaccine", + prevalence = 0, + as_proportion = FALSE, susceptibility_reduction = .9, transmission_reduction = .5, recovery_enhancer = .5, diff --git a/man/tool.Rd b/man/tool.Rd index cef1eecb..f5beaa0c 100644 --- a/man/tool.Rd +++ b/man/tool.Rd @@ -6,7 +6,6 @@ \alias{set_name_tool} \alias{get_name_tool} \alias{add_tool} -\alias{add_tool_n} \alias{rm_tool} \alias{tool_fun_logit} \alias{set_susceptibility_reduction} @@ -22,10 +21,15 @@ \alias{set_death_reduction_ptr} \alias{set_death_reduction_fun} \alias{print.epiworld_agents_tools} +\alias{set_distribution_tool} +\alias{distribute_tool_randomly} +\alias{distribute_tool_to_set} \title{Tools in epiworld} \usage{ tool( name, + prevalence, + as_proportion, susceptibility_reduction, transmission_reduction, recovery_enhancer, @@ -38,8 +42,6 @@ get_name_tool(tool) add_tool(model, tool, proportion) -add_tool_n(model, tool, n) - rm_tool(model, tool_pos) tool_fun_logit(vars, coefs, model) @@ -69,10 +71,21 @@ set_death_reduction_ptr(tool, model, param) set_death_reduction_fun(tool, model, tfun) \method{print}{epiworld_agents_tools}(x, max_print = 10, ...) + +set_distribution_tool(tool, distfun) + +distribute_tool_randomly(prevalence, as_proportion) + +distribute_tool_to_set(agents_ids) } \arguments{ \item{name}{Name of the tool} +\item{prevalence}{Numeric scalar. Prevalence of the tool.} + +\item{as_proportion}{Logical scalar. If \code{TRUE}, \code{prevalence} is interpreted +as a proportion of the total number of agents in the model.} + \item{susceptibility_reduction}{Numeric. Proportion it reduces susceptibility.} \item{transmission_reduction}{Numeric. Proportion it reduces transmission.} @@ -85,9 +98,7 @@ set_death_reduction_fun(tool, model, tfun) \item{model}{Model} -\item{proportion}{In the case of \code{add_tool}, a proportion, otherwise, an integer.} - -\item{n}{A positive integer. Number of agents to initially have the tool.} +\item{proportion}{Deprecated.} \item{tool_pos}{Positive integer. Index of the tool's position in the model.} @@ -109,6 +120,11 @@ will be added to the tool (see details).} \item{max_print}{Numeric scalar. Maximum number of tools to print.} \item{...}{Currently ignored.} + +\item{distfun}{An object of class \code{epiworld_tool_distfun}.} + +\item{agents_ids}{Integer vector. Indices of the agents to which the tool +will be assigned.} } \value{ \itemize{ @@ -125,11 +141,6 @@ will be added to the tool (see details).} \link{epiworld_tool}. } -\itemize{ -\item The \code{add_tool_n} function adds the specified tool to the model of class -\link{epiworld_model} with specified count n. -} - \itemize{ \item The \code{rm_tool} function removes the specified tool from a model. } @@ -153,6 +164,16 @@ to the specified tool of class \link{epiworld_tool}. \item The \code{set_death_reduction} function assigns a probability decrease to the specified tool of class \link{epiworld_tool}. } + +\itemize{ +\item The \code{distribute_tool_randomly} function returns a distribution function of +class \code{epiworld_tool_distfun}. +} + +\itemize{ +\item The \code{distribute_tool_to_set} function returns a distribution function of +class \code{epiworld_tool_distfun}. +} } \description{ Tools are functions that affect how agents react to the virus. They can be @@ -171,6 +192,17 @@ In the case of \code{set_susceptibility_reduction_ptr}, \code{set_transmission_r \code{set_death_reduction_ptr}, the corresponding parameters are passed as a pointer to the tool. The implication of using pointers is that the values will be read directly from the \code{model} object, so changes will be reflected. + +The \code{set_distribution_tool} function assigns a distribution function to the +specified tool of class \link{epiworld_tool}. The distribution function can be +created using the functions \code{\link[=distribute_tool_randomly]{distribute_tool_randomly()}} and +\code{\link[=distribute_tool_to_set]{distribute_tool_to_set()}}. + +The \code{distribute_tool_randomly} function creates a distribution function that +randomly assigns the tool to a proportion of the population. + +The \code{distribute_tool_to_set} function creates a distribution function that +assigns the tool to a set of agents. } \examples{ # Simple model @@ -189,6 +221,8 @@ plot(model_sirconn) epitool <- tool( name = "Vaccine", + prevalence = 0.5, + as_proportion = TRUE, susceptibility_reduction = .9, transmission_reduction = .5, recovery_enhancer = .5, @@ -199,14 +233,16 @@ epitool set_name_tool(epitool, 'Pfizer') # Assigning name to the tool get_name_tool(epitool) # Returning the name of the tool -add_tool(model_sirconn, epitool, .5) +add_tool(model_sirconn, epitool) run(model_sirconn, ndays = 100, seed = 1912) model_sirconn plot(model_sirconn) # To declare a certain number of individuals with the tool rm_tool(model_sirconn, 0) # Removing epitool from the model -add_tool_n(model_sirconn, epitool, 5500) +# Setting prevalence to 0.1 +set_distribution_tool(epitool, distribute_tool_randomly(0.1, TRUE)) +add_tool(model_sirconn, epitool) run(model_sirconn, ndays = 100, seed = 1912) # Adjusting probabilities due to tool @@ -214,6 +250,9 @@ set_susceptibility_reduction(epitool, 0.1) # Susceptibility reduction set_transmission_reduction(epitool, 0.2) # Transmission reduction set_recovery_enhancer(epitool, 0.15) # Probability increase of recovery set_death_reduction(epitool, 0.05) # Probability reduction of death + +rm_tool(model_sirconn, 0) +add_tool(model_sirconn, epitool) run(model_sirconn, ndays = 100, seed = 1912) # Run model to view changes @@ -235,13 +274,15 @@ agents_smallworld( # Creating a tool mask_wearing <- tool( name = "Mask", + prevalence = 0.5, + as_proportion = TRUE, susceptibility_reduction = 0.0, transmission_reduction = 0.3, # Only transmission recovery_enhancer = 0.0, death_reduction = 0.0 ) -add_tool(sir, mask_wearing, .5) +add_tool(sir, mask_wearing) run(sir, ndays = 50, seed = 11) hist_0 <- get_hist_total(sir) diff --git a/man/virus.Rd b/man/virus.Rd index 617f6877..b911a4ef 100644 --- a/man/virus.Rd +++ b/man/virus.Rd @@ -6,7 +6,6 @@ \alias{set_name_virus} \alias{get_name_virus} \alias{add_virus} -\alias{add_virus_n} \alias{virus_set_state} \alias{rm_virus} \alias{virus_fun_logit} @@ -22,10 +21,15 @@ \alias{set_incubation} \alias{set_incubation_ptr} \alias{set_incubation_fun} +\alias{set_distribution_virus} +\alias{distribute_virus_randomly} +\alias{distribute_virus_set} \title{Virus design} \usage{ virus( name, + prevalence, + as_proportion, prob_infecting, recovery_rate = 0.5, prob_death = 0, @@ -39,8 +43,6 @@ get_name_virus(virus) add_virus(model, virus, proportion) -add_virus_n(model, virus, n) - virus_set_state(virus, init, end, removed) rm_virus(model, virus_pos) @@ -70,10 +72,21 @@ set_incubation(virus, incubation) set_incubation_ptr(virus, model, param) set_incubation_fun(virus, model, vfun) + +set_distribution_virus(virus, distfun) + +distribute_virus_randomly(prevalence, as_proportion) + +distribute_virus_set(agents_ids) } \arguments{ \item{name}{of the virus} +\item{prevalence}{Numeric scalar. Prevalence of the virus.} + +\item{as_proportion}{Logical scalar. If \code{TRUE}, the prevalence is set as a +proportion of the total number of agents in the model.} + \item{prob_infecting}{Numeric scalar. Probability of infection (transmission).} \item{recovery_rate}{Numeric scalar. Probability of recovery.} @@ -88,9 +101,7 @@ set_incubation_fun(virus, model, vfun) \item{model}{An object of class \code{epiworld_model}.} -\item{proportion}{In the case of \code{add_virus}, a proportion, otherwise, an integer.} - -\item{n}{A positive integer. Initial count of agents to have the virus.} +\item{proportion}{Deprecated.} \item{init, end, removed}{states after acquiring a virus, removing a virus, and removing the agent as a result of the virus, respectively.} @@ -109,6 +120,11 @@ coefficients associated to the logit probability.} will be added to the virus (see details).} \item{vfun}{An object of class \code{epiworld_virus_fun}.} + +\item{distfun}{An object of class \code{epiworld_distribution_virus}.} + +\item{agents_ids}{Integer vector. Indices of the agents that will receive the +virus.} } \value{ \itemize{ @@ -126,12 +142,6 @@ a name to the virus of choice. virus of choice to the model object of class \link{epiworld_model}. } -\itemize{ -\item The \code{add_virus_n} function does not return a value, but instead adds a -specified number of agents with the virus of choice to the model object -of class \link{epiworld_model}. -} - \itemize{ \item The \code{virus_set_state} function does not return a value but assigns epidemiological properties to the specified virus of class \link{epiworld_virus}. @@ -164,6 +174,11 @@ assigns a probability to death for the specified virus of class \item The \code{set_incubation} function does not return a value, but instead assigns an incubation period to the specified virus of class \link{epiworld_virus}. } + +\itemize{ +\item The \code{distribute_virus_randomly} function returns a function that can be +used to distribute the virus in the model. +} } \description{ Viruses can be considered to be anything that can be transmitted (e.g., @@ -185,6 +200,11 @@ In the case of \code{set_prob_infecting_ptr}, \code{set_prob_recovery_ptr}, and \code{set_prob_death_ptr}, the corresponding parameters is passed as a pointer to the virus. The implication of using pointers is that the values will be read directly from the \code{model} object, so changes will be reflected. + +The \code{distribute_virus_randomly} function is a factory function +used to randomly distribute the virus in the model. The prevalence can be set +as a proportion or as a number of agents. The resulting function can then be +passed to \code{set_distribution_virus}. } \examples{ mseirconn <- ModelSEIRCONN( @@ -197,10 +217,12 @@ mseirconn <- ModelSEIRCONN( recovery_rate = 0.99 ) -delta <- virus("Delta Variant", 0, .5, .2, .01) +delta <- virus( + "Delta Variant", 0, .5, .2, .01, prevalence = 0.3, as_proportion = TRUE +) # Adding virus and setting/getting virus name -add_virus(mseirconn, delta, .3) +add_virus(mseirconn, delta) set_name_virus(delta, "COVID-19 Strain") get_name_virus(delta) @@ -208,8 +230,8 @@ run(mseirconn, ndays = 100, seed = 992) mseirconn rm_virus(mseirconn, 0) # Removing the first virus from the model object -add_virus_n(mseirconn, delta, 100) # Setting initial count of delta virus - # to n = 100 +set_distribution_virus(delta, distribute_virus_randomly(100, as_proportion = FALSE)) +add_virus(mseirconn, delta) # Setting parameters for the delta virus manually set_prob_infecting(delta, 0.5) @@ -221,7 +243,9 @@ run(mseirconn, ndays = 100, seed = 992) # Run the model to observe changes # 1: Infected # 2: Recovered # 3: Dead -delta2 <- virus("Delta Variant 2", 0, .5, .2, .01) +delta2 <- virus( + "Delta Variant 2", 0, .5, .2, .01, prevalence = 0, as_proportion = TRUE +) virus_set_state(delta2, 1, 2, 3) # Using the logit function -------------- sir <- ModelSIR( diff --git a/playground/.gitignore b/playground/.gitignore index 2e1261ba..ece2f076 100755 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -1,2 +1,4 @@ *_files/ *_cache/ + +/.quarto/ diff --git a/src/cpp11.cpp b/src/cpp11.cpp index 6ed1ba5f..b8ad0851 100644 --- a/src/cpp11.cpp +++ b/src/cpp11.cpp @@ -187,6 +187,104 @@ extern "C" SEXP _epiworldR_get_today_total_cpp(SEXP model) { return cpp11::as_sexp(get_today_total_cpp(cpp11::as_cpp>(model))); END_CPP11 } +// entities.cpp +SEXP get_entities_cpp(SEXP model); +extern "C" SEXP _epiworldR_get_entities_cpp(SEXP model) { + BEGIN_CPP11 + return cpp11::as_sexp(get_entities_cpp(cpp11::as_cpp>(model))); + END_CPP11 +} +// entities.cpp +SEXP get_entity_cpp(SEXP entities, int idx); +extern "C" SEXP _epiworldR_get_entity_cpp(SEXP entities, SEXP idx) { + BEGIN_CPP11 + return cpp11::as_sexp(get_entity_cpp(cpp11::as_cpp>(entities), cpp11::as_cpp>(idx))); + END_CPP11 +} +// entities.cpp +SEXP entity_cpp(std::string name, double preval, bool as_proportion, bool to_unassigned); +extern "C" SEXP _epiworldR_entity_cpp(SEXP name, SEXP preval, SEXP as_proportion, SEXP to_unassigned) { + BEGIN_CPP11 + return cpp11::as_sexp(entity_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(preval), cpp11::as_cpp>(as_proportion), cpp11::as_cpp>(to_unassigned))); + END_CPP11 +} +// entities.cpp +int get_entity_size_cpp(SEXP entity); +extern "C" SEXP _epiworldR_get_entity_size_cpp(SEXP entity) { + BEGIN_CPP11 + return cpp11::as_sexp(get_entity_size_cpp(cpp11::as_cpp>(entity))); + END_CPP11 +} +// entities.cpp +int entity_add_agent_cpp(SEXP entity, SEXP agent, SEXP model); +extern "C" SEXP _epiworldR_entity_add_agent_cpp(SEXP entity, SEXP agent, SEXP model) { + BEGIN_CPP11 + return cpp11::as_sexp(entity_add_agent_cpp(cpp11::as_cpp>(entity), cpp11::as_cpp>(agent), cpp11::as_cpp>(model))); + END_CPP11 +} +// entities.cpp +std::string get_entity_name_cpp(SEXP entity); +extern "C" SEXP _epiworldR_get_entity_name_cpp(SEXP entity) { + BEGIN_CPP11 + return cpp11::as_sexp(get_entity_name_cpp(cpp11::as_cpp>(entity))); + END_CPP11 +} +// entities.cpp +int add_entity_cpp(SEXP model, SEXP entity); +extern "C" SEXP _epiworldR_add_entity_cpp(SEXP model, SEXP entity) { + BEGIN_CPP11 + return cpp11::as_sexp(add_entity_cpp(cpp11::as_cpp>(model), cpp11::as_cpp>(entity))); + END_CPP11 +} +// entities.cpp +int rm_entity_cpp(SEXP model, int entity_pos); +extern "C" SEXP _epiworldR_rm_entity_cpp(SEXP model, SEXP entity_pos) { + BEGIN_CPP11 + return cpp11::as_sexp(rm_entity_cpp(cpp11::as_cpp>(model), cpp11::as_cpp>(entity_pos))); + END_CPP11 +} +// entities.cpp +int load_agents_entities_ties_cpp(SEXP model, SEXP agents_ids, SEXP entities_ids); +extern "C" SEXP _epiworldR_load_agents_entities_ties_cpp(SEXP model, SEXP agents_ids, SEXP entities_ids) { + BEGIN_CPP11 + return cpp11::as_sexp(load_agents_entities_ties_cpp(cpp11::as_cpp>(model), cpp11::as_cpp>(agents_ids), cpp11::as_cpp>(entities_ids))); + END_CPP11 +} +// entities.cpp +cpp11::data_frame entity_get_agents_cpp(SEXP entity); +extern "C" SEXP _epiworldR_entity_get_agents_cpp(SEXP entity) { + BEGIN_CPP11 + return cpp11::as_sexp(entity_get_agents_cpp(cpp11::as_cpp>(entity))); + END_CPP11 +} +// entities.cpp +int print_entity_cpp(SEXP entity); +extern "C" SEXP _epiworldR_print_entity_cpp(SEXP entity) { + BEGIN_CPP11 + return cpp11::as_sexp(print_entity_cpp(cpp11::as_cpp>(entity))); + END_CPP11 +} +// entities.cpp +SEXP set_distribution_entity_cpp(SEXP entity, SEXP fun); +extern "C" SEXP _epiworldR_set_distribution_entity_cpp(SEXP entity, SEXP fun) { + BEGIN_CPP11 + return cpp11::as_sexp(set_distribution_entity_cpp(cpp11::as_cpp>(entity), cpp11::as_cpp>(fun))); + END_CPP11 +} +// entities.cpp +SEXP distribute_entity_randomly_cpp(double prevalence, bool as_proportion, bool to_unassigned); +extern "C" SEXP _epiworldR_distribute_entity_randomly_cpp(SEXP prevalence, SEXP as_proportion, SEXP to_unassigned) { + BEGIN_CPP11 + return cpp11::as_sexp(distribute_entity_randomly_cpp(cpp11::as_cpp>(prevalence), cpp11::as_cpp>(as_proportion), cpp11::as_cpp>(to_unassigned))); + END_CPP11 +} +// entities.cpp +SEXP distribute_entity_to_set_cpp(integers agents_ids); +extern "C" SEXP _epiworldR_distribute_entity_to_set_cpp(SEXP agents_ids) { + BEGIN_CPP11 + return cpp11::as_sexp(distribute_entity_to_set_cpp(cpp11::as_cpp>(agents_ids))); + END_CPP11 +} // epimodels.cpp SEXP ModelSURV_cpp(std::string name, double prevalence, double efficacy_vax, double latent_period, double prob_symptoms, double prop_vaccinated, double prop_vax_redux_transm, double infect_period, double prop_vax_redux_infect, double surveillance_prob, double transmission_rate, double prob_death, double prob_noreinfect); extern "C" SEXP _epiworldR_ModelSURV_cpp(SEXP name, SEXP prevalence, SEXP efficacy_vax, SEXP latent_period, SEXP prob_symptoms, SEXP prop_vaccinated, SEXP prop_vax_redux_transm, SEXP infect_period, SEXP prop_vax_redux_infect, SEXP surveillance_prob, SEXP transmission_rate, SEXP prob_death, SEXP prob_noreinfect) { @@ -278,6 +376,20 @@ extern "C" SEXP _epiworldR_ModelDiffNet_cpp(SEXP name, SEXP prevalence, SEXP pro return cpp11::as_sexp(ModelDiffNet_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(prevalence), cpp11::as_cpp>(prob_adopt), cpp11::as_cpp>(normalize_exposure), cpp11::as_cpp>(data), cpp11::as_cpp>(data_ncols), cpp11::as_cpp>>(data_cols), cpp11::as_cpp>>(params))); END_CPP11 } +// epimodels.cpp +SEXP ModelSIRMixing_cpp(std::string name, unsigned int n, double prevalence, double contact_rate, double transmission_rate, double recovery_rate, std::vector< double > contact_matrix); +extern "C" SEXP _epiworldR_ModelSIRMixing_cpp(SEXP name, SEXP n, SEXP prevalence, SEXP contact_rate, SEXP transmission_rate, SEXP recovery_rate, SEXP contact_matrix) { + BEGIN_CPP11 + return cpp11::as_sexp(ModelSIRMixing_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(n), cpp11::as_cpp>(prevalence), cpp11::as_cpp>(contact_rate), cpp11::as_cpp>(transmission_rate), cpp11::as_cpp>(recovery_rate), cpp11::as_cpp>>(contact_matrix))); + END_CPP11 +} +// epimodels.cpp +SEXP ModelSEIRMixing_cpp(std::string name, unsigned int n, double prevalence, double contact_rate, double transmission_rate, double incubation_days, double recovery_rate, std::vector< double > contact_matrix); +extern "C" SEXP _epiworldR_ModelSEIRMixing_cpp(SEXP name, SEXP n, SEXP prevalence, SEXP contact_rate, SEXP transmission_rate, SEXP incubation_days, SEXP recovery_rate, SEXP contact_matrix) { + BEGIN_CPP11 + return cpp11::as_sexp(ModelSEIRMixing_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(n), cpp11::as_cpp>(prevalence), cpp11::as_cpp>(contact_rate), cpp11::as_cpp>(transmission_rate), cpp11::as_cpp>(incubation_days), cpp11::as_cpp>(recovery_rate), cpp11::as_cpp>>(contact_matrix))); + END_CPP11 +} // model.cpp SEXP print_cpp(SEXP m, bool lite); extern "C" SEXP _epiworldR_print_cpp(SEXP m, SEXP lite) { @@ -468,24 +580,17 @@ extern "C" SEXP _epiworldR_clone_model_cpp(SEXP model) { END_CPP11 } // tool.cpp -SEXP tool_cpp(std::string name, double susceptibility_reduction, double transmission_reduction, double recovery_enhancer, double death_reduction); -extern "C" SEXP _epiworldR_tool_cpp(SEXP name, SEXP susceptibility_reduction, SEXP transmission_reduction, SEXP recovery_enhancer, SEXP death_reduction) { +SEXP tool_cpp(std::string name, double prevalence, bool as_proportion, double susceptibility_reduction, double transmission_reduction, double recovery_enhancer, double death_reduction); +extern "C" SEXP _epiworldR_tool_cpp(SEXP name, SEXP prevalence, SEXP as_proportion, SEXP susceptibility_reduction, SEXP transmission_reduction, SEXP recovery_enhancer, SEXP death_reduction) { BEGIN_CPP11 - return cpp11::as_sexp(tool_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(susceptibility_reduction), cpp11::as_cpp>(transmission_reduction), cpp11::as_cpp>(recovery_enhancer), cpp11::as_cpp>(death_reduction))); + return cpp11::as_sexp(tool_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(prevalence), cpp11::as_cpp>(as_proportion), cpp11::as_cpp>(susceptibility_reduction), cpp11::as_cpp>(transmission_reduction), cpp11::as_cpp>(recovery_enhancer), cpp11::as_cpp>(death_reduction))); END_CPP11 } // tool.cpp -int add_tool_cpp(SEXP m, SEXP t, double preval); -extern "C" SEXP _epiworldR_add_tool_cpp(SEXP m, SEXP t, SEXP preval) { +int add_tool_cpp(SEXP m, SEXP t); +extern "C" SEXP _epiworldR_add_tool_cpp(SEXP m, SEXP t) { BEGIN_CPP11 - return cpp11::as_sexp(add_tool_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(t), cpp11::as_cpp>(preval))); - END_CPP11 -} -// tool.cpp -int add_tool_n_cpp(SEXP m, SEXP t, size_t preval); -extern "C" SEXP _epiworldR_add_tool_n_cpp(SEXP m, SEXP t, SEXP preval) { - BEGIN_CPP11 - return cpp11::as_sexp(add_tool_n_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(t), cpp11::as_cpp>(preval))); + return cpp11::as_sexp(add_tool_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(t))); END_CPP11 } // tool.cpp @@ -621,32 +726,46 @@ extern "C" SEXP _epiworldR_print_agent_tools_cpp(SEXP tools) { return cpp11::as_sexp(print_agent_tools_cpp(cpp11::as_cpp>(tools))); END_CPP11 } -// virus.cpp -SEXP virus_cpp(std::string name, double prob_infecting, double prob_recovery, double prob_death, double post_immunity, double incubation); -extern "C" SEXP _epiworldR_virus_cpp(SEXP name, SEXP prob_infecting, SEXP prob_recovery, SEXP prob_death, SEXP post_immunity, SEXP incubation) { +// tool.cpp +SEXP set_distribution_tool_cpp(SEXP tool, SEXP distfun); +extern "C" SEXP _epiworldR_set_distribution_tool_cpp(SEXP tool, SEXP distfun) { + BEGIN_CPP11 + return cpp11::as_sexp(set_distribution_tool_cpp(cpp11::as_cpp>(tool), cpp11::as_cpp>(distfun))); + END_CPP11 +} +// tool.cpp +SEXP distribute_tool_randomly_cpp(double prevalence, bool as_proportion); +extern "C" SEXP _epiworldR_distribute_tool_randomly_cpp(SEXP prevalence, SEXP as_proportion) { BEGIN_CPP11 - return cpp11::as_sexp(virus_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(prob_infecting), cpp11::as_cpp>(prob_recovery), cpp11::as_cpp>(prob_death), cpp11::as_cpp>(post_immunity), cpp11::as_cpp>(incubation))); + return cpp11::as_sexp(distribute_tool_randomly_cpp(cpp11::as_cpp>(prevalence), cpp11::as_cpp>(as_proportion))); + END_CPP11 +} +// tool.cpp +SEXP distribute_tool_to_set_cpp(integers agents_ids); +extern "C" SEXP _epiworldR_distribute_tool_to_set_cpp(SEXP agents_ids) { + BEGIN_CPP11 + return cpp11::as_sexp(distribute_tool_to_set_cpp(cpp11::as_cpp>(agents_ids))); END_CPP11 } // virus.cpp -SEXP virus_set_state_cpp(SEXP v, size_t init, size_t end, size_t removed); -extern "C" SEXP _epiworldR_virus_set_state_cpp(SEXP v, SEXP init, SEXP end, SEXP removed) { +SEXP virus_cpp(std::string name, double prevalence, bool as_proportion, double prob_infecting, double prob_recovery, double prob_death, double post_immunity, double incubation); +extern "C" SEXP _epiworldR_virus_cpp(SEXP name, SEXP prevalence, SEXP as_proportion, SEXP prob_infecting, SEXP prob_recovery, SEXP prob_death, SEXP post_immunity, SEXP incubation) { BEGIN_CPP11 - return cpp11::as_sexp(virus_set_state_cpp(cpp11::as_cpp>(v), cpp11::as_cpp>(init), cpp11::as_cpp>(end), cpp11::as_cpp>(removed))); + return cpp11::as_sexp(virus_cpp(cpp11::as_cpp>(name), cpp11::as_cpp>(prevalence), cpp11::as_cpp>(as_proportion), cpp11::as_cpp>(prob_infecting), cpp11::as_cpp>(prob_recovery), cpp11::as_cpp>(prob_death), cpp11::as_cpp>(post_immunity), cpp11::as_cpp>(incubation))); END_CPP11 } // virus.cpp -SEXP add_virus_cpp(SEXP m, SEXP v, double preval); -extern "C" SEXP _epiworldR_add_virus_cpp(SEXP m, SEXP v, SEXP preval) { +SEXP virus_set_state_cpp(SEXP v, size_t init, size_t end, size_t removed); +extern "C" SEXP _epiworldR_virus_set_state_cpp(SEXP v, SEXP init, SEXP end, SEXP removed) { BEGIN_CPP11 - return cpp11::as_sexp(add_virus_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(v), cpp11::as_cpp>(preval))); + return cpp11::as_sexp(virus_set_state_cpp(cpp11::as_cpp>(v), cpp11::as_cpp>(init), cpp11::as_cpp>(end), cpp11::as_cpp>(removed))); END_CPP11 } // virus.cpp -SEXP add_virus_n_cpp(SEXP m, SEXP v, size_t preval); -extern "C" SEXP _epiworldR_add_virus_n_cpp(SEXP m, SEXP v, SEXP preval) { +SEXP add_virus_cpp(SEXP m, SEXP v); +extern "C" SEXP _epiworldR_add_virus_cpp(SEXP m, SEXP v) { BEGIN_CPP11 - return cpp11::as_sexp(add_virus_n_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(v), cpp11::as_cpp>(preval))); + return cpp11::as_sexp(add_virus_cpp(cpp11::as_cpp>(m), cpp11::as_cpp>(v))); END_CPP11 } // virus.cpp @@ -768,6 +887,27 @@ extern "C" SEXP _epiworldR_set_name_virus_cpp(SEXP virus, SEXP name) { return cpp11::as_sexp(set_name_virus_cpp(cpp11::as_cpp>(virus), cpp11::as_cpp>(name))); END_CPP11 } +// virus.cpp +SEXP set_distribution_virus_cpp(SEXP virus, SEXP dist); +extern "C" SEXP _epiworldR_set_distribution_virus_cpp(SEXP virus, SEXP dist) { + BEGIN_CPP11 + return cpp11::as_sexp(set_distribution_virus_cpp(cpp11::as_cpp>(virus), cpp11::as_cpp>(dist))); + END_CPP11 +} +// virus.cpp +SEXP distribute_virus_randomly_cpp(double prevalence, bool as_proportion); +extern "C" SEXP _epiworldR_distribute_virus_randomly_cpp(SEXP prevalence, SEXP as_proportion) { + BEGIN_CPP11 + return cpp11::as_sexp(distribute_virus_randomly_cpp(cpp11::as_cpp>(prevalence), cpp11::as_cpp>(as_proportion))); + END_CPP11 +} +// virus.cpp +SEXP distribute_virus_to_set_cpp(integers agents_ids); +extern "C" SEXP _epiworldR_distribute_virus_to_set_cpp(SEXP agents_ids) { + BEGIN_CPP11 + return cpp11::as_sexp(distribute_virus_to_set_cpp(cpp11::as_cpp>(agents_ids))); + END_CPP11 +} extern "C" { static const R_CallMethodDef CallEntries[] = { @@ -775,31 +915,45 @@ static const R_CallMethodDef CallEntries[] = { {"_epiworldR_ModelSEIRCONN_cpp", (DL_FUNC) &_epiworldR_ModelSEIRCONN_cpp, 7}, {"_epiworldR_ModelSEIRDCONN_cpp", (DL_FUNC) &_epiworldR_ModelSEIRDCONN_cpp, 8}, {"_epiworldR_ModelSEIRD_cpp", (DL_FUNC) &_epiworldR_ModelSEIRD_cpp, 6}, + {"_epiworldR_ModelSEIRMixing_cpp", (DL_FUNC) &_epiworldR_ModelSEIRMixing_cpp, 8}, {"_epiworldR_ModelSEIR_cpp", (DL_FUNC) &_epiworldR_ModelSEIR_cpp, 5}, {"_epiworldR_ModelSIRCONN_cpp", (DL_FUNC) &_epiworldR_ModelSIRCONN_cpp, 6}, {"_epiworldR_ModelSIRDCONN_cpp", (DL_FUNC) &_epiworldR_ModelSIRDCONN_cpp, 7}, {"_epiworldR_ModelSIRD_cpp", (DL_FUNC) &_epiworldR_ModelSIRD_cpp, 5}, {"_epiworldR_ModelSIRLogit_cpp", (DL_FUNC) &_epiworldR_ModelSIRLogit_cpp, 10}, + {"_epiworldR_ModelSIRMixing_cpp", (DL_FUNC) &_epiworldR_ModelSIRMixing_cpp, 7}, {"_epiworldR_ModelSIR_cpp", (DL_FUNC) &_epiworldR_ModelSIR_cpp, 4}, {"_epiworldR_ModelSISD_cpp", (DL_FUNC) &_epiworldR_ModelSISD_cpp, 5}, {"_epiworldR_ModelSIS_cpp", (DL_FUNC) &_epiworldR_ModelSIS_cpp, 4}, {"_epiworldR_ModelSURV_cpp", (DL_FUNC) &_epiworldR_ModelSURV_cpp, 13}, + {"_epiworldR_add_entity_cpp", (DL_FUNC) &_epiworldR_add_entity_cpp, 2}, {"_epiworldR_add_globalevent_cpp", (DL_FUNC) &_epiworldR_add_globalevent_cpp, 2}, {"_epiworldR_add_tool_agent_cpp", (DL_FUNC) &_epiworldR_add_tool_agent_cpp, 5}, - {"_epiworldR_add_tool_cpp", (DL_FUNC) &_epiworldR_add_tool_cpp, 3}, - {"_epiworldR_add_tool_n_cpp", (DL_FUNC) &_epiworldR_add_tool_n_cpp, 3}, + {"_epiworldR_add_tool_cpp", (DL_FUNC) &_epiworldR_add_tool_cpp, 2}, {"_epiworldR_add_virus_agent_cpp", (DL_FUNC) &_epiworldR_add_virus_agent_cpp, 5}, - {"_epiworldR_add_virus_cpp", (DL_FUNC) &_epiworldR_add_virus_cpp, 3}, - {"_epiworldR_add_virus_n_cpp", (DL_FUNC) &_epiworldR_add_virus_n_cpp, 3}, + {"_epiworldR_add_virus_cpp", (DL_FUNC) &_epiworldR_add_virus_cpp, 2}, {"_epiworldR_agents_from_edgelist_cpp", (DL_FUNC) &_epiworldR_agents_from_edgelist_cpp, 5}, {"_epiworldR_agents_smallworld_cpp", (DL_FUNC) &_epiworldR_agents_smallworld_cpp, 5}, {"_epiworldR_change_state_cpp", (DL_FUNC) &_epiworldR_change_state_cpp, 4}, {"_epiworldR_clone_model_cpp", (DL_FUNC) &_epiworldR_clone_model_cpp, 1}, + {"_epiworldR_distribute_entity_randomly_cpp", (DL_FUNC) &_epiworldR_distribute_entity_randomly_cpp, 3}, + {"_epiworldR_distribute_entity_to_set_cpp", (DL_FUNC) &_epiworldR_distribute_entity_to_set_cpp, 1}, + {"_epiworldR_distribute_tool_randomly_cpp", (DL_FUNC) &_epiworldR_distribute_tool_randomly_cpp, 2}, + {"_epiworldR_distribute_tool_to_set_cpp", (DL_FUNC) &_epiworldR_distribute_tool_to_set_cpp, 1}, + {"_epiworldR_distribute_virus_randomly_cpp", (DL_FUNC) &_epiworldR_distribute_virus_randomly_cpp, 2}, + {"_epiworldR_distribute_virus_to_set_cpp", (DL_FUNC) &_epiworldR_distribute_virus_to_set_cpp, 1}, + {"_epiworldR_entity_add_agent_cpp", (DL_FUNC) &_epiworldR_entity_add_agent_cpp, 3}, + {"_epiworldR_entity_cpp", (DL_FUNC) &_epiworldR_entity_cpp, 4}, + {"_epiworldR_entity_get_agents_cpp", (DL_FUNC) &_epiworldR_entity_get_agents_cpp, 1}, {"_epiworldR_get_agent_cpp", (DL_FUNC) &_epiworldR_get_agent_cpp, 2}, {"_epiworldR_get_agents_cpp", (DL_FUNC) &_epiworldR_get_agents_cpp, 1}, {"_epiworldR_get_agents_data_ncols_cpp", (DL_FUNC) &_epiworldR_get_agents_data_ncols_cpp, 1}, {"_epiworldR_get_agents_states_cpp", (DL_FUNC) &_epiworldR_get_agents_states_cpp, 1}, {"_epiworldR_get_agents_tools_cpp", (DL_FUNC) &_epiworldR_get_agents_tools_cpp, 1}, + {"_epiworldR_get_entities_cpp", (DL_FUNC) &_epiworldR_get_entities_cpp, 1}, + {"_epiworldR_get_entity_cpp", (DL_FUNC) &_epiworldR_get_entity_cpp, 2}, + {"_epiworldR_get_entity_name_cpp", (DL_FUNC) &_epiworldR_get_entity_name_cpp, 1}, + {"_epiworldR_get_entity_size_cpp", (DL_FUNC) &_epiworldR_get_entity_size_cpp, 1}, {"_epiworldR_get_generation_time_cpp", (DL_FUNC) &_epiworldR_get_generation_time_cpp, 1}, {"_epiworldR_get_hist_tool_cpp", (DL_FUNC) &_epiworldR_get_hist_tool_cpp, 1}, {"_epiworldR_get_hist_total_cpp", (DL_FUNC) &_epiworldR_get_hist_total_cpp, 1}, @@ -829,15 +983,18 @@ static const R_CallMethodDef CallEntries[] = { {"_epiworldR_has_tool_cpp", (DL_FUNC) &_epiworldR_has_tool_cpp, 2}, {"_epiworldR_has_virus_cpp", (DL_FUNC) &_epiworldR_has_virus_cpp, 2}, {"_epiworldR_initial_states_cpp", (DL_FUNC) &_epiworldR_initial_states_cpp, 2}, + {"_epiworldR_load_agents_entities_ties_cpp", (DL_FUNC) &_epiworldR_load_agents_entities_ties_cpp, 3}, {"_epiworldR_make_saver_cpp", (DL_FUNC) &_epiworldR_make_saver_cpp, 10}, {"_epiworldR_print_agent_cpp", (DL_FUNC) &_epiworldR_print_agent_cpp, 3}, {"_epiworldR_print_agent_tools_cpp", (DL_FUNC) &_epiworldR_print_agent_tools_cpp, 1}, {"_epiworldR_print_cpp", (DL_FUNC) &_epiworldR_print_cpp, 2}, + {"_epiworldR_print_entity_cpp", (DL_FUNC) &_epiworldR_print_entity_cpp, 1}, {"_epiworldR_print_global_action_cpp", (DL_FUNC) &_epiworldR_print_global_action_cpp, 1}, {"_epiworldR_print_tool_cpp", (DL_FUNC) &_epiworldR_print_tool_cpp, 1}, {"_epiworldR_print_virus_cpp", (DL_FUNC) &_epiworldR_print_virus_cpp, 1}, {"_epiworldR_queuing_off_cpp", (DL_FUNC) &_epiworldR_queuing_off_cpp, 1}, {"_epiworldR_queuing_on_cpp", (DL_FUNC) &_epiworldR_queuing_on_cpp, 1}, + {"_epiworldR_rm_entity_cpp", (DL_FUNC) &_epiworldR_rm_entity_cpp, 2}, {"_epiworldR_rm_globalevent_cpp", (DL_FUNC) &_epiworldR_rm_globalevent_cpp, 2}, {"_epiworldR_rm_tool_cpp", (DL_FUNC) &_epiworldR_rm_tool_cpp, 2}, {"_epiworldR_rm_virus_cpp", (DL_FUNC) &_epiworldR_rm_virus_cpp, 2}, @@ -847,6 +1004,9 @@ static const R_CallMethodDef CallEntries[] = { {"_epiworldR_set_death_reduction_cpp", (DL_FUNC) &_epiworldR_set_death_reduction_cpp, 2}, {"_epiworldR_set_death_reduction_fun_cpp", (DL_FUNC) &_epiworldR_set_death_reduction_fun_cpp, 3}, {"_epiworldR_set_death_reduction_ptr_cpp", (DL_FUNC) &_epiworldR_set_death_reduction_ptr_cpp, 3}, + {"_epiworldR_set_distribution_entity_cpp", (DL_FUNC) &_epiworldR_set_distribution_entity_cpp, 2}, + {"_epiworldR_set_distribution_tool_cpp", (DL_FUNC) &_epiworldR_set_distribution_tool_cpp, 2}, + {"_epiworldR_set_distribution_virus_cpp", (DL_FUNC) &_epiworldR_set_distribution_virus_cpp, 2}, {"_epiworldR_set_incubation_cpp", (DL_FUNC) &_epiworldR_set_incubation_cpp, 2}, {"_epiworldR_set_incubation_fun_cpp", (DL_FUNC) &_epiworldR_set_incubation_fun_cpp, 3}, {"_epiworldR_set_incubation_ptr_cpp", (DL_FUNC) &_epiworldR_set_incubation_ptr_cpp, 3}, @@ -873,11 +1033,11 @@ static const R_CallMethodDef CallEntries[] = { {"_epiworldR_set_transmission_reduction_fun_cpp", (DL_FUNC) &_epiworldR_set_transmission_reduction_fun_cpp, 3}, {"_epiworldR_set_transmission_reduction_ptr_cpp", (DL_FUNC) &_epiworldR_set_transmission_reduction_ptr_cpp, 3}, {"_epiworldR_size_cpp", (DL_FUNC) &_epiworldR_size_cpp, 1}, - {"_epiworldR_tool_cpp", (DL_FUNC) &_epiworldR_tool_cpp, 5}, + {"_epiworldR_tool_cpp", (DL_FUNC) &_epiworldR_tool_cpp, 7}, {"_epiworldR_tool_fun_logit_cpp", (DL_FUNC) &_epiworldR_tool_fun_logit_cpp, 3}, {"_epiworldR_verbose_off_cpp", (DL_FUNC) &_epiworldR_verbose_off_cpp, 1}, {"_epiworldR_verbose_on_cpp", (DL_FUNC) &_epiworldR_verbose_on_cpp, 1}, - {"_epiworldR_virus_cpp", (DL_FUNC) &_epiworldR_virus_cpp, 6}, + {"_epiworldR_virus_cpp", (DL_FUNC) &_epiworldR_virus_cpp, 8}, {"_epiworldR_virus_fun_logit_cpp", (DL_FUNC) &_epiworldR_virus_fun_logit_cpp, 3}, {"_epiworldR_virus_set_state_cpp", (DL_FUNC) &_epiworldR_virus_set_state_cpp, 4}, {NULL, NULL, 0} diff --git a/src/entities.cpp b/src/entities.cpp new file mode 100644 index 00000000..f20534c6 --- /dev/null +++ b/src/entities.cpp @@ -0,0 +1,237 @@ + +#include "cpp11.hpp" +#include "cpp11/external_pointer.hpp" +#include "cpp11/matrix.hpp" +#include "cpp11/data_frame.hpp" +#include "epiworld-common.h" + +using namespace cpp11; +using namespace epiworld; + +[[cpp11::register]] +SEXP get_entities_cpp( + SEXP model +) { + + // Making some room + + cpp11::external_pointer> ptr(model); + + cpp11::writable::list res; + + for (auto & i : ptr->get_entities()) { + cpp11::external_pointer> entity(&i, false); + res.push_back(entity); + } + + return res; + +} + +[[cpp11::register]] +SEXP get_entity_cpp( + SEXP entities, + int idx +) { + + cpp11::external_pointer> > ptr(entities); + + cpp11::external_pointer> entity( + &ptr->at(static_cast(idx)), false + ); + + return entity; + +} + + +[[cpp11::register]] +SEXP entity_cpp( + std::string name, + double preval, + bool as_proportion, + bool to_unassigned +) { + + cpp11::external_pointer> ptr( + new Entity<>( + name, + distribute_entity_randomly<>( + preval, + as_proportion, + to_unassigned + ) + ) + ); + + return ptr; + +} + +[[cpp11::register]] +int get_entity_size_cpp(SEXP entity) { + auto res = cpp11::external_pointer>(entity)->size(); + return static_cast(res); +} + +[[cpp11::register]] +int entity_add_agent_cpp(SEXP entity, SEXP agent, SEXP model) { + + cpp11::external_pointer> ptr(entity); + cpp11::external_pointer> ptr_agent(agent); + cpp11::external_pointer> ptr_model(model); + + ptr->add_agent(&(*ptr_agent), &(*ptr_model)); + + return 0; +} + +[[cpp11::register]] +std::string get_entity_name_cpp(SEXP entity) { + return cpp11::external_pointer>(entity)->get_name(); +} + +[[cpp11::register]] +int add_entity_cpp( + SEXP model, + SEXP entity + ) { + + cpp11::external_pointer> ptr_model(model); + cpp11::external_pointer> ptr_entity(entity); + + ptr_model->add_entity(*ptr_entity); + + return 0; +} + +[[cpp11::register]] +int rm_entity_cpp(SEXP model, int entity_pos) { + + cpp11::external_pointer> ptr_model(model); + + ptr_model->rm_entity( + static_cast(entity_pos) + ); + + return 0; +} + +[[cpp11::register]] +int load_agents_entities_ties_cpp( + SEXP model, + SEXP agents_ids, + SEXP entities_ids +) { + + cpp11::external_pointer> ptr_model(model); + + if (LENGTH(agents_ids) != LENGTH(entities_ids)) { + cpp11::stop("agents_ids and entities_ids must have the same length"); + } + + ptr_model->load_agents_entities_ties( + INTEGER(agents_ids), + INTEGER(entities_ids), + LENGTH(agents_ids) + ); + + return 0; + +} + +[[cpp11::register]] +cpp11::data_frame entity_get_agents_cpp(SEXP entity) { + + cpp11::external_pointer> ptr(entity); + + cpp11::writable::integers agent; + cpp11::writable::integers entity_id; + + int id = static_cast(ptr->get_id()); + auto agents_ids = ptr->get_agents(); + size_t nagents = ptr->size(); + for (size_t i = 0u; i < nagents; ++i) { + agent.push_back(static_cast(agents_ids[i])); + entity_id.push_back(id); + } + + return cpp11::writable::data_frame({ + "agent"_nm = agent, + "entity"_nm = entity_id + }); + +} + +[[cpp11::register]] +int print_entity_cpp(SEXP entity) { + cpp11::external_pointer>(entity)->print(); + return 0; +} + +[[cpp11::register]] +SEXP set_distribution_entity_cpp( + SEXP entity, + SEXP fun +) { + + external_pointer> entity_ptr(entity); + external_pointer> fun_ptr(fun); + + entity_ptr->set_distribution(*fun_ptr); + + return entity; + +} + +[[cpp11::register]] +SEXP distribute_entity_randomly_cpp( + double prevalence, + bool as_proportion, + bool to_unassigned +) { + + external_pointer> res( + new EntityToAgentFun<>( + distribute_entity_randomly<>( + prevalence, + as_proportion, + to_unassigned + ) + ) + ); + + return res; + +} + +[[cpp11::register]] +SEXP distribute_entity_to_set_cpp( + integers agents_ids +) { + + // Converting integers to std::vector + std::vector ids; + for (auto & id : as_cpp>(agents_ids)) + { + if (id < 0) + stop("Agent's ID must be a positive integer."); + ids.push_back(static_cast(id)); + } + + external_pointer> res( + new EntityToAgentFun<>( + distribute_entity_to_set(ids) + ) + ); + + return res; + +} + +// [[cpp11::register]] +// int entity_set_name_cpp(SEXP entity, std::string name) { +// cpp11::external_pointer>(entity)->set_name(name); +// return 0; +// } + diff --git a/src/epimodels.cpp b/src/epimodels.cpp index d2f9aed6..bdf41cd8 100644 --- a/src/epimodels.cpp +++ b/src/epimodels.cpp @@ -120,7 +120,6 @@ SEXP ModelSIS_cpp( SEXP ModelSIRCONN_cpp( std::string name, unsigned int n, - double prevalence, double contact_rate, double transmission_rate, @@ -435,3 +434,60 @@ SEXP ModelDiffNet_cpp( } +[[cpp11::register]] +SEXP ModelSIRMixing_cpp( + std::string name, + unsigned int n, + double prevalence, + double contact_rate, + double transmission_rate, + double recovery_rate, + std::vector< double > contact_matrix +) { + + // Creating a pointer to a ModelSIRMixing model + cpp11::external_pointer> ptr( + new epiworld::epimodels::ModelSIRMixing<>( + name, + n, + prevalence, + contact_rate, + transmission_rate, + recovery_rate, + contact_matrix + ) + ); + + return ptr; + +} + +[[cpp11::register]] +SEXP ModelSEIRMixing_cpp( + std::string name, + unsigned int n, + double prevalence, + double contact_rate, + double transmission_rate, + double incubation_days, + double recovery_rate, + std::vector< double > contact_matrix +) { + + // Creating a pointer to a ModelSIRMixing model + cpp11::external_pointer> ptr( + new epiworld::epimodels::ModelSEIRMixing<>( + name, + n, + prevalence, + contact_rate, + transmission_rate, + incubation_days, + recovery_rate, + contact_matrix + ) + ); + + return ptr; + +} \ No newline at end of file diff --git a/src/tool.cpp b/src/tool.cpp index a696ccc9..d39f8ab6 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -15,13 +15,19 @@ using namespace epiworld; [[cpp11::register]] SEXP tool_cpp( std::string name, + double prevalence, + bool as_proportion, double susceptibility_reduction, double transmission_reduction, double recovery_enhancer, double death_reduction ) { - WrapTool(tool)(new epiworld::Tool(name)); + WrapTool(tool)(new epiworld::Tool( + name, + prevalence, + as_proportion + )); if (susceptibility_reduction > 0) tool->set_susceptibility_reduction(susceptibility_reduction); @@ -41,22 +47,10 @@ SEXP tool_cpp( } [[cpp11::register]] -int add_tool_cpp(SEXP m, SEXP t, double preval) { +int add_tool_cpp(SEXP m, SEXP t) { cpp11::external_pointer>(m)->add_tool( - *cpp11::external_pointer>(t), - preval - ); - - return 0; -} - -[[cpp11::register]] -int add_tool_n_cpp(SEXP m, SEXP t, size_t preval) { - - cpp11::external_pointer>(m)->add_tool_n( - *cpp11::external_pointer>(t), - preval + *cpp11::external_pointer>(t) ); return 0; @@ -280,12 +274,72 @@ cpp11::writable::list get_agents_tools_cpp(SEXP model) { } - [[cpp11::register]] SEXP print_agent_tools_cpp(SEXP tools) { external_pointer> vptr(tools); vptr->print(); return tools; } + +[[cpp11::register]] +SEXP set_distribution_tool_cpp( + SEXP tool, + SEXP distfun + ) { + + WrapTool(toolptr)(tool); + external_pointer> tfunptr(distfun); + + toolptr->set_distribution(*tfunptr); + + return tool; + +} + +[[cpp11::register]] +SEXP distribute_tool_randomly_cpp( + double prevalence, + bool as_proportion +) { + + external_pointer> res( + new ToolToAgentFun<>( + distribute_tool_randomly( + prevalence, + as_proportion + ) + ) + ); + + return res; + +} + +[[cpp11::register]] +SEXP distribute_tool_to_set_cpp( + integers agents_ids +) { + + // Converting integers to std::vector + std::vector ids; + for (auto & id : as_cpp>(agents_ids)) + { + if (id < 0) + stop("Agent's ID must be a positive integer."); + ids.push_back(static_cast(id)); + } + + + external_pointer> res( + new ToolToAgentFun<>( + distribute_tool_to_set(ids) + ) + ); + + return res; +} + + + #undef WrapTool diff --git a/src/virus.cpp b/src/virus.cpp index d1fbb97b..eed27cb1 100644 --- a/src/virus.cpp +++ b/src/virus.cpp @@ -15,6 +15,8 @@ using namespace epiworld; [[cpp11::register]] SEXP virus_cpp( std::string name, + double prevalence, + bool as_proportion, double prob_infecting, double prob_recovery, double prob_death, @@ -22,7 +24,9 @@ SEXP virus_cpp( double incubation ) { - WrapVirus(virus)(new epiworld::Virus(name)); + WrapVirus(virus)(new epiworld::Virus( + name, prevalence, as_proportion + )); virus->set_prob_infecting(prob_infecting); virus->set_prob_recovery(prob_recovery); @@ -55,22 +59,10 @@ SEXP virus_set_state_cpp( } [[cpp11::register]] -SEXP add_virus_cpp(SEXP m, SEXP v, double preval) { +SEXP add_virus_cpp(SEXP m, SEXP v) { external_pointer>(m)->add_virus( - *external_pointer>(v), - preval - ); - - return m; -} - -[[cpp11::register]] -SEXP add_virus_n_cpp(SEXP m, SEXP v, size_t preval) { - - external_pointer>(m)->add_virus_n( - *external_pointer>(v), - preval + *external_pointer>(v) ); return m; @@ -276,5 +268,60 @@ SEXP set_name_virus_cpp(SEXP virus, std::string name) { return virus; } +[[cpp11::register]] +SEXP set_distribution_virus_cpp(SEXP virus, SEXP dist) { + + external_pointer> distfun(dist); + + external_pointer>(virus)->set_distribution( + *distfun + ); + + return virus; + +} + +[[cpp11::register]] +SEXP distribute_virus_randomly_cpp( + double prevalence, + bool as_proportion +) { + + external_pointer> distfun( + new VirusToAgentFun<>( + distribute_virus_randomly( + prevalence, as_proportion + ) + ) + ); + + return distfun; + +} + +[[cpp11::register]] +SEXP distribute_virus_to_set_cpp( + integers agents_ids +) { + + // Converting integers to std::vector + std::vector ids; + for (auto & id : as_cpp>(agents_ids)) + { + if (id < 0) + stop("Agent's ID must be a positive integer."); + ids.push_back(static_cast(id)); + } + + external_pointer> distfun( + new VirusToAgentFun<>( + distribute_virus_to_set(ids) + ) + ); + + return distfun; + +} + #undef WrapVirus \ No newline at end of file diff --git a/vignettes/.gitignore b/vignettes/.gitignore index 097b2416..9e2bd63c 100644 --- a/vignettes/.gitignore +++ b/vignettes/.gitignore @@ -1,2 +1,4 @@ *.html *.R + +/.quarto/ diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index 5bab0436..90d39fb2 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -161,10 +161,13 @@ the new virus' prevalence (which is set to 0.01 in this example). ```{r design-and-add} # Building the virus -flu <- virus(name = "Flu", prob_infecting = .3) +flu <- virus( + name = "Flu", prob_infecting = .3, + prevalence = .0001, as_proportion = TRUE + ) # Adding the virus to the model -add_virus(model_sir, flu, .0001) +add_virus(model_sir, flu) ``` After running the updated model with the new virus included for 50 days, the @@ -194,7 +197,7 @@ par(op) Now, the implementation of tools to combat any viruses and viruses in the model will be demonstrated. First, for the sake of simplicity, remove the flu virus -from the SIR model object (keep in mind the index for the flu virus in the +from the SIR model object (keep in mind the index for the flu virus in the model object is 1). Next, provide parameters for the new tool using the `tool` function. These parameters include the name of the tool, any reduction in probabilities for the SIR model parameters, and increased probability of @@ -209,13 +212,15 @@ rm_virus(model_sir, 1) vaccine <- tool( name = "Vaccine", + prevalence = 0.5, + as_proportion = TRUE, susceptibility_reduction = .9, transmission_reduction = .5, recovery_enhancer = .5, death_reduction = .9 ) -add_tool(model_sir, vaccine, 0.5) +add_tool(model_sir, vaccine) run(model_sir, ndays = 50, seed = 1231) ``` diff --git a/vignettes/mixing.Rmd b/vignettes/mixing.Rmd new file mode 100644 index 00000000..7fb29c8f --- /dev/null +++ b/vignettes/mixing.Rmd @@ -0,0 +1,148 @@ +--- +title: "Mixing models" +author: + - George Vega Yon +date: "`r Sys.Date()`" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Mixing models} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r setup, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", out.width = "80%", fig.width = 7, fig.height = 5, + fig.align = "center" +) +``` + +## Introduction + +This vignette shows how to create a mixing model using the `epiworldR` package. Mixing models feature multiple populations. Each group, called `Entities`, has a subset of the agents. The agents can interact with each other within the same group and with agents from other groups. A contact matrix defines the interaction between agents. + +## An SEIR model with mixing + +For this example, we will simulate an outbreak featuring three populations. The contact matrix is defined as follows: + +$$ +\left[% +\begin{array}{ccc} +0.9 & 0.05 & 0.05 \\ +0.1 & 0.8 & 0.1 \\ +0.1 & 0.2 & 0.7 \\ +\end{array}% +\right] +$$ + +This matrix represents the probability of an agent from population $i$ interacting with an agent from population $j$. The matrix is row-stochastic, meaning that the sum of each row is equal to 1. + +We will build this model using the `entity` class in epiworld. The following code chunk instantiates three entities and the contact matrix. + +```{r entity-matrix-setup} +library(epiworldR) + +e1 <- entity("Population 1", 3e3, as_proportion = FALSE) +e2 <- entity("Population 2", 3e3, as_proportion = FALSE) +e3 <- entity("Population 3", 3e3, as_proportion = FALSE) + +# Row-stochastic matrix (rowsums 1) +cmatrix <- c( + c(0.9, 0.05, 0.05), + c(0.1, 0.8, 0.1), + c(0.1, 0.2, 0.7) +) |> matrix(byrow = TRUE, nrow = 3) +``` + +With these in hand, we can proceed to create a mixing model. The following code chunk creates a model, an SEIR with mixing, and adds the entities to the model: + +```{r model-build} +N <- 9e3 + +flu_model <- ModelSEIRMixing( + name = "Flu", + n = N, + prevalence = 1 / N, + contact_rate = 20, + transmission_rate = 0.1, + recovery_rate = 1 / 7, + incubation_days = 7, + contact_matrix = cmatrix +) + +# Adding the entities +flu_model |> + add_entity(e1) |> + add_entity(e2) |> + add_entity(e3) +``` + +The function `add_entity` adds the corresponding entity. The default behavior randomly assigns agents to the entities at the beginning of the simulation. Agents may be assigned to more than one entity. The following code-chunk simulates the model for 100 days, summarizes the results, and plots the incidence curve: + +```{r model-simulate} +set.seed(331) +run(flu_model, ndays = 100) +summary(flu_model) +plot_incidence(flu_model) +``` + +## Investigating the history + +After running the simulation, a possible question is: how many agents from each entity were infected each day? The following code chunk retrieves the agents from each entity and the transmissions that occurred during the simulation: + +```{r investigate, eval=TRUE} +library(data.table) + +agents_entities <- lapply(get_entities(flu_model), \(e) { + entity_get_agents(e) +}) |> rbindlist() + +head(agents_entities) +``` + +We can retrieve the daily incidence for each entity by merging the transmissions with the agents' entities. The following code chunk accomplishes this: + +```{r transmissions} +# Retrieving the transmissions +transmissions <- get_transmissions(flu_model) |> + data.table() + +# We only need the date and the source +transmissions <- subset( + transmissions, + select = c("date", "source") +) + +# Attaching the entity to the source +transmissions <- merge( + transmissions, + agents_entities, + by.x = "source", by.y = "agent" +) + +# Aggregating by date x entity (counts) +transmissions <- transmissions[, .N, by = .(date, entity)] + +# Taking a look at the data +head(transmissions) +``` + +With this information, we can now visualize the daily incidence for each entity. The following code chunk plots the daily incidence for each entity: + +```{r transmissions-plot} +setorder(transmissions, date, entity) + +ran <- range(transmissions$N) +transmissions[entity == 0, plot( + x = date, y = N, type = "l", col = "black", ylim = ran)] +transmissions[entity == 1, lines(x = date, y = N, col = "red")] +transmissions[entity == 2, lines(x = date, y = N, col = "blue")] + +legend( + "topright", + legend = c("Population 1", "Population 2", "Population 3"), + col = c("black", "red", "blue"), + lty = 1 +) +``` \ No newline at end of file diff --git a/vignettes/run-multiple.Rmd b/vignettes/run-multiple.Rmd index 575176b1..a080d22b 100644 --- a/vignettes/run-multiple.Rmd +++ b/vignettes/run-multiple.Rmd @@ -60,7 +60,7 @@ parallel computing. # Generating a saver saver <- make_saver("total_hist", "reproductive") # Running and printing -run_multiple(model_seirconn, ndays = 50, nsims = 50, saver = saver, nthreads = 1) +run_multiple(model_seirconn, ndays = 50, nsims = 50, saver = saver, nthreads = 2) ```