EpiNow2 Stan Functions
renewal_infections.hpp
Go to the documentation of this file.
1#ifndef EPINOW2_RENEWAL_INFECTIONS_HPP
2#define EPINOW2_RENEWAL_INFECTIONS_HPP
3
4/**
5 * Renewal equation with a reverse-mode adjoint.
6 *
7 * Backs the Stan function
8 * `renewal_infections(seed, R, gt_rev_pmf, pop, use_pop, pop_floor, nht)`
9 * declared in inst/stan/functions/infections.stan, which is the loop of
10 * generate_infections() after the seeding infections are set.
11 *
12 * Notation (1-based, as in the Stan code): the seeding infections are
13 * I_1, ..., I_{t_s}, R_t (t = 1, ..., T) is the reproduction number,
14 * g is the reversed generation time PMF of length G, and the generation
15 * time weight of a gap of tau days is g_tau = gt_rev_pmf[G - tau].
16 *
17 * Forward, for t = 1, ..., T with u = t_s + t:
18 * lambda_t = sum_{tau = 1}^{min(G, u) - 1} g_tau I_{u - tau},
19 * the total infectiousness (the tau = 0 term pairs with I_u, which is not
20 * yet set). Without susceptible depletion
21 * I_u = R_t lambda_t.
22 * With depletion (use_pop = 2, or use_pop = 1 and t > nht)
23 * S_t = fmax(pop_floor, pop - I^c_t), a_t = R_t lambda_t / S_t,
24 * I_u = S_t fmax(0, 1 - exp(-a_t)),
25 * where I^c_1 is the sum of the seeding infections and
26 * I^c_{t + 1} = I^c_t + I_u.
27 *
28 * Reverse. Write xbar for the gradient of the target with respect to x.
29 * Walk t = T, ..., 1. When step t is reached, Ibar_u already holds every
30 * contribution from later times (through lambda and I^c). Then
31 * no depletion: Rbar_t = Ibar_u lambda_t, lambdabar_t = Ibar_u R_t;
32 * depletion: with e_t = exp(-a_t),
33 * abar_t = Ibar_u S_t e_t,
34 * Rbar_t = abar_t lambda_t / S_t,
35 * lambdabar_t = abar_t R_t / S_t,
36 * Sbar_t = Ibar_u (1 - e_t) - abar_t a_t / S_t,
37 * popbar += Sbar_t and I^cbar_t -= Sbar_t when
38 * S_t = pop - I^c_t;
39 * then Ibar_{u - tau} += lambdabar_t g_tau,
40 * gbar_tau += lambdabar_t I_{u - tau},
41 * and I^cbar_t is passed back to I_{u - 1} and I^cbar_{t - 1}. Finally
42 * the seeding infections receive Ibar_{1:t_s} + I^cbar_1. The fmax()
43 * branches follow Stan's fmax(): the second argument is taken, with its
44 * gradient, when it is at least the first and not NaN.
45 *
46 * The forward pass and this reverse walk work on plain doubles, so the
47 * whole renewal loop is one autodiff node, rather than one dot_product()
48 * plus several scalar nodes per time step. Any of seed, R, gt_rev_pmf and
49 * pop may be var or double; only the gradients of var inputs are
50 * computed. As with convolve_with_rev_pmf(), the function lives in
51 * namespace epinow2 and is made visible in stan::math.
52 */
53
54#include <stan/math.hpp>
55#include <algorithm>
56#include <cmath>
57#include <ostream>
58#include <stdexcept>
59
60namespace epinow2 {
61namespace internal {
62
63/**
64 * Forward values kept for the reverse pass.
65 *
66 * Each member has one entry per time step, except I, which also holds the
67 * seeds. The entries of flag are bit sets, with 1 for depletion,
68 * 2 for S = pop - C (not the floor) and 4 for fmax(0, 1 - e) passing
69 * 1 - e through.
70 *
71 * @tparam Vec Vector type, either Eigen::VectorXd or an arena vector.
72 */
73template <typename Vec>
75 Vec I, lambda, S, e, a, flag;
76};
77
78/**
79 * Whether susceptible depletion applies at a time step.
80 *
81 * @param use_pop Population adjustment (0 none, 1 after nht, 2 all).
82 * @param s Zero-based time step after the seeds.
83 * @param nht Number of time steps before the adjustment starts when
84 * use_pop = 1.
85 * @return True if depletion applies.
86 */
87inline bool renewal_depletes(int use_pop, int s, int nht) {
88 return (use_pop == 1 && s + 1 > nht) || use_pop == 2;
89}
90
91/**
92 * Forward pass of the renewal equation on doubles.
93 *
94 * Fills st with the infections and the values needed by renewal_reverse().
95 *
96 * @param seed Seeding infections.
97 * @param R Reproduction numbers.
98 * @param g Reversed generation time PMF.
99 * @param pop Initial susceptible population.
100 * @param use_pop Population adjustment (0 none, 1 after nht, 2 all).
101 * @param pop_floor Minimum susceptible population.
102 * @param nht Number of time steps before the adjustment starts when
103 * use_pop = 1.
104 * @param st State to fill.
105 */
106template <typename State>
107inline void renewal_forward(const Eigen::VectorXd& seed,
108 const Eigen::VectorXd& R,
109 const Eigen::VectorXd& g, double pop,
110 int use_pop, double pop_floor, int nht,
111 State& st) {
112 const int u0 = seed.size();
113 const int ot = R.size();
114 const int G = g.size();
115 st.I = Eigen::VectorXd::Zero(u0 + ot);
116 st.I.head(u0) = seed;
117 st.lambda.resize(ot);
118 st.S.resize(ot);
119 st.e.resize(ot);
120 st.a.resize(ot);
121 st.flag = Eigen::VectorXd::Zero(ot);
122 double C = use_pop ? seed.sum() : 0.0;
123 for (int s = 0; s < ot; ++s) {
124 const int u = u0 + s;
125 const int K = std::min(G, u + 1) - 1;
126 const double lambda
127 = K > 0 ? st.I.segment(u - K, K).dot(g.segment(G - 1 - K, K)) : 0.0;
128 st.lambda(s) = lambda;
129 double inf;
130 if (renewal_depletes(use_pop, s, nht)) {
131 const double sraw = pop - C;
132 const bool s_active = sraw >= pop_floor;
133 const double S = s_active ? sraw : pop_floor;
134 const double a = R(s) * lambda / S;
135 const double e = std::exp(-a);
136 const double h = 1.0 - e;
137 const bool h_active = h >= 0.0;
138 st.flag(s) = 1 + 2 * s_active + 4 * h_active;
139 st.S(s) = S;
140 st.a(s) = a;
141 st.e(s) = e;
142 inf = S * (h_active ? h : 0.0);
143 } else {
144 inf = R(s) * lambda;
145 }
146 st.I(u) = inf;
147 if (use_pop && s < ot - 1) {
148 C += inf;
149 }
150 }
151}
152
153/**
154 * Reverse pass of the renewal equation on doubles.
155 *
156 * @param st State filled by renewal_forward().
157 * @param R Reproduction numbers.
158 * @param g Reversed generation time PMF.
159 * @param use_pop Population adjustment (0 none, 1 after nht, 2 all).
160 * @param Ibar_in Adjoint of the infections, seeds included.
161 * @param seedbar Set to the adjoint of the seeds.
162 * @param Rbar Adjoint of R, accumulated into.
163 * @param gbar Adjoint of g, accumulated into.
164 * @param popbar Adjoint of pop, accumulated into.
165 */
166template <typename State>
167inline void renewal_reverse(const State& st, const Eigen::VectorXd& R,
168 const Eigen::VectorXd& g, int use_pop,
169 const Eigen::VectorXd& Ibar_in,
170 Eigen::VectorXd& seedbar, Eigen::VectorXd& Rbar,
171 Eigen::VectorXd& gbar, double& popbar) {
172 const int ot = R.size();
173 const int G = g.size();
174 const int u0 = st.I.size() - ot;
175 Eigen::VectorXd Ibar = Ibar_in;
176 double Cbar = 0.0;
177 for (int s = ot - 1; s >= 0; --s) {
178 const int u = u0 + s;
179 if (use_pop && s < ot - 1) {
180 Ibar(u) += Cbar;
181 }
182 const double ib = Ibar(u);
183 double lambdabar;
184 const int flag = static_cast<int>(st.flag(s));
185 if (flag & 1) {
186 const double S = st.S(s);
187 double Sbar = 0.0;
188 if (flag & 4) {
189 const double abar = ib * S * st.e(s);
190 Sbar = ib * (1.0 - st.e(s)) - abar * st.a(s) / S;
191 Rbar(s) += abar * st.lambda(s) / S;
192 lambdabar = abar * R(s) / S;
193 } else {
194 lambdabar = 0.0;
195 }
196 if (flag & 2) {
197 popbar += Sbar;
198 Cbar -= Sbar;
199 }
200 } else {
201 Rbar(s) += ib * st.lambda(s);
202 lambdabar = ib * R(s);
203 }
204 const int K = std::min(G, u + 1) - 1;
205 if (K > 0) {
206 Ibar.segment(u - K, K) += lambdabar * g.segment(G - 1 - K, K);
207 gbar.segment(G - 1 - K, K) += lambdabar * st.I.segment(u - K, K);
208 }
209 }
210 seedbar = Ibar.head(u0);
211 if (use_pop) {
212 seedbar.array() += Cbar;
213 }
214}
215
216} // namespace internal
217
218/**
219 * Run the renewal equation after the seeding infections.
220 *
221 * The last argument is the output stream of the Stan calling convention,
222 * which is not used.
223 *
224 * @param seed Seeding infections, length t_s >= 1 (var or double).
225 * @param R Reproduction numbers, length T (var or double).
226 * @param g Reversed generation time PMF (var or double).
227 * @param pop Initial susceptible population (var or double).
228 * @param use_pop Population adjustment (0 none, 1 after nht, 2 all).
229 * @param pop_floor Minimum susceptible population (double).
230 * @param nht Number of time steps before the population adjustment starts
231 * when use_pop = 1.
232 * @return Infections, length t_s + T, starting with the seeds.
233 * @throws std::domain_error if seed is empty.
234 */
235template <typename T0, typename T1, typename T2, typename T3,
236 stan::require_all_eigen_col_vector_t<T0, T1, T2>* = nullptr,
237 stan::require_stan_scalar_t<T3>* = nullptr>
238inline Eigen::Matrix<stan::return_type_t<T0, T1, T2, T3>, Eigen::Dynamic, 1>
239renewal_infections(const T0& seed, const T1& R, const T2& g, const T3& pop,
240 const int& use_pop, const double& pop_floor, const int& nht,
241 std::ostream* /* pstream__ */) {
242 using stan::arena_t;
243 using stan::math::var;
244 using stan::math::value_of;
245 constexpr bool seed_var = stan::is_var<stan::value_type_t<T0>>::value;
246 constexpr bool R_var = stan::is_var<stan::value_type_t<T1>>::value;
247 constexpr bool g_var = stan::is_var<stan::value_type_t<T2>>::value;
248 constexpr bool pop_var = stan::is_var<T3>::value;
249 if (seed.size() < 1) {
250 throw std::domain_error("renewal_infections: seeding time must be >= 1");
251 }
252 const double pop_d = value_of(pop);
253 if constexpr (!seed_var && !R_var && !g_var && !pop_var) {
255 internal::renewal_forward(value_of(seed), value_of(R), value_of(g), pop_d,
256 use_pop, pop_floor, nht, st);
257 return st.I;
258 } else {
259 arena_t<Eigen::Matrix<stan::value_type_t<T0>, -1, 1>> seed_a = seed;
260 arena_t<Eigen::Matrix<stan::value_type_t<T1>, -1, 1>> R_a = R;
261 arena_t<Eigen::Matrix<stan::value_type_t<T2>, -1, 1>> g_a = g;
262 arena_t<Eigen::VectorXd> R_val = value_of(R_a);
263 arena_t<Eigen::VectorXd> g_val = value_of(g_a);
265 internal::renewal_forward(value_of(seed_a), R_val, g_val, pop_d, use_pop,
266 pop_floor, nht, fwd);
268 fwd.I, fwd.lambda, fwd.S, fwd.e, fwd.a, fwd.flag};
269 const int n = fwd.I.size();
270 arena_t<Eigen::Matrix<var, -1, 1>> res(n);
271 for (int i = 0; i < n; ++i) {
272 res.coeffRef(i) = var(fwd.I(i));
273 }
274 T3 pop_v = pop;
275 stan::math::reverse_pass_callback([=]() mutable {
276 Eigen::VectorXd seedbar;
277 Eigen::VectorXd Rbar = Eigen::VectorXd::Zero(R_val.size());
278 Eigen::VectorXd gbar = Eigen::VectorXd::Zero(g_val.size());
279 double popbar = 0.0;
280 internal::renewal_reverse(st, R_val, g_val, use_pop, res.adj(),
281 seedbar, Rbar, gbar, popbar);
282 if constexpr (seed_var) {
283 seed_a.adj() += seedbar;
284 }
285 if constexpr (R_var) {
286 R_a.adj() += Rbar;
287 }
288 if constexpr (g_var) {
289 g_a.adj() += gbar;
290 }
291 if constexpr (pop_var) {
292 pop_v.adj() += popbar;
293 }
294 });
295 return Eigen::Matrix<var, -1, 1>(res);
296 }
297}
298
299} // namespace epinow2
300
301namespace stan {
302namespace math {
303using ::epinow2::renewal_infections;
304} // namespace math
305} // namespace stan
306
307#endif
void renewal_forward(const Eigen::VectorXd &seed, const Eigen::VectorXd &R, const Eigen::VectorXd &g, double pop, int use_pop, double pop_floor, int nht, State &st)
bool renewal_depletes(int use_pop, int s, int nht)
void renewal_reverse(const State &st, const Eigen::VectorXd &R, const Eigen::VectorXd &g, int use_pop, const Eigen::VectorXd &Ibar_in, Eigen::VectorXd &seedbar, Eigen::VectorXd &Rbar, Eigen::VectorXd &gbar, double &popbar)
Eigen::Matrix< stan::return_type_t< T0, T1, T2, T3 >, Eigen::Dynamic, 1 > renewal_infections(const T0 &seed, const T1 &R, const T2 &g, const T3 &pop, const int &use_pop, const double &pop_floor, const int &nht, std::ostream *)