AIM-PIbd-32-Kurbanova-A-A/aimenv/Lib/site-packages/scipy/special/special/tools.h
2024-10-02 22:15:59 +04:00

270 lines
9.0 KiB
C++

/* Building blocks for implementing special functions */
#pragma once
#include "config.h"
#include "error.h"
namespace special {
namespace detail {
/* Result type of a "generator", a callable object that produces a value
* each time it is called.
*/
template <typename Generator>
using generator_result_t = std::decay_t<std::invoke_result_t<Generator>>;
/* Used to deduce the type of the numerator/denominator of a fraction. */
template <typename Pair>
struct pair_traits;
template <typename T>
struct pair_traits<std::pair<T, T>> {
using value_type = T;
};
template <typename Pair>
using pair_value_t = typename pair_traits<Pair>::value_type;
/* Used to extract the "value type" of a complex type. */
template <typename T>
struct real_type {
using type = T;
};
template <typename T>
struct real_type<std::complex<T>> {
using type = T;
};
template <typename T>
using real_type_t = typename real_type<T>::type;
// Return NaN, handling both real and complex types.
template <typename T>
SPECFUN_HOST_DEVICE inline std::enable_if_t<std::is_floating_point_v<T>, T> maybe_complex_NaN() {
return std::numeric_limits<T>::quiet_NaN();
}
template <typename T>
SPECFUN_HOST_DEVICE inline std::enable_if_t<!std::is_floating_point_v<T>, T> maybe_complex_NaN() {
using V = typename T::value_type;
return {std::numeric_limits<V>::quiet_NaN(), std::numeric_limits<V>::quiet_NaN()};
}
// Series evaluators.
template <typename Generator, typename T = generator_result_t<Generator>>
SPECFUN_HOST_DEVICE T series_eval(Generator &g, T init_val, real_type_t<T> tol, std::uint64_t max_terms,
const char *func_name) {
/* Sum an infinite series to a given precision.
*
* g : a generator of terms for the series.
*
* init_val : A starting value that terms are added to. This argument determines the
* type of the result.
*
* tol : relative tolerance for stopping criterion.
*
* max_terms : The maximum number of terms to add before giving up and declaring
* non-convergence.
*
* func_name : The name of the function within SciPy where this call to series_eval
* will ultimately be used. This is needed to pass to set_error in case
* of non-convergence.
*/
T result = init_val;
T term;
for (std::uint64_t i = 0; i < max_terms; ++i) {
term = g();
result += term;
if (std::abs(term) < std::abs(result) * tol) {
return result;
}
}
// Exceeded max terms without converging. Return NaN.
set_error(func_name, SF_ERROR_NO_RESULT, NULL);
return maybe_complex_NaN<T>();
}
template <typename Generator, typename T = generator_result_t<Generator>>
SPECFUN_HOST_DEVICE T series_eval_fixed_length(Generator &g, T init_val, std::uint64_t num_terms) {
/* Sum a fixed number of terms from a series.
*
* g : a generator of terms for the series.
*
* init_val : A starting value that terms are added to. This argument determines the
* type of the result.
*
* max_terms : The number of terms from the series to sum.
*
*/
T result = init_val;
for (std::uint64_t i = 0; i < num_terms; ++i) {
result += g();
}
return result;
}
/* Performs one step of Kahan summation. */
template <typename T>
SPECFUN_HOST_DEVICE void kahan_step(T& sum, T& comp, T x) {
T y = x - comp;
T t = sum + y;
comp = (t - sum) - y;
sum = t;
}
/* Evaluates an infinite series using Kahan summation.
*
* Denote the series by
*
* S = a[0] + a[1] + a[2] + ...
*
* And for n = 0, 1, 2, ..., denote its n-th partial sum by
*
* S[n] = a[0] + a[1] + ... + a[n]
*
* This function computes S[0], S[1], ... until a[n] is sufficiently
* small or if the maximum number of terms have been evaluated.
*
* Parameters
* ----------
* g
* Reference to generator that yields the sequence of values a[1],
* a[2], a[3], ...
*
* tol
* Relative tolerance for convergence. Specifically, stop iteration
* as soon as `abs(a[n]) <= tol * abs(S[n])` for some n >= 1.
*
* max_terms
* Maximum number of terms after a[0] to evaluate. It should be set
* large enough such that the convergence criterion is guaranteed
* to have been satisfied within that many terms if there is no
* rounding error.
*
* init_val
* a[0]. Default is zero. The type of this parameter (T) is used
* for intermediary computations as well as the result.
*
* Return Value
* ------------
* If the convergence criterion is satisfied by some `n <= max_terms`,
* returns `(S[n], n)`. Otherwise, returns `(S[max_terms], 0)`.
*/
template <typename Generator, typename T = generator_result_t<Generator>>
SPECFUN_HOST_DEVICE std::pair<T, std::uint64_t> series_eval_kahan(
Generator &&g, real_type_t<T> tol, std::uint64_t max_terms, T init_val = T(0)) {
T sum = init_val;
T comp = 0;
for (std::uint64_t i = 0; i < max_terms; ++i) {
T term = g();
kahan_step(sum, comp, term);
if (std::abs(term) <= tol * std::abs(sum)) {
return {sum, i + 1};
}
}
return {sum, 0};
}
/* Generator that yields the difference of successive convergents of a
* continued fraction.
*
* Let f[n] denote the n-th convergent of a continued fraction:
*
* a[1] a[2] a[n]
* f[n] = b[0] + ------ ------ ... ----
* b[1] + b[2] + b[n]
*
* with f[0] = b[0]. This generator yields the sequence of values
* f[1]-f[0], f[2]-f[1], f[3]-f[2], ...
*
* Constructor Arguments
* ---------------------
* cf
* Reference to generator that yields the terms of the continued
* fraction as (numerator, denominator) pairs, starting from
* (a[1], b[1]).
*
* `cf` must outlive the ContinuedFractionSeriesGenerator object.
*
* The constructed object always eagerly retrieves the next term
* of the continued fraction. Specifically, (a[1], b[1]) is
* retrieved upon construction, and (a[n], b[n]) is retrieved after
* (n-1) calls of `()`.
*
* Type Arguments
* --------------
* T
* Type in which computations are performed and results are turned.
*
* Remarks
* -------
* The series is computed using the recurrence relation described in [1].
*
* No error checking is performed. The caller must ensure that all terms
* are finite and that intermediary computations do not trigger floating
* point exceptions such as overflow.
*
* The numerical stability of this method depends on the characteristics
* of the continued fraction being evaluated.
*
* Reference
* ---------
* [1] Gautschi, W. (1967). “Computational Aspects of Three-Term
* Recurrence Relations.” SIAM Review, 9(1):24-82.
*/
template <typename Generator, typename T = pair_value_t<generator_result_t<Generator>>>
class ContinuedFractionSeriesGenerator {
public:
explicit ContinuedFractionSeriesGenerator(Generator &cf) : cf_(cf) {
init();
}
double operator()() {
double v = v_;
advance();
return v;
}
private:
void init() {
auto [num, denom] = cf_();
T a = num;
T b = denom;
u_ = T(1);
v_ = a / b;
b_ = b;
}
void advance() {
auto [num, denom] = cf_();
T a = num;
T b = denom;
u_ = T(1) / (T(1) + (a * u_) / (b * b_));
v_ *= (u_ - T(1));
b_ = b;
}
Generator& cf_; // reference to continued fraction generator
T v_; // v[n] == f[n] - f[n-1], n >= 1
T u_; // u[1] = 1, u[n] = v[n]/v[n-1], n >= 2
T b_; // last denominator, i.e. b[n-1]
};
/* Converts a continued fraction into a series whose terms are the
* difference of its successive convergents.
*
* See ContinuedFractionSeriesGenerator for details.
*/
template <typename Generator, typename T = pair_value_t<generator_result_t<Generator>>>
SPECFUN_HOST_DEVICE ContinuedFractionSeriesGenerator<Generator, T>
continued_fraction_series(Generator &cf) {
return ContinuedFractionSeriesGenerator<Generator, T>(cf);
}
} // namespace detail
} // namespace special