Automatic Differentiation
 
Loading...
Searching...
No Matches
normal_lcdf.hpp
Go to the documentation of this file.
1#ifndef STAN_MATH_OPENCL_PRIM_NORMAL_LCDF_HPP
2#define STAN_MATH_OPENCL_PRIM_NORMAL_LCDF_HPP
3#ifdef STAN_OPENCL
4
12
13namespace stan {
14namespace math {
15namespace internal {
16constexpr char normal_lcdf_opencl_func[] = "normal_lcdf(OpenCL)";
18 double x2 = normal_lcdf_scaled_diff * normal_lcdf_scaled_diff;
19 double normal_lcdf_n = 0;
20 // Rigorous numerical approximations are applied here to deal with values
21 // of |normal_lcdf_scaled_diff|>>0. This is needed to deal with rare
22 // base-rate logistic regression problems where it is useful to use an
23 // alternative link function instead.
24 //
25 // use erfc() instead of erf() in order to retain precision
26 // since for x>0 erfc()->0
27 if (normal_lcdf_scaled_diff > 0.0) {
28 // CDF(x) = 1/2 + 1/2erf(x) = 1 - 1/2erfc(x)
29 normal_lcdf_n = log1p(-0.5 * erfc(normal_lcdf_scaled_diff));
30 if (isnan(normal_lcdf_n)) {
31 normal_lcdf_n = 0;
32 }
33 } else if (normal_lcdf_scaled_diff > -4.0) {
34 // CDF(x) = 1/2 - 1/2erf(-x) = 1/2erfc(-x)
35 normal_lcdf_n = log(erfc(-normal_lcdf_scaled_diff)) - M_LN2;
36 } else if (10.0 * log(fabs(normal_lcdf_scaled_diff)) < log(DBL_MAX)) {
37 // entering territory where erfc(-x)~0
38 // need to use direct numerical approximation of normal_lcdf_n instead
39 // the following based on W. J. Cody, Math. Comp. 23(107):631-638 (1969)
40 // CDF(x) = 1/2erfc(-x)
41 double x4 = pow(normal_lcdf_scaled_diff, 4);
42 double x6 = pow(normal_lcdf_scaled_diff, 6);
43 double x8 = pow(normal_lcdf_scaled_diff, 8);
44 double x10 = pow(normal_lcdf_scaled_diff, 10);
45 double temp_p
46 = 0.000658749161529837803157 + 0.0160837851487422766278 / x2
47 + 0.125781726111229246204 / x4 + 0.360344899949804439429 / x6
48 + 0.305326634961232344035 / x8 + 0.0163153871373020978498 / x10;
49 double temp_q = -0.00233520497626869185443 - 0.0605183413124413191178 / x2
50 - 0.527905102951428412248 / x4
51 - 1.87295284992346047209 / x6
52 - 2.56852019228982242072 / x8 - 1.0 / x10;
53 normal_lcdf_n = -M_LN2 + log(0.5 * M_2_SQRTPI + (temp_p / temp_q) / x2)
54 - log(-normal_lcdf_scaled_diff) - x2;
55 } else {
56 // normal_lcdf_scaled_diff^10 term will overflow
57 normal_lcdf_n = -INFINITY;
58 });
59// NOLINTBEGIN
61 double normal_ldncdf = 0.0; double t = 0.0; double t2 = 0.0;
62 double t4 = 0.0; double normal_lcdf_exp_m_x2 = 0.0;
63 double normal_lcdf_inv_x2 = 0.0;
64
65 // calculate using piecewise function
66 // (due to instability / inaccuracy in the various approximations)
67 if (normal_lcdf_deriv_scaled_diff > 2.9) {
68 // approximation derived from Abramowitz and Stegun (1964) 7.1.26
69 t = 1.0 / (1.0 + 0.3275911 * normal_lcdf_deriv_scaled_diff);
70 t2 = t * t;
71 t4 = pow(t, 4);
72 // A&S 7.1.26 keeps exp(-x2) in the numerator, as R's pnorm do_del
73 // does; refs in stan/math/prim/prob/normal_lcdf.hpp
74 normal_lcdf_exp_m_x2 = exp(-x2);
75 normal_ldncdf
76 = 0.5 * M_2_SQRTPI * normal_lcdf_exp_m_x2
77 / (1.0
78 - normal_lcdf_exp_m_x2
79 * (0.254829592 - 0.284496736 * t + 1.421413741 * t2
80 - 1.453152027 * t2 * t + 1.061405429 * t4));
81 } else if (normal_lcdf_deriv_scaled_diff > 2.5) {
82 // in the trouble area where all of the standard numerical
83 // approximations are unstable - bridge the gap using Taylor
84 // expansions of the analytic function
85 // use Taylor expansion centred around x=2.7
86 t = normal_lcdf_deriv_scaled_diff - 2.7;
87 t2 = t * t;
88 t4 = pow(t, 4);
89 normal_ldncdf = 0.0003849882382 - 0.002079084702 * t + 0.005229340880 * t2
90 - 0.008029540137 * t2 * t + 0.008232190507 * t4
91 - 0.005692364250 * t4 * t + 0.002399496363 * pow(t, 6);
92 } else if (normal_lcdf_deriv_scaled_diff > 2.1) {
93 // use Taylor expansion centred around x=2.3
94 t = normal_lcdf_deriv_scaled_diff - 2.3;
95 t2 = t * t;
96 t4 = pow(t, 4);
97 normal_ldncdf = 0.002846135439 - 0.01310032351 * t + 0.02732189391 * t2
98 - 0.03326906904 * t2 * t + 0.02482478940 * t4
99 - 0.009883071924 * t4 * t - 0.0002771362254 * pow(t, 6);
100 } else if (normal_lcdf_deriv_scaled_diff > 1.5) {
101 // use Taylor expansion centred around x=1.85
102 t = normal_lcdf_deriv_scaled_diff - 1.85;
103 t2 = t * t;
104 t4 = pow(t, 4);
105 normal_ldncdf = 0.01849212058 - 0.06876280470 * t + 0.1099906382 * t2
106 - 0.09274533184 * t2 * t + 0.03543327418 * t4
107 + 0.005644855518 * t4 * t - 0.01111434424 * pow(t, 6);
108 } else if (normal_lcdf_deriv_scaled_diff > 0.8) {
109 // use Taylor expansion centred around x=1.15
110 t = normal_lcdf_deriv_scaled_diff - 1.15;
111 t2 = t * t;
112 t4 = pow(t, 4);
113 normal_ldncdf = 0.1585747034 - 0.3898677543 * t + 0.3515963775 * t2
114 - 0.09748053605 * t2 * t - 0.04347986191 * t4
115 + 0.02182506378 * t4 * t + 0.01074751427 * pow(t, 6);
116 } else if (normal_lcdf_deriv_scaled_diff > 0.1) {
117 // use Taylor expansion centred around x=0.45
118 t = normal_lcdf_deriv_scaled_diff - 0.45;
119 t2 = t * t;
120 t4 = pow(t, 4);
121 normal_ldncdf = 0.6245634904 - 0.9521866949 * t + 0.3986215682 * t2
122 + 0.04700850676 * t2 * t - 0.03478651979 * t4
123 - 0.01772675404 * t4 * t + 0.0006577254811 * pow(t, 6);
124 } else if (normal_lcdf_deriv_scaled_diff < -29.0) {
125 // asymptotic Mills ratio, DLMF 7.12.1; grows linearly as -2*scaled_diff,
126 // so no quadratic fit can track it. Same 1/x^2 series shape as R's pnorm
127 normal_lcdf_inv_x2 = 1.0 / x2;
128 normal_ldncdf
129 = -2.0 * normal_lcdf_deriv_scaled_diff
130 / (1.0
131 + normal_lcdf_inv_x2
132 * (-0.5
133 + normal_lcdf_inv_x2
134 * (0.75 + normal_lcdf_inv_x2 * -1.875)));
135 } else if (10.0 * log(fabs(normal_lcdf_deriv_scaled_diff)) < log(DBL_MAX)) {
136 // approximation derived from Abramowitz and Stegun (1964) 7.1.26
137 // use fact that erf(x)=-erf(-x)
138 // Abramowitz and Stegun define this for -inf<x<0 but seems to be
139 // accurate for -inf<x<0.1
140 t = 1.0 / (1.0 - 0.3275911 * normal_lcdf_deriv_scaled_diff);
141 t2 = t * t;
142 t4 = pow(t, 4);
143 normal_ldncdf
144 = M_2_SQRTPI
145 / (0.254829592 * t - 0.284496736 * t2 + 1.421413741 * t2 * t
146 - 1.453152027 * t4 + 1.061405429 * t4 * t);
147 // check if we need to add a correction term
148 // (from cubic fit of residuals)
149 if (normal_lcdf_deriv_scaled_diff < -17.0) {
150 normal_ldncdf += 0.0001263257217272 * x2 * normal_lcdf_deriv_scaled_diff
151 + 0.0123586859488623 * x2
152 - 0.0860505264736028 * normal_lcdf_deriv_scaled_diff
153 - 1.252783383752970;
154 } else if (normal_lcdf_deriv_scaled_diff < -7.0) {
155 normal_ldncdf
156 += 0.000471585349920831 * x2 * normal_lcdf_deriv_scaled_diff
157 + 0.0296839305424034 * x2
158 + 0.207402143352332 * normal_lcdf_deriv_scaled_diff
159 + 0.425316974683324;
160 } else if (normal_lcdf_deriv_scaled_diff < -3.9) {
161 normal_ldncdf
162 += -0.0006972280656443 * x2 * normal_lcdf_deriv_scaled_diff
163 + 0.0068218494628567 * x2
164 + 0.0585761964460277 * normal_lcdf_deriv_scaled_diff
165 + 0.1034397670201370;
166 } else if (normal_lcdf_deriv_scaled_diff < -2.1) {
167 normal_ldncdf
168 += -0.0018742199480885 * x2 * normal_lcdf_deriv_scaled_diff
169 - 0.0097119598291202 * x2
170 - 0.0170137970924080 * normal_lcdf_deriv_scaled_diff
171 - 0.0100428567412041;
172 }
173 } else { normal_ldncdf = INFINITY; });
174// NOLINTEND
175} // namespace internal
176
190template <
191 const char* func = internal::normal_lcdf_opencl_func, typename T_y_cl,
192 typename T_loc_cl, typename T_scale_cl,
194 T_scale_cl>* = nullptr,
195 require_any_not_stan_scalar_t<T_y_cl, T_loc_cl, T_scale_cl>* = nullptr>
197 const T_y_cl& y, const T_loc_cl& mu, const T_scale_cl& sigma) {
198 static constexpr const char* function = func;
199 using std::isfinite;
200 using std::isnan;
201
202 check_consistent_sizes(function, "Random variable", y, "Location parameter",
203 mu, "Scale parameter", sigma);
204 const size_t N = max_size(y, mu, sigma);
205 if (N == 0) {
206 return 0.0;
207 }
208
209 const auto& y_col = as_column_vector_or_scalar(y);
210 const auto& mu_col = as_column_vector_or_scalar(mu);
211 const auto& sigma_col = as_column_vector_or_scalar(sigma);
212
213 const auto& y_val = value_of(y_col);
214 const auto& mu_val = value_of(mu_col);
215 const auto& sigma_val = value_of(sigma_col);
216
217 auto check_y_not_nan
218 = check_cl(function, "Random variable", y_val, "not NaN");
219 auto y_not_nan_expr = !isnan(y_val);
220 auto check_mu_finite
221 = check_cl(function, "Location parameter", mu_val, "finite");
222 auto mu_finite_expr = isfinite(mu_val);
223 auto check_sigma_positive
224 = check_cl(function, "Scale parameter", sigma_val, "positive");
225 auto sigma_positive_expr = 0 < sigma_val;
226
227 auto scaled_diff = elt_divide(y_val - mu_val, sigma_val * SQRT_TWO);
228
229 auto sigma_sqrt2 = sigma_val * SQRT_TWO;
230
231 auto lcdf_n = opencl_code<internal::opencl_normal_lcdf_impl>(
232 std::make_tuple("normal_lcdf_scaled_diff"), scaled_diff)
233 .template output<double>("normal_lcdf_n");
234 auto lcdf_expr = colwise_sum(lcdf_n);
235
236 auto ldncdf
237 = opencl_code<internal::opencl_normal_lcdf_ldncdf_impl>(
238 std::make_tuple("normal_lcdf_deriv_scaled_diff"), scaled_diff)
239 .template output<double>("normal_ldncdf");
240 auto y_deriv = elt_divide(ldncdf, sigma_sqrt2);
241 auto mu_deriv = -y_deriv;
242 auto sigma_deriv = -elt_divide(elt_multiply(ldncdf, scaled_diff), sigma_val);
243
244 matrix_cl<double> lcdf_cl;
245 matrix_cl<double> y_deriv_cl;
246 matrix_cl<double> mu_deriv_cl;
247 matrix_cl<double> sigma_deriv_cl;
248
249 results(check_y_not_nan, check_mu_finite, check_sigma_positive, lcdf_cl,
250 y_deriv_cl, mu_deriv_cl, sigma_deriv_cl)
251 = expressions(y_not_nan_expr, mu_finite_expr, sigma_positive_expr,
252 lcdf_expr, calc_if<is_autodiff_v<T_y_cl>>(y_deriv),
253 calc_if<is_autodiff_v<T_loc_cl>>(mu_deriv),
254 calc_if<is_autodiff_v<T_scale_cl>>(sigma_deriv));
255
256 double lcdf = sum(from_matrix_cl(lcdf_cl));
257
258 auto ops_partials = make_partials_propagator(y_col, mu_col, sigma_col);
259
260 if constexpr (is_autodiff_v<T_y_cl>) {
261 partials<0>(ops_partials) = std::move(y_deriv_cl);
262 }
263 if constexpr (is_autodiff_v<T_loc_cl>) {
264 partials<1>(ops_partials) = std::move(mu_deriv_cl);
265 }
266 if constexpr (is_autodiff_v<T_scale_cl>) {
267 partials<2>(ops_partials) = std::move(sigma_deriv_cl);
268 }
269 return ops_partials.build(lcdf);
270}
271
272} // namespace math
273} // namespace stan
274#endif
275#endif
Represents an arithmetic matrix on the OpenCL device.
Definition matrix_cl.hpp:47
elt_multiply_< as_operation_cl_t< T_a >, as_operation_cl_t< T_b > > elt_multiply(T_a &&a, T_b &&b)
isfinite_< as_operation_cl_t< T > > isfinite(T &&a)
auto check_cl(const char *function, const char *var_name, T &&y, const char *must_be)
Constructs a check on opencl matrix or expression.
Definition check_cl.hpp:219
results_cl< T_results... > results(T_results &&... results)
Deduces types for constructing results_cl object.
auto as_column_vector_or_scalar(T &&a)
as_column_vector_or_scalar of a kernel generator expression.
elt_divide_< as_operation_cl_t< T_a >, as_operation_cl_t< T_b > > elt_divide(T_a &&a, T_b &&b)
calc_if_< true, as_operation_cl_t< T > > calc_if(T &&a)
Definition calc_if.hpp:121
auto colwise_sum(T &&a)
Column wise sum - reduction of a kernel generator expression.
expressions_cl< T_expressions... > expressions(T_expressions &&... expressions)
Deduces types for constructing expressions_cl object.
return_type_t< T_y_cl, T_loc_cl, T_scale_cl > normal_lcdf(const T_y_cl &y, const T_loc_cl &mu, const T_scale_cl &sigma)
Returns the normal log complementary cumulative distribution function for the given location,...
auto from_matrix_cl(const T &src)
Copies the source matrix that is stored on the OpenCL device to the destination Eigen matrix.
Definition copy.hpp:61
require_all_t< is_prim_or_rev_kernel_expression< std::decay_t< Types > >... > require_all_prim_or_rev_kernel_expression_t
Require type satisfies is_prim_or_rev_kernel_expression.
typename return_type< Ts... >::type return_type_t
Convenience type for the return type of the specified template parameters.
const char opencl_normal_lcdf_ldncdf_impl[]
const char opencl_normal_lcdf_impl[]
bool isnan(double_d a)
Definition double_d.hpp:327
constexpr char normal_lcdf_opencl_func[]
auto pow(const T1 &x1, const T2 &x2)
Definition pow.hpp:32
T value_of(const fvar< T > &v)
Return the value of the specified variable.
Definition value_of.hpp:18
fvar< T > log(const fvar< T > &x)
Definition log.hpp:18
static constexpr double SQRT_TWO
The value of the square root of 2, .
void check_consistent_sizes(const char *)
Trivial no input case, this function is a no-op.
fvar< T > erfc(const fvar< T > &x)
Definition erfc.hpp:16
fvar< T > log1p(const fvar< T > &x)
Definition log1p.hpp:12
auto sum(const std::vector< T > &m)
Return the sum of the entries of the specified standard vector.
Definition sum.hpp:23
int64_t max_size(const T1 &x1, const Ts &... xs)
Calculate the size of the largest input.
Definition max_size.hpp:20
auto make_partials_propagator(Ops &&... ops)
Construct an partials_propagator.
fvar< T > fabs(const fvar< T > &x)
Definition fabs.hpp:16
fvar< T > exp(const fvar< T > &x)
Definition exp.hpp:15
The lgamma implementation in stan-math is based on either the reentrant safe lgamma_r implementation ...
bool isnan(const stan::math::var &a)
Checks if the given number is NaN.
Definition std_isnan.hpp:18
#define STRINGIFY(...)
Definition stringify.hpp:9