EpiNow2 Stan Functions
infections.stan
Go to the documentation of this file.
1/**
2 * Infection Modeling Functions
3 *
4 * This group of functions handles the generation, calculation, and backcalculation
5 * of infection time series in the model. These functions implement the core
6 * epidemiological dynamics, including the renewal equation approach.
7 *
8 * @ingroup infections_estimation
9 */
10
11/**
12 * Calculate infectiousness for a single time point
13 *
14 * This function computes the weighted sum of past infections with the generation
15 * time distribution to determine the current infectiousness.
16 *
17 * @param infections Vector of infection counts
18 * @param gt_rev_pmf Vector of reversed generation time PMF
19 * @param seeding_time Number of time steps used for seeding
20 * @param index Current time index (relative to seeding_time)
21 * @return The infectiousness at the specified time point
22 *
23 * @par Example
24 * With `infections = {10, 20, 30, 40, 50}`, `gt_rev_pmf = {0.2, 0.5, 0.3}`,
25 * `seeding_time = 0` and `index = 3`, the infectiousness is the weighted
26 * sum of the three most recent infections up to time 3:
27 * `update_infectiousness(infections, gt_rev_pmf, 0, 3)` returns
28 * `10 * 0.2 + 20 * 0.5 + 30 * 0.3 = 21`.
29 *
30 * @ingroup infections_estimation
31 */
32real update_infectiousness(vector infections, vector gt_rev_pmf,
33 int seeding_time, int index){
34 int gt_length = num_elements(gt_rev_pmf);
35 // work out where to start the convolution of past infections with the
36 // generation time distribution: (current_time - maximal generation time) if
37 // that is >= 1, otherwise 1
38 int inf_start = max(1, (index + seeding_time - gt_length + 1));
39 // work out where to end the convolution: current_time
40 int inf_end = (index + seeding_time);
41 // number of indices of the generation time to sum over
42 // (inf_end - inf_start + 1)
43 int pmf_accessed = min(gt_length, index + seeding_time);
44 // calculate the elements of the convolution
45 real new_inf = dot_product(
46 infections[inf_start:inf_end], tail(gt_rev_pmf, pmf_accessed)
47 );
48 return(new_inf);
49}
50
51/**
52 * Run the renewal equation from the seeding infections.
53 *
54 * Implemented in C++, with a hand-written gradient, in
55 * `inst/include/epinow2/renewal_infections.hpp`, which gives the maths.
56 *
57 * @param seed Vector of seeding infections
58 * @param R Vector of reproduction numbers
59 * @param gt_rev_pmf Vector of reversed generation time PMF
60 * @param pop Initial susceptible population
61 * @param use_pop Population adjustment mode (0=none, 1=after nht, 2=all)
62 * @param pop_floor Minimum susceptible population
63 * @param nht Number of time steps before the adjustment when use_pop = 1
64 * @return A vector of infections, starting with the seeding infections
65 *
66 * @ingroup infections_estimation
67 */
68vector renewal_infections(vector seed, vector R, vector gt_rev_pmf, real pop,
69 int use_pop, data real pop_floor, int nht);
70
71/**
72 * @ingroup infections_estimation
73 * @brief Generate infections using a renewal equation approach
74 *
75 * This function implements the renewal equation to generate a time series of
76 * infections based on reproduction numbers and the generation time distribution.
77 * It can also account for population depletion if a population size is specified.
78 * It sets the seeding infections and then calls renewal_infections().
79 *
80 * @param R Vector of reproduction numbers
81 * @param uot Unobserved time (seeding time)
82 * @param gt_rev_pmf Vector of reversed generation time PMF
83 * @param initial_infections Array of initial infection values
84 * @param pop Initial susceptible population (0 for unlimited)
85 * @param use_pop Population adjustment mode (0=none, 1=forecast only, 2=all)
86 * @param pop_floor Minimum susceptible population (floor to prevent instability)
87 * @param ht Horizon time
88 * @param obs_scale Whether to scale by fraction observed (1) or not (0)
89 * @param fraction_observed Fraction of infections that are observed
90 * @param initial_as_scale Whether initial infections are a scaling factor (1) or not (0)
91 * @return A vector of infection counts
92 */
93vector generate_infections(vector R, int uot, vector gt_rev_pmf,
94 array[] real initial_infections, real pop,
95 int use_pop, data real pop_floor, int ht, int obs_scale, real fraction_observed,
96 int initial_as_scale) {
97 int ot = num_elements(R);
98 vector[uot] seed;
99 real growth = R_to_r(R[1], gt_rev_pmf, 1e-3);
100 // Initialise infections using daily growth
101 if (initial_as_scale) {
102 seed[1] = exp(initial_infections[1] - growth * uot);
103 if (obs_scale) {
104 seed[1] = seed[1] / fraction_observed;
105 }
106 } else {
107 seed[1] = exp(initial_infections[1]);
108 }
109 if (uot > 1) {
110 real exp_growth = exp(growth);
111 for (s in 2:uot) {
112 seed[s] = seed[s - 1] * exp_growth;
113 }
114 }
115 return renewal_infections(
116 seed, R, gt_rev_pmf, pop, use_pop, pop_floor, ot - ht
117 );
118}
119
120/**
121 * Backcalculate infections from cases
122 *
123 * This function estimates infections by working backwards from observed cases,
124 * applying noise to account for uncertainty in the process.
125 *
126 * @param shifted_cases Vector of shifted case counts
127 * @param noise Vector of noise values
128 * @param fixed Whether to use fixed (1) or variable (0) noise
129 * @param prior Prior type to use (0: noise only, 1: cases * noise, 2: random walk)
130 * @return A vector of infection counts
131 *
132 * @par Example
133 * With `shifted_cases = {10, 20, 30}`, no noise (`noise = {0, 0, 0}`,
134 * so `exp(noise) = 1`) and `prior = 1`, each case count is scaled by 1 and
135 * offset by the `1e-5` numerical floor:
136 * `deconvolve_infections(shifted_cases, noise, 0, 1)` returns
137 * approximately `{10, 20, 30}`.
138 *
139 * @ingroup infections_estimation
140 */
141vector deconvolve_infections(vector shifted_cases, vector noise, int fixed,
142 int prior) {
143 int t = num_elements(shifted_cases);
144 vector[t] infections = rep_vector(1e-5, t);
145 if (!fixed) {
146 vector[t] exp_noise = exp(noise);
147 if (prior == 1) {
148 infections = infections + shifted_cases .* exp_noise;
149 } else if (prior == 0) {
150 infections = infections + exp_noise;
151 } else if (prior == 2) {
152 infections[1] = infections[1] + shifted_cases[1] * exp_noise[1];
153 for (i in 2:t) {
154 infections[i] = infections[i - 1] * exp_noise[i];
155 }
156 }
157 } else {
158 infections = infections + shifted_cases;
159 }
160 return(infections);
161}
real update_infectiousness(vector infections, vector gt_rev_pmf, int seeding_time, int index)
vector generate_infections(vector R, int uot, vector gt_rev_pmf, array[] real initial_infections, real pop, int use_pop, data real pop_floor, int ht, int obs_scale, real fraction_observed, int initial_as_scale)
Generate infections using a renewal equation approach.
vector deconvolve_infections(vector shifted_cases, vector noise, int fixed, int prior)
vector renewal_infections(vector seed, vector R, vector gt_rev_pmf, real pop, int use_pop, data real pop_floor, int nht)
real R_to_r(real R, vector gt_rev_pmf, data real abs_tol)