Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions stan/math/prim/prob/yule_simon_cdf.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#ifndef STAN_MATH_PRIM_PROB_YULE_SIMON_CDF_HPP
#define STAN_MATH_PRIM_PROB_YULE_SIMON_CDF_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/constants.hpp>
#include <stan/math/prim/fun/digamma.hpp>
#include <stan/math/prim/fun/beta.hpp>
#include <stan/math/prim/fun/lgamma.hpp>
#include <stan/math/prim/fun/max_size.hpp>
#include <stan/math/prim/fun/scalar_seq_view.hpp>
#include <stan/math/prim/fun/size.hpp>
#include <stan/math/prim/fun/size_zero.hpp>
#include <stan/math/prim/fun/value_of.hpp>
#include <stan/math/prim/functor/partials_propagator.hpp>

namespace stan {
namespace math {

/** \ingroup prob_dists
* Returns the CDF of the Yule-Simon distribution with shape parameter.
* Given containers of matching sizes, returns the product of probabilities.
*
* @tparam T_n type of outcome parameter
* @tparam T_alpha type of shape parameter
*
* @param n outcome variable
* @param alpha shape parameter
* @return probability or product of probabilities
* @throw std::domain_error if alpha fails to be positive
* @throw std::invalid_argument if container sizes mismatch
*/
template <typename T_n, typename T_alpha>
inline return_type_t<T_alpha> yule_simon_cdf(const T_n& n,
const T_alpha& alpha) {
using T_partials_return = partials_return_t<T_n, T_alpha>;
using T_n_ref = ref_type_t<T_n>;
using T_alpha_ref = ref_type_t<T_alpha>;
static constexpr const char* function = "yule_simon_cdf";

check_consistent_sizes(function, "Outcome variable", n, "Shape parameter",
alpha);
if (size_zero(n, alpha)) {
return 1.0;
}

T_n_ref n_ref = n;
T_alpha_ref alpha_ref = alpha;
check_positive_finite(function, "Shape parameter", alpha_ref);

scalar_seq_view<T_n> n_vec(n);
scalar_seq_view<T_alpha_ref> alpha_vec(alpha_ref);
const size_t max_size_seq_view = max_size(n_ref, alpha_ref);

// Explicit return for invalid or extreme values
// The gradients are technically ill-defined, but treated as zero
for (int i = 0; i < stan::math::size(n); i++) {
if (n_vec.val(i) < 1.0) {
return 0.0;
}
if (n_vec.val(i) == std::numeric_limits<int>::max()) {
return 1.0;
}
}

T_partials_return cdf(1.0);
auto ops_partials = make_partials_propagator(alpha_ref);
for (size_t i = 0; i < max_size_seq_view; i++) {
auto np1 = n_vec.val(i) + 1.0;
auto ap1 = alpha_vec.val(i) + 1.0;
auto nap1 = n_vec.val(i) + ap1;

auto ccdf = stan::math::exp(lgamma(ap1) + lgamma(np1) - lgamma(nap1));
cdf *= 1.0 - ccdf;

if constexpr (is_autodiff_v<T_alpha>) {
auto chain_rule_term = -ccdf / (1.0 - ccdf);
partials<0>(ops_partials)[i]
+= (digamma(ap1) - digamma(nap1)) * chain_rule_term;
}
}

if constexpr (is_autodiff_v<T_alpha>) {
for (size_t i = 0; i < stan::math::size(alpha); ++i) {
partials<0>(ops_partials)[i] *= cdf;
}
}

return ops_partials.build(cdf);
}

} // namespace math
} // namespace stan
#endif
74 changes: 74 additions & 0 deletions test/prob/yule_simon/yule_simon_cdf_test.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Arguments: Ints, Doubles
#include <stan/math/prim/prob/yule_simon_cdf.hpp>
#include <stan/math/prim/fun/lgamma.hpp>

using std::numeric_limits;
using std::vector;

class AgradCdfYuleSimon : public AgradCdfTest {
public:
void valid_values(vector<vector<double>>& parameters, vector<double>& cdf) {
vector<double> param(2);

param[0] = 5; // n
param[1] = 20.0; // alpha
parameters.push_back(param);
cdf.push_back(0.9999811782420478); // expected cdf

param[0] = 10; // n
param[1] = 5.5; // alpha
parameters.push_back(param);
cdf.push_back(0.9997987132162779); // expected cdf

param[0] = 1; // n
param[1] = 0.1; // alpha
parameters.push_back(param);
cdf.push_back(0.0909090909090918); // expected cdf

param[0] = 1; // n
param[1] = 0.01; // alpha
parameters.push_back(param);
cdf.push_back(0.0099009900990106); // expected cdf
}

void invalid_values(vector<size_t>& index, vector<double>& value) {
// n

// alpha
index.push_back(1U);
value.push_back(0.0);

index.push_back(1U);
value.push_back(-1.0);

index.push_back(1U);
value.push_back(std::numeric_limits<double>::infinity());
}

// BOUND INCLUDED IN ORDER FOR TEST TO PASS WITH CURRENT FRAMEWORK
bool has_lower_bound() { return false; }

bool has_upper_bound() { return false; }

template <class T_n, class T_alpha, typename T2, typename T3, typename T4,
typename T5>
stan::return_type_t<T_n, T_alpha> cdf(const T_n& n, const T_alpha& alpha,
const T2&, const T3&, const T4&,
const T5&) {
return stan::math::yule_simon_cdf(n, alpha);
}

template <class T_n, class T_alpha, typename T2, typename T3, typename T4,
typename T5>
stan::return_type_t<T_n, T_alpha> cdf_function(const T_n& n,
const T_alpha& alpha,
const T2&, const T3&,
const T4&, const T5&) {
using stan::math::lgamma;

auto log_ccdf
= lgamma(alpha + 1.0) + lgamma(n + 1.0) - lgamma(n + alpha + 1.0);

return 1.0 - stan::math::exp(log_ccdf);
}
};