33
44#include < stan/math/prim/meta.hpp>
55#include < stan/math/prim/err.hpp>
6- #include < stan/math/prim/fun/max_size.hpp>
7- #include < stan/math/prim/fun/scalar_seq_view.hpp>
8- #include < stan/math/prim/fun/size.hpp>
6+ #include < stan/math/prim/fun/any.hpp>
7+ #include < stan/math/prim/fun/as_value_column_array_or_scalar.hpp>
8+ #include < stan/math/prim/fun/constants.hpp>
9+ #include < stan/math/prim/fun/elt_divide.hpp>
10+ #include < stan/math/prim/fun/exp.hpp>
11+ #include < stan/math/prim/fun/expm1.hpp>
12+ #include < stan/math/prim/fun/log1m.hpp>
13+ #include < stan/math/prim/fun/prod.hpp>
14+ #include < stan/math/prim/fun/select.hpp>
915#include < stan/math/prim/fun/size_zero.hpp>
16+ #include < stan/math/prim/fun/sum.hpp>
1017#include < stan/math/prim/fun/value_of.hpp>
11- #include < vector>
12- #include < stan/math/prim/prob/neg_binomial_cdf.hpp>
13- #include < stan/math/prim/fun/elt_divide.hpp>
14- #include < stan/math/prim/fun/subtract.hpp>
18+ #include < stan/math/prim/functor/partials_propagator.hpp>
1519
1620namespace stan {
1721namespace math {
@@ -20,69 +24,74 @@ namespace math {
2024 * Returns the CDF of the geometric distribution. Given containers of
2125 * matching sizes, returns the product of probabilities.
2226 *
23- * Delegates to the negative binomial CDF with alpha = 1 and
24- * beta = theta / (1 - theta).
27+ * The geometric distribution counts the number of failures before
28+ * the first success: P(N <= n | theta) = 1 - (1 - theta)^(n + 1 ).
2529 *
2630 * @tparam T_n type of outcome variable
2731 * @tparam T_prob type of success probability parameter
2832 *
2933 * @param n outcome variable (number of failures before first success)
3034 * @param theta success probability parameter
3135 * @return probability or product of probabilities
32- * @throw std::domain_error if theta is not in ( 0, 1]
36+ * @throw std::domain_error if theta is not in [ 0, 1]
3337 * @throw std::invalid_argument if container sizes mismatch
3438 */
35- template <typename T_n, typename T_prob>
39+ template <typename T_n, typename T_prob,
40+ require_all_not_nonscalar_prim_or_rev_kernel_expression_t <
41+ T_n, T_prob>* = nullptr >
3642inline return_type_t <T_prob> geometric_cdf (const T_n& n, const T_prob& theta) {
37- using T_n_ref = ref_type_t <T_n>;
38- using T_prob_ref = ref_type_t <T_prob>;
43+ using T_partials_return = partials_return_t <T_n, T_prob >;
44+ using T_theta_ref = ref_type_t <T_prob>;
3945 static constexpr const char * function = " geometric_cdf" ;
46+ check_consistent_sizes (function, " Random variable" , n,
47+ " Probability parameter" , theta);
48+ T_theta_ref theta_ref = theta;
49+ const auto & n_arr = as_value_column_array_or_scalar (n);
50+ const auto & theta_arr = as_value_column_array_or_scalar (theta_ref);
51+ check_bounded (function, " Probability parameter" , theta_arr, 0.0 , 1.0 );
4052
41- check_consistent_sizes (function, " Outcome variable" , n,
42- " Success probability parameter" , theta);
4353 if (size_zero (n, theta)) {
4454 return 1.0 ;
4555 }
4656
47- T_n_ref n_ref = n;
48- T_prob_ref theta_ref = theta;
49- check_bounded (function, " Success probability parameter" , value_of (theta_ref),
50- 0.0 , 1.0 );
57+ auto ops_partials = make_partials_propagator (theta_ref);
5158
52- scalar_seq_view<T_n_ref> n_vec (n_ref);
53- for (int i = 0 ; i < stan::math::size (n); i++) {
54- if (n_vec.val (i) < 0 ) {
55- return 0.0 ;
56- }
59+ // P(N <= n) = 0 for n < 0
60+ if (any (n_arr < 0 )) {
61+ return ops_partials.build (0.0 );
5762 }
5863
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 ;
66- }
67- }
68- if (all_theta_one) {
69- return 1.0 ;
64+ // theta = 0 is degenerate: P(N <= n) = 0 for any finite n.
65+ // Avoid divide-by-zero in the partials path below.
66+ if (any (theta_arr == 0.0 )) {
67+ return ops_partials.build (0.0 );
7068 }
7169
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]));
70+ // P_i = 1 - (1 - theta)^(n + 1) = -expm1((n + 1) * log1m(theta))
71+ // For theta = 1: log1m(1) = -inf, (n+1)*-inf = -inf (n >= 0),
72+ // expm1(-inf) = -1, so P_i = 1 (correct: certain success means
73+ // N <= n always for n >= 0).
74+ const auto & log1m_theta = log1m (theta_arr);
75+ const auto & P_i = -expm1 ((n_arr + 1.0 ) * log1m_theta);
76+ const T_partials_return P = prod (P_i);
77+
78+ if constexpr (is_autodiff_v<T_prob>) {
79+ // d/dtheta P_i = (n + 1) * (1 - theta)^n
80+ // = (n + 1) * exp(n * log1m(theta))
81+ // For n = 0: (n+1)*exp(0) = 1; the select avoids 0 * log1m(1) = NaN
82+ // when theta = 1.
83+ // For n > 0, theta = 1: (n+1) * exp(n * -inf) = (n+1) * 0 = 0
84+ // (correct: derivative vanishes once CDF saturates at 1).
85+ const auto & dP_dtheta = select (n_arr == 0 , T_partials_return (1.0 ),
86+ (n_arr + 1.0 ) * exp (n_arr * log1m_theta));
87+ if constexpr (is_stan_scalar_v<T_prob>) {
88+ partials<0 >(ops_partials) = sum (P * elt_divide (dP_dtheta, P_i));
89+ } else {
90+ partials<0 >(ops_partials) = P * elt_divide (dP_dtheta, P_i);
8091 }
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);
8592 }
93+
94+ return ops_partials.build (P);
8695}
8796
8897} // namespace math
0 commit comments