Skip to content

Commit fc471d8

Browse files
authoredJun 9, 2021
Update Vaccine Model (#211)
This commit includes 3 big upgrades to the vaccine model: Strain aware - the vaccine is now strain aware and different levels of efficacy can be applied to different strains Immunity model - the immunity is now extended to 3 levels: full protection, protection against symptoms, protection against severe disease Vaccine conveyed immunity - instead of 2 types of vaccine either conveying full or against symptoms, each vaccine can convey each of the 3 types of immunity with different levels of efficacy The strain model is also extended to allow different levels of hospitalisation. Details of sub-commits: * Preparatory work for vaccine strain awareness Separate the transition event functions between vaccine types * Preparation for multiple strains + vaccines Add immune_to_symptoms to the individual * More prep for strain aware vaccines Give event a short which can be passed to the transition function Pass wane time to the vaccine_protect event Make immune from symptoms the max of current and that from vaccine * Add a vaccine object containing the details of each vaccine Add and test the object only * Convert Python and C code to use vaccine object Leave the Python functions backwards compatible (still need to do R) * Convert info_short on the event to a null pointer Pass a pointer to the vaccine in the event * Make immune_to_symptoms strain aware Currently set to the same value for all strains * Check-point on way to making vaccine strain aware Make vaccine against symptoms strain aware * Add set_immune() for strain specific immunity Make full vaccination strain aware Check strain specific hazard when seed infecting * Consolidate adding infection event into single function Fix seed_infection_by_idx for multiple strains Turn on test_vaccinet_protection_multi_strain for full protection vaccine * Rename time_susceptible to immune_full for consistency * Add immune_full macro for checking immunity * Unify vaccine types (remove backwards compatibility in Python interface) A single vaccine can give full protection and protection from symptoms at different levels of efficacy * Add third type of immunity - immune from severe symptoms Allow individuals to develop disease but not progress to severe symptoms requiring hopsitalisation For the vaccines, allow to specify efficacy for the new type of severity * Add multiple strains to the properties of the Python object Extend test_add_vaccine to include to multiple strains include both when the vaccine is the same for all strains and when different between strains * Add Python strain object and update tests to use it Leave Python interface backwards compatible using the strain_idx * Make hospitalisation_fraction variable for new strains * For seed infections, explicitly use current time as time_inf_infector * Fix VACCINE_STATUS list * Add Strain object in R and upade Model$add_strain Fix Network.R object * Add wrapper for set_cross_immunity_matrix * Add ability for seed_infect_by_idx to take a strain * Add add_vaccine and Vaccine object to R interface * Switch VaccineSchedule in R interface to use Vaccine object * Only set to susceptible if no current disease (fix double infection bug) * Fix counter of total_vacinated in R vaccine schedule * Pass strain directly to new_infection and add_infection_event Previously inferred from infector, however, problem when seeding infections in individuals having more than one infection * Set sd infectious muptiplier to 0 in presymp/symp test to reduce noise Reduce size of population to speed up tests. * Add example_multi_strain_vaccinate.py Example demonstrating how to create multiple strains and vaccines. * Remove bogus type mismatch warning Copy hospitalised_fraction to an array pointer when creating initial strain * Add description of cross-immunity and vaccinations
1 parent d9fa0ee commit fc471d8

37 files changed

+1721
-348
lines changed
 

‎DESCRIPTION

+2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ Collate:
5959
Network.R
6060
Model.R
6161
Simulation.R
62+
Strain.R
6263
VaccineSchedule.R
64+
Vaccine.R
6365
Suggests:
6466
testthat
6567
RoxygenNote: 7.1.1

‎Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ CFLAGS = -g -Wall -fmessage-length=0 -I$(INC) $(shell gsl-config --cflags) -O0
4747
LDFLAGS = -L$(LIB) $(shell gsl-config --libs)
4848

4949
# Swig's input
50-
SWIG_INPUT = src/disease.h src/ward.h src/nurse.h src/network_utils.i src/input.h src/individual.h src/hospital.h src/params.h src/structure.h src/constant.h src/doctor.h src/utilities.h src/model_utils.i src/covid19.i src/list.h src/network.h src/model.h src/interventions.h src/params_utils.i src/demographics.h src/strain.h
50+
SWIG_INPUT = src/disease.h src/ward.h src/nurse.h src/network_utils.i src/vaccine_utils.i src/strain_utils.i src/input.h src/individual.h src/hospital.h src/params.h src/structure.h src/constant.h src/doctor.h src/utilities.h src/model_utils.i src/covid19.i src/list.h src/network.h src/model.h src/interventions.h src/params_utils.i src/demographics.h src/strain.h
5151

5252
# Swig's output
5353
SWIG_OUTPUT_PY = src/covid19_wrap.o src/covid19_wrap.c src/covid19.py src/_covid19.cpython-37m-darwin.so src/build src/covid19.egg-info

‎NAMESPACE

+2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ export("Network")
1414
export("Environment")
1515
export("Agent")
1616
export("Simulation")
17+
export("Strain")
1718
export("COVID19IBM")
1819
export("VaccineSchedule")
20+
export("Vaccine")
1921

2022
# wrapper function for R6 calsses
2123
export("Parameters.new")

‎R/Model.R

+159-42
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ SWIG_calculate_R_instanteous <- calculate_R_instanteous
2323
SWIG_seed_infect_by_idx <- seed_infect_by_idx
2424
SWIG_add_new_strain <- add_new_strain
2525
SWIG_destroy_model <- destroy_model
26+
SWIG_set_cross_immunity_probability <- set_cross_immunity_probability
2627

2728

2829
#' R6Class Model
@@ -225,8 +226,13 @@ Model <- R6Class( classname = 'Model', cloneable = FALSE,
225226
enum <- get_base_param_from_enum(param)
226227
if (!is.null(enum)) {
227228
# multi-value parameter (C array)
228-
getter <- get(paste0("get_model_param_", enum$base_name))
229-
result <- getter( self$c_model, enum$index )
229+
getter_str = paste0("get_model_param_", enum$base_name)
230+
if (exists(getter_str)) {
231+
getter <- get(paste0("get_model_param_", enum$base_name))
232+
result <- getter( self$c_model, enum$index )
233+
} else {
234+
result = private$params_object$get_param(param)
235+
}
230236
} else {
231237
# single-value parameter
232238
getter_str <- paste0("get_model_param_", param)
@@ -465,26 +471,81 @@ Model <- R6Class( classname = 'Model', cloneable = FALSE,
465471
#' @param strain_idx The idx of the strain the person is infected with
466472
#' @param network_id The network ID.
467473
#' @return \code{TRUE} on success, \code{FALSE} otherwise.
468-
seed_infect_by_idx = function(ID, strain_idx = 1, network_id = -1 )
474+
seed_infect_by_idx = function(ID, strain_idx = 0, strain = NULL, network_id = -1 )
469475
{
470476
n_total <- private$c_params$n_total
471-
472477
if (ID < 0 || ID >= n_total) {
473478
stop("ID out of range (0<=ID<n_total)")
474479
}
475-
480+
481+
if( !is.null( strain ) )
482+
{
483+
if (!is.R6(strain) || !('Strain' %in% class(strain)))
484+
stop("argument strain must be an object of type Strain")
485+
486+
strain_idx = strain$idx()
487+
}
488+
489+
n_strains = self$c_model$n_initialised_strains;
490+
if( strain_idx < 0 || strain_idx >= n_strains )
491+
stop( "strain_idx out of range (0 <= strain_idx < self$c_model$n_initialized_strains)" )
492+
476493
res <- SWIG_seed_infect_by_idx(self$c_model, ID, strain_idx, network_id)
477494
return(as.logical(res))
478495
},
479496

480497
#' @description Adds a new strain (variant)
481498
#' Wrapper for C API \code{add_new_strain}.
482499
#' @param transmission_multiplier The relative transmission rate of the strain
483-
#' @return \code{strain_idx} The index assigned to the new strain
484-
add_new_strain = function(ID, transmission_multiplier = 1 )
500+
#' @param hospitalised_fraction the fraction of symptomatic (not mild) who progress to hospital [default: None is no change)]
501+
#' @return \code{Strain} A Strain object representing this strain
502+
add_new_strain = function( transmission_multiplier = 1, hospitalised_fraction = NA )
503+
{
504+
505+
max_n_strains = self$get_param( "max_n_strains" )
506+
n_strains = self$c_model$n_initialised_strains;
507+
508+
if( n_strains == max_n_strains )
509+
stop( "cannot add any more strains - increase the parameter max_n_strains at the initialisation of the model" )
510+
511+
if( is.na( hospitalised_fraction ) )
512+
{
513+
hospitalised_fraction = c()
514+
for( idx in 1:length( AgeGroupEnum ) )
515+
hospitalised_fraction[ idx ] = self$get_param(
516+
sprintf( "hospitalised_fraction%s", names( AgeGroupEnum[idx])) )
517+
}
518+
519+
c_model_ptr <- private$c_model_ptr()
520+
strain_idx<-.Call('R_add_new_strain',c_model_ptr,transmission_multiplier,
521+
hospitalised_fraction, PACKAGE='OpenABMCovid19');
522+
523+
return( Strain$new( self, strain_idx ) )
524+
},
525+
526+
#' @description Set the cross_immunity matrix
527+
#' Wrapper for C API \code{set_cross_immunity_probability}.
528+
#' @param cross_immunity the cross immunity matrix
529+
#' @return
530+
531+
set_cross_immunity_matrix = function( cross_immunity )
485532
{
486-
strain_idx <- add_new_strain(self$c_model, transmission_multiplier )
487-
return(strain_idx)
533+
max_n_strains = self$get_param("max_n_strains")
534+
535+
if( !is.matrix( cross_immunity ) )
536+
stop( "cross_immunity is a matrix ")
537+
538+
if( max( nrow( cross_immunity ), ncol( cross_immunity ) ) > max_n_strains )
539+
stop( "cross_immunnity matrix is of maximum size max_n_strains")
540+
541+
for( i in 1:nrow( cross_immunity) )
542+
for( j in 1:ncol( cross_immunity) )
543+
{
544+
if( cross_immunity[i,j] < 0 || cross_immunity[i,j] > 1 )
545+
stop( "cross_immunity must be between 0 and 1")
546+
547+
SWIG_set_cross_immunity_probability( self$c_model, i-1, j-1, cross_immunity[i,j] )
548+
}
488549
},
489550

490551
#' @description Get the list of network IDs
@@ -535,43 +596,90 @@ Model <- R6Class( classname = 'Model', cloneable = FALSE,
535596
return(as.data.frame(tmp))
536597
},
537598

538-
#' @description Vaccinate an individual.
539-
#' Wrapper for C API \code{intervention_vaccinate_by_idx}.
540-
#' @param ID The ID of the individual (must be \code{0 <= ID <= n_total}).
541-
#' @param vaccine_type The type of vaccine, see \code{\link{VACCINE_TYPES}}.
542-
#' @param efficacy Probability that the person is successfully vaccinated
599+
#' @description Add a new vaccine.
600+
#' Wrapper for C API \code{add_vaccine}.
601+
#' @param full_efficacy Probability that the person is successfully vaccinated
543602
#' (must be \code{0 <= efficacy <= 1}).
603+
#' @param symptoms_efficacy Probability that the person is successfully vaccinated
604+
#' against getting symptoms (must be \code{0 <= efficacy <= 1}).
605+
#' @param sever_efficacy Probability that the person is successfully vaccinated
606+
#' against getting severer symptoms (must be \code{0 <= efficacy <= 1}).
544607
#' @param time_to_protect Delay before it takes effect (in days).
545608
#' @param vaccine_protection_period The duration of the vaccine before it
546609
#' wanes.
610+
#' @return Vaccine object
611+
add_vaccine = function(
612+
full_efficacy = 1.0,
613+
symptoms_efficacy = 1.0,
614+
severe_efficacy = 1.0,
615+
time_to_protect = 14,
616+
vaccine_protection_period = 1000
617+
)
618+
{
619+
if( time_to_protect < 1 )
620+
stop( "vaccine must take at least one day to take effect" )
621+
622+
if( vaccine_protection_period <= time_to_protect )
623+
stop( "vaccine must protect for longer than it takes to by effective" )
624+
625+
n_strains = self$get_param( "max_n_strains" )
626+
627+
if( length( full_efficacy ) == 1 )
628+
full_efficacy = rep( full_efficacy, n_strains )
629+
630+
if( length( full_efficacy ) != n_strains )
631+
stop( "full_efficacy must be a float or a list of length max_n_strains" )
632+
633+
if( length( symptoms_efficacy ) == 1 )
634+
symptoms_efficacy = rep( symptoms_efficacy, n_strains )
635+
636+
if( length( symptoms_efficacy ) != n_strains )
637+
stop( "symptoms_efficacy must be a float or a list of length max_n_strains" )
638+
639+
if( length( severe_efficacy ) == 1 )
640+
severe_efficacy = rep( severe_efficacy, n_strains )
641+
642+
if( length( severe_efficacy ) != n_strains )
643+
stop( "severe_efficacy must be a float or a list of length max_n_strains" )
644+
645+
for( idx in 1:n_strains )
646+
{
647+
if( full_efficacy[ idx ] < 0 | full_efficacy[ idx ] > 1 )
648+
stop( "full_efficacy must be between 0 and 1" )
649+
650+
if( symptoms_efficacy[ idx ] < 0 | symptoms_efficacy[ idx ] > 1 )
651+
stop( "symptoms_efficacy must be between 0 and 1" )
652+
653+
if( severe_efficacy[ idx ] < 0 | severe_efficacy[ idx ] > 1 )
654+
stop( "severe_efficacy must be between 0 and 1" )
655+
}
656+
657+
c_model_ptr <- private$c_model_ptr()
658+
idx<-.Call('R_add_vaccine', c_model_ptr, full_efficacy, symptoms_efficacy,
659+
severe_efficacy, time_to_protect, vaccine_protection_period,
660+
PACKAGE='OpenABMCovid19');
661+
662+
return( Vaccine$new( self, idx ) )
663+
},
664+
665+
#' @description Vaccinate an individual.
666+
#' Wrapper for C API \code{intervention_vaccinate_by_idx}.
667+
#' @param ID The ID of the individual (must be \code{0 <= ID <= n_total}).
668+
#' @param vaccine The of vaccine object to be given
547669
#' @return Logical value, \code{TRUE} if vaccinated \code{FALSE} otherwise.
548-
vaccinate_individual = function(
549-
ID,
550-
vaccine_type = 0,
551-
efficacy = 1.0,
552-
time_to_protect = 14,
553-
vaccine_protection_period = 1000 )
670+
vaccinate_individual = function( ID, vaccine )
554671
{
555672
n_total <- private$c_params$n_total
556673

557674
if (ID < 0 || ID >= n_total) {
558675
stop("ID out of range (0<=ID<n_total)")
559676
}
560-
if (efficacy < 0 || efficacy > 1) {
561-
stop("efficacy must be between 0 and 1")
562-
}
563-
if (time_to_protect < 1) {
564-
stop("vaccine must take at least one day to take effect")
565-
}
566-
if (vaccine_protection_period <= time_to_protect) {
567-
stop("vaccine must protect for longer than it takes to by effective")
568-
}
569-
if (!is.numeric(vaccine_type) || ! vaccine_type %in% VACCINE_TYPES) {
570-
stop("vaccine type must be listed in VaccineTypesEnum")
571-
}
572677

573-
res <- intervention_vaccinate_by_idx(self$c_model, ID, vaccine_type,
574-
efficacy, time_to_protect, vaccine_protection_period)
678+
if (!is.R6(vaccine) || !('Vaccine' %in% class(vaccine)))
679+
stop("argument vaccine must be an object of type Vaccine")
680+
681+
res <- intervention_vaccinate_by_idx(self$c_model, ID, vaccine$c_vaccine )
682+
575683
return(as.logical(res))
576684
},
577685

@@ -584,14 +692,23 @@ Model <- R6Class( classname = 'Model', cloneable = FALSE,
584692
if (!is.R6(schedule) || !('VaccineSchedule' %in% class(schedule))) {
585693
stop("argument VaccineSchedule must be an object of type VaccineSchedule")
586694
}
587-
return(as.logical(intervention_vaccinate_age_group(
588-
self$c_model,
589-
schedule$fraction_to_vaccinate,
590-
schedule$vaccine_type,
591-
schedule$efficacy,
592-
schedule$time_to_protect,
593-
schedule$vaccine_protection_period,
594-
schedule$total_vaccinated)))
695+
696+
vaccine = schedule$vaccine
697+
if (!is.R6(vaccine) || !('Vaccine' %in% class(vaccine))) {
698+
stop("argument schedule$vaccine must be an object of type Vaccine")
699+
}
700+
701+
c_model_ptr = private$c_model_ptr()
702+
c_vaccine_ptr = vaccine$c_vaccine@ref
703+
total_vaccinated = schedule$total_vaccinated
704+
fraction_to_vaccinate = schedule$fraction_to_vaccinate
705+
706+
new_vaccinated <-.Call( "R_intervention_vaccinate_age_group",
707+
c_model_ptr, fraction_to_vaccinate, c_vaccine_ptr )
708+
709+
schedule$total_vaccinated = total_vaccinated + new_vaccinated
710+
711+
return( sum( new_vaccinated ) )
595712
},
596713

597714
#' @description Gets information about all individuals. Wrapper for

‎R/Network.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Network <- R6Class( classname = 'Network', cloneable = FALSE,
1818

1919
#' the C network R pointer object
2020
c_network_ptr = function() {
21-
return( private$c_network()@ref )
21+
return( self$c_network@ref )
2222
},
2323

2424
#' check the C network still exists

‎R/Parameters.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ Parameters <- R6Class( classname = 'Parameters', cloneable = FALSE,
106106

107107
#' the C params R pointer object
108108
c_params_ptr = function() {
109-
return( private$.c_params()@ref )
109+
return( self$c_params@ref )
110110
},
111111

112112
#' check the C params still exists

‎R/Strain.R

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#' R6Class Strain
2+
#'
3+
#' @description
4+
#' Strain object has information about each new strain
5+
#'
6+
#' @examples
7+
#' # Add new strain with increased transmissibility
8+
#' strain = model$add_new_strain( transmission_multiplier = 1.3 )
9+
#'
10+
Strain <- R6Class( classname = 'Strain', cloneable = FALSE,
11+
12+
private = list(
13+
#' the strain ID
14+
id = NULL,
15+
16+
#' .c_strain External pointer, reference to \code{strain} C struct.
17+
.c_strain = NULL,
18+
19+
#' the C strain R pointer object
20+
c_strain_ptr = function() {
21+
return( self$c_strain@ref )
22+
},
23+
24+
#' check the C strain still exists
25+
c_strain_valid = function() {
26+
return( !is_null_xptr( private$.c_strain@ref ))
27+
}
28+
),
29+
30+
active = list(
31+
#' @field c_strain the C strain R pointer object (SWIG wrapped)
32+
c_strain = function( val = NULL )
33+
{
34+
if( is.null( val ) )
35+
{
36+
if( private$c_strain_valid() )
37+
return( private$.c_strain )
38+
stop( "c_strain is no longer valid - create a new strain")
39+
}
40+
else
41+
stop( "cannot set c_strain" )
42+
}
43+
),
44+
45+
public = list(
46+
47+
#' @param model R6 Model object
48+
#' @param stain_id The strain ID.
49+
initialize = function( model, strain_id )
50+
{
51+
if( !is.R6(model) || !inherits( model, "Model"))
52+
stop( "model must be a R6 class of type Model")
53+
54+
private$.c_strain <- get_strain_by_id( model$c_model, strain_id )
55+
private$id <- strain_id
56+
},
57+
58+
#' @description Wrapper for C API \code{strain$idx()}.
59+
#' @return the index of the strain
60+
idx = function() {
61+
return(strain_idx( self$c_strain ))
62+
},
63+
64+
#' @description Wrapper for C API \code{strain$transmission_multiplier()}.
65+
#' @return the transmission_multiplier of the strain
66+
transmission_multiplier = function() {
67+
return(strain_transmission_multiplier( self$c_strain ))
68+
},
69+
70+
#' @description Wrapper for C API \code{strain$hospitalised_fraction()}.
71+
#' @return the hospitalised fraction for the strain
72+
hospitalised_fraction = function()
73+
{
74+
c_strain_ptr = private$c_strain_ptr();
75+
return( .Call('R_strain_hospitalised_fraction',c_strain_ptr,PACKAGE='OpenABMCovid19') )
76+
}
77+
)
78+
79+
)

0 commit comments

Comments
 (0)
Please sign in to comment.