Uni20
Asynchronous tensor and runtime library.
Loading...
Searching...
No Matches
elliptic_theta_jet.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright (C) 2026 Ian McCulloch
3#pragma once
4#include "elliptic_theta.hpp"
5#include <algorithm>
6#include <array>
7#include <cstdint>
8#include <utility>
9
10namespace uni20
11{
15template <Complex C> struct theta_jet_result
16{
17 std::array<C, 3> derivative;
19};
20namespace detail::theta_jet_detail
21{
22template <Complex C> bool finite(C const& z) { return uni20::isfinite(z.real()) && uni20::isfinite(z.imag()); }
23template <Complex C> class Sum {
24 public:
25 explicit Sum(C const& zero) : sum_(zero), correction_(zero) {}
26 void add(C const& value)
27 {
28 C const corrected = value - correction_;
29 C const next = sum_ + corrected;
30 correction_ = (next - sum_) - corrected;
31 sum_ = next;
32 }
33 C value() const { return sum_; }
34
35 private:
36 C sum_, correction_;
37};
38// Inputs have already been bounded to the signed reduction range (or to the
39// much smaller series count). Conversion therefore cannot narrow a live index.
40template <ThetaReal R> std::int64_t integral_value(R const& value)
41{
42#if UNI20_HAS_MPREAL
43 if constexpr (std::same_as<R, mpreal>)
44 return mpfr_get_sj(value.native_handle(), MPFR_RNDN);
45 else
46#endif
47 return static_cast<std::int64_t>(value);
48}
49template <ThetaReal R> std::size_t term_count(R const& t, std::size_t max_terms)
50{
51 R const one = scalar_at_precision<R>(1, precision_of(t));
52 R const pi = 4 * math::atan(one);
53 R const count = math::ceil(math::sqrt((-math::log(numeric_limits<R>::epsilon(t)) + R{32}) / pi)) + R{3};
54 if (count > scalar_at_precision<R>(max_terms, precision_of(t)))
55 throw std::runtime_error("theta jet exhausted its term budget");
56 return static_cast<std::size_t>(integral_value(count));
57}
58// Keep phases in cycles until sinpi/cospi reduce the quadrant. Evaluating a
59// rounded multiple of pi creates nonzero cos(pi/2) or sin(pi) residuals which
60// derivative factors can amplify by 1/t or 1/t^2.
61template <ThetaReal R> complex<R> phase_cycles(R const& cycles)
62{
63 R const twice = 2 * cycles;
64 return {math::cospi(twice), math::sinpi(twice)};
65}
66template <ThetaReal R> complex<R> phase_product(R const& a, R const& b)
67{
68 R const product = a * b, residual = math::fma(a, b, -product);
69 auto phase = phase_cycles(product);
70 // Retain fractional cycles lost in a large product, and low bits beside a
71 // quarter cycle. Multiplying the phases avoids rounding the residual away.
72 if (residual != 0) phase *= phase_cycles(residual);
73 return phase;
74}
75template <ThetaReal R> complex<R> phase_quotient(R const& a, R const& y, R const& t)
76{
77 R const quotient = y / t, residual = math::fma(-quotient, t, y);
78 auto phase = phase_product(a, quotient);
79 if (residual != 0) phase *= phase_product(a, residual / t);
80 return phase;
81}
82
83// Combine Fourier decay and growth before multiplication by pi. This retains
84// finite exponents near a half-period as well as avoiding infinity-infinity.
85// In the reduced rectangle t +/- 2*y is nonnegative, so an overflow here can
86// only mean a vanishing exponential, never a lost cancellation.
87template <ThetaReal R> R fourier_exponent(R const& n, bool half_series, R const& t, R const& y, R const& pi)
88{
89 if (!half_series) return -pi * (n + 1) * (n * t + (t + 2 * y));
90 R const edge = math::abs(y) + y;
91 if (n == 0) return -pi * edge;
92 return -pi * (n * (n * t + (t + 2 * y)) + edge);
93}
94// Fundamental rectangle |Re(u)|<=1/2, |Im(u)|<=t/2. Both representations
95// converge geometrically faster than exp(-pi*n*n) in their selected regimes.
96template <Complex C> theta_jet_result<C> theta1_reduced(C u, make_real_t<C> t, bool gaussian, std::size_t max_terms)
97{
98 using Real = make_real_t<C>;
99 using Complex = C;
100 auto const p = precision_of(t);
101 Real const zero = scalar_at_precision<Real>(0, p), one = scalar_at_precision<Real>(1, p);
102 Real const half = one / 2;
103 Complex const zero_complex(zero, zero);
104 Real const pi = Real{4} * math::atan(one);
105 auto const terms = term_count(t, max_terms);
106 Real const x = u.real(), y = u.imag();
107 theta_jet_result<C> result{{zero_complex, zero_complex, zero_complex}, zero};
108 // Isolate the largest exponential before evaluating any series term.
109 if (gaussian)
110 result.log_scale = pi * (y * y - (half - math::abs(x)) * (half - math::abs(x))) / t - math::log(t) / Real{2};
111 else
112 result.log_scale = -pi * (t / Real{4} - math::abs(y));
113 std::array<Sum<C>, 3> sums{Sum<C>(zero_complex), Sum<C>(zero_complex), Sum<C>(zero_complex)};
114 // Finite-precision counters reuse MPFR storage through in-place +=.
115 Real a = half, n_real = zero;
116 for (unsigned n = 0; n < terms; ++n, a += one, n_real += one)
117 {
118 Real const sign = n % 2 ? -one : one;
119 Complex value, first, second;
120 if (!gaussian)
121 {
122 Real const omega = Real{2} * pi * a;
123 if (math::abs(omega * y) <= one)
124 {
125 Real const fall = (n_real * (n_real + one)) * t * pi;
126 Real const weight = math::exp(-fall - pi * math::abs(y));
127 auto const phase = phase_product(a, x);
128 Real const sh = math::sinh(omega * y), ch = math::cosh(omega * y);
129 value = Real{2} * sign * weight * Complex(phase.imag() * ch, phase.real() * sh);
130 first = Real{2} * sign * omega * weight * Complex(phase.real() * ch, -phase.imag() * sh);
131 }
132 else
133 {
134 Real const ep = fourier_exponent(n_real, true, t, y, pi),
135 em = fourier_exponent(n_real, true, t, -y, pi);
136 auto const phase = phase_product(a, x);
137 Complex const positive = math::exp(ep) * phase;
138 Complex const negative = math::exp(em) * uni20::conj(phase);
139 value = Complex(zero, -sign) * (positive - negative);
140 first = sign * omega * (positive + negative);
141 }
142 second = -omega * omega * value;
143 }
144 else
145 {
146 Real const omega = Real{2} * pi * a / t, fall = n_real * (n_real + one);
147 Complex difference, sum;
148 if (math::abs(omega * x) <= one)
149 {
150 Complex const weight = math::exp(-pi * (fall + math::abs(x)) / t) * phase_quotient(-x, y, t);
151 if (weight == zero_complex) continue;
152 auto const phase = phase_quotient(a, y, t);
153 Real const sh = math::sinh(omega * x), ch = math::cosh(omega * x);
154 difference = Real{2} * weight * Complex(sh * phase.real(), ch * phase.imag());
155 sum = Real{2} * weight * Complex(ch * phase.real(), sh * phase.imag());
156 Complex const center = Real{2} * pi * (u / t);
157 value = sign * difference;
158 first = sign * (-center * difference + omega * sum);
159 // Apply the Gaussian weight before each frequency factor: omega^2 can
160 // overflow even when omega*(omega*difference) is representable.
161 second = sign * (center * (center * difference) + omega * (omega * difference) -
162 (Real{2} * pi * difference) / t - Real{2} * center * (omega * sum));
163 }
164 else
165 {
166 Complex const positive = math::exp(-pi * (fall + math::abs(x) - Real{2} * a * x) / t) *
167 phase_quotient(a - x, y, t);
168 Complex const negative = math::exp(-pi * (fall + math::abs(x) + Real{2} * a * x) / t) *
169 phase_quotient(-x - a, y, t);
170 // Differentiate each centered Gaussian before combining the pair.
171 // Expanding (u +/- a)^2 would cancel terms of order 1/t^2 near a center.
172 auto derivative = [&](Complex const& term, Complex const& displacement, unsigned order) {
173 if (term == zero_complex) return zero_complex;
174 Complex const slope = -Real{2} * pi * (displacement / t);
175 return order == 1 ? slope * term : slope * (slope * term) - (Real{2} * pi * term) / t;
176 };
177 value = sign * (positive - negative);
178 first = sign * (derivative(positive, u - a, 1) - derivative(negative, u + a, 1));
179 second = sign * (derivative(positive, u - a, 2) - derivative(negative, u + a, 2));
180 }
181 }
182 sums[0].add(value);
183 sums[1].add(first);
184 sums[2].add(second);
185 }
186 for (unsigned r = 0; r < 3; ++r)
187 result.derivative[r] = sums[r].value();
188 if (u == zero_complex)
189 {
190 result.derivative[0] = zero_complex;
191 result.derivative[2] = zero_complex;
192 }
193 return result;
194}
195
196// Poisson series centered at the integers for theta2/theta3. Evaluating this
197// directly retains tiny u; translating u by 1/2 can round that displacement away.
198template <Complex C> theta_jet_result<C> even_gaussian(unsigned kind, C u, make_real_t<C> t, std::size_t max_terms)
199{
200 using R = make_real_t<C>;
201 auto const p = precision_of(t);
202 R const zero = scalar_at_precision<R>(0, p), one = scalar_at_precision<R>(1, p);
203 R const pi = R(4) * math::atan(one);
204 C const cz(zero, zero);
205 R const x = u.real(), root_t = math::sqrt(t);
206 R const sx = x / root_t, sy = u.imag() / root_t;
207 theta_jet_result<C> result{{cz, cz, cz}, pi * (sy * sy - sx * sx) - math::log(t) / 2};
208 std::array<Sum<C>, 3> sums{Sum<C>(cz), Sum<C>(cz), Sum<C>(cz)};
209 auto add = [&](R const& center, R const& exponent, R const& sign) {
210 R const weight = math::exp(exponent);
211 if (weight == zero) return;
212 C const term = sign * weight * phase_quotient(center - x, u.imag(), t);
213 C const slope = -2 * pi * ((u - center) / t);
214 sums[0].add(term);
215 sums[1].add(slope * term);
216 sums[2].add(slope * (slope * term) - (2 * pi * term) / t);
217 };
218 add(zero, zero, one);
219 auto const terms = term_count(t, max_terms);
220 R a = one;
221 for (unsigned n = 1; n <= terms; ++n, a += one)
222 {
223 R const sign = kind == 2 && n % 2 ? -one : one;
224 add(a, -pi * a * ((a - 2 * x) / t), sign);
225 add(-a, -pi * a * ((a + 2 * x) / t), sign);
226 }
227 for (unsigned d = 0; d < 3; ++d)
228 result.derivative[d] = sums[d].value();
229 return result;
230}
231
232// Translate only after reducing u. Choose the nearest imaginary half-period,
233// subtracting it so a displacement from a theta4 zero is not rounded away.
234template <Complex C> theta_jet_result<C> theta4_gaussian(C u, make_real_t<C> t, std::size_t max_terms)
235{
236 using R = make_real_t<C>;
237 auto const p = precision_of(t);
238 R const zero = scalar_at_precision<R>(0, p), one = scalar_at_precision<R>(1, p);
239 R const pi = R(4) * math::atan(one), sign = u.imag() > zero ? -one : one;
240 C const shifted(u.real(), u.imag() + sign * (t / 2));
241 auto result = theta1_reduced(shifted, t, true, max_terms);
242 C const slope(zero, sign * pi);
243 result.derivative[2] += R(2) * slope * result.derivative[1] + slope * slope * result.derivative[0];
244 result.derivative[1] += slope * result.derivative[0];
245 C const phase = C(zero, -sign) * phase_product(sign / 2, u.real());
246 for (auto& d : result.derivative)
247 d *= phase;
248 result.log_scale -= pi * (t / 4 + sign * u.imag());
249 if (u == C(zero, zero)) result.derivative[1] = C(zero, zero);
250 return result;
251}
252
253template <Complex C> theta_jet_result<C> even_reduced(unsigned kind, C u, make_real_t<C> t, std::size_t max_terms)
254{
255 using Real = make_real_t<C>;
256 using Complex = C;
257 auto const p = precision_of(t);
258 Real const zero = scalar_at_precision<Real>(0, p), one = scalar_at_precision<Real>(1, p);
259 Real const half = one / 2;
260 Complex const zero_complex(zero, zero);
261 Real const pi = Real{4} * math::atan(one);
262 theta_jet_result<C> result{{zero_complex, zero_complex, zero_complex}, zero};
263 bool const half_series = kind == 2;
264 result.log_scale = half_series ? -pi * (t / Real{4} - math::abs(u.imag())) : zero;
265 std::array<Sum<C>, 3> sums{Sum<C>(zero_complex), Sum<C>(zero_complex), Sum<C>(zero_complex)};
266 if (!half_series) sums[0].add(Complex(one, zero));
267 auto const terms = term_count(t, max_terms);
268 Real a = half_series ? half : one, n_real = zero;
269 for (unsigned n = 0; n < terms; ++n, a += one, n_real += one)
270 {
271 Real const omega = Real{2} * pi * a;
272 Real const sign = kind == 4 && n % 2 == 0 ? -one : one;
273 Complex value, first;
274 if (math::abs(omega * u.imag()) <= one)
275 {
276 Real const fall = (half_series ? n_real * (n_real + one) : a * a) * t * pi,
277 shift = half_series ? pi * math::abs(u.imag()) : zero;
278 Real const weight = Real{2} * sign * math::exp(-fall - shift);
279 auto const phase = phase_product(a, u.real());
280 Real const sh = math::sinh(omega * u.imag()), ch = math::cosh(omega * u.imag());
281 value = weight * Complex(phase.real() * ch, -phase.imag() * sh);
282 first = -weight * omega * Complex(phase.imag() * ch, phase.real() * sh);
283 }
284 else
285 {
286 Real const ep = fourier_exponent(n_real, half_series, t, u.imag(), pi),
287 em = fourier_exponent(n_real, half_series, t, -u.imag(), pi);
288 auto const phase = phase_product(a, u.real());
289 Complex const aterm = math::exp(ep) * phase;
290 Complex const bterm = math::exp(em) * uni20::conj(phase);
291 value = sign * (aterm + bterm);
292 first = Complex(zero, sign * omega) * (aterm - bterm);
293 }
294 sums[0].add(value);
295 sums[1].add(first);
296 sums[2].add(-omega * omega * value);
297 }
298 for (unsigned d = 0; d < 3; ++d)
299 result.derivative[d] = sums[d].value();
300 if (kind == 4 && u.real() == zero && math::abs(u.imag()) == t / Real{2})
301 result.derivative[0] = zero_complex;
302 return result;
303}
304
305template <Complex C> theta_jet_result<C> theta1(C u, make_real_t<C> t, unsigned kind, std::size_t max_terms)
306{
307 using Real = make_real_t<C>;
308 using Complex = C;
309 auto const p = precision_of(t);
310 Real const zero = scalar_at_precision<Real>(0, p), one = scalar_at_precision<Real>(1, p);
311 Real const half = one / 2;
312 Real const pi = Real{4} * math::atan(one);
313 Real const eps = numeric_limits<Real>::epsilon(t);
314 Real const limit = std::min(one / (Real{16} * eps), Real(std::numeric_limits<std::int64_t>::max() / 4));
315 Real const vertical = u.imag() / t;
316 if (!uni20::isfinite(vertical) || math::abs(u.real()) > limit || math::abs(vertical) > limit)
317 throw std::overflow_error("theta argument reduction exceeds scalar precision");
318 auto nearest_integer = [&](Real const& value) {
319 Real const lower = math::floor(value);
320 return value - lower < half ? lower : lower + one;
321 };
322 Real const nr = nearest_integer(u.real());
323 Real mr = nearest_integer(vertical);
324 Real ry = math::fma(-mr, t, u.imag());
325 // Division may round across a half-period. Decide from the residual of the
326 // original inputs, without rounding away a small displacement from a zero.
327 if (ry >= t / 2)
328 mr += one;
329 else if (ry < -t / 2)
330 mr -= one;
331 ry = math::fma(-mr, t, u.imag());
332 auto const n = integral_value(nr), m = integral_value(mr);
333 Complex const reduced(u.real() - nr, ry), cz(zero, zero);
334 // Real half-period translations exchange theta1/theta2 and theta3/theta4.
335 // Subtract the nearest half-period before evaluation: this preserves small
336 // displacements from zeros and stationary points instead of cancelling
337 // separately rounded Fourier/Gaussian terms at the edge of the rectangle.
338 Complex centered = reduced;
339 unsigned centered_kind = kind;
340 bool negate = false;
341 if (math::abs(reduced.real()) > one / 4)
342 {
343 bool const positive = reduced.real() > zero;
344 centered.real(reduced.real() + (positive ? -half : half));
345 centered_kind = kind < 3 ? 3 - kind : 7 - kind;
346 negate = (kind == 1 && !positive) || (kind == 2 && positive);
347 }
348 bool const taylor = centered_kind > 1 && t < one && centered != cz &&
349 math::abs(centered) < math::sqrt(eps) * t / Real(8);
350 Complex const at = taylor ? cz : centered;
351 auto result = centered_kind == 1 ? theta1_reduced(at, t, t < one, max_terms)
352 : t < one ? (centered_kind == 4 ? theta4_gaussian(at, t, max_terms)
353 : even_gaussian(centered_kind, at, t, max_terms))
354 : even_reduced(centered_kind, at, t, max_terms);
355 if (centered_kind > 1 && at == cz) result.derivative[1] = cz;
356 if (taylor)
357 {
358 result.derivative[0] += result.derivative[2] * centered * centered / Real(2);
359 result.derivative[1] = result.derivative[2] * centered;
360 }
361 if (negate)
362 for (auto& d : result.derivative)
363 d = -d;
364 // Quasi-periodicity, differentiated before reconstructing the common scale.
365 Complex const slope(zero, -Real{2} * pi * mr);
366 result.derivative[2] += Real{2} * slope * result.derivative[1] + slope * slope * result.derivative[0];
367 result.derivative[1] += slope * result.derivative[0];
368 auto const parity = kind == 1 ? n + m : kind == 2 ? n : kind == 4 ? m : 0;
369 Complex phase = phase_product(-mr, reduced.real());
370 if (parity % 2) phase = -phase;
371 for (auto& d : result.derivative)
372 d *= phase;
373 result.log_scale = math::fma(pi * mr, mr * t + Real{2} * reduced.imag(), result.log_scale);
374 return result;
375}
376template <Complex C> theta_jet_result<C> evaluate(unsigned kind, C u, make_real_t<C> t, std::size_t max_terms)
377{
378 using Real = make_real_t<C>;
379 auto const p = precision_of(t);
380 Real const zero = scalar_at_precision<Real>(0, p);
381 if (kind < 1 || kind > 4 || !finite(u) || !uni20::isfinite(t) || !(t > zero))
382 throw std::invalid_argument("theta requires kind 1..4, finite u, and finite t>0");
383 auto result = theta1(u, t, kind, max_terms);
384 if (!uni20::isfinite(result.log_scale)) throw std::overflow_error("theta logarithmic scale exceeds precision");
385 for (auto d : result.derivative)
386 if (!finite(d)) throw std::overflow_error("theta scaled derivatives exceed precision");
387 return result;
388}
389} // namespace detail::theta_jet_detail
400template <Complex C>
401 requires detail::ThetaReal<make_real_t<C>>
402theta_jet_result<C> elliptic_theta_jet(unsigned kind, C const& u, make_real_t<C> const& t, std::size_t max_terms = 4096)
403{
404 auto const p = precision_of(t);
405 if (kind < 1 || kind > 4 || max_terms == 0 || p.is_exact() || precision_of(u) != p)
406 throw std::invalid_argument("theta jet requires kind 1..4, a positive budget and matching finite precision");
407 if (!detail::theta_jet_detail::finite(u) || !uni20::isfinite(t) || !(t > 0))
408 throw std::invalid_argument("theta jet requires finite u and finite t>0");
409#if UNI20_HAS_MPREAL
410 if constexpr (std::same_as<make_real_t<C>, mpreal>)
411 {
412 if (p.bit_count() > std::numeric_limits<Precision::bit_count_type>::max() - 32)
413 throw std::overflow_error("theta jet guard precision exceeds supported range");
414 auto const work = Precision::bits(p.bit_count() + 32);
415 auto result = detail::theta_jet_detail::evaluate(kind, u.at(work), t.at(work), max_terms);
416 for (auto& d : result.derivative)
417 {
418 d = d.at(p);
419 if (!detail::theta_jet_detail::finite(d)) throw std::overflow_error("theta jet derivative exceeds result range");
420 }
421 result.log_scale = result.log_scale.at(p);
422 if (!uni20::isfinite(result.log_scale)) throw std::overflow_error("theta jet scale exceeds result range");
423 return result;
424 }
425 else
426#endif
427 return detail::theta_jet_detail::evaluate(kind, u, t, max_terms);
428}
429
433template <Complex C>
434 requires detail::ThetaReal<make_real_t<C>>
436 std::size_t max_terms = 4096)
437{
438 if (p.is_exact()) throw std::invalid_argument("theta jet requires explicit finite precision");
439 return elliptic_theta_jet(kind, scalar_at_precision<C>(u, p), scalar_at_precision<make_real_t<C>>(t, p), max_terms);
440}
441
449template <Complex C>
450 requires detail::ThetaReal<make_real_t<C>>
451C elliptic_theta_ratio(theta_jet_result<C> const& a, unsigned da, theta_jet_result<C> const& b, unsigned db)
452{
453 if (da > 2 || db > 2) throw std::invalid_argument("theta derivative order must be 0..2");
454 using R = make_real_t<C>;
455 auto const p = precision_of(a.log_scale);
456 if (p.is_exact() || precision_of(b.log_scale) != p || precision_of(a.derivative[da]) != p ||
457 precision_of(b.derivative[db]) != p)
458 throw std::invalid_argument("theta ratio requires matching finite precision");
460 !detail::theta_jet_detail::finite(a.derivative[da]) || !detail::theta_jet_detail::finite(b.derivative[db]))
461 throw std::invalid_argument("theta ratio requires finite scales and selected derivatives");
462 R const zero = scalar_at_precision<R>(0, p), one = scalar_at_precision<R>(1, p);
463 R const numerator = math::abs(a.derivative[da]), denominator = math::abs(b.derivative[db]);
464 if (!(denominator > zero)) throw std::domain_error("theta ratio has a zero denominator");
465 if (numerator == zero) return C(zero, zero);
466 R const shift = a.log_scale - b.log_scale;
467 if (math::abs(shift) <= one)
468 {
469 auto const direct = (a.derivative[da] / b.derivative[db]) * math::exp(shift);
470 if (detail::theta_jet_detail::finite(direct) && math::abs(direct) > zero) return direct;
471 }
472 auto log_norm_and_phase = [&](C const& z) -> std::pair<R, C> {
473 // Scale components first, both for overflowing norms and for subnormal
474 // norms whose rounded value cannot accurately normalize the phase.
475 R const scale = std::max(math::abs(z.real()), math::abs(z.imag()));
476 C const scaled = z / scale;
477 R const scaled_norm = math::abs(scaled);
478 return {math::log(scale) + math::log(scaled_norm), scaled / scaled_norm};
479 };
480 auto const [ln_a, phase_a] = log_norm_and_phase(a.derivative[da]);
481 auto const [ln_b, phase_b] = log_norm_and_phase(b.derivative[db]);
482 R const magnitude = math::exp(a.log_scale - b.log_scale + ln_a - ln_b);
483 if (!uni20::isfinite(magnitude) || !(magnitude > zero))
484 throw std::overflow_error("theta ratio magnitude is outside scalar range");
485 return magnitude * phase_a * uni20::conj(phase_b);
486}
487} // namespace uni20
Exact arithmetic or an explicit binary working precision.
Definition core/precision.hpp:13
static constexpr Precision bits(bit_count_type count)
Select a positive binary significand size.
Definition core/precision.hpp:21
Owning exact rational or finite-precision MPFR real, with eager arithmetic.
Definition mpreal.hpp:74
locale_t value
Definition data_table.cpp:49
uni20::complex< T > conj(detail::standard_complex< T > x)
Returns the complex conjugate for complex-valued scalars.
Definition math.hpp:70
typename make_real< T >::type make_real_t
Alias for the underlying real-valued type of T.
Definition scalar_traits.hpp:168
constexpr bool isfinite(I const &x) noexcept
Returns whether an integer scalar is finite.
Definition math.hpp:121
T product(std::vector< T > const &values)
Definition mpreal_exact_example.cpp:13
Definition qnum.hpp:403
constexpr detail::scalar_math::exp_fn exp
Natural exponential with scalar-specific precision handling.
Definition math.hpp:465
constexpr detail::scalar_math::cosh_fn cosh
Hyperbolic cosine.
Definition math.hpp:491
constexpr detail::scalar_math::sinpi_fn sinpi
Sine of pi times x, with argument reduction before multiplication by pi.
Definition math.hpp:640
constexpr detail::scalar_math::sinh_fn sinh
Hyperbolic sine.
Definition math.hpp:489
constexpr detail::scalar_math::ceil_fn ceil
Least integral value not less than the argument, in the scalar's result type.
Definition math.hpp:509
constexpr detail::scalar_math::atan_fn atan
Inverse tangent in radians.
Definition math.hpp:503
constexpr detail::scalar_math::sqrt_fn sqrt
Square root; exact inputs requiring approximation need an explicit finite precision.
Definition math.hpp:461
constexpr detail::scalar_math::floor_fn floor
Round toward negative infinity.
Definition math.hpp:513
constexpr detail::scalar_math::abs_fn abs
Absolute value or complex magnitude, preserving the selected scalar implementation.
Definition math.hpp:459
constexpr detail::scalar_math::fma_fn fma
Fused multiplication and addition with one final rounding.
Definition math.hpp:555
constexpr detail::scalar_math::imag_fn imag
Read the imaginary component by value; real zero retains the input's type and precision.
Definition math.hpp:673
constexpr detail::scalar_math::log_fn log
Natural logarithm with scalar-specific precision handling.
Definition math.hpp:467
constexpr detail::scalar_math::cospi_fn cospi
Cosine of pi times x, with argument reduction before multiplication by pi.
Definition math.hpp:642
Definition data_table.cpp:8
Definition async/async.hpp:26
constexpr auto pi
Pi as a native constant or a precision-aware MPFR descriptor.
Definition math_constants.hpp:37
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
theta_jet_result< C > elliptic_theta_jet(unsigned kind, C const &u, make_real_t< C > const &t, std::size_t max_terms=4096)
Evaluate theta_kind(pi*u | i*t) and its first two u derivatives.
Definition elliptic_theta_jet.hpp:402
constexpr Precision precision_of(T const &value)
Read scalar precision without changing any scalar or provider state.
Definition runtime_precision.hpp:53
constexpr auto abs(basic_half_int< T > value) -> basic_half_int< T >
Return the absolute value of a half-integer.
Definition half_int.hpp:431
constexpr S scalar_at_precision(V const &value, Precision p)
Construct or convert a scalar at one explicit precision.
Definition runtime_precision.hpp:76
C elliptic_theta_ratio(theta_jet_result< C > const &a, unsigned da, theta_jet_result< C > const &b, unsigned db)
Ratio of two scaled theta derivatives without reconstructing either jet.
Definition elliptic_theta_jet.hpp:451
void scale(Tensor &tensor, Scalar factor)
Multiply every stored numerical block by a scalar in place.
Definition block_tensor_linear.hpp:400
int add(int a, int b)
Definition dummy.cpp:6
semantic_glyph vertical
Definition presentation.cpp:1933
Theta value and first two u derivatives with a shared real exponential scale.
Definition elliptic_theta_jet.hpp:16
std::array< C, 3 > derivative
Definition elliptic_theta_jet.hpp:17
make_real_t< C > log_scale
Definition elliptic_theta_jet.hpp:18