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