diff --git a/stan/math/prim/prob/generalized_normal_lpdf.hpp b/stan/math/prim/prob/generalized_normal_lpdf.hpp new file mode 100644 index 00000000000..919597d9418 --- /dev/null +++ b/stan/math/prim/prob/generalized_normal_lpdf.hpp @@ -0,0 +1,154 @@ +#ifndef STAN_MATH_PRIM_PROB_GENERALIZED_NORMAL_LPDF_HPP +#define STAN_MATH_PRIM_PROB_GENERALIZED_NORMAL_LPDF_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace stan { +namespace math { + +/** \ingroup prob_dists + * The log of the generalized normal density for the specified scalar(s) given + * the specified location, scale and shape parameters. y, mu, alpha, or beta can + * each be either a scalar or a vector. Any vector inputs must be the same + * length. + * + *

The result log probability is defined to be the sum of the + * log probabilities for each observation/mean/scale/shape tuple. + * + * @tparam T_y type of scalar + * @tparam T_loc type of location parameter + * @tparam T_scale type of scale parameter + * @tparam T_shape type of shape parameter + * @param y (Sequence of) scalar(s) + * @param mu (Sequence of) location parameter(s) + * @param alpha (Sequence of) scale parameter(s) + * @param beta (Sequence of) shape parameter(s) + * @return The log of the product of the densities + * @throw std::domain_error if alpha or beta is not positive + */ +template * = nullptr> +inline return_type_t generalized_normal_lpdf( + T_y&& y, T_loc&& mu, T_scale&& alpha, T_shape&& beta) { + using T_partials_return = partials_return_t; + using T_y_ref = ref_type_if_not_constant_t; + using T_mu_ref = ref_type_if_not_constant_t; + using T_alpha_ref = ref_type_if_not_constant_t; + using T_beta_ref = ref_type_if_not_constant_t; + static constexpr const char* function = "generalized_normal_lpdf"; + check_consistent_sizes(function, "Random variable", y, "Location parameter", + mu, "Scale parameter", alpha, "Shape parameter", beta); + + T_y_ref y_ref = std::forward(y); + T_mu_ref mu_ref = std::forward(mu); + T_alpha_ref alpha_ref = std::forward(alpha); + T_beta_ref beta_ref = std::forward(beta); + + decltype(auto) y_val = to_ref(as_value_column_array_or_scalar(y_ref)); + decltype(auto) mu_val = to_ref(as_value_column_array_or_scalar(mu_ref)); + decltype(auto) alpha_val = to_ref(as_value_column_array_or_scalar(alpha_ref)); + decltype(auto) beta_val = to_ref(as_value_column_array_or_scalar(beta_ref)); + + check_not_nan(function, "Random variable", y_val); + check_finite(function, "Location parameter", mu_val); + check_positive(function, "Scale parameter", alpha_val); + + // With β = +∞ this could be defined to be uniform, but we don't support that. + check_positive(function, "Shape parameter", beta_val); + + if (size_zero(y, mu, alpha, beta)) { + return 0; + } + if constexpr (!include_summand::value) { + return 0; + } + + const auto& inv_beta1p + = to_ref_if::value>(inv(beta_val) + 1); + const auto& diff + = to_ref_if::value>(y_val - mu_val); + const auto& inv_alpha = to_ref(inv(alpha_val)); + const auto& scaled_abs_diff + = to_ref_if::value>(abs(diff) + * inv_alpha); + const auto& scaled_abs_diff_pow + = to_ref_if::value>( + pow(scaled_abs_diff, beta_val)); + const size_t N = max_size(y, mu, alpha, beta); + + T_partials_return logp = -sum(scaled_abs_diff_pow); + + if constexpr (include_summand::value) { + logp -= LOG_TWO * N; + } + if constexpr (include_summand::value) { + logp -= sum(log(alpha_val)) * (N / math::size(alpha)); + } + if constexpr (include_summand::value) { + logp -= sum(lgamma(inv_beta1p)) * (N / math::size(beta)); + } + + auto ops_partials + = make_partials_propagator(y_ref, mu_ref, alpha_ref, beta_ref); + + if constexpr (!is_constant_all::value) { + // note: The partial derivatives for y, μ are undefined when + // y == μ && beta < 1. + // The derivative limit as y → μ (i.e. diff → 0) has the following cases: + // β > 1: 0 from both sides (defined as 0) + // β == 1: +1/α from right, but -1/α from left (defined as + // 0, consistent with double_exponential_lpdf) + // β < 1: -∞ from left as y → μ, but +∞ from right (undefined) + auto rep_deriv = eval(sign(diff) * beta_val + * pow(scaled_abs_diff, beta_val - 1) * inv_alpha); + if constexpr (!is_constant::value) { + partials<0>(ops_partials) = -rep_deriv; + } + if constexpr (!is_constant::value) { + partials<1>(ops_partials) = std::move(rep_deriv); + } + } + if constexpr (!is_constant::value) { + partials<2>(ops_partials) + = (beta_val * scaled_abs_diff_pow - 1) * inv_alpha; + } + if constexpr (!is_constant::value) { + partials<3>(ops_partials) + = digamma(inv_beta1p) * inv_square(beta_val) + - multiply_log(scaled_abs_diff_pow, scaled_abs_diff); + } + + return ops_partials.build(logp); +} + +template +inline return_type_t generalized_normal_lpdf( + T_y&& y, T_loc&& mu, T_scale&& alpha, T_shape&& beta) { + return generalized_normal_lpdf( + std::forward(y), std::forward(mu), + std::forward(alpha), std::forward(beta)); +} + +} // namespace math +} // namespace stan +#endif diff --git a/test/prob/generalized_normal/generalized_normal_test.hpp b/test/prob/generalized_normal/generalized_normal_test.hpp new file mode 100644 index 00000000000..ee26e3eb486 --- /dev/null +++ b/test/prob/generalized_normal/generalized_normal_test.hpp @@ -0,0 +1,176 @@ +// Arguments: Doubles, Doubles, Doubles, Doubles +#include +#include +#include +#include +#include +#include + +using std::numeric_limits; +using std::vector; + +class AgradDistributionGeneralizedNormal : public AgradDistributionTest { + public: + void valid_values(vector >& parameters, + vector& log_prob) { + vector param(4); + + param[0] = 0; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 2; // beta + parameters.push_back(param); + log_prob.push_back( + -0.57236494292470008707171367567652935582); // expected log_prob + + param[0] = 1; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 2; // beta + parameters.push_back(param); + log_prob.push_back( + -1.5723649429247000870717136756765293558); // expected log_prob + + param[0] = -2; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 2; // beta + parameters.push_back(param); + log_prob.push_back( + -4.5723649429247000870717136756765293558); // expected log_prob + + param[0] = -3.5; // y + param[1] = 1.9; // mu + param[2] = 7.2; // alpha + param[3] = 2; // beta + parameters.push_back(param); + log_prob.push_back( + -3.1089459689467097140959090592117462617); // expected log_prob + + param[0] = 0; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1; // beta + parameters.push_back(param); + log_prob.push_back( + -0.69314718055994530941723212145817656808); // expected log_prob + + param[0] = 1; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1; // beta + parameters.push_back(param); + log_prob.push_back( + -1.6931471805599453094172321214581765681); // expected log_prob + + param[0] = -2; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1; // beta + parameters.push_back(param); + log_prob.push_back( + -2.6931471805599453094172321214581765681); // expected log_prob + + param[0] = -3.5; // y + param[1] = 1.9; // mu + param[2] = 7.2; // alpha + param[3] = 1; // beta + parameters.push_back(param); + log_prob.push_back( + -3.4172282065819549364414275049933934740); // expected log_prob + + param[0] = 0; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1.5; // beta + parameters.push_back(param); + log_prob.push_back( + -0.59083234759930449611508182336583846717); // expected log_prob + + param[0] = 1; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1.5; // beta + parameters.push_back(param); + log_prob.push_back( + -1.5908323475993044961150818233658384672); // expected log_prob + + param[0] = -2; // y + param[1] = 0; // mu + param[2] = 1; // alpha + param[3] = 1.5; // beta + parameters.push_back(param); + log_prob.push_back( + -3.4192594723454945937184592717852346243); // expected log_prob + + param[0] = -3.5; // y + param[1] = 1.9; // mu + param[2] = 7.2; // alpha + param[3] = 1.5; // beta + parameters.push_back(param); + log_prob.push_back( + -3.2144324264596431082120695849657575107); // expected log_prob + } + + void invalid_values(vector& index, vector& value) { + // y + + // mu + index.push_back(1U); + value.push_back(numeric_limits::infinity()); + + index.push_back(1U); + value.push_back(-numeric_limits::infinity()); + + // alpha + index.push_back(2U); + value.push_back(0.0); + + index.push_back(2U); + value.push_back(-1.0); + + index.push_back(2U); + value.push_back(-numeric_limits::infinity()); + + // beta + index.push_back(3U); + value.push_back(0.0); + + index.push_back(3U); + value.push_back(-1.0); + + index.push_back(3U); + value.push_back(-numeric_limits::infinity()); + } + + template + stan::return_type_t log_prob( + const T_y& y, const T_loc& mu, const T_scale& alpha, const T_shape& beta, + const T5&, const T6&) { + return stan::math::generalized_normal_lpdf(y, mu, alpha, beta); + } + + template + stan::return_type_t log_prob( + const T_y& y, const T_loc& mu, const T_scale& alpha, const T_shape& beta, + const T5&, const T6&) { + return stan::math::generalized_normal_lpdf(y, mu, alpha, beta); + } + + template + stan::return_type_t log_prob_function( + const T_y& y, const T_loc& mu, const T_scale& alpha, const T_shape& beta, + const T5&, const T6&) { + using stan::math::abs; + using stan::math::inv; + using stan::math::lgamma; + using stan::math::log; + using stan::math::LOG_TWO; + + return -LOG_TWO - log(alpha) - lgamma(1.0 + inv(beta)) + - pow(abs(y - mu) / alpha, beta); + } +};