EpiNow2 Stan Functions
convolve_with_rev_pmf.hpp
Go to the documentation of this file.
1#ifndef EPINOW2_CONVOLVE_WITH_REV_PMF_HPP
2#define EPINOW2_CONVOLVE_WITH_REV_PMF_HPP
3
4/**
5 * Convolution of a vector with a reversed PMF, with a reverse-mode adjoint.
6 *
7 * Implements `convolve_with_rev_pmf(x, y, len)` from
8 * inst/stan/functions/convolve.stan.
9 *
10 * Notation (1-based, as in the Stan code): x has length n, y has length D
11 * and is the reversed PMF, and z is the output of length len, with
12 * n <= len <= n + D - 1. The weight of lag d (d = 0, ..., D - 1) is
13 * w_d = y_{D - d}, and x_s = 0 outside 1, ..., n.
14 *
15 * Forward:
16 * z_t = sum_{d = 0}^{D - 1} w_d x_{t - d}, t = 1, ..., len.
17 * This is evaluated lag by lag, one vector update per lag,
18 * z_{d+1:d+m} += w_d x_{1:m}, m = min(n, len - d),
19 * which is z_{d+1:n} += w_d x_{1:n-d} when len = n. When len > n the
20 * output runs past the end of x and m caps the update at the last input,
21 * so the extra outputs are the tail of the full convolution.
22 *
23 * Reverse: the adjoint of a convolution is the matching correlation.
24 * With zbar the adjoint of z, for each lag d
25 * xbar_{1:m} += w_d zbar_{d+1:d+m},
26 * wbar_d += zbar_{d+1:d+m}^T x_{1:m},
27 * and ybar_{D - d} = wbar_d.
28 *
29 * Both passes run on doubles in one reverse_pass_callback, so the
30 * convolution is one autodiff node rather than one dot_product() node per
31 * output. x and y may each be var or double. The function is put in
32 * stan::math so that Stan-generated code finds it.
33 */
34
35#include <stan/math.hpp>
36#include <algorithm>
37#include <ostream>
38#include <stdexcept>
39
40namespace epinow2 {
41namespace internal {
42
43/**
44 * Check that len is a valid output length for the convolution.
45 *
46 * @param n Length of x.
47 * @param D Length of y.
48 * @param len Requested output length.
49 * @throws std::domain_error if len < n or len > n + D - 1.
50 */
51inline void check_convolve_len(int n, int D, int len) {
52 if (n + D - 1 < len) {
53 throw std::domain_error(
54 "convolve_with_rev_pmf: len is longer than x and y convolved");
55 }
56 if (n > len) {
57 throw std::domain_error("convolve_with_rev_pmf: len is shorter than x");
58 }
59}
60
61/**
62 * Forward pass of the convolution on doubles.
63 *
64 * Computes z_{d+1:d+m} += w_d x_{1:m} for each lag d, with w_d = y_{D - d}.
65 *
66 * @param x Input vector of length n.
67 * @param y Reversed PMF of length D.
68 * @param len Output length, n <= len <= n + D - 1.
69 * @return Vector z of length len.
70 */
71inline Eigen::VectorXd convolve_forward(const Eigen::VectorXd& x,
72 const Eigen::VectorXd& y, int len) {
73 const int n = x.size();
74 const int D = y.size();
75 Eigen::VectorXd z = Eigen::VectorXd::Zero(len);
76 for (int d = 0; d < D; ++d) {
77 const int m = std::min(n, len - d);
78 if (m <= 0) {
79 break;
80 }
81 z.segment(d, m) += y(D - 1 - d) * x.head(m);
82 }
83 return z;
84}
85
86/**
87 * Reverse pass of the convolution on doubles.
88 *
89 * Accumulates xbar_{1:m} += w_d zbar_{d+1:d+m} and
90 * wbar_d += zbar_{d+1:d+m}^T x_{1:m}, writing wbar_d to ybar_{D - d}.
91 *
92 * @param x Input vector of length n.
93 * @param y Reversed PMF of length D.
94 * @param zbar Adjoint of the output, of length len.
95 * @param xbar Adjoint of x, of length n, accumulated into. May be null.
96 * @param ybar Adjoint of y, of length D, accumulated into. May be null.
97 */
98inline void convolve_reverse(const Eigen::VectorXd& x,
99 const Eigen::VectorXd& y,
100 const Eigen::VectorXd& zbar,
101 Eigen::VectorXd* xbar, Eigen::VectorXd* ybar) {
102 const int n = x.size();
103 const int D = y.size();
104 const int len = zbar.size();
105 for (int d = 0; d < D; ++d) {
106 const int m = std::min(n, len - d);
107 if (m <= 0) {
108 break;
109 }
110 if (xbar) {
111 xbar->head(m) += y(D - 1 - d) * zbar.segment(d, m);
112 }
113 if (ybar) {
114 (*ybar)(D - 1 - d) += zbar.segment(d, m).dot(x.head(m));
115 }
116 }
117}
118
119} // namespace internal
120
121/**
122 * Convolve x with the reversed PMF y, returning a vector of length len.
123 *
124 * The last argument is the output stream of the Stan calling convention,
125 * which is not used.
126 *
127 * @param x Input vector (var or double).
128 * @param y Reversed PMF (var or double).
129 * @param len Output length, n <= len <= n + D - 1.
130 * @throws std::domain_error if len is outside that range.
131 */
132template <typename T0, typename T1,
133 stan::require_all_eigen_col_vector_t<T0, T1>* = nullptr>
134inline Eigen::Matrix<stan::return_type_t<T0, T1>, Eigen::Dynamic, 1>
135convolve_with_rev_pmf(const T0& x, const T1& y, const int& len,
136 std::ostream* /* pstream__ */) {
137 using stan::arena_t;
138 using stan::math::var;
139 constexpr bool x_var = stan::is_var<stan::value_type_t<T0>>::value;
140 constexpr bool y_var = stan::is_var<stan::value_type_t<T1>>::value;
141 internal::check_convolve_len(x.size(), y.size(), len);
142 if constexpr (!x_var && !y_var) {
143 return internal::convolve_forward(stan::math::value_of(x),
144 stan::math::value_of(y), len);
145 } else {
146 arena_t<Eigen::Matrix<stan::value_type_t<T0>, Eigen::Dynamic, 1>>
147 x_arena = x;
148 arena_t<Eigen::Matrix<stan::value_type_t<T1>, Eigen::Dynamic, 1>>
149 y_arena = y;
150 arena_t<Eigen::VectorXd> x_val = stan::math::value_of(x_arena);
151 arena_t<Eigen::VectorXd> y_val = stan::math::value_of(y_arena);
152 const Eigen::VectorXd z = internal::convolve_forward(x_val, y_val, len);
153 arena_t<Eigen::Matrix<var, Eigen::Dynamic, 1>> res(len);
154 for (int i = 0; i < len; ++i) {
155 res.coeffRef(i) = var(z(i));
156 }
157 stan::math::reverse_pass_callback(
158 [=]() mutable {
159 const Eigen::VectorXd zbar = res.adj();
160 Eigen::VectorXd xbar;
161 Eigen::VectorXd ybar;
162 if constexpr (x_var) {
163 xbar = Eigen::VectorXd::Zero(x_val.size());
164 }
165 if constexpr (y_var) {
166 ybar = Eigen::VectorXd::Zero(y_val.size());
167 }
168 internal::convolve_reverse(x_val, y_val, zbar,
169 x_var ? &xbar : nullptr,
170 y_var ? &ybar : nullptr);
171 if constexpr (x_var) {
172 x_arena.adj() += xbar;
173 }
174 if constexpr (y_var) {
175 y_arena.adj() += ybar;
176 }
177 });
178 return Eigen::Matrix<var, Eigen::Dynamic, 1>(res);
179 }
180}
181
182} // namespace epinow2
183
184namespace stan {
185namespace math {
186using ::epinow2::convolve_with_rev_pmf;
187} // namespace math
188} // namespace stan
189
190#endif
void check_convolve_len(int n, int D, int len)
Eigen::VectorXd convolve_forward(const Eigen::VectorXd &x, const Eigen::VectorXd &y, int len)
void convolve_reverse(const Eigen::VectorXd &x, const Eigen::VectorXd &y, const Eigen::VectorXd &zbar, Eigen::VectorXd *xbar, Eigen::VectorXd *ybar)
Eigen::Matrix< stan::return_type_t< T0, T1 >, Eigen::Dynamic, 1 > convolve_with_rev_pmf(const T0 &x, const T1 &y, const int &len, std::ostream *)