Skip to content

Commit 407dcfe

Browse files
authored
Merge pull request #3236 from lingium/feature/issue-3235-yule-simon-lccdf
add yule_simon_lccdf
2 parents 340e343 + 94dda8f commit 407dcfe

2 files changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#ifndef STAN_MATH_PRIM_PROB_YULE_SIMON_LCCDF_HPP
2+
#define STAN_MATH_PRIM_PROB_YULE_SIMON_LCCDF_HPP
3+
4+
#include <stan/math/prim/meta.hpp>
5+
#include <stan/math/prim/err.hpp>
6+
#include <stan/math/prim/fun/constants.hpp>
7+
#include <stan/math/prim/fun/digamma.hpp>
8+
#include <stan/math/prim/fun/beta.hpp>
9+
#include <stan/math/prim/fun/lgamma.hpp>
10+
#include <stan/math/prim/fun/max_size.hpp>
11+
#include <stan/math/prim/fun/scalar_seq_view.hpp>
12+
#include <stan/math/prim/fun/size.hpp>
13+
#include <stan/math/prim/fun/size_zero.hpp>
14+
#include <stan/math/prim/fun/value_of.hpp>
15+
#include <stan/math/prim/functor/partials_propagator.hpp>
16+
17+
namespace stan {
18+
namespace math {
19+
20+
/** \ingroup prob_dists
21+
* Returns the log CCDF of the Yule-Simon distribution with shape parameter.
22+
* Given containers of matching sizes, returns the log sum of probabilities.
23+
*
24+
* @tparam T_n type of outcome parameter
25+
* @tparam T_alpha type of shape parameter
26+
*
27+
* @param n outcome variable
28+
* @param alpha shape parameter
29+
* @return log probability or log sum of probabilities
30+
* @throw std::domain_error if alpha fails to be positive
31+
* @throw std::invalid_argument if container sizes mismatch
32+
*/
33+
template <typename T_n, typename T_alpha>
34+
inline return_type_t<T_alpha> yule_simon_lccdf(const T_n& n,
35+
const T_alpha& alpha) {
36+
using T_partials_return = partials_return_t<T_n, T_alpha>;
37+
using T_n_ref = ref_type_t<T_n>;
38+
using T_alpha_ref = ref_type_t<T_alpha>;
39+
static constexpr const char* function = "yule_simon_lccdf";
40+
41+
check_consistent_sizes(function, "Outcome variable", n, "Shape parameter",
42+
alpha);
43+
if (size_zero(n, alpha)) {
44+
return 0.0;
45+
}
46+
47+
T_n_ref n_ref = n;
48+
T_alpha_ref alpha_ref = alpha;
49+
check_positive_finite(function, "Shape parameter", alpha_ref);
50+
51+
scalar_seq_view<T_n> n_vec(n);
52+
scalar_seq_view<T_alpha_ref> alpha_vec(alpha_ref);
53+
const size_t max_size_seq_view = max_size(n_ref, alpha_ref);
54+
55+
// Explicit return for invalid or extreme values
56+
// The gradients are technically ill-defined, but treated as zero
57+
for (int i = 0; i < stan::math::size(n); i++) {
58+
if (n_vec.val(i) < 1.0) {
59+
return 0.0;
60+
}
61+
if (n_vec.val(i) == std::numeric_limits<int>::max()) {
62+
return negative_infinity();
63+
}
64+
}
65+
66+
T_partials_return log_ccdf(0.0);
67+
auto ops_partials = make_partials_propagator(alpha_ref);
68+
for (size_t i = 0; i < max_size_seq_view; i++) {
69+
auto np1 = n_vec.val(i) + 1.0;
70+
auto ap1 = alpha_vec.val(i) + 1.0;
71+
auto nap1 = n_vec.val(i) + ap1;
72+
log_ccdf += lgamma(ap1) + lgamma(np1) - lgamma(nap1);
73+
74+
if constexpr (is_autodiff_v<T_alpha>) {
75+
partials<0>(ops_partials)[i] += digamma(ap1) - digamma(nap1);
76+
}
77+
}
78+
79+
return ops_partials.build(log_ccdf);
80+
}
81+
82+
} // namespace math
83+
} // namespace stan
84+
#endif
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Arguments: Ints, Doubles
2+
#include <stan/math/prim/prob/yule_simon_lccdf.hpp>
3+
#include <stan/math/prim/fun/lgamma.hpp>
4+
5+
using std::numeric_limits;
6+
using std::vector;
7+
8+
class AgradCcdfLogYuleSimon : public AgradCcdfLogTest {
9+
public:
10+
void valid_values(vector<vector<double>>& parameters,
11+
vector<double>& cdf_log) {
12+
vector<double> param(2);
13+
14+
param[0] = 5; // n
15+
param[1] = 20.0; // alpha
16+
parameters.push_back(param);
17+
cdf_log.push_back(std::log(1.0 - 0.9999811782420478)); // expected ccdf_log
18+
19+
param[0] = 10; // n
20+
param[1] = 5.5; // alpha
21+
parameters.push_back(param);
22+
cdf_log.push_back(std::log(1.0 - 0.9997987132162779)); // expected ccdf_log
23+
}
24+
25+
void invalid_values(vector<size_t>& index, vector<double>& value) {
26+
// n
27+
28+
// alpha
29+
index.push_back(1U);
30+
value.push_back(0.0);
31+
32+
index.push_back(1U);
33+
value.push_back(-1.0);
34+
35+
index.push_back(1U);
36+
value.push_back(std::numeric_limits<double>::infinity());
37+
}
38+
39+
// BOUND INCLUDED IN ORDER FOR TEST TO PASS WITH CURRENT FRAMEWORK
40+
bool has_lower_bound() { return false; }
41+
42+
bool has_upper_bound() { return false; }
43+
44+
template <class T_n, class T_alpha, typename T2, typename T3, typename T4,
45+
typename T5>
46+
stan::return_type_t<T_n, T_alpha> ccdf_log(const T_n& n, const T_alpha& alpha,
47+
const T2&, const T3&, const T4&,
48+
const T5&) {
49+
return stan::math::yule_simon_lccdf(n, alpha);
50+
}
51+
52+
template <class T_n, class T_alpha, typename T2, typename T3, typename T4,
53+
typename T5>
54+
stan::return_type_t<T_n, T_alpha> ccdf_log_function(const T_n& n,
55+
const T_alpha& alpha,
56+
const T2&, const T3&,
57+
const T4&, const T5&) {
58+
using stan::math::lgamma;
59+
60+
auto log_ccdf
61+
= lgamma(alpha + 1.0) + lgamma(n + 1.0) - lgamma(n + alpha + 1.0);
62+
63+
return log_ccdf;
64+
}
65+
};

0 commit comments

Comments
 (0)