Skip to content

Commit 8f30241

Browse files
committed
Refactor geometric to delegate to neg_binomial(1, beta)
Refactor lpmf, cdf, lcdf, lccdf, and rng to delegate to the corresponding neg_binomial functions with alpha=1 and beta=theta/(1-theta), as suggested in review. Handle theta=1 (where beta diverges) with early returns. Add autodiff test HPPs for lpmf, lcdf, and lccdf.
1 parent aa576fd commit 8f30241

9 files changed

Lines changed: 333 additions & 165 deletions

stan/math/prim/prob/geometric_cdf.hpp

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,37 @@
33

44
#include <stan/math/prim/meta.hpp>
55
#include <stan/math/prim/err.hpp>
6-
#include <stan/math/prim/fun/constants.hpp>
7-
#include <stan/math/prim/fun/exp.hpp>
8-
#include <stan/math/prim/fun/log1m.hpp>
96
#include <stan/math/prim/fun/max_size.hpp>
107
#include <stan/math/prim/fun/scalar_seq_view.hpp>
118
#include <stan/math/prim/fun/size.hpp>
129
#include <stan/math/prim/fun/size_zero.hpp>
1310
#include <stan/math/prim/fun/value_of.hpp>
14-
#include <stan/math/prim/functor/partials_propagator.hpp>
15-
#include <limits>
11+
#include <stan/math/prim/prob/neg_binomial_cdf.hpp>
12+
#include <stan/math/prim/fun/elt_divide.hpp>
13+
#include <stan/math/prim/fun/subtract.hpp>
1614

1715
namespace stan {
1816
namespace math {
1917

2018
/** \ingroup prob_dists
21-
* Returns the CDF of the geometric distribution with success probability
22-
* parameter. Given containers of matching sizes, returns the product of
23-
* probabilities.
19+
* Returns the CDF of the geometric distribution. Given containers of
20+
* matching sizes, returns the product of probabilities.
2421
*
25-
* CDF: F(n | theta) = 1 - (1 - theta)^(n + 1)
22+
* Delegates to the negative binomial CDF with alpha = 1 and
23+
* beta = theta / (1 - theta).
2624
*
2725
* @tparam T_n type of outcome variable
2826
* @tparam T_prob type of success probability parameter
2927
*
3028
* @param n outcome variable (number of failures before first success)
3129
* @param theta success probability parameter
3230
* @return probability or product of probabilities
33-
* @throw std::domain_error if theta is not in [0, 1]
31+
* @throw std::domain_error if theta is not in (0, 1]
3432
* @throw std::invalid_argument if container sizes mismatch
3533
*/
3634
template <typename T_n, typename T_prob>
3735
inline return_type_t<T_prob> geometric_cdf(const T_n& n,
3836
const T_prob& theta) {
39-
using T_partials_return = partials_return_t<T_n, T_prob>;
4037
using T_n_ref = ref_type_t<T_n>;
4138
using T_prob_ref = ref_type_t<T_prob>;
4239
static constexpr const char* function = "geometric_cdf";
@@ -53,42 +50,39 @@ inline return_type_t<T_prob> geometric_cdf(const T_n& n,
5350
value_of(theta_ref), 0.0, 1.0);
5451

5552
scalar_seq_view<T_n_ref> n_vec(n_ref);
56-
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
57-
const size_t max_size_seq_view = max_size(n_ref, theta_ref);
58-
5953
for (int i = 0; i < stan::math::size(n); i++) {
6054
if (n_vec.val(i) < 0) {
6155
return 0.0;
6256
}
6357
}
6458

65-
T_partials_return cdf(1.0);
66-
auto ops_partials = make_partials_propagator(theta_ref);
67-
for (size_t i = 0; i < max_size_seq_view; i++) {
68-
const auto theta_val = theta_vec.val(i);
69-
const auto n_val = n_vec.val(i);
70-
const auto np1 = n_val + 1.0;
71-
const auto log1m_theta = log1m(theta_val);
72-
const auto ccdf_i = stan::math::exp(np1 * log1m_theta);
73-
const auto cdf_i = 1.0 - ccdf_i;
74-
75-
cdf *= cdf_i;
76-
77-
if constexpr (is_autodiff_v<T_prob>) {
78-
if (cdf_i > 0.0 && theta_val > 0.0) {
79-
partials<0>(ops_partials)[i]
80-
+= np1 * ccdf_i / ((1.0 - theta_val) * cdf_i);
81-
}
59+
// theta = 1 => CDF is always 1 for n >= 0
60+
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
61+
bool all_theta_one = true;
62+
for (size_t i = 0; i < stan::math::size(theta); i++) {
63+
if (value_of(theta_vec[i]) != 1.0) {
64+
all_theta_one = false;
65+
break;
8266
}
8367
}
68+
if (all_theta_one) {
69+
return 1.0;
70+
}
8471

85-
if constexpr (is_autodiff_v<T_prob>) {
86-
for (size_t i = 0; i < stan::math::size(theta); ++i) {
87-
partials<0>(ops_partials)[i] *= cdf;
72+
if constexpr (is_stan_scalar_v<T_prob>) {
73+
const auto beta = theta_ref / (1.0 - theta_ref);
74+
return neg_binomial_cdf(n_ref, 1, beta);
75+
} else if constexpr (is_std_vector_v<T_prob>) {
76+
std::vector<value_type_t<T_prob>> beta;
77+
beta.reserve(stan::math::size(theta));
78+
for (size_t i = 0; i < stan::math::size(theta); i++) {
79+
beta.push_back(theta_vec[i] / (1.0 - theta_vec[i]));
8880
}
81+
return neg_binomial_cdf(n_ref, 1, beta);
82+
} else {
83+
const auto beta = elt_divide(theta_ref, subtract(1.0, theta_ref));
84+
return neg_binomial_cdf(n_ref, 1, beta);
8985
}
90-
91-
return ops_partials.build(cdf);
9286
}
9387

9488
} // namespace math

stan/math/prim/prob/geometric_lccdf.hpp

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,37 @@
33

44
#include <stan/math/prim/meta.hpp>
55
#include <stan/math/prim/err.hpp>
6-
#include <stan/math/prim/fun/constants.hpp>
7-
#include <stan/math/prim/fun/log1m.hpp>
86
#include <stan/math/prim/fun/max_size.hpp>
97
#include <stan/math/prim/fun/scalar_seq_view.hpp>
108
#include <stan/math/prim/fun/size.hpp>
119
#include <stan/math/prim/fun/size_zero.hpp>
1210
#include <stan/math/prim/fun/value_of.hpp>
13-
#include <stan/math/prim/functor/partials_propagator.hpp>
11+
#include <stan/math/prim/prob/neg_binomial_lccdf.hpp>
12+
#include <stan/math/prim/fun/elt_divide.hpp>
13+
#include <stan/math/prim/fun/subtract.hpp>
1414

1515
namespace stan {
1616
namespace math {
1717

1818
/** \ingroup prob_dists
19-
* Returns the log CCDF of the geometric distribution with success probability
20-
* parameter. Given containers of matching sizes, returns the log sum of
21-
* probabilities.
19+
* Returns the log CCDF of the geometric distribution. Given containers of
20+
* matching sizes, returns the log sum of probabilities.
2221
*
23-
* log CCDF: (n + 1) * log(1 - theta)
22+
* Delegates to the negative binomial log CCDF with alpha = 1 and
23+
* beta = theta / (1 - theta).
2424
*
2525
* @tparam T_n type of outcome variable
2626
* @tparam T_prob type of success probability parameter
2727
*
2828
* @param n outcome variable (number of failures before first success)
2929
* @param theta success probability parameter
3030
* @return log complementary probability or log sum
31-
* @throw std::domain_error if theta is not in [0, 1]
31+
* @throw std::domain_error if theta is not in (0, 1]
3232
* @throw std::invalid_argument if container sizes mismatch
3333
*/
3434
template <typename T_n, typename T_prob>
3535
inline return_type_t<T_prob> geometric_lccdf(const T_n& n,
3636
const T_prob& theta) {
37-
using T_partials_return = partials_return_t<T_n, T_prob>;
3837
using T_n_ref = ref_type_t<T_n>;
3938
using T_prob_ref = ref_type_t<T_prob>;
4039
static constexpr const char* function = "geometric_lccdf";
@@ -51,31 +50,35 @@ inline return_type_t<T_prob> geometric_lccdf(const T_n& n,
5150
value_of(theta_ref), 0.0, 1.0);
5251

5352
scalar_seq_view<T_n_ref> n_vec(n_ref);
54-
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
55-
const size_t max_size_seq_view = max_size(n_ref, theta_ref);
56-
5753
for (int i = 0; i < stan::math::size(n); i++) {
5854
if (n_vec.val(i) < 0) {
5955
return 0.0;
6056
}
6157
}
6258

63-
T_partials_return log_ccdf(0.0);
64-
auto ops_partials = make_partials_propagator(theta_ref);
65-
for (size_t i = 0; i < max_size_seq_view; i++) {
66-
const auto theta_val = theta_vec.val(i);
67-
const auto n_val = n_vec.val(i);
68-
const auto np1 = n_val + 1.0;
69-
const auto log1m_theta = log1m(theta_val);
70-
71-
log_ccdf += np1 * log1m_theta;
72-
73-
if constexpr (is_autodiff_v<T_prob>) {
74-
partials<0>(ops_partials)[i] += -np1 / (1.0 - theta_val);
59+
// theta = 1 => CCDF = 0 for n >= 0, log CCDF = -inf
60+
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
61+
const size_t max_sz = max_size(n_ref, theta_ref);
62+
for (size_t i = 0; i < max_sz; i++) {
63+
if (value_of(theta_vec[i]) == 1.0 && n_vec.val(i) >= 0) {
64+
return negative_infinity();
7565
}
7666
}
7767

78-
return ops_partials.build(log_ccdf);
68+
if constexpr (is_stan_scalar_v<T_prob>) {
69+
const auto beta = theta_ref / (1.0 - theta_ref);
70+
return neg_binomial_lccdf(n_ref, 1, beta);
71+
} else if constexpr (is_std_vector_v<T_prob>) {
72+
std::vector<value_type_t<T_prob>> beta;
73+
beta.reserve(stan::math::size(theta));
74+
for (size_t i = 0; i < stan::math::size(theta); i++) {
75+
beta.push_back(theta_vec[i] / (1.0 - theta_vec[i]));
76+
}
77+
return neg_binomial_lccdf(n_ref, 1, beta);
78+
} else {
79+
const auto beta = elt_divide(theta_ref, subtract(1.0, theta_ref));
80+
return neg_binomial_lccdf(n_ref, 1, beta);
81+
}
7982
}
8083

8184
} // namespace math

stan/math/prim/prob/geometric_lcdf.hpp

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,37 @@
33

44
#include <stan/math/prim/meta.hpp>
55
#include <stan/math/prim/err.hpp>
6-
#include <stan/math/prim/fun/constants.hpp>
7-
#include <stan/math/prim/fun/exp.hpp>
8-
#include <stan/math/prim/fun/log1m.hpp>
9-
#include <stan/math/prim/fun/log1m_exp.hpp>
106
#include <stan/math/prim/fun/max_size.hpp>
117
#include <stan/math/prim/fun/scalar_seq_view.hpp>
128
#include <stan/math/prim/fun/size.hpp>
139
#include <stan/math/prim/fun/size_zero.hpp>
1410
#include <stan/math/prim/fun/value_of.hpp>
15-
#include <stan/math/prim/functor/partials_propagator.hpp>
11+
#include <stan/math/prim/prob/neg_binomial_lcdf.hpp>
12+
#include <stan/math/prim/fun/elt_divide.hpp>
13+
#include <stan/math/prim/fun/subtract.hpp>
1614

1715
namespace stan {
1816
namespace math {
1917

2018
/** \ingroup prob_dists
21-
* Returns the log CDF of the geometric distribution with success probability
22-
* parameter. Given containers of matching sizes, returns the log sum of
23-
* probabilities.
19+
* Returns the log CDF of the geometric distribution. Given containers of
20+
* matching sizes, returns the log sum of probabilities.
2421
*
25-
* log CDF: log(1 - (1 - theta)^(n + 1))
22+
* Delegates to the negative binomial log CDF with alpha = 1 and
23+
* beta = theta / (1 - theta).
2624
*
2725
* @tparam T_n type of outcome variable
2826
* @tparam T_prob type of success probability parameter
2927
*
3028
* @param n outcome variable (number of failures before first success)
3129
* @param theta success probability parameter
3230
* @return log probability or log sum of probabilities
33-
* @throw std::domain_error if theta is not in [0, 1]
31+
* @throw std::domain_error if theta is not in (0, 1]
3432
* @throw std::invalid_argument if container sizes mismatch
3533
*/
3634
template <typename T_n, typename T_prob>
3735
inline return_type_t<T_prob> geometric_lcdf(const T_n& n,
3836
const T_prob& theta) {
39-
using T_partials_return = partials_return_t<T_n, T_prob>;
4037
using T_n_ref = ref_type_t<T_n>;
4138
using T_prob_ref = ref_type_t<T_prob>;
4239
static constexpr const char* function = "geometric_lcdf";
@@ -53,36 +50,39 @@ inline return_type_t<T_prob> geometric_lcdf(const T_n& n,
5350
value_of(theta_ref), 0.0, 1.0);
5451

5552
scalar_seq_view<T_n_ref> n_vec(n_ref);
56-
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
57-
const size_t max_size_seq_view = max_size(n_ref, theta_ref);
58-
5953
for (int i = 0; i < stan::math::size(n); i++) {
6054
if (n_vec.val(i) < 0) {
6155
return negative_infinity();
6256
}
6357
}
6458

65-
T_partials_return log_cdf(0.0);
66-
auto ops_partials = make_partials_propagator(theta_ref);
67-
for (size_t i = 0; i < max_size_seq_view; i++) {
68-
const auto theta_val = theta_vec.val(i);
69-
const auto n_val = n_vec.val(i);
70-
const auto np1 = n_val + 1.0;
71-
const auto log1m_theta = log1m(theta_val);
72-
const auto log_ccdf = np1 * log1m_theta;
73-
74-
log_cdf += log1m_exp(log_ccdf);
75-
76-
if constexpr (is_autodiff_v<T_prob>) {
77-
const auto ccdf = stan::math::exp(log_ccdf);
78-
if (theta_val > 0.0 && ccdf < 1.0) {
79-
partials<0>(ops_partials)[i]
80-
+= np1 * ccdf / ((1.0 - theta_val) * (1.0 - ccdf));
81-
}
59+
// theta = 1 => log CDF is 0 for n >= 0
60+
scalar_seq_view<T_prob_ref> theta_vec(theta_ref);
61+
bool all_theta_one = true;
62+
for (size_t i = 0; i < stan::math::size(theta); i++) {
63+
if (value_of(theta_vec[i]) != 1.0) {
64+
all_theta_one = false;
65+
break;
8266
}
8367
}
68+
if (all_theta_one) {
69+
return 0.0;
70+
}
8471

85-
return ops_partials.build(log_cdf);
72+
if constexpr (is_stan_scalar_v<T_prob>) {
73+
const auto beta = theta_ref / (1.0 - theta_ref);
74+
return neg_binomial_lcdf(n_ref, 1, beta);
75+
} else if constexpr (is_std_vector_v<T_prob>) {
76+
std::vector<value_type_t<T_prob>> beta;
77+
beta.reserve(stan::math::size(theta));
78+
for (size_t i = 0; i < stan::math::size(theta); i++) {
79+
beta.push_back(theta_vec[i] / (1.0 - theta_vec[i]));
80+
}
81+
return neg_binomial_lcdf(n_ref, 1, beta);
82+
} else {
83+
const auto beta = elt_divide(theta_ref, subtract(1.0, theta_ref));
84+
return neg_binomial_lcdf(n_ref, 1, beta);
85+
}
8686
}
8787

8888
} // namespace math

0 commit comments

Comments
 (0)