1 #ifndef STAN_MATH_PRIM_PROB_VON_MISES_CDF_HPP
2 #define STAN_MATH_PRIM_PROB_VON_MISES_CDF_HPP
3
4 #include <stan/math/prim/fun/constants.hpp>
5 #include <stan/math/prim/fun/modified_bessel_first_kind.hpp>
6 #include <cmath>
7
8 namespace stan {
9 namespace math {
10
11 namespace internal {
12
13 /**
14 * This implementation of the von Mises cdf
15 * is a copy of scipy's. see:
16 * https://github.com/scipy/scipy/blob/8dba340293fe20e62e173bdf2c10ae208286692f/scipy/stats/vonmises.py
17 *
18 * When k < 50, approximate the von Mises cdf
19 * with the following expansion that comes from
20 * scipy.
21 */
22 template <typename T_x, typename T_k>
von_mises_cdf_series(const T_x & x,const T_k & k)23 return_type_t<T_x, T_k> von_mises_cdf_series(const T_x& x, const T_k& k) {
24 const double pi = stan::math::pi();
25 int p = value_of_rec(28 + 0.5 * k - 100 / (k + 5) + 1);
26 auto s = sin(x);
27 auto c = cos(x);
28 auto sn = sin(p * x);
29 auto cn = cos(p * x);
30
31 using return_t = return_type_t<T_x, T_k>;
32 return_t R = 0.0;
33 return_t V = 0.0;
34
35 int n;
36 for (n = 1; n < p; n++) {
37 auto sn_tmp = sn * c - cn * s;
38 cn = cn * c + sn * s;
39 sn = sn_tmp;
40 R = 1 / (2.0 * (p - n) / k + R);
41 V = R * (sn / (p - n) + V);
42 }
43 return 0.5 + x / TWO_PI + V / pi;
44 }
45
46 /**
47 * conv_mises_cdf_normapprox(x, k) is used to approximate the von
48 * Mises cdf for k > 50. In this regime, the von Mises cdf
49 * is well-approximated by a normal distribution.
50 */
51 template <typename T_x, typename T_k>
von_mises_cdf_normalapprox(const T_x & x,const T_k & k)52 return_type_t<T_x, T_k> von_mises_cdf_normalapprox(const T_x& x, const T_k& k) {
53 using std::exp;
54 using std::sqrt;
55
56 const auto b = sqrt(2 / pi()) * exp(k) / modified_bessel_first_kind(0, k);
57 const auto z = b * sin(x / 2);
58 const double mu = 0;
59 const double sigma = 1;
60
61 return normal_cdf(z, mu, sigma);
62 }
63
64 /**
65 * This function calculates the cdf of the von Mises distribution in
66 * the case where the distribution has support on (-pi, pi) and
67 * has mean 0. If k is sufficiently small, this function approximates
68 * the cdf with a Gaussian. Otherwise, use the expansion from scipy.
69 */
70 template <typename T_x, typename T_k>
von_mises_cdf_centered(const T_x & x,const T_k & k)71 return_type_t<T_x, T_k> von_mises_cdf_centered(const T_x& x, const T_k& k) {
72 double ck = 50;
73 using return_t = return_type_t<T_x, T_k>;
74 return_t f;
75 if (k < 49) {
76 f = von_mises_cdf_series(x, k);
77 if (f < 0) {
78 f = 0.0;
79 return f;
80 }
81 if (f > 1) {
82 f = 1.0;
83 return f;
84 }
85 return f;
86 } else if (k < 50) {
87 f = (50.0 - k) * von_mises_cdf_series(x, 49.0)
88 + (k - 49.0) * von_mises_cdf_normalapprox(x, 50.0);
89 return f;
90 } else {
91 f = von_mises_cdf_normalapprox(x, k);
92 return f;
93 }
94 }
95
96 } // namespace internal
97
98 /** \ingroup prob_dists
99 * Calculates the cumulative distribution function of the von Mises
100 * distribution:
101 *
102 * \f$VonMisesCDF(x, \mu, \kappa) = \frac{1}{2\pi I_0(\kappa)} \int_{-\pi}^x\f$
103 * \f$e^{\kappa cos(t - \mu)} dt\f$
104 *
105 * where
106 *
107 * \f$x \in [-\pi, \pi]\f$, \f$\mu \in \mathbb{R}\f$,
108 * and \f$\kappa \in \mathbb{R}^+\f$.
109 *
110 * @param x Variate on the interval \f$[-pi, pi]\f$
111 * @param mu The mean of the distribution
112 * @param k The inverse scale of the distriubtion
113 * @return The von Mises cdf evaluated at the specified arguments
114 * @tparam T_x Type of x
115 * @tparam T_mu Type of mean parameter
116 * @tparam T_k Type of inverse scale parameter
117 */
118 template <typename T_x, typename T_mu, typename T_k>
von_mises_cdf(const T_x & x,const T_mu & mu,const T_k & k)119 inline return_type_t<T_x, T_mu, T_k> von_mises_cdf(const T_x& x, const T_mu& mu,
120 const T_k& k) {
121 using internal::von_mises_cdf_centered;
122 const double pi = stan::math::pi();
123
124 using T_return = return_type_t<T_x, T_mu, T_k>;
125 using T_x_ref = ref_type_t<T_x>;
126 using T_mu_ref = ref_type_t<T_mu>;
127 using T_k_ref = ref_type_t<T_k>;
128 static char const* function = "von_mises_cdf";
129 check_consistent_sizes(function, "Random variable", x, "Location parameter",
130 mu, "Scale parameter", k);
131
132 T_x_ref x_ref = x;
133 T_mu_ref mu_ref = mu;
134 T_k_ref k_ref = k;
135
136 check_bounded(function, "Random variable", value_of(x_ref), -pi, pi);
137 check_finite(function, "Location parameter", value_of(mu_ref));
138 check_positive(function, "Scale parameter", value_of(k_ref));
139
140 if (size_zero(x, mu, k)) {
141 return 1.0;
142 }
143
144 T_return res(1.0);
145
146 scalar_seq_view<T_x_ref> x_vec(x_ref);
147 scalar_seq_view<T_mu_ref> mu_vec(mu_ref);
148 scalar_seq_view<T_k_ref> k_vec(k_ref);
149 size_t N = max_size(x, mu, k);
150
151 for (size_t n = 0; n < N; ++n) {
152 auto x_n = x_vec[n];
153 auto mu_n = mu_vec[n];
154 auto k_n = k_vec[n];
155
156 if (x_n == -pi) {
157 res *= 0.0;
158 } else if (x_n == pi) {
159 res *= 1.0;
160 } else {
161 // shift x so that mean is 0
162 T_return x2 = x_n - mu_n;
163
164 // x2 is on an interval (2*n*pi, (2*n + 1)*pi), move it to (-pi, pi)
165 x2 += pi;
166 const auto x_floor = floor(x2 / TWO_PI);
167 const auto x_modded = x2 - x_floor * TWO_PI;
168 x2 = x_modded - pi;
169
170 // mu is on an interval (2*n*pi, (2*n + 1)*pi), move it to (-pi, pi)
171 T_return mu2 = mu_n + pi;
172 const auto mu_floor = floor(mu2 / TWO_PI);
173 const auto mu_modded = mu2 - mu_floor * TWO_PI;
174 mu2 = mu_modded - pi;
175
176 // find cut
177 T_return cut, bound_val;
178 if (mu2 < 0) {
179 cut = mu2 + pi;
180 bound_val = -pi - mu2;
181 }
182 if (mu2 >= 0) {
183 cut = mu2 - pi;
184 bound_val = pi - mu2;
185 }
186
187 T_return f_bound_val = von_mises_cdf_centered(bound_val, k_n);
188 T_return cdf_n;
189 if (x_n <= cut) {
190 cdf_n = von_mises_cdf_centered(x2, k_n) - f_bound_val;
191 } else {
192 cdf_n = von_mises_cdf_centered(x2, k_n) + 1 - f_bound_val;
193 }
194
195 if (cdf_n < 0.0)
196 cdf_n = 0.0;
197
198 res *= cdf_n;
199 }
200 }
201
202 return res;
203 }
204
205 } // namespace math
206 } // namespace stan
207
208 #endif
209