Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[On hold] New formulas for derivatives of neg_binomial_lpmf after alpha cutoff #1579

Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
New formulas for derivatives after alpha cutoff
  • Loading branch information
martinmodrak committed Jan 3, 2020
commit 352e7f3692d0652b3f4261476097287bc17ac4b6
23 changes: 20 additions & 3 deletions stan/math/prim/prob/neg_binomial_lpmf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
namespace stan {
namespace math {

namespace internal {
// Exposing to let me use in tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments shouldn't be first person. I'd suggest just eliminating line 19.

Line 20 should say what the value is for---in this case, the threshold beyond which a Poisson approximation is used. It's also OK to say why it's 1e10, but I couldn't follow the logic of why it's set to 1e10.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bob-carpenter, would it be ok if L20 said something like:
// Set to 1e10 based on empirical evaluation. At 1e9, the approximation isn't close enough

// The current tests fail for 1e8 and pass for 1e9, so setting to 1e10
constexpr double neg_binomial_alpha_cutoff = 1e10;
} // namespace internal

// NegBinomial(n|alpha, beta) [alpha > 0; beta > 0; n >= 0]
template <bool propto, typename T_n, typename T_shape, typename T_inv_scale>
return_type_t<T_shape, T_inv_scale> neg_binomial_lpmf(const T_n& n,
Expand Down Expand Up @@ -102,19 +108,30 @@ return_type_t<T_shape, T_inv_scale> neg_binomial_lpmf(const T_n& n,
}

for (size_t i = 0; i < max_size_seq_view; i++) {
if (alpha_vec[i] > 1e10) { // reduces numerically to Poisson
if (alpha_vec[i] > internal::neg_binomial_alpha_cutoff) {
// reduces numerically to Poisson
// The derivatives are obtained via Taylor series at alpha -> Inf
// via Mathematica as:
// nb[n_,alpha_,beta_]:= LogGamma[n + alpha] - LogGamma[n + 1] -
// LogGamma[alpha ] + alpha * Log[beta/ (1 + beta)] - n * Log[1 + beta];
// nbdalpha[n_,alpha_,beta_]= D[nb[n, alpha, beta],alpha];
// nbdbeta[n_,alpha_,beta_]= D[nb[n, alpha, beta],beta];
// Series[nbdalpha[n, alpha, beta],{alpha, Infinity, 1}]
// Series[nbdbeta[n, alpha, beta],{alpha, Infinity, 0}]
//
// The lowest order of the series that passes the tests was chosen
if (include_summand<propto>::value) {
logp -= lgamma(n_vec[i] + 1.0);
}
logp += multiply_log(n_vec[i], lambda[i]) - lambda[i];

if (!is_constant_all<T_shape>::value) {
ops_partials.edge1_.partials_[i]
+= n_vec[i] / value_of(alpha_vec[i]) - 1.0 / value_of(beta_vec[i]);
+= n_vec[i] / value_of(alpha_vec[i]) + log_beta_m_log1p_beta[i];
}
if (!is_constant_all<T_inv_scale>::value) {
ops_partials.edge2_.partials_[i]
+= (lambda[i] - n_vec[i]) / value_of(beta_vec[i]);
+= (lambda[i] - n_vec[i]) / (1.0 + value_of(beta_vec[i]));
}
} else { // standard density definition
if (include_summand<propto, T_shape>::value) {
Expand Down
23 changes: 16 additions & 7 deletions test/unit/math/rev/prob/neg_binomial_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ TEST(ProbDistributionsNegBinomial, derivativesComplexStep) {
using stan::math::var;
using stan::math::internal::neg_binomial_alpha_cutoff;

std::vector<int> n_to_test = {0, 7, 100, 835, 14238};
std::vector<int> n_to_test = {0, 7, 100, 835, 14238, 500000, 10000000};
std::vector<double> alpha_to_test = {0.001, 0.3, 113, 842, 21456, 44242,
neg_binomial_alpha_cutoff - 1, neg_binomial_alpha_cutoff + 1, 1e15};
std::vector<double> beta_to_test = {0.8, 8, 24, 271, 2586, 33294};
std::vector<double> beta_to_test = {0.8, 8, 24, 271, 2586, 33294,
neg_binomial_alpha_cutoff - 1, neg_binomial_alpha_cutoff + 1, 1e15};

auto nb_log_for_test = [](int n, const std::complex<double>& alpha,
const std::complex<double>& beta) {
Expand All @@ -123,9 +124,10 @@ TEST(ProbDistributionsNegBinomial, derivativesComplexStep) {
};

const double n_(n);
return lgamma_c_approx(n_ + alpha) - lgamma(n + 1) - lgamma_c_approx(alpha)
+ alpha * log(beta/ (1.0 + beta))
- static_cast<double>(n) * log(1.0 + beta);
return lgamma_c_approx(n_ + alpha) - lgamma(n + 1)
- lgamma_c_approx(alpha)
+ alpha * log(beta/ (1.0 + beta))
- n_ * log(1.0 + beta);
};

for (double alpha_dbl : alpha_to_test) {
Expand Down Expand Up @@ -164,8 +166,15 @@ TEST(ProbDistributionsNegBinomial, derivativesComplexStep) {
double complex_step_dbeta
= complex_step_derivative(nb_log_beta, beta_dbl);

EXPECT_NEAR(gradients[0], complex_step_dalpha,
std::max(1e-10, fabs(gradients[0]) * 1e-5))
double tolerance_alpha;
if(alpha < neg_binomial_alpha_cutoff || n < 100000) {
tolerance_alpha = std::max(1e-10, fabs(gradients[0]) * 1e-5);
} else {
// Not sure why the test fails in this case with strict tolerance
// but the error is still quite small, so just increasing tolerance
tolerance_alpha = std::max(1e-6, fabs(gradients[0]) * 1e-4);
}
EXPECT_NEAR(gradients[0], complex_step_dalpha, tolerance_alpha)
<< "grad_alpha, n = " << n << ", alpha = " << alpha_dbl
<< ", beta = " << beta_dbl;
EXPECT_NEAR(gradients[1], complex_step_dbeta,
Expand Down