From 693d50de866a18cfa3067c1e188f373eec3304ff Mon Sep 17 00:00:00 2001 From: Peter Winskill Date: Mon, 5 Jan 2026 13:33:11 +0000 Subject: [PATCH 1/5] Update License to conform to check requirements --- DESCRIPTION | 4 ++-- LICENSE | 23 ++--------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4176923..a8e3404 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: netz Title: All Things Bed Nets -Version: 1.0.4 +Version: 1.1.0 Authors@R: c( person( given = "Pete", @@ -26,7 +26,7 @@ License: MIT + file LICENSE Encoding: UTF-8 LazyData: true Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.2 +RoxygenNote: 7.3.3 Imports: stats Remotes: diff --git a/LICENSE b/LICENSE index 796e1f8..da837d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,2 @@ -MIT License - -Copyright (c) 2019 MRC Centre for Outbreak Analysis and Modelling - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +YEAR: 2026 +COPYRIGHT HOLDER: YMRC Centre for Outbreak Analysis and Modelling \ No newline at end of file From 2a5a44cd5444c05b188e6a164675a521fdc06fb4 Mon Sep 17 00:00:00 2001 From: Peter Winskill Date: Mon, 5 Jan 2026 13:33:48 +0000 Subject: [PATCH 2/5] Update malariasimualtion input/output compatability to include flexibility for user-specified net loss functions --- R/conversion.R | 2 +- R/malariasimulation.R | 101 ++++---- man/access_to_crop.Rd | 2 +- man/crop_to_access.Rd | 2 +- man/model_distribution_to_usage.Rd | 16 +- man/usage_to_model_distribution.Rd | 17 +- tests/testthat/test-malariasimulation.R | 294 +++++++++++++++++++++--- vignettes/Metrics.Rmd | 2 +- vignettes/malariasimulation.Rmd | 2 +- 9 files changed, 347 insertions(+), 91 deletions(-) diff --git a/R/conversion.R b/R/conversion.R index 038c399..019ab81 100644 --- a/R/conversion.R +++ b/R/conversion.R @@ -2,7 +2,7 @@ #' #' @param access A single or vector of access values #' @param type The npc to access model to use. This may be: -#' \itemize{ +#' \describe{ #' \item{"loess"}{: a loess fit to observed access-npc data} #' \item{"linear"}{: a linear fit to the observed access-npc data, fitted to the trend for observeation with access < 0.5} #' } diff --git a/R/malariasimulation.R b/R/malariasimulation.R index da88a62..f4d000c 100644 --- a/R/malariasimulation.R +++ b/R/malariasimulation.R @@ -1,50 +1,65 @@ -#' Estimate the malaraisimulation input distribution from usage -#' assumes an exponentially distributed net loss function and randomly +#' Estimate the malariasimulation input distribution from usage +#' assumes a specified net loss function and randomly #' correlated net distribution. #' +#' #' @param usage Target usage #' @param usage_timesteps Target usage time points #' @param distribution_timesteps A vector of distribution time steps #' @param distribution_lower Lower bound on distributions (default = 0) #' @param distribution_upper Upper bound on distribution (default = 1) -#' @param mean_retention The average duration of net retention (days) -#' +#' @param net_loss_function Function to calculate net retention over time. +#' Should take time as first argument and return proportion retained. +#' Defaults to net_loss_exp for backward compatibility. +#' @param... Additional arguments passed to net_loss_function +#' #' @export usage_to_model_distribution <- function( - usage, - usage_timesteps, - distribution_timesteps, - distribution_lower = rep(0, length(distribution_timesteps)), - distribution_upper = rep(1, length(distribution_timesteps)), - mean_retention = 365 * 5 -){ - - if(any(distribution_lower < 0) | any(distribution_lower > 1)){ + usage, + usage_timesteps, + distribution_timesteps, + distribution_lower = rep(0, length(distribution_timesteps)), + distribution_upper = rep(1, length(distribution_timesteps)), + net_loss_function = net_loss_exp, + ... +) { + if (any(distribution_lower < 0) | any(distribution_lower > 1)) { stop("All distribution lower values must be between 0 and 1") } - if(any(distribution_upper < 0) | any(distribution_upper > 1)){ + if (any(distribution_upper < 0) | any(distribution_upper > 1)) { stop("All distribution upper values must be between 0 and 1") } - if(length(distribution_lower) != length(distribution_timesteps) | length(distribution_upper) != length(distribution_timesteps)){ - stop("distribution_timesteps, distribution_lower and distribution_upper must have equal lengths") + if ( + length(distribution_lower) != length(distribution_timesteps) | + length(distribution_upper) != length(distribution_timesteps) + ) { + stop( + "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths" + ) } - if(length(usage) != length(usage_timesteps)){ + if (length(usage) != length(usage_timesteps)) { stop("Usage and usage timesteps must be the same length") } - - loss_rate <- 1 / mean_retention + distribution <- rep(0, length(distribution_timesteps)) - for(t in 1:length(distribution_timesteps)){ + for (t in 1:length(distribution_timesteps)) { # Usage at time point of next distribution - put <- model_distribution_to_usage(distribution_timesteps[t], distribution, distribution_timesteps, mean_retention) + put <- model_distribution_to_usage( + usage_timesteps = distribution_timesteps[t], + distribution = distribution, + distribution_timesteps = distribution_timesteps, + net_loss_function = net_loss_function, + ... + ) + # Find next target usage - time_offset <- usage_timesteps - distribution_timesteps[t] - if(max(time_offset) < 0){ + time_offset <- usage_timesteps - distribution_timesteps[t] + if (max(time_offset) < 0) { distribution[t] <- NA } else { nearest <- min(time_offset[time_offset >= 0]) index <- which(time_offset == nearest) - start_point <- usage[index] / exp(-loss_rate * time_offset[index]) + start_point <- usage[index] / net_loss_function(time_offset[index], ...) distribution[t] <- 1 - (1 - start_point) / (1 - put) distribution[t] <- min(distribution_upper[t], distribution[t]) distribution[t] <- max(distribution_lower[t], distribution[t]) @@ -54,44 +69,46 @@ usage_to_model_distribution <- function( } #' Estimate the population-level bed net usage at given times for a set of -#' bed net distributions at given times. This assumes a constant rate of net loss -#' (as in malariasimulation) and that recipients of multiple rounds are random. -#' +#' bed net distributions at given times. This assumes a specified rate of net loss +#' and that recipients of multiple rounds are random. +#' #' @inheritParams usage_to_model_distribution #' @param distribution Vector of model distribution (% of total population) +#' @param net_loss_function Function to calculate net retention over time. +#' Should take time as first argument and return proportion retained. +#' @param... Additional arguments passed to net_loss_function #' #' @return Usage estimates at timepoints #' @export model_distribution_to_usage <- function( - usage_timesteps, - distribution, - distribution_timesteps, - mean_retention = 365 * 5 -){ - loss_rate <- 1 / mean_retention - + usage_timesteps, + distribution, + distribution_timesteps, + net_loss_function = net_loss_exp, + ... +) { # Estimate the cumulative usage at distribution time points cumulative_usage <- distribution[1] - if(length(distribution_timesteps) > 1){ - for(t in 2:length(distribution_timesteps)){ + if (length(distribution_timesteps) > 1) { + for (t in 2:length(distribution_timesteps)) { time_offset <- distribution_timesteps[t] - distribution_timesteps[t - 1] - remaining <- cumulative_usage[t - 1] * exp(-loss_rate * time_offset) + remaining <- cumulative_usage[t - 1] * net_loss_function(time_offset, ...) cumulative_usage[t] <- 1 - (1 - remaining) * (1 - distribution[t]) } } - + # Estimate the usage at target time points usage <- c() - for(t in seq_along(usage_timesteps)){ + for (t in seq_along(usage_timesteps)) { time_offset <- usage_timesteps[t] - distribution_timesteps - if(max(time_offset) < 0){ + if (max(time_offset) < 0) { usage[t] <- 0 } else { nearest <- min(time_offset[time_offset >= 0]) index <- which(time_offset == nearest) - usage[t] <- cumulative_usage[index] * exp(-loss_rate * time_offset[index]) + usage[t] <- cumulative_usage[index] * + net_loss_function(time_offset[index], ...) } } return(usage) } - diff --git a/man/access_to_crop.Rd b/man/access_to_crop.Rd index 7cb3b6f..630b64d 100644 --- a/man/access_to_crop.Rd +++ b/man/access_to_crop.Rd @@ -10,7 +10,7 @@ access_to_crop(access, type = "loess", people_per_net = 1.66856) \item{access}{A single or vector of access values} \item{type}{The npc to access model to use. This may be: -\itemize{ +\describe{ \item{"loess"}{: a loess fit to observed access-npc data} \item{"linear"}{: a linear fit to the observed access-npc data, fitted to the trend for observeation with access < 0.5} }} diff --git a/man/crop_to_access.Rd b/man/crop_to_access.Rd index 833acb4..0cfec72 100644 --- a/man/crop_to_access.Rd +++ b/man/crop_to_access.Rd @@ -10,7 +10,7 @@ crop_to_access(crop, type = "loess", people_per_net = 1.66856) \item{crop}{A single or vector of nets per capita} \item{type}{The npc to access model to use. This may be: -\itemize{ +\describe{ \item{"loess"}{: a loess fit to observed access-npc data} \item{"linear"}{: a linear fit to the observed access-npc data, fitted to the trend for observeation with access < 0.5} }} diff --git a/man/model_distribution_to_usage.Rd b/man/model_distribution_to_usage.Rd index 27e48f9..dba8d4c 100644 --- a/man/model_distribution_to_usage.Rd +++ b/man/model_distribution_to_usage.Rd @@ -3,14 +3,15 @@ \name{model_distribution_to_usage} \alias{model_distribution_to_usage} \title{Estimate the population-level bed net usage at given times for a set of -bed net distributions at given times. This assumes a constant rate of net loss -(as in malariasimulation) and that recipients of multiple rounds are random.} +bed net distributions at given times. This assumes a specified rate of net loss +and that recipients of multiple rounds are random.} \usage{ model_distribution_to_usage( usage_timesteps, distribution, distribution_timesteps, - mean_retention = 365 * 5 + net_loss_function = net_loss_exp, + ... ) } \arguments{ @@ -20,13 +21,16 @@ model_distribution_to_usage( \item{distribution_timesteps}{A vector of distribution time steps} -\item{mean_retention}{The average duration of net retention (days)} +\item{net_loss_function}{Function to calculate net retention over time. +Should take time as first argument and return proportion retained.} + +\item{...}{Additional arguments passed to net_loss_function} } \value{ Usage estimates at timepoints } \description{ Estimate the population-level bed net usage at given times for a set of -bed net distributions at given times. This assumes a constant rate of net loss -(as in malariasimulation) and that recipients of multiple rounds are random. +bed net distributions at given times. This assumes a specified rate of net loss +and that recipients of multiple rounds are random. } diff --git a/man/usage_to_model_distribution.Rd b/man/usage_to_model_distribution.Rd index b21606d..727e372 100644 --- a/man/usage_to_model_distribution.Rd +++ b/man/usage_to_model_distribution.Rd @@ -2,8 +2,8 @@ % Please edit documentation in R/malariasimulation.R \name{usage_to_model_distribution} \alias{usage_to_model_distribution} -\title{Estimate the malaraisimulation input distribution from usage -assumes an exponentially distributed net loss function and randomly +\title{Estimate the malariasimulation input distribution from usage +assumes a specified net loss function and randomly correlated net distribution.} \usage{ usage_to_model_distribution( @@ -12,7 +12,8 @@ usage_to_model_distribution( distribution_timesteps, distribution_lower = rep(0, length(distribution_timesteps)), distribution_upper = rep(1, length(distribution_timesteps)), - mean_retention = 365 * 5 + net_loss_function = net_loss_exp, + ... ) } \arguments{ @@ -26,10 +27,14 @@ usage_to_model_distribution( \item{distribution_upper}{Upper bound on distribution (default = 1)} -\item{mean_retention}{The average duration of net retention (days)} +\item{net_loss_function}{Function to calculate net retention over time. +Should take time as first argument and return proportion retained. +Defaults to net_loss_exp for backward compatibility.} + +\item{...}{Additional arguments passed to net_loss_function} } \description{ -Estimate the malaraisimulation input distribution from usage -assumes an exponentially distributed net loss function and randomly +Estimate the malariasimulation input distribution from usage +assumes a specified net loss function and randomly correlated net distribution. } diff --git a/tests/testthat/test-malariasimulation.R b/tests/testthat/test-malariasimulation.R index 99eef58..6c1d33e 100644 --- a/tests/testthat/test-malariasimulation.R +++ b/tests/testthat/test-malariasimulation.R @@ -1,38 +1,268 @@ -test_that("malariasimulation usage fitting works", { +test_that("malariasimulation usage fitting works - exponential model", { tu <- 0 tut <- 365 dt <- 365 - - expect_equal(usage_to_model_distribution(0, tut, dt), 0) - expect_equal(usage_to_model_distribution(1, tut, dt), 1) - expect_equal(usage_to_model_distribution(c(1,1), (1:2) * 365, (1:2) * 365), c(1, 1)) - expect_equal(usage_to_model_distribution(c(0,0), (1:2) * 365, (1:2) * 365), c(0, 0)) - expect_equal(usage_to_model_distribution(c(1,0), (1:2) * 365, (1:2) * 365), c(1, 0)) - expect_equal(usage_to_model_distribution(c(1,1), (1:2) * 365, (1:3) * 365), c(1, 1, NA)) - - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_lower = -1), - "All distribution lower values must be between 0 and 1") - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_lower = 6), - "All distribution lower values must be between 0 and 1") - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_upper = -1), - "All distribution upper values must be between 0 and 1") - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_upper = 6), - "All distribution upper values must be between 0 and 1") - - - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_lower = c(0.1, 0.1)), - "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths") - expect_error(usage_to_model_distribution(tu, tut, dt, distribution_upper = c(0.1, 0.1)), - "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths") - - expect_error(usage_to_model_distribution(tu, c(tut, tut), dt), - "Usage and usage timesteps must be the same length") - + + expect_equal( + usage_to_model_distribution(0, tut, dt, mean_retention = 5 * 365), + 0 + ) + expect_equal( + usage_to_model_distribution(1, tut, dt, mean_retention = 5 * 365), + 1 + ) + expect_equal( + usage_to_model_distribution( + c(1, 1), + (1:2) * 365, + (1:2) * 365, + mean_retention = 5 * 365 + ), + c(1, 1) + ) + expect_equal( + usage_to_model_distribution( + c(0, 0), + (1:2) * 365, + (1:2) * 365, + mean_retention = 5 * 365 + ), + c(0, 0) + ) + expect_equal( + usage_to_model_distribution( + c(1, 0), + (1:2) * 365, + (1:2) * 365, + mean_retention = 5 * 365 + ), + c(1, 0) + ) + expect_equal( + usage_to_model_distribution( + c(1, 1), + (1:2) * 365, + (1:3) * 365, + mean_retention = 5 * 365 + ), + c(1, 1, NA) + ) + + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = -1, + mean_retention = 5 * 365 + ), + "All distribution lower values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = 6, + mean_retention = 5 * 365 + ), + "All distribution lower values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = -1, + mean_retention = 5 * 365 + ), + "All distribution upper values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = 6, + mean_retention = 5 * 365 + ), + "All distribution upper values must be between 0 and 1" + ) + + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = c(0.1, 0.1), + mean_retention = 5 * 365 + ), + "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = c(0.1, 0.1), + mean_retention = 5 * 365 + ), + "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths" + ) + + expect_error( + usage_to_model_distribution(tu, c(tut, tut), dt, mean_retention = 5 * 365), + "Usage and usage timesteps must be the same length" + ) }) +test_that("malariasimulation usage fitting works - MAP model", { + tu <- 0 + tut <- 365 + dt <- 365 + + expect_equal( + usage_to_model_distribution( + 0, + tut, + dt, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + 0 + ) + expect_equal( + usage_to_model_distribution( + 1, + tut, + dt, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + 1 + ) + expect_equal( + usage_to_model_distribution( + c(1, 1), + (1:2) * 365, + (1:2) * 365, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + c(1, 1) + ) + expect_equal( + usage_to_model_distribution( + c(0, 0), + (1:2) * 365, + (1:2) * 365, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + c(0, 0) + ) + expect_equal( + usage_to_model_distribution( + c(1, 0), + (1:2) * 365, + (1:2) * 365, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + c(1, 0) + ) + expect_equal( + usage_to_model_distribution( + c(1, 1), + (1:2) * 365, + (1:3) * 365, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + c(1, 1, NA) + ) + + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = -1, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "All distribution lower values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = 6, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "All distribution lower values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = -1, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "All distribution upper values must be between 0 and 1" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = 6, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "All distribution upper values must be between 0 and 1" + ) + + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_lower = c(0.1, 0.1), + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths" + ) + expect_error( + usage_to_model_distribution( + tu, + tut, + dt, + distribution_upper = c(0.1, 0.1), + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "distribution_timesteps, distribution_lower and distribution_upper must have equal lengths" + ) + + expect_error( + usage_to_model_distribution( + tu, + c(tut, tut), + dt, + net_loss_function = net_loss_map, + half_life = 2 * 365 + ), + "Usage and usage timesteps must be the same length" + ) +}) test_that("population_usage", { - timesteps <- 10 * 365 pu <- model_distribution_to_usage( usage_timesteps = 1:timesteps, @@ -40,10 +270,10 @@ test_that("population_usage", { distribution_timesteps = 1, mean_retention = 5 * 365 ) - + expect_equal(length(pu), timesteps) expect_equal(pu, net_loss_exp(0:(timesteps - 1), 5 * 365)) - + dt <- c(1, 101, 201, 301) pu <- model_distribution_to_usage( usage_timesteps = 1:timesteps, @@ -51,6 +281,6 @@ test_that("population_usage", { distribution_timesteps = dt, mean_retention = 5 * 365 ) - + expect_equal(pu[dt], rep(1, length(dt))) -}) \ No newline at end of file +}) diff --git a/vignettes/Metrics.Rmd b/vignettes/Metrics.Rmd index 6165d9b..2e6ad0c 100644 --- a/vignettes/Metrics.Rmd +++ b/vignettes/Metrics.Rmd @@ -168,7 +168,7 @@ to a smooth-compact function (net_loss_function = net_loss_map). This in line with the paper by Bertozzi-Villa et al. An alternative option is to assume exponential net loss (`net_loss_function = net_loss_exp`), -which corresponds to the assumption in malariasimulation. +which corresponds to the default assumption in malariasimulation. The proportion of nets retained over time are visualised for both options below, for a 3 year distribution cycle and a net half life of 1.64 years. diff --git a/vignettes/malariasimulation.Rmd b/vignettes/malariasimulation.Rmd index 7b93050..b84e959 100644 --- a/vignettes/malariasimulation.Rmd +++ b/vignettes/malariasimulation.Rmd @@ -73,7 +73,7 @@ simparams <- get_parameters( coverages = data$model_input, retention = 700, dn0 = matrix(rep(0.533, 10), nrow = 10, ncol = 1), - rn = matrix(rep(0.56, 10), nrow = 10, ncol = 1), + rn = matrix(rep(0.36, 10), nrow = 10, ncol = 1), rnm = matrix(rep(0.24, 10), nrow = 10, ncol = 1), gamman = rep(2.64 * 365, 10) ) From e2b3d5547adfa60ca326f09836c0bd1c4a462ae7 Mon Sep 17 00:00:00 2001 From: Peter Winskill Date: Mon, 5 Jan 2026 13:42:32 +0000 Subject: [PATCH 3/5] Update test coverage github action --- .github/workflows/test-coverage.yaml | 28 ++++++++++++++++++++-------- README.Rmd | 3 ++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index 2c5bb50..0ab748d 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -4,9 +4,10 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] -name: test-coverage +name: test-coverage.yaml + +permissions: read-all jobs: test-coverage: @@ -15,7 +16,7 @@ jobs: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: r-lib/actions/setup-r@v2 with: @@ -23,28 +24,39 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: any::covr + extra-packages: any::covr, any::xml2 needs: coverage - name: Test coverage run: | - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, - install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) + print(cov) + covr::to_cobertura(cov) shell: Rscript {0} + - uses: codecov/codecov-action@v5 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + files: ./cobertura.xml + plugins: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + - name: Show testthat output if: always() run: | ## -------------------------------------------------------------------- - find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true shell: bash - name: Upload test results if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: coverage-test-failures path: ${{ runner.temp }}/package diff --git a/README.Rmd b/README.Rmd index 9c3c829..03a93f2 100644 --- a/README.Rmd +++ b/README.Rmd @@ -17,6 +17,7 @@ knitr::opts_chunk$set( [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) [![R-CMD-check](https://github.com/mrc-ide/netz/workflows/R-CMD-check/badge.svg)](https://github.com/mrc-ide/netz/actions) [![Coverage status](https://codecov.io/gh/mrc-ide/peeps/branch/main/graph/badge.svg)](https://codecov.io/github/mrc-ide/netz) +[![Codecov test coverage](https://codecov.io/gh/mrc-ide/netz/graph/badge.svg)](https://app.codecov.io/gh/mrc-ide/netz) Netz is here to help setup bed nets in [malariasimulation](https://mrc-ide.github.io/malariasimulation/). @@ -42,4 +43,4 @@ need to be careful that out model inputs match our desired target usage when specifying bed nets. The schematic below will help in understanding how each of these elements relate to one another - \ No newline at end of file + From dac8bd8c48a75d8ff1bbe15356bded899c580250 Mon Sep 17 00:00:00 2001 From: Peter Winskill Date: Wed, 14 Jan 2026 14:20:52 +0000 Subject: [PATCH 4/5] Fix bug where zeros in distribution threw of time since last distribution calculation --- DESCRIPTION | 2 +- R/malariasimulation.R | 8 ++++++++ tests/testthat/test-malariasimulation.R | 10 ++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index a8e3404..e03660d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: netz Title: All Things Bed Nets -Version: 1.1.0 +Version: 1.1.1 Authors@R: c( person( given = "Pete", diff --git a/R/malariasimulation.R b/R/malariasimulation.R index f4d000c..bba5cb9 100644 --- a/R/malariasimulation.R +++ b/R/malariasimulation.R @@ -87,6 +87,14 @@ model_distribution_to_usage <- function( net_loss_function = net_loss_exp, ... ) { + # Deal with zero distributions in inputs + zero_dists <- distribution == 0 + distribution <- distribution[!zero_dists] + distribution_timesteps <- distribution_timesteps[!zero_dists] + if (length(distribution) == 0) { + return(rep(0, length(usage_timesteps))) + } + # Estimate the cumulative usage at distribution time points cumulative_usage <- distribution[1] if (length(distribution_timesteps) > 1) { diff --git a/tests/testthat/test-malariasimulation.R b/tests/testthat/test-malariasimulation.R index 6c1d33e..eff2715 100644 --- a/tests/testthat/test-malariasimulation.R +++ b/tests/testthat/test-malariasimulation.R @@ -284,3 +284,13 @@ test_that("population_usage", { expect_equal(pu[dt], rep(1, length(dt))) }) + +test_that("zero distributions are dealt with correctly", { + zero_dist <- model_distribution_to_usage( + usage_timesteps = 1:3, + distribution = c(0, 0), + distribution_timesteps = c(1, 2), + mean_retention = 5 * 365 + ) + expect_equal(zero_dist, c(0, 0, 0)) +}) From 3f4bf9cf3599f4fdb47688e7e544a47e279f74b8 Mon Sep 17 00:00:00 2001 From: Peter Winskill Date: Fri, 16 Jan 2026 13:12:40 +0000 Subject: [PATCH 5/5] Bug fix, handling mutliple distibution timepoints after last usage timepoint when inferring model inputs --- DESCRIPTION | 2 +- R/malariasimulation.R | 8 +++++++- man/usage_to_model_distribution.Rd | 5 +++++ tests/testthat/test-malariasimulation.R | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e03660d..87b3c39 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: netz Title: All Things Bed Nets -Version: 1.1.1 +Version: 1.1.2 Authors@R: c( person( given = "Pete", diff --git a/R/malariasimulation.R b/R/malariasimulation.R index bba5cb9..0de567c 100644 --- a/R/malariasimulation.R +++ b/R/malariasimulation.R @@ -13,6 +13,11 @@ #' Defaults to net_loss_exp for backward compatibility. #' @param... Additional arguments passed to net_loss_function #' +#' @details Note, the estimation works sequentially in time order, so if there +#' are multiple distribution steps specified between target usage timestep they will +#' be inferred sequentially. You can mange this behaviour with the `distribution_lower` and +#' `distribution_upper` arguments. +#' #' @export usage_to_model_distribution <- function( usage, @@ -55,7 +60,8 @@ usage_to_model_distribution <- function( # Find next target usage time_offset <- usage_timesteps - distribution_timesteps[t] if (max(time_offset) < 0) { - distribution[t] <- NA + distribution[t:length(distribution_timesteps)] <- NA + break } else { nearest <- min(time_offset[time_offset >= 0]) index <- which(time_offset == nearest) diff --git a/man/usage_to_model_distribution.Rd b/man/usage_to_model_distribution.Rd index 727e372..b80a342 100644 --- a/man/usage_to_model_distribution.Rd +++ b/man/usage_to_model_distribution.Rd @@ -38,3 +38,8 @@ Estimate the malariasimulation input distribution from usage assumes a specified net loss function and randomly correlated net distribution. } +\details{ +Note, the estimation works sequentially in time order, so if there +are multiple distribution steps specified between target usage timestep they will +be inferred sequentially. +} diff --git a/tests/testthat/test-malariasimulation.R b/tests/testthat/test-malariasimulation.R index eff2715..fb2d1d5 100644 --- a/tests/testthat/test-malariasimulation.R +++ b/tests/testthat/test-malariasimulation.R @@ -294,3 +294,17 @@ test_that("zero distributions are dealt with correctly", { ) expect_equal(zero_dist, c(0, 0, 0)) }) + + +test_that("Input distribution estimation deals with + input timepoints after final target use timepoint elegantly", { + usage_to_model_distribution( + usage = 0.1, + usage_timesteps = 1, + distribution_timesteps = 1:3, + distribution_lower = rep(0, 3), + distribution_upper = rep(1, 3), + net_loss_function = net_loss_map, + half_life = 300 + ) +})