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(
[](https://www.repostatus.org/#active)
[](https://github.com/mrc-ide/netz/actions)
[](https://codecov.io/github/mrc-ide/netz)
+[](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
+ )
+})