/* Translated into C++ by SciPy developers in 2024.
* Original header with Copyright information appears below.
*/
/* iv.c
*
* Modified Bessel function of noninteger order
*
*
*
* SYNOPSIS:
*
* double v, x, y, iv();
*
* y = iv( v, x );
*
*
*
* DESCRIPTION:
*
* Returns modified Bessel function of order v of the
* argument. If x is negative, v must be integer valued.
*
*/
/* iv.c */
/* Modified Bessel function of noninteger order */
/* If x < 0, then v must be an integer. */
/*
* Parts of the code are copyright:
*
* Cephes Math Library Release 2.8: June, 2000
* Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier
*
* And other parts:
*
* Copyright (c) 2006 Xiaogang Zhang
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0.
*
* Boost Software License - Version 1.0 - August 17th, 2003
*
* Permission is hereby granted, free of charge, to any person or
* organization obtaining a copy of the software and accompanying
* documentation covered by this license (the "Software") to use, reproduce,
* display, distribute, execute, and transmit the Software, and to prepare
* derivative works of the Software, and to permit third-parties to whom the
* Software is furnished to do so, all subject to the following:
*
* The copyright notices in the Software and this entire statement,
* including the above license grant, this restriction and the following
* disclaimer, must be included in all copies of the Software, in whole or
* in part, and all derivative works of the Software, unless such copies or
* derivative works are solely in the form of machine-executable object code
* generated by a source language processor.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE
* DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY,
* WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* And the rest are:
*
* Copyright (C) 2009 Pauli Virtanen
* Distributed under the same license as Scipy.
*
*/
#pragma once
#include "../config.h"
#include "../error.h"
#include "const.h"
#include "gamma.h"
#include "trig.h"
namespace xsf {
namespace cephes {
namespace detail {
/*
* Compute Iv from (AMS5 9.7.1), asymptotic expansion for large |z|
* Iv ~ exp(x)/sqrt(2 pi x) ( 1 + (4*v*v-1)/8x + (4*v*v-1)(4*v*v-9)/8x/2! + ...)
*/
XSF_HOST_DEVICE inline double iv_asymptotic(double v, double x) {
double mu;
double sum, term, prefactor, factor;
int k;
prefactor = std::exp(x) / std::sqrt(2 * M_PI * x);
if (prefactor == std::numeric_limits<double>::infinity()) {
return prefactor;
}
mu = 4 * v * v;
sum = 1.0;
term = 1.0;
k = 1;
do {
factor = (mu - (2 * k - 1) * (2 * k - 1)) / (8 * x) / k;
if (k > 100) {
/* didn't converge */
set_error("iv(iv_asymptotic)", SF_ERROR_NO_RESULT, NULL);
break;
}
term *= -factor;
sum += term;
++k;
} while (std::abs(term) > MACHEP * std::abs(sum));
return sum * prefactor;
}
/*
* Uniform asymptotic expansion factors, (AMS5 9.3.9; AMS5 9.3.10)
*
* Computed with:
* --------------------
import numpy as np
t = np.poly1d([1,0])
def up1(p):
return .5*t*t*(1-t*t)*p.deriv() + 1/8. * ((1-5*t*t)*p).integ()
us = [np.poly1d([1])]
for k in range(10):
us.append(up1(us[-1]))
n = us[-1].order
for p in us:
print "{" + ", ".join(["0"]*(n-p.order) + map(repr, p)) + "},"
print "N_UFACTORS", len(us)
print "N_UFACTOR_TERMS", us[-1].order + 1
* --------------------
*/
constexpr int iv_N_UFACTORS = 11;
constexpr int iv_N_UFACTOR_TERMS = 31;
constexpr double iv_asymptotic_ufactors[iv_N_UFACTORS][iv_N_UFACTOR_TERMS] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -0.20833333333333334,
0.0, 0.125, 0.0},
{0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0.3342013888888889,
0.0,
-0.40104166666666669,
0.0,
0.0703125,
0.0,
0.0},
{0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, -1.0258125964506173,
0.0, 1.8464626736111112,
0.0, -0.89121093750000002,
0.0, 0.0732421875,
0.0, 0.0,
0.0},
{0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
4.6695844234262474,
0.0,
-11.207002616222995,
0.0,
8.78912353515625,
0.0,
-2.3640869140624998,
0.0,
0.112152099609375,
0.0,
0.0,
0.0,
0.0},
{0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, 0,
0, -28.212072558200244,
0.0, 84.636217674600744,
0.0, -91.818241543240035,
0.0, 42.534998745388457,
0.0, -7.3687943594796312,
0.0, 0.22710800170898438,
0.0, 0.0,
0.0, 0.0,
0.0},
{0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
212.5701300392171,
0.0,
-765.25246814118157,
0.0,
1059.9904525279999,
0.0,
-699.57962737613275,
0.0,
218.19051174421159,
0.0,
-26.491430486951554,
0.0,
0.57250142097473145,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0},
{0, 0,
0, 0,
0, 0,
0, 0,
0, -1919.4576623184068,
0.0, 8061.7221817373083,
0.0, -13586.550006434136,
0.0, 11655.393336864536,
0.0, -5305.6469786134048,
0.0, 1200.9029132163525,
0.0, -108.09091978839464,
0.0, 1.7277275025844574,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0},
{0,
0,
0,
0,
0,
0,
20204.291330966149,
0.0,
-96980.598388637503,
0.0,
192547.0012325315,
0.0,
-203400.17728041555,
0.0,
122200.46498301747,
0.0,
-41192.654968897557,
0.0,
7109.5143024893641,
0.0,
-493.915304773088,
0.0,
6.074042001273483,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0},
{0, 0,
0, -242919.18790055133,
0.0, 1311763.6146629769,
0.0, -2998015.9185381061,
0.0, 3763271.2976564039,
0.0, -2813563.2265865342,
0.0, 1268365.2733216248,
0.0, -331645.17248456361,
0.0, 45218.768981362737,
0.0, -2499.8304818112092,
0.0, 24.380529699556064,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0, 0.0,
0.0},
{3284469.8530720375,
0.0,
-19706819.11843222,
0.0,
50952602.492664628,
0.0,
-74105148.211532637,
0.0,
66344512.274729028,
0.0,
-37567176.660763353,
0.0,
13288767.166421819,
0.0,
-2785618.1280864552,
0.0,
308186.40461266245,
0.0,
-13886.089753717039,
0.0,
110.01714026924674,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0}};
/*
* Compute Iv, Kv from (AMS5 9.7.7 + 9.7.8), asymptotic expansion for large v
*/
XSF_HOST_DEVICE inline void ikv_asymptotic_uniform(double v, double x, double *i_value, double *k_value) {
double i_prefactor, k_prefactor;
double t, t2, eta, z;
double i_sum, k_sum, term, divisor;
int k, n;
int sign = 1;
if (v < 0) {
/* Negative v; compute I_{-v} and K_{-v} and use (AMS 9.6.2) */
sign = -1;
v = -v;
}
z = x / v;
t = 1 / std::sqrt(1 + z * z);
t2 = t * t;
eta = std::sqrt(1 + z * z) + std::log(z / (1 + 1 / t));
i_prefactor = std::sqrt(t / (2 * M_PI * v)) * std::exp(v * eta);
i_sum = 1.0;
k_prefactor = std::sqrt(M_PI * t / (2 * v)) * std::exp(-v * eta);
k_sum = 1.0;
divisor = v;
for (n = 1; n < iv_N_UFACTORS; ++n) {
/*
* Evaluate u_k(t) with Horner's scheme;
* (using the knowledge about which coefficients are zero)
*/
term = 0;
for (k = iv_N_UFACTOR_TERMS - 1 - 3 * n; k < iv_N_UFACTOR_TERMS - n; k += 2) {
term *= t2;
term += iv_asymptotic_ufactors[n][k];
}
for (k = 1; k < n; k += 2) {
term *= t2;
}
if (n % 2 == 1) {
term *= t;
}
/* Sum terms */
term /= divisor;
i_sum += term;
k_sum += (n % 2 == 0) ? term : -term;
/* Check convergence */
if (std::abs(term) < MACHEP) {
break;
}
divisor *= v;
}
if (std::abs(term) > 1e-3 * std::abs(i_sum)) {
/* Didn't converge */
set_error("ikv_asymptotic_uniform", SF_ERROR_NO_RESULT, NULL);
}
if (std::abs(term) > MACHEP * std::abs(i_sum)) {
/* Some precision lost */
set_error("ikv_asymptotic_uniform", SF_ERROR_LOSS, NULL);
}
if (k_value != NULL) {
/* symmetric in v */
*k_value = k_prefactor * k_sum;
}
if (i_value != NULL) {
if (sign == 1) {
*i_value = i_prefactor * i_sum;
} else {
/* (AMS 9.6.2) */
*i_value = (i_prefactor * i_sum + (2 / M_PI) * xsf::cephes::sinpi(v) * k_prefactor * k_sum);
}
}
}
/*
* The following code originates from the Boost C++ library,
* from file `boost/math/special_functions/detail/bessel_ik.hpp`,
* converted from C++ to C.
*/
/*
* Modified Bessel functions of the first and second kind of fractional order
*
* Calculate K(v, x) and K(v+1, x) by method analogous to
* Temme, Journal of Computational Physics, vol 21, 343 (1976)
*/
XSF_HOST_DEVICE inline int temme_ik_series(double v, double x, double *K, double *K1) {
double f, h, p, q, coef, sum, sum1, tolerance;
double a, b, c, d, sigma, gamma1, gamma2;
std::uint64_t k;
double gp;
double gm;
/*
* |x| <= 2, Temme series converge rapidly
* |x| > 2, the larger the |x|, the slower the convergence
*/
XSF_ASSERT(std::abs(x) <= 2);
XSF_ASSERT(std::abs(v) <= 0.5f);
gp = xsf::cephes::Gamma(v + 1) - 1;
gm = xsf::cephes::Gamma(-v + 1) - 1;
a = std::log(x / 2);
b = std::exp(v * a);
sigma = -a * v;
c = std::abs(v) < MACHEP ? 1 : xsf::cephes::sinpi(v) / (v * M_PI);
d = std::abs(sigma) < MACHEP ? 1 : std::sinh(sigma) / sigma;
gamma1 = std::abs(v) < MACHEP ? -SCIPY_EULER : (0.5 / v) * (gp - gm) * c;
gamma2 = (2 + gp + gm) * c / 2;
/* initial values */
p = (gp + 1) / (2 * b);
q = (1 + gm) * b / 2;
f = (std::cosh(sigma) * gamma1 + d * (-a) * gamma2) / c;
h = p;
coef = 1;
sum = coef * f;
sum1 = coef * h;
/* series summation */
tolerance = MACHEP;
for (k = 1; k < MAXITER; k++) {
f = (k * f + p + q) / (k * k - v * v);
p /= k - v;
q /= k + v;
h = p - k * f;
coef *= x * x / (4 * k);
sum += coef * f;
sum1 += coef * h;
if (std::abs(coef * f) < std::abs(sum) * tolerance) {
break;
}
}
if (k == MAXITER) {
set_error("ikv_temme(temme_ik_series)", SF_ERROR_NO_RESULT, NULL);
}
*K = sum;
*K1 = 2 * sum1 / x;
return 0;
}
/* Evaluate continued fraction fv = I_(v+1) / I_v, derived from
* Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73 */
XSF_HOST_DEVICE inline int CF1_ik(double v, double x, double *fv) {
double C, D, f, a, b, delta, tiny, tolerance;
std::uint64_t k;
/*
* |x| <= |v|, CF1_ik converges rapidly
* |x| > |v|, CF1_ik needs O(|x|) iterations to converge
*/
/*
* modified Lentz's method, see
* Lentz, Applied Optics, vol 15, 668 (1976)
*/
tolerance = 2 * MACHEP;
tiny = 1 / std::sqrt(std::numeric_limits<double>::max());
C = f = tiny; /* b0 = 0, replace with tiny */
D = 0;
for (k = 1; k < MAXITER; k++) {
a = 1;
b = 2 * (v + k) / x;
C = b + a / C;
D = b + a * D;
if (C == 0) {
C = tiny;
}
if (D == 0) {
D = tiny;
}
D = 1 / D;
delta = C * D;
f *= delta;
if (std::abs(delta - 1) <= tolerance) {
break;
}
}
if (k == MAXITER) {
set_error("ikv_temme(CF1_ik)", SF_ERROR_NO_RESULT, NULL);
}
*fv = f;
return 0;
}
/*
* Calculate K(v, x) and K(v+1, x) by evaluating continued fraction
* z1 / z0 = U(v+1.5, 2v+1, 2x) / U(v+0.5, 2v+1, 2x), see
* Thompson and Barnett, Computer Physics Communications, vol 47, 245 (1987)
*/
XSF_HOST_DEVICE inline int CF2_ik(double v, double x, double *Kv, double *Kv1) {
double S, C, Q, D, f, a, b, q, delta, tolerance, current, prev;
std::uint64_t k;
/*
* |x| >= |v|, CF2_ik converges rapidly
* |x| -> 0, CF2_ik fails to converge
*/
XSF_ASSERT(std::abs(x) > 1);
/*
* Steed's algorithm, see Thompson and Barnett,
* Journal of Computational Physics, vol 64, 490 (1986)
*/
tolerance = MACHEP;
a = v * v - 0.25;
b = 2 * (x + 1); /* b1 */
D = 1 / b; /* D1 = 1 / b1 */
f = delta = D; /* f1 = delta1 = D1, coincidence */
prev = 0; /* q0 */
current = 1; /* q1 */
Q = C = -a; /* Q1 = C1 because q1 = 1 */
S = 1 + Q * delta; /* S1 */
for (k = 2; k < MAXITER; k++) { /* starting from 2 */
/* continued fraction f = z1 / z0 */
a -= 2 * (k - 1);
b += 2;
D = 1 / (b + a * D);
delta *= b * D - 1;
f += delta;
/* series summation S = 1 + \sum_{n=1}^{\infty} C_n * z_n / z_0 */
q = (prev - (b - 2) * current) / a;
prev = current;
current = q; /* forward recurrence for q */
C *= -a / k;
Q += C * q;
S += Q * delta;
/* S converges slower than f */
if (std::abs(Q * delta) < std::abs(S) * tolerance) {
break;
}
}
if (k == MAXITER) {
set_error("ikv_temme(CF2_ik)", SF_ERROR_NO_RESULT, NULL);
}
*Kv = std::sqrt(M_PI / (2 * x)) * std::exp(-x) / S;
*Kv1 = *Kv * (0.5 + v + x + (v * v - 0.25) * f) / x;
return 0;
}
/* Flags for what to compute */
enum { ikv_temme_need_i = 0x1, ikv_temme_need_k = 0x2 };
/*
* Compute I(v, x) and K(v, x) simultaneously by Temme's method, see
* Temme, Journal of Computational Physics, vol 19, 324 (1975)
*/
XSF_HOST_DEVICE inline void ikv_temme(double v, double x, double *Iv_p, double *Kv_p) {
/* Kv1 = K_(v+1), fv = I_(v+1) / I_v */
/* Ku1 = K_(u+1), fu = I_(u+1) / I_u */
double u, Iv, Kv, Kv1, Ku, Ku1, fv;
double W, current, prev, next;
int reflect = 0;
unsigned n, k;
int kind;
kind = 0;
if (Iv_p != NULL) {
kind |= ikv_temme_need_i;
}
if (Kv_p != NULL) {
kind |= ikv_temme_need_k;
}
if (v < 0) {
reflect = 1;
v = -v; /* v is non-negative from here */
kind |= ikv_temme_need_k;
}
n = std::round(v);
u = v - n; /* -1/2 <= u < 1/2 */
if (x < 0) {
if (Iv_p != NULL)
*Iv_p = std::numeric_limits<double>::quiet_NaN();
if (Kv_p != NULL)
*Kv_p = std::numeric_limits<double>::quiet_NaN();
set_error("ikv_temme", SF_ERROR_DOMAIN, NULL);
return;
}
if (x == 0) {
Iv = (v == 0) ? 1 : 0;
if (kind & ikv_temme_need_k) {
set_error("ikv_temme", SF_ERROR_OVERFLOW, NULL);
Kv = std::numeric_limits<double>::infinity();
} else {
Kv = std::numeric_limits<double>::quiet_NaN(); /* any value will do */
}
if (reflect && (kind & ikv_temme_need_i)) {
double z = (u + n % 2);
Iv = xsf::cephes::sinpi(z) == 0 ? Iv : std::numeric_limits<double>::infinity();
if (std::isinf(Iv)) {
set_error("ikv_temme", SF_ERROR_OVERFLOW, NULL);
}
}
if (Iv_p != NULL) {
*Iv_p = Iv;
}
if (Kv_p != NULL) {
*Kv_p = Kv;
}
return;
}
/* x is positive until reflection */
W = 1 / x; /* Wronskian */
if (x <= 2) { /* x in (0, 2] */
temme_ik_series(u, x, &Ku, &Ku1); /* Temme series */
} else { /* x in (2, \infty) */
CF2_ik(u, x, &Ku, &Ku1); /* continued fraction CF2_ik */
}
prev = Ku;
current = Ku1;
for (k = 1; k <= n; k++) { /* forward recurrence for K */
next = 2 * (u + k) * current / x + prev;
prev = current;
current = next;
}
Kv = prev;
Kv1 = current;
if (kind & ikv_temme_need_i) {
double lim = (4 * v * v + 10) / (8 * x);
lim *= lim;
lim *= lim;
lim /= 24;
if ((lim < MACHEP * 10) && (x > 100)) {
/*
* x is huge compared to v, CF1 may be very slow
* to converge so use asymptotic expansion for large
* x case instead. Note that the asymptotic expansion
* isn't very accurate - so it's deliberately very hard
* to get here - probably we're going to overflow:
*/
Iv = iv_asymptotic(v, x);
} else {
CF1_ik(v, x, &fv); /* continued fraction CF1_ik */
Iv = W / (Kv * fv + Kv1); /* Wronskian relation */
}
} else {
Iv = std::numeric_limits<double>::quiet_NaN(); /* any value will do */
}
if (reflect) {
double z = (u + n % 2);
if (Iv_p != NULL) {
*Iv_p = Iv + (2 / M_PI) * xsf::cephes::sinpi(z) * Kv; /* reflection formula */
}
if (Kv_p != NULL) {
*Kv_p = Kv;
}
} else {
if (Iv_p != NULL) {
*Iv_p = Iv;
}
if (Kv_p != NULL) {
*Kv_p = Kv;
}
}
return;
}
} // namespace detail
XSF_HOST_DEVICE inline double iv(double v, double x) {
int sign;
double t, ax, res;
if (std::isnan(v) || std::isnan(x)) {
return std::numeric_limits<double>::quiet_NaN();
}
/* If v is a negative integer, invoke symmetry */
t = std::floor(v);
if (v < 0.0) {
if (t == v) {
v = -v; /* symmetry */
t = -t;
}
}
/* If x is negative, require v to be an integer */
sign = 1;
if (x < 0.0) {
if (t != v) {
set_error("iv", SF_ERROR_DOMAIN, NULL);
return (std::numeric_limits<double>::quiet_NaN());
}
if (v != 2.0 * std::floor(v / 2.0)) {
sign = -1;
}
}
/* Avoid logarithm singularity */
if (x == 0.0) {
if (v == 0.0) {
return 1.0;
}
if (v < 0.0) {
set_error("iv", SF_ERROR_OVERFLOW, NULL);
return std::numeric_limits<double>::infinity();
} else
return 0.0;
}
ax = std::abs(x);
if (std::abs(v) > 50) {
/*
* Uniform asymptotic expansion for large orders.
*
* This appears to overflow slightly later than the Boost
* implementation of Temme's method.
*/
detail::ikv_asymptotic_uniform(v, ax, &res, NULL);
} else {
/* Otherwise: Temme's method */
detail::ikv_temme(v, ax, &res, NULL);
}
res *= sign;
return res;
}
} // namespace cephes
} // namespace xsf