Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
native_upper_gamma_math.hpp
Go to the documentation of this file.
1#pragma once
2
6
7namespace uni20::detail::native_math::upper_gamma_detail
8{
9template <NativeReal R> struct compensated_log
10{
11 R sum = 0, low = 0;
12 void add(R v)
13 {
14 R n = sum + v;
15 low += std::abs(sum) >= std::abs(v) ? (sum - n) + v : (v - n) + sum;
16 sum = n;
17 }
18 void product(R a, R b)
19 {
20 R v = a * b;
21 this->add(v);
22 if (std::isfinite(v)) this->add(std::fma(a, b, -v));
23 }
24 R value() const { return sum + low; }
25 R exp() const
26 {
27 if (std::isinf(sum)) return sum > 0 ? sum : R(0);
28 R v = sum + low;
29 R e = std::abs(sum) >= std::abs(low) ? (sum - v) + low : (low - v) + sum;
30 R h = std::exp(v / 2);
31 if (h == 0 || std::isinf(h)) return h;
32 return h * (h * std::exp(e));
33 }
34};
35template <NativeReal R> R log1p_ratio(R x)
36{
37 return std::abs(x) < numeric_limits<R>::epsilon() / 2 ? R(1) : std::log1p(x) / x;
38}
39template <NativeReal R> R expm1_ratio(R x)
40{
41 return std::abs(x) < numeric_limits<R>::epsilon() / 2 ? R(1) : std::expm1(x) / x;
42}
43// Evaluate log(Gamma(1+a))/a without first rounding 1+a. The shifted
44// Stirling divided difference has a bounded derivative remainder.
45template <NativeReal R> R gamma1p_log_quotient(R a)
46{
47 if (a == 0) return -euler_gamma<R>;
48 constexpr int p = numeric_limits<R>::digits(), N = p <= 24 ? 16 : p <= 64 ? 32 : 64;
49 constexpr int terms = p <= 24 ? 4 : p <= 53 ? 8 : p <= 64 ? 10 : 16;
50 constexpr R c[] = {R(1) / 12,
51 R(-1) / 360,
52 R(1) / 1260,
53 R(-1) / 1680,
54 R(1) / 1188,
55 R(-691) / 360360,
56 R(1) / 156,
57 R(-3617) / 122400,
58 R(43867) / 244188,
59 R(-174611) / 125400,
60 R(77683) / 5796,
61 R(-236364091LL) / 1506960,
62 R(657931) / 300,
63 R(-3392780147LL) / 93960,
64 R(1723168255201LL) / 2492028,
65 R(-7709321041217LL) / 505920};
66 R r = a / N, l = std::log1p(r), q = log1p_ratio(r), power = R(1) / N;
67 compensated_log<R> s;
68 s.add(std::log(R(N)));
69 s.add(std::fma((a - R(.5)) / N, q, q - 1));
70 for (int k = 1; k < N; ++k)
71 s.add(-log1p_ratio(a / R(k)) / R(k));
72 for (int k = 0; k < terms; ++k)
73 {
74 R m = R(2 * k + 1);
75 s.add(c[k] * power * (-m / R(N)) * q * expm1_ratio(-m * l));
76 power /= R(N * N);
77 }
78 return s.value();
79}
80template <NativeReal R> R small_scaled(R a, R x)
81{
82 using L = numeric_limits<R>;
83 R d = gamma1p_log_quotient(a) - std::log(x);
84 compensated_log<R> s;
85 s.add(d * expm1_ratio(a * d));
86 R power = 1;
87 for (int k = 1; k <= 2 * L::digits() + 32; ++k)
88 {
89 power *= -x / R(k);
90 R term = power / (a + R(k));
91 s.add(-term);
92 if (std::abs(term) < L::epsilon() * std::abs(s.sum) / 16) return std::exp(x) * s.value();
93 }
94 return L::quiet_NaN();
95}
96// Neighboring positive Stieltjes truncations bracket the exact scaled
97// value. Rounded endpoints are an empirical convergence test, not intervals.
98template <NativeReal R> R positive_fraction(R a, R x)
99{
100 using L = numeric_limits<R>;
101 for (int depth = 16; depth <= 64 * L::digits(); depth *= 2)
102 {
103 R lo = (R(depth) - a) / (1 + R(depth) / x), hi = R(depth) - a;
104 for (int k = depth - 1; k >= 1; --k)
105 {
106 lo = (R(k) - a) / (1 + R(k) / (x + lo));
107 hi = (R(k) - a) / (1 + R(k) / (x + hi));
108 }
109 lo = 1 / (x + lo);
110 hi = 1 / (x + hi);
111 if (std::abs(hi - lo) <= 4 * L::epsilon() * lo) return lo + (hi - lo) / 2;
112 }
113 return L::quiet_NaN();
114}
115template <NativeReal R> R tail_fraction(R a, R x)
116{
117 using L = numeric_limits<R>;
118 R previous = 0;
119 for (int depth = 8; depth <= 64 * L::digits(); depth *= 2)
120 {
121 R value = 0;
122 for (int k = depth; k >= 1; --k)
123 value = (a - R(k)) * (R(k) / (x - a + R(2 * k + 1) + value));
124 value = 1 / (x - a + 1 + value);
125 if (std::abs(value - previous) <= 2 * L::epsilon() * value) return value;
126 previous = value;
127 }
128 return L::quiet_NaN();
129}
130template <NativeReal R> compensated_log<R> lgamma(R a)
131{
132 constexpr int cutoff = numeric_limits<R>::digits() <= 24 ? 8 : numeric_limits<R>::digits() <= 64 ? 16 : 32;
133 compensated_log<R> s;
134 if (a < cutoff)
135 {
136 s.add(std::log(std::tgamma(a)));
137 return s;
138 }
139 R l = std::log(a);
140 s.product(a, l);
141 s.add(-l / 2);
142 s.add(-a);
143 s.add(std::log(2 * pi<R>) / 2);
144 s.add(detail::native_math::stirling_correction(R(1) / a));
145 return s;
146}
147template <NativeReal R> R scaled_result(R a, R x, R u)
148{
149 if (std::isnan(u)) return u;
150 using L = numeric_limits<R>;
151 R estimate = std::fma(a, std::log(x), -x) + std::log(u);
152 if (L::epsilon() * std::abs(a) * std::abs(std::log(x)) + L::epsilon() * x > R(1) / 32 ||
153 std::abs(estimate - std::log(L::max())) < 4 || std::abs(estimate - std::log(L::denorm_min())) < 4)
154 return upper_gamma_guard::upper_gamma(a, x);
155 if (a <= 0)
156 {
157 R half = std::pow(x, a / 2) * std::exp(-x / 2);
158 return half * (half * u);
159 }
160 compensated_log<R> s;
161 R lx = std::log(x);
162 if (std::isfinite(a * lx))
163 {
164 s.product(a, lx);
165 s.add(-x);
166 }
167 else
168 {
169 s.add(std::fma(a, lx, -x));
170 }
171 if (std::isinf(s.sum)) return s.sum > 0 ? s.sum : R(0);
172 s.add(std::log(u));
173 return s.exp();
174}
175template <NativeReal R> R evaluate(R a, R x)
176{
177 using L = numeric_limits<R>;
178 if (std::isnan(a) || std::isnan(x)) return L::quiet_NaN();
179 if (a == L::infinity()) return std::isfinite(x) ? a : L::quiet_NaN();
180 if (a == -L::infinity()) return x > 0 ? (x < 1 ? L::infinity() : R(0)) : L::quiet_NaN();
181 if (x < 0) return L::quiet_NaN();
182 if (x == 0) return std::tgamma(a);
183 if (x == L::infinity()) return R(0);
184 if (a <= 1 && x > R(2 * L::max_exponent)) return R(0);
185 // Integrating over [max(a,x),max(a,x)+1] gives an overflowing lower
186 // bound here. Avoid an unnecessarily difficult transition iteration.
187 if (a > R(2 * L::max_exponent) && x / 2 <= a) return L::infinity();
188 if (a == 1) return std::exp(-x);
189 if (a >= R(.5) && x <= a)
190 {
191 auto g = lgamma(a);
192 compensated_log<R> lb;
193 lb.product(a, std::log(a));
194 lb.add(-std::log(a));
195 lb.add(-a);
196 lb.add(-R(1));
197 if (a >= 1 && lb.value() > std::log(L::max()) + 1) return L::infinity();
198 compensated_log<R> s;
199 R term = 1;
200 s.add(term);
201 for (int k = 1; k <= 64 * L::digits(); ++k)
202 {
203 term *= x / (a + R(k));
204 s.add(term);
205 if (term < L::epsilon() * s.sum / 16)
206 {
207 compensated_log<R> l;
208 l.product(a, std::log(x));
209 l.add(-x);
210 l.add(-std::log(a));
211 l.add(-g.sum);
212 l.add(-g.low);
213 R lower = l.exp() * s.value();
214 g.add(std::log1p(-lower));
215 if (std::abs(g.value() - std::log(L::max())) < 4) return upper_gamma_guard::lower_complement(a, x);
216 return g.exp();
217 }
218 }
219 return L::quiet_NaN();
220 }
221 constexpr int cutoff = L::digits() <= 24 ? 8 : L::digits() <= 64 ? 16 : 32;
222 if (x <= 1 && a > -R(cutoff) && a < R(.5))
223 {
224 int count = int(std::ceil(-a - R(.5)));
225 if (count < 0) count = 0;
226 R b = a + R(count);
227 R u = small_scaled(b, x);
228 for (int k = 1; k <= count; ++k)
229 u = (std::fma(x, u, -R(1))) / (b - R(k));
230 return scaled_result(a, x, u);
231 }
232 R u = a <= 0 && x >= 1 ? positive_fraction(a, x) : tail_fraction(a, x);
233 return scaled_result(a, x, u);
234}
235} // namespace uni20::detail::native_math::upper_gamma_detail
236
237namespace uni20::detail::native_math
238{
243template <NativeReal R> R upper_gamma(R a, R x) { return upper_gamma_detail::evaluate(a, x); }
244} // namespace uni20::detail::native_math
locale_t value
Definition data_table.cpp:49
T product(std::vector< T > const &values)
Definition mpreal_exact_example.cpp:13
Definition qnum.hpp:403
Definition cli.cpp:6
constexpr detail::scalar_math::exp_fn exp
Natural exponential with scalar-specific precision handling.
Definition math.hpp:465
constexpr detail::scalar_math::upper_gamma_fn upper_gamma
Upper unregularized incomplete Gamma, with shape and lower integration endpoint.
Definition math.hpp:593
constexpr detail::scalar_math::lgamma_fn lgamma
Logarithm of the absolute Gamma function; native providers are reentrant.
Definition math.hpp:569
Definition data_table.cpp:8
void sum(BackendSelector selector, async::Async< OutputTensor > &output, async::Async< InputTensor > const &input)
Schedule a full sum into an explicit async scalar tensor.
Definition linalg/async/reductions.hpp:309
mpreal expm1(mpreal const &x)
Exponential minus one without cancellation; retain input precision or exact zero.
Definition mpreal.hpp:722
int add(int a, int b)
Definition dummy.cpp:6
mpreal log1p(mpreal const &x)
Logarithm of one plus x without cancellation; retain input precision or exact zero.
Definition mpreal.hpp:743