spam-classifier
/
venv
/lib
/python3.11
/site-packages
/scipy
/special
/tests
/test_hypergeometric.py
import pytest | |
import numpy as np | |
from numpy.testing import assert_allclose, assert_equal | |
import scipy.special as sc | |
class TestHyperu: | |
def test_negative_x(self): | |
a, b, x = np.meshgrid( | |
[-1, -0.5, 0, 0.5, 1], | |
[-1, -0.5, 0, 0.5, 1], | |
np.linspace(-100, -1, 10), | |
) | |
assert np.all(np.isnan(sc.hyperu(a, b, x))) | |
def test_special_cases(self): | |
assert sc.hyperu(0, 1, 1) == 1.0 | |
def test_nan_inputs(self, a, b, x): | |
assert np.isnan(sc.hyperu(a, b, x)) == np.any(np.isnan([a, b, x])) | |
def test_gh_15650_mp(self, a, b, x, expected): | |
# See https://github.com/scipy/scipy/issues/15650 | |
# b == 1, |a| < 0.25, 0 < x < 1 | |
# | |
# This purpose of this test is to check the accuracy of results | |
# in the region that was impacted by gh-15650. | |
# | |
# Reference values computed with mpmath using the script: | |
# | |
# import itertools as it | |
# import numpy as np | |
# | |
# from mpmath import mp | |
# | |
# rng = np.random.default_rng(1234) | |
# | |
# cases = [] | |
# for a, x in it.product( | |
# np.random.uniform(-0.25, 0.25, size=6), | |
# np.logspace(-5, -1, 4), | |
# ): | |
# with mp.workdps(100): | |
# cases.append((float(a), 1.0, float(x), float(mp.hyperu(a, 1.0, x)))) | |
assert_allclose(sc.hyperu(a, b, x), expected, rtol=1e-13) | |
def test_gh_15650_sanity(self): | |
# The purpose of this test is to sanity check hyperu in the region that | |
# was impacted by gh-15650 by making sure there are no excessively large | |
# results, as were reported there. | |
a = np.linspace(-0.5, 0.5, 500) | |
x = np.linspace(1e-6, 1e-1, 500) | |
a, x = np.meshgrid(a, x) | |
results = sc.hyperu(a, 1.0, x) | |
assert np.all(np.abs(results) < 1e3) | |
class TestHyp1f1: | |
def test_nan_inputs(self, a, b, x): | |
assert np.isnan(sc.hyp1f1(a, b, x)) | |
def test_poles(self): | |
assert_equal(sc.hyp1f1(1, [0, -1, -2, -3, -4], 0.5), np.inf) | |
def test_special_cases(self, a, b, x, result): | |
# Hit all the special case branches at the beginning of the | |
# function. Desired answers computed using Mpmath. | |
assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=1e-15) | |
def test_geometric_convergence(self, a, b, x, result): | |
# Test the region where we are relying on the ratio of | |
# | |
# (|a| + 1) * |x| / |b| | |
# | |
# being small. Desired answers computed using Mpmath | |
assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=1e-15) | |
def test_a_negative_integer(self, a, b, x, result): | |
# Desired answers computed using Mpmath. | |
assert_allclose(sc.hyp1f1(a, b, x), result, atol=0, rtol=2e-14) | |
def test_assorted_cases(self, a, b, x, expected): | |
# Expected values were computed with mpmath.hyp1f1(a, b, x). | |
assert_allclose(sc.hyp1f1(a, b, x), expected, atol=0, rtol=1e-14) | |
def test_a_neg_int_and_b_equal_x(self): | |
# This is a case where the Boost wrapper will call hypergeometric_pFq | |
# instead of hypergeometric_1F1. When we use a version of Boost in | |
# which https://github.com/boostorg/math/issues/833 is fixed, this | |
# test case can probably be moved into test_assorted_cases. | |
# The expected value was computed with mpmath.hyp1f1(a, b, x). | |
a = -10.0 | |
b = 2.5 | |
x = 2.5 | |
expected = 0.0365323664364104338721 | |
computed = sc.hyp1f1(a, b, x) | |
assert_allclose(computed, expected, atol=0, rtol=1e-13) | |
def test_gh_11099(self, a, b, x, desired): | |
# All desired results computed using Mpmath | |
assert sc.hyp1f1(a, b, x) == desired | |
def test_x_zero_a_and_b_neg_ints_and_a_ge_b(self, a): | |
assert sc.hyp1f1(a, -3, 0) == 1 | |
# In the following tests with complex z, the reference values | |
# were computed with mpmath.hyp1f1(a, b, z), and verified with | |
# Wolfram Alpha Hypergeometric1F1(a, b, z), except for the | |
# case a=0.1, b=1, z=7-24j, where Wolfram Alpha reported | |
# "Standard computation time exceeded". That reference value | |
# was confirmed in an online Matlab session, with the commands | |
# | |
# > format long | |
# > hypergeom(0.1, 1, 7-24i) | |
# ans = | |
# -3.712349651834209 + 4.554636556672912i | |
# | |
def test_complex_z(self, a, b, z, ref): | |
h = sc.hyp1f1(a, b, z) | |
assert_allclose(h, ref, rtol=4e-15) | |
# The "legacy edge cases" mentioned in the comments in the following | |
# tests refers to the behavior of hyp1f1(a, b, x) when b is a nonpositive | |
# integer. In some subcases, the behavior of SciPy does not match that | |
# of Boost (1.81+), mpmath and Mathematica (via Wolfram Alpha online). | |
# If the handling of these edges cases is changed to agree with those | |
# libraries, these test will have to be updated. | |
def test_legacy_case1(self, b): | |
# Test results of hyp1f1(0, n, x) for n <= 0. | |
# This is a legacy edge case. | |
# Boost (versions greater than 1.80), Mathematica (via Wolfram Alpha | |
# online) and mpmath all return 1 in this case, but SciPy's hyp1f1 | |
# returns inf. | |
assert_equal(sc.hyp1f1(0, b, [-1.5, 0, 1.5]), [np.inf, np.inf, np.inf]) | |
def test_legacy_case2(self): | |
# This is a legacy edge case. | |
# In software such as boost (1.81+), mpmath and Mathematica, | |
# the value is 1. | |
assert sc.hyp1f1(-4, -3, 0) == np.inf | |