diff --git a/.Rbuildignore b/.Rbuildignore index 77f8be6c..cf9338ec 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -14,3 +14,4 @@ docs codecov.yml tests/performance joss +.vscode \ No newline at end of file diff --git a/R/RcppExports.R b/R/RcppExports.R index 4a992774..13e3df07 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -65,6 +65,10 @@ filter_bitset_bitset <- function(b, other) { .Call(`_individual_filter_bitset_bitset`, b, other) } +bitset_choose <- function(b, k) { + invisible(.Call(`_individual_bitset_choose`, b, k)) +} + create_categorical_variable <- function(categories, values) { .Call(`_individual_create_categorical_variable`, categories, values) } diff --git a/R/bitset.R b/R/bitset.R index 2c50c498..3c8c079e 100644 --- a/R/bitset.R +++ b/R/bitset.R @@ -6,8 +6,8 @@ #' WARNING: all operations (except \code{$not}) are in-place so please use \code{$copy} #' if you would like to perform an operation without destroying your current bitset. #' @importFrom R6 R6Class -#' @export Bitset -Bitset <- R6::R6Class( +#' @export +Bitset <- R6Class( 'Bitset', public = list( #' @field .bitset a pointer to the underlying IterableBitset @@ -94,6 +94,17 @@ Bitset <- R6::R6Class( } self }, + + #' @description choose k random items in the bitset + #' @param k the number of items in the bitset to keep. The selection of + #' these k items from N total items in the bitset is random, and + #' k should be chosen such that 0 <= k < N. + choose = function(k) { + stopifnot(is.finite(k)) + stopifnot(k < bitset_size(self$.bitset)) + bitset_choose(self$.bitset, as.integer(k)) + self + }, #' @description returns a copy the bitset copy = function() Bitset$new(from = bitset_copy(self$.bitset)), @@ -117,8 +128,16 @@ Bitset <- R6::R6Class( #' @export filter_bitset = function(bitset, other) { if ( inherits(other, "Bitset")) { - return(Bitset$new(from = filter_bitset_bitset(bitset$.bitset, other$.bitset))) + if (other$size() > 0) { + return(Bitset$new(from = filter_bitset_bitset(bitset$.bitset, other$.bitset))) + } else { + return(Bitset$new(size = bitset$max_size)) + } } else { - return(Bitset$new(from = filter_bitset_vector(bitset$.bitset, as.integer(other)))) + if (length(other) > 0) { + return(Bitset$new(from = filter_bitset_vector(bitset$.bitset, as.integer(other)))) + } else { + return(Bitset$new(size = bitset$max_size)) + } } } diff --git a/R/categorical_variable.R b/R/categorical_variable.R index 6a65fa56..d1af4935 100644 --- a/R/categorical_variable.R +++ b/R/categorical_variable.R @@ -6,7 +6,7 @@ #' if possible becuase certain operations will be faster. #' @importFrom R6 R6Class #' @export -CategoricalVariable <- R6::R6Class( +CategoricalVariable <- R6Class( 'CategoricalVariable', public = list( diff --git a/R/double_variable.R b/R/double_variable.R index 1648288d..1164056a 100644 --- a/R/double_variable.R +++ b/R/double_variable.R @@ -2,7 +2,7 @@ #' @description Represents a continuous variable for an individual. #' @importFrom R6 R6Class #' @export -DoubleVariable <- R6::R6Class( +DoubleVariable <- R6Class( 'DoubleVariable', public = list( .variable = NULL, diff --git a/R/event.R b/R/event.R index c376acbb..5ea77711 100644 --- a/R/event.R +++ b/R/event.R @@ -2,7 +2,7 @@ #' @description Describes a general event in the simulation #' @importFrom R6 R6Class #' @export -Event <- R6::R6Class( +Event <- R6Class( 'Event', public = list( diff --git a/R/integer_variable.R b/R/integer_variable.R index b57dddb5..7d805fdf 100644 --- a/R/integer_variable.R +++ b/R/integer_variable.R @@ -6,7 +6,7 @@ #' household or age bin. #' @importFrom R6 R6Class #' @export -IntegerVariable <- R6::R6Class( +IntegerVariable <- R6Class( 'IntegerVariable', public = list( diff --git a/R/render.R b/R/render.R index 27e0573b..28c87c5e 100644 --- a/R/render.R +++ b/R/render.R @@ -2,7 +2,7 @@ #' @description Class to render output for the simulation #' @importFrom R6 R6Class #' @export -Render <- R6::R6Class( +Render <- R6Class( 'Render', private = list( .vectors = list(), diff --git a/R/targeted_event.R b/R/targeted_event.R index 363691bc..77dec9a1 100644 --- a/R/targeted_event.R +++ b/R/targeted_event.R @@ -1,8 +1,9 @@ #' @title TargetedEvent Class #' @description Describes a targeted event in the simulation #' This is useful for events which are triggered for a sub-population. +#' @importFrom R6 R6Class #' @export -TargetedEvent <- R6::R6Class( +TargetedEvent <- R6Class( 'TargetedEvent', inherit = Event, public = list( diff --git a/inst/include/IterableBitset.h b/inst/include/IterableBitset.h index a6169f5b..9ae96bab 100644 --- a/inst/include/IterableBitset.h +++ b/inst/include/IterableBitset.h @@ -433,6 +433,35 @@ inline IterableBitset filter_bitset( return result; } +//' @title randomly keep N items in the bitset +//' @description retain N items in the bitset. This function +//' modifies the bitset. +template +inline void bitset_choose_internal( + IterableBitset& b, + const size_t k +){ + auto to_remove = Rcpp::sample( + b.size(), + b.size() - k, + false, // replacement + R_NilValue, // evenly distributed + false // one based + ); + std::sort(to_remove.begin(), to_remove.end()); + auto bitset_i = 0u; + auto bitset_it = b.cbegin(); + for (auto i : to_remove) { + while(bitset_i != i) { + ++bitset_i; + ++bitset_it; + } + b.erase(*bitset_it); + ++bitset_i; + ++bitset_it; + } +} + //' @title sample the bitset //' @description retain a subset of values contained in this bitset, //' where each element has probability 'rate' to remain. diff --git a/inst/include/Variable.h b/inst/include/Variable.h index b440dfbe..7522d436 100644 --- a/inst/include/Variable.h +++ b/inst/include/Variable.h @@ -9,6 +9,7 @@ struct Variable { virtual void update() = 0; + virtual ~Variable() {}; }; #endif /* INST_INCLUDE_VARIABLE_H_ */ diff --git a/man/Bitset.Rd b/man/Bitset.Rd index 526d2205..2b3ebb2c 100644 --- a/man/Bitset.Rd +++ b/man/Bitset.Rd @@ -33,6 +33,7 @@ if you would like to perform an operation without destroying your current bitset \item \href{#method-xor}{\code{Bitset$xor()}} \item \href{#method-set_difference}{\code{Bitset$set_difference()}} \item \href{#method-sample}{\code{Bitset$sample()}} +\item \href{#method-choose}{\code{Bitset$choose()}} \item \href{#method-copy}{\code{Bitset$copy()}} \item \href{#method-to_vector}{\code{Bitset$to_vector()}} \item \href{#method-clone}{\code{Bitset$clone()}} @@ -203,6 +204,25 @@ probabilities for keeping each element} } } \if{html}{\out{
}} +\if{html}{\out{
}} +\if{latex}{\out{\hypertarget{method-choose}{}}} +\subsection{Method \code{choose()}}{ +choose k random items in the bitset +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{Bitset$choose(k)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{k}}{the number of items in the bitset to keep. The selection of +these k items from N total items in the bitset is random, and +k should be chosen such that 0 <= k < N.} +} +\if{html}{\out{
}} +} +} +\if{html}{\out{
}} \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-copy}{}}} \subsection{Method \code{copy()}}{ diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index d3b0e65f..dfd79138 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -187,6 +187,17 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// bitset_choose +void bitset_choose(const Rcpp::XPtr b, const size_t k); +RcppExport SEXP _individual_bitset_choose(SEXP bSEXP, SEXP kSEXP) { +BEGIN_RCPP + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const Rcpp::XPtr >::type b(bSEXP); + Rcpp::traits::input_parameter< const size_t >::type k(kSEXP); + bitset_choose(b, k); + return R_NilValue; +END_RCPP +} // create_categorical_variable Rcpp::XPtr create_categorical_variable(const std::vector& categories, const std::vector& values); RcppExport SEXP _individual_create_categorical_variable(SEXP categoriesSEXP, SEXP valuesSEXP) { @@ -845,6 +856,7 @@ static const R_CallMethodDef CallEntries[] = { {"_individual_bitset_to_vector", (DL_FUNC) &_individual_bitset_to_vector, 1}, {"_individual_filter_bitset_vector", (DL_FUNC) &_individual_filter_bitset_vector, 2}, {"_individual_filter_bitset_bitset", (DL_FUNC) &_individual_filter_bitset_bitset, 2}, + {"_individual_bitset_choose", (DL_FUNC) &_individual_bitset_choose, 2}, {"_individual_create_categorical_variable", (DL_FUNC) &_individual_create_categorical_variable, 2}, {"_individual_categorical_variable_queue_update", (DL_FUNC) &_individual_categorical_variable_queue_update, 3}, {"_individual_categorical_variable_get_index_of", (DL_FUNC) &_individual_categorical_variable_get_index_of, 2}, diff --git a/src/bitset.cpp b/src/bitset.cpp index 84f4af90..d32c1abd 100644 --- a/src/bitset.cpp +++ b/src/bitset.cpp @@ -155,3 +155,11 @@ Rcpp::XPtr filter_bitset_bitset( true ); } + +//[[Rcpp::export]] +void bitset_choose( + const Rcpp::XPtr b, + const size_t k +) { + bitset_choose_internal(*b, k); +} diff --git a/src/event.cpp b/src/event.cpp index b2d70d52..796b5c73 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -115,7 +115,7 @@ void process_listener( ) { size_t t = event->t; (*listener)(t); -}; +} // [[Rcpp::export]] void process_targeted_listener( diff --git a/src/prefab.cpp b/src/prefab.cpp index 6b8d2e6b..fadf917f 100644 --- a/src/prefab.cpp +++ b/src/prefab.cpp @@ -56,7 +56,7 @@ Rcpp::XPtr fixed_probability_multinomial_process_internal( }), true ); -}; +} // [[Rcpp::export]] @@ -105,7 +105,7 @@ Rcpp::XPtr multi_probability_multinomial_process_internal( }), true ); -}; +} // [[Rcpp::export]] Rcpp::XPtr multi_probability_bernoulli_process_internal( @@ -129,7 +129,7 @@ Rcpp::XPtr multi_probability_bernoulli_process_internal( }), true ); -}; +} // [[Rcpp::export]] Rcpp::XPtr infection_age_process_internal( diff --git a/tests/testthat/test-bitset.R b/tests/testthat/test-bitset.R index 0ef3e7e4..6bb2c4e0 100644 --- a/tests/testthat/test-bitset.R +++ b/tests/testthat/test-bitset.R @@ -167,9 +167,47 @@ test_that("bitset filtering works for bitsets", { expect_equal(filter_bitset(b, f)$to_vector(), c(1, 6)) }) +test_that("bitset filtering works when given empty index", { + b <- Bitset$new(10)$insert(c(1, 5, 6)) + f <- Bitset$new(10) + expect_equal(filter_bitset(b, f)$size(), 0) + expect_equal(filter_bitset(b, integer(0))$size(), 0) +}) + test_that("bitset throws error when given bad input probabilities in sample", { b <- Bitset$new(10)$insert(1:10) expect_error( b$sample(rate = c(rep(0.1,9),NA)) ) }) + +test_that("bitset choose behaves properly when given an empty bitset", { + b <- Bitset$new(10) + expect_error( + b$choose(5) + ) + expect_error( + b$choose(-1) + ) + expect_error( + b$choose(100) + ) + expect_error( + b$choose(Inf) + ) +}) + +test_that("bitset choose behaves properly when given a bitset with elements", { + + b <- Bitset$new(10)$insert(1:8) + expect_equal(b$copy()$or(b$copy()$choose(5))$to_vector(), b$to_vector()) # check that b$choose is a subset of b + + b <- Bitset$new(10)$insert(1:8) + expect_equal(b$choose(5)$size(), 5) + + b <- Bitset$new(10)$insert(1:8) + expect_equal(b$choose(0)$size(), 0) + + b <- Bitset$new(10)$insert(1:8) + expect_error(b$choose(8)) +})