Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Competing hazards recovery infection resolution #300

Merged
merged 40 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b36be83
competing_hazards.R: Giovanni designed two objects: CompetingOutcome …
RJSheppard May 9, 2024
0e05f75
Added a stash source_human function to the competing hazards function…
RJSheppard May 9, 2024
f47bb3e
competing_hazards.R: Giovanni designed two objects: CompetingOutcome …
RJSheppard May 9, 2024
c8c3245
Added a stash source_human function to the competing hazards function…
RJSheppard May 9, 2024
c5947ef
To correctly render ages and incidence, given the competing hazards s…
RJSheppard May 23, 2024
4820186
Removed "age" from age outputs in tests and vignettes.
RJSheppard May 23, 2024
d563f6e
Swap back to explicitly labelled age outputs.
RJSheppard May 23, 2024
b8980c1
Merge branch 'dev' into competing_hazards_recovery_infection_resolution
RJSheppard May 24, 2024
62791a2
Removed missed merge commit line.
RJSheppard May 24, 2024
1b30562
Correcting rendering tests (merging error).
RJSheppard May 24, 2024
352fbae
biting_process renamed as create_biting_process, where functionality …
RJSheppard Jun 24, 2024
8866b5a
competing_hazards.R: Giovanni designed two objects: CompetingOutcome …
RJSheppard May 9, 2024
7702e19
Added a stash source_human function to the competing hazards function…
RJSheppard May 9, 2024
479c26e
To correctly render ages and incidence, given the competing hazards s…
RJSheppard May 23, 2024
6a4311a
Removed "age" from age outputs in tests and vignettes.
RJSheppard May 23, 2024
13748fd
competing_hazards.R: Giovanni designed two objects: CompetingOutcome …
RJSheppard May 9, 2024
98c982f
Added a stash source_human function to the competing hazards function…
RJSheppard May 9, 2024
16d83d3
Removed missed merge commit line.
RJSheppard May 24, 2024
c649292
biting_process renamed as create_biting_process, where functionality …
RJSheppard Jun 24, 2024
c85ca93
Re-added "age" to age outputs.
RJSheppard Jun 24, 2024
a6692e3
Merge branch 'competing_hazards_recovery_infection_resolution' of htt…
RJSheppard Jun 24, 2024
9142c58
Corrected age output names in Demography vignette to enable it to run…
RJSheppard Jun 26, 2024
b6787f7
Removed redundant commented lines.
RJSheppard Jul 3, 2024
5fec286
Add print step to touchstone workflow
giovannic Jul 3, 2024
598ade0
Revert "Add print step to touchstone workflow"
giovannic Jul 3, 2024
09e6e8c
Improve cumsum performance
plietar Jul 4, 2024
0f21bbc
Remove outlining of function
plietar Jul 4, 2024
a58e064
Optimize competing hazards.
plietar Jul 4, 2024
1f7310b
Fix empty occurs case.
plietar Jul 4, 2024
e2242b2
Simplify competing hazards as a single random draw.
plietar Jul 10, 2024
4c98559
Try to optimize competing hazards
plietar Jul 10, 2024
0bd7c10
All all states but S to create_recovery_rates_process
plietar Jul 10, 2024
91697ba
Merge remote-tracking branch 'origin/dev' into competing_hazards_reco…
plietar Jul 10, 2024
ded6876
Add a bitset_at implementation that works with a logical index.
plietar Jul 10, 2024
8f9bcfc
Fix tests
plietar Jul 10, 2024
f363b94
Fix more tests
plietar Jul 11, 2024
b339b7a
Add tests
plietar Jul 12, 2024
f2cacfb
Use new individual features
plietar Jul 27, 2024
e1bc3c1
Merge remote-tracking branch 'origin/dev' into competing_hazards_reco…
plietar Jul 27, 2024
babc34b
Fix output test
plietar Jul 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Remotes:
mrc-ide/malariaEquilibrium,
mrc-ide/individual
Imports:
individual (>= 0.1.16),
individual (>= 0.1.17),
malariaEquilibrium (>= 1.0.1),
Rcpp,
statmod,
Expand Down
9 changes: 6 additions & 3 deletions R/biting_process.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#' on the other populations
#' @param mixing_index an index for this population's position in the
#' lagged_infectivity list (default: 1)
#' @param infection_outcome competing hazards object for infection rates
#' @param timestep the current timestep
#' @noRd
create_biting_process <- function(
renderer,
Expand All @@ -26,12 +28,12 @@ create_biting_process <- function(
lagged_infectivity,
lagged_eir,
mixing_fn = NULL,
mixing_index = 1
mixing_index = 1,
infection_outcome
) {
function(timestep) {
# Calculate combined EIR
age <- get_age(variables$birth$get_values(), timestep)

bitten_humans <- simulate_bites(
renderer,
solvers,
Expand All @@ -54,7 +56,8 @@ create_biting_process <- function(
age,
parameters,
timestep,
renderer
renderer,
infection_outcome
)
}
}
Expand Down
95 changes: 95 additions & 0 deletions R/competing_hazards.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
## Define classes to resolve competing hazards
CompetingOutcome <- R6::R6Class(
"CompetingOutcome",
private = list(
targeted_process = NULL
),
public = list(
initialize = function(targeted_process, size){
if (!is.function(targeted_process)){
stop("targeted_process must be a function")
}
if (!is.numeric(size) || size <= 0){
stop("size must be positive integer")
}
private$targeted_process <- targeted_process

self$target <- individual::Bitset$new(size)
self$rates <- NULL
},
set_rates = function(target, rates){
stopifnot(target$size() == length(rates))

self$target$copy_from(target)
self$rates <- rates
},
execute = function(t, target){
private$targeted_process(t, target)
},
reset = function() {
self$target$clear()
self$rates <- NULL
},
target = NULL,
rates = NULL
)
)

CompetingHazard <- R6::R6Class(
"CompetingHazard",
private = list(
size = NULL,
outcomes = list(),
# RNG is passed in because mockery is not able to stub runif
# TODO: change when fixed
rng = NULL
),
public = list(
initialize = function(size, outcomes, rng = runif){
if (length(outcomes) == 0){
stop("At least one outcome must be provided")
}
if (!all(sapply(outcomes, function(x) inherits(x, "CompetingOutcome")))){
stop("All outcomes must be of class CompetingOutcome")
}
private$size <- size
private$outcomes <- outcomes
private$rng <- rng
},
resolve = function(t){
candidates <- individual::Bitset$new(private$size)
for (o in private$outcomes) {
candidates$or(o$target)
}

rates <- matrix(ncol = length(private$outcomes), nrow = candidates$size(), 0)
for (i in seq_along(private$outcomes)) {
idx <- bitset_index(
candidates,
private$outcomes[[i]]$target)

rates[idx, i] <- private$outcomes[[i]]$rates
}

total_rates <- rowSums(rates)
probs <- rate_to_prob(total_rates) * (rates / total_rates)
probs[is.na(probs)] <- 0

rng <- private$rng(candidates$size())

cumulative <- rep(0, candidates$size())

for (o in seq_along(private$outcomes)) {
next_cumulative <- cumulative + probs[,o]
selected <- (rng > cumulative) & (rng <= next_cumulative)
cumulative <- next_cumulative

target <- bitset_at(candidates, selected)
if (target$size() > 0) {
private$outcomes[[o]]$execute(t, target)
}
private$outcomes[[o]]$reset()
}
}
)
)
141 changes: 83 additions & 58 deletions R/disease_progression.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,70 @@
#' @title Calculate recovery rates
#' @description Calculates recovery rates for each individual in the population
#' for storage in competing hazards object and subsequent resolution
#'
#' @param variables the available human variables
#' @param recovery_outcome competing hazards object for recovery rates
#' @noRd
create_recovery_rates_process <- function(
variables,
recovery_outcome
) {
function(timestep){
target <- variables$state$get_index_of("S")$not()
recovery_outcome$set_rates(
target,
variables$recovery_rates$get_values(target))
}
}


#' @title Disease progression outcomes (recovery)
#' @description Following resolution of competing hazards, update state and
#' infectivity of sampled individuals
#'
#' @param timestep the current timestep
#' @param target the sampled recovering individuals
#' @param variables the available human variables
#' @param parameters model parameters
#' @param renderer competing hazards object for recovery rates
#' @noRd
recovery_outcome_process <- function(
timestep,
target,
variables,
parameters,
renderer
){

update_to_asymptomatic_infection(
variables,
parameters,
timestep,
variables$state$get_index_of("D")$and(target)
)

update_infection(
variables$state,
"U",
variables$infectivity,
parameters$cu,
variables$recovery_rates,
1/parameters$du,
variables$state$get_index_of("A")$and(target)
)

update_infection(
variables$state,
"S",
variables$infectivity,
0,
variables$recovery_rates,
0,
variables$state$get_index_of(c("U","Tr"))$and(target)
)

}

#' @title Update the state of an individual as infection events occur
#' @description Randomly moves individuals towards the later stages of disease
#' and updates their infectivity
Expand All @@ -8,63 +75,17 @@
#' @param new_infectivity the new infectivity of the progressed individuals
#' @noRd
update_infection <- function(
state,
to_state,
infectivity,
new_infectivity,
to_move
) {
state$queue_update(to_state, to_move)
infectivity$queue_update(new_infectivity, to_move)
}

create_progression_process <- function(
state,
from_state,
to_state,
rate,
infectivity,
new_infectivity
new_infectivity,
recovery_rates,
new_recovery_rate,
to_move
) {
function(timestep) {

# Retrieve the indices of all individuals in the to_move state:
index <- state$get_index_of(from_state)

# If the length of rate is greater than 1 (when it's a variable):
if (inherits(rate, "DoubleVariable")) {
rate <- rate$get_values(index)
}

# Sample the individuals to be moved into a new Bitset using the transition rate(s):
to_move <- index$sample(1/rate)

# Update the infection status of those individuals who are moving:
update_infection(
state,
to_state,
infectivity,
new_infectivity,
to_move
)
}
}

create_asymptomatic_progression_process <- function(
state,
rate,
variables,
parameters
) {
function(timestep) {
to_move <- state$get_index_of('D')$sample(1/rate)
update_to_asymptomatic_infection(
variables,
parameters,
timestep,
to_move
)
}
state$queue_update(to_state, to_move)
infectivity$queue_update(new_infectivity, to_move)
recovery_rates$queue_update(new_recovery_rate, to_move)
}

#' @title Modelling the progression to asymptomatic disease
Expand All @@ -75,11 +96,11 @@ create_asymptomatic_progression_process <- function(
#' @param parameters model parameters
#' @noRd
update_to_asymptomatic_infection <- function(
variables,
parameters,
timestep,
to_move
) {
variables,
parameters,
timestep,
to_move
) {
if (to_move$size() > 0) {
variables$state$queue_update('A', to_move)
new_infectivity <- asymptomatic_infectivity(
Expand All @@ -94,5 +115,9 @@ update_to_asymptomatic_infection <- function(
new_infectivity,
to_move
)
variables$recovery_rates$queue_update(
1/parameters$da,
to_move
)
}
}
Loading
Loading