// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // bessel.hpp // Template wrappers for the Bessel function family // // Provided functions: // besselJ(nu, x), besselY(nu, x) — ordinary Bessel of the 1st/2nd kind (real order) // besselI(nu, x), besselK(nu, x) — modified Bessel of the 1st/2nd kind (real order) // sphericalBesselJ(n, x), sphericalBesselY(n, x) — spherical Bessel of the 1st/2nd kind // besselJPrime(nu, x), besselYPrime(nu, x) — derivatives (recurrence) // besselIPrime(nu, x), besselKPrime(nu, x) — modified Bessel derivatives // hankelH1(n, z), hankelH2(n, z) — Hankel function of the 1st/2nd kind (DLMF 10.4.3) // hankelH1Prime(n, z), hankelH2Prime(n, z) — Hankel function derivatives // sphericalHankelH1(n, z), sphericalHankelH2(n, z) — spherical Hankel functions // besselJ/Y/I/K(n, z) — Complex support // besselJ/Y/I/K(n, z, precision) — Complex support // // Supported types: // float, double, long double — delegated to C++17 std:: // Float — delegated to the sangi:: implementation // Complex — Taylor series / A&S formulas // Complex — arbitrary-precision Taylor series / A&S formulas #ifndef SANGI_SPECIAL_BESSEL_HPP #define SANGI_SPECIAL_BESSEL_HPP #include #include #include #include #include #include namespace sangi { namespace special { // ================================================================ // Ordinary Bessel function of the 1st kind J_ν(x) // ================================================================ /// Native floating-point types: delegate to std::cyl_bessel_j template [[nodiscard]] T besselJ(T nu, T x) { if (std::isnan(nu) || std::isnan(x)) return std::numeric_limits::quiet_NaN(); return std::cyl_bessel_j(nu, x); } /// Convenience overload for integer order template [[nodiscard]] T besselJ(int n, T x) { return besselJ(static_cast(n), x); } // Float type: use sangi::besselJ(n, x, precision) directly. // ================================================================ // Ordinary Bessel function of the 2nd kind Y_ν(x) (Neumann function) // ================================================================ /// Native floating-point types: delegate to std::cyl_neumann template [[nodiscard]] T besselY(T nu, T x) { if (std::isnan(nu) || std::isnan(x)) return std::numeric_limits::quiet_NaN(); return std::cyl_neumann(nu, x); } /// Convenience overload for integer order template [[nodiscard]] T besselY(int n, T x) { return besselY(static_cast(n), x); } // Float type: use sangi::besselY(n, x, precision) directly. // ================================================================ // Modified Bessel function of the 1st kind I_ν(x) // ================================================================ /// Native floating-point types: delegate to std::cyl_bessel_i template [[nodiscard]] T besselI(T nu, T x) { if (std::isnan(nu) || std::isnan(x)) return std::numeric_limits::quiet_NaN(); return std::cyl_bessel_i(nu, x); } /// Convenience overload for integer order template [[nodiscard]] T besselI(int n, T x) { return besselI(static_cast(n), x); } // Float type: use sangi::besselI(n, x, precision) directly. // ================================================================ // Modified Bessel function of the 2nd kind K_ν(x) // ================================================================ /// Native floating-point types: delegate to std::cyl_bessel_k template [[nodiscard]] T besselK(T nu, T x) { if (std::isnan(nu) || std::isnan(x)) return std::numeric_limits::quiet_NaN(); if (x <= T(0)) return std::numeric_limits::quiet_NaN(); return std::cyl_bessel_k(nu, x); } /// Convenience overload for integer order template [[nodiscard]] T besselK(int n, T x) { return besselK(static_cast(n), x); } // Float type: use sangi::besselK(n, x, precision) directly. // ================================================================ // Spherical Bessel function of the 1st kind j_n(x) // j_n(x) = √(π/(2x)) · J_{n+1/2}(x) // ================================================================ /// Native floating-point types: delegate to std::sph_bessel template [[nodiscard]] T sphericalBesselJ(unsigned int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); return std::sph_bessel(n, x); } // Float type: use sangi::sphericalBesselJ(n, x, precision) directly. // ================================================================ // Spherical Bessel function of the 2nd kind y_n(x) (spherical Neumann function) // y_n(x) = √(π/(2x)) · Y_{n+1/2}(x) // ================================================================ /// Native floating-point types: delegate to std::sph_neumann template [[nodiscard]] T sphericalBesselY(unsigned int n, T x) { if (std::isnan(x)) return std::numeric_limits::quiet_NaN(); return std::sph_neumann(n, x); } // Float type: use sangi::sphericalBesselY(n, x, precision) directly. // ================================================================ // Derivatives (via recurrence relations) // DLMF 10.6.1: J'_ν(x) = (J_{ν-1}(x) - J_{ν+1}(x)) / 2 // DLMF 10.6.3: Y'_ν(x) = (Y_{ν-1}(x) - Y_{ν+1}(x)) / 2 // DLMF 10.29.1: I'_ν(x) = (I_{ν-1}(x) + I_{ν+1}(x)) / 2 // DLMF 10.29.3: K'_ν(x) = -(K_{ν-1}(x) + K_{ν+1}(x)) / 2 // ================================================================ template [[nodiscard]] T besselJPrime(T nu, T x) { return (besselJ(nu - T(1), x) - besselJ(nu + T(1), x)) / T(2); } template [[nodiscard]] T besselYPrime(T nu, T x) { return (besselY(nu - T(1), x) - besselY(nu + T(1), x)) / T(2); } template [[nodiscard]] T besselIPrime(T nu, T x) { return (besselI(nu - T(1), x) + besselI(nu + T(1), x)) / T(2); } template [[nodiscard]] T besselKPrime(T nu, T x) { return -(besselK(nu - T(1), x) + besselK(nu + T(1), x)) / T(2); } // ================================================================ // Complex Bessel functions (Complex and Complex) // ================================================================ // J_n(z): Taylor series (z/2)^n Σ (-1)^m (z/2)^{2m} / (m!(m+n)!) // I_n(z): Taylor series (z/2)^n Σ (z/2)^{2m} / (m!(m+n)!) // Y_n(z): A&S 9.1.11 (2/π)J_n·ln(z/2) - (1/π)Σ... - (1/π)Σ... // K_n(z): A&S 9.6.11 (-1)^{n+1}·I_n·ln(z/2) + ... // ================================================================ namespace detail { /// J_n(z) — Taylor series (integer order, complex argument) template [[nodiscard]] Complex besselJ_complex(int n, const Complex& z, R eps, int max_iter) { using C = Complex; int abs_n = (n >= 0) ? n : -n; if (sangi::abs(z) == R(0)) return (abs_n == 0) ? C(R(1)) : C(R(0)); C z_half = z / R(2); C z_half_sq = z_half * z_half; C neg_z_half_sq = -z_half_sq; // (z/2)^n C z_half_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_n = z_half_n * z_half; // term_0 = 1/n! R fact_inv(1); for (int i = 1; i <= abs_n; i++) fact_inv = fact_inv / R(i); C term(fact_inv); C sum = term; for (int m = 1; m < max_iter; m++) { // term_m = term_{m-1} · (-(z/2)²) / (m·(m+n)) term = term * neg_z_half_sq / R(static_cast(m) * (m + abs_n)); sum = sum + term; if (m >= 3 && sangi::abs(term) < eps * sangi::abs(sum)) break; } C result = z_half_n * sum; if (n < 0 && abs_n % 2 != 0) result = -result; return result; } /// I_n(z) — Taylor series (integer order, complex argument) template [[nodiscard]] Complex besselI_complex(int n, const Complex& z, R eps, int max_iter) { using C = Complex; int abs_n = (n >= 0) ? n : -n; if (sangi::abs(z) == R(0)) return (abs_n == 0) ? C(R(1)) : C(R(0)); C z_half = z / R(2); C z_half_sq = z_half * z_half; // (z/2)^n C z_half_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_n = z_half_n * z_half; // term_0 = 1/n! R fact_inv(1); for (int i = 1; i <= abs_n; i++) fact_inv = fact_inv / R(i); C term(fact_inv); C sum = term; for (int m = 1; m < max_iter; m++) { // term_m = term_{m-1} · (z/2)² / (m·(m+n)) — no sign flip term = term * z_half_sq / R(static_cast(m) * (m + abs_n)); sum = sum + term; if (m >= 3 && sangi::abs(term) < eps * sangi::abs(sum)) break; } C result = z_half_n * sum; // I_{-n}(z) = I_n(z) for integer n — unchanged return result; } /// Y_n(z) — A&S 9.1.11 (integer order, complex argument) /// Y_n(z) = (2/π)·J_n(z)·ln(z/2) /// - (1/π)·(z/2)^{-n}·Σ_{k=0}^{n-1} (n-k-1)!/k! · (z²/4)^k /// - (1/π)·(z/2)^n · Σ_{k=0}^{∞} (-1)^k·(H_k+H_{k+n})/(k!·(k+n)!) · (z²/4)^k template [[nodiscard]] Complex besselY_complex(int n, const Complex& z, R eps, int max_iter, R pi_val, R euler_gamma) { using C = Complex; int abs_n = (n >= 0) ? n : -n; C z_half = z / R(2); C z_half_sq = z_half * z_half; C ln_z_half = sangi::log(z_half); R two_over_pi = R(2) / pi_val; R one_over_pi = R(1) / pi_val; // Compute J_n(z) C jn = besselJ_complex(abs_n, z, eps, max_iter); // Part A: (2/π) · J_n(z) · (γ + ln(z/2)) [DLMF 10.8.1] C result = C(two_over_pi) * jn * (C(euler_gamma) + ln_z_half); // Part B: -(1/π) · (z/2)^{-n} · Σ_{k=0}^{n-1} (n-k-1)!/k! · (z²/4)^k if (abs_n > 0) { C z_half_neg_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_neg_n = z_half_neg_n / z_half; // k=0: (n-1)! R bk_coeff(1); for (int i = 1; i < abs_n; i++) bk_coeff = bk_coeff * R(i); C sumB(bk_coeff); for (int k = 1; k < abs_n; k++) { // recurrence: coeff_k = coeff_{k-1} · (z²/4) / (k·(n-k)) bk_coeff = bk_coeff / R(static_cast(k) * (abs_n - k)); sumB = sumB + C(bk_coeff) * sangi::pow(z_half_sq, k); } result = result - C(one_over_pi) * z_half_neg_n * sumB; } // Part C: -(1/π) · (z/2)^n · Σ_{k=0}^{∞} (-1)^k (H_k+H_{k+n})/(k!·(k+n)!) · (z²/4)^k C z_half_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_n = z_half_n * z_half; // Avoid exact/exact poison: for R=Float, divide harmonic numbers / factorial // inverses by a working-precision unit. R(1)/R(j) with both operands exact // falls back to default precision and tops out at ~57 digits // (AUDIT_FLOAT_UNIT_MIXING root cause #1). For R=double, unit=1 is the identity. R unit(1); if constexpr (std::is_same_v) { unit.setEffectiveBits(sangi::Float::precisionToBits(pi_val.precision())); } R H_k(0); // H_0 = 0 R H_kn(0); // H_n = Σ_{j=1}^{n} 1/j for (int j = 1; j <= abs_n; j++) H_kn = H_kn + unit / R(j); // k=0: 1/n! R ck_coeff = unit; for (int i = 1; i <= abs_n; i++) ck_coeff = ck_coeff / R(i); C neg_z_half_sq = -z_half_sq; C ck_term(ck_coeff); // track the z^0 term C sumC = ck_term * C(H_k + H_kn); for (int k = 1; k < max_iter; k++) { H_k = H_k + unit / R(k); H_kn = H_kn + unit / R(k + abs_n); // recurrence: ck_term_{k} = ck_term_{k-1} · (-(z²/4)) / (k·(k+n)) ck_term = ck_term * neg_z_half_sq / R(static_cast(k) * (k + abs_n)); C contrib = ck_term * C(H_k + H_kn); sumC = sumC + contrib; if (k >= 5 && sangi::abs(contrib) < eps * sangi::abs(sumC)) break; } result = result - C(one_over_pi) * z_half_n * sumC; // Y_{-n}(z) = (-1)^n · Y_n(z) if (n < 0 && abs_n % 2 != 0) result = -result; return result; } /// K_n(z) — A&S 9.6.11 (integer order, complex argument) /// K_n(z) = (-1)^{n+1}·I_n(z)·ln(z/2) /// + (1/2)·(z/2)^{-n}·Σ_{k=0}^{n-1} (-1)^k·(n-k-1)!/k! · (z²/4)^k /// + (-1)^n·(1/2)·(z/2)^n·Σ_{k=0}^{∞} (H_k+H_{k+n}-2γ)/(k!·(k+n)!) · (z²/4)^k template [[nodiscard]] Complex besselK_complex(int n, const Complex& z, R eps, int max_iter, R pi_val, R euler_gamma) { using C = Complex; int abs_n = (n >= 0) ? n : -n; C z_half = z / R(2); C z_half_sq = z_half * z_half; C ln_z_half = sangi::log(z_half); R two_gamma = R(2) * euler_gamma; // Compute I_n(z) C in = besselI_complex(abs_n, z, eps, max_iter); // Part A: (-1)^{n+1} · I_n(z) · ln(z/2) C partA = in * ln_z_half; if (abs_n % 2 == 0) partA = -partA; // (-1)^{n+1} // Part B: (1/2)·(z/2)^{-n} · Σ_{k=0}^{n-1} (-1)^k·(n-k-1)!/k! · (z²/4)^k C partB(R(0)); if (abs_n > 0) { C z_half_neg_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_neg_n = z_half_neg_n / z_half; // k=0: (n-1)! R bk_coeff(1); for (int i = 1; i < abs_n; i++) bk_coeff = bk_coeff * R(i); C neg_z_half_sq = -z_half_sq; C bk_term(bk_coeff); C sumB = bk_term; for (int k = 1; k < abs_n; k++) { // recurrence: bk_term_{k} = bk_term_{k-1} · (-(z²/4)) / (k·(n-k)) bk_term = bk_term * neg_z_half_sq / R(static_cast(k) * (abs_n - k)); sumB = sumB + bk_term; } partB = C(R(1) / R(2)) * z_half_neg_n * sumB; } // Part C: (-1)^n · (1/2) · (z/2)^n · Σ_{k=0}^{∞} (H_k+H_{k+n}-2γ)/(k!·(k+n)!) · (z²/4)^k C z_half_n(R(1)); for (int i = 0; i < abs_n; i++) z_half_n = z_half_n * z_half; // Avoid exact/exact poison (same as besselY): divide harmonic numbers / factorial inverses by the working-precision unit. R unit(1); if constexpr (std::is_same_v) { unit.setEffectiveBits(sangi::Float::precisionToBits(pi_val.precision())); } R H_k(0); R H_kn(0); for (int j = 1; j <= abs_n; j++) H_kn = H_kn + unit / R(j); // k=0: 1/n! R ck_coeff = unit; for (int i = 1; i <= abs_n; i++) ck_coeff = ck_coeff / R(i); C ck_term(ck_coeff); C sumC = ck_term * C(H_k + H_kn - two_gamma); for (int k = 1; k < max_iter; k++) { H_k = H_k + unit / R(k); H_kn = H_kn + unit / R(k + abs_n); // recurrence: (z²/4) / (k·(k+n)) ck_term = ck_term * z_half_sq / R(static_cast(k) * (k + abs_n)); C contrib = ck_term * C(H_k + H_kn - two_gamma); sumC = sumC + contrib; if (k >= 5 && sangi::abs(contrib) < eps * sangi::abs(sumC)) break; } R sign_n = (abs_n % 2 == 0) ? R(1) : R(-1); C partC = C(sign_n * R(1) / R(2)) * z_half_n * sumC; C result = partA + partB + partC; // K_{-n}(z) = K_n(z) for integer n return result; } } // namespace detail // ================================================================ // Complex overloads // ================================================================ [[nodiscard]] inline Complex besselJ(int n, const Complex& z) { return detail::besselJ_complex(n, z, std::numeric_limits::epsilon(), 300); } [[nodiscard]] inline Complex besselI(int n, const Complex& z) { return detail::besselI_complex(n, z, std::numeric_limits::epsilon(), 300); } [[nodiscard]] inline Complex besselY(int n, const Complex& z) { return detail::besselY_complex(n, z, std::numeric_limits::epsilon(), 300, std::numbers::pi, 0.5772156649015328606065120900824024310421593359); } [[nodiscard]] inline Complex besselK(int n, const Complex& z) { return detail::besselK_complex(n, z, std::numeric_limits::epsilon(), 300, std::numbers::pi, 0.5772156649015328606065120900824024310421593359); } // ================================================================ // Complex overloads // ================================================================ [[nodiscard]] inline Complex besselJ(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 20; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact (harmonic numbers H_k etc.) at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); C result = detail::besselJ_complex(n, zw, Float::epsilon(wp), 10 * wp); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex besselJ(int n, const Complex& z) { return besselJ(n, z, Float::defaultPrecision()); } [[nodiscard]] inline Complex besselI(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 20; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact (harmonic numbers H_k etc.) at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); C result = detail::besselI_complex(n, zw, Float::epsilon(wp), 10 * wp); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex besselI(int n, const Complex& z) { return besselI(n, z, Float::defaultPrecision()); } [[nodiscard]] inline Complex besselY(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 30; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact (harmonic numbers H_k etc.) at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); Float pi_val = Float::pi(wp); Float euler_gamma = Float::euler(wp); C result = detail::besselY_complex(n, zw, Float::epsilon(wp), 10 * wp, pi_val, euler_gamma); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex besselY(int n, const Complex& z) { return besselY(n, z, Float::defaultPrecision()); } [[nodiscard]] inline Complex besselK(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 30; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact (harmonic numbers H_k etc.) at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); Float pi_val = Float::pi(wp); Float euler_gamma = Float::euler(wp); C result = detail::besselK_complex(n, zw, Float::epsilon(wp), 10 * wp, pi_val, euler_gamma); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex besselK(int n, const Complex& z) { return besselK(n, z, Float::defaultPrecision()); } // ================================================================ // Hankel functions (1st/2nd kind) // DLMF 10.4.3: // H_n^{(1)}(z) = J_n(z) + i·Y_n(z) // H_n^{(2)}(z) = J_n(z) - i·Y_n(z) // ================================================================ /// Hankel function of the 1st kind H_n^{(1)}(z) — Complex [[nodiscard]] inline Complex hankelH1(int n, const Complex& z) { using C = Complex; C jn = besselJ(n, z); C yn = besselY(n, z); // H^(1) = J + i·Y → (Jre - Yim, Jim + Yre) return C(jn.re - yn.im, jn.im + yn.re); } /// Hankel function of the 2nd kind H_n^{(2)}(z) — Complex [[nodiscard]] inline Complex hankelH2(int n, const Complex& z) { using C = Complex; C jn = besselJ(n, z); C yn = besselY(n, z); // H^(2) = J - i·Y → (Jre + Yim, Jim - Yre) return C(jn.re + yn.im, jn.im - yn.re); } /// Hankel function of the 1st kind H_n^{(1)}(z) — Complex [[nodiscard]] inline Complex hankelH1(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 30; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); Float pi_val = Float::pi(wp); Float euler_gamma = Float::euler(wp); Float eps = Float::epsilon(wp); int max_iter = 10 * wp; C jn = detail::besselJ_complex(n, zw, eps, max_iter); C yn = detail::besselY_complex(n, zw, eps, max_iter, pi_val, euler_gamma); // H^(1) = J + i·Y → (Jre - Yim, Jim + Yre) C result(jn.re - yn.im, jn.im + yn.re); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex hankelH1(int n, const Complex& z) { return hankelH1(n, z, Float::defaultPrecision()); } /// Hankel function of the 2nd kind H_n^{(2)}(z) — Complex [[nodiscard]] inline Complex hankelH2(int n, const Complex& z, int precision) { using C = Complex; int wp = precision + 30; Float::PrecisionScope _ps(wp); // compute in-function exact÷exact at wp digits // PrecisionGuard removed: setResultPrecision(wp) on zw propagates req=wp. C zw = z; zw.re.setResultPrecision(wp); zw.im.setResultPrecision(wp); Float pi_val = Float::pi(wp); Float euler_gamma = Float::euler(wp); Float eps = Float::epsilon(wp); int max_iter = 10 * wp; C jn = detail::besselJ_complex(n, zw, eps, max_iter); C yn = detail::besselY_complex(n, zw, eps, max_iter, pi_val, euler_gamma); // H^(2) = J - i·Y → (Jre + Yim, Jim - Yre) C result(jn.re + yn.im, jn.im - yn.re); result.re.setPrecision(precision); result.im.setPrecision(precision); return result; } [[nodiscard]] inline Complex hankelH2(int n, const Complex& z) { return hankelH2(n, z, Float::defaultPrecision()); } // ================================================================ // Hankel function derivatives (recurrence) // DLMF 10.6.5: // H'^{(k)}_n(z) = (H^{(k)}_{n-1}(z) - H^{(k)}_{n+1}(z)) / 2 // ================================================================ /// H'^{(1)}_n(z) — Complex [[nodiscard]] inline Complex hankelH1Prime(int n, const Complex& z) { return (hankelH1(n - 1, z) - hankelH1(n + 1, z)) / Complex(2.0); } /// H'^{(2)}_n(z) — Complex [[nodiscard]] inline Complex hankelH2Prime(int n, const Complex& z) { return (hankelH2(n - 1, z) - hankelH2(n + 1, z)) / Complex(2.0); } /// H'^{(1)}_n(z) — Complex [[nodiscard]] inline Complex hankelH1Prime(int n, const Complex& z, int precision) { return (hankelH1(n - 1, z, precision) - hankelH1(n + 1, z, precision)) / Complex(Float(2)); } [[nodiscard]] inline Complex hankelH1Prime(int n, const Complex& z) { return hankelH1Prime(n, z, Float::defaultPrecision()); } /// H'^{(2)}_n(z) — Complex [[nodiscard]] inline Complex hankelH2Prime(int n, const Complex& z, int precision) { return (hankelH2(n - 1, z, precision) - hankelH2(n + 1, z, precision)) / Complex(Float(2)); } [[nodiscard]] inline Complex hankelH2Prime(int n, const Complex& z) { return hankelH2Prime(n, z, Float::defaultPrecision()); } // ================================================================ // Spherical Hankel functions // DLMF 10.47.5: // h_n^{(1)}(z) = √(π/(2z)) · H_{n+1/2}^{(1)}(z) // = j_n(z) + i·y_n(z) // h_n^{(2)}(z) = √(π/(2z)) · H_{n+1/2}^{(2)}(z) // = j_n(z) - i·y_n(z) // ================================================================ /// Complex version of the spherical Bessel functions j_n(z), y_n(z) (recurrence) /// DLMF 10.51.1: j_{n+1}(z) = (2n+1)/z · j_n(z) - j_{n-1}(z) namespace detail { template [[nodiscard]] Complex sphericalBesselJ_complex(int n, const Complex& z) { using C = Complex; if (sangi::abs(z) == R(0)) return (n == 0) ? C(R(1)) : C(R(0)); // j_0(z) = sin(z)/z, j_1(z) = sin(z)/z² - cos(z)/z C sinz = sangi::sin(z); C cosz = sangi::cos(z); C inv_z = C(R(1)) / z; C j0 = sinz * inv_z; if (n == 0) return j0; C j1 = sinz * inv_z * inv_z - cosz * inv_z; if (n == 1) return j1; C j_prev = j0; C j_curr = j1; for (int k = 1; k < n; k++) { C j_next = C(R(2 * k + 1)) * inv_z * j_curr - j_prev; j_prev = j_curr; j_curr = j_next; } return j_curr; } template [[nodiscard]] Complex sphericalBesselY_complex(int n, const Complex& z) { using C = Complex; // y_0(z) = -cos(z)/z, y_1(z) = -cos(z)/z² - sin(z)/z C sinz = sangi::sin(z); C cosz = sangi::cos(z); C inv_z = C(R(1)) / z; C y0 = -cosz * inv_z; if (n == 0) return y0; C y1 = -cosz * inv_z * inv_z - sinz * inv_z; if (n == 1) return y1; C y_prev = y0; C y_curr = y1; for (int k = 1; k < n; k++) { C y_next = C(R(2 * k + 1)) * inv_z * y_curr - y_prev; y_prev = y_curr; y_curr = y_next; } return y_curr; } } // namespace detail /// Spherical Hankel function of the 1st kind h_n^{(1)}(z) = j_n(z) + i·y_n(z) [[nodiscard]] inline Complex sphericalHankelH1(int n, const Complex& z) { using C = Complex; C jn = detail::sphericalBesselJ_complex(n, z); C yn = detail::sphericalBesselY_complex(n, z); // j + i·y → (j.re - y.im, j.im + y.re) return C(jn.re - yn.im, jn.im + yn.re); } /// Spherical Hankel function of the 2nd kind h_n^{(2)}(z) = j_n(z) - i·y_n(z) [[nodiscard]] inline Complex sphericalHankelH2(int n, const Complex& z) { using C = Complex; C jn = detail::sphericalBesselJ_complex(n, z); C yn = detail::sphericalBesselY_complex(n, z); // j - i·y → (j.re + y.im, j.im - y.re) return C(jn.re + yn.im, jn.im - yn.re); } } // namespace special } // namespace sangi #endif // SANGI_SPECIAL_BESSEL_HPP