File size: 5,854 Bytes
7885a28 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
/* Translated from Cython into C++ by SciPy developers in 2024.
* Original header with Copyright information appears below.
*/
/* Implementation of sin/cos/sinh/cosh integrals for complex arguments
*
* Sources
* [1] Fredrik Johansson and others. mpmath: a Python library for
* arbitrary-precision floating-point arithmetic (version 0.19),
* December 2013. http://mpmath.org/.
* [2] NIST, "Digital Library of Mathematical Functions",
* https://dlmf.nist.gov/
*/
#pragma once
#include "config.h"
#include "error.h"
#include "expint.h"
#include "cephes/const.h"
#include "cephes/sici.h"
#include "cephes/shichi.h"
namespace xsf {
namespace detail {
XSF_HOST_DEVICE inline void sici_power_series(int sgn, std::complex<double> z,
std::complex<double> *s, std::complex<double> *c) {
/* DLMF 6.6.5 and 6.6.6. If sgn = -1 computes si/ci, and if sgn = 1
* computes shi/chi.
*/
std::complex<double> fac = z;
*s = fac;
*c = 0;
std::complex<double> term1, term2;
for (int n = 1; n < 100; n++) {
fac *= static_cast<double>(sgn)*z/(2.0*n);
term2 = fac/(2.0*n);
*c += term2;
fac *= z/(2.0*n + 1.0);
term1 = fac/(2.0*n + 1.0);
*s += term1;
constexpr double tol = std::numeric_limits<double>::epsilon();
if (std::abs(term1) < tol*std::abs(*s) && std::abs(term2) < tol*std::abs(*c)) {
break;
}
}
}
}
XSF_HOST_DEVICE inline int sici(std::complex<double> z,
std::complex<double> *si, std::complex<double> *ci) {
/* Compute sin/cos integrals at complex arguments. The algorithm
* largely follows that of [1].
*/
constexpr double EULER = xsf::cephes::detail::SCIPY_EULER;
if (z == std::numeric_limits<double>::infinity()) {
*si = M_PI_2;
*ci = 0;
return 0;
}
if (z == -std::numeric_limits<double>::infinity()) {
*si = -M_PI_2;
*ci = {0.0, M_PI};
return 0;
}
if (std::abs(z) < 0.8) {
// Use the series to avoid cancellation in si
detail::sici_power_series(-1, z, si, ci);
if (z == 0.0) {
set_error("sici", SF_ERROR_DOMAIN, NULL);
*ci = {-std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN()};
} else {
*ci += EULER + std::log(z);
}
return 0;
}
// DLMF 6.5.5/6.5.6 plus DLMF 6.4.4/6.4.6/6.4.7
std::complex<double> jz = std::complex<double>(0.0, 1.0) * z;
std::complex<double> term1 = expi(jz);
std::complex<double> term2 = expi(-jz);
*si = std::complex<double>(0.0, -0.5)*(term1 - term2);
*ci = 0.5*(term1 + term2);
if (z.real() == 0) {
if (z.imag() > 0) {
*ci += std::complex<double>(0.0, M_PI_2);
} else if (z.imag() < 0) {
*ci -= std::complex<double>(0.0, M_PI_2);
}
} else if (z.real() > 0) {
*si -= M_PI_2;
} else {
*si += M_PI_2;
if (z.imag() >= 0) {
*ci += std::complex<double>(0.0, M_PI);
} else {
*ci -= std::complex<double>(0.0, M_PI);
}
}
return 0;
}
XSF_HOST_DEVICE inline int sici(std::complex<float> z,
std::complex<float> *si_f, std::complex<float> *ci_f) {
std::complex<double> si;
std::complex<double> ci;
int res = sici(z, &si, &ci);
*si_f = si;
*ci_f = ci;
return res;
}
XSF_HOST_DEVICE inline int shichi(std::complex<double> z,
std::complex<double> *shi, std::complex<double> *chi) {
/* Compute sinh/cosh integrals at complex arguments. The algorithm
* largely follows that of [1].
*/
constexpr double EULER = xsf::cephes::detail::SCIPY_EULER;
if (z == std::numeric_limits<double>::infinity()) {
*shi = std::numeric_limits<double>::infinity();
*chi = std::numeric_limits<double>::infinity();
return 0;
}
if (z == -std::numeric_limits<double>::infinity()) {
*shi = -std::numeric_limits<double>::infinity();
*chi = std::numeric_limits<double>::infinity();
return 0;
}
if (std::abs(z) < 0.8) {
// Use the series to avoid cancellation in shi
detail::sici_power_series(1, z, shi, chi);
if (z == 0.0) {
set_error("shichi", SF_ERROR_DOMAIN, NULL);
*chi = {-std::numeric_limits<double>::infinity(), std::numeric_limits<double>::quiet_NaN()};
} else {
*chi += EULER + std::log(z);
}
return 0;
}
std::complex<double> term1 = expi(z);
std::complex<double> term2 = expi(-z);
*shi = 0.5*(term1 - term2);
*chi = 0.5*(term1 + term2);
if (z.imag() > 0) {
*shi -= std::complex<double>(0.0, 0.5*M_PI);
*chi += std::complex<double>(0.0, 0.5*M_PI);
} else if (z.imag() < 0) {
*shi += std::complex<double>(0.0, 0.5*M_PI);
*chi -= std::complex<double>(0.0, 0.5*M_PI);
} else if (z.real() < 0) {
*chi += std::complex<double>(0.0, M_PI);
}
return 0;
}
XSF_HOST_DEVICE inline int shichi(std::complex<float> z,
std::complex<float> *shi_f, std::complex<float> *chi_f) {
std::complex<double> shi;
std::complex<double> chi;
int res = shichi(z, &shi, &chi);
*shi_f = shi;
*chi_f = chi;
return res;
}
XSF_HOST_DEVICE inline int sici(double x, double *si, double *ci) {
return cephes::sici(x, si, ci);
}
XSF_HOST_DEVICE inline int shichi(double x, double *shi, double *chi) {
return cephes::shichi(x, shi, chi);
}
XSF_HOST_DEVICE inline int sici(float x, float *si_f, float *ci_f) {
double si;
double ci;
int res = cephes::sici(x, &si, &ci);
*si_f = si;
*ci_f = ci;
return res;
}
XSF_HOST_DEVICE inline int shichi(float x, float *shi_f, float *chi_f) {
double shi;
double chi;
int res = cephes::shichi(x, &shi, &chi);
*shi_f = shi;
*chi_f = chi;
return res;
}
}
|