#pragma once #include "cephes/igam.h" #include "config.h" #include "error.h" #include "tools.h" namespace xsf { XSF_HOST_DEVICE inline double gdtrib(double a, double p, double x) { if (std::isnan(p) || std::isnan(a) || std::isnan(x)) { return std::numeric_limits::quiet_NaN(); } if (!((0 <= p) && (p <= 1))) { set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter p is out of range"); return std::numeric_limits::quiet_NaN(); } if (!(a > 0) || std::isinf(a)) { set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter a is out of range"); return std::numeric_limits::quiet_NaN(); } if (!(x >= 0) || std::isinf(x)) { set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter x is out of range"); return std::numeric_limits::quiet_NaN(); } if (x == 0.0) { if (p == 0.0) { set_error("gdtrib", SF_ERROR_DOMAIN, "Indeterminate result for (x, p) == (0, 0)."); return std::numeric_limits::quiet_NaN(); } /* gdtrib(a, p, x) tends to 0 as x -> 0 when p > 0 */ return 0.0; } if (p == 0.0) { /* gdtrib(a, p, x) tends to infinity as p -> 0 from the right when x > 0. */ set_error("gdtrib", SF_ERROR_SINGULAR, NULL); return std::numeric_limits::infinity(); } if (p == 1.0) { /* gdtrib(a, p, x) tends to 0 as p -> 1.0 from the left when x > 0. */ return 0.0; } double q = 1.0 - p; auto func = [a, p, q, x](double b) { if (p <= q) { return cephes::igam(b, a * x) - p; } return q - cephes::igamc(b, a * x); }; double lower_bound = std::numeric_limits::min(); double upper_bound = std::numeric_limits::max(); /* To explain the magic constants used below: * 1.0 is the initial guess for the root. -0.875 is the initial step size * for the leading bracket endpoint if the bracket search will proceed to the * left, likewise 7.0 is the initial step size when the bracket search will * proceed to the right. 0.125 is the scale factor for a left moving bracket * search and 8.0 the scale factor for a right moving bracket search. These * constants are chosen so that: * * 1. The scale factor and bracket endpoints remain powers of 2, allowing for * exact arithmetic, preventing roundoff error from causing numerical catastrophe * which could lead to unexpected results. * 2. The bracket sizes remain constant in a relative sense. Each candidate bracket * will contain roughly the same number of floating point values. This means that * the number of necessary function evaluations in the worst case scenario for * Chandrupatla's algorithm will remain constant. * * false specifies that the function is not decreasing. 342 is equal to * max(ceil(log_8(DBL_MAX)), ceil(log_(1/8)(DBL_MIN))). An upper bound for the * number of iterations needed in this bracket search to check all normalized * floating point values. */ auto [xl, xr, f_xl, f_xr, bracket_status] = detail::bracket_root_for_cdf_inversion( func, 1.0, lower_bound, upper_bound, -0.875, 7.0, 0.125, 8, false, 342 ); if (bracket_status == 1) { set_error("gdtrib", SF_ERROR_UNDERFLOW, NULL); return 0.0; } if (bracket_status == 2) { set_error("gdtrib", SF_ERROR_OVERFLOW, NULL); return std::numeric_limits::infinity(); } if (bracket_status >= 3) { set_error("gdtrib", SF_ERROR_OTHER, "Computational Error"); return std::numeric_limits::quiet_NaN(); } auto [result, root_status] = detail::find_root_chandrupatla( func, xl, xr, f_xl, f_xr, std::numeric_limits::epsilon(), 1e-100, 100 ); if (root_status) { /* The root finding return should only fail if there's a bug in our code. */ set_error("gdtrib", SF_ERROR_OTHER, "Computational Error, (%.17g, %.17g, %.17g)", a, p, x); return std::numeric_limits::quiet_NaN(); } return result; } } // namespace xsf