From b384546727b13d32c790a91886a7930b555632c7 Mon Sep 17 00:00:00 2001 From: dartvader316 Date: Wed, 25 Sep 2024 19:17:30 +0300 Subject: [PATCH 001/251] BUG:signal: Fix passing lti as system to cont2discrete --- scipy/signal/_lti_conversion.py | 5 +++-- scipy/signal/tests/test_cont2discrete.py | 10 +++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/scipy/signal/_lti_conversion.py b/scipy/signal/_lti_conversion.py index 52c6efbbfa53..eb2c7fe309c6 100644 --- a/scipy/signal/_lti_conversion.py +++ b/scipy/signal/_lti_conversion.py @@ -431,8 +431,9 @@ def cont2discrete(system, dt, method="zoh", alpha=None): >>> plt.show() """ - if len(system) == 1: - return system.to_discrete() + if hasattr(system, 'to_discrete') and callable(system.to_discrete): + return system.to_discrete(dt=dt, method=method, alpha=alpha) + if len(system) == 2: sysd = cont2discrete(tf2ss(system[0], system[1]), dt, method=method, alpha=alpha) diff --git a/scipy/signal/tests/test_cont2discrete.py b/scipy/signal/tests/test_cont2discrete.py index 4492692f74ed..dc396fb5c925 100644 --- a/scipy/signal/tests/test_cont2discrete.py +++ b/scipy/signal/tests/test_cont2discrete.py @@ -353,19 +353,27 @@ def test_c2d_ss(self): B = np.array([[0], [1]]) C = np.array([[1, 0]]) D = 0 + dt = 0.05 A_res = np.array([[0.985136404135682, 0.004876671474795], [0.009753342949590, 0.965629718236502]]) B_res = np.array([[0.000122937599964], [0.049135527547844]]) sys_ssc = lti(A, B, C, D) - sys_ssd = sys_ssc.to_discrete(0.05) + sys_ssd = sys_ssc.to_discrete(dt=dt) xp_assert_close(sys_ssd.A, A_res) xp_assert_close(sys_ssd.B, B_res) xp_assert_close(sys_ssd.C, C) xp_assert_close(sys_ssd.D, np.zeros_like(sys_ssd.D)) + sys_ssd2 = c2d(sys_ssc, dt=dt) + + xp_assert_close(sys_ssd2.A, A_res) + xp_assert_close(sys_ssd2.B, B_res) + xp_assert_close(sys_ssd2.C, C) + xp_assert_close(sys_ssd2.D, np.zeros_like(sys_ssd2.D)) + def test_c2d_tf(self): sys = lti([0.5, 0.3], [1.0, 0.4]) From 51dd4f4dbf7903a2ba4814140ff4245859e7a11f Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 Jan 2025 12:50:18 -0800 Subject: [PATCH 002/251] Begin to draft discrete distribution --- scipy/stats/__init__.py | 2 +- scipy/stats/_distribution_infrastructure.py | 186 +++++++++++++++----- scipy/stats/_new_distributions.py | 46 ++++- scipy/stats/_probability_distribution.py | 146 +++++++++++++++ 4 files changed, 323 insertions(+), 57 deletions(-) diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index b6a73e7b7814..bb16ee20b20f 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -649,7 +649,7 @@ from ._distribution_infrastructure import ( make_distribution, Mixture, order_statistic, truncate, exp, log, abs ) -from ._new_distributions import Normal, Uniform +from ._new_distributions import Normal, Uniform, Binomial from ._mgc import multiscale_graphcorr from ._correlation import chatterjeexi diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index d7ac0bf52c4a..f5da946defda 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -264,6 +264,8 @@ class _SimpleDomain(_Domain): defined symbolically. contains(item, parameter_values) Determines whether the argument is contained within the domain + draw(size, rng, proportions, parameter_values) + Draws random values based on the domain. """ def __init__(self, endpoints=(-inf, inf), inclusive=(False, False)): @@ -366,44 +368,6 @@ def contains(self, item, parameter_values=None): in_right = item <= b if right_inclusive else item < b return in_left & in_right - -class _RealDomain(_SimpleDomain): - r""" Represents a simply-connected subset of the real line; i.e., an interval - - Completes the implementation of the `_SimpleDomain` class for simple - domains on the real line. - - Methods - ------- - define_parameters(*parameters) - (Inherited) Records any parameters used to define the endpoints of the - domain. - get_numerical_endpoints(parameter_values) - (Inherited) Gets the numerical values of the domain endpoints, which - may have been defined symbolically. - contains(item, parameter_values) - (Inherited) Determines whether the argument is contained within the - domain - __str__() - Returns a string representation of the domain, e.g. "[a, b)". - draw(size, rng, proportions, parameter_values) - Draws random values based on the domain. Proportions of values within - the domain, on the endpoints of the domain, outside the domain, - and having value NaN are specified by `proportions`. - - """ - - def __str__(self): - a, b = self.endpoints - left_inclusive, right_inclusive = self.inclusive - - left = "[" if left_inclusive else "(" - a = self.symbols.get(a, f"{a}") - right = "]" if right_inclusive else ")" - b = self.symbols.get(b, f"{b}") - - return f"{left}{a}, {b}{right}" - def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): r""" Draw random values from the domain. @@ -459,16 +423,73 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): return z +class _RealDomain(_SimpleDomain): + r""" Represents a simply-connected subset of the real line; i.e., an interval + + Completes the implementation of the `_SimpleDomain` class for simple + domains on the real line. + + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Inherited) Determines whether the argument is contained within the + domain + __str__() + Returns a string representation of the domain, e.g. "[a, b)". + """ + + def __str__(self): + a, b = self.endpoints + left_inclusive, right_inclusive = self.inclusive + + left = "[" if left_inclusive else "(" + a = self.symbols.get(a, f"{a}") + right = "]" if right_inclusive else ")" + b = self.symbols.get(b, f"{b}") + + return f"{left}{a}, {b}{right}" + + class _IntegerDomain(_SimpleDomain): - r""" Representation of a domain of consecutive integers. + r""" Represents an interval of integers - Completes the implementation of the `_SimpleDomain` class for domains - composed of consecutive integer values. + Completes the implementation of the `_SimpleDomain` class for simple + domains on the integers. + + Methods + ------- + define_parameters(*parameters) + (Inherited) Records any parameters used to define the endpoints of the + domain. + get_numerical_endpoints(parameter_values) + (Inherited) Gets the numerical values of the domain endpoints, which + may have been defined symbolically. + contains(item, parameter_values) + (Overridden) Determines whether the argument is contained within the + domain + draw(n, type_, min, max, squeezed_base_shape, rng=None) + (Inherited) Draws random values based on the domain. + __str__() + Returns a string representation of the domain, e.g. "{a, a+1, ..., b-1, b}". - To be completed when needed. """ - def __init__(self): - raise NotImplementedError + + def contains(self, item, parameter_values=None): + super_contains = super().contains(item, parameter_values) + integral = (item == np.round(item)) + return super_contains and integral + + def __str__(self): + a, b = self.endpoints + a = self.symbols.get(a, f"{a}") + b = self.symbols.get(b, f"{b}") + return f"{{{a}, {a+1}, ..., {b-1}, {b}}}" class _Parameter(ABC): @@ -835,6 +856,7 @@ def _set_invalid_nan(f): # Wrapper for input / output validation and standardization of distribution # functions that accept either the quantile or percentile as an argument: # logpdf, pdf + # logpmf, pmf # logcdf, cdf # logccdf, ccdf # ilogcdf, icdf @@ -850,9 +872,10 @@ def _set_invalid_nan(f): endpoints = {'icdf': (0, 1), 'iccdf': (0, 1), 'ilogcdf': (-np.inf, 0), 'ilogccdf': (-np.inf, 0)} replacements = {'logpdf': (-inf, -inf), 'pdf': (0, 0), + 'logpmf': (-inf, -inf), 'pmf': (0, 0), '_logcdf1': (-inf, 0), '_logccdf1': (0, -inf), '_cdf1': (0, 1), '_ccdf1': (1, 0)} - replace_strict = {'pdf', 'logpdf'} + replace_strict = {'pdf', 'logpdf', 'pmf', 'logpmf'} replace_exact = {'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} clip = {'_cdf1', '_ccdf1'} clip_log = {'_logcdf1', '_logccdf1'} @@ -1400,7 +1423,7 @@ def _generate_example(dist_family): return example -class ContinuousDistribution(_ProbabilityDistribution): +class _UnivariateDistribution(_ProbabilityDistribution): r""" Class that represents a continuous statistical distribution. Parameters @@ -2323,7 +2346,7 @@ def kurtosis(self, *, method=None, convention='non-excess'): # See the note corresponding with the "Distribution Parameters" for more # information. - ## Probability Density Functions + ## Probability Density/Mass Functions @_set_invalid_nan def logpdf(self, x, /, *, method=None): @@ -2361,6 +2384,42 @@ def _pdf_formula(self, x, **params): def _pdf_logexp(self, x, **params): return np.exp(self._logpdf_dispatch(x, **params)) + @_set_invalid_nan + def logpmf(self, x, /, *, method=None): + return self._logpmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _logpmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_logpmf_formula'): + method = self._logpmf_formula + elif _isnull(self.tol): # ensure that developers override _logpmf + method = self._logpmf_logexp + return method + + def _logpmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _logpmf_logexp(self, x, **params): + return np.log(self._pmf_dispatch(x, **params)) + + @_set_invalid_nan + def pmf(self, x, /, *, method=None): + return self._pmf_dispatch(x, method=method, **self._parameters) + + @_dispatch + def _pmf_dispatch(self, x, *, method=None, **params): + if self._overrides('_pmf_formula'): + method = self._pmf_formula + else: + method = self._pmf_logexp + return method + + def _pmf_formula(self, x, **params): + raise NotImplementedError(self._not_implemented) + + def _pmf_logexp(self, x, **params): + return np.exp(self._logpmf_dispatch(x, **params)) + ## Cumulative Distribution Functions def logcdf(self, x, y=None, /, *, method=None): @@ -3417,6 +3476,37 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): # these methods reasonably efficiently. +class ContinuousDistribution(_UnivariateDistribution): + def _overrides(self, method_name): + if method_name in {'_logpmf_formula', '_pmf_formula'}: + return True + return super()._overrides(method_name) + + ## Probability Density / Mass Functions + def _pmf_formula(self, x, **params): + return np.zeros_like(x) + + def _logpmf_formula(self, x, **params): + return np.full_like(x, -np.inf) + + +class DiscreteDistribution(_UnivariateDistribution): + ## Algorithms + + ## Other + def _overrides(self, method_name): + if method_name in {'_logpdf_formula', '_pdf_formula'}: + return True + return super()._overrides(method_name) + + # These should probably check whether pmf = 0 + def _logpdf_formula(self, x, **params): + return np.full_like(x, np.inf) + + def _pdf_formula(self, x, **params): + return np.full_like(x, np.inf) + + # Special case the names of some new-style distributions in `make_distribution` _distribution_names = { 'argus': 'ARGUS', @@ -4362,6 +4452,8 @@ def order_statistic(X, /, *, r, n): return OrderStatisticDistribution(X, r=r, n=n) +# Someday, I'd like for Mixture to become a `_UnivariateDistribution`, but that +# day is not today. class Mixture(_ProbabilityDistribution): r"""Representation of a mixture distribution. diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 894117e29542..7d7d4262fbbd 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -5,10 +5,10 @@ from scipy import special from scipy.stats._distribution_infrastructure import ( - ContinuousDistribution, _RealDomain, _RealParameter, _Parameterization, - _combine_docs) + ContinuousDistribution, DiscreteDistribution, _RealDomain, _IntegerDomain, + _RealParameter, _Parameterization, _combine_docs) -__all__ = ['Normal', 'Uniform'] +__all__ = ['Normal', 'Uniform', 'Binomial'] class Normal(ContinuousDistribution): @@ -367,9 +367,37 @@ def _pdf_formula(self, x, *, a, **kwargs): return x ** (a - 1) * np.exp(-x) / special.gamma(a) -# Distribution classes need only define the summary and beginning of the extended -# summary portion of the class documentation. All other documentation, including -# examples, is generated automatically. -_module = sys.modules[__name__].__dict__ -for dist_name in __all__: - _module[dist_name].__doc__ = _combine_docs(_module[dist_name]) +class Binomial(DiscreteDistribution): + r"""Binomial distribution with prescribed success probability and number of trials + + The probability density function of the binomial distribution is: + + .. math:: + + f(x) = {n \choose x} p^x (1 - p)^{n-x} + + """ + _n_domain = _IntegerDomain(endpoints=(0, inf), inclusive=(True, False)) + _p_domain = _RealDomain(endpoints=(0, 1), inclusive=(True, True)) + _x_support = _IntegerDomain(endpoints=(0, 'n'), inclusive=(True, True)) + + _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) + _p_param = _RealParameter('p', domain=_p_domain, typical=(0.25, 0.75)) + _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) + + _parameterizations = [_Parameterization(_n_param, _p_param)] + _variable = _x_param + + def __init__(self, *, n, p, **kwargs): + super().__init__(n=n, p=p, **kwargs) + + def _pmf_formula(self, x, *, n, p, **kwargs): + return special.binom(n, x) * p**x * (1 - p)**(n - x) + + +# # Distribution classes need only define the summary and beginning of the extended +# # summary portion of the class documentation. All other documentation, including +# # examples, is generated automatically. +# _module = sys.modules[__name__].__dict__ +# for dist_name in __all__: +# _module[dist_name].__doc__ = _combine_docs(_module[dist_name]) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 9b092694240e..5911511a7dc1 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -846,6 +846,152 @@ def logpdf(self, x, /, *, method): """ raise NotImplementedError() + def pmf(self, x, /, *, method=None): + r"""Probability mass function + + The probability density function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = P(X = x) + + `pmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the PMF. By default (``None``), the + infrastructure chooses between the following options, listed in + order of precedence. + + - ``'formula'``: use a formula for the PMF itself + - ``'logexp'``: evaluate the log-PMF and exponentiate + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The PMF evaluated at the argument `x`. + + See Also + -------- + cdf + logpmf + + Notes + ----- + Suppose a discrete probability distribution has support :math:`[l, r]`. + By definition of the support, the PMF evaluates to its minimum value + of :math:`0` outside the support; i.e. for :math:`x < l` or + :math:`x > r`. + + References + ---------- + .. [1] Probability mass function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_mass_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> from scipy import stats + >>> X = stats.Binomial(n=10, b=0.5) + + Evaluate the PMF at the desired argument: + + >>> X.pmf(5) + 0.5 + + """ + raise NotImplementedError() + + def logpmf(self, x, /, *, method=None): + r"""Log of the probability mass function + + The probability density function ("PMF"), denoted :math:`f(x)`, is the + probability that the random variable :math:`X` will assume the value :math:`x`. + + .. math:: + + f(x) = \frac{d}{dx} F(x) + + `logpmf` computes the logarithm of the probability density function + ("log-PMF"), :math:`\log(f(x))`, but it may be numerically favorable + compared to the naive implementation (computing :math:`f(x)` and + taking the logarithm). + + `logpmf` accepts `x` for :math:`x`. + + Parameters + ---------- + x : array_like + The argument of the log-PMF. + method : {None, 'formula', 'logexp'} + The strategy used to evaluate the log-PMF. By default (``None``), the + infrastructure chooses between the following options, listed in order + of precedence. + + - ``'formula'``: use a formula for the log-PMF itself + - ``'logexp'``: evaluate the PMF and takes its logarithm + + Not all `method` options are available for all distributions. + If the selected `method` is not available, a ``NotImplementedError`` + will be raised. + + Returns + ------- + out : array + The log-PMF evaluated at the argument `x`. + + See Also + -------- + pmf + logcdf + + Notes + ----- + Suppose a continuous probability distribution has support :math:`[l, r]`. + By definition of the support, the log-PMF evaluates to its minimum value + of :math:`-\infty` (i.e. :math:`\log(0)`) outside the support; i.e. for + :math:`x < l` or :math:`x > r`. + + For distributions with infinite support, it is common for `pmf` to return + a value of ``0`` when the argument is theoretically within the support; + this can occur because the true value of the PMF is too small to be + represented by the chosen dtype. The log-PMF, however, will often be finite + (not ``-inf``) over a much larger domain. Consequently, it may be preferred + to work with the logarithms of probabilities and probability densities to + avoid underflow. + + References + ---------- + .. [1] Probability density function, *Wikipedia*, + https://en.wikipedia.org/wiki/Probability_density_function + + Examples + -------- + Instantiate a distribution with the desired parameters: + + >>> import numpy as np + >>> from scipy import stats + >>> X = stats.Uniform(a=-1.0, b=1.0) + + Evaluate the log-PMF at the desired argument: + + >>> X.logpmf(0.5) + -0.6931471805599453 + >>> np.allclose(X.logpmf(0.5), np.log(X.pmf(0.5))) + True + + """ + raise NotImplementedError() + @abstractmethod def cdf(self, x, y, /, *, method): r"""Cumulative distribution function From 4695db74257209a98dff36effdb8a823bb43c0ce Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 Jan 2025 15:17:46 -0800 Subject: [PATCH 003/251] ENH: stats.DiscreteDistribution: basic functionality for several methods --- scipy/stats/_distribution_infrastructure.py | 60 +++++++++++++-------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index f5da946defda..5c29989b6262 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -9,7 +9,7 @@ from scipy._lib._util import _lazywhere, _rng_spawn from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats -from scipy.integrate import tanhsinh as _tanhsinh +from scipy.integrate import tanhsinh as _tanhsinh, nsum from scipy.optimize._bracket import _bracket_root, _bracket_minimum from scipy.optimize._chandrupatla import _chandrupatla, _chandrupatla_minimize from scipy.stats._probability_distribution import _ProbabilityDistribution @@ -2008,10 +2008,14 @@ def _quadrature(self, integrand, limits=None, args=None, args = np.broadcast_arrays(*args) # If we know the median or mean, consider breaking up the interval rtol = None if _isnull(self.tol) else self.tol - res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) # For now, we ignore the status, but I want to return the error # estimate - see question 5 at the top. - return res.integral + if isinstance(self, ContinuousDistribution): + res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) + return res.integral + else: + res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)) + return res.sum def _solve_bounded(self, f, p, *, bounds=None, params=None): # Finds the argument of a function that produces the desired output. @@ -2076,7 +2080,7 @@ def _overrides(self, method_name): # For more complete discussion of the considerations, see: # https://github.com/scipy/scipy/pull/21050#discussion_r1707798901 method = getattr(self.__class__, method_name, None) - super_method = getattr(ContinuousDistribution, method_name, None) + super_method = getattr(_UnivariateDistribution, method_name, None) return method is not super_method ### Distribution properties @@ -2205,8 +2209,8 @@ def _logentropy_logexp_safe(self, **params): def _logentropy_quadrature(self, **params): def logintegrand(x, **params): - logpdf = self._logpdf_dispatch(x, **params) - return logpdf + np.log(0j+logpdf) + logpxf = self._logpxf_dispatch(x, **params) + return logpxf + np.log(0j+logpxf) res = self._quadrature(logintegrand, params=params, log=True) return _log_real_standardize(res + np.pi*1j) @@ -2232,9 +2236,9 @@ def _entropy_logexp(self, **params): def _entropy_quadrature(self, **params): def integrand(x, **params): - pdf = self._pdf_dispatch(x, **params) - logpdf = self._logpdf_dispatch(x, **params) - return logpdf * pdf + pxf = self._pxf_dispatch(x, **params) + logpxf = self._logpxf_dispatch(x, **params) + return logpxf * pxf return -self._quadrature(integrand, params=params) @_set_invalid_nan_property @@ -2279,7 +2283,7 @@ def _mode_optimization(self, **params): a, b = self._support(**params) m = self._median_dispatch(**params) - f, args = _kwargs2args(lambda x, **params: -self._pdf_dispatch(x, **params), + f, args = _kwargs2args(lambda x, **params: -self._pxf_dispatch(x, **params), args=(), kwargs=params) res_b = _bracket_minimum(f, m, xmin=a, xmax=b, args=args) res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr, args=args) @@ -2485,7 +2489,7 @@ def _logcdf2_logexp_safe(self, x, y, **params): return out[()] def _logcdf2_quadrature(self, x, y, **params): - logres = self._quadrature(self._logpdf_dispatch, limits=(x, y), + logres = self._quadrature(self._logpxf_dispatch, limits=(x, y), log=True, params=params) return logres @@ -2526,7 +2530,7 @@ def _logcdf_logexp_safe(self, x, **params): def _logcdf_quadrature(self, x, **params): a, _ = self._support(**params) - return self._quadrature(self._logpdf_dispatch, limits=(a, x), + return self._quadrature(self._logpxf_dispatch, limits=(a, x), params=params, log=True) def cdf(self, x, y=None, /, *, method=None): @@ -2594,7 +2598,7 @@ def _cdf2_subtraction_safe(self, x, y, **params): return out[()] def _cdf2_quadrature(self, x, y, **params): - return self._quadrature(self._pdf_dispatch, limits=(x, y), params=params) + return self._quadrature(self._pxf_dispatch, limits=(x, y), params=params) @_set_invalid_nan def _cdf1(self, x, *, method): @@ -2636,7 +2640,7 @@ def _cdf_complement_safe(self, x, **params): def _cdf_quadrature(self, x, **params): a, _ = self._support(**params) - return self._quadrature(self._pdf_dispatch, limits=(a, x), + return self._quadrature(self._pxf_dispatch, limits=(a, x), params=params) def logccdf(self, x, y=None, /, *, method=None): @@ -2704,7 +2708,7 @@ def _logccdf_logexp_safe(self, x, **params): def _logccdf_quadrature(self, x, **params): _, b = self._support(**params) - return self._quadrature(self._logpdf_dispatch, limits=(x, b), + return self._quadrature(self._logpxf_dispatch, limits=(x, b), params=params, log=True) def ccdf(self, x, y=None, /, *, method=None): @@ -2774,7 +2778,7 @@ def _ccdf_complement_safe(self, x, **params): def _ccdf_quadrature(self, x, **params): _, b = self._support(**params) - return self._quadrature(self._pdf_dispatch, limits=(x, b), + return self._quadrature(self._pxf_dispatch, limits=(x, b), params=params) ## Inverse cumulative distribution functions @@ -3076,7 +3080,7 @@ def _moment_raw_dispatch(self, order, *, methods, **params): moment = self._moment_raw_general(order, **params) if moment is None and 'quadrature' in methods: - moment = self._moment_integrate_pdf(order, center=self._zero, **params) + moment = self._moment_from_pxf(order, center=self._zero, **params) if moment is None and 'quadrature_icdf' in methods: moment = self._moment_integrate_icdf(order, center=self._zero, **params) @@ -3139,7 +3143,7 @@ def _moment_central_dispatch(self, order, *, methods, **params): if moment is None and 'quadrature' in methods: mean = self._moment_raw_dispatch(self._one, **params, methods=self._moment_methods) - moment = self._moment_integrate_pdf(order, center=mean, **params) + moment = self._moment_from_pxf(order, center=mean, **params) if moment is None and 'quadrature_icdf' in methods: mean = self._moment_raw_dispatch(self._one, **params, @@ -3230,10 +3234,10 @@ def _moment_standardized_general(self, order, **params): general_standard_moments = {0: self._one, 1: self._zero, 2: self._one} return general_standard_moments.get(order, None) - def _moment_integrate_pdf(self, order, center, **params): + def _moment_from_pxf(self, order, center, **params): def integrand(x, order, center, **params): - pdf = self._pdf_dispatch(x, **params) - return pdf*(x-center)**order + pxf = self._pxf_dispatch(x, **params) + return pxf*(x-center)**order return self._quadrature(integrand, args=(order, center), params=params) def _moment_integrate_icdf(self, order, center, **params): @@ -3269,7 +3273,7 @@ def _logmoment(self, order=1, *, logcenter=None, standardized=False): def _logmoment_quad(self, order, logcenter, **params): def logintegrand(x, order, logcenter, **params): - logpdf = self._logpdf_dispatch(x, **params) + logpdf = self._logpxf_dispatch(x, **params) return logpdf + order * _logexpxmexpy(np.log(x + 0j), logcenter) ## if logx == logcenter, `_logexpxmexpy` returns (-inf + 0j) ## multiplying by order produces (-inf + nan j) - bad @@ -3489,6 +3493,12 @@ def _pmf_formula(self, x, **params): def _logpmf_formula(self, x, **params): return np.full_like(x, -np.inf) + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pdf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpdf_dispatch(x, method=method, **params) + class DiscreteDistribution(_UnivariateDistribution): ## Algorithms @@ -3506,6 +3516,12 @@ def _logpdf_formula(self, x, **params): def _pdf_formula(self, x, **params): return np.full_like(x, np.inf) + def _pxf_dispatch(self, x, *, method=None, **params): + return self._pmf_dispatch(x, method=method, **params) + + def _logpxf_dispatch(self, x, *, method=None, **params): + return self._logpmf_dispatch(x, method=method, **params) + # Special case the names of some new-style distributions in `make_distribution` _distribution_names = { From caab6d383ac549af71f5dbdf980fc5551fa214bb Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 Jan 2025 18:43:31 -0800 Subject: [PATCH 004/251] ENH: DiscreteDistribution: make inverse functions *almost* work; need to fix ccdf/logccdf edge cases --- scipy/stats/_distribution_infrastructure.py | 67 +++++++++++++++++---- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 5c29989b6262..38227e260726 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2014,10 +2014,11 @@ def _quadrature(self, integrand, limits=None, args=None, res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) return res.integral else: - res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)) - return res.sum + res = np.asarray(nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum) + res[a > b] = 0 # nsum should probably return 0 instead of nan + return res[()] - def _solve_bounded(self, f, p, *, bounds=None, params=None): + def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): # Finds the argument of a function that produces the desired output. # Much of this should be added to _bracket_root / _chandrupatla. xmin, xmax = self._support(**params) if bounds is None else bounds @@ -2061,8 +2062,10 @@ def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` res = _bracket_root(f3, xl0=a, xr0=b, xmin=xmin, xmax=xmax, args=args) # For now, we ignore the status, but I want to use the bracket width # as an error estimate - see question 5 at the top. + xrtol = None if _isnull(self.tol) else self.tol - return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, xrtol=xrtol).x + xatol = None if xatol is None else xatol + return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, xrtol=xrtol, xatol=xatol) ## Other @@ -2804,7 +2807,7 @@ def _ilogcdf_complement(self, x, **params): return self._ilogccdf_dispatch(_log1mexp(x), **params) def _ilogcdf_inversion(self, x, **params): - return self._solve_bounded(self._logcdf_dispatch, x, params=params) + return self._solve_bounded(self._logcdf_dispatch, x, params=params).x @_set_invalid_nan def icdf(self, p, /, *, method=None): @@ -2839,7 +2842,7 @@ def _icdf_complement_safe(self, x, **params): return out[()] def _icdf_inversion(self, x, **params): - return self._solve_bounded(self._cdf_dispatch, x, params=params) + return self._solve_bounded(self._cdf_dispatch, x, params=params).x @_set_invalid_nan def ilogccdf(self, logp, /, *, method=None): @@ -2862,7 +2865,7 @@ def _ilogccdf_complement(self, x, **params): return self._ilogcdf_dispatch(_log1mexp(x), **params) def _ilogccdf_inversion(self, x, **params): - return self._solve_bounded(self._logccdf_dispatch, x, params=params) + return self._solve_bounded(self._logccdf_dispatch, x, params=params).x @_set_invalid_nan def iccdf(self, p, /, *, method=None): @@ -2897,7 +2900,7 @@ def _iccdf_complement_safe(self, x, **params): return out[()] def _iccdf_inversion(self, x, **params): - return self._solve_bounded(self._ccdf_dispatch, x, params=params) + return self._solve_bounded(self._ccdf_dispatch, x, params=params).x ### Sampling Functions # The following functions for drawing samples from the distribution are @@ -3486,7 +3489,6 @@ def _overrides(self, method_name): return True return super()._overrides(method_name) - ## Probability Density / Mass Functions def _pmf_formula(self, x, **params): return np.zeros_like(x) @@ -3501,9 +3503,6 @@ def _logpxf_dispatch(self, x, *, method=None, **params): class DiscreteDistribution(_UnivariateDistribution): - ## Algorithms - - ## Other def _overrides(self, method_name): if method_name in {'_logpdf_formula', '_pdf_formula'}: return True @@ -3522,6 +3521,50 @@ def _pxf_dispatch(self, x, *, method=None, **params): def _logpxf_dispatch(self, x, *, method=None, **params): return self._logpmf_dispatch(x, method=method, **params) + def _ccdf_quadrature(self, x, **params): + return super()._ccdf_quadrature(np.floor(x + 1), **params) + + def _logccdf_quadrature(self, x, **params): + return super()._logccdf_quadrature(np.floor(x + 1), **params) + + def _icdf_inversion(self, x, **params): + res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=1) + return np.where(res.fl == 0, np.floor(res.xl), np.floor(res.xr)) + + def _ilogcdf_inversion(self, x, **params): + res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=1) + return np.where(res.fl == 0, np.floor(res.xl), np.floor(res.xr)) + + def _iccdf_inversion(self, x, **params): + res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=0.9) + return np.where(res.fr == 0, np.ceil(res.xr), np.ceil(res.xl)) + + def _ilogccdf_inversion(self, x, **params): + res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=1) + return np.where(res.fr == 0, np.ceil(res.xr), np.ceil(res.xl)) + + # For initial PR, leave these out + + def _cdf2_dispatch(self, x, y, *, method=None, **params): + message = "Two-argument CDF is not available for discrete distributions." + raise NotImplementedError(message) + + def _logcdf2_dispatch(self, x, y, *, method=None, **params): + message = "Two-argument log-CDF is not available for discrete distributions." + raise NotImplementedError(message) + + def _ccdf2_dispatch(self, x, y, *, method=None, **params): + message = "Two-argument CCDF is not available for discrete distributions." + raise NotImplementedError(message) + + def _logccdf2_dispatch(self, x, y, *, method=None, **params): + message = "Two-argument log-CCDF is not available for discrete distributions." + raise NotImplementedError(message) + + def _logentropy_dispatch(self, *, method=None, **params): + message = "Log-entropy is not available for discrete distributions." + raise NotImplementedError(message) + # Special case the names of some new-style distributions in `make_distribution` _distribution_names = { From 3a775a3218f02d7c50096435df3c3e394cad14ed Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 Jan 2025 22:53:42 -0800 Subject: [PATCH 005/251] MAINT: fix _draw, some edge cases --- scipy/stats/_distribution_infrastructure.py | 52 ++++++++++++++++----- scipy/stats/_new_distributions.py | 14 +++--- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 38227e260726..8fbba4aa2191 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -6,7 +6,7 @@ import numpy as np from numpy import inf -from scipy._lib._util import _lazywhere, _rng_spawn +from scipy._lib._util import _lazywhere, _rng_spawn, _RichResult from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats from scipy.integrate import tanhsinh as _tanhsinh, nsum @@ -392,6 +392,9 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): """ rng = np.random.default_rng(rng) + def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) + uniform = rng.uniform if isinstance(self, _RealDomain) else ints + # get copies of min and max with no nans so that uniform doesn't fail min_nn, max_nn = min.copy(), max.copy() i = np.isnan(min_nn) | np.isnan(max_nn) @@ -401,7 +404,7 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): shape = (n,) + squeezed_base_shape if type_ == 'in': - z = rng.uniform(min_nn, max_nn, size=shape) + z = uniform(min_nn, max_nn, size=shape) elif type_ == 'on': z_on_shape = shape @@ -412,8 +415,8 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): elif type_ == 'out': # make this work for infinite bounds - z = min_nn - rng.uniform(size=shape) - zr = max_nn + rng.uniform(size=shape) + z = min_nn - uniform(1, 5, size=shape) + zr = max_nn + uniform(1, 5, size=shape) i = rng.random(size=n) < 0.5 z[i] = zr[i] @@ -489,7 +492,9 @@ def __str__(self): a, b = self.endpoints a = self.symbols.get(a, f"{a}") b = self.symbols.get(b, f"{b}") - return f"{{{a}, {a+1}, ..., {b-1}, {b}}}" + ap1 = f"{a} + 1" if isinstance(a, str) else f"{a + 1}" + bm1 = f"{b} - 1" if isinstance(b, str) else f"{b - 1}" + return f"{{{a}, {ap1}, ..., {bm1}, {b}}}" class _Parameter(ABC): @@ -537,7 +542,7 @@ def __init__(self, name, *, domain, symbol=None, typical=None): self.symbol = symbol or name self.domain = domain if typical is not None and not isinstance(typical, _Domain): - typical = _RealDomain(typical) + typical = domain.__class__(typical) self.typical = typical or domain def __str__(self): @@ -839,7 +844,7 @@ def draw(self, sizes=None, rng=None, proportions=None, region='domain'): # we can draw values in order a, b, c. parameter_values = {} - if not len(sizes) or not np.iterable(sizes[0]): + if sizes is None or not len(sizes) or not np.iterable(sizes[0]): sizes = [sizes]*len(self.parameters) for size, param in zip(sizes, self.parameters.values()): @@ -879,6 +884,9 @@ def _set_invalid_nan(f): replace_exact = {'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} clip = {'_cdf1', '_ccdf1'} clip_log = {'_logcdf1', '_logccdf1'} + # relevant to discrete distributions only + replace_non_integral = {'pmf', 'logpmf'} + @functools.wraps(f) def filtered(self, x, *args, **kwargs): @@ -889,6 +897,9 @@ def filtered(self, x, *args, **kwargs): x = np.asarray(x) dtype = self._dtype shape = self._shape + discrete = isinstance(self, DiscreteDistribution) + keep_low_endpoint = discrete and method_name in {'_cdf1', '_logcdf1', + '_ccdf1', '_logccdf1'} # Ensure that argument is at least as precise as distribution # parameters, which are already at least floats. This will avoid issues @@ -917,7 +928,7 @@ def filtered(self, x, *args, **kwargs): # and the result will be set to the appropriate value. left_inc, right_inc = self._variable.domain.inclusive mask_low = (x < low if (method_name in replace_strict and left_inc) - else x <= low) + or keep_low_endpoint else x <= low) mask_high = (x > high if (method_name in replace_strict and right_inc) else x >= high) mask_invalid = (mask_low | mask_high) @@ -934,6 +945,13 @@ def filtered(self, x, *args, **kwargs): any_endpoint = (mask_endpoint if mask_endpoint.shape == () else np.any(mask_endpoint)) + # Check for non-integral arguments to PMF method + any_non_integral = False + if method_name in replace_non_integral: + mask_non_integral = x != np.floor(x) + any_non_integral = (mask_non_integral if mask_non_integral.shape == () + else np.any(mask_non_integral)) + # Set out-of-domain arguments to NaN. The result will be set to the # appropriate value later. if any_invalid: @@ -951,12 +969,18 @@ def filtered(self, x, *args, **kwargs): if res.shape != shape: # faster to check first res = np.broadcast_to(res, self._shape) - res_needs_copy = res_needs_copy or any_invalid or any_endpoint + res_needs_copy = (res_needs_copy or any_invalid + or any_endpoint or any_non_integral) if res_needs_copy: res = np.array(res, dtype=dtype, copy=True) - # For arguments outside the function domain, replace results + # For non-integral arguments to PMF, replace with zero + if any_non_integral: + zero = -np.inf if method_name == 'logpmf' else 0 + res[mask_non_integral] = zero + + # For arguments outside the function domain, replace results if any_invalid: replace_low, replace_high = ( replacements.get(method_name, (np.nan, np.nan))) @@ -977,7 +1001,8 @@ def filtered(self, x, *args, **kwargs): a[mask_high_endpoint] if method_name.endswith('ccdf') else b[mask_high_endpoint]) - res[mask_low_endpoint] = replace_low_endpoint + if not keep_low_endpoint: + res[mask_low_endpoint] = replace_low_endpoint res[mask_high_endpoint] = replace_high_endpoint # Clip probabilities to [0, 1] @@ -2027,7 +2052,10 @@ def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax) if not p.size: # might need to figure out result type based on p - return np.empty(p.shape, dtype=self._dtype) + res = _RichResult() + empty = np.empty(p.shape, dtype=self._dtype) + res.xl, res.x, res.xr = empty, empty, empty + res.fl, res.fr = empty, empty def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` return f(x, **kwargs) - _p diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 7d7d4262fbbd..99c4da245041 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -383,7 +383,7 @@ class Binomial(DiscreteDistribution): _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) _p_param = _RealParameter('p', domain=_p_domain, typical=(0.25, 0.75)) - _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) + _x_param = _RealParameter('x', domain=_x_support, typical=(0, 10)) _parameterizations = [_Parameterization(_n_param, _p_param)] _variable = _x_param @@ -395,9 +395,9 @@ def _pmf_formula(self, x, *, n, p, **kwargs): return special.binom(n, x) * p**x * (1 - p)**(n - x) -# # Distribution classes need only define the summary and beginning of the extended -# # summary portion of the class documentation. All other documentation, including -# # examples, is generated automatically. -# _module = sys.modules[__name__].__dict__ -# for dist_name in __all__: -# _module[dist_name].__doc__ = _combine_docs(_module[dist_name]) +# Distribution classes need only define the summary and beginning of the extended +# summary portion of the class documentation. All other documentation, including +# examples, is generated automatically. +_module = sys.modules[__name__].__dict__ +for dist_name in __all__: + _module[dist_name].__doc__ = _combine_docs(_module[dist_name]) From 9fbd796c0993dd3b859fe7ad2c915da39c6c67d6 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 10 Jan 2025 22:59:22 -0800 Subject: [PATCH 006/251] DOC: stats.DiscreteDistribution: fix examples [skip ci] --- scipy/stats/_probability_distribution.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 5911511a7dc1..40ff9ab41a77 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -901,12 +901,12 @@ def pmf(self, x, /, *, method=None): Instantiate a distribution with the desired parameters: >>> from scipy import stats - >>> X = stats.Binomial(n=10, b=0.5) + >>> X = stats.Binomial(n=10, p=0.5) Evaluate the PMF at the desired argument: >>> X.pmf(5) - 0.5 + np.float64(0.24609375) """ raise NotImplementedError() @@ -978,15 +978,14 @@ def logpmf(self, x, /, *, method=None): -------- Instantiate a distribution with the desired parameters: - >>> import numpy as np >>> from scipy import stats - >>> X = stats.Uniform(a=-1.0, b=1.0) + >>> X = stats.Binomial(n=10, p=0.5) Evaluate the log-PMF at the desired argument: - >>> X.logpmf(0.5) - -0.6931471805599453 - >>> np.allclose(X.logpmf(0.5), np.log(X.pmf(0.5))) + >>> X.logpmf(5) + np.float64(-1.4020427180880297) + >>> np.allclose(X.logpmf(5), np.log(X.pmf(5))) True """ From de3251dd1f5801510cdfe9dcbd0574b19a3c70f0 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 11 Jan 2025 10:36:15 -0800 Subject: [PATCH 007/251] ENH: _UnivariateDistribution.plot: adapt to discrete distributions --- scipy/stats/_distribution_infrastructure.py | 44 ++++++++++++++++----- scipy/stats/_probability_distribution.py | 2 +- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 8fbba4aa2191..3b0b3ec3ae4f 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -486,7 +486,7 @@ class _IntegerDomain(_SimpleDomain): def contains(self, item, parameter_values=None): super_contains = super().contains(item, parameter_values) integral = (item == np.round(item)) - return super_contains and integral + return super_contains & integral def __str__(self): a, b = self.endpoints @@ -3320,7 +3320,7 @@ def logintegrand(x, order, logcenter, **params): ### Convenience - def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): + def plot(self, x='x', y=None, *, t=None, ax=None): r"""Plot a function of the distribution. Convenience function for quick visualization of the distribution @@ -3396,14 +3396,24 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): # - when the parameters of the distribution are an array, # use the full range of abscissae for all curves + discrete = isinstance(self, DiscreteDistribution) t_is_quantile = {'x', 'icdf', 'iccdf', 'ilogcdf', 'ilogccdf'} t_is_probability = {'cdf', 'ccdf', 'logcdf', 'logccdf'} valid_t = t_is_quantile.union(t_is_probability) - valid_xy = valid_t.union({'pdf', 'logpdf'}) + valid_xy = valid_t.union({'pdf', 'logpdf', 'pmf', 'logpmf'}) + y_default = 'pmf' if discrete else 'pdf' + y = y_default if y is None else y ndim = self._ndim x_name, y_name = x, y - t_name, tlim = t[0], np.asarray(t[1:]) + t_name = 'cdf' if t is None else t[0] + + a, b = self.support() + tliml_default = 0 if np.all(np.isfinite(a)) else 0.0005 + tliml = tliml_default if t is None else t[1] + tlimr_default = 1 if np.all(np.isfinite(b)) else 0.9995 + tlimr = tlimr_default if t is None else t[2] + tlim = np.asarray([tliml, tlimr]) tlim = tlim[:, np.newaxis] if ndim else tlim # pdf/logpdf are not valid for `t` because we can't easily invert them @@ -3419,7 +3429,7 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): message = (f'Argument `y` of `{self.__class__.__name__}.plot` "' f'must be one of {valid_xy}') - if t_name not in valid_xy: + if y_name not in valid_xy: raise ValueError(message) # This could just be a warning @@ -3452,16 +3462,32 @@ def plot(self, x='x', y='pdf', *, t=('cdf', 0.0005, 0.9995), ax=None): raise ValueError(message) # form quantile grid - grid = np.linspace(0, 1, 300) - grid = grid[:, np.newaxis] if ndim else grid - q = qlim[0] + (qlim[1] - qlim[0]) * grid + if discrete and x_name in t_is_quantile: + # should probably aggregate for large ranges + q = np.arange(np.min(qlim[0]), np.max(qlim[1]) + 1) + q = q[:, np.newaxis] if ndim else q + else: + grid = np.linspace(0, 1, 300) + grid = grid[:, np.newaxis] if ndim else grid + q = qlim[0] + (qlim[1] - qlim[0]) * grid + q = np.round(q) if discrete else q # compute requested x and y at quantile grid x = q if x_name in t_is_quantile else getattr(self, x_name)(q) y = q if y_name in t_is_quantile else getattr(self, y_name)(q) # make plot - ax.plot(x, y) + x, y = np.broadcast_arrays(x.T, np.atleast_2d(y.T)) + for xi, yi in zip(x, y): # plot is vectorized, but bar/step don't seem to be + if discrete and x_name in t_is_quantile and y_name == 'pmf': + # should this just be a step plot, too? + ax.bar(xi, yi, alpha=np.sqrt(1/y.shape[0])) # alpha heuristic + elif discrete and x_name in t_is_quantile: + values = yi + edges = np.concatenate((xi, [xi[-1]+1])) + ax.stairs(values, edges, baseline=None) + else: + ax.plot(xi, yi) ax.set_xlabel(f"${x_name}$") ax.set_ylabel(f"${y_name}$") ax.set_title(str(self)) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 40ff9ab41a77..1cdb5f3418ce 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -365,7 +365,7 @@ def mean(self, *, method): @abstractmethod def median(self, *, method): - r"""Median (50th percentil) + r"""Median (50th percentile) If a continuous random variable :math:`X` has probability :math:`0.5` of taking on a value less than :math:`m`, then :math:`m` is the median. From 73e19274ec200d1f943553b6fe5c50eedb48152d Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 11 Jan 2025 12:39:12 -0800 Subject: [PATCH 008/251] MAINT: DiscreteDistribution.i_: fix inverse method edge cases --- scipy/stats/_distribution_infrastructure.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 3b0b3ec3ae4f..7d3adb8c01ad 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2040,7 +2040,8 @@ def _quadrature(self, integrand, limits=None, args=None, return res.integral else: res = np.asarray(nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum) - res[a > b] = 0 # nsum should probably return 0 instead of nan + zero = -np.inf if log else 0 + res[a > b] = zero # nsum should probably return 0 instead of nan return res[()] def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): @@ -3575,6 +3576,12 @@ def _pxf_dispatch(self, x, *, method=None, **params): def _logpxf_dispatch(self, x, *, method=None, **params): return self._logpmf_dispatch(x, method=method, **params) + def _cdf_quadrature(self, x, **params): + return super()._cdf_quadrature(np.floor(x), **params) + + def _logcdf_quadrature(self, x, **params): + return super()._logcdf_quadrature(np.floor(x), **params) + def _ccdf_quadrature(self, x, **params): return super()._ccdf_quadrature(np.floor(x + 1), **params) @@ -3583,19 +3590,19 @@ def _logccdf_quadrature(self, x, **params): def _icdf_inversion(self, x, **params): res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl == 0, np.floor(res.xl), np.floor(res.xr)) + return np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) def _ilogcdf_inversion(self, x, **params): res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl == 0, np.floor(res.xl), np.floor(res.xr)) + return np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) def _iccdf_inversion(self, x, **params): - res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=0.9) - return np.where(res.fr == 0, np.ceil(res.xr), np.ceil(res.xl)) + res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=1) + return np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) def _ilogccdf_inversion(self, x, **params): res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=1) - return np.where(res.fr == 0, np.ceil(res.xr), np.ceil(res.xl)) + return np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) # For initial PR, leave these out From 1fbbc49acad6be15a76d40a3325481fcc346c073 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 11 Jan 2025 15:07:31 -0800 Subject: [PATCH 009/251] ENH: _ProbabilityDistribution: update considering discrete RVs --- scipy/stats/_distribution_infrastructure.py | 50 ++++-- scipy/stats/_probability_distribution.py | 175 ++++++++++++++------ 2 files changed, 164 insertions(+), 61 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 7d3adb8c01ad..6cb8d87521d8 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1355,9 +1355,9 @@ def _generate_example(dist_family): instantiation = f"{name}()" X = dist - p = 0.32 - x = round(X.icdf(p), 2) - y = round(X.icdf(2 * p), 2) + prob = 0.32 + x = round(X.icdf(prob), 2) + y = round(X.icdf(2 * prob), 2) example = f""" To use the distribution class, it must be instantiated using keyword @@ -1392,19 +1392,22 @@ def _generate_example(dist_family): """ example += f""" - To evaluate the probability density function of the underlying distribution + To evaluate the probability density/mass function of the underlying distribution at argument ``x={x}``: >>> x = {x} - >>> X.pdf(x) - {X.pdf(x)} - + >>> X.pdf(x), X.pmf(x) + {X.pdf(x), X.pmf(x)} + The cumulative distribution function, its complement, and the logarithm of these functions are evaluated similarly. >>> np.allclose(np.exp(X.logccdf(x)), 1 - X.cdf(x)) True + """ + if issubclass(dist_family, ContinuousDistribution): + example_continuous = f""" The inverse of these functions with respect to the argument ``x`` is also available. @@ -1420,22 +1423,39 @@ def _generate_example(dist_family): >>> y = {y} >>> np.allclose(X.ccdf(x, y), 1 - (X.cdf(y) - X.cdf(x))) True + """ + example += example_continuous + example += f""" There are methods for computing measures of central tendency, dispersion, higher moments, and entropy. >>> X.mean(), X.median(), X.mode() {X.mean(), X.median(), X.mode()} + >>> X.variance(), X.standard_deviation() {X.variance(), X.standard_deviation()} + >>> X.skewness(), X.kurtosis() {X.skewness(), X.kurtosis()} + >>> np.allclose(X.moment(order=6, kind='standardized'), ... X.moment(order=6, kind='central') / X.variance()**3) True + """ + + if issubclass(dist_family, ContinuousDistribution): + example += f""" >>> np.allclose(np.exp(X.logentropy()), X.entropy()) True + """ + else: + example += f""" + >>> X.entropy() + {X.entropy()} + """ + example += f""" Pseudo-random samples can be drawn from the underlying distribution using ``sample``. @@ -3332,14 +3352,18 @@ def plot(self, x='x', y=None, *, t=None, ax=None): x, y : str, optional String indicating the quantities to be used as the abscissa and ordinate (horizontal and vertical coordinates), respectively. - Defaults are ``'x'`` (the domain of the random variable) and - ``'pdf'`` (the probability density function). Valid values are: - 'x', 'pdf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logcdf', - 'logccdf', 'ilogcdf', 'ilogccdf'. + Defaults are ``'x'`` (the domain of the random variable) and either + ``'pdf'`` (the probability density function) (continuous) or + ``'pdf'`` (the probability density function) (discrete). + Valid values are: + 'x', 'pdf', 'pmf', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logpdf', 'logpmf', + 'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'. t : 3-tuple of (str, float, float), optional Tuple indicating the limits within which the quantities are plotted. - Default is ``('cdf', 0.001, 0.999)`` indicating that the central - 99.9% of the distribution is to be shown. Valid values are: + The default is ``('cdf', 0.0005, 0.9995)`` if the domain is infinite, + indicating that the central 99.9% of the distribution is to be shown; + otherwise, endpoints of the support are used where they are finite. + Valid values are: 'x', 'cdf', 'ccdf', 'icdf', 'iccdf', 'logcdf', 'logccdf', 'ilogcdf', 'ilogccdf'. ax : `matplotlib.axes`, optional diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 1cdb5f3418ce..f74adbc461c5 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -30,8 +30,8 @@ def support(self): Notes ----- Suppose a continuous probability distribution has support ``(l, r)``. - The following table summarizes the value returned by methods - of ``ContinuousDistribution`` for arguments outside the support. + The following table summarizes the value returned by several + methods when the argument is outside the support. +----------------+---------------------+---------------------+ | Method | Value for ``x < l`` | Value for ``x > r`` | @@ -49,13 +49,16 @@ def support(self): | ``logccdf(x)`` | 0 | -inf | +----------------+---------------------+---------------------+ - For the ``cdf`` and related methods, the inequality need not be - strict; i.e. the tabulated value is returned when the method is - evaluated *at* the corresponding boundary. + For discrete distributions, the same table is applicable with + ``pmf`` and ``logpmf`` substituted for ``pdf`` and ``logpdf``. + + For the ``cdf`` and related methods of continuous distributions, the + inequality need not be strict; i.e. the tabulated value is returned + when the method is evaluated *at* the corresponding boundary. The following table summarizes the value returned by the inverse - methods of ``ContinuousDistribution`` for arguments at the boundaries - of the domain ``0`` to ``1``. + methods for arguments ``0`` and ``1``, whether the distribution + is continuous or discrete. +-------------+-----------+-----------+ | Method | ``x = 0`` | ``x = 1`` | @@ -65,7 +68,7 @@ def support(self): | ``icdf(x)`` | ``r`` | ``l`` | +-------------+-----------+-----------+ - For the inverse log-functions, the same values are returned for + For the inverse log-functions, the same values are returned for ``x = log(0)`` and ``x = log(1)``. All inverse functions return ``nan`` when evaluated at an argument outside the domain ``0`` to ``1``. @@ -173,7 +176,7 @@ def moment(self, order, kind, *, method): In terms of probability density function :math:`f(x)` and support :math:`\chi`, the "raw" moment (about the origin) of order :math:`n` of - a random variable :math:`X` is: + a continuous random variable :math:`X` is: .. math:: @@ -195,6 +198,9 @@ def moment(self, order, kind, *, method): \tilde{\mu}_n(X) = \frac{\mu_n(X)} {\sigma^n} + The definitions for discrete random variables are analogous, with + sums over the support replacing the integrals. + Parameters ---------- order : int @@ -369,11 +375,15 @@ def median(self, *, method): If a continuous random variable :math:`X` has probability :math:`0.5` of taking on a value less than :math:`m`, then :math:`m` is the median. - That is, the median is the value :math:`m` for which: + + More generally, a median is a value :math:`m` for which: .. math:: - P(X ≤ m) = 0.5 = P(X ≥ m) + P(X ≤ m) ≤ 0.5 ≥ P(X ≥ m) + + For discrete random variables, the median may not be unique, in which + case the smallest value satisfying the definition is reported. Parameters ---------- @@ -740,6 +750,8 @@ def pdf(self, x, /, *, method): :math:`1`; since the valus is a probability *density*, only its integral over the support must equal :math:`1`. + For discrete distributions, `pdf` returns ``inf`` at supported points. + References ---------- .. [1] Probability density function, *Wikipedia*, @@ -823,6 +835,8 @@ def logpdf(self, x, /, *, method): to work with the logarithms of probabilities and probability densities to avoid underflow. + For discrete distributions, `pdf` returns ``inf`` at supported points. + References ---------- .. [1] Probability density function, *Wikipedia*, @@ -849,7 +863,7 @@ def logpdf(self, x, /, *, method): def pmf(self, x, /, *, method=None): r"""Probability mass function - The probability density function ("PMF"), denoted :math:`f(x)`, is the + The probability mass function ("PMF"), denoted :math:`f(x)`, is the probability that the random variable :math:`X` will assume the value :math:`x`. .. math:: @@ -886,10 +900,13 @@ def pmf(self, x, /, *, method=None): Notes ----- - Suppose a discrete probability distribution has support :math:`[l, r]`. + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. By definition of the support, the PMF evaluates to its minimum value - of :math:`0` outside the support; i.e. for :math:`x < l` or - :math:`x > r`. + of :math:`0` for non-integral :math:`x` and for :math:`x` outside the support; + i.e. for :math:`x < l` or :math:`x > r`. + + For continuous distributions, `pmf` returns ``0`` at all real arguments. References ---------- @@ -914,14 +931,14 @@ def pmf(self, x, /, *, method=None): def logpmf(self, x, /, *, method=None): r"""Log of the probability mass function - The probability density function ("PMF"), denoted :math:`f(x)`, is the + The probability mass function ("PMF"), denoted :math:`f(x)`, is the probability that the random variable :math:`X` will assume the value :math:`x`. .. math:: f(x) = \frac{d}{dx} F(x) - `logpmf` computes the logarithm of the probability density function + `logpmf` computes the logarithm of the probability mass function ("log-PMF"), :math:`\log(f(x))`, but it may be numerically favorable compared to the naive implementation (computing :math:`f(x)` and taking the logarithm). @@ -956,10 +973,11 @@ def logpmf(self, x, /, *, method=None): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. + Suppose a discrete probability distribution has support over the integers + :math:`{l, l+1, ..., r-1, r}`. By definition of the support, the log-PMF evaluates to its minimum value - of :math:`-\infty` (i.e. :math:`\log(0)`) outside the support; i.e. for - :math:`x < l` or :math:`x > r`. + of :math:`-\infty` (i.e. :math:`\log(0)`) for non-integral :math:`x` and + for :math:`x` outside the support; i.e. for :math:`x < l` or :math:`x > r`. For distributions with infinite support, it is common for `pmf` to return a value of ``0`` when the argument is theoretically within the support; @@ -1003,20 +1021,22 @@ def cdf(self, x, y, /, *, method): F(x) = P(X ≤ x) - A two-argument variant of this function is also defined as the - probability the random variable :math:`X` will assume a value between - :math:`x` and :math:`y`. + A two-argument variant of this function is also defined for continuous + random variables as the probability the random variable :math:`X` will + assume a value between :math:`x` and :math:`y`. .. math:: F(x, y) = P(x ≤ X ≤ y) - `cdf` accepts `x` for :math:`x` and `y` for :math:`y`. + `cdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous + random variables). Parameters ---------- x, y : array_like - The arguments of the CDF. `x` is required; `y` is optional. + The arguments of the CDF. `x` is required; `y` is optional for continuous + random variables and not accepted for discrete random variables. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} The strategy used to evaluate the CDF. By default (``None``), the one-argument form of the function @@ -1065,6 +1085,17 @@ def cdf(self, x, y, /, *, method): The CDF evaluates to its minimum value of :math:`0` for :math:`x ≤ l` and its maximum value of :math:`1` for :math:`x ≥ r`. + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CDF :math:`F(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + F(x) = \sum_{u=l}^{u=\lfloor x \rfloor} f(u) + + The CDF evaluates to its minimum value of :math:`0` for :math:`x < l` + and its maximum value of :math:`1` for :math:`x ≥ r`. + The CDF is also known simply as the "distribution function". References @@ -1096,14 +1127,22 @@ def cdf(self, x, y, /, *, method): def icdf(self, p, /, *, method): r"""Inverse of the cumulative distribution function. - The inverse of the cumulative distribution function ("inverse CDF"), - denoted :math:`F^{-1}(p)`, is the argument :math:`x` for which the - cumulative distribution function :math:`F(x)` evaluates to :math:`p`. + For monotonic continuous distributions, the inverse of the cumulative + distribution function ("inverse CDF"), denoted :math:`F^{-1}(p)`, is the + argument :math:`x` for which the cumulative distribution function + :math:`F(x)` evaluates to :math:`p`. .. math:: F^{-1}(p) = x \quad \text{s.t.} \quad F(x) = p + When a strict "inverse" of the cumulative distribution function does not + exist (e.g. discrete random variables), the "inverse CDF" is defined by + convention as the smallest value within the support :math:`\chi` for which + :math:`F(x)` is at least :math:`p`. + + F^{-1}(p) = \min_\chi \quad \text{s.t.} \quad F(x) ≥ p + `icdf` accepts `p` for :math:`p \in [0, 1]`. Parameters @@ -1137,7 +1176,7 @@ def icdf(self, p, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse CDF returns its minimum value of :math:`l` at :math:`p = 0` and its maximum value of :math:`r` at :math:`p = 1`. Because the CDF has range :math:`[0, 1]`, the inverse CDF is only defined on the @@ -1188,18 +1227,21 @@ def ccdf(self, x, y, /, *, method): G(x) = 1 - F(x) = P(X > x) - A two-argument variant of this function is: + A two-argument variant of this function, available only for continuous + random variables, is: .. math:: G(x, y) = 1 - F(x, y) = P(X < x \text{ or } X > y) - `ccdf` accepts `x` for :math:`x` and `y` for :math:`y`. + `ccdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous + random variables). Parameters ---------- x, y : array_like - The arguments of the CCDF. `x` is required; `y` is optional. + The arguments of the CCDF. `x` is required; `y` is optional for continuous + random variables and not accepted for discrete random variables. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} The strategy used to evaluate the CCDF. By default (``None``), the infrastructure chooses between the @@ -1248,6 +1290,18 @@ def ccdf(self, x, y, /, *, method): The CCDF returns its minimum value of :math:`0` for :math:`x ≥ r` and its maximum value of :math:`1` for :math:`x ≤ l`. + Suppose a discrete probability distribution has support :math:`[l, r]`. + The CCDF :math:`G(x)` is related to the probability mass function + :math:`f(x)` by: + + .. math:: + + G(x) = \int_x^r f(u) du + G(x) = \sum_{u=\lfloor x + 1 \rfloor}^{u=r} f(u) + + The CCDF evaluates to its minimum value of :math:`0` for :math:`x ≥ r` + and its maximum value of :math:`1` for :math:`x < l`. + The CCDF is also known as the "survival function". References @@ -1291,6 +1345,13 @@ def iccdf(self, p, /, *, method): G^{-1}(p) = x \quad \text{s.t.} \quad G(x) = p + When a strict "inverse" of the complementary cumulative distribution function + does not exist (e.g. discrete random variables), the "inverse CCDF" is defined + by convention as the smallest value within the support :math:`\chi` for which + :math:`G(x)` is no greater than :math:`p`. + + G^{-1}(p) = \min_\chi \quad \text{s.t.} \quad G(x) ≤ p + `iccdf` accepts `p` for :math:`p \in [0, 1]`. Parameters @@ -1319,7 +1380,7 @@ def iccdf(self, p, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse CCDF returns its minimum value of :math:`l` at :math:`p = 1` and its maximum value of :math:`r` at :math:`p = 0`. Because the CCDF has range :math:`[0, 1]`, the inverse CCDF is only defined on the @@ -1366,9 +1427,9 @@ def logcdf(self, x, y, /, *, method): F(x) = P(X ≤ x) - A two-argument variant of this function is also defined as the - probability the random variable :math:`X` will assume a value between - :math:`x` and :math:`y`. + A two-argument variant of this function is also defined for continuous + random variables as the probability the random variable :math:`X` will + assume a value between :math:`x` and :math:`y`. .. math:: @@ -1379,12 +1440,14 @@ def logcdf(self, x, y, /, *, method): numerically favorable compared to the naive implementation (computing the CDF and taking the logarithm). - `logcdf` accepts `x` for :math:`x` and `y` for :math:`y`. + `logcdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous + random variables). Parameters ---------- x, y : array_like - The arguments of the log-CDF. `x` is required; `y` is optional. + The arguments of the log-CDF. `x` is required; `y` is optional for + continuous random variables and not accepted for discrete random variables. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} The strategy used to evaluate the log-CDF. By default (``None``), the one-argument form of the function @@ -1420,7 +1483,8 @@ def logcdf(self, x, y, /, *, method): Suppose a continuous probability distribution has support :math:`[l, r]`. The log-CDF evaluates to its minimum value of :math:`\log(0) = -\infty` for :math:`x ≤ l` and its maximum value of :math:`\log(1) = 0` for - :math:`x ≥ r`. + :math:`x ≥ r`. An analogous statement can be made for discrete distributions, + but the inequality governing the minimum value is strict. For distributions with infinite support, it is common for `cdf` to return a value of ``0`` when the argument @@ -1513,7 +1577,7 @@ def ilogcdf(self, logp, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. + Suppose a probability distribution has support :math:`[l, r]`. The inverse log-CDF returns its minimum value of :math:`l` at :math:`\log(p) = \log(0) = -\infty` and its maximum value of :math:`r` at :math:`\log(p) = \log(1) = 0`. Because the log-CDF has range @@ -1563,7 +1627,8 @@ def logccdf(self, x, y, /, *, method): G(x) = 1 - F(x) = P(X > x) - A two-argument variant of this function is: + A two-argument variant of this function, defined only for continuous + random variables, is: .. math:: @@ -1574,12 +1639,14 @@ def logccdf(self, x, y, /, *, method): but it may be numerically favorable compared to the naive implementation (computing the CDF and taking the logarithm). - `logccdf` accepts `x` for :math:`x` and `y` for :math:`y`. + `logccdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous + random variables). Parameters ---------- x, y : array_like - The arguments of the log-CCDF. `x` is required; `y` is optional. + The arguments of the log-CCDF. `x` is required; `y` is optional for + continuous random variables and not accepted for discrete random variables. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} The strategy used to evaluate the log-CCDF. By default (``None``), the one-argument form of the function @@ -1616,7 +1683,8 @@ def logccdf(self, x, y, /, *, method): Suppose a continuous probability distribution has support :math:`[l, r]`. The log-CCDF returns its minimum value of :math:`\log(0)=-\infty` for :math:`x ≥ r` and its maximum value of :math:`\log(1) = 0` for - :math:`x ≤ l`. + :math:`x ≤ l`. An analogous statement can be made for discrete distributions, + but the inequality governing the maximum value is strict. For distributions with infinite support, it is common for `ccdf` to return a value of ``0`` when the argument @@ -1699,7 +1767,7 @@ def ilogccdf(self, logp, /, *, method): Notes ----- - Suppose a continuous probability distribution has support :math:`[l, r]`. The + Suppose a probability distribution has support :math:`[l, r]`. The inverse log-CCDF returns its minimum value of :math:`l` at :math:`\log(p) = \log(1) = 0` and its maximum value of :math:`r` at :math:`\log(p) = \log(0) = -\infty`. Because the log-CCDF has range @@ -1746,13 +1814,16 @@ def logentropy(self, *, method): r"""Logarithm of the differential entropy In terms of probability density function :math:`f(x)` and support - :math:`\chi`, the differential entropy (or simply "entropy") of a random - variable :math:`X` is: + :math:`\chi`, the differential entropy (or simply "entropy") of a + continuous random variable :math:`X` is: .. math:: h(X) = - \int_{\chi} f(x) \log f(x) dx + The definition for a discrete random variable is analogous, with a + sum over the support replacing the integral. + `logentropy` computes the logarithm of the differential entropy ("log-entropy"), :math:`log(h(X))`, but it may be numerically favorable compared to the naive implementation (computing :math:`h(X)` then @@ -1779,6 +1850,11 @@ def logentropy(self, *, method): out : array The log-entropy. + Raises + ------ + NotImplementedError + If the random variable is discrete. + See Also -------- entropy @@ -1820,7 +1896,7 @@ def logentropy(self, *, method): """ raise NotImplementedError() - + @abstractmethod def entropy(self, *, method): r"""Differential entropy @@ -1833,6 +1909,9 @@ def entropy(self, *, method): h(X) = - \int_{\chi} f(x) \log f(x) dx + The definition for a discrete random variable is analogous, with a + sum over the support replacing the integral. + Parameters ---------- method : {None, 'formula', 'logexp', 'quadrature'} From 9b80644474b3060353033d0948a65472948c4ee3 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 11 Jan 2025 15:21:36 -0800 Subject: [PATCH 010/251] ENH: DiscreteDistribution: adjust edge cases --- scipy/stats/_distribution_infrastructure.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 6cb8d87521d8..b5edbcbb9865 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -948,7 +948,7 @@ def filtered(self, x, *args, **kwargs): # Check for non-integral arguments to PMF method any_non_integral = False if method_name in replace_non_integral: - mask_non_integral = x != np.floor(x) + mask_non_integral = (x != np.floor(x)) & ~np.isnan(x) any_non_integral = (mask_non_integral if mask_non_integral.shape == () else np.any(mask_non_integral)) @@ -3614,19 +3614,27 @@ def _logccdf_quadrature(self, x, **params): def _icdf_inversion(self, x, **params): res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + res = np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + res[np.isnan(x)] = np.nan + return res def _ilogcdf_inversion(self, x, **params): res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + res = np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + res[np.isnan(x)] = np.nan + return res def _iccdf_inversion(self, x, **params): res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res = np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res[np.isnan(x)] = np.nan + return res def _ilogccdf_inversion(self, x, **params): res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=1) - return np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res = np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res[np.isnan(x)] = np.nan + return res # For initial PR, leave these out From 8bb18afda9fd258918cbf1b2e561b85cdece82fb Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 11 Jan 2025 22:04:15 -0800 Subject: [PATCH 011/251] MAINT: DiscreteDistribution.mode: return integer mode --- scipy/stats/_distribution_infrastructure.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index b5edbcbb9865..f16ba29d9f93 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2328,7 +2328,7 @@ def _mode_dispatch(self, method=None, **params): def _mode_formula(self, **params): raise NotImplementedError(self._not_implemented) - def _mode_optimization(self, **params): + def _mode_optimization(self, xatol=None, **params): if not self._size: return np.empty(self._shape, dtype=self._dtype) @@ -2338,7 +2338,8 @@ def _mode_optimization(self, **params): f, args = _kwargs2args(lambda x, **params: -self._pxf_dispatch(x, **params), args=(), kwargs=params) res_b = _bracket_minimum(f, m, xmin=a, xmax=b, args=args) - res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr, args=args) + res = _chandrupatla_minimize(f, res_b.xl, res_b.xm, res_b.xr, + args=args, xatol=xatol) mode = np.asarray(res.x) mode_at_boundary = res_b.status == -1 mode_at_left = mode_at_boundary & (res_b.fl <= res_b.fm) @@ -3636,6 +3637,20 @@ def _ilogccdf_inversion(self, x, **params): res[np.isnan(x)] = np.nan return res + def _mode_optimization(self, **params): + # If `x` is the true mode of a unimodal continuous function, we can find + # the mode among the integers by rounding in each direction and checking + # which is better. I think `xatol` should be able to be 1 if the documented + # convergence criterion were implemented, but the actual convergence + # criterion is different, and I'm not sure if it controls the total width + # of the bracket. `xatol=0.1` might not be safe as-implemented. + x = super()._mode_optimization(xatol=0.1, **params) + xl, xr = np.floor(x), np.ceil(x) + fl, fr = self._pmf_dispatch(xl, **params), self._pmf_dispatch(xr, **params) + mode = np.asarray(xl) + mode[fr > fl] = xr + return mode + # For initial PR, leave these out def _cdf2_dispatch(self, x, y, *, method=None, **params): From 89680e5249b9ac88982440e7455276674460374b Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 10:03:37 -0800 Subject: [PATCH 012/251] MAINT: stats.DiscreteDistribution: avoid NaNs/warnings in entropy and nsum --- scipy/integrate/_tanhsinh.py | 2 +- scipy/stats/_distribution_infrastructure.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scipy/integrate/_tanhsinh.py b/scipy/integrate/_tanhsinh.py index d0b303ef690f..c8ce4beb2d6d 100644 --- a/scipy/integrate/_tanhsinh.py +++ b/scipy/integrate/_tanhsinh.py @@ -1308,7 +1308,7 @@ def _integral_bound(f, a, b, step, args, constants, xp): tol = special.logsumexp(xp.stack((tol, rtol + lb.integral)), axis=0) else: tol = tol + rtol*lb.integral - i_skip = lb.status < 0 # avoid unnecessary f_evals if integral is divergent + i_skip = lb.status == -3 # avoid unnecessary f_evals if integral is divergent tol[i_skip] = xp.nan status = lb.status diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index f16ba29d9f93..3b440b424374 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2290,7 +2290,10 @@ def _entropy_quadrature(self, **params): def integrand(x, **params): pxf = self._pxf_dispatch(x, **params) logpxf = self._logpxf_dispatch(x, **params) - return logpxf * pxf + temp = np.asarray(pxf) + i = (pxf != 0) # 0 * inf -> nan; should be 0 + temp[i] = pxf[i]*logpxf[i] + return temp return -self._quadrature(integrand, params=params) @_set_invalid_nan_property @@ -2457,7 +2460,8 @@ def _logpmf_formula(self, x, **params): raise NotImplementedError(self._not_implemented) def _logpmf_logexp(self, x, **params): - return np.log(self._pmf_dispatch(x, **params)) + with np.errstate(divide='ignore'): + return np.log(self._pmf_dispatch(x, **params)) @_set_invalid_nan def pmf(self, x, /, *, method=None): From 7f728dc2acae7b40cbb6c5100451fa011b72c6a5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 12:22:05 -0800 Subject: [PATCH 013/251] MAINT: stats.DiscreteDistribution: linting/adjustments for CI --- scipy/integrate/tests/test_tanhsinh.py | 4 +-- scipy/stats/_distribution_infrastructure.py | 27 ++++++++++++--------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/scipy/integrate/tests/test_tanhsinh.py b/scipy/integrate/tests/test_tanhsinh.py index 18f9cbedf48d..a7103c283a1c 100644 --- a/scipy/integrate/tests/test_tanhsinh.py +++ b/scipy/integrate/tests/test_tanhsinh.py @@ -1069,8 +1069,8 @@ def f(x): return 1 / x res = nsum(f, xp.asarray(0), xp.asarray(10), maxterms=0) - assert xp.isnan(res.sum) - assert xp.isnan(res.error) + assert xp.isinf(res.sum) + assert xp.isinf(res.error) assert res.status == -2 res = nsum(f, xp.asarray(0), xp.asarray(10), maxterms=1) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 3b440b424374..4869cad193f0 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -414,9 +414,8 @@ def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) z[~i] = max elif type_ == 'out': - # make this work for infinite bounds - z = min_nn - uniform(1, 5, size=shape) - zr = max_nn + uniform(1, 5, size=shape) + z = min_nn - uniform(1, 5, size=shape) # 1, 5 is arbitary; we just want + zr = max_nn + uniform(1, 5, size=shape) # some numbers outside domain i = rng.random(size=n) < 0.5 z[i] = zr[i] @@ -1355,9 +1354,9 @@ def _generate_example(dist_family): instantiation = f"{name}()" X = dist - prob = 0.32 - x = round(X.icdf(prob), 2) - y = round(X.icdf(2 * prob), 2) + p = 0.32 + x = round(X.icdf(p), 2) + y = round(X.icdf(2 * p), 2) # noqa: F841 example = f""" To use the distribution class, it must be instantiated using keyword @@ -1406,8 +1405,10 @@ def _generate_example(dist_family): True """ + # When two-arg CDF is implemented for DiscreteDistribution, consider removing + # the special-casing here. if issubclass(dist_family, ContinuousDistribution): - example_continuous = f""" + example_continuous = """ The inverse of these functions with respect to the argument ``x`` is also available. @@ -1444,8 +1445,9 @@ def _generate_example(dist_family): True """ + # When logentropy is implemented for DiscreteDistribution, remove special-casing if issubclass(dist_family, ContinuousDistribution): - example += f""" + example += """ >>> np.allclose(np.exp(X.logentropy()), X.entropy()) True """ @@ -2059,9 +2061,9 @@ def _quadrature(self, integrand, limits=None, args=None, res = _tanhsinh(f, a, b, args=args, log=log, rtol=rtol) return res.integral else: - res = np.asarray(nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum) - zero = -np.inf if log else 0 - res[a > b] = zero # nsum should probably return 0 instead of nan + res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum + res = np.asarray(res) + res[a > b] = -np.inf if log else 0 # fix in nsum? return res[()] def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): @@ -2114,7 +2116,8 @@ def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` xrtol = None if _isnull(self.tol) else self.tol xatol = None if xatol is None else xatol - return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, xrtol=xrtol, xatol=xatol) + tolerances = dict(xrtol=xrtol, xatol=xatol) + return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, **tolerances) ## Other From 5e95928c22737b7311e22e236881771dffbcb3f8 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 13:39:43 -0800 Subject: [PATCH 014/251] DOC: stats.DiscreteDistribution: fix doc issues, hopefully [docs only] --- scipy/stats/__init__.py | 1 + scipy/stats/_distribution_infrastructure.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index bb16ee20b20f..e3237ef3340c 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -471,6 +471,7 @@ make_distribution Normal Uniform + Binomial Mixture order_statistic truncate diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 4869cad193f0..1b51cc5afef5 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1288,7 +1288,7 @@ def _combine_docs(dist_family, *, include_examples=True): fields.remove('Examples') doc = ClassDoc(dist_family) - superdoc = ClassDoc(ContinuousDistribution) + superdoc = ClassDoc(_UnivariateDistribution) for field in fields: if field in {"Methods", "Attributes"}: doc[field] = superdoc[field] @@ -4626,8 +4626,6 @@ def order_statistic(X, /, *, r, n): return OrderStatisticDistribution(X, r=r, n=n) -# Someday, I'd like for Mixture to become a `_UnivariateDistribution`, but that -# day is not today. class Mixture(_ProbabilityDistribution): r"""Representation of a mixture distribution. From 6247fc769c949447bdf84d735613fc7e03755dab Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 15:53:55 -0800 Subject: [PATCH 015/251] DOC: stats.DiscreteDistribution: additional doc fixes [docs only] --- scipy/stats/_distribution_infrastructure.py | 4 +- scipy/stats/_probability_distribution.py | 53 ++++++++++++--------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 1b51cc5afef5..93f253a2ee8e 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1408,7 +1408,7 @@ def _generate_example(dist_family): # When two-arg CDF is implemented for DiscreteDistribution, consider removing # the special-casing here. if issubclass(dist_family, ContinuousDistribution): - example_continuous = """ + example_continuous = f""" The inverse of these functions with respect to the argument ``x`` is also available. @@ -4199,7 +4199,7 @@ def truncate(X, lb=-np.inf, ub=np.inf): Furthermore, `truncate` can be applied to any random variable: >>> Rayleigh = stats.make_distribution(stats.rayleigh) - >>> W = stats.truncate(Rayleigh(), lb=0, ub=3) + >>> W = stats.truncate(Rayleigh(), lb=0.5, ub=3) >>> W.plot() >>> plt.show() diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index f74adbc461c5..fad38f699440 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -223,7 +223,8 @@ def moment(self, order, kind, *, method): vice versa (see Notes) - ``'normalize'``: normalize a central moment to get a standardized or vice versa - - ``'quadrature'``: numerically integrate according to the definition + - ``'quadrature'``: numerically integrate (or, in the discrete case, sum) + according to the definition Not all `method` options are available for all orders, kinds, and distributions. If the selected `method` is not available, a @@ -438,8 +439,8 @@ def mode(self, *, method): Informally, the mode is a value that a random variable has the highest probability (density) of assuming. That is, the mode is the element of - the support :math:`\chi` that maximizes the probability density - function :math:`f(x)`: + the support :math:`\chi` that maximizes the probability density (or mass, + for discrete random variables) function :math:`f(x)`: .. math:: @@ -453,7 +454,7 @@ def mode(self, *, method): following options, listed in order of precedence. - ``'formula'``: use a formula for the median - - ``'optimization'``: numerically maximize the PDF + - ``'optimization'``: numerically maximize the PDF/PMF Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` @@ -475,7 +476,7 @@ def mode(self, *, method): For some distributions #. the mode is not unique (e.g. the uniform distribution); - #. the PDF has one or more singularities, and it is debateable whether + #. the PDF has one or more singularities, and it is debatable whether a singularity is considered to be in the domain and called the mode (e.g. the gamma distribution with shape parameter less than 1); and/or #. the probability density function may have one or more local maxima @@ -747,10 +748,11 @@ def pdf(self, x, /, *, method): By definition of the support, the PDF evaluates to its minimum value of :math:`0` outside the support; i.e. for :math:`x < l` or :math:`x > r`. The maximum of the PDF may be less than or greater than - :math:`1`; since the valus is a probability *density*, only its integral + :math:`1`; since the value is a probability *density*, only its integral over the support must equal :math:`1`. - For discrete distributions, `pdf` returns ``inf`` at supported points. + For discrete distributions, `pdf` returns ``inf`` at supported points + and ``0`` elsewhere. References ---------- @@ -835,7 +837,8 @@ def logpdf(self, x, /, *, method): to work with the logarithms of probabilities and probability densities to avoid underflow. - For discrete distributions, `pdf` returns ``inf`` at supported points. + For discrete distributions, `logpdf` returns ``inf`` at supported points and + ``-inf`` (``log(0)``) elsewhere. References ---------- @@ -996,6 +999,7 @@ def logpmf(self, x, /, *, method=None): -------- Instantiate a distribution with the desired parameters: + >>> import numpy as np >>> from scipy import stats >>> X = stats.Binomial(n=10, p=0.5) @@ -1045,7 +1049,8 @@ def cdf(self, x, y, /, *, method): - ``'formula'``: use a formula for the CDF itself - ``'logexp'``: evaluate the log-CDF and exponentiate - ``'complement'``: evaluate the CCDF and take the complement - - ``'quadrature'``: numerically integrate the PDF + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) In place of ``'complement'``, the two-argument form accepts: @@ -1091,7 +1096,7 @@ def cdf(self, x, y, /, *, method): .. math:: - F(x) = \sum_{u=l}^{u=\lfloor x \rfloor} f(u) + F(x) = \sum_{u=l}^{\lfloor x \rfloor} f(u) The CDF evaluates to its minimum value of :math:`0` for :math:`x < l` and its maximum value of :math:`1` for :math:`x ≥ r`. @@ -1250,7 +1255,8 @@ def ccdf(self, x, y, /, *, method): - ``'formula'``: use a formula for the CCDF itself - ``'logexp'``: evaluate the log-CCDF and exponentiate - ``'complement'``: evaluate the CDF and take the complement - - ``'quadrature'``: numerically integrate the PDF + - ``'quadrature'``: numerically integrate the PDF (or, in the discrete + case, sum the PMF) The two-argument form chooses between: @@ -1296,8 +1302,7 @@ def ccdf(self, x, y, /, *, method): .. math:: - G(x) = \int_x^r f(u) du - G(x) = \sum_{u=\lfloor x + 1 \rfloor}^{u=r} f(u) + G(x) = \sum_{u=\lfloor x + 1 \rfloor}^{r} f(u) The CCDF evaluates to its minimum value of :math:`0` for :math:`x ≥ r` and its maximum value of :math:`1` for :math:`x < l`. @@ -1457,7 +1462,8 @@ def logcdf(self, x, y, /, *, method): - ``'logexp'``: evaluate the CDF and take the logarithm - ``'complement'``: evaluate the log-CCDF and take the logarithmic complement (see Notes) - - ``'quadrature'``: numerically log-integrate the log-PDF + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) In place of ``'complement'``, the two-argument form accepts: @@ -1656,7 +1662,8 @@ def logccdf(self, x, y, /, *, method): - ``'logexp'``: evaluate the CCDF and take the logarithm - ``'complement'``: evaluate the log-CDF and take the logarithmic complement (see Notes) - - ``'quadrature'``: numerically log-integrate the log-PDF + - ``'quadrature'``: numerically log-integrate the log-PDF (or, in the + discrete case, log-sum the log-PMF) The two-argument form chooses between: @@ -1821,8 +1828,8 @@ def logentropy(self, *, method): h(X) = - \int_{\chi} f(x) \log f(x) dx - The definition for a discrete random variable is analogous, with a - sum over the support replacing the integral. + The definition for a discrete random variable is analogous, with the PMF + replacing the PDF and a sum over the support replacing the integral. `logentropy` computes the logarithm of the differential entropy ("log-entropy"), :math:`log(h(X))`, but it may be numerically favorable @@ -1838,8 +1845,8 @@ def logentropy(self, *, method): - ``'formula'``: use a formula for the log-entropy itself - ``'logexp'``: evaluate the entropy and take the logarithm - - ``'quadrature'``: numerically log-integrate the logarithm of the - entropy integrand + - ``'quadrature'``: numerically log-integrate (or, in the discrete + case, log-sum) the logarithm of the entropy integrand (summand) Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` @@ -1909,8 +1916,8 @@ def entropy(self, *, method): h(X) = - \int_{\chi} f(x) \log f(x) dx - The definition for a discrete random variable is analogous, with a - sum over the support replacing the integral. + The definition for a discrete random variable is analogous, with the + PMF replacing the PDF and a sum over the support replacing the integral. Parameters ---------- @@ -1921,7 +1928,9 @@ def entropy(self, *, method): - ``'formula'``: use a formula for the entropy itself - ``'logexp'``: evaluate the log-entropy and exponentiate - - ``'quadrature'``: use numerical integration + - ``'quadrature'``: numerically integrate (or, in the discrete + case, sum) the entropy integrand (summand) + Not all `method` options are available for all distributions. If the selected `method` is not available, a ``NotImplementedError`` From 7e52dde1d7df17e00382fb68b7ba38cfafa0278a Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 17:25:33 -0800 Subject: [PATCH 016/251] MAINT: stats: update transformed distributions for continuous only --- scipy/stats/_distribution_infrastructure.py | 31 +++++++++++++++++++++ scipy/stats/tests/test_continuous.py | 9 +++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 93f253a2ee8e..90b3813d9ce0 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -4009,6 +4009,9 @@ def wrapped(self, p, *args, loc, scale, sign, **kwargs): class TransformedDistribution(ContinuousDistribution): def __init__(self, X, /, *args, **kwargs): + if not isinstance(X, ContinuousDistribution): + message = "Transformations are only supported for continuous RVs." + raise NotImplementedError(message) self._copy_parameterization() self._variable = X._variable self._dist = X @@ -4306,6 +4309,26 @@ def _pdf_dispatch(self, x, *args, loc, scale, sign, **params): pdf = self._dist._pdf_dispatch(x, *args, **params) return pdf / np.abs(scale) + def _logpmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpmf = self._dist._logpmf_dispatch(x, *args, **params) + return logpmf - np.log(np.abs(scale)) + + def _pmf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pmf = self._dist._pmf_dispatch(x, *args, **params) + return pmf / np.abs(scale) + + def _logpxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + logpxf = self._dist._logpxf_dispatch(x, *args, **params) + return logpxf - np.log(np.abs(scale)) + + def _pxf_dispatch(self, x, *args, loc, scale, sign, **params): + x = self._transform(x, loc, scale) + pxf = self._dist._pxf_dispatch(x, *args, **params) + return pxf / np.abs(scale) + # Sorry about the magic. This is just a draft to show the behavior. @_shift_scale_distribution_function def _logcdf_dispatch(self, x, *, method=None, **params): @@ -4876,6 +4899,14 @@ def logpdf(self, x, /, *, method=None): self._raise_if_method(method) return self._logsum('logpdf', x) + def pmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._sum('pmf', x) + + def logpmf(self, x, /, *, method=None): + self._raise_if_method(method) + return self._logsum('logpmf', x) + def cdf(self, x, y=None, /, *, method=None): self._raise_if_method(method) args = (x,) if y is None else (x, y) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 3fccf119dd63..67fd471d900e 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1138,7 +1138,14 @@ def test_repr_str_docs(self): class TestTransforms: - # putting this at the top to hopefully avoid merge conflicts + def test_ContinuousDistribution_only(self): + X = stats.Binomial(n=10, p=0.5) + # This is applied at the top level TransformedDistribution, + # so testing one subclass is enough + message = "Transformations are only supported for continuous RVs." + with pytest.raises(NotImplementedError, match=message): + stats.exp(X) + def test_truncate(self): rng = np.random.default_rng(81345982345826) lb = rng.random((3, 1)) From b8683fc01cea3eb43d702fe936547f55ac5a0d09 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 17:49:34 -0800 Subject: [PATCH 017/251] ENH: stats.make_distribution: update for DiscreteDistribution [skip ci] --- scipy/stats/_distribution_infrastructure.py | 18 ++++++++++++------ scipy/stats/tests/test_continuous.py | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 90b3813d9ce0..9491aed6433e 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3814,8 +3814,8 @@ class or its methods for more information. if dist in {stats.levy_stable, stats.vonmises}: raise NotImplementedError(f"`{dist.name}` is not supported.") - if not isinstance(dist, stats.rv_continuous): - message = "The argument must be an instance of `rv_continuous`." + if not isinstance(dist, (stats.rv_continuous, stats.rv_discrete)): + message = "Argument must be an instance of `rv_continuous` or `rv_discrete`." raise ValueError(message) parameters = [] @@ -3832,8 +3832,12 @@ class or its methods for more information. _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) + if isinstance(dist, stats.rv_continuous): + old_class, new_class = stats.rv_continuous, ContinuousDistribution + else: + old_class, new_class = stats.rv_discrete, DiscreteDistribution - class CustomDistribution(ContinuousDistribution): + class CustomDistribution(new_class): _parameterizations = ([_Parameterization(*parameters)] if parameters else []) _variable = _x_param @@ -3884,6 +3888,8 @@ def _moment_standard_formula(self, order, **kwargs): methods = {'_logpdf': '_logpdf_formula', '_pdf': '_pdf_formula', + '_logpmf': '_logpmf_formula', + '_pmf': '_pmf_formula', '_logcdf': '_logcdf_formula', '_cdf': '_cdf_formula', '_logsf': '_logccdf_formula', @@ -3901,14 +3907,14 @@ def _moment_standard_formula(self, order, **kwargs): continue # If method of old distribution overrides generic implementation... method = getattr(dist.__class__, old_method, None) - super_method = getattr(stats.rv_continuous, old_method, None) + super_method = getattr(old_class, old_method, None) if method is not super_method: # Make it an attribute of the new object with the new name setattr(CustomDistribution, new_method, getattr(dist, old_method)) def _overrides(method_name): return (getattr(dist.__class__, method_name, None) - is not getattr(stats.rv_continuous, method_name, None)) + is not getattr(old_class, method_name, None)) if _overrides('_get_support'): domain = CustomDistribution._variable.domain @@ -3929,7 +3935,7 @@ def _overrides(method_name): support_etc = _combine_docs(CustomDistribution, include_examples=False).lstrip() docs = [ f"This class represents `scipy.stats.{dist.name}` as a subclass of " - "`ContinuousDistribution`.", + f"`{new_class}`.", f"The `repr`/`str` of class instances is `{repr_str}`.", f"The PDF of the distribution is defined {support_etc}" ] diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 67fd471d900e..50ec89f9be9d 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1114,7 +1114,7 @@ def test_input_validation(self): with pytest.raises(NotImplementedError, match=message): stats.make_distribution(stats.vonmises) - message = "The argument must be an instance of `rv_continuous`." + message = "Argument must be an instance of `rv_continuous` or `rv_discrete`." with pytest.raises(ValueError, match=message): stats.make_distribution(object()) From 063fbc4f1821c1426d644948a5e260df60ebd7f7 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sun, 12 Jan 2025 18:49:11 -0800 Subject: [PATCH 018/251] ENH: stats.make_distribution: add support for discrete distributions --- scipy/stats/_discrete_distns.py | 2 +- scipy/stats/_distribution_infrastructure.py | 63 ++++++++++++++------- scipy/stats/tests/test_continuous.py | 33 +++++++---- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index 138c56d76a09..a4aedce4d475 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1056,7 +1056,7 @@ class planck_gen(rv_discrete): """ def _shape_info(self): - return [_ShapeInfo("lambda", False, (0, np.inf), (False, False))] + return [_ShapeInfo("lambda_", False, (0, np.inf), (False, False))] def _argcheck(self, lambda_): return lambda_ > 0 diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 9491aed6433e..347d59b2392b 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1288,7 +1288,7 @@ def _combine_docs(dist_family, *, include_examples=True): fields.remove('Examples') doc = ClassDoc(dist_family) - superdoc = ClassDoc(_UnivariateDistribution) + superdoc = ClassDoc(UnivariateDistribution) for field in fields: if field in {"Methods", "Attributes"}: doc[field] = superdoc[field] @@ -1470,7 +1470,7 @@ def _generate_example(dist_family): return example -class _UnivariateDistribution(_ProbabilityDistribution): +class UnivariateDistribution(_ProbabilityDistribution): r""" Class that represents a continuous statistical distribution. Parameters @@ -2135,7 +2135,7 @@ def _overrides(self, method_name): # For more complete discussion of the considerations, see: # https://github.com/scipy/scipy/pull/21050#discussion_r1707798901 method = getattr(self.__class__, method_name, None) - super_method = getattr(_UnivariateDistribution, method_name, None) + super_method = getattr(UnivariateDistribution, method_name, None) return method is not super_method ### Distribution properties @@ -3570,7 +3570,7 @@ def plot(self, x='x', y=None, *, t=None, ax=None): # these methods reasonably efficiently. -class ContinuousDistribution(_UnivariateDistribution): +class ContinuousDistribution(UnivariateDistribution): def _overrides(self, method_name): if method_name in {'_logpmf_formula', '_pmf_formula'}: return True @@ -3589,7 +3589,7 @@ def _logpxf_dispatch(self, x, *, method=None, **params): return self._logpdf_dispatch(x, method=method, **params) -class DiscreteDistribution(_UnivariateDistribution): +class DiscreteDistribution(UnivariateDistribution): def _overrides(self, method_name): if method_name in {'_logpdf_formula', '_pdf_formula'}: return True @@ -3683,6 +3683,7 @@ def _logentropy_dispatch(self, *, method=None, **params): # Special case the names of some new-style distributions in `make_distribution` _distribution_names = { + # Continuous 'argus': 'ARGUS', 'betaprime': 'BetaPrime', 'chi2': 'ChiSquared', @@ -3755,41 +3756,59 @@ def _logentropy_dispatch(self, *, method=None, **params): 'weibull_min': 'Weibull', 'weibull_max': 'ReflectedWeibull', 'wrapcauchy': 'WrappedCauchyLine', + # Discrete + 'betabinom': 'BetaBinomial', + 'betanbinom': 'BetaNegativeBinomial', + 'dlaplace': 'LaplaceDiscrete', + 'geom': 'Geometric', + 'hypergeom': 'Hypergeometric', + 'logser': 'LogarithmicSeries', + 'nbinom': 'NegativeBinomial', + 'nchypergeom_fisher': 'NoncentralHypergeometricFisher', + 'nchypergeom_wallenius': 'NoncentralHypergeometricWallenius', + 'nhypergeom': 'NegativeHypergeometric', + 'poisson_binom': 'PoissonBinomial', + 'randint': 'UniformDiscrete', + 'yulesimon': 'YuleSimon', + 'zipf': 'Zeta', } # beta, genextreme, gengamma, t, tukeylambda need work for 1D arrays def make_distribution(dist): - """Generate a `ContinuousDistribution` from an instance of `rv_continuous` + """Generate a `UnivariateDistribution` from an instance of `rv_generic` - The returned value is a `ContinuousDistribution` subclass. Like any subclass - of `ContinuousDistribution`, it must be instantiated (i.e. by passing all shape - parameters as keyword arguments) before use. Once instantiated, the resulting - object will have the same interface as any other instance of - `ContinuousDistribution`; e.g., `scipy.stats.Normal`. + The returned value is a `ContinuousDistribution` subclass if the input is an + instance of `rv_continuous` or a `DiscreteDistribution` subclass if the input + is an instance of `rv_discrete`. Like any subclass of `UnivariateDistribution`, + it must be instantiated (i.e. by passing all shape parameters as keyword + arguments) before use. Once instantiated, the resulting object will have the + same interface as any other instance of `UnivariateDistribution`; e.g., + `scipy.stats.Normal`, `scipy.stats.Binomial`. .. note:: `make_distribution` does not work perfectly with all instances of - `rv_continuous`. Known failures include `levy_stable` and `vonmises`, - and some methods of some distributions will not support array shape - parameters. + `rv_continuous`. Known failures include `levy_stable`, `vonmises`, + `hypergeom`, `nchypergeom_fisher`, `nchypergeom_wallenius`, + `poisson_binom`, and `zipian`; and some methods of some distributions + will not support array shape parameters. Parameters ---------- - dist : `rv_continuous` - Instance of `rv_continuous`. + dist : `rv_continuous` or `rv_discrete` + Instance of `rv_continuous` or `rv_discrete` Returns ------- - CustomDistribution : `ContinuousDistribution` - A subclass of `ContinuousDistribution` corresponding with `dist`. The + CustomDistribution : `UnivariateDistribution` + A subclass of `UnivariateDistribution` corresponding with `dist`. The initializer requires all shape parameters to be passed as keyword arguments - (using the same names as the instance of `rv_continuous`). + (using the same names as the instance of `rv_continuous`/`rv_discrete`). Notes ----- - The documentation of `ContinuousDistribution` is not rendered. See below for + The documentation of `UnivariateDistribution` is not rendered. See below for an example of how to instantiate the class (i.e. pass all shape parameters of `dist` to the initializer as keyword arguments). Documentation of all methods is identical to that of `scipy.stats.Normal`. Use ``help`` on the returned @@ -3811,7 +3830,9 @@ class or its methods for more information. >>> plt.show() """ - if dist in {stats.levy_stable, stats.vonmises}: + if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, + stats.nchypergeom_fisher, stats.nchypergeom_wallenius, + stats.poisson_binom, stats.zipfian}: raise NotImplementedError(f"`{dist.name}` is not supported.") if not isinstance(dist, (stats.rv_continuous, stats.rv_discrete)): diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 50ec89f9be9d..0e5612553619 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -13,7 +13,7 @@ from scipy.stats._fit import _kolmogorov_smirnov from scipy.stats._ksstats import kolmogn from scipy.stats import qmc -from scipy.stats._distr_params import distcont +from scipy.stats._distr_params import distcont, distdiscrete from scipy.stats._distribution_infrastructure import ( _Domain, _RealDomain, _Parameter, _Parameterization, _RealParameter, ContinuousDistribution, ShiftedScaledDistribution, _fiinfo, @@ -1031,19 +1031,25 @@ def test_shapes(self): class TestMakeDistribution: - @pytest.mark.parametrize('i, distdata', enumerate(distcont)) + @pytest.mark.parametrize('i, distdata', enumerate(distcont + distdiscrete)) def test_make_distribution(self, i, distdata): distname = distdata[0] slow = {'argus', 'exponpow', 'exponweib', 'genexpon', 'gompertz', 'halfgennorm', 'johnsonsb', 'kappa4', 'ksone', 'kstwo', 'kstwobign', 'powerlognorm', - 'powernorm', 'recipinvgauss', 'studentized_range', 'vonmises_line'} + 'powernorm', 'recipinvgauss', 'studentized_range', 'vonmises_line', + 'betanbinom', 'zipf', 'logser', 'skellam'} # discrete if not int(os.environ.get('SCIPY_XSLOW', '0')) and distname in slow: pytest.skip('Skipping as XSLOW') - if distname in { # skip these distributions - 'levy_stable', # private methods seem to require >= 1d args - 'vonmises', # circular distribution; shouldn't work + if distname in { # skip these distributions + 'levy_stable', # private methods seem to require >= 1d args + 'vonmises', # circular distribution; shouldn't work + 'poisson_binom', # vector shape parameter + 'hypergeom', # distribution functions need interpolation + 'zipfian', # distribution functions need interpolation + 'nchypergeom_fisher', # distribution functions don't accept NaN + 'nchypergeom_wallenius', # distribution functions don't accept NaN }: return @@ -1083,15 +1089,22 @@ def test_make_distribution(self, i, distdata): if distname not in skip_kurtosis: assert_allclose(X.kurtosis(convention='excess'), k, rtol=rtol, atol=atol) - assert_allclose(X.logpdf(x), Y.logpdf(x), rtol=rtol) - assert_allclose(X.pdf(x), Y.pdf(x), rtol=rtol) + if isinstance(dist, stats.rv_continuous): + assert_allclose(X.logpdf(x), Y.logpdf(x), rtol=rtol) + assert_allclose(X.pdf(x), Y.pdf(x), rtol=rtol) + else: + assert_allclose(X.logpmf(x), Y.logpmf(x), rtol=rtol) + assert_allclose(X.pmf(x), Y.pmf(x), rtol=rtol) assert_allclose(X.logcdf(x), Y.logcdf(x), rtol=rtol) assert_allclose(X.cdf(x), Y.cdf(x), rtol=rtol) if distname not in skip_logccdf: assert_allclose(X.logccdf(x), Y.logsf(x), rtol=rtol) assert_allclose(X.ccdf(x), Y.sf(x), rtol=rtol) - assert_allclose(X.icdf(p), Y.ppf(p), rtol=rtol) - assert_allclose(X.iccdf(p), Y.isf(p), rtol=rtol) + if isinstance(dist, stats.rv_continuous): + # For discrete distributions, these won't agree at the far left end + # of the support, and the new infrastructure is slow there (for now). + assert_allclose(X.icdf(p), Y.ppf(p), rtol=rtol) + assert_allclose(X.iccdf(p), Y.isf(p), rtol=rtol) for order in range(5): if distname not in skip_raw.get(order, {}): assert_allclose(X.moment(order, kind='raw'), From b8383c85682394358988ea290592bc857d08f13f Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Mon, 13 Jan 2025 22:02:58 -0800 Subject: [PATCH 019/251] DOC: stats.DiscreteDistribution: improve doc rendering [docs only] --- scipy/stats/_distribution_infrastructure.py | 2 +- scipy/stats/_probability_distribution.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 347d59b2392b..bfd975afeabf 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1306,7 +1306,7 @@ def _combine_docs(dist_family, *, include_examples=True): def _generate_domain_support(dist_family): n_parameterizations = len(dist_family._parameterizations) - domain = f"\nfor :math:`x` in {dist_family._variable.domain}.\n" + domain = f"\nfor :math:`x \\in {dist_family._variable.domain}`.\n" if n_parameterizations == 0: support = """ diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index fad38f699440..584e509fac4b 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -1146,6 +1146,8 @@ def icdf(self, p, /, *, method): convention as the smallest value within the support :math:`\chi` for which :math:`F(x)` is at least :math:`p`. + .. math:: + F^{-1}(p) = \min_\chi \quad \text{s.t.} \quad F(x) ≥ p `icdf` accepts `p` for :math:`p \in [0, 1]`. @@ -1355,6 +1357,8 @@ def iccdf(self, p, /, *, method): by convention as the smallest value within the support :math:`\chi` for which :math:`G(x)` is no greater than :math:`p`. + .. math:: + G^{-1}(p) = \min_\chi \quad \text{s.t.} \quad G(x) ≤ p `iccdf` accepts `p` for :math:`p \in [0, 1]`. @@ -1787,7 +1791,7 @@ def ilogccdf(self, logp, /, *, method): however, the *logarithm* of this resulting probability may be represented in floating point arithmetic, in which case this function may be used to find the argument of the CCDF for which the *logarithm* - of the resulting probability is `y = \log(p)`. + of the resulting probability is :math:`y = \log(p)`. The "logarithmic complement" of a number :math:`z` is mathematically equivalent to :math:`\log(1-\exp(z))`, but it is computed to avoid loss @@ -1832,7 +1836,7 @@ def logentropy(self, *, method): replacing the PDF and a sum over the support replacing the integral. `logentropy` computes the logarithm of the differential entropy - ("log-entropy"), :math:`log(h(X))`, but it may be numerically favorable + ("log-entropy"), :math:`\log(h(X))`, but it may be numerically favorable compared to the naive implementation (computing :math:`h(X)` then taking the logarithm). From 9b99cb12c84359e888ae598eaf54f2aae8066943 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 15 Jan 2025 11:04:03 -0800 Subject: [PATCH 020/251] ENH: DiscreteDistribution: add 2-arg cdf --- scipy/integrate/_tanhsinh.py | 4 ++ scipy/integrate/tests/test_tanhsinh.py | 18 +++++--- scipy/stats/_distribution_infrastructure.py | 50 +++++++++++++-------- scipy/stats/_probability_distribution.py | 42 +++++++---------- 4 files changed, 65 insertions(+), 49 deletions(-) diff --git a/scipy/integrate/_tanhsinh.py b/scipy/integrate/_tanhsinh.py index c8ce4beb2d6d..83c3e67173ac 100644 --- a/scipy/integrate/_tanhsinh.py +++ b/scipy/integrate/_tanhsinh.py @@ -1181,6 +1181,7 @@ def nsum(f, a, b, *, step=1, args=(), log=False, maxterms=int(2**20), tolerances # Branch for direct sum evaluation / integral approximation / invalid input i0 = ~valid_abstep # invalid + i0b = b < a # zero i1 = (nterms + 1 <= maxterms) & ~i0 # direct sum evaluation i2 = xp.isfinite(a) & ~i1 & ~i0 # infinite sum to the right i3 = xp.isfinite(b) & ~i2 & ~i1 & ~i0 # infinite sum to the left @@ -1190,6 +1191,9 @@ def nsum(f, a, b, *, step=1, args=(), log=False, maxterms=int(2**20), tolerances S[i0], E[i0] = xp.nan, xp.nan status[i0] = -1 + S[i0b], E[i0b] = zero, zero + status[i0b] = 0 + if xp.any(i1): args_direct = [arg[i1] for arg in args] tmp = _direct(f, a[i1], b[i1], step[i1], args_direct, constants, xp) diff --git a/scipy/integrate/tests/test_tanhsinh.py b/scipy/integrate/tests/test_tanhsinh.py index a7103c283a1c..fb366f9c2ea3 100644 --- a/scipy/integrate/tests/test_tanhsinh.py +++ b/scipy/integrate/tests/test_tanhsinh.py @@ -807,13 +807,21 @@ def test_input_validation(self, xp): with pytest.raises(ValueError, match=message): nsum(f, a, b, tolerances=dict(rtol=pytest)) - with np.errstate(all='ignore'): + with (np.errstate(all='ignore')): res = nsum(f, xp.asarray([np.nan, np.inf]), xp.asarray(1.)) - assert xp.all((res.status == -1) & xp.isnan(res.sum) - & xp.isnan(res.error) & ~res.success & res.nfev == 1) + assert (res.status[0] == -1) and not res.success[0] + assert xp.isnan(res.sum[0]) and xp.isnan(res.error[0]) + assert (res.status[1] == 0) and res.success[1] + assert res.sum[1] == res.error[1] + assert xp.all(res.nfev[0] == 1) + res = nsum(f, xp.asarray(10.), xp.asarray([np.nan, 1])) - assert xp.all((res.status == -1) & xp.isnan(res.sum) - & xp.isnan(res.error) & ~res.success & res.nfev == 1) + assert (res.status[0] == -1) and not res.success[0] + assert xp.isnan(res.sum[0]) and xp.isnan(res.error[0]) + assert (res.status[1] == 0) and res.success[1] + assert res.sum[1] == res.error[1] + assert xp.all(res.nfev[0] == 1) + res = nsum(f, xp.asarray(1.), xp.asarray(10.), step=xp.asarray([xp.nan, -xp.inf, xp.inf, -1, 0])) assert xp.all((res.status == -1) & xp.isnan(res.sum) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index bfd975afeabf..640291331f3c 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3620,6 +3620,38 @@ def _ccdf_quadrature(self, x, **params): def _logccdf_quadrature(self, x, **params): return super()._logccdf_quadrature(np.floor(x + 1), **params) + def _cdf2_quadrature(self, x, y, **params): + return super()._cdf2_quadrature(np.ceil(x), np.floor(y), **params) + + def _logcdf2_quadrature(self, x, y, **params): + return super()._logcdf2_quadrature(np.ceil(x), np.floor(y), **params) + + def _cdf2_subtraction(self, x, y, **params): + x_, y_ = np.floor(x), np.floor(y) + cdf_ymx = super()._cdf2_subtraction(x_, y_, **params) + pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) + return cdf_ymx + pmf_x + + def _logcdf2_subtraction(self, x, y, **params): + x_, y_ = np.floor(x), np.floor(y) + logcdf_ymx = super()._logcdf2_subtraction(x_, y_, **params) + logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) + return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) + + def _ccdf2_addition(self, x, y, **params): + a, _ = self._support(**params) + ccdf_y = self._ccdf_dispatch(y, **params) + _cdf, args = _kwargs2args(self._cdf_dispatch, kwargs=params) + cdf_xm1 = _lazywhere(x - 1 >= a, [x - 1] + args, _cdf, fillvalue=0) + return ccdf_y + cdf_xm1 + + def _logccdf2_addition(self, x, y, **params): + a, _ = self._support(**params) + logccdf_y = self._logccdf_dispatch(y, **params) + _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) + logcdf_xm1 = _lazywhere(x - 1 >= a, [x - 1] + args, _logcdf, fillvalue=-np.inf) + return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) + def _icdf_inversion(self, x, **params): res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=1) res = np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) @@ -3658,24 +3690,6 @@ def _mode_optimization(self, **params): mode[fr > fl] = xr return mode - # For initial PR, leave these out - - def _cdf2_dispatch(self, x, y, *, method=None, **params): - message = "Two-argument CDF is not available for discrete distributions." - raise NotImplementedError(message) - - def _logcdf2_dispatch(self, x, y, *, method=None, **params): - message = "Two-argument log-CDF is not available for discrete distributions." - raise NotImplementedError(message) - - def _ccdf2_dispatch(self, x, y, *, method=None, **params): - message = "Two-argument CCDF is not available for discrete distributions." - raise NotImplementedError(message) - - def _logccdf2_dispatch(self, x, y, *, method=None, **params): - message = "Two-argument log-CCDF is not available for discrete distributions." - raise NotImplementedError(message) - def _logentropy_dispatch(self, *, method=None, **params): message = "Log-entropy is not available for discrete distributions." raise NotImplementedError(message) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 584e509fac4b..400026bd1f21 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -1025,22 +1025,20 @@ def cdf(self, x, y, /, *, method): F(x) = P(X ≤ x) - A two-argument variant of this function is also defined for continuous - random variables as the probability the random variable :math:`X` will - assume a value between :math:`x` and :math:`y`. + A two-argument variant of this function is also defined as the + probability the random variable :math:`X` will assume a value between + :math:`x` and :math:`y`. .. math:: F(x, y) = P(x ≤ X ≤ y) - `cdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous - random variables). + `cdf` accepts `x` for :math:`x` and `y` for :math:`y`. Parameters ---------- x, y : array_like - The arguments of the CDF. `x` is required; `y` is optional for continuous - random variables and not accepted for discrete random variables. + The arguments of the CDF. `x` is required; `y` is optional. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} The strategy used to evaluate the CDF. By default (``None``), the one-argument form of the function @@ -1234,21 +1232,18 @@ def ccdf(self, x, y, /, *, method): G(x) = 1 - F(x) = P(X > x) - A two-argument variant of this function, available only for continuous - random variables, is: + A two-argument variant of this function is: .. math:: G(x, y) = 1 - F(x, y) = P(X < x \text{ or } X > y) - `ccdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous - random variables). + `ccdf` accepts `x` for :math:`x` and `y` for :math:`y`. Parameters ---------- x, y : array_like - The arguments of the CCDF. `x` is required; `y` is optional for continuous - random variables and not accepted for discrete random variables. + The arguments of the CCDF. `x` is required; `y` is optional. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} The strategy used to evaluate the CCDF. By default (``None``), the infrastructure chooses between the @@ -1436,9 +1431,9 @@ def logcdf(self, x, y, /, *, method): F(x) = P(X ≤ x) - A two-argument variant of this function is also defined for continuous - random variables as the probability the random variable :math:`X` will - assume a value between :math:`x` and :math:`y`. + A two-argument variant of this function is also defined as the + probability the random variable :math:`X` will assume a value between + :math:`x` and :math:`y`. .. math:: @@ -1449,14 +1444,12 @@ def logcdf(self, x, y, /, *, method): numerically favorable compared to the naive implementation (computing the CDF and taking the logarithm). - `logcdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous - random variables). + `logcdf` accepts `x` for :math:`x` and `y` for :math:`y`. Parameters ---------- x, y : array_like - The arguments of the log-CDF. `x` is required; `y` is optional for - continuous random variables and not accepted for discrete random variables. + The arguments of the log-CDF. `x` is required; `y` is optional. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'subtraction'} The strategy used to evaluate the log-CDF. By default (``None``), the one-argument form of the function @@ -1637,8 +1630,7 @@ def logccdf(self, x, y, /, *, method): G(x) = 1 - F(x) = P(X > x) - A two-argument variant of this function, defined only for continuous - random variables, is: + A two-argument variant of this function is: .. math:: @@ -1649,14 +1641,12 @@ def logccdf(self, x, y, /, *, method): but it may be numerically favorable compared to the naive implementation (computing the CDF and taking the logarithm). - `logccdf` accepts `x` for :math:`x` and `y` for :math:`y` (for continuous - random variables). + `logccdf` accepts `x` for :math:`x` and `y` for :math:`y`. Parameters ---------- x, y : array_like - The arguments of the log-CCDF. `x` is required; `y` is optional for - continuous random variables and not accepted for discrete random variables. + The arguments of the log-CCDF. `x` is required; `y` is optional. method : {None, 'formula', 'logexp', 'complement', 'quadrature', 'addition'} The strategy used to evaluate the log-CCDF. By default (``None``), the one-argument form of the function From 14c39ad039b2cf1387f92738c3cc9da48e66e6d9 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 15 Jan 2025 17:47:03 -0800 Subject: [PATCH 021/251] ENH: stats.DiscreteDistribution: implement logentropy --- scipy/stats/_distribution_infrastructure.py | 13 ++++++++++--- scipy/stats/_probability_distribution.py | 11 +++-------- scipy/stats/tests/test_continuous.py | 4 ++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 640291331f3c..5ddc1d60928a 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3690,9 +3690,16 @@ def _mode_optimization(self, **params): mode[fr > fl] = xr return mode - def _logentropy_dispatch(self, *, method=None, **params): - message = "Log-entropy is not available for discrete distributions." - raise NotImplementedError(message) + def _logentropy_quadrature(self, **params): + def logintegrand(x, **params): + logpmf = self._logpmf_dispatch(x, **params) + # Entropy summand is -pmf*log(pmf), so log-entropy summand is + # logpmf + log(logpmf) + pi*j. But pmf is always between 0 and 1, + # so logpmf is always negative, and so log(logpmf) = log(-logpmf) + pi*j. + # The two imaginary components "cancel" each other out (which we would + # expect because each term of the entropy summand is positive). + return logpmf + np.log(-logpmf) + return self._quadrature(logintegrand, params=params, log=True) # Special case the names of some new-style distributions in `make_distribution` diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 400026bd1f21..0e3c915f8cc9 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -1851,11 +1851,6 @@ def logentropy(self, *, method): out : array The log-entropy. - Raises - ------ - NotImplementedError - If the random variable is discrete. - See Also -------- entropy @@ -1863,9 +1858,9 @@ def logentropy(self, *, method): Notes ----- - If the entropy of a distribution is negative, then the log-entropy - is complex with imaginary part :math:`\pi`. For - consistency, the result of this function always has complex dtype, + The differential entropy of a continuous distribution can be negative. + In this case, the log-entropy is complex with imaginary part :math:`\pi`. + For consistency, the result of this function always has complex dtype, regardless of the value of the imaginary part. References diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 0e5612553619..dbce6cb0fc17 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1081,6 +1081,10 @@ def test_make_distribution(self, i, distdata): assert_allclose(X.support(), Y.support()) if distname not in skip_entropy: assert_allclose(X.entropy(), Y.entropy(), rtol=rtol) + if isinstance(Y, stats.rv_discrete): + # some continuous distributions have trouble with `logentropy` because + # it uses complex numbers + assert_allclose(np.exp(X.logentropy()), Y.entropy(), rtol=rtol) assert_allclose(X.median(), Y.median(), rtol=rtol) assert_allclose(X.mean(), m, rtol=rtol, atol=atol) assert_allclose(X.variance(), v, rtol=rtol, atol=atol) From e95c79df7336c6ce1250c99bfdc258dec3cdf8ef Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 15 Jan 2025 17:51:41 -0800 Subject: [PATCH 022/251] ENH: stats.make_distribution: support zipfian distribution --- scipy/stats/_discrete_distns.py | 3 ++- scipy/stats/_distribution_infrastructure.py | 4 ++-- scipy/stats/tests/test_continuous.py | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index a4aedce4d475..6b26490cc0f7 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1450,10 +1450,11 @@ def _pmf(self, k, a, n): return 1.0 / _gen_harmonic(n, a) * k**-a def _cdf(self, k, a, n): + k = np.floor(k) return _gen_harmonic(k, a) / _gen_harmonic(n, a) def _sf(self, k, a, n): - k = k + 1 # # to match SciPy convention + k = np.floor(k + 1) # # to match SciPy convention # see http://www.math.wm.edu/~leemis/chart/UDR/PDFs/Zipf.pdf return ((k**a*(_gen_harmonic(n, a) - _gen_harmonic(k, a)) + 1) / (k**a*_gen_harmonic(n, a))) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 5ddc1d60928a..4bd5b6f475b4 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3812,7 +3812,7 @@ def make_distribution(dist): `make_distribution` does not work perfectly with all instances of `rv_continuous`. Known failures include `levy_stable`, `vonmises`, `hypergeom`, `nchypergeom_fisher`, `nchypergeom_wallenius`, - `poisson_binom`, and `zipian`; and some methods of some distributions + `poisson_binom`; and some methods of some distributions will not support array shape parameters. Parameters @@ -3853,7 +3853,7 @@ class or its methods for more information. """ if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, stats.nchypergeom_fisher, stats.nchypergeom_wallenius, - stats.poisson_binom, stats.zipfian}: + stats.poisson_binom}: raise NotImplementedError(f"`{dist.name}` is not supported.") if not isinstance(dist, (stats.rv_continuous, stats.rv_discrete)): diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index dbce6cb0fc17..4335ccd4aac0 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1047,7 +1047,6 @@ def test_make_distribution(self, i, distdata): 'vonmises', # circular distribution; shouldn't work 'poisson_binom', # vector shape parameter 'hypergeom', # distribution functions need interpolation - 'zipfian', # distribution functions need interpolation 'nchypergeom_fisher', # distribution functions don't accept NaN 'nchypergeom_wallenius', # distribution functions don't accept NaN }: From 099384821681daeb76fd0945b85a23ff27f93878 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Sun, 2 Feb 2025 19:38:25 +0100 Subject: [PATCH 023/251] MAINT: signal.csd(): Port away from using `_spectral_helper` * This allows to mark the internal functions `_spectral_helper`, `fft_helper` and `_triage_segements` as legacy, since they are only used by the legacy functions `spectrogram` and `stft`. * Some small additions to the unit tests to achieve 100% coverage for `csd()`. * Remove csd related functions from file `_scipy_spectral_tst_shim.py` * An explanation of the `ShortTimeFFT` usage was added to the `csd()` docstr. --- scipy/signal/_spectral_py.py | 198 ++++++++++++++---- .../signal/tests/_scipy_spectral_test_shim.py | 173 +-------------- scipy/signal/tests/test_spectral.py | 16 +- 3 files changed, 173 insertions(+), 214 deletions(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 85002c3c70e0..f416ff3e52fa 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -4,10 +4,11 @@ import numpy.typing as npt from scipy import fft as sp_fft from . import _signaltools +from ._short_time_fft import ShortTimeFFT, FFT_MODE_TYPE from .windows import get_window from ._arraytools import const_ext, even_ext, odd_ext, zero_ext import warnings -from typing import Literal +from typing import cast, Literal __all__ = ['periodogram', 'welch', 'lombscargle', 'csd', 'coherence', @@ -685,7 +686,8 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, length of the window. noverlap: int, optional Number of points to overlap between segments. If `None`, - ``noverlap = nperseg // 2``. Defaults to `None`. + ``noverlap = nperseg // 2``. Defaults to `None` and may + not be greater than `nperseg`. nfft : int, optional Length of the FFT used, if a zero padded FFT is desired. If `None`, the FFT length is `nperseg`. Defaults to `None`. @@ -739,7 +741,7 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. @@ -759,17 +761,17 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, Examples -------- + The following example plots the cross power spectral density of two signals with + some common features: + >>> import numpy as np >>> from scipy import signal >>> import matplotlib.pyplot as plt >>> rng = np.random.default_rng() - - Generate two test signals with some common features. - - >>> fs = 10e3 - >>> N = 1e5 - >>> amp = 20 - >>> freq = 1234.0 + ... + ... # Generate two test signals with some common features: + >>> N, fs = 100_000, 10e3 # number of samples and sampling frequency + >>> amp, freq = 20, 1234.0 # amplitude and frequency of utilized sine signal >>> noise_power = 0.001 * fs / 2 >>> time = np.arange(N) / fs >>> b, a = signal.butter(2, 0.25, 'low') @@ -777,40 +779,147 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, >>> y = signal.lfilter(b, a, x) >>> x += amp*np.sin(2*np.pi*freq*time) >>> y += rng.normal(scale=0.1*np.sqrt(noise_power), size=time.shape) + ... + ... # Compute and plot the magnitude of the cross spectral density: + >>> nperseg, noverlap, win = 1024, 512, 'hann' + >>> f, Pxy = signal.csd(x, y, fs, win, nperseg, noverlap) + >>> fig0, ax0 = plt.subplots(tight_layout=True) + >>> ax0.set_title(f"CSD ({win.title()}-window, {nperseg=}, {noverlap=})") + >>> ax0.set(xlabel="Frequency $f$ in kHz", ylabel="CSD Magnitude in V²/Hz") + >>> ax0.semilogy(f/1e3, np.abs(Pxy)) + >>> ax0.grid() + >>> plt.show() - Compute and plot the magnitude of the cross spectral density. + The cross spectral density is calculated by taking the average over the time slices + of a spectrogram: - >>> f, Pxy = signal.csd(x, y, fs, nperseg=1024) - >>> plt.semilogy(f, np.abs(Pxy)) - >>> plt.xlabel('frequency [Hz]') - >>> plt.ylabel('CSD [V**2/Hz]') - >>> plt.show() + >>> SFT = signal.ShortTimeFFT.from_window('hann', fs, nperseg, noverlap, + ... scale_to='psd', fft_mode='onesided2X', + ... phase_shift=None) + >>> Sxy1 = SFT.spectrogram(y, x, detr='constant', k_offset=nperseg//2, + ... p0=0, p1=(N-noverlap) // SFT.hop) + >>> Pxy1 = Sxy1.mean(axis=-1) + >>> np.allclose(Pxy, Pxy1) # same result as with csd() + True + Allthough this function uses the `ShortTimeFFT` internally, the results of using an + approach analog to the code snippet above and the `csd()` function may deviate due + to implementation details. Notable differences are: + + * There is no direct `ShortTimeFFT` equivalent for the `csd()` parameter + combination ``return_onesided=True, scaling='density'``, since + ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd()` + returning the doubled squared magnitude in this case, which does not have a + sensible interpretation. + * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the + behavior of the utilized `~scipy.fft` module. Thus, those are the dtypes being + returned. The `csd` function casts the return values to `float32` / `complex64` + if the input is `float32` / `complex64` as well. + * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas + `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where + ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window + positioning is different (consult the code snippet above). + + Note that the code snippet above can be easily adapted to determine other + statistical properties than the mean value. """ - freqs, _, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, - nfft, detrend, return_onesided, scaling, - axis, mode='psd') + # The following lines are resembling the behavior of the originally utilized + # `_spectral_helper()` function: + same_data, axis = y is x, int(axis) + x = np.asarray(x) + + if not same_data: + y = np.asarray(y) + # Check if we can broadcast the outer axes together + x_outer, y_outer = list(x.shape), list(y.shape) + x_outer.pop(axis) + y_outer.pop(axis) + try: + outer_shape = np.broadcast(np.empty(x_outer), np.empty(y_outer)).shape + except ValueError as e: + raise ValueError('x and y cannot be broadcast together.') from e + if x.size == 0 or y.size == 0: + out_shape = outer_shape + (min([x.shape[axis], y.shape[axis]]),) + empty_out = np.moveaxis(np.empty(out_shape), -1, axis) + return empty_out, empty_out + out_dtype = np.result_type(x, y, np.complex64) + else: # x is y: + if x.size == 0: + return np.empty(x.shape), np.empty(x.shape) + out_dtype = np.result_type(x, np.complex64) + + n = x.shape[axis] if same_data else max(x.shape[axis], y.shape[axis]) + if isinstance(window, str) or isinstance(window, tuple): + nperseg = int(nperseg) if nperseg is not None else 256 + if nperseg < 1: + raise ValueError(f"Parameter {nperseg=} is not a positive integer!") + elif n < nperseg: + warnings.warn(f"{nperseg=} is greater than signal length max(len(x), " + + f"len(y)) = {n}, using nperseg = {n}", stacklevel=3) + nperseg = n + win = get_window(window, nperseg) + else: + win = np.asarray(window) + if nperseg is None: + nperseg = len(win) + if nperseg != len(win): + raise ValueError(f"{nperseg=} does not equal {len(win)=}") + + nfft = int(nfft) if nfft is not None else nperseg + if nfft < nperseg: + raise ValueError(f"{nfft=} must be greater than or equal to {nperseg=}!") + noverlap = int(noverlap) if noverlap is not None else nperseg // 2 + if noverlap >= nperseg: + raise ValueError(f"{noverlap=} must be less than {nperseg=}!") + if np.iscomplexobj(x) and return_onesided: + return_onesided = False + + # using cast() to make mypy happy: + fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided else 'twosided') + if scaling not in (scales := {'spectrum': 'magnitude', 'density': 'psd'}): + raise ValueError(f"Parameter {scaling=} not in {scales}!") + + SFT = ShortTimeFFT(win, nperseg - noverlap, fs, fft_mode=fft_mode, mfft=nfft, + scale_to=scales[scaling], phase_shift=None) + # csd() calculates X.conj()*Y instead of X*Y.conj(): + Pxy = SFT.spectrogram(y, x, detr=None if detrend is False else detrend, + p0=0, p1=(n - noverlap) // SFT.hop, k_offset=nperseg // 2, + axis=axis) + + # Note: + # 'onesided2X' scaling of ShortTimeFFT conflicts with the + # scaling='spectrum' parameter, since it doubles the squared magnitude, + # which in the view of the ShortTimeFFT implementation does not make sense. + # Hence, the doubling of the square is implemented here: + if return_onesided: + f_axis = Pxy.ndim - 1 + axis if axis < 0 else axis + Pxy = np.moveaxis(Pxy, f_axis, -1) + Pxy[..., 1:-1 if SFT.mfft % 2 == 0 else None] *= 2 + Pxy = np.moveaxis(Pxy, -1, f_axis) # Average over windows. - if len(Pxy.shape) >= 2 and Pxy.size > 0: - if Pxy.shape[-1] > 1: - if average == 'median': - # np.median must be passed real arrays for the desired result - bias = _median_bias(Pxy.shape[-1]) - if np.iscomplexobj(Pxy): - Pxy = (np.median(np.real(Pxy), axis=-1) - + 1j * np.median(np.imag(Pxy), axis=-1)) - else: - Pxy = np.median(Pxy, axis=-1) - Pxy /= bias - elif average == 'mean': - Pxy = Pxy.mean(axis=-1) + if Pxy.shape[-1] > 1: + if average == 'median': + # np.median must be passed real arrays for the desired result + bias = _median_bias(Pxy.shape[-1]) + if np.iscomplexobj(Pxy): + Pxy = (np.median(np.real(Pxy), axis=-1) + + np.median(np.imag(Pxy), axis=-1) * 1j) else: - raise ValueError(f'average must be "median" or "mean", got {average}') + Pxy = np.median(Pxy, axis=-1) + Pxy /= bias + elif average == 'mean': + Pxy = Pxy.mean(axis=-1) else: - Pxy = np.reshape(Pxy, Pxy.shape[:-1]) + raise ValueError(f"Parameter {average=} must be 'median' or 'mean'!") + else: + Pxy = np.reshape(Pxy, Pxy.shape[:-1]) - return freqs, Pxy + # cast output type; + Pxy = Pxy.astype(out_dtype) + if same_data: + Pxy = Pxy.real + return SFT.f, Pxy def spectrogram(x, fs=1.0, window=('tukey', .25), nperseg=None, noverlap=None, @@ -1890,6 +1999,11 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, padded=False): """Calculate various forms of windowed FFTs for PSD, CSD, etc. + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and `stft` + (which are also in this file). + This is a helper function that implements the commonality between the stft, psd, csd, and spectrogram functions. It is not designed to be called externally. The windows are not averaged over; the result @@ -1934,8 +2048,8 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the cross spectral density ('density') - where `Pxy` has units of V**2/Hz and computing the cross - spectrum ('spectrum') where `Pxy` has units of V**2, if `x` + where `Pxy` has units of V²/Hz and computing the cross + spectrum ('spectrum') where `Pxy` has units of V², if `x` and `y` are measured in V and `fs` is measured in Hz. Defaults to 'density' axis : int, optional @@ -2184,6 +2298,11 @@ def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides): Calculate windowed FFT, for internal use by `scipy.signal._spectral_helper`. + .. legacy:: function + + This function is solely used by the legacy `_spectral_helper` function, + which s located also in this file. + This is a helper function that does the main FFT calculation for `_spectral helper`. All input validation is performed there, and the data axis is assumed to be the last axis of x. It is not designed to @@ -2233,6 +2352,11 @@ def _triage_segments(window, nperseg, input_length): Parses window and nperseg arguments for spectrogram and _spectral_helper. This is a helper function, not meant to be called externally. + .. legacy:: function + + This function is soley used by the legacy functions `spectrogram` and + `_spectral_helper` (which are also in this file). + Parameters ---------- window : string, tuple, or ndarray diff --git a/scipy/signal/tests/_scipy_spectral_test_shim.py b/scipy/signal/tests/_scipy_spectral_test_shim.py index c23f310bcae4..b0026a586ac1 100644 --- a/scipy/signal/tests/_scipy_spectral_test_shim.py +++ b/scipy/signal/tests/_scipy_spectral_test_shim.py @@ -24,11 +24,10 @@ from numpy.testing import assert_allclose from scipy.signal import ShortTimeFFT -from scipy.signal import csd, get_window, stft, istft +from scipy.signal import get_window, stft, istft from scipy.signal._arraytools import const_ext, even_ext, odd_ext, zero_ext from scipy.signal._short_time_fft import FFT_MODE_TYPE -from scipy.signal._spectral_py import _spectral_helper, _triage_segments, \ - _median_bias +from scipy.signal._spectral_py import _triage_segments def _stft_wrapper(x, fs=1.0, window='hann', nperseg=256, noverlap=None, @@ -234,156 +233,6 @@ def _istft_wrapper(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, return t, x, (ST.lower_border_end[0], k_hi) -def _csd_wrapper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1, average='mean'): - """Wrapper for the `csd()` function based on `ShortTimeFFT` for - unit testing. - """ - freqs, _, Pxy = _csd_test_shim(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis) - - # The following code is taken from csd(): - if len(Pxy.shape) >= 2 and Pxy.size > 0: - if Pxy.shape[-1] > 1: - if average == 'median': - # np.median must be passed real arrays for the desired result - bias = _median_bias(Pxy.shape[-1]) - if np.iscomplexobj(Pxy): - Pxy = (np.median(np.real(Pxy), axis=-1) - + 1j * np.median(np.imag(Pxy), axis=-1)) - else: - Pxy = np.median(Pxy, axis=-1) - Pxy /= bias - elif average == 'mean': - Pxy = Pxy.mean(axis=-1) - else: - raise ValueError(f'average must be "median" or "mean", got {average}') - else: - Pxy = np.reshape(Pxy, Pxy.shape[:-1]) - - return freqs, Pxy - - -def _csd_test_shim(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1): - """Compare output of _spectral_helper() and ShortTimeFFT, more - precisely _spect_helper_csd() for used in csd_wrapper(). - - The motivation of this function is to test if the ShortTimeFFT-based - wrapper `_spect_helper_csd()` returns the same values as `_spectral_helper`. - This function should only be usd by csd() in (unit) testing. - """ - freqs, t, Pxy = _spectral_helper(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis, - mode='psd') - freqs1, Pxy1 = _spect_helper_csd(x, y, fs, window, nperseg, noverlap, nfft, - detrend, return_onesided, scaling, axis) - - np.testing.assert_allclose(freqs1, freqs) - amax_Pxy = max(np.abs(Pxy).max(), 1) if Pxy.size else 1 - atol = np.finfo(Pxy.dtype).resolution * amax_Pxy # needed for large Pxy - # for c_ in range(Pxy.shape[-1]): - # np.testing.assert_allclose(Pxy1[:, c_], Pxy[:, c_], atol=atol) - np.testing.assert_allclose(Pxy1, Pxy, atol=atol) - return freqs, t, Pxy - - -def _spect_helper_csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1): - """Wrapper for replacing _spectral_helper() by using the ShortTimeFFT - for use by csd(). - - This function should be only used by _csd_test_shim() and is only useful - for testing the ShortTimeFFT implementation. - """ - - # The following lines are taken from the original _spectral_helper(): - same_data = y is x - axis = int(axis) - - # Ensure we have np.arrays, get outdtype - x = np.asarray(x) - if not same_data: - y = np.asarray(y) - # outdtype = np.result_type(x, y, np.complex64) - # else: - # outdtype = np.result_type(x, np.complex64) - - if not same_data: - # Check if we can broadcast the outer axes together - xouter = list(x.shape) - youter = list(y.shape) - xouter.pop(axis) - youter.pop(axis) - try: - outershape = np.broadcast(np.empty(xouter), np.empty(youter)).shape - except ValueError as e: - raise ValueError('x and y cannot be broadcast together.') from e - - if same_data: - if x.size == 0: - return np.empty(x.shape), np.empty(x.shape) - else: - if x.size == 0 or y.size == 0: - outshape = outershape + (min([x.shape[axis], y.shape[axis]]),) - emptyout = np.moveaxis(np.empty(outshape), -1, axis) - return emptyout, emptyout - - if nperseg is not None: # if specified by user - nperseg = int(nperseg) - if nperseg < 1: - raise ValueError('nperseg must be a positive integer') - - # parse window; if array like, then set nperseg = win.shape - n = x.shape[axis] if same_data else max(x.shape[axis], y.shape[axis]) - win, nperseg = _triage_segments(window, nperseg, input_length=n) - - if nfft is None: - nfft = nperseg - elif nfft < nperseg: - raise ValueError('nfft must be greater than or equal to nperseg.') - else: - nfft = int(nfft) - - if noverlap is None: - noverlap = nperseg // 2 - else: - noverlap = int(noverlap) - if noverlap >= nperseg: - raise ValueError('noverlap must be less than nperseg.') - nstep = nperseg - noverlap - - if np.iscomplexobj(x) and return_onesided: - return_onesided = False - - # using cast() to make mypy happy: - fft_mode = cast(FFT_MODE_TYPE, 'onesided' if return_onesided - else 'twosided') - scale = {'spectrum': 'magnitude', 'density': 'psd'}[scaling] - SFT = ShortTimeFFT(win, nstep, fs, fft_mode=fft_mode, mfft=nfft, - scale_to=scale, phase_shift=None) - - # _spectral_helper() calculates X.conj()*Y instead of X*Y.conj(): - Pxy = SFT.spectrogram(y, x, detr=None if detrend is False else detrend, - p0=0, p1=(n-noverlap)//SFT.hop, k_offset=nperseg//2, - axis=axis).conj() - # Note: - # 'onesided2X' scaling of ShortTimeFFT conflicts with the - # scaling='spectrum' parameter, since it doubles the squared magnitude, - # which in the view of the ShortTimeFFT implementation does not make sense. - # Hence, the doubling of the square is implemented here: - if return_onesided: - f_axis = Pxy.ndim - 1 + axis if axis < 0 else axis - Pxy = np.moveaxis(Pxy, f_axis, -1) - Pxy[..., 1:-1 if SFT.mfft % 2 == 0 else None] *= 2 - Pxy = np.moveaxis(Pxy, -1, f_axis) - - return SFT.f, Pxy - - def stft_compare(x, fs=1.0, window='hann', nperseg=256, noverlap=None, nfft=None, detrend=False, return_onesided=True, boundary='zeros', padded=True, axis=-1, scaling='spectrum'): @@ -468,21 +317,3 @@ def istft_compare(Zxx, fs=1.0, window='hann', nperseg=None, noverlap=None, assert_allclose(x_wrapper[k_lo:k_hi], x[k_lo:k_hi], atol=atol, rtol=rtol, err_msg=f"Signal values {e_msg_part}") return t, x - - -def csd_compare(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, - nfft=None, detrend='constant', return_onesided=True, - scaling='density', axis=-1, average='mean'): - """Assert that the results from the existing `csd()` and `_csd_wrapper()` - are close to each other. """ - kw = dict(x=x, y=y, fs=fs, window=window, nperseg=nperseg, - noverlap=noverlap, nfft=nfft, detrend=detrend, - return_onesided=return_onesided, scaling=scaling, axis=axis, - average=average) - freqs0, Pxy0 = csd(**kw) - freqs1, Pxy1 = _csd_wrapper(**kw) - - assert_allclose(freqs1, freqs0) - assert_allclose(Pxy1, Pxy0) - assert_allclose(freqs1, freqs0) - return freqs0, Pxy0 diff --git a/scipy/signal/tests/test_spectral.py b/scipy/signal/tests/test_spectral.py index 12dac6300b9e..f5d0e9a59a0b 100644 --- a/scipy/signal/tests/test_spectral.py +++ b/scipy/signal/tests/test_spectral.py @@ -10,7 +10,7 @@ from scipy import signal from scipy.fft import fftfreq, rfftfreq, fft, irfft from scipy.integrate import trapezoid -from scipy.signal import (periodogram, welch, lombscargle, coherence, +from scipy.signal import (periodogram, welch, lombscargle, coherence, csd, spectrogram, check_COLA, check_NOLA) from scipy.signal.windows import hann from scipy.signal._spectral_py import _spectral_helper @@ -18,7 +18,6 @@ # Compare ShortTimeFFT.stft() / ShortTimeFFT.istft() with stft() / istft(): from scipy.signal.tests._scipy_spectral_test_shim import stft_compare as stft from scipy.signal.tests._scipy_spectral_test_shim import istft_compare as istft -from scipy.signal.tests._scipy_spectral_test_shim import csd_compare as csd class TestPeriodogram: @@ -416,8 +415,7 @@ def test_short_data(self): #for string-like window, input signal length < nperseg value gives #UserWarning, sets nperseg to x.shape[-1] with suppress_warnings() as sup: - msg = "nperseg = 256 is greater than input length = 8, using nperseg = 8" - sup.filter(UserWarning, msg) + sup.filter(UserWarning, "nperseg=256 is greater than signal.*") f, p = welch(x,window='hann') # default nperseg f1, p1 = welch(x,window='hann', nperseg=256) # user-specified nperseg f2, p2 = welch(x, nperseg=8) # valid nperseg, doesn't give warning @@ -735,6 +733,8 @@ def test_window_external(self): win_err = signal.get_window('hann', 32) assert_raises(ValueError, csd, x, x, 10, win_err, nperseg=None) # because win longer than signal + with pytest.raises(ValueError, match="Parameter nperseg=0.*"): + csd(x, x, 0, nperseg=0) def test_empty_input(self): f, p = csd([],np.zeros(10)) @@ -779,8 +779,7 @@ def test_short_data(self): #for string-like window, input signal length < nperseg value gives #UserWarning, sets nperseg to x.shape[-1] with suppress_warnings() as sup: - msg = "nperseg = 256 is greater than input length = 8, using nperseg = 8" - sup.filter(UserWarning, msg) + sup.filter(UserWarning, "nperseg=256 is greater than signal length.*") f, p = csd(x, x, window='hann') # default nperseg f1, p1 = csd(x, x, window='hann', nperseg=256) # user-specified nperseg f2, p2 = csd(x, x, nperseg=8) # valid nperseg, doesn't give warning @@ -811,6 +810,11 @@ def test_nfft_too_short(self): assert_raises(ValueError, csd, np.ones(12), np.zeros(12), nfft=3, nperseg=4) + def test_incompatible_inputs(self): + with pytest.raises(ValueError, match='x and y cannot be broadcast.*'): + csd(np.ones((1, 8, 1)), np.ones((2, 8)), nperseg=4) + + def test_real_onesided_even_32(self): x = np.zeros(16, 'f') x[0] = 1 From 04c8afdb890013f421ef7cc0a3ffb2bcda7b6262 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Tue, 4 Feb 2025 12:27:12 +0100 Subject: [PATCH 024/251] signal.csd(): Changed `np.broadcast` into the more efficient`np.broadcast_shapes` --- scipy/signal/_spectral_py.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index f416ff3e52fa..9ecce0763ffd 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -835,7 +835,7 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, x_outer.pop(axis) y_outer.pop(axis) try: - outer_shape = np.broadcast(np.empty(x_outer), np.empty(y_outer)).shape + outer_shape = np.broadcast_shapes(x_outer, y_outer) except ValueError as e: raise ValueError('x and y cannot be broadcast together.') from e if x.size == 0 or y.size == 0: From e749cfb0127fde33368b2aaaed71614f4814516c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 5 Mar 2025 14:23:58 -0800 Subject: [PATCH 025/251] MAINT: stats.DiscreteDistribution: fixup after merge --- scipy/stats/_distribution_infrastructure.py | 22 ++++++++++----------- scipy/stats/tests/test_continuous.py | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 8816972036e9..ceaff751844e 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3925,7 +3925,7 @@ class or its methods for more information. message = ("The argument must be an instance of `rv_continuous`, " "`rv_discrete`, or an instance of a class with attribute " "`__make_distribution_version__ >= 1.16`.") - + raise ValueError(message) def _make_distribution_rv_generic(dist): parameters = [] @@ -3938,6 +3938,16 @@ def _make_distribution_rv_generic(dist): parameters.append(param) names.append(shape_info.name) + repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) + if isinstance(dist, stats.rv_continuous): + old_class, new_class = stats.rv_continuous, ContinuousDistribution + else: + old_class, new_class = stats.rv_discrete, DiscreteDistribution + + def _overrides(method_name): + return (getattr(dist.__class__, method_name, None) + is not getattr(old_class, method_name, None)) + if _overrides("_get_support"): def left(**parameter_values): a, _ = dist._get_support(**parameter_values) @@ -3954,12 +3964,6 @@ def right(**parameter_values): _x_support = _RealDomain(endpoints=endpoints, inclusive=(True, True)) _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) - repr_str = _distribution_names.get(dist.name, dist.name.capitalize()) - if isinstance(dist, stats.rv_continuous): - old_class, new_class = stats.rv_continuous, ContinuousDistribution - else: - old_class, new_class = stats.rv_discrete, DiscreteDistribution - class CustomDistribution(new_class): _parameterizations = ([_Parameterization(*parameters)] if parameters else []) @@ -4028,10 +4032,6 @@ def _moment_standard_formula(self, order, **kwargs): # Make it an attribute of the new object with the new name setattr(CustomDistribution, new_method, getattr(dist, old_method)) - def _overrides(method_name): - return (getattr(dist.__class__, method_name, None) - is not getattr(old_class, method_name, None)) - if _overrides('_munp'): CustomDistribution._moment_raw_formula = _moment_raw_formula diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 50bed2e042b2..1e19ee5cd9f6 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1049,6 +1049,7 @@ def test_rv_generic(self, i, distdata): 'hypergeom', # distribution functions need interpolation 'nchypergeom_fisher', # distribution functions don't accept NaN 'nchypergeom_wallenius', # distribution functions don't accept NaN + 'skellam', # during `entropy`, Fatal Python error: Aborted! }: return @@ -1272,7 +1273,7 @@ def test_input_validation(self): with pytest.raises(NotImplementedError, match=message): stats.make_distribution(stats.vonmises) - message = "Argument must be an instance of `rv_continuous` or `rv_discrete`." + message = "The argument must be an instance of..." with pytest.raises(ValueError, match=message): stats.make_distribution(object()) From 281fc1954e709d32d2d399829f3395b28fabb0fb Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 5 Mar 2025 17:19:54 -0800 Subject: [PATCH 026/251] STY: stats.DiscreteDistribution: fix lint [lint only] --- scipy/stats/_distribution_infrastructure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index ceaff751844e..8021d5beadc3 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -7,7 +7,7 @@ import numpy as np from numpy import inf -from scipy._lib._util import _rng_spawn, _RichResult +from scipy._lib._util import _rng_spawn, _RichResult, _lazywhere from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats from scipy.special._ufuncs import _log1mexp @@ -3917,7 +3917,7 @@ class or its methods for more information. stats.poisson_binom}: raise NotImplementedError(f"`{dist.name}` is not supported.") - if isinstance(dist, (stats.rv_continuous, stats.rv_discrete)): + if isinstance(dist, stats.rv_continuous | stats.rv_discrete): return _make_distribution_rv_generic(dist) elif getattr(dist, "__make_distribution_version__", "0.0.0") >= "1.16.0": return _make_distribution_custom(dist) From 5496bc9b15bcd825bb2ed0dbb10b4e1208b091a7 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 7 Mar 2025 19:33:13 -0800 Subject: [PATCH 027/251] MAINT: stats.quantile: fixup quantile for p < minimum plotting position --- scipy/stats/_quantile.py | 7 +++++-- scipy/stats/tests/test_quantile.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_quantile.py b/scipy/stats/_quantile.py index 7ddfce7e992b..b4bbb3f627b6 100644 --- a/scipy/stats/_quantile.py +++ b/scipy/stats/_quantile.py @@ -205,8 +205,9 @@ def quantile(x, p, *, method='linear', axis=0, nan_policy='propagate', keepdims= Note that indices ``j`` and ``j + 1`` are clipped to the range ``0`` to ``n - 1`` when the results of the formula would be outside the allowed - range of non-negative indices. The ``-1`` in the formulas for ``j`` and - ``g`` accounts for Python's 0-based indexing. + range of non-negative indices. When ``j`` is clipped to zero, ``g`` is + set to zero as well. The ``-1`` in the formulas for ``j`` and ``g`` + accounts for Python's 0-based indexing. The table above includes only the estimators from [1]_ that are continuous functions of probability `p` (estimators 4-9). SciPy also provides the @@ -313,6 +314,8 @@ def _quantile_hf(y, p, n, method, xp): if method in {'inverted_cdf', 'averaged_inverted_cdf', 'closest_observation'}: g = xp.asarray(g) g = xpx.at(g, jg < 0).set(0) + + g[j < 0] = 0 j = xp.clip(j, 0., n - 1) jp1 = xp.clip(j + 1, 0., n - 1) diff --git a/scipy/stats/tests/test_quantile.py b/scipy/stats/tests/test_quantile.py index b181f305b53d..87cc13870ad9 100644 --- a/scipy/stats/tests/test_quantile.py +++ b/scipy/stats/tests/test_quantile.py @@ -86,8 +86,8 @@ def test_input_validation(self, xp): 'hazen', 'interpolated_inverted_cdf', 'linear', 'median_unbiased', 'normal_unbiased', 'weibull']) @pytest.mark.parametrize('shape_x, shape_p, axis', - [(10, None, -1), (10, 3, -1), (10, (2, 3), -1), - ((10, 2), None, 0), ((10, 2), None, 0)]) + [(10, None, -1), (10, 10, -1), (10, (2, 3), -1), + ((10, 2), None, 0), ((10, 2), None, 0),]) def test_against_numpy(self, method, shape_x, shape_p, axis, xp): dtype = xp_default_dtype(xp) rng = np.random.default_rng(23458924568734956) From 516788d7eabde4a765b5628823a19cb77831f7bc Mon Sep 17 00:00:00 2001 From: dschmitz89 Date: Thu, 20 Mar 2025 22:15:27 +0100 Subject: [PATCH 028/251] TST: add tests for ncfdtri --- scipy/special/tests/test_cdflib.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scipy/special/tests/test_cdflib.py b/scipy/special/tests/test_cdflib.py index 4fb1a29ab451..7e6a2e3023da 100644 --- a/scipy/special/tests/test_cdflib.py +++ b/scipy/special/tests/test_cdflib.py @@ -3,7 +3,6 @@ The following functions still need tests: -- ncfdtri - ncfdtridfn - ncfdtridfd - ncfdtrinc @@ -491,7 +490,7 @@ def test_bdtrik_nbdtrik_inf(): @pytest.mark.parametrize( - "dfn,dfd,nc,f,expected", + "dfn,dfd,nc,f,expected_cdf", [[100.0, 0.1, 0.1, 100.0, 0.29787396410092676], [100.0, 100.0, 0.01, 0.1, 4.4344737598690424e-26], [100.0, 0.01, 0.1, 0.01, 0.002848616633080384], @@ -505,7 +504,7 @@ def test_bdtrik_nbdtrik_inf(): [100.0, 100.0, 0.1, 10.0, 1.0], [1.0, 0.1, 100.0, 10.0, 0.02926064279680897]] ) -def test_ncfdtr(dfn, dfd, nc, f, expected): +def test_ncfdtr_ncfdtri(dfn, dfd, nc, f, expected_cdf): # Reference values computed with mpmath with the following script # # import numpy as np @@ -548,8 +547,11 @@ def test_ncfdtr(dfn, dfd, nc, f, expected): # rng = np.random.default_rng(1234) # sample_idx = rng.choice(len(re), replace=False, size=12) # cases = np.array(cases)[sample_idx].tolist() - assert_allclose(sp.ncfdtr(dfn, dfd, nc, f), expected, rtol=1e-13, atol=0) - + assert_allclose(sp.ncfdtr(dfn, dfd, nc, f), expected_cdf, rtol=1e-13, atol=0) + # testing tails where the CDF reaches 0 or 1 does not make sense for inverses + # of a CDF as they are not bijective in these regions + if 0 < expected_cdf < 1: + assert_allclose(sp.ncfdtri(dfn, dfd, nc, expected_cdf), f, rtol=5e-11) @pytest.mark.parametrize( "args", From 2519b270dc304fffe8ad9c68b4ba80d815821ce9 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Sat, 22 Mar 2025 12:02:53 +0100 Subject: [PATCH 029/251] Improved docstrs of `signal.welch()` and `signal.csd()` by clarifying relationship between squared magnitude/cross spectrum and PSD/cross PSD. --- scipy/signal/_spectral_py.py | 21 ++++++++++++++++----- scipy/signal/tests/test_spectral.py | 9 +++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 9ecce0763ffd..68d82ef37d1a 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -555,6 +555,7 @@ def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, See Also -------- + csd: Cross power spectral density using Welch's method periodogram: Simple, optionally modified periodogram lombscargle: Lomb-Scargle periodogram for unevenly sampled data @@ -562,12 +563,16 @@ def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower - windows may require a larger overlap. + windows may require a larger overlap. If `noverlap` is 0, this + method is equivalent to Bartlett's method [2]_. - If `noverlap` is 0, this method is equivalent to Bartlett's method - [2]_. + The ratio of the squared magnitude (``scaling='spectrum'``) divided the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of the power spectral density and @@ -745,6 +750,12 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, signal power, while not over counting any of the data. Narrower windows may require a larger overlap. + The ratio of the cross spectrum (``scaling='spectrum'``) divided by the cross + spectral density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of a spectral density and an (amplitude) spectrum. @@ -1935,7 +1946,7 @@ def coherence(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, ----- An appropriate amount of overlap will depend on the choice of window and on your requirements. For the default Hann window an overlap of - 50% is a reasonable trade off between accurately estimating the + 50% is a reasonable trade-off between accurately estimating the signal power, while not over counting any of the data. Narrower windows may require a larger overlap. diff --git a/scipy/signal/tests/test_spectral.py b/scipy/signal/tests/test_spectral.py index f5d0e9a59a0b..a4c62574676a 100644 --- a/scipy/signal/tests/test_spectral.py +++ b/scipy/signal/tests/test_spectral.py @@ -558,6 +558,15 @@ def test_average(self): assert_raises(ValueError, welch, x, nperseg=8, average='unrecognised-average') + def test_ratio_scale_to(self): + """Verify the factor of ``sum(abs(window)**2)*fs / abs(sum(window))**2`` + used in the `welch` and `csd` docstrs. """ + x, win, fs = np.array([1., 0, 0, 0]), np.ones(4), 12 + params = dict(fs=fs, window=win, return_onesided=False, detrend=None) + p_dens = welch(x, scaling='density', **params)[1] + p_spec = welch(x, scaling='spectrum', **params)[1] + p_fac = sum(win**2)*fs / abs(sum(win))**2 + assert_allclose(p_spec / p_dens, p_fac) class TestCSD: def test_pad_shorter_x(self): From adad9b7a97b035c6bc08af3759bdce96c91d8138 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Sun, 23 Mar 2025 19:28:28 +0100 Subject: [PATCH 030/251] Improved docstrs of `signal.periodogram()` by clarifying relationship between squared magnitude/cross spectrum and PSD/cross PSD. --- scipy/signal/_spectral_py.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 68d82ef37d1a..1302040b9342 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -378,8 +378,8 @@ def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', complex data, a two-sided spectrum is always returned. scaling : { 'density', 'spectrum' }, optional Selects between computing the power spectral density ('density') - where `Pxx` has units of V**2/Hz and computing the squared magnitude - spectrum ('spectrum') where `Pxx` has units of V**2, if `x` + where `Pxx` has units of V²/Hz and computing the squared magnitude + spectrum ('spectrum') where `Pxx` has units of V², if `x` is measured in V and `fs` is measured in Hz. Defaults to 'density' axis : int, optional @@ -400,6 +400,12 @@ def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', Notes ----- + The ratio of the squared magnitude (``scaling='spectrum'``) divided the spectral + power density (``scaling='density'``) is the constant factor of + ``sum(abs(window)**2)*fs / abs(sum(window))**2``. + If `return_onesided` is ``True``, the values of the negative frequencies are added + to values of the corresponding positive ones. + Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of the power spectral density and the magnitude (squared) spectrum. From 5ed5aa5b045c8039c5f70f0afc4b5e70f052b07f Mon Sep 17 00:00:00 2001 From: pratham-mcw Date: Mon, 7 Apr 2025 16:58:11 +0530 Subject: [PATCH 031/251] ENH: ndimage:Rotate Performance Enhancement on WoA --- scipy/ndimage/src/ni_interpolation.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scipy/ndimage/src/ni_interpolation.c b/scipy/ndimage/src/ni_interpolation.c index fe73b4bf47fe..f0f86a7f32c9 100644 --- a/scipy/ndimage/src/ni_interpolation.c +++ b/scipy/ndimage/src/ni_interpolation.c @@ -265,7 +265,7 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, npy_intp ftmp[NPY_MAXDIMS], *fcoordinates = NULL, *foffsets = NULL; npy_intp cstride = 0, kk, hh, ll, jj; npy_intp size; - double **splvals = NULL, icoor[NPY_MAXDIMS]; + double **splvals = NULL, icoor[NPY_MAXDIMS], tmp; npy_intp idimensions[NPY_MAXDIMS], istrides[NPY_MAXDIMS]; NI_Iterator io, ic; npy_double *matrix = matrix_ar ? (npy_double*)PyArray_DATA(matrix_ar) : NULL; @@ -420,10 +420,16 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, /* do an affine transformation: */ npy_double *p = matrix; for(hh = 0; hh < irank; hh++) { - icoor[hh] = 0.0; - for(ll = 0; ll < orank; ll++) - icoor[hh] += io.coordinates[ll] * *p++; - icoor[hh] += shift[hh]; + tmp = shift[hh]; + ll = 0; + for (; ll + 1 < orank; ll += 2) { + tmp += io.coordinates[ll] * *p++; + tmp += io.coordinates[ll + 1] * *p++; + } + if (ll < orank) { + tmp += io.coordinates[ll] * *p++; + } + icoor[hh] = tmp; } } else if (coordinates) { /* mapping is from an coordinates array: */ @@ -508,7 +514,9 @@ NI_GeometricTransform(PyArrayObject *input, int (*map)(npy_intp*, double*, edge_offsets[hh] = NULL; } } - get_spline_interpolation_weights(cc, order, splvals[hh]); + if(order!=0){ + get_spline_interpolation_weights(cc, order, splvals[hh]); + } } else { /* we use the constant border condition: */ constant = 1; From 34cf1d9e168acba96d03afa8a22e8ab5d75c4a0a Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Tue, 8 Apr 2025 14:36:42 +0200 Subject: [PATCH 032/251] BUG: signal.resample: Fix complex-valued signal problems. In function `resample`: * Refactor to make code a little bit more accessible. * Rework of docstr (also Closes #13788). * Fix indexing bug for parameter `num=2` (Closes #14569) * Fix a corner case when parameter `t` is of integer type (Closes #15153) In `signal.test.tests_signaltools.py`: * Add unit test for verifying fix of #14569 * Increase coverage of `resample` and `resample_polyphase` to 100%. Additionally: * Handle unpaired bins correctly in `signal.envelope()`. * Improve docstr of `signal.decimate` clarifying what to do with non-integer down-sampling factors (Closes #13789). --- scipy/signal/_signaltools.py | 356 +++++++++++++------------ scipy/signal/tests/test_signaltools.py | 77 ++++++ scipy/signal/tests/test_windows.py | 2 +- 3 files changed, 261 insertions(+), 174 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 2c138d24632a..825c7b710e71 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -2926,8 +2926,12 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, else: Z[..., bp.start:], Z[..., 0:(n + 1) // 2] = 0, 0 - z_res = fak * (sp_fft.ifft(Z, n=n_out) if np.iscomplexobj(z) else - sp_fft.irfft(Z, n=n_out)) + if np.iscomplexobj(z): # resample() accounts for unpaired bins: + z_res = resample(Z, n_out, axis=-1, domain='freq') # ifft() with corrections + else: # account for unpaired bin at m//2 before doing irfft(): + if n_out != n and (m := min(n, n_out)) % 2 == 0: + Z[..., m//2] *= 2 if n_out < n else 0.5 + z_res = fak * sp_fft.irfft(Z, n=n_out) return np.stack((z_env, np.moveaxis(z_res, -1, axis)), axis=0) def _cmplx_sort(p): @@ -3500,38 +3504,58 @@ def invresz(r, p, k, tol=1e-3, rtype='avg'): def resample(x, num, t=None, axis=0, window=None, domain='time'): - """ - Resample `x` to `num` samples using Fourier method along the given axis. + """Resample `x` to `num` samples using the Fourier method along the given `axis`. - The resampled signal starts at the same value as `x` but is sampled - with a spacing of ``len(x) / num * (spacing of x)``. Because a - Fourier method is used, the signal is assumed to be periodic. + The resampling is performed by shortening or zero-padding the FFT of `x`. This has + the advantages of providing an ideal antialiasing filter and allowing arbitrary + up- or down-sampling ratios. The main drawback is the requirement of assuming `x` + to be a periodic signal. Parameters ---------- x : array_like - The data to be resampled. + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time/frequency axis. It is assumed + here that ``n_x = x.shape[axis]`` specifies the number of samples and ``T`` the + sampling interval. num : int - The number of samples in the resampled signal. + The number of samples of the resampled output signal. It may be larger or + smaller than ``n_x``. t : array_like, optional - If `t` is given, it is assumed to be the equally spaced sample - positions associated with the signal data in `x`. + If `t` is not ``None`` (default), then the timestamps of the resampled signal + are also returned. `t` must contain at least the first two timestamps of the + input signal `x` (all others are ignored). The timestamps of the output signal + are determined by ``t[0] + T * n_x / num * np.arange(num)`` with + ``T = t[1] - t[0]``. axis : int, optional - The axis of `x` that is resampled. Default is 0. + The time/frequency axis of `x` along which the resampling take place. + The Default is 0. window : array_like, callable, string, float, or tuple, optional - Specifies the window applied to the signal in the Fourier - domain. See below for details. - domain : string, optional - A string indicating the domain of the input `x`: - ``time`` Consider the input `x` as time-domain (Default), - ``freq`` Consider the input `x` as frequency-domain. + If not ``None`` (default), specifies a filter in the Fourier domain, which is + applied before resampling. I.e., the FFT ``X`` of `x` is calculated by + ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a sepctral + windowing function ``W(f_X)`` which consumes the frequencies + ``f_X = fftfreq(n_x, T)``. + + If `window` is a 1d array of length `n_x` then ``W=window``. + If `window` is a function then ``W = window(f_X)``. + Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., + ``W = fftshift(signal.get_window(window, n_x))``. + + domain : 'time' | 'freq', optional + If set to ``'time'`` (default) then an FFT is applied to `x`, otherwise + (``'freq'``) it is asssmued that an FFT was already applied, i.e., + ``x = fft(x_t, axis=axis)`` with ``x_t`` being the input signal in the time + domain. Returns ------- - resampled_x or (resampled_x, resampled_t) - Either the resampled array, or, if `t` was given, a tuple - containing the resampled array and the corresponding resampled - positions. + x_r : ndarray + The resampled signal made up of `num` samples and sampling interval + ``T * n_x / num``. + t_r : ndarray, optional + The `num` equidistant timestamps of `x_r`. + This is only returned if paramater `t` is not ``None``. See Also -------- @@ -3540,170 +3564,151 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): Notes ----- - The argument `window` controls a Fourier-domain window that tapers - the Fourier spectrum before zero-padding to alleviate ringing in - the resampled values for sampled signals you didn't intend to be - interpreted as band-limited. - - If `window` is a function, then it is called with a vector of inputs - indicating the frequency bins (i.e. fftfreq(x.shape[axis]) ). - - If `window` is an array of the same length as `x.shape[axis]` it is - assumed to be the window to be applied directly in the Fourier - domain (with dc and low-frequency first). - - For any other type of `window`, the function `scipy.signal.get_window` - is called to generate the window. - - The first sample of the returned vector is the same as the first - sample of the input vector. The spacing between samples is changed - from ``dx`` to ``dx * len(x) / num``. - - If `t` is not None, then it is used solely to calculate the resampled - positions `resampled_t` - - As noted, `resample` uses FFT transformations, which can be very - slow if the number of input or output samples is large and prime; - see :func:`~scipy.fft.fft`. In such cases, it can be faster to first downsample - a signal of length ``n`` with :func:`~scipy.signal.resample_poly` by a factor of - ``n//num`` before using `resample`. Note that this approach changes the - characteristics of the antialiasing filter. + This function uses the more efficient one-sided FFT, i.e. `~scipy.fft.rfft` / + `~scipy.fft.irfft`, if `x` is real-valued and in the time domain. + Else, the two-sided FFT, i.e., `~scipy.fft.fft` / `~scipy.fft.ifft`, is used + (all FFT functions are taken from the `scipy.fft` module). + + If a `window` is applied to a real-valued `x`, the one-sided spectral windowing + function is determined by taking the average of the negative and the positive + frequency component. This ensures that real-valued signals and complex signals with + zero imaginary part are treated identically. I.e., passing `x` or passing + ``x.astype(np.complex128)`` produce the same numeric result. + + If the number of input or output samples are prime or have few prime factors, this + function may be slow due to utilizing FFTs. Consult `~scipy.fft.prev_fast_len` and + `~scipy.fft.next_fast_len` for determining efficient signals lengths. + Alternatively, utilizing `resample_poly` to calculate an intermediate signal (as + illustrated in the example below) can result in significant speed increases. + + `resample` is intended to be used for periodic signals with equidistant sampling + intervals. For non-periodic signals, `resample_poly` may be a better choice. + Consult the `scipy.interpolate` module for methods of resampling signals with + non-constant sampling intervals. Examples -------- - Note that the end of the resampled data rises to meet the first - sample of the next cycle: + The following example depicts a signal being up-sampled from 20 samples to 100 + samples. The ringing at the beginning of the up-sampled signal is due to + interpreting the signal being periodic. The red square in the plot illustrates that + periodictiy by showing the first sample of the next cycle of the signal. >>> import numpy as np - >>> from scipy import signal - - >>> x = np.linspace(0, 10, 20, endpoint=False) - >>> y = np.cos(-x**2/6.0) - >>> f = signal.resample(y, 100) - >>> xnew = np.linspace(0, 10, 100, endpoint=False) - >>> import matplotlib.pyplot as plt - >>> plt.plot(x, y, 'go-', xnew, f, '.-', 10, y[0], 'ro') - >>> plt.legend(['data', 'resampled'], loc='best') + >>> from scipy.signal import resample + ... + >>> n0, n1 = 20, 100 # number of samples + >>> t0 = np.linspace(0, 10, n0, endpoint=False) # input time stamps + >>> x0 = np.cos(-t0**2/6) # input signal + ... + >>> x1 = resample(x0, n1) # resampled signal + >>> t1 = np.linspace(0, 10, n1, endpoint=False) # timestamps of x1 + ... + >>> fig0, ax0 = plt.subplots(1, 1, tight_layout=True) + >>> ax0.set_title(f"Resampling $x(t)$ from {n0} samples to {n1} samples") + >>> ax0.set(xlabel="Time $t$", ylabel="Amplitude $x(t)$") + >>> ax0.plot(t1, x1, '.-', alpha=.5, label=f"Resampled") + >>> ax0.plot(t0, x0, 'o-', alpha=.5, label="Original") + >>> ax0.plot(10, x0[0], 'rs', alpha=.5, label="Next Cycle") + >>> ax0.legend(loc='best') + >>> ax0.grid(True) >>> plt.show() - Consider the following signal ``y`` where ``len(y)`` is a large prime number: - >>> N = 55949 - >>> freq = 100 - >>> x = np.linspace(0, 1, N) - >>> y = np.cos(2 * np.pi * freq * x) + The following example illustrates that `~scipy.fft.rfft` / `~scipy.fft.irfft` + combination does not always produce the correct resampling result: - Due to ``N`` being prime, - - >>> num = 5000 - >>> f = signal.resample(signal.resample_poly(y, 1, N // num), num) + >>> from scipy.fft import irfft, rfft + >>> from scipy.signal import resample + ... + >>> n0, n1 = 8, 4 # number of samples + >>> x0 = irfft([0, 0, 8, 0, 0], n=n0) # input signal + >>> x1 = resample(x0, num=n1) + >>> y1 = irfft(rfft(x0), n=n1) * n1/n0 + >>> for x_, n_ in zip((x0, x1, y1), ('x0', 'x1', 'y1')): + ... print(f"{n_} = {x_}") + x0 = [ 2. 0. -2. 0. 2. 0. -2. 0.] + x1 = [ 2. -2. 2. -2.] + y1 = [ 1. -1. 1. -1.] + + It can be easily verified that ``x1`` is correct and ``y1`` is not. To produce + correct results, `resample` correctly accounts for unpaired frequency bins. Here, + ``fft(x1)`` has the frequency bins ``[0, 1, -2, -1]`` (which can be determined with + `~scipy.fft.fftfreq`), where the entry ``-2`` does not have a positive pair ``+2``. + For that reason, `resample` scaled this unpaired bin by 1/2. + + The following code snippet shows how to use `resample_poly` to speed up the + down-sampling: - runs significantly faster than + >>> import numpy as np + >>> from scipy.fft import next_fast_len + >>> from scipy.signal import resample, resample_poly + ... + >>> n0 = 19937 # number of input samples - prime + >>> n1 = 128 # number of output samples - fast FFT length + >>> x0 = np.random.rand(n0) # input signal + ... + >>> y1 = resample(x0, n1) # slow due to n0 being prime + >>> y1_p = resample(resample_poly(x0, 1, n0 // n1), n1) # This is faster - >>> f = signal.resample(y, num) + Note that the `y1` and `y1_p` are not identical due to the differences in the + utilized antialiasing filter. """ - if domain not in ('time', 'freq'): - raise ValueError("Acceptable domain flags are 'time' or" - f" 'freq', not domain={domain}") + raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") x = np.asarray(x) - Nx = x.shape[axis] - - # Check if we can use faster real FFT - real_input = np.isrealobj(x) - - if domain == 'time': - # Forward transform - if real_input: - X = sp_fft.rfft(x, axis=axis) - else: # Full complex FFT - X = sp_fft.fft(x, axis=axis) - else: # domain == 'freq' - X = x - - # Apply window to spectrum - if window is not None: - if callable(window): - W = window(sp_fft.fftfreq(Nx)) - elif isinstance(window, np.ndarray): - if window.shape != (Nx,): - raise ValueError('window must have the same length as data') - W = window - else: - W = sp_fft.ifftshift(get_window(window, Nx)) - - newshape_W = [1] * x.ndim - newshape_W[axis] = X.shape[axis] - if real_input: - # Fold the window back on itself to mimic complex behavior - W_real = W.copy() - W_real[1:] += W_real[-1:0:-1] - W_real[1:] *= 0.5 - X *= W_real[:newshape_W[axis]].reshape(newshape_W) - else: - X *= W.reshape(newshape_W) - - # Copy each half of the original spectrum to the output spectrum, either - # truncating high frequencies (downsampling) or zero-padding them - # (upsampling) - - # Placeholder array for output spectrum - newshape = list(x.shape) - if real_input: - newshape[axis] = num // 2 + 1 + if x.ndim > 1: # moving active axis to end allows to use `...` in indexing: + x = np.swapaxes(x, axis, -1) + n_x = x.shape[-1] # number of samples along the time/frequency axis + s_fac = n_x / num # scaling factor represents sample interval dilatation + m = min(num, n_x) # number of relevant frequency bins + m2 = m // 2 + 1 # number of relevant frequency bins of a one-sided FFT + + if window is None: # Determine spectral windowing function: + W = None + elif callable(window): + W = window(sp_fft.fftfreq(n_x)) + elif isinstance(window, np.ndarray): + if window.shape != (n_x,): + raise ValueError(f"{window.shape=} != ({n_x},), i.e., window length " + + "is not equal to number of frequency bins!") + W = window.copy() # we do not want not modify the function's parameters else: - newshape[axis] = num - Y = np.zeros(newshape, X.dtype) - - # Copy positive frequency components (and Nyquist, if present) - N = min(num, Nx) - nyq = N // 2 + 1 # Slice index that includes Nyquist if present - sl = [slice(None)] * x.ndim - sl[axis] = slice(0, nyq) - Y[tuple(sl)] = X[tuple(sl)] - if not real_input: - # Copy negative frequency components - if N > 2: # (slice expression doesn't collapse to empty array) - sl[axis] = slice(nyq - N, None) - Y[tuple(sl)] = X[tuple(sl)] - - # Split/join Nyquist component(s) if present - # So far we have set Y[+N/2]=X[+N/2] - if N % 2 == 0: - if num < Nx: # downsampling - if real_input: - sl[axis] = slice(N//2, N//2 + 1) - Y[tuple(sl)] *= 2. - else: - # select the component of Y at frequency +N/2, - # add the component of X at -N/2 - sl[axis] = slice(-N//2, -N//2 + 1) - Y[tuple(sl)] += X[tuple(sl)] - elif Nx < num: # upsampling - # select the component at frequency +N/2 and halve it - sl[axis] = slice(N//2, N//2 + 1) - Y[tuple(sl)] *= 0.5 - if not real_input: - temp = Y[tuple(sl)] - # set the component at -N/2 equal to the component at +N/2 - sl[axis] = slice(num-N//2, num-N//2 + 1) - Y[tuple(sl)] = temp - - # Inverse transform - if real_input: - y = sp_fft.irfft(Y, num, axis=axis) - else: - y = sp_fft.ifft(Y, axis=axis, overwrite_x=True) - - y *= (float(num) / float(Nx)) - - if t is None: - return y - else: - new_t = np.arange(0, num) * (t[1] - t[0]) * Nx / float(num) + t[0] - return y, new_t + W = sp_fft.fftshift(get_window(window, n_x)) + + if domain == 'time' and np.isrealobj(x): # utilize more efficient one-sided FFT: + X = sp_fft.rfft(x) + if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 + n_X = X.shape[-1] + W[1:n_X] += W[:-n_X:-1] + W[1:n_X] /= 2 + X *= W[:n_X] # apply window + X = X[..., :m2] # extract relevant data + if m % 2 == 0 and num != n_x: # Account for unpaired bin at m//2: + X[..., m//2] *= 2 if num < n_x else 0.5 + x_r = sp_fft.irfft(X / s_fac, n=num, overwrite_x=True) + else: # use standard two-sided FFT: + X = sp_fft.fft(x) if domain == 'time' else x + if W is not None: + X = X * W # writing X *= W could modify parameter x + Y = np.zeros(X.shape[:-1] + (num,), dtype=X.dtype) + Y[..., :m2] = X[..., :m2] # copy part up to Nyquist frequency + if m2 < m: # == m > 2 + Y[..., m2-m:] = X[..., m2-m:] # copy negative frequency part + if m % 2 == 0: # Account for unpaired bin at m//2: + if num < n_x: # down-sampling: unite bin pair into one unpaired bin + Y[..., -m//2] += X[..., -m//2] + elif n_x < num: # up-sampling: split unpaired bin into bin pair + Y[..., m//2] /= 2 + Y[..., num-m//2] = Y[..., m//2] + x_r = sp_fft.ifft(Y / s_fac, n=num, overwrite_x=True) + + if x_r.ndim > 1: # moving active axis back to original position: + x_r = np.swapaxes(x_r, -1, axis) + if t is not None: + return x_r, t[0] + (t[1] - t[0]) * s_fac * np.arange(num) + return x_r def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), @@ -5048,11 +5053,12 @@ def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True): Parameters ---------- x : array_like - The signal to be downsampled, as an N-dimensional array. + The input signal made up of equidistant samples. If `x` is a multidimensional + array, the parameter `axis` specifies the time axis. q : int - The downsampling factor. When using IIR downsampling, it is recommended - to call `decimate` multiple times for downsampling factors higher than - 13. + The downsampling factor, which is a postive integer. When using IIR + downsampling, it is recommended to call `decimate` multiple times for + downsampling factors higher than 13. n : int, optional The order of the filter (1 less than the length for 'fir'). Defaults to 8 for 'iir' and 20 times the downsampling factor for 'fir'. @@ -5081,6 +5087,10 @@ def decimate(x, q, n=None, ftype='iir', axis=-1, zero_phase=True): Notes ----- + For non-integer downsampling factors, `~scipy.signal.resample` can be used. Consult + the `scipy.interpolate` module for methods of resampling signals with non-constant + sampling intervals. + The ``zero_phase`` keyword was added in 0.18.0. The possibility to use instances of ``dlti`` as ``ftype`` was added in 0.18.0. diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 2df8a047e039..7d1a7c2ede68 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -1392,19 +1392,30 @@ def test_basic(self, xp): num = 256 win = signal.get_window(('kaiser', 8.0), 160) assert_raises(ValueError, signal.resample, sig, num, window=win) + assert_raises(ValueError, signal.resample, sig, num, domain='INVALID') # Other degenerate conditions assert_raises(ValueError, signal.resample_poly, sig, 'yo', 1) assert_raises(ValueError, signal.resample_poly, sig, 1, 0) + assert_raises(ValueError, signal.resample_poly, sig, 1.3, 2) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1.3) assert_raises(ValueError, signal.resample_poly, sig, 2, 1, padtype='') assert_raises(ValueError, signal.resample_poly, sig, 2, 1, padtype='mean', cval=10) + assert_raises(ValueError, signal.resample_poly, sig, 2, 1, window=np.eye(2)) # test for issue #6505 - should not modify window.shape when axis ≠ 0 sig2 = np.tile(np.arange(160), (2, 1)) signal.resample(sig2, num, axis=-1, window=win) assert win.shape == (160,) + # Ensure coverage for parameter cval=None and cval != None: + x_ref = signal.resample_poly(sig, 2, 1) + x0 = signal.resample_poly(sig, 2, 1, padtype='constant') + x1 = signal.resample_poly(sig, 2, 1, padtype='constant', cval=0) + xp_assert_equal(x1, x_ref) + xp_assert_equal(x0, x_ref) + @pytest.mark.parametrize('window', (None, 'hamming')) @pytest.mark.parametrize('N', (20, 19)) @pytest.mark.parametrize('num', (100, 101, 10, 11)) @@ -1548,6 +1559,53 @@ def test_resample_methods(self, method, ext, padtype, xp): y2_true = np.array([1., 0.]) xp_assert_close(y2_test, y2_true, atol=1e-12) + + @pytest.mark.parametrize("n_in", (8, 9)) + @pytest.mark.parametrize("n_out", (3, 4)) + def test_resample_win_func(self, n_in, n_out): + """Test callable window function. """ + x_in = np.ones(n_in) + + def win(freqs): + """Scale input by 1/2""" + return 0.5 * np.ones_like(freqs) + + y0 = signal.resample(x_in, n_out) + y1 = signal.resample(x_in, n_out, window=win) + + xp_assert_close(2*y1, y0, atol=1e-12) + + @pytest.mark.parametrize("n_in", (6, 12)) + @pytest.mark.parametrize("n_out", (3, 4)) + def test__resample_param_t(self, n_in, n_out): + """Verify behavior for parameter `t`. + + Note that only `t[0]` and `t[1]` are utilized. + """ + t0, dt = 10, 2 + x_in = np.ones(n_in) + + y0 = signal.resample(x_in, n_out) + y1, t1 = signal.resample(x_in, n_out, t=[t0, t0+dt]) + t_ref = 10 + np.arange(len(y0)) * dt * n_in / n_out + + xp_assert_equal(y1, y0) # no influence of `t` + xp_assert_close(t1, t_ref, atol=1e-12) + + @pytest.mark.parametrize("n1", (2, 3, 7, 8)) + @pytest.mark.parametrize("n0", (2, 3, 7, 8)) + def test_resample_nyquist(self, n0, n1): + """Test behavior at Nyquist frequency to ensure issue #14569 is fixed. """ + f_ny = min(n0, n1) // 2 + tt = (np.arange(n_) / n_ for n_ in (n0, n1)) + x0, x1 = (np.cos(2 * np.pi * f_ny * t_) for t_ in tt) + + y1_r = signal.resample(x0, n1) + y1_c = signal.resample(x0 + 0j, n1) + + xp_assert_close(y1_r, x1, atol=1e-12) + xp_assert_close(y1_c.real, x1, atol=1e-12) + def test_poly_vs_filtfilt(self, xp): # Check that up=1.0 gives same answer as filtfilt + slicing random_state = np.random.RandomState(17) @@ -3498,6 +3556,25 @@ def test_compare_envelope_hilbert(self, X, xp): e_env = envelope(x, (None, None), residual=None) self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error") + def test_nyquist(self): + """Test behavior when input is a cosine at the Nyquist frequency. + + Resampling even length signals, requires accounting for unpaired bins at the + Nyquist frequency (consults the source code of `resample`). + + Since `envelope` excludes the Nyquist frequency from the envelope calculation, + only the residues need to be investigated. + """ + x4 = sp_fft.irfft([0, 0, 8]) # = [2, -2, 2, -2] + x6 = signal.resample(x4, num=6) # = [2, -1, -1, 2, -1, -1] + y6, y6_res = envelope(x4, n_out=6, residual='all') # real-valued case + z6, z6_res = envelope(x4 + 0j, n_out=6, residual='all') # complex-valued case + + xp_assert_close(y6, np.zeros(6), atol=1e-12) + xp_assert_close(y6_res, x6, atol=1e-12) + + xp_assert_close(z6, np.zeros(6, dtype=z6.dtype), atol=1e-12) + xp_assert_close(z6_res, x6.astype(z6.dtype), atol=1e-12) @skip_xp_backends(np_only=True) class TestPartialFractionExpansion: diff --git a/scipy/signal/tests/test_windows.py b/scipy/signal/tests/test_windows.py index 1dad96494b5a..4ac36edb3a84 100644 --- a/scipy/signal/tests/test_windows.py +++ b/scipy/signal/tests/test_windows.py @@ -824,7 +824,7 @@ def test_array_as_window(self, xp): sig = xp.arange(128) win = windows.get_window(('kaiser', 8.0), osfactor // 2, xp=xp) - with assert_raises(ValueError, match='must have the same length'): + with assert_raises(ValueError, match='^window.shape='): resample(sig, len(sig) * osfactor, window=win) def test_general_cosine(self, xp): From 2bcc6996f63805ed9e829ac4109b0af8c8923ec2 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Tue, 8 Apr 2025 16:41:56 +0200 Subject: [PATCH 033/251] MAINT: signal.resample/signal.envelope: Port functions to array API standard. --- scipy/signal/_signaltools.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 825c7b710e71..05d153b169e1 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -2871,6 +2871,7 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, >>> fg0.subplots_adjust(left=0.08, right=0.97, wspace=0.15) >>> plt.show() """ + xp = array_namespace(z) if not (-z.ndim <= axis < z.ndim): raise ValueError(f"Invalid parameter {axis=} for {z.shape=}!") if not (z.shape[axis] > 0): @@ -2893,12 +2894,12 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, f"for n={z.shape[axis]=} and {bp_in=}!") # moving active axis to end allows to use `...` for indexing: - z = np.moveaxis(z, axis, -1) + z = xp.moveaxis(z, axis, -1) - if np.iscomplexobj(z): + if xp.isdtype(z.dtype, 'complex floating'): Z = sp_fft.fft(z) else: # avoid calculating negative frequency bins for real signals: - Z = np.zeros_like(z, dtype=sp_fft.rfft(z.flat[:1]).dtype) + Z = xp.zeros_like(z, dtype=sp_fft.rfft(z.flat[:1]).dtype) Z[..., :n//2 + 1] = sp_fft.rfft(z) if bp.start > 0: # make signal analytic within bp_in band: Z[..., bp] *= 2 @@ -2910,8 +2911,8 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, bp_shift = slice(bp.start + n//2, bp.stop + n//2) z_bb = sp_fft.ifft(sp_fft.fftshift(Z, axes=-1)[..., bp_shift], n=n_out) * fak - z_env = np.abs(z_bb) if not squared else z_bb.real ** 2 + z_bb.imag ** 2 - z_env = np.moveaxis(z_env, -1, axis) + z_env = xp.abs(z_bb) if not squared else z_bb.real ** 2 + z_bb.imag ** 2 + z_env = xp.moveaxis(z_env, -1, axis) # Calculate the residual from the input bandpass filter: if residual is None: @@ -2926,13 +2927,14 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, else: Z[..., bp.start:], Z[..., 0:(n + 1) // 2] = 0, 0 - if np.iscomplexobj(z): # resample() accounts for unpaired bins: + if xp.isdtype(z.dtype, 'complex floating'): # resample accounts for unpaired bins: z_res = resample(Z, n_out, axis=-1, domain='freq') # ifft() with corrections else: # account for unpaired bin at m//2 before doing irfft(): if n_out != n and (m := min(n, n_out)) % 2 == 0: Z[..., m//2] *= 2 if n_out < n else 0.5 z_res = fak * sp_fft.irfft(Z, n=n_out) - return np.stack((z_env, np.moveaxis(z_res, -1, axis)), axis=0) + return xp.stack((z_env, xp.moveaxis(z_res, -1, axis)), axis=0) + def _cmplx_sort(p): """Sort roots based on magnitude. @@ -3657,9 +3659,10 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): if domain not in ('time', 'freq'): raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") - x = np.asarray(x) + xp = array_namespace(x, t) + x = xp.asarray(x) if x.ndim > 1: # moving active axis to end allows to use `...` in indexing: - x = np.swapaxes(x, axis, -1) + x = xp.moveaxis(x, axis, -1) n_x = x.shape[-1] # number of samples along the time/frequency axis s_fac = n_x / num # scaling factor represents sample interval dilatation m = min(num, n_x) # number of relevant frequency bins @@ -3669,15 +3672,15 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): W = None elif callable(window): W = window(sp_fft.fftfreq(n_x)) - elif isinstance(window, np.ndarray): + elif hasattr(window, 'shape'): # must be an array object if window.shape != (n_x,): raise ValueError(f"{window.shape=} != ({n_x},), i.e., window length " + "is not equal to number of frequency bins!") - W = window.copy() # we do not want not modify the function's parameters + W = xp.asarray(window, copy=True) # prevent modifying the function parameters else: W = sp_fft.fftshift(get_window(window, n_x)) - if domain == 'time' and np.isrealobj(x): # utilize more efficient one-sided FFT: + if domain == 'time' and xp.isdtype(x.dtype, 'real floating'): # utilize rfft(): X = sp_fft.rfft(x) if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 n_X = X.shape[-1] @@ -3692,7 +3695,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): X = sp_fft.fft(x) if domain == 'time' else x if W is not None: X = X * W # writing X *= W could modify parameter x - Y = np.zeros(X.shape[:-1] + (num,), dtype=X.dtype) + Y = xp.zeros(X.shape[:-1] + (num,), dtype=X.dtype) Y[..., :m2] = X[..., :m2] # copy part up to Nyquist frequency if m2 < m: # == m > 2 Y[..., m2-m:] = X[..., m2-m:] # copy negative frequency part @@ -3705,9 +3708,9 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): x_r = sp_fft.ifft(Y / s_fac, n=num, overwrite_x=True) if x_r.ndim > 1: # moving active axis back to original position: - x_r = np.swapaxes(x_r, -1, axis) + x_r = xp.moveaxis(x_r, -1, axis) if t is not None: - return x_r, t[0] + (t[1] - t[0]) * s_fac * np.arange(num) + return x_r, t[0] + (t[1] - t[0]) * s_fac * xp.arange(num) return x_r From 88efdf7763fc2cd2756c5cc1632f391ed83ced4f Mon Sep 17 00:00:00 2001 From: Dietrich Brunn <12721170+DietBru@users.noreply.github.com> Date: Fri, 11 Apr 2025 11:20:23 +0200 Subject: [PATCH 034/251] Apply suggestions from code review Co-authored-by: Evgeni Burovski --- scipy/signal/_signaltools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 05d153b169e1..94507e2d698c 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3535,12 +3535,12 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): window : array_like, callable, string, float, or tuple, optional If not ``None`` (default), specifies a filter in the Fourier domain, which is applied before resampling. I.e., the FFT ``X`` of `x` is calculated by - ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a sepctral + ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral windowing function ``W(f_X)`` which consumes the frequencies ``f_X = fftfreq(n_x, T)``. If `window` is a 1d array of length `n_x` then ``W=window``. - If `window` is a function then ``W = window(f_X)``. + If `window` is a callable then ``W = window(f_X)``. Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., ``W = fftshift(signal.get_window(window, n_x))``. @@ -3618,7 +3618,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): The following example illustrates that `~scipy.fft.rfft` / `~scipy.fft.irfft` - combination does not always produce the correct resampling result: + combination does not always produce the correct resampling result, while `resample` does: >>> from scipy.fft import irfft, rfft >>> from scipy.signal import resample From 76636f9e7c8b58f4bef3f286c61ed41359f91683 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Sat, 12 Apr 2025 13:05:38 +0200 Subject: [PATCH 035/251] MAINT: signal.resample: Improve docstr and apply review suggestions --- scipy/signal/_signaltools.py | 86 ++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 34 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 94507e2d698c..b7cffac57305 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3524,25 +3524,24 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): The number of samples of the resampled output signal. It may be larger or smaller than ``n_x``. t : array_like, optional - If `t` is not ``None`` (default), then the timestamps of the resampled signal - are also returned. `t` must contain at least the first two timestamps of the - input signal `x` (all others are ignored). The timestamps of the output signal - are determined by ``t[0] + T * n_x / num * np.arange(num)`` with - ``T = t[1] - t[0]``. + If `t` is not ``None``, then the timestamps of the resampled signal are also + returned. `t` must contain at least the first two timestamps of the input + signal `x` (all others are ignored). The timestamps of the output signal are + determined by ``t[0] + T * n_x / num * np.arange(num)`` with + ``T = t[1] - t[0]``. Default is ``None``. axis : int, optional The time/frequency axis of `x` along which the resampling take place. The Default is 0. window : array_like, callable, string, float, or tuple, optional - If not ``None`` (default), specifies a filter in the Fourier domain, which is - applied before resampling. I.e., the FFT ``X`` of `x` is calculated by - ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral - windowing function ``W(f_X)`` which consumes the frequencies - ``f_X = fftfreq(n_x, T)``. + If not ``None``, it specifies a filter in the Fourier domain, which is applied + before resampling. I.e., the FFT ``X`` of `x` is calculated by + ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral windowing + function ``W(f_X)`` which consumes the frequencies ``f_X = fftfreq(n_x, T)``. If `window` is a 1d array of length `n_x` then ``W=window``. If `window` is a callable then ``W = window(f_X)``. Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., - ``W = fftshift(signal.get_window(window, n_x))``. + ``W = fftshift(signal.get_window(window, n_x))``. Default is ``None``. domain : 'time' | 'freq', optional If set to ``'time'`` (default) then an FFT is applied to `x`, otherwise @@ -3561,8 +3560,10 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): See Also -------- - decimate : Downsample the signal after applying an FIR or IIR filter. - resample_poly : Resample using polyphase filtering and an FIR filter. + decimate : Downsample a (periodic/non-periodic) signal after applying an FIR + or IIR filter. + resample_poly : Resample a (periodic/non-periodic) signal using polyphase filtering + and an FIR filter. Notes ----- @@ -3617,33 +3618,46 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): >>> plt.show() - The following example illustrates that `~scipy.fft.rfft` / `~scipy.fft.irfft` - combination does not always produce the correct resampling result, while `resample` does: + The following example illustrates that a `~scipy.fft.rfft` / `~scipy.fft.irfft` + combination does not always produce the correct resampling result, while + `resample` does: - >>> from scipy.fft import irfft, rfft + >>> import numpy as np + >>> from scipy.fft import irfft, fft, fftfreq, rfft >>> from scipy.signal import resample ... >>> n0, n1 = 8, 4 # number of samples - >>> x0 = irfft([0, 0, 8, 0, 0], n=n0) # input signal - >>> x1 = resample(x0, num=n1) - >>> y1 = irfft(rfft(x0), n=n1) * n1/n0 - >>> for x_, n_ in zip((x0, x1, y1), ('x0', 'x1', 'y1')): - ... print(f"{n_} = {x_}") - x0 = [ 2. 0. -2. 0. 2. 0. -2. 0.] - x1 = [ 2. -2. 2. -2.] - y1 = [ 1. -1. 1. -1.] - - It can be easily verified that ``x1`` is correct and ``y1`` is not. To produce - correct results, `resample` correctly accounts for unpaired frequency bins. Here, - ``fft(x1)`` has the frequency bins ``[0, 1, -2, -1]`` (which can be determined with - `~scipy.fft.fftfreq`), where the entry ``-2`` does not have a positive pair ``+2``. - For that reason, `resample` scaled this unpaired bin by 1/2. + >>> t0, t1 = (np.linspace(0, 2, n_, endpoint=False) for n_ in (n0, n1)) # in s + >>> x0, x1 = (np.cos(2*np.pi*t_) for t_ in (t0, t1)) # 1 Hz cosine signal + >>> y1_r = resample(x0, num=n1) + >>> np.allclose(y1_r, x1) # correct result, as expected + True + >>> y1_f = irfft(rfft(x0), n=n1) * n1/n0 + >>> np.allclose(y1_f, x1) # wrong result + False + >>> # Compare FFTs: + >>> fftfreq(n0, t0[1]-t0[0]) # frequencies of fft(x0) + array([ 0. , 0.5, 1. , 1.5, -2. , -1.5, -1. , -0.5]) + >>> np.round(fft(x0), 3) # FFT of x0 + array([-0.-0.j, -0.+0.j, 4.-0.j, 0.+0.j, 0.-0.j, 0.-0.j, 4.+0.j, -0.-0.j]) + >>> fftfreq(n1, t1[1]-t1[0]) # frequencies of fft(x1) and fft(y1_f) + array([ 0. , 0.5, -1. , -0.5]) + >>> np.round(fft(x1), 3) # reference FFT + array([0.+0.j, 0.+0.j, 4.+0.j, 0.+0.j]) + >>> np.round(fft(y1_f), 3) # irfft/rfft off by factor 2 in the Nyuist frequency + array([0.+0.j, 0.+0.j, 2.+0.j, 0.+0.j]) + + The reason for the different results lies in `resample` correctly treating unpaired + frequency bins. I.e., the input `x1` has a bin pair ±1 Hz, whereas the output has + only one unpaired bin at -1 Hz, which demands rescaling of that bin. Special + treatment is required if ``n_x != num`` and ``min(n_x, num)`` is even. If the bin + values at `±m` are zero, the, obviously, no special treatment is needed. Consult + the source code of `resample` for details. The following code snippet shows how to use `resample_poly` to speed up the down-sampling: >>> import numpy as np - >>> from scipy.fft import next_fast_len >>> from scipy.signal import resample, resample_poly ... >>> n0 = 19937 # number of input samples - prime @@ -3651,10 +3665,14 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): >>> x0 = np.random.rand(n0) # input signal ... >>> y1 = resample(x0, n1) # slow due to n0 being prime - >>> y1_p = resample(resample_poly(x0, 1, n0 // n1), n1) # This is faster + >>> # This is faster: + >>> y1_p = resample(resample_poly(x0, 1, n0 // n1, padtype='wrap'), n1) Note that the `y1` and `y1_p` are not identical due to the differences in the - utilized antialiasing filter. + utilized antialiasing filter in each function. In both functions the antialiasing + filter can be customized by modifying the `window` parameter, though `resample` + interprets it as a spectral window function, whereas `resample_poly` assumes it to + be some form of impulse response. """ if domain not in ('time', 'freq'): raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") @@ -3680,7 +3698,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): else: W = sp_fft.fftshift(get_window(window, n_x)) - if domain == 'time' and xp.isdtype(x.dtype, 'real floating'): # utilize rfft(): + if domain == 'time' and not xp.isdtype(x.dtype, 'complex floating'): # use rfft(): X = sp_fft.rfft(x) if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 n_X = X.shape[-1] From 0a8095b64dd7bc39b19627ab7b56ef9a43edcb4d Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Thu, 17 Apr 2025 13:40:58 +0200 Subject: [PATCH 036/251] TST: fix `boxcox_llf` test failure on main This was caused by a recent change to add array API support, the used `xp_assert_close` has an rtol that's more tight than `np.testing.assert_allclose` (5.96e-8 vs. 1e-7), so just reset the precision to the original value to make the test pass again. Failure on Arch Linux x86-64 in a conda-forge environment: ``` _________________________ TestBoxcox_llf.test_instability_gh20021[numpy] _________________________ scipy/stats/tests/test_morestats.py:2047: in test_instability_gh20021 xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64)) E AssertionError: E Not equal to tolerance rtol=5.96046e-08, atol=0 E E Mismatched elements: 1 / 1 (100%) E Max absolute difference among violations: 1.41313445e-06 E Max relative difference among violations: 9.22169979e-08 E ACTUAL: array(-15.324014) E DESIRED: array(-15.324013) ``` [skip ci] --- scipy/stats/tests/test_morestats.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index f54e7ab3e627..d54d48b78ffa 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2044,7 +2044,8 @@ def test_instability_gh20021(self, xp): llf = stats.boxcox_llf(1e-8, data) # The expected value was computed with mpsci, set mpmath.mp.dps=100 # expect float64 output for integer input - xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64)) + xp_assert_close(llf, xp.asarray(-15.32401272869016598, dtype=xp.float64), + rtol=1e-7) def test_axis(self, xp): data = xp.asarray([[100, 200], [300, 400]]) From 110213df42a1e5ffe2a62feea1b4906e1ae8d4c8 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 17 Apr 2025 14:06:07 -0700 Subject: [PATCH 037/251] MAINT: _lib: eliminate try/excepts in EIM (#22848) --- scipy/_lib/_elementwise_iterative_method.py | 29 +++++++-------------- scipy/stats/_continued_fraction.py | 1 + 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/scipy/_lib/_elementwise_iterative_method.py b/scipy/_lib/_elementwise_iterative_method.py index 82910af1bf54..14c5c1949ddf 100644 --- a/scipy/_lib/_elementwise_iterative_method.py +++ b/scipy/_lib/_elementwise_iterative_method.py @@ -290,14 +290,10 @@ def _check_termination(work, res, res_work_pairs, active, check_termination, if not preserve_shape: # compress the arrays to avoid unnecessary computation for key, val in work.items(): - # Need to find a better way than these try/excepts - # Somehow need to keep compressible numerical args separate - if key == 'args': + # `continued_fraction` hacks `n`; improve if this becomes a problem + if key in {'args', 'n'}: continue - try: - work[key] = val[proceed] - except (IndexError, TypeError, KeyError): # not a compressible array - work[key] = val + work[key] = val[proceed] if getattr(val, 'ndim', 0) > 0 else val work.args = [arg[proceed] for arg in work.args] return active @@ -317,24 +313,17 @@ def _update_active(work, res, res_work_pairs, active, mask, preserve_shape, xp): active_mask = xpx.at(active_mask)[active].set(True) active_mask = active_mask & mask for key, val in update_dict.items(): - try: - res[key] = xpx.at(res[key])[active_mask].set(val[active_mask]) - except (IndexError, TypeError, KeyError): - res[key] = xpx.at(res[key])[active_mask].set(val) + val = val[active_mask] if getattr(val, 'ndim', 0) > 0 else val + res[key] = xpx.at(res[key])[active_mask].set(val) else: active_mask = active[mask] for key, val in update_dict.items(): - try: - res[key] = xpx.at(res[key])[active_mask].set(val[mask]) - except (IndexError, TypeError, KeyError): - res[key] = xpx.at(res[key])[active_mask].set(val) + val = val[mask] if getattr(val, 'ndim', 0) > 0 else val + res[key] = xpx.at(res[key])[active_mask].set(val) else: for key, val in update_dict.items(): - if preserve_shape: - try: - val = val[active] - except (IndexError, TypeError, KeyError): - pass + if preserve_shape and getattr(val, 'ndim', 0) > 0: + val = val[active] res[key] = xpx.at(res[key])[active].set(val) diff --git a/scipy/stats/_continued_fraction.py b/scipy/stats/_continued_fraction.py index efa0411608ab..4966b1c99ed0 100644 --- a/scipy/stats/_continued_fraction.py +++ b/scipy/stats/_continued_fraction.py @@ -8,6 +8,7 @@ from scipy import special # Todo: +# Avoid special-casing key 'n' in _lib._elementwise_iterative_method::_check_termination # Rearrange termination condition to allow absolute and relative tolerances? # Interpret/return |f_n - f_{n-1}| as an error estimate? # Return gracefully for size=0 arrays From 052981390aeacc94a45c620e00a4b1e2224c0606 Mon Sep 17 00:00:00 2001 From: Maxwell Bileschi Date: Thu, 17 Apr 2025 17:17:17 -0400 Subject: [PATCH 038/251] MAINT: spatial.pdist: make dimensionality error more descriptive (#22855) * Make pdist dimensionality error more descriptive * Update scipy/spatial/distance.py --------- Co-authored-by: Lucas Colley --- scipy/spatial/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index 8ed75b56d0d6..8f9cb116b6c0 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -2294,7 +2294,7 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): X = _asarray(X) if X.ndim != 2: - raise ValueError('A 2-dimensional array must be passed.') + raise ValueError(f'A 2-dimensional array must be passed. (Shape was {X.shape}).') n = X.shape[0] return xpx.lazy_apply(_np_pdist, X, out, From d1f37b6bddc02676c34f1116776c6c746936461c Mon Sep 17 00:00:00 2001 From: pmav99 Date: Sat, 19 Apr 2025 00:22:50 +0300 Subject: [PATCH 039/251] DOC: Fix typo in `ndimage.generic_gradient_magnitude()` [docs only] --- scipy/ndimage/_filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/ndimage/_filters.py b/scipy/ndimage/_filters.py index e26b9329570b..36700ec8a000 100644 --- a/scipy/ndimage/_filters.py +++ b/scipy/ndimage/_filters.py @@ -1146,7 +1146,7 @@ def generic_gradient_magnitude(input, derivative, output=None, Returns ------- - generic_gradient_matnitude : ndarray + generic_gradient_magnitude : ndarray Filtered array. Has the same shape as `input`. """ From 6dbfa8c1463e33129cff2dabb01b67174a9bdf32 Mon Sep 17 00:00:00 2001 From: Elia Tomasi <68066172+tomasel@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:22:25 +0200 Subject: [PATCH 040/251] MAINT/TST: rewording of "ties" into "tied pairs" for clearer meaning (#22859) * Rewording of "ties" into "tied pairs" for clearer meaning This is a very simple modification that I thought could be useful for people new to this metric. The use of "ties" in this context is misleading, possibly hinting to only counting tied elements and not actually all the possible tied pairs. This looks more appropriate and also keeps the phrase consistent, together with "concordant pairs" and "discordant pairs". * lint --------- Co-authored-by: Lucas Colley --- scipy/stats/_stats_py.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 2f7e8e1af065..84aa28749a9e 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -5617,8 +5617,8 @@ def kendalltau(x, y, *, nan_policy='propagate', tau_c = 2 (P - Q) / (n**2 * (m - 1) / m) where P is the number of concordant pairs, Q the number of discordant - pairs, T the number of ties only in `x`, and U the number of ties only in - `y`. If a tie occurs for the same pair in both `x` and `y`, it is not + pairs, T the number of tied pairs only in `x`, and U the number of tied pairs only + in `y`. If a tie occurs for the same pair in both `x` and `y`, it is not added to either T or U. n is the total number of samples, and m is the number of unique values in either `x` or `y`, whichever is smaller. From a9fa5e981678842091b81328584e24aa6cc68754 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 19 Apr 2025 17:04:39 -0700 Subject: [PATCH 041/251] TST: integrate/spatial: make fail_slow allowances [skip ci] --- scipy/integrate/tests/test_quadrature.py | 2 ++ scipy/spatial/tests/test_kdtree.py | 1 + 2 files changed, 3 insertions(+) diff --git a/scipy/integrate/tests/test_quadrature.py b/scipy/integrate/tests/test_quadrature.py index b7179903ee11..3cd2238b76ed 100644 --- a/scipy/integrate/tests/test_quadrature.py +++ b/scipy/integrate/tests/test_quadrature.py @@ -631,6 +631,7 @@ def _get_theoretical_diff_between_simps_and_cum_simps(self, y, x): # `simpson` uses the trapezoidal rule return theoretical_difference + @pytest.mark.fail_slow(10) @pytest.mark.thread_unsafe @pytest.mark.slow @given( @@ -662,6 +663,7 @@ def simpson_reference(y): res[..., 1:], ref[..., 1:] + theoretical_difference[..., 1:], atol=1e-16 ) + @pytest.mark.fail_slow(10) @pytest.mark.thread_unsafe @pytest.mark.slow @given( diff --git a/scipy/spatial/tests/test_kdtree.py b/scipy/spatial/tests/test_kdtree.py index 8b912f249a22..c1c41db04ebe 100644 --- a/scipy/spatial/tests/test_kdtree.py +++ b/scipy/spatial/tests/test_kdtree.py @@ -426,6 +426,7 @@ def test_random_ball_vectorized(kdtree_type): assert_(isinstance(r[0, 0], list)) +@pytest.mark.fail_slow(5) def test_query_ball_point_multithreading(kdtree_type): np.random.seed(0) n = 5000 From ab22ca35d73f982bd4d13d9f4936ad3e25d2fdb8 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 20 Apr 2025 09:49:48 +0200 Subject: [PATCH 042/251] REV: TST: stats: don't encapsulate pytest.warns Reverts gh-22771, because it should no longer be needed after the pytest-run-parallel 0.4.0 release. --- scipy/stats/tests/test_stats.py | 60 +++++++++++---------------------- 1 file changed, 19 insertions(+), 41 deletions(-) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9260e64e0704..9a8e6aaf1e65 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -83,6 +83,13 @@ lazy_xp_function(stats.tmax, static_argnames=("inclusive", "axis")) +def eager_warns(x, warning_type, match=None): + """pytest.warns context manager, but only if x is not a lazy array.""" + if is_lazy_array(x): + return contextlib.nullcontext() + return pytest.warns(warning_type, match=match) + + class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision @@ -3062,11 +3069,7 @@ def test_zscore_nan_raise(self, xp): def test_zscore_constant_input_1d(self, xp): x = xp.asarray([-0.087] * 3) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z = stats.zscore(x) xp_assert_equal(z, xp.full(x.shape, xp.nan)) @@ -3077,16 +3080,12 @@ def test_zscore_constant_input_1d(self, xp): def test_zscore_constant_input_2d(self, xp): x = xp.asarray([[10.0, 10.0, 10.0, 10.0], [10.0, 11.0, 12.0, 13.0]]) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z0 = stats.zscore(x, axis=0) xp_assert_close(z0, xp.asarray([[xp.nan, -1.0, -1.0, -1.0], [xp.nan, 1.0, 1.0, 1.0]])) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z1 = stats.zscore(x, axis=1) xp_assert_equal(z1, xp.stack([xp.asarray([xp.nan, xp.nan, xp.nan, xp.nan]), stats.zscore(x[1, :])])) @@ -3095,7 +3094,7 @@ def test_zscore_constant_input_2d(self, xp): xp_assert_equal(z, xp.reshape(stats.zscore(xp.reshape(x, (-1,))), x.shape)) y = xp.ones((3, 6)) - with warn_ctx: + with eager_warns(y, RuntimeWarning, match="Precision loss occurred..."): z = stats.zscore(y, axis=None) xp_assert_equal(z, xp.full(y.shape, xp.asarray(xp.nan))) @@ -3106,17 +3105,14 @@ def test_zscore_constant_input_2d_nan_policy_omit(self, xp): [10.0, 12.0, xp.nan, 10.0]]) s = (3/2)**0.5 s2 = 2**0.5 - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z0 = stats.zscore(x, nan_policy='omit', axis=0) xp_assert_close(z0, xp.asarray([[xp.nan, -s, -1.0, xp.nan], [xp.nan, 0, 1.0, xp.nan], [xp.nan, s, xp.nan, xp.nan]])) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."): z1 = stats.zscore(x, nan_policy='omit', axis=1) xp_assert_close(z1, xp.asarray([[xp.nan, xp.nan, xp.nan, xp.nan], [-s, 0, s, xp.nan], @@ -3734,25 +3730,17 @@ def test_skew_propagate_nan(self, xp): def test_skew_constant_value(self, xp): # Skewness of a constant input should be NaN (gh-16061) a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(a) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - with warn_ctx: + with eager_warns(a, RuntimeWarning, match="Precision loss occurred"): xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a*2.**50), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a/2.**50), xp.asarray(xp.nan)) - with warn_ctx: xp_assert_equal(stats.skew(a, bias=False), xp.asarray(xp.nan)) - # # similarly, from gh-11086: - a = xp.asarray([14.3]*7) - with warn_ctx: + # # similarly, from gh-11086: + a = xp.asarray([14.3]*7) xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) - a = 1. + xp.arange(-3., 4)*1e-16 - with warn_ctx: + a = 1. + xp.arange(-3., 4)*1e-16 xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) @skip_xp_backends(eager_only=True) @@ -3854,17 +3842,10 @@ def test_kurtosis_propagate_nan(self): def test_kurtosis_constant_value(self, xp): # Kurtosis of a constant input should be NaN (gh-16061) a = xp.asarray([-0.27829495]*10) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(a) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(a, RuntimeWarning, match="Precision loss occurred"): assert xp.isnan(stats.kurtosis(a, fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a * float(2**50), fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False)) - with warn_ctx: assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False)) @pytest.mark.parametrize('axis', [-1, 0, 2, None]) @@ -6110,11 +6091,8 @@ def test_ttest_ind_zero_division(self, xp): # test zero division problem x = xp.zeros(3) y = xp.ones(3) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(x) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - with warn_ctx: + with eager_warns(x, RuntimeWarning, match="Precision loss occurred"): t, p = stats.ttest_ind(x, y, equal_var=False) xp_assert_equal(t, xp.asarray(-xp.inf)) From 5c3c682f97f0e04f6eb9f88a95a31da91f5232f6 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 20 Apr 2025 09:51:35 +0200 Subject: [PATCH 043/251] TST: move the `eager_warns` context manager to `_lib` and mark it as thread-unsafe It being not thread-safe is true for Python 3.13 at least, because `warnings` isn't thread-safe at all. This *should* be fixed in Python 3.14, but we'll have to test that thoroughly when it's available, especially since pytest itself doesn't claim it is thread-safe and isn't planning on changing that. --- scipy/_lib/_array_api.py | 26 +++++++++++++++++++------- scipy/stats/tests/test_morestats.py | 9 ++++++--- scipy/stats/tests/test_stats.py | 19 ++++--------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index a724ff74144a..a73ad16f4af9 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -6,9 +6,10 @@ The SciPy use case of the Array API is described on the following page: https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy """ -import os +import contextlib import dataclasses import functools +import os import textwrap from collections.abc import Generator, Iterable, Iterator @@ -39,8 +40,8 @@ __all__ = [ '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal', - 'default_xp', 'is_lazy_array', 'is_marray', - 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch', + 'default_xp', 'eager_warns', 'is_lazy_array', 'is_marray', + 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch', 'SCIPY_ARRAY_API', 'SCIPY_DEVICE', 'scipy_namespace_for', 'xp_assert_close', 'xp_assert_equal', 'xp_assert_less', 'xp_copy', 'xp_device', 'xp_ravel', 'xp_size', @@ -246,7 +247,7 @@ def xp_copy(x: Array, *, xp: ModuleType | None = None) -> Array: @contextmanager def default_xp(xp: ModuleType) -> Generator[None, None, None]: """In all ``xp_assert_*`` and ``assert_*`` function calls executed within this - context manager, test by default that the array namespace is + context manager, test by default that the array namespace is the provided across all arrays, unless one explicitly passes the ``xp=`` parameter or ``check_namespace=False``. @@ -260,6 +261,17 @@ def default_xp(xp: ModuleType) -> Generator[None, None, None]: _default_xp_ctxvar.reset(token) +def eager_warns(x, warning_type, match=None): + """pytest.warns context manager, but only if x is not a lazy array.""" + import pytest + # This attribute is interpreted by pytest-run-parallel, ensuring that tests that use + # `eager_warns` aren't run in parallel (since pytest.warns isn't thread-safe). + __thread_safe__ = False # noqa: F841 + if is_lazy_array(x): + return contextlib.nullcontext() + return pytest.warns(warning_type, match=match) + + def _strict_check(actual, desired, xp, *, check_namespace=True, check_dtype=True, check_shape=True, check_0d=True): @@ -270,7 +282,7 @@ def _strict_check(actual, desired, xp, *, xp = _default_xp_ctxvar.get() except LookupError: xp = array_namespace(desired) - + if check_namespace: _assert_matching_namespace(actual, desired, xp) @@ -486,7 +498,7 @@ def xp_result_type(*args, force_floating=False, xp): standard `result_type` in a few ways: - There is a `force_floating` argument that ensures that the result type - is floating point, even when all args are integer. + is floating point, even when all args are integer. - When a TypeError is raised (e.g. due to an unsupported promotion) and `force_floating=True`, we define a custom rule: use the result type of the default float and any other floats passed. See @@ -542,7 +554,7 @@ def xp_promote(*args, broadcast=False, force_floating=False, xp): This function accepts array-like iterables, which are immediately converted to the namespace's arrays before result type calculation. Consequently, the result dtype may be different when an argument is `1.` vs `[1.]`. - + See Also -------- xp_result_type diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index d54d48b78ffa..628ca9a039dc 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -2,10 +2,11 @@ # # Further enhancements and tests added by numerous SciPy developers. # +import contextlib import math -import warnings +import re import sys -import contextlib +import warnings from functools import partial import numpy as np @@ -16,7 +17,7 @@ suppress_warnings) import pytest from pytest import raises as assert_raises -import re + from scipy import optimize, stats, special from scipy.stats._morestats import _abw_state, _get_As_weibull, _Avals_weibull from .common_tests import check_named_results @@ -365,6 +366,7 @@ def test_weibull_min_case_B(self): with pytest.raises(ValueError, match=message): stats.anderson(x, 'weibull_min') + @pytest.mark.thread_unsafe def test_weibull_warning_error(self): # Check for warning message when there are too few observations # This is also an example in which an error occurs during fitting @@ -2024,6 +2026,7 @@ def test_2d_input(self, xp): llf2 = stats.boxcox_llf(lmbda, np.vstack([x, x]).T) xp_assert_close(xp.asarray([llf, llf]), xp.asarray(llf2), rtol=1e-12) + @pytest.mark.thread_unsafe def test_empty(self, xp): message = "One or more sample arguments is too small..." context = (pytest.warns(SmallSampleWarning, match=message) if is_numpy(xp) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 9a8e6aaf1e65..26d59c0b814f 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -40,9 +40,9 @@ LinregressResult, _xp_mean, _xp_var, _SimpleChi2) from scipy._lib._util import AxisError from scipy.conftest import skip_xp_invalid_arg -from scipy._lib._array_api import (array_namespace, is_lazy_array, is_numpy, - is_torch, xp_default_dtype, xp_size, SCIPY_ARRAY_API, - make_skip_xp_backends) +from scipy._lib._array_api import (array_namespace, eager_warns, is_lazy_array, + is_numpy, is_torch, xp_default_dtype, xp_size, + SCIPY_ARRAY_API, make_skip_xp_backends) from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal import scipy._lib.array_api_extra as xpx from scipy._lib.array_api_extra.testing import lazy_xp_function @@ -83,13 +83,6 @@ lazy_xp_function(stats.tmax, static_argnames=("inclusive", "axis")) -def eager_warns(x, warning_type, match=None): - """pytest.warns context manager, but only if x is not a lazy array.""" - if is_lazy_array(x): - return contextlib.nullcontext() - return pytest.warns(warning_type, match=match) - - class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision @@ -2977,11 +2970,7 @@ def test_degenerate_input(self, xp): scores = xp.arange(3) compare = xp.ones(3) ref = xp.asarray([-xp.inf, xp.nan, xp.inf]) - warn_ctx = ( - contextlib.nullcontext() if is_lazy_array(scores) - else pytest.warns(RuntimeWarning, match="Precision loss occurred...")) - - with warn_ctx: + with eager_warns(scores, RuntimeWarning, match="Precision loss occurred..."): res = stats.zmap(scores, compare) xp_assert_equal(res, ref) From 14abfae987012e23768c99f86b05b60081f52e8b Mon Sep 17 00:00:00 2001 From: Eric Zitong Zhou Date: Mon, 21 Apr 2025 00:38:07 -0700 Subject: [PATCH 044/251] TST: optimize.VectorFunction: add test for J0=None branch in _VectorHessWrapper._fd_hess --- scipy/optimize/tests/test_differentiable_functions.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scipy/optimize/tests/test_differentiable_functions.py b/scipy/optimize/tests/test_differentiable_functions.py index 1f8f53b794fd..9351497665a3 100644 --- a/scipy/optimize/tests/test_differentiable_functions.py +++ b/scipy/optimize/tests/test_differentiable_functions.py @@ -780,6 +780,15 @@ def test_finite_difference_hess_linear_operator(self): assert_array_equal(ex.nhev, nhev) assert_array_equal(analit.nhev+approx.nhev, nhev) + # Test VectorFunction.hess_wrapped with J0=None + x = np.array([1.5, 0.5]) + v = np.array([1.0, 2.0]) + njev_before = approx.hess_wrapped.njev + H = approx.hess_wrapped(x, v, J0=None) + assert isinstance(H, LinearOperator) + # The njev counter should be incremented by exactly 1 + assert approx.hess_wrapped.njev == njev_before + 1 + def test_fgh_overlap(self): # VectorFunction.fun/jac should return copies to internal attributes ex = ExVectorialFunction() From 5bc3d8814d566ef328f41cfa69ccd797c68b0d02 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Mon, 21 Apr 2025 08:49:03 +0100 Subject: [PATCH 045/251] MAINT: linalg.svd: raise correct error message for GESDD when info=-4 (#22864) --- scipy/linalg/_decomp_svd.py | 3 +++ scipy/linalg/tests/test_decomp.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/scipy/linalg/_decomp_svd.py b/scipy/linalg/_decomp_svd.py index 6155afdb478c..4f8a9a2ccded 100644 --- a/scipy/linalg/_decomp_svd.py +++ b/scipy/linalg/_decomp_svd.py @@ -169,6 +169,9 @@ def svd(a, full_matrices=True, compute_uv=True, overwrite_a=False, if info > 0: raise LinAlgError("SVD did not converge") if info < 0: + if lapack_driver == "gesdd" and info == -4: + msg = "A has a NaN entry" + raise ValueError(msg) raise ValueError(f'illegal value in {-info}th argument of internal gesdd') if compute_uv: return u, s, v diff --git a/scipy/linalg/tests/test_decomp.py b/scipy/linalg/tests/test_decomp.py index d6c85bbc3223..37200f67847c 100644 --- a/scipy/linalg/tests/test_decomp.py +++ b/scipy/linalg/tests/test_decomp.py @@ -1224,6 +1224,13 @@ def test_svd_gesdd_nofegfault(): svd(df) +def test_gesdd_nan_error_message(): + A = np.eye(2) + A[0, 0] = np.nan + with pytest.raises(ValueError, match="NaN"): + svd(A, check_finite=False) + + class TestSVDVals: @pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64]) From 9a5160795f220753927c7f95ccdd8718b4a8741b Mon Sep 17 00:00:00 2001 From: Matthias Urlichs Date: Wed, 23 Apr 2025 12:31:56 +0200 Subject: [PATCH 046/251] Merge pull request #22869 from smurfix/main BUG: optimize._highspy: don't import from inside a C module --- scipy/optimize/_linprog_highs.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scipy/optimize/_linprog_highs.py b/scipy/optimize/_linprog_highs.py index 468f840b2d02..98a70ef8b1d4 100644 --- a/scipy/optimize/_linprog_highs.py +++ b/scipy/optimize/_linprog_highs.py @@ -23,13 +23,13 @@ HighsDebugLevel, ObjSense, HighsModelStatus, -) -from ._highspy._core.simplex_constants import ( - SimplexStrategy, - SimplexEdgeWeightStrategy, + simplex_constants as s_c, # [1] ) from scipy.sparse import csc_array, vstack, issparse +# [1]: Directly importing from "._highspy._core.simplex_constants" +# causes problems when reloading. +# See https://github.com/scipy/scipy/pull/22869 for details. def _highs_to_scipy_status_message(highs_status, highs_message): """Converts HiGHS status number/message to SciPy status number/message""" @@ -293,13 +293,13 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, simplex_dual_edge_weight_strategy, 'simplex_dual_edge_weight_strategy', choices={'dantzig': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDantzig, 'devex': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyDevex, 'steepest-devex': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategyChoose, 'steepest': \ - SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge, + s_c.SimplexEdgeWeightStrategy.kSimplexEdgeWeightStrategySteepestEdge, None: None}) c, A_ub, b_ub, A_eq, b_eq, bounds, x0, integrality = lp @@ -334,7 +334,7 @@ def _linprog_highs(lp, solver, time_limit=None, presolve=True, 'primal_feasibility_tolerance': primal_feasibility_tolerance, 'simplex_dual_edge_weight_strategy': simplex_dual_edge_weight_strategy_enum, - 'simplex_strategy': SimplexStrategy.kSimplexStrategyDual, + 'simplex_strategy': s_c.SimplexStrategy.kSimplexStrategyDual, 'ipm_iteration_limit': maxiter, 'simplex_iteration_limit': maxiter, 'mip_rel_gap': mip_rel_gap, From 1e8316ca33f0963341c33b39a734b674444e0cab Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 23 Apr 2025 13:54:13 +0200 Subject: [PATCH 047/251] TST: mark `Pearsonr.test_bootstrap_ci` and `test_beta` as slow `pytest-fail-slow` isn't happy with the `pearsonr` test, and this is indeed quite slow: ``` _____________________ TestPearsonr.test_bootstrap_ci[less] _____________________ Test passed but took too long to run: Duration 1.347333775000152s > 1.0s ___________________ TestPearsonr.test_bootstrap_ci[greater] ____________________ Test passed but took too long to run: Duration 1.3463727320004182s > 1.0s __________________ TestPearsonr.test_bootstrap_ci[two-sided] ___________________ Test passed but took too long to run: Duration 1.3466387099997519s > 1.0s ``` The `mpmath::test_beta` is close to the 1 second checkpoint, and timing jitted will make it fail. Furthermore, one Linux-only `sparsetools` test is given a larger margin before failing; it takes about a second but we shouldn't mark it slow to avoid losing test coverage (all other tests are already marked as slow for that overflow check). mark a few more tests --- scipy/sparse/tests/test_sparsetools.py | 1 + scipy/special/tests/test_mpmath.py | 1 + scipy/stats/tests/test_stats.py | 1 + 3 files changed, 3 insertions(+) diff --git a/scipy/sparse/tests/test_sparsetools.py b/scipy/sparse/tests/test_sparsetools.py index 6c9b275a46e6..3a254d449eca 100644 --- a/scipy/sparse/tests/test_sparsetools.py +++ b/scipy/sparse/tests/test_sparsetools.py @@ -120,6 +120,7 @@ def setup_method(self): def teardown_method(self): gc.collect() + @pytest.mark.fail_slow(2) # keep in fast set, only non-slow test def test_coo_todense(self): # Check *_todense routines (cf. gh-2179) # diff --git a/scipy/special/tests/test_mpmath.py b/scipy/special/tests/test_mpmath.py index 43e84e444f2e..0cfa9126050f 100644 --- a/scipy/special/tests/test_mpmath.py +++ b/scipy/special/tests/test_mpmath.py @@ -321,6 +321,7 @@ def evf(mu, nu, x): # beta # ------------------------------------------------------------------------------ +@pytest.mark.slow @check_version(mpmath, '0.15') def test_beta(): np.random.seed(1234) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 26d59c0b814f..49a08e604949 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -651,6 +651,7 @@ def test_resampling_pvalue(self, method_name, alternative): assert_equal(res2.statistic, res.statistic) assert_equal(res2.pvalue, res.pvalue) + @pytest.mark.slow @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided')) def test_bootstrap_ci(self, alternative): rng = np.random.default_rng(2462935790378923) From b31d8dadf2a43696d5183302b6f7d49e14f5cca9 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Tue, 14 Jan 2025 17:10:00 +0100 Subject: [PATCH 048/251] CI: limit pytest-fail-slow usage to a single CI job Avoids spurious failures on PRs, which happen frequently. Closes gh-20806 --- .github/workflows/linux.yml | 10 +++++++--- .github/workflows/linux_intel_oneAPI.yml | 2 +- .github/workflows/windows.yml | 16 ++++++++-------- .github/workflows/windows_intel_oneAPI.yml | 2 +- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b8faec729b26..0914548a2e3a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -233,7 +233,7 @@ jobs: ################################################################################# gcc9: # Purpose is to examine builds with oldest-supported gcc and test with pydata/sparse. - name: Oldest GCC & pydata/sparse, fast, py3.11/npMin, pip+pytest + name: Oldest GCC & pydata/sparse, full, py3.11/npMin, pip+pytest needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -574,7 +574,7 @@ jobs: ################################################################################# test_aarch64: - name: aarch64, fast, py3.12/npAny, pip+pytest + name: aarch64, fast, fail slow, py3.12/npAny, pip+pytest needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -598,6 +598,10 @@ jobs: - name: Install Python packages run: | python -m pip install -r requirements/build.txt -r requirements/test.txt + # We want to check for test timing only in a single job, on Linux, running the + # fast test suite. This is that job. See gh-20806 for previous issues + # after running this job on Windows and in multiple jobs. + python -m pip install pytest-fail-slow - name: Install SciPy run: | @@ -607,4 +611,4 @@ jobs: run: | export OMP_NUM_THREADS=2 cd .. - pytest --pyargs scipy -m 'not slow' + pytest --pyargs scipy -m 'not slow' --durations=0 --durations-min=0.5 --fail-slow=1.0 diff --git a/.github/workflows/linux_intel_oneAPI.yml b/.github/workflows/linux_intel_oneAPI.yml index 8d7cf44c842a..ab3a5a618d28 100644 --- a/.github/workflows/linux_intel_oneAPI.yml +++ b/.github/workflows/linux_intel_oneAPI.yml @@ -87,7 +87,7 @@ jobs: shell: bash -l {0} run: | conda activate scipy-dev - conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pooch rich-click click doit pydevtool hypothesis - name: Initialise Intel oneAPI and Build SciPy shell: bash -l {0} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b1d04d64df2a..ee1eee2d9768 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,8 +21,8 @@ jobs: name: Get commit message uses: ./.github/workflows/commit_message.yml - fast_dev_py_fail_slow: - name: fail slow, fast, py3.12/npAny, dev.py + fast_dev_py: + name: fast, py3.12/npAny, dev.py needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -49,7 +49,7 @@ jobs: - name: pip-packages run: | - pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis python -m pip install -r requirements/openblas.txt - name: Build @@ -60,12 +60,12 @@ jobs: run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 -- --durations=0 --durations-min=0.25 --fail-slow=1.0 + python dev.py test -j2 -- --durations=25 ############################################################################# - full_dev_py_min_numpy_fail_slow: - name: fail slow, full, py3.11/npMin, dev.py + full_dev_py_min_numpy: + name: full, py3.11/npMin, dev.py needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -91,7 +91,7 @@ jobs: - name: pip-packages run: | # 1.25.2 is currently our oldest supported NumPy version - python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich_click click doit pydevtool hypothesis + python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis python -m pip install -r requirements/openblas.txt - name: Build @@ -102,7 +102,7 @@ jobs: run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 --mode full -- --durations=0 --durations-min=1.0 --timeout=60 --fail-slow=5.0 + python dev.py test -j2 --mode full -- --durations=25 --timeout=60 ############################################################################# diff --git a/.github/workflows/windows_intel_oneAPI.yml b/.github/workflows/windows_intel_oneAPI.yml index e0cf0ed6d5d3..b7225a989de0 100644 --- a/.github/workflows/windows_intel_oneAPI.yml +++ b/.github/workflows/windows_intel_oneAPI.yml @@ -85,7 +85,7 @@ jobs: - name: Install packages from conda shell: cmd /C call {0} run: | - conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pytest-fail-slow pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pooch rich-click click doit pydevtool hypothesis # MSVC is unable to compile Pythran code, therefore we need to use # -C-Duse-pythran=false while building SciPy. From 7c767f7b197b982c9e117309bd4be8c1db85c600 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Fri, 25 Apr 2025 08:50:12 +0100 Subject: [PATCH 049/251] DEP: linalg: deprecate disp argument for signm, logm, sqrtm (#22427) * DEP: linalg.logm: deprecate disp argument * DEP: linalg.signm: deprecate disp argument * DEP: linalg.sqrtm: deprecate disp and blocksize arguments * TST: linalg: test disp and blocksize deprecations * DOC: fix refguide check --- scipy/linalg/_matfuncs.py | 47 ++++++++++++++++++++++++++--- scipy/linalg/tests/test_batch.py | 8 ++--- scipy/linalg/tests/test_matfuncs.py | 44 +++++++++++++++++---------- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/scipy/linalg/_matfuncs.py b/scipy/linalg/_matfuncs.py index 48328b4d9d45..3e020ca8a508 100644 --- a/scipy/linalg/_matfuncs.py +++ b/scipy/linalg/_matfuncs.py @@ -146,7 +146,7 @@ def fractional_matrix_power(A, t): @_apply_over_batch(('A', 2)) -def logm(A, disp=True): +def logm(A, disp=_NoValue): """ Compute matrix logarithm. @@ -161,6 +161,11 @@ def logm(A, disp=True): Emit warning if error in the result is estimated large instead of returning estimated error. (Default: True) + .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(expm(logm(A)) - A, 1) / norm(A, 1)``. + Returns ------- logm : (N, N) ndarray @@ -201,6 +206,12 @@ def logm(A, disp=True): [ 1., 4.]]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated " + "and will be removed in SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) A = np.asarray(A) # squareness checked in `_logm` # Avoid circular import ... this is OK, right? import scipy.linalg._matfuncs_inv_ssq @@ -413,14 +424,19 @@ def sqrtm(A, disp=_NoValue, blocksize=_NoValue): A : ndarray Input with last two dimensions are square ``(..., n, n)``. disp : bool, optional - Deprecated keyword. It will be removed in 1.20.0. + Print warning if error in the result is estimated large + instead of returning estimated error. (Default: True) .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(X @ X - A, 'fro')**2 / norm(A, 'fro')`` blocksize : integer, optional - Deprecated keyword. It has no effect and will be removed in 1.18.0. .. deprecated:: 1.16.0 + The `blocksize` argument is deprecated as it is unused by the algorithm + and will be removed in SciPy 1.18.0. Returns ------- @@ -471,6 +487,17 @@ def sqrtm(A, disp=_NoValue, blocksize=_NoValue): [ 1., 4.]]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated and will be removed in SciPy " + "1.18.0.", + DeprecationWarning, stacklevel=2) + if blocksize is not _NoValue: + warnings.warn("The `blocksize` argument is deprecated and will be removed in " + "SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) + a = np.asarray(A) if a.size == 1 and a.ndim < 2: return np.array([[np.exp(a.item())]]) @@ -864,7 +891,7 @@ def funm(A, func, disp=True): @_apply_over_batch(('A', 2)) -def signm(A, disp=True): +def signm(A, disp=_NoValue): """ Matrix sign function. @@ -878,6 +905,11 @@ def signm(A, disp=True): Print warning if error in the result is estimated large instead of returning estimated error. (Default: True) + .. deprecated:: 1.16.0 + The `disp` argument is deprecated and will be + removed in SciPy 1.18.0. The previously returned error estimate + can be computed as ``norm(signm @ signm - signm, 1)``. + Returns ------- signm : (N, N) ndarray @@ -897,6 +929,13 @@ def signm(A, disp=True): array([-1.+0.j, 1.+0.j, 1.+0.j]) """ + if disp is _NoValue: + disp = True + else: + warnings.warn("The `disp` argument is deprecated " + "and will be removed in SciPy 1.18.0.", + DeprecationWarning, stacklevel=2) + A = _asarray_square(A) def rounded_sign(x): diff --git a/scipy/linalg/tests/test_batch.py b/scipy/linalg/tests/test_batch.py index 915cefadf6c3..4e62860952f1 100644 --- a/scipy/linalg/tests/test_batch.py +++ b/scipy/linalg/tests/test_batch.py @@ -142,17 +142,15 @@ def test_fractional_matrix_power(self, dtype): res2 = linalg.fractional_matrix_power(A, 1.5) np.testing.assert_equal(res1, res2) - @pytest.mark.parametrize('disp', [False, True]) @pytest.mark.parametrize('dtype', floating) - def test_logm(self, dtype, disp): + def test_logm(self, dtype): # One test failed absolute tolerance with default random seed rng = np.random.default_rng(89940026998903887141749720079406074936) A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng) A = A + 3*np.eye(4) # avoid complex output for real input - n_out = 1 if disp else 2 - res1 = self.batch_test(linalg.logm, A, n_out=n_out, kwargs=dict(disp=disp)) + res1 = self.batch_test(linalg.logm, A) # test that `disp` can be passed by position - res2 = linalg.logm(A, disp) + res2 = linalg.logm(A) for res1i, res2i in zip(res1, res2): np.testing.assert_equal(res1i, res2i) diff --git a/scipy/linalg/tests/test_matfuncs.py b/scipy/linalg/tests/test_matfuncs.py index 3b3a69267a51..e5233a553dc0 100644 --- a/scipy/linalg/tests/test_matfuncs.py +++ b/scipy/linalg/tests/test_matfuncs.py @@ -65,7 +65,7 @@ def test_nils(self): def test_defective1(self): a = array([[0.0,1,0,0],[1,0,1,0],[0,0,0,1],[0,0,1,0]]) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? def test_defective2(self): @@ -75,7 +75,7 @@ def test_defective2(self): [-10.0,6.0,-20.0,-18.0,-2.0], [-9.6,9.6,-25.5,-15.4,-2.0], [9.8,-4.8,18.0,18.2,2.0])) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? def test_defective3(self): @@ -86,7 +86,7 @@ def test_defective3(self): [0., 0., 0., 0., 3., 10., 0.], [0., 0., 0., 0., 0., -2., 25.], [0., 0., 0., 0., 0., 0., -3.]]) - signm(a, disp=False) + signm(a) #XXX: what would be the correct result? @@ -94,6 +94,7 @@ class TestLogM: def setup_method(self): self.rng = np.random.default_rng(1738098768840254) + @pytest.mark.filterwarnings("ignore:.*inaccurate.*:RuntimeWarning") def test_nils(self): a = array([[-2., 25., 0., 0., 0., 0., 0.], [0., -3., 10., 3., 3., 3., 0.], @@ -103,14 +104,15 @@ def test_nils(self): [0., 0., 0., 0., 0., -2., 25.], [0., 0., 0., 0., 0., 0., -3.]]) m = (identity(7)*3.1+0j)-a - logm(m, disp=False) + logm(m) #XXX: what would be the correct result? + @pytest.mark.filterwarnings("ignore:.*inaccurate.*:RuntimeWarning") def test_al_mohy_higham_2012_experiment_1_logm(self): # The logm completes the round trip successfully. # Note that the expm leg of the round trip is badly conditioned. A = _get_al_mohy_higham_2012_experiment_1() - A_logm, info = logm(A, disp=False) + A_logm = logm(A) A_round_trip = expm(A_logm) assert_allclose(A_round_trip, A, rtol=5e-5, atol=1e-14) @@ -118,7 +120,7 @@ def test_al_mohy_higham_2012_experiment_1_funm_log(self): # The raw funm with np.log does not complete the round trip. # Note that the expm leg of the round trip is badly conditioned. A = _get_al_mohy_higham_2012_experiment_1() - A_funm_log, info = funm(A, np.log, disp=False) + A_funm_log = funm(A, np.log) A_round_trip = expm(A_funm_log) assert_(not np.allclose(A_round_trip, A, rtol=1e-5, atol=1e-14)) @@ -152,7 +154,7 @@ def test_round_trip_random_complex(self): 1j*self.rng.standard_normal((n, n))) for scale in np.logspace(-4, 4, 9): M = M_unscaled * scale - M_logm, info = logm(M, disp=False) + M_logm = logm(M) M_round_trip = expm(M_logm) assert_allclose(M_round_trip, M) @@ -173,17 +175,17 @@ def test_logm_type_preservation_and_conversion(self): # check float type preservation A = np.array(matrix_as_list, dtype=float) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char not in complex_dtype_chars) # check complex type preservation A = np.array(matrix_as_list, dtype=complex) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char in complex_dtype_chars) # check float->complex type conversion for the matrix negation A = -np.array(matrix_as_list, dtype=float) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) assert_(A_logm.dtype.char in complex_dtype_chars) def test_complex_spectrum_real_logm(self): @@ -194,7 +196,7 @@ def test_complex_spectrum_real_logm(self): X = np.array(M, dtype=dt) w = scipy.linalg.eigvals(X) assert_(1e-2 < np.absolute(w.imag).sum()) - Y, info = logm(X, disp=False) + Y = logm(X) assert_(np.issubdtype(Y.dtype, np.inexact)) assert_allclose(expm(Y), X) @@ -206,7 +208,7 @@ def test_real_mixed_sign_spectrum(self): [[0, 1], [1, 0]]): for dt in float, complex: A = np.array(M, dtype=dt) - A_logm, info = logm(A, disp=False) + A_logm, info = logm(A) assert_(np.issubdtype(A_logm.dtype, np.complexfloating)) @pytest.mark.thread_unsafe @@ -215,7 +217,7 @@ def test_exactly_singular(self): B = np.asarray([[1, 1], [0, 0]]) for M in A, A.T, B, B.T: expected_warning = _matfuncs_inv_ssq.LogmExactlySingularWarning - L, info = assert_warns(expected_warning, logm, M, disp=False) + L = assert_warns(expected_warning, logm, M) E = expm(L) assert_allclose(E, M, atol=1e-14) @@ -223,7 +225,7 @@ def test_exactly_singular(self): def test_nearly_singular(self): M = np.array([[1e-100]]) expected_warning = _matfuncs_inv_ssq.LogmNearlySingularWarning - L, info = assert_warns(expected_warning, logm, M, disp=False) + L = assert_warns(expected_warning, logm, M) E = expm(L) assert_allclose(E, M, atol=1e-14) @@ -629,7 +631,7 @@ def test_random_matrices_and_powers(self): # to compute the fractional matrix power. # These can be compared because they both use the principal branch. A_power = fractional_matrix_power(A, p) - A_logm, info = logm(A, disp=False) + A_logm = logm(A) A_power_expm_logm = expm(A_logm * p) assert_allclose(A_power, A_power_expm_logm) @@ -638,7 +640,7 @@ def test_al_mohy_higham_2012_experiment_1(self): A = _get_al_mohy_higham_2012_experiment_1() # Test remainder matrix power. - A_funm_sqrt, info = funm(A, np.sqrt, disp=False) + A_funm_sqrt = funm(A, np.sqrt) A_sqrtm = sqrtm(A) A_rem_power = _matfuncs_inv_ssq._remainder_matrix_power(A, 0.5) A_power = fractional_matrix_power(A, 0.5) @@ -1104,3 +1106,13 @@ def test_empty(self): b = np.empty((5, 0)) res = khatri_rao(a, b) assert_allclose(res, np.empty((15, 0))) + +@pytest.mark.parametrize('func', + [logm, sqrtm, signm]) +def test_disp_dep(func): + with pytest.deprecated_call(): + func(np.eye(2), disp=False) + +def test_blocksize_dep(): + with pytest.deprecated_call(): + sqrtm(np.eye(2), blocksize=10) From 33d709d97e03379aa071f31851d8aba6200840bc Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 25 Apr 2025 13:01:27 +0200 Subject: [PATCH 050/251] TST: signal: make freqs and freqs_zpk tests Array API aware --- scipy/signal/tests/test_filter_design.py | 91 +++++++++++++----------- 1 file changed, 51 insertions(+), 40 deletions(-) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 0a59780dee22..16c3231492bf 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -28,6 +28,7 @@ lp2bs_zpk) from scipy.signal._filter_design import (_cplxreal, _cplxpair, _norm_factor, _bessel_poly, _bessel_zeros) +from scipy.signal._filter_design import _logspace skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -548,49 +549,51 @@ def test_bad_args(self): class TestFreqs: - def test_basic(self): - _, h = freqs([1.0], [1.0], worN=8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + _, h = freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8) + assert_array_almost_equal(h, xp.ones(8)) - def test_output(self): + def test_output(self, xp): # 1st order low-pass filter: H(s) = 1 / (s + 1) - w = [0.1, 1, 10, 100] - num = [1] - den = [1, 1] + w = xp.asarray([0.1, 1, 10, 100]) + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) w, H = freqs(num, den, worN=w) s = w * 1j expected = 1 / (s + 1) - assert_array_almost_equal(H.real, expected.real) - assert_array_almost_equal(H.imag, expected.imag) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) - def test_freq_range(self): + def test_freq_range(self, xp): # Test that freqresp() finds a reasonable frequency range. # 1st order low-pass filter: H(s) = 1 / (s + 1) # Expected range is from 0.01 to 10. - num = [1] - den = [1, 1] + num = xp.asarray([1.]) + den = xp.asarray([1, 1.]) n = 10 - expected_w = np.logspace(-2, 1, n) + expected_w = _logspace(-2, 1, n, xp=xp) w, H = freqs(num, den, worN=n) assert_array_almost_equal(w, expected_w) - def test_plot(self): + def test_plot(self, xp): def plot(w, h): - assert_array_almost_equal(h, np.ones(8)) + assert_array_almost_equal(h, xp.ones(8)) - assert_raises(ZeroDivisionError, freqs, [1.0], [1.0], worN=8, - plot=lambda w, h: 1 / 0) - freqs([1.0], [1.0], worN=8, plot=plot) + with assert_raises(ZeroDivisionError): + freqs([1.0], [1.0], worN=8, plot=lambda w, h: 1 / 0) - def test_backward_compat(self): + freqs(xp.asarray([1.0]), xp.asarray([1.0]), worN=8, plot=plot) + + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqs([1.0], [1.0]) - w2, h2 = freqs([1.0], [1.0], None) + w1, h1 = freqs(xp.asarray([1.0]), xp.asarray([1.0])) + w2, h2 = freqs(xp.asarray([1.0]), xp.asarray([1.0]), None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_w_or_N_types(self): + @skip_xp_backends(np_only=True, reason="numpy scalars") + def test_w_or_N_types(self, xp): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): @@ -607,51 +610,59 @@ def test_w_or_N_types(self): class TestFreqs_zpk: - def test_basic(self): - _, h = freqs_zpk([1.0], [1.0], [1.0], worN=8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + _, h = freqs_zpk( + xp.asarray([1.0]), xp.asarray([1.0]), xp.asarray([1.0]), worN=8 + ) + assert_array_almost_equal(h, xp.ones(8)) - def test_output(self): + def test_output(self, xp): # 1st order low-pass filter: H(s) = 1 / (s + 1) - w = [0.1, 1, 10, 100] - z = [] - p = [-1] + w = xp.asarray([0.1, 1, 10, 100]) + z = xp.asarray([]) + p = xp.asarray([-1.0]) k = 1 w, H = freqs_zpk(z, p, k, worN=w) s = w * 1j expected = 1 / (s + 1) - assert_array_almost_equal(H.real, expected.real) - assert_array_almost_equal(H.imag, expected.imag) + assert_array_almost_equal(xp.real(H), xp.real(expected)) + assert_array_almost_equal(xp.imag(H), xp.imag(expected)) - def test_freq_range(self): + def test_freq_range(self, xp): # Test that freqresp() finds a reasonable frequency range. # 1st order low-pass filter: H(s) = 1 / (s + 1) # Expected range is from 0.01 to 10. - z = [] - p = [-1] + z = xp.asarray([]) + p = xp.asarray([-1.]) k = 1 n = 10 - expected_w = np.logspace(-2, 1, n) + expected_w = _logspace(-2, 1, n, xp=xp) w, H = freqs_zpk(z, p, k, worN=n) assert_array_almost_equal(w, expected_w) - def test_vs_freqs(self): + def test_vs_freqs(self, xp): b, a = cheby1(4, 5, 100, analog=True, output='ba') z, p, k = cheby1(4, 5, 100, analog=True, output='zpk') + b, a = map(xp.asarray, (b, a)) # XXX convert cheby1 + z, p = map(xp.asarray, (z, p)) + w1, h1 = freqs(b, a) w2, h2 = freqs_zpk(z, p, k) xp_assert_close(w1, w2) xp_assert_close(h1, h2, rtol=1e-6) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqs_zpk([1.0], [1.0], [1.0]) - w2, h2 = freqs_zpk([1.0], [1.0], [1.0], None) + # Also, keep testing `k` a length-one list: it is documented as a scalar, + # but the implementation was allowing for a one-element array-likes + w1, h1 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0]) + w2, h2 = freqs_zpk(xp.asarray([1.0]), xp.asarray([1.0]), [1.0], None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_w_or_N_types(self): + @skip_xp_backends(np_only=True, reason="numpy scalars") + def test_w_or_N_types(self, xp): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): From 5f52e90e724846fb626b300f617a192cf0701edf Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 25 Apr 2025 13:02:03 +0200 Subject: [PATCH 051/251] ENH: signal: make freqs, freqs_zpk array API compatible --- scipy/signal/_filter_design.py | 101 +++++++++++++++++++++++++-------- scipy/signal/_polyutils.py | 68 ++++++++++++++++++++++ scipy/signal/meson.build | 1 + 3 files changed, 145 insertions(+), 25 deletions(-) create mode 100644 scipy/signal/_polyutils.py diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index d51f9eae183d..51c6fb88c747 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -15,9 +15,12 @@ from scipy.special import comb from scipy._lib._util import float_factorial from scipy.signal._arraytools import _validate_fs +from scipy.signal import _polyutils as _pu import scipy._lib.array_api_extra as xpx -from scipy._lib._array_api import array_namespace, xp_promote, xp_size +from scipy._lib._array_api import ( + array_namespace, xp_promote, xp_size, xp_default_dtype +) from scipy._lib.array_api_compat import numpy as np_compat @@ -58,6 +61,26 @@ def _is_int_type(x): return True +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302 +def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp): + if not isinstance(base, (float, int)) and xp.asarray(base).ndim > 0: + # If base is non-scalar, broadcast it with the others, since it + # may influence how axis is interpreted. + start, stop, step = map(xp.asarray, (start, stop, step)) + ndmax = xp.broadcast_arrays(start, stop, base).ndim + start, stop, base = ( + xpx.atleast_nd(a, ndim=ndmax) + for a in (start, stop, base) + ) + base = xp.expand_dims(base, axis=axis) + y = xp.linspace(start, stop, num=num, endpoint=endpoint) + + yp = xp.pow(base, y) + if dtype is None: + return yp + return xp.astype(yp, dtype, copy=False) + + def findfreqs(num, den, N, kind='ba'): """ Find array of frequencies for computing the response of an analog filter. @@ -93,27 +116,45 @@ def findfreqs(num, den, N, kind='ba'): 3.16227766e-01, 1.00000000e+00, 3.16227766e+00, 1.00000000e+01, 3.16227766e+01, 1.00000000e+02]) """ + xp = array_namespace(num, den) + num, den = map(xp.asarray, (num, den)) + if kind == 'ba': - ep = atleast_1d(roots(den)) + 0j - tz = atleast_1d(roots(num)) + 0j + ep = xpx.atleast_nd(_pu.polyroots(den, xp=xp), ndim=1, xp=xp) + tz = xpx.atleast_nd(_pu.polyroots(num, xp=xp), ndim=1, xp=xp) elif kind == 'zp': - ep = atleast_1d(den) + 0j - tz = atleast_1d(num) + 0j + ep = xpx.atleast_nd(den, ndim=1, xp=xp) + tz = xpx.atleast_nd(num, ndim=1, xp=xp) else: raise ValueError("input must be one of {'ba', 'zp'}") - if len(ep) == 0: - ep = atleast_1d(-1000) + 0j + # XXX a use case for .astype("complex floating"), finally + if xp.isdtype(ep.dtype, 'real floating'): + tgt_dtype = xp.complex64 if ep.dtype == xp.float32 else xp.complex128 + ep = xp.astype(ep, tgt_dtype) - ez = np.r_[ep[ep.imag >= 0], tz[(np.abs(tz) < 1e5) & (tz.imag >= 0)]] + if xp.isdtype(tz.dtype, 'real floating'): + tgt_dtype = xp.complex64 if tz.dtype == xp.float32 else xp.complex128 + tz = xp.astype(tz, tgt_dtype) - integ = np.abs(ez) < 1e-10 - hfreq = np.round(np.log10(np.max(3 * np.abs(ez.real + integ) + - 1.5 * ez.imag)) + 0.5) - lfreq = np.round(np.log10(0.1 * np.min(np.abs((ez + integ).real) + - 2 * ez.imag)) - 0.5) + if ep.shape[0] == 0: + ep = xp.asarray([-1000], dtype=ep.dtype) - w = np.logspace(lfreq, hfreq, N) + ez = xp.concat(( + ep[xp.imag(ep) >= 0], + tz[(xp.abs(tz) < 1e5) & (xp.imag(tz) >= 0)] + )) + + integ = xp.astype(xp.abs(ez) < 1e-10, ez.dtype) # XXX True->1, False->0 + hfreq = xp.round( + xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5 + ) + + lfreq = xp.round( + xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 + ) + + w = _logspace(lfreq, hfreq, N, xp=xp) return w @@ -178,16 +219,18 @@ def freqs(b, a, worN=200, plot=None): >>> plt.show() """ + xp = array_namespace(b, a, worN) + if worN is None: # For backwards compatibility w = findfreqs(b, a, 200) elif _is_int_type(worN): w = findfreqs(b, a, worN) else: - w = atleast_1d(worN) + w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp) s = 1j * w - h = polyval(b, s) / polyval(a, s) + h = _pu.polyval(b, s, xp=xp) / _pu.polyval(a, s, xp=xp) if plot is not None: plot(w, h) @@ -254,8 +297,13 @@ def freqs_zpk(z, p, k, worN=200): >>> plt.show() """ - k = np.asarray(k) - if k.size > 1: + xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) + + # NB: k is documented to be a scalar; for backwards compat we keep allowing it + # to be a size-1 array, but it does not influence the namespace calculation. + k = xp.asarray(k, dtype=xp_default_dtype(xp)) + if xp_size(k) > 1: raise ValueError('k must be a single scalar gain') if worN is None: @@ -266,10 +314,10 @@ def freqs_zpk(z, p, k, worN=200): else: w = worN - w = atleast_1d(w) + w = xpx.atleast_nd(xp.asarray(w), ndim=1, xp=xp) s = 1j * w - num = polyvalfromroots(s, z) - den = polyvalfromroots(s, p) + num = _pu.npp_polyvalfromroots(s, z, xp=xp) + den = _pu.npp_polyvalfromroots(s, p, xp=xp) h = k * num/den return w, h @@ -576,7 +624,10 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): >>> plt.show() """ - z, p = map(atleast_1d, (z, p)) + xp = array_namespace(z, p) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + p = xpx.atleast_nd(p, ndim=1, xp=xp) fs = _validate_fs(fs, allow_none=False) @@ -587,11 +638,11 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): if worN is None: # For backwards compatibility - w = np.linspace(0, lastpoint, 512, endpoint=False) + w = xp.linspace(0, lastpoint, 512, endpoint=False) elif _is_int_type(worN): - w = np.linspace(0, lastpoint, worN, endpoint=False) + w = xp.linspace(0, lastpoint, worN, endpoint=False) else: - w = atleast_1d(worN) + w = xpx.atleast_nd(worN, ndim=1, xp=xp) w = 2*pi*w/fs zm1 = exp(1j * w) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py new file mode 100644 index 000000000000..c7d8361ce1af --- /dev/null +++ b/scipy/signal/_polyutils.py @@ -0,0 +1,68 @@ +"""Partial replacements for numpy polynomial routines, with Array API compatibility. + +This module contains both "old-style", np.poly1d, routines from the main numpy +namespace, and "new-style", np.polynomial.polynomial, routines. + +To distinguish the two sets, the "new-style" routine names start with `npp_` +""" +import scipy._lib.array_api_extra as xpx + + +def polyroots(coef, *, xp): + """numpy.roots, best-effor replacement + """ + if coef.shape[0] < 2: + return xp.asarray([], dtype=coef.dtype) + + root_func = getattr(xp, 'roots', None) + if root_func: + # OK on numpy and jax, is broken on cupy + return root_func(coef) + + # companion matrix + n = coef.shape[0] + a = xp.eye(n - 1, n - 1, k=-1) + a[:, -1] = -xp.flip(coef[1:]) / coef[0] + + # non-symmetric eigenvalue problem is not in the spec but is available on e.g. torch + if hasattr(xp.linalg, 'eigvals'): + return xp.linalg.eigvals(a) + else: + import numpy as np + return xp.asarray(np.linalg.eigvals(np.asarray(a))) + + +# ### Old-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L702-L779 +def polyval(p, x, *, xp): + """ Old-style polynomial, `np.polyval` + """ + p = xp.asarray(p) + x = xp.asarray(x) + y = xp.zeros_like(x) + + for pv in p: + y = y * x + pv + return y + + +# ### New-style routines ### + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L758-L842 +def npp_polyvalfromroots(x, r, *, xp, tensor=True): + r = xp.asarray(r, copy=None) + r = xpx.atleast_nd(r, ndim=1, xp=xp) + # if r.dtype.char in '?bBhHiIlLqQpP': + # r = r.astype(np.double) + + if isinstance(x, (tuple, list)): + x = np.asarray(x) + + if tensor: + r = xp.reshape(r, r.shape + (1,) * x.ndim) + elif x.ndim >= r.ndim: + raise ValueError("x.ndim must be < r.ndim when tensor == False") + return xp.prod(x - r, axis=0) diff --git a/scipy/signal/meson.build b/scipy/signal/meson.build index 655abb9aa683..e5aec46b2294 100644 --- a/scipy/signal/meson.build +++ b/scipy/signal/meson.build @@ -74,6 +74,7 @@ py3.install_sources([ '_ltisys.py', '_max_len_seq.py', '_peak_finding.py', + '_polyutils.py', '_savitzky_golay.py', '_short_time_fft.py', '_signaltools.py', From 8c9b687fefa926c827e4dcc014040727361d3d0d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 25 Apr 2025 13:33:23 +0200 Subject: [PATCH 052/251] ENH: signal: freqz, freqz_zpk array API --- scipy/signal/_filter_design.py | 70 ++++-- scipy/signal/_polyutils.py | 19 +- scipy/signal/tests/test_filter_design.py | 300 ++++++++++++++--------- 3 files changed, 240 insertions(+), 149 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 51c6fb88c747..1f7c6ab8900f 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -4,12 +4,11 @@ import warnings import numpy as np -from numpy import (atleast_1d, poly, polyval, roots, asarray, +from numpy import (atleast_1d, poly, roots, asarray, pi, absolute, sqrt, tan, log10, arcsinh, sin, exp, cosh, arccosh, ceil, conjugate, sinh, concatenate, prod, array) from numpy.polynomial.polynomial import polyval as npp_polyval -from numpy.polynomial.polynomial import polyvalfromroots from scipy import special, optimize, fft as sp_fft from scipy.special import comb @@ -63,16 +62,16 @@ def _is_int_type(x): # https://github.com/numpy/numpy/blob/v2.2.0/numpy/_core/function_base.py#L195-L302 def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp): - if not isinstance(base, (float, int)) and xp.asarray(base).ndim > 0: + if not isinstance(base, float | int) and xp.asarray(base).ndim > 0: # If base is non-scalar, broadcast it with the others, since it # may influence how axis is interpreted. - start, stop, step = map(xp.asarray, (start, stop, step)) + start, stop, base = map(xp.asarray, (start, stop, base)) ndmax = xp.broadcast_arrays(start, stop, base).ndim start, stop, base = ( xpx.atleast_nd(a, ndim=ndmax) for a in (start, stop, base) ) - base = xp.expand_dims(base, axis=axis) + base = xp.expand_dims(base) y = xp.linspace(start, stop, num=num, endpoint=endpoint) yp = xp.pow(base, y) @@ -481,8 +480,14 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, (2, 1024) """ - b = atleast_1d(b) - a = atleast_1d(a) + xp = array_namespace(b, a) + + b, a = map(xp.asarray, (b, a)) + if xp.isdtype(a.dtype, 'integral'): + a = xp.astype(a, xp_default_dtype(xp)) + + b = xpx.atleast_nd(b, ndim=1, xp=xp) + a = xpx.atleast_nd(a, ndim=1, xp=xp) fs = _validate_fs(fs, allow_none=False) @@ -500,40 +505,48 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, lastpoint = 2 * pi if whole else pi # if include_nyquist is true and whole is false, w should # include end point - w = np.linspace(0, lastpoint, N, + w = xp.linspace(0, lastpoint, N, endpoint=include_nyquist and not whole) n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N - if (a.size == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) + if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) and n_fft >= b.shape[0] and n_fft > 0): # TODO: review threshold acc. to benchmark? - if np.isrealobj(b) and np.isrealobj(a): + + if (xp.isdtype(b.dtype, "real floating") and + xp.isdtype(a.dtype, "real floating") + ): fft_func = sp_fft.rfft else: fft_func = sp_fft.fft - h = fft_func(b, n=n_fft, axis=0)[:N] + + h = fft_func(b, n=n_fft, axis=0) + h = h[:min(N, h.shape[0]), ...] h /= a + if fft_func is sp_fft.rfft and whole: # exclude DC and maybe Nyquist (no need to use axis_reverse # here because we can build reversal with the truncation) - stop = -1 if n_fft % 2 == 1 else -2 - h_flip = slice(stop, 0, -1) - h = np.concatenate((h, h[h_flip].conj())) + stop = None if n_fft % 2 == 1 else -1 + h_flipped = xp.flip(h[1:stop, ...], axis=0) + h = xp.concat((h, xp.conj(h_flipped))) if b.ndim > 1: # Last axis of h has length 1, so drop it. h = h[..., 0] # Move the first axis of h to the end. - h = np.moveaxis(h, 0, -1) + h = xp.moveaxis(h, 0, -1) else: - w = atleast_1d(worN) + w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) del worN - w = 2*pi*w/fs + w = 2 * pi * w / fs if h is None: # still need to compute using freqs w - zm1 = exp(-1j * w) - h = (npp_polyval(zm1, b, tensor=False) / - npp_polyval(zm1, a, tensor=False)) + zm1 = xp.exp(-1j * w) + h = (_pu.npp_polyval(zm1, b, tensor=False, xp=xp) / + _pu.npp_polyval(zm1, a, tensor=False, xp=xp)) - w = w*(fs/(2*pi)) + w = w * (fs / (2 * pi)) if plot is not None: plot(w, h) @@ -625,6 +638,7 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): """ xp = array_namespace(z, p) + z, p = map(xp.asarray, (z, p)) z = xpx.atleast_nd(z, ndim=1, xp=xp) p = xpx.atleast_nd(p, ndim=1, xp=xp) @@ -642,11 +656,15 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): elif _is_int_type(worN): w = xp.linspace(0, lastpoint, worN, endpoint=False) else: - w = xpx.atleast_nd(worN, ndim=1, xp=xp) - w = 2*pi*w/fs - - zm1 = exp(1j * w) - h = k * polyvalfromroots(zm1, z) / polyvalfromroots(zm1, p) + w = xp.asarray(worN) + if xp.isdtype(w.dtype, 'integral'): + w = xp.astype(w, xp_default_dtype(xp)) + w = xpx.atleast_nd(w, ndim=1, xp=xp) + w = 2 * pi * w / fs + + zm1 = xp.exp(1j * w) + func = _pu.npp_polyvalfromroots + h = xp.asarray(k) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp) w = w*(fs/(2*pi)) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py index c7d8361ce1af..79680a2c933f 100644 --- a/scipy/signal/_polyutils.py +++ b/scipy/signal/_polyutils.py @@ -51,6 +51,21 @@ def polyval(p, x, *, xp): # ### New-style routines ### +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L845-L894 +def npp_polyval(x, c, *, xp, tensor=True): + c = xp.asarray(c, copy=None) + c = xpx.atleast_nd(c, ndim=1, xp=xp) + if isinstance(x, tuple | list): + x = xp.asarray(x) + if tensor: + c = xp.reshape(c, (c.shape + (1,)*x.ndim)) + + c0 = c[-1, ...] + for i in range(2, c.shape[0] + 1): + c0 = c[-i, ...] + c0*x + return c0 + + # https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L758-L842 def npp_polyvalfromroots(x, r, *, xp, tensor=True): r = xp.asarray(r, copy=None) @@ -58,8 +73,8 @@ def npp_polyvalfromroots(x, r, *, xp, tensor=True): # if r.dtype.char in '?bBhHiIlLqQpP': # r = r.astype(np.double) - if isinstance(x, (tuple, list)): - x = np.asarray(x) + if isinstance(x, tuple | list): + x = xp.asarray(x) if tensor: r = xp.reshape(r, r.shape + (1,) * x.ndim) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 16c3231492bf..0635ce05c8b0 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -13,7 +13,7 @@ from pytest import raises as assert_raises from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, - assert_array_almost_equal, + assert_array_almost_equal, xp_size ) from numpy import array, spacing, sin, pi, sort @@ -679,97 +679,124 @@ def test_w_or_N_types(self, xp): class TestFreqz: - def test_ticket1441(self): + def test_ticket1441(self, xp): """Regression test for ticket 1441.""" # Because freqz previously used arange instead of linspace, # when N was large, it would return one more point than # requested. N = 100000 - w, h = freqz([1.0], worN=N) + w, h = freqz(xp.asarray([1.0]), worN=N) assert w.shape == (N,) - def test_basic(self): - w, h = freqz([1.0], worN=8) - assert_array_almost_equal(w, np.pi * np.arange(8) / 8.) - assert_array_almost_equal(h, np.ones(8)) - w, h = freqz([1.0], worN=9) - assert_array_almost_equal(w, np.pi * np.arange(9) / 9.) - assert_array_almost_equal(h, np.ones(9)) - - for a in [1, np.ones(2)]: - w, h = freqz(np.ones(2), a, worN=0) + def test_basic(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 9.) + assert_array_almost_equal(h, xp.ones(9)) + + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == np.dtype('complex128') + assert h.dtype == xp.complex128 - t = np.linspace(0, 1, 4, endpoint=False) + def test_basic2(self, xp): + t = xp.linspace(0, 1, 4, endpoint=False) for b, a, h_whole in zip( - ([1., 0, 0, 0], np.sin(2 * np.pi * t)), - ([1., 0, 0, 0], [0.5, 0, 0, 0]), - ([1., 1., 1., 1.], [0, -4j, 0, 4j])): + (xp.asarray([1., 0, 0, 0]), xp.sin(2 * xp.pi * t)), + (xp.asarray([1., 0, 0, 0]), xp.asarray([0.5, 0, 0, 0])), + (xp.asarray([1., 1., 1., 1.]), xp.asarray([0, -4j, 0, 4j])) + ): + w, h = freqz(b, a, worN=4, whole=True) - expected_w = np.linspace(0, 2 * np.pi, 4, endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, 4, endpoint=False) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) + # simultaneously check int-like support w, h = freqz(b, a, worN=np.int32(4), whole=True) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) + w, h = freqz(b, a, worN=w, whole=True) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) - def test_basic_whole(self): - w, h = freqz([1.0], worN=8, whole=True) - assert_array_almost_equal(w, 2 * np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + @skip_xp_backends(np_only=True, reason="numpy scalars") + def test_basic3(self, xp): + t = xp.linspace(0, 1, 4, endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, 4, endpoint=False) + for b, a, h_whole in zip( + (xp.asarray([1., 0, 0, 0]), xp.sin(2 * xp.pi * t)), + (xp.asarray([1., 0, 0, 0]), xp.asarray([0.5, 0, 0, 0])), + (xp.asarray([1., 1., 1., 1.]), xp.asarray([0, -4j, 0, 4j])) + ): + + w, h = freqz(b, a, worN=np.int32(4), whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) + + w, h = freqz(b, a, worN=w, whole=True) + assert_array_almost_equal(w, expected_w) + assert_array_almost_equal(h, h_whole) - def test_plot(self): + def test_basic_whole(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) + + def test_plot(self, xp): def plot(w, h): - assert_array_almost_equal(w, np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - assert_raises(ZeroDivisionError, freqz, [1.0], worN=8, - plot=lambda w, h: 1 / 0) - freqz([1.0], worN=8, plot=plot) + with assert_raises(ZeroDivisionError): + freqz(xp.asarray([1.0]), worN=8, plot=lambda w, h: 1 / 0) - def test_fft_wrapping(self): + freqz(xp.asarray([1.0]), worN=8, plot=plot) + + def test_fft_wrapping(self, xp): # Some simple real FIR filters bs = list() # filters as_ = list() hs_whole = list() hs_half = list() # 3 taps - t = np.linspace(0, 1, 3, endpoint=False) - bs.append(np.sin(2 * np.pi * t)) + t = xp.linspace(0, 1, 3, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) as_.append(3.) - hs_whole.append([0, -0.5j, 0.5j]) - hs_half.append([0, np.sqrt(1./12.), -0.5j]) + hs_whole.append(xp.asarray([0, -0.5j, 0.5j])) + hs_half.append(xp.asarray([0, math.sqrt(1./12.), -0.5j])) # 4 taps - t = np.linspace(0, 1, 4, endpoint=False) - bs.append(np.sin(2 * np.pi * t)) + t = xp.linspace(0, 1, 4, endpoint=False) + bs.append(xp.sin(2 * xp.pi * t)) as_.append(0.5) - hs_whole.append([0, -4j, 0, 4j]) - hs_half.append([0, np.sqrt(8), -4j, -np.sqrt(8)]) + hs_whole.append(xp.asarray([0, -4j, 0, 4j])) + hs_half.append(xp.asarray([0, math.sqrt(8), -4j, -math.sqrt(8)])) del t for ii, b in enumerate(bs): # whole a = as_[ii] - expected_w = np.linspace(0, 2 * np.pi, len(b), endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, b.shape[0], endpoint=False) w, h = freqz(b, a, worN=expected_w, whole=True) # polyval err_msg = f'b = {b}, a={a}' assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) - w, h = freqz(b, a, worN=len(b), whole=True) # FFT + + w, h = freqz(b, a, worN=b.shape[0], whole=True) # FFT assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_whole[ii], err_msg=err_msg) + # non-whole - expected_w = np.linspace(0, np.pi, len(b), endpoint=False) + expected_w = xp.linspace(0, xp.pi, b.shape[0], endpoint=False) w, h = freqz(b, a, worN=expected_w, whole=False) # polyval assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) - w, h = freqz(b, a, worN=len(b), whole=False) # FFT + + w, h = freqz(b, a, worN=b.shape[0], whole=False) # FFT assert_array_almost_equal(w, expected_w, err_msg=err_msg) assert_array_almost_equal(h, hs_half[ii], err_msg=err_msg) @@ -777,139 +804,150 @@ def test_fft_wrapping(self): # assume polyval is accurate rng = np.random.RandomState(0) for ii in range(2, 10): # number of taps - b = rng.randn(ii) + b = xp.asarray(rng.randn(ii)) for kk in range(2): - a = rng.randn(1) if kk == 0 else rng.randn(3) + a = xp.asarray(rng.randn(1) if kk == 0 else rng.randn(3)) for jj in range(2): if jj == 1: - b = b + rng.randn(ii) * 1j + b = b + xp.asarray(rng.randn(ii)) * 1j + # whole - expected_w = np.linspace(0, 2 * np.pi, ii, endpoint=False) + expected_w = xp.linspace(0, 2 * xp.pi, ii, endpoint=False) w, expected_h = freqz(b, a, worN=expected_w, whole=True) assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=True) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, expected_h) + # half - expected_w = np.linspace(0, np.pi, ii, endpoint=False) + expected_w = xp.linspace(0, xp.pi, ii, endpoint=False) w, expected_h = freqz(b, a, worN=expected_w, whole=False) assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=False) assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, expected_h) - def test_broadcasting1(self): + def test_broadcasting1(self, xp): # Test broadcasting with worN an integer or a 1-D array, # b and a are n-dimensional arrays. np.random.seed(123) b = np.random.rand(3, 5, 1) a = np.random.rand(2, 1) + b, a = map(xp.asarray, (b, a)) + for whole in [False, True]: # Test with worN being integers (one fast for FFT and one not), # a 1-D array, and an empty array. - for worN in [16, 17, np.linspace(0, 1, 10), np.array([])]: + for worN in [16, 17, xp.linspace(0, 1, 10), xp.asarray([])]: w, h = freqz(b, a, worN=worN, whole=whole) for k in range(b.shape[1]): bk = b[:, k, 0] ak = a[:, 0] ww, hh = freqz(bk, ak, worN=worN, whole=whole) xp_assert_close(ww, w) - xp_assert_close(hh, h[k]) + xp_assert_close(hh, h[k, ...]) - def test_broadcasting2(self): + def test_broadcasting2(self, xp): # Test broadcasting with worN an integer or a 1-D array, # b is an n-dimensional array, and a is left at the default value. np.random.seed(123) b = np.random.rand(3, 5, 1) + b = xp.asarray(b) for whole in [False, True]: - for worN in [16, 17, np.linspace(0, 1, 10)]: + for worN in [16, 17, xp.linspace(0, 1, 10)]: # FIXME w, h = freqz(b, worN=worN, whole=whole) for k in range(b.shape[1]): bk = b[:, k, 0] ww, hh = freqz(bk, worN=worN, whole=whole) xp_assert_close(ww, w) - xp_assert_close(hh, h[k]) + xp_assert_close(hh, h[k, :]) - def test_broadcasting3(self): + def test_broadcasting3(self, xp): # Test broadcasting where b.shape[-1] is the same length # as worN, and a is left at the default value. np.random.seed(123) N = 16 b = np.random.rand(3, N) + b = xp.asarray(b) for whole in [False, True]: - for worN in [N, np.linspace(0, 1, N)]: + for worN in [N, xp.linspace(0, 1, N)]: w, h = freqz(b, worN=worN, whole=whole) - assert w.size == N + assert xp_size(w) == N for k in range(N): bk = b[:, k] ww, hh = freqz(bk, worN=w[k], whole=whole) - xp_assert_close(ww, np.asarray(w[k])[None]) - xp_assert_close(hh, np.asarray(h[k])[None]) + xp_assert_close(ww, xp.asarray(w[k])[None]) + xp_assert_close(hh, xp.asarray(h[k])[None]) - def test_broadcasting4(self): + def test_broadcasting4(self, xp): # Test broadcasting with worN a 2-D array. np.random.seed(123) b = np.random.rand(4, 2, 1, 1) a = np.random.rand(5, 2, 1, 1) + b, a = map(xp.asarray, (b, a)) + for whole in [False, True]: for worN in [np.random.rand(6, 7), np.empty((6, 0))]: + worN = xp.asarray(worN) w, h = freqz(b, a, worN=worN, whole=whole) xp_assert_close(w, worN, rtol=1e-14) assert h.shape == (2,) + worN.shape for k in range(2): ww, hh = freqz(b[:, k, 0, 0], a[:, k, 0, 0], - worN=worN.ravel(), + worN=xp.reshape(worN, (-1,)), whole=whole) - xp_assert_close(ww, worN.ravel(), rtol=1e-14) - xp_assert_close(hh, h[k, :, :].ravel()) + xp_assert_close(ww, xp.reshape(worN, (-1,)), rtol=1e-14) + xp_assert_close(hh, xp.reshape(h[k, :, :], (-1,))) - def test_backward_compat(self): + @skip_xp_backends(np_only=True) + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default w1, h1 = freqz([1.0], 1) w2, h2 = freqz([1.0], 1, None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - b = [0.039479155677484369, 0.11843746703245311, 0.11843746703245311, - 0.039479155677484369] - a = [1.0, -1.3199152021838287, 0.80341991081938424, - -0.16767146321568049] + b = xp.asarray([0.039479155677484369, 0.11843746703245311, 0.11843746703245311, + 0.039479155677484369]) + a = xp.asarray([1.0, -1.3199152021838287, 0.80341991081938424, + -0.16767146321568049]) # N = None, whole=False w1, h1 = freqz(b, a, fs=fs) w2, h2 = freqz(b, a) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz(b, a, whole=True, fs=fs) w2, h2 = freqz(b, a, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz(b, a, 5, fs=fs) w2, h2 = freqz(b, a, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz(b, a, 5, whole=True, fs=fs) w2, h2 = freqz(b, a, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz(b, a, w, fs=fs) - w2, h2 = freqz(b, a, 2*pi*np.array(w)/fs) + w2, h2 = freqz(b, a, 2*pi*xp.asarray(w, dtype=xp.float64)/ fs) xp_assert_close(h1, h2) - xp_assert_close(w, w1, check_dtype=False) + xp_assert_close(w1, xp.asarray(w), check_dtype=False) - def test_w_or_N_types(self): + @skip_xp_backends(np_only=True, reason="numpy scalars") + def test_w_or_N_types(self, xp): # Measure at 7 (polyval) or 8 (fft) equally-spaced points for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), np.array(7), @@ -927,26 +965,26 @@ def test_w_or_N_types(self): # Measure at frequency 8 Hz for w in (8.0, 8.0+0j): # Only makes sense when fs is specified - w_out, h = freqz([1.0], worN=w, fs=100) - assert_array_almost_equal(w_out, [8]) - assert_array_almost_equal(h, [1]) + w_out, h = freqz(xp.asarray([1.0]), worN=w, fs=100) + assert_array_almost_equal(w_out, xp.asarray([8])) + assert_array_almost_equal(h, xp.asarray(1.), check_0d=False) - def test_nyquist(self): - w, h = freqz([1.0], worN=8, include_nyquist=True) - assert_array_almost_equal(w, np.pi * np.arange(8) / 7.) - assert_array_almost_equal(h, np.ones(8)) - w, h = freqz([1.0], worN=9, include_nyquist=True) - assert_array_almost_equal(w, np.pi * np.arange(9) / 8.) - assert_array_almost_equal(h, np.ones(9)) + def test_nyquist(self, xp): + w, h = freqz(xp.asarray([1.0]), worN=8, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 7.) + assert_array_almost_equal(h, xp.ones(8)) + w, h = freqz(xp.asarray([1.0]), worN=9, include_nyquist=True) + assert_array_almost_equal(w, xp.pi * xp.arange(9, dtype=w.dtype) / 8.) + assert_array_almost_equal(h, xp.ones(9)) - for a in [1, np.ones(2)]: - w, h = freqz(np.ones(2), a, worN=0, include_nyquist=True) + for a in [1, xp.ones(2)]: + w, h = freqz(xp.ones(2), a, worN=0, include_nyquist=True) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == np.dtype('complex128') + assert h.dtype == xp.complex128 - w1, h1 = freqz([1.0], worN=8, whole = True, include_nyquist=True) - w2, h2 = freqz([1.0], worN=8, whole = True, include_nyquist=False) + w1, h1 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=True) + w2, h2 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=False) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) @@ -961,12 +999,13 @@ def test_nyquist(self): (False, True, 257), (True, False, 257), (True, True, 257)]) - def test_17289(self, whole, nyquist, worN): - d = [0, 1] + def test_17289(self, whole, nyquist, worN, xp): + d = xp.asarray([0.0, 1.0]) w, Drfft = freqz(d, worN=32, whole=whole, include_nyquist=nyquist) _, Dpoly = freqz(d, worN=w) xp_assert_close(Drfft, Dpoly) + @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): freqz([1.0], fs=np.array([10, 20])) @@ -1204,81 +1243,99 @@ def test_fs_validation(self): class TestFreqz_zpk: - def test_ticket1441(self): + def test_ticket1441(self, xp): """Regression test for ticket 1441.""" # Because freqz previously used arange instead of linspace, # when N was large, it would return one more point than # requested. N = 100000 - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=N) + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=N) assert w.shape == (N,) - def test_basic(self): - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=8) - assert_array_almost_equal(w, np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8) + assert_array_almost_equal(w, xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - def test_basic_whole(self): - w, h = freqz_zpk([0.5], [0.5], 1.0, worN=8, whole=True) - assert_array_almost_equal(w, 2 * np.pi * np.arange(8.0) / 8) - assert_array_almost_equal(h, np.ones(8)) + def test_basic_whole(self, xp): + w, h = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, worN=8, whole=True) + assert_array_almost_equal(w, 2 * xp.pi * xp.arange(8.0) / 8) + assert_array_almost_equal(h, xp.ones(8)) - def test_vs_freqz(self): + def test_vs_freqz(self, xp): b, a = cheby1(4, 5, 0.5, analog=False, output='ba') z, p, k = cheby1(4, 5, 0.5, analog=False, output='zpk') + b, a = map(xp.asarray, (b, a)) # XXX convert cheby1 + z, p = map(xp.asarray, (z, p)) + w1, h1 = freqz(b, a) w2, h2 = freqz_zpk(z, p, k) xp_assert_close(w1, w2) xp_assert_close(h1, h2, rtol=1e-6) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default - w1, h1 = freqz_zpk([0.5], [0.5], 1.0) - w2, h2 = freqz_zpk([0.5], [0.5], 1.0, None) + w1, h1 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0) + w2, h2 = freqz_zpk(xp.asarray([0.5]), xp.asarray([0.5]), 1.0, None) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - z = [-1, -1, -1] - p = [0.4747869998473389+0.4752230717749344j, 0.37256600288916636, - 0.4747869998473389-0.4752230717749344j] + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) k = 0.03934683014103762 # N = None, whole=False w1, h1 = freqz_zpk(z, p, k, whole=False, fs=fs) w2, h2 = freqz_zpk(z, p, k, whole=False) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz_zpk(z, p, k, whole=True, fs=fs) w2, h2 = freqz_zpk(z, p, k, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz_zpk(z, p, k, 5, fs=fs) w2, h2 = freqz_zpk(z, p, k, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz_zpk(z, p, k, 5, whole=True, fs=fs) w2, h2 = freqz_zpk(z, p, k, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array_likes") + def test_fs_param2(self, xp): + fs = 900 + z = xp.asarray([-1, -1, -1.0]) + p = xp.asarray( + [0.4747869998473389 + 0.4752230717749344j, + 0.37256600288916636, + 0.4747869998473389 - 0.4752230717749344j] + ) + k = 0.03934683014103762 # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz_zpk(z, p, k, w, fs=fs) - w2, h2 = freqz_zpk(z, p, k, 2*pi*np.array(w)/fs) + w2, h2 = freqz_zpk(z, p, k, 2*pi*xp.asarray(w)/fs) xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) - def test_w_or_N_types(self): + @skip_xp_backends(np_only=True, reason="numpy scalars") + def test_w_or_N_types(self, xp): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): @@ -1298,9 +1355,10 @@ def test_w_or_N_types(self): assert_array_almost_equal(w_out, [8]) assert_array_almost_equal(h, [1]) + @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): - freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10, 20])) + freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10., 20])) with pytest.raises(ValueError, match="Sampling.*be none."): freqz_zpk([1.0], [1.0], [1.0], fs=None) From 843c84ff1931daea8b81ef1bea69614c572028fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Fri, 25 Apr 2025 23:30:50 +0530 Subject: [PATCH 053/251] DEV: use `spin` (#21674) This enables using `spin` instead of `dev.py` for development tasks. `dev.py` is not yet removed, in order to allow for a smooth transition. --- .circleci/config.yml | 18 +- .github/workflows/array_api.yml | 9 +- .github/workflows/lint.yml | 8 +- .github/workflows/linux.yml | 76 +- .github/workflows/linux_intel_oneAPI.yml | 8 +- .github/workflows/macos.yml | 24 +- .github/workflows/musllinux.yml | 21 +- .github/workflows/windows.yml | 20 +- .github/workflows/windows_intel_oneAPI.yml | 6 +- .spin/cmds.py | 1035 ++++++++++++++++++++ environment.yml | 1 + pyproject.toml | 26 + 12 files changed, 1156 insertions(+), 96 deletions(-) create mode 100644 .spin/cmds.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b532ae2fcbf..ee71c40d84c0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -76,14 +76,14 @@ jobs: command: | pip install numpy cython pybind11 pythran ninja meson pip install -r requirements/doc.txt - pip install mpmath gmpy2 "asv>=0.6.0" click rich-click doit pydevtool pooch threadpoolctl + pip install mpmath gmpy2 "asv>=0.6.0" pooch threadpoolctl spin # extra benchmark deps pip install pyfftw cffi pytest - run: name: build SciPy command: | - python dev.py build -j2 + spin build -j2 - save_cache: key: deps_ccache-{{ .Branch }} @@ -115,8 +115,8 @@ jobs: name: build docs no_output_timeout: 25m command: | - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages - python dev.py --no-build doc -j2 2>&1 | tee sphinx_log.txt + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages + spin docs -j2 2>&1 | tee sphinx_log.txt - run: name: Check sphinx log for warnings (which are treated as errors) @@ -148,7 +148,7 @@ jobs: name: run asv no_output_timeout: 30m command: | - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages cd benchmarks asv machine --machine CircleCI export SCIPY_GLOBAL_BENCH_NUMTRIALS=1 @@ -176,8 +176,8 @@ jobs: no_output_timeout: 25m command: | sudo apt-get install -y wamerican-small - export PYTHONPATH=$PWD/build-install/lib/python3.11/site-packages - python dev.py --no-build refguide-check + export PYTHONPATH=$PWD/build-install/usr/lib/python3.11/site-packages + spin refguide-check - run: name: smoke-docs @@ -185,13 +185,13 @@ jobs: command: | pip install matplotlib hypothesis pip install scipy-doctest - python dev.py smoke-docs + spin smoke-docs - run: name: smoke-tutorials no_output_timeout: 10m command: | - python dev.py smoke-tutorials + spin smoke-tutorials # Upload build output to scipy/devdocs repository, using SSH deploy keys. # The keys are only available for builds on main branch. diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 88e99e87265b..af4c488e838a 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -60,7 +60,7 @@ jobs: - name: Install Python packages run: | - python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click rich-click doit pydevtool pooch hypothesis array-api-strict + python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click pooch hypothesis array-api-strict spin - name: Install PyTorch CPU run: | @@ -98,14 +98,15 @@ jobs: - name: Setup build and install scipy run: | - python dev.py build + spin build - name: Test SciPy run: | export OMP_NUM_THREADS=2 - python dev.py --no-build test -b all $XP_TESTS -- --durations 3 --timeout=60 + # expand as more modules are supported by adding to `XP_TESTS` above + spin test -b all $XP_TESTS -- --durations 3 --timeout=60 - name: Test SciPy with torch/float32 run: | export SCIPY_DEFAULT_DTYPE="float32" - python dev.py --no-build test -b torch $XP_TESTS -- --durations 3 --timeout=60 + spin test -b torch $XP_TESTS -- --durations 3 --timeout=60 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 641f9e5db5c7..a93da7403cd7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -39,19 +39,17 @@ jobs: - name: Install Python packages run: | - python -m pip install ruff cython-lint packaging tach + python -m pip install ruff cython-lint packaging tach spin - name: Lint run: | set -euo pipefail - python tools/lint.py --diff-against origin/$GITHUB_BASE_REF - python tools/check_unicode.py - python tools/check_test_name.py + spin lint --diff-against origin/$GITHUB_BASE_REF - name: Check that Python.h is first in any file including it. shell: bash run: | - python tools/check_python_h_first.py + python tools/check_python_h_first.py - name: Check module interdependencies run: | diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0914548a2e3a..593830d5b12e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,7 +26,7 @@ jobs: uses: ./.github/workflows/commit_message.yml test_meson: - name: mypy (py3.11) & dev deps (py3.13), fast, dev.py + name: mypy (py3.11) & dev deps (py3.13), fast, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -65,13 +65,13 @@ jobs: - name: Install Python packages if: matrix.python-version == '3.11' run: | - python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson click rich-click doit pydevtool pooch hypothesis + python -m pip install numpy cython pytest pytest-xdist pytest-timeout pybind11 mpmath gmpy2 pythran ninja meson pooch hypothesis spin - name: Install Python packages from repositories if: matrix.python-version == '3.13-dev' # this run will use python dev versions when available run: | python -m pip install git+https://github.com/numpy/numpy.git - python -m pip install ninja cython pytest pybind11 pytest-xdist pytest-timeout click rich-click doit pydevtool pooch hypothesis "setuptools<67.3" meson + python -m pip install ninja cython pytest pybind11 pytest-xdist pytest-timeout spin pooch hypothesis "setuptools<67.3" meson python -m pip install git+https://github.com/serge-sans-paille/pythran.git # Disable Meson master testing until upstream option handling is fixed, see scipy#22534 # python -m pip install git+https://github.com/mesonbuild/meson.git @@ -103,7 +103,7 @@ jobs: - name: Setup build and install scipy run: | - python dev.py build --werror + spin build --werror - name: Ccache performance shell: bash -l {0} @@ -119,10 +119,10 @@ jobs: - name: Check usage of install tags run: | rm -r ${{ env.INSTALLDIR }} - python dev.py build --tags=runtime,python-runtime,devel + spin build --tags=runtime,python-runtime,devel python tools/check_installation.py ${{ env.INSTALLDIR }} --no-tests rm -r ${{ env.INSTALLDIR }} - python dev.py build --tags=runtime,python-runtime,devel,tests + spin build --tags=runtime,python-runtime,devel,tests python tools/check_installation.py ${{ env.INSTALLDIR }} - name: Check build-internal dependencies @@ -135,12 +135,12 @@ jobs: python -m pip install mypy==1.10.0 types-psutil typing_extensions python -m pip install pybind11 sphinx - python -u dev.py mypy + spin mypy - name: Test SciPy run: | export OMP_NUM_THREADS=2 - python dev.py --no-build test -j2 -- --durations 10 --timeout=60 + spin test -j2 -- --durations 10 --timeout=60 ################################################################################# test_venv_install: @@ -285,7 +285,7 @@ jobs: ################################################################################# prerelease_deps_coverage_64bit_blas: # TODO: re-enable ILP64 build. - name: Prerelease deps & coverage report, full, py3.11/npMin & py3.11/npPre, dev.py, SCIPY_ARRAY_API=1 + name: Prerelease deps & coverage report, full, py3.11/npMin & py3.11/npPre, spin, SCIPY_ARRAY_API=1 needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -321,7 +321,7 @@ jobs: - name: Install Python packages run: | - python -m pip install cython pythran ninja meson-python pybind11 click rich_click pydevtool + python -m pip install cython pythran ninja meson-python pybind11 spin python -m pip install --pre --upgrade pytest pytest-cov pytest-xdist mpmath gmpy2 threadpoolctl pooch hypothesis matplotlib python -m pip install -r requirements/openblas.txt # Install numpy last, to ensure we get nightly (avoid possible <2.0 constraints). @@ -348,7 +348,7 @@ jobs: - name: Build and install SciPy run: | - python dev.py build --gcov --with-scipy-openblas + spin build --gcov --with-scipy-openblas - name: Ccache performance shell: bash -l {0} @@ -363,11 +363,11 @@ jobs: run: | export OPENBLAS_NUM_THREADS=1 export SCIPY_ARRAY_API=1 - python dev.py --no-build test --coverage -j2 --mode full -- --cov --cov-report term-missing + spin test --no-build --coverage -j2 --mode full -- --cov --cov-report term-missing ################################################################################# linux_32bit: - name: 32-bit, fast, py3.11/npMin, dev.py + name: 32-bit, fast, py3.11/npMin, spin needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -389,7 +389,7 @@ jobs: uname -a && \ python3.11 -m venv test && \ source test/bin/activate && \ - python -m pip install doit click rich_click pydevtool meson ninja && \ + python -m pip install spin meson ninja && \ python -m pip install -r requirements/openblas.txt && \ # Ensure that scipy-openblas is picked up by the numpy<1.26 build cat > \$HOME/.numpy-site.cfg <- - -t scipy.cluster - -t scipy.constants - -t scipy.datasets - -t scipy.differentiate - -t scipy.fft - -t scipy.fftpack - -t scipy.integrate - -t scipy.interpolate - -t scipy.io - -t scipy.linalg - -t scipy.misc - -t scipy.ndimage - -t scipy.odr - -t scipy.optimize - -t scipy.signal - -t scipy.sparse - -t scipy.special - -t scipy.stats + scipy.cluster + scipy.constants + scipy.datasets + scipy.differentiate + scipy.fft + scipy.fftpack + scipy.integrate + scipy.interpolate + scipy.io + scipy.linalg + scipy.misc + scipy.ndimage + scipy.odr + scipy.optimize + scipy.signal + scipy.sparse + scipy.special + scipy.stats run: | # Note: only fast tests; full test suite is unlikely to uncover anything more, # and it'll be quite slow with pytest-run-parallel - python dev.py --no-build test $TEST_SUBMODULES -- --parallel-threads=4 + spin test -t $TEST_SUBMODULES -- --parallel-threads=4 ################################################################################# clang-17-build-only: diff --git a/.github/workflows/linux_intel_oneAPI.yml b/.github/workflows/linux_intel_oneAPI.yml index ab3a5a618d28..ae227bc14720 100644 --- a/.github/workflows/linux_intel_oneAPI.yml +++ b/.github/workflows/linux_intel_oneAPI.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/workflows/commit_message.yml icx_icpx_ifx_mkl: - name: py3.12, dev.py + name: py3.12, spin needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -87,7 +87,7 @@ jobs: shell: bash -l {0} run: | conda activate scipy-dev - conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja numpy cython pybind11 pytest pytest-xdist pytest-timeout pooch hypothesis spin - name: Initialise Intel oneAPI and Build SciPy shell: bash -l {0} @@ -99,11 +99,11 @@ jobs: # xref https://github.com/conda-forge/pythran-feedstock/issues/77). pip install pythran unset FFLAGS - CC=icx CXX=icpx FC=ifx python dev.py build -C-Dblas=mkl-dynamic-lp64-iomp -C-Dlapack=mkl-dynamic-lp64-iomp + CC=icx CXX=icpx FC=ifx spin build -- -Dblas=mkl-dynamic-lp64-iomp -Dlapack=mkl-dynamic-lp64-iomp - name: Test scipy shell: bash -l {0} run: | . /opt/intel/oneapi/setvars.sh --force conda activate scipy-dev - python dev.py test + spin test diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index cc2f83189737..408e70ced09f 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -115,14 +115,14 @@ jobs: # optional test dependencies mamba install scikit-umfpack scikit-sparse - CC="ccache $CC" python dev.py build + CC="ccache $CC" spin build - name: Test SciPy shell: bash -l {0} run: | conda activate scipy-dev export OMP_NUM_THREADS=2 - python dev.py -n test -j2 + spin test -j2 - name: Ccache statistics shell: bash -l {0} @@ -131,7 +131,7 @@ jobs: test_scipy_openblas: - name: M1 & OpenBLAS, fast, py3.11/npAny, dev.py + name: M1 & OpenBLAS, fast, py3.11/npAny, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -157,7 +157,7 @@ jobs: - name: Build and Install SciPy run: | sudo xcode-select -s /Applications/Xcode_15.2.app - + git submodule update --init GFORTRAN_LOC=$(which gfortran-13) ln -s $GFORTRAN_LOC gfortran @@ -166,17 +166,17 @@ jobs: # Ensure we have gfortran dylib GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) export DYLD_LIBRARY_PATH=$GFORTRAN_LIB - - pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy + + pip install meson cython pythran pybind11 ninja numpy spin pip install -r requirements/openblas.txt - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas pip install pooch pytest hypothesis - python dev.py -n test + spin test test_meson_accelerate: - name: Accelerate, fast, py3.11/npAny, dev.py + name: Accelerate, fast, py3.11/npAny, spin needs: get_commit_message # If using act to run CI locally the github object does not exist and # the usual skipping should not be enforced @@ -212,8 +212,8 @@ jobs: GFORTRAN_LIB=$(dirname `gfortran --print-file-name libgfortran.dylib`) export DYLD_LIBRARY_PATH=$GFORTRAN_LIB - pip install click doit pydevtool rich_click meson cython pythran pybind11 ninja numpy - python dev.py build --with-accelerate + pip install meson cython pythran pybind11 ninja numpy spin + spin build --with-accelerate pip install pooch pytest hypothesis - python dev.py -n test + spin test diff --git a/.github/workflows/musllinux.yml b/.github/workflows/musllinux.yml index 104ced857c26..f39c25bed119 100644 --- a/.github/workflows/musllinux.yml +++ b/.github/workflows/musllinux.yml @@ -24,7 +24,7 @@ jobs: uses: ./.github/workflows/commit_message.yml musllinux_x86_64: - name: musl Ubuntu-latest, fast, py3.11/npAny, dev.py + name: musl Ubuntu-latest, fast, py3.11/npAny, spin needs: get_commit_message runs-on: ubuntu-latest # If using act to run CI locally the github object does not exist and @@ -40,17 +40,17 @@ jobs: steps: - name: Get source - run: | + run: | apk update --quiet apk add build-base gfortran git - git config --global --add safe.directory $PWD - + git config --global --add safe.directory $PWD + if [ $GITHUB_EVENT_NAME != pull_request ]; then git clone --recursive --branch=$GITHUB_REF_NAME https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE git reset --hard $GITHUB_SHA - else + else git clone --recursive https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE git fetch origin $GITHUB_REF:my_ref_name git checkout $GITHUB_BASE_REF @@ -62,7 +62,7 @@ jobs: - name: prep build environment - run: | + run: | cd $RUNNER_TEMP python -m venv test_env source test_env/bin/activate @@ -70,12 +70,11 @@ jobs: python -m pip install cython numpy # python -m pip install --upgrade --pre -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy - python -m pip install meson ninja pybind11 pythran pytest hypothesis - python -m pip install click rich_click doit pydevtool pooch + python -m pip install meson ninja pybind11 pythran pytest hypothesis spin pooch python -m pip install -r requirements/openblas.txt - + chmod +x tools/wheels/cibw_before_build_linux.sh - tools/wheels/cibw_before_build_linux.sh --nightly . + tools/wheels/cibw_before_build_linux.sh --nightly . - name: test run: | @@ -84,4 +83,4 @@ jobs: source test_env/bin/activate cd $GITHUB_WORKSPACE export PKG_CONFIG_PATH=$PWD - python dev.py test + spin test diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ee1eee2d9768..3ef04e4313c1 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,8 +21,8 @@ jobs: name: Get commit message uses: ./.github/workflows/commit_message.yml - fast_dev_py: - name: fast, py3.12/npAny, dev.py + fast_spin: + name: fast, py3.12/npAny, spin needs: get_commit_message # Ensure (a) this doesn't run on forks by default, and # (b) it does run with Act locally (`github` doesn't exist there) @@ -49,23 +49,23 @@ jobs: - name: pip-packages run: | - pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis + pip install numpy cython pybind11 pythran meson ninja pytest pytest-xdist pytest-timeout pooch spin hypothesis python -m pip install -r requirements/openblas.txt - name: Build run: | - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas - name: Test run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 -- --durations=25 + spin test -j2 -- --durations=25 ############################################################################# - full_dev_py_min_numpy: - name: full, py3.11/npMin, dev.py + full_spin_min_numpy: + name: full, py3.11/npMin, spin needs: get_commit_message if: > needs.get_commit_message.outputs.message == 1 @@ -91,18 +91,18 @@ jobs: - name: pip-packages run: | # 1.25.2 is currently our oldest supported NumPy version - python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pooch rich_click click doit pydevtool hypothesis + python -m pip install numpy==1.25.2 cython pybind11 pythran meson-python meson ninja pytest pytest-xdist pytest-timeout pooch spin hypothesis python -m pip install -r requirements/openblas.txt - name: Build run: | - python dev.py build --with-scipy-openblas + spin build --with-scipy-openblas - name: Test run: | # test runner parallel clashes with OpenBLAS multithreading $env:OPENBLAS_NUM_THREADS=1 - python dev.py test -j2 --mode full -- --durations=25 --timeout=60 + spin test -j2 --mode full -- --durations=25 --timeout=60 ############################################################################# diff --git a/.github/workflows/windows_intel_oneAPI.yml b/.github/workflows/windows_intel_oneAPI.yml index b7225a989de0..c7c37bb09819 100644 --- a/.github/workflows/windows_intel_oneAPI.yml +++ b/.github/workflows/windows_intel_oneAPI.yml @@ -85,7 +85,7 @@ jobs: - name: Install packages from conda shell: cmd /C call {0} run: | - conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pooch rich-click click doit pydevtool hypothesis + conda install -c conda-forge pkg-config meson meson-python ninja openblas libblas=*=*openblas numpy==2.0 cython pybind11 pytest pytest-xdist pytest-timeout pooch spin hypothesis # MSVC is unable to compile Pythran code, therefore we need to use # -C-Duse-pythran=false while building SciPy. @@ -96,7 +96,7 @@ jobs: call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" set FC=ifx call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" - python dev.py build -C-Duse-pythran=false -C--vsenv + spin build -C-Duse-pythran=false -C--vsenv # "import scipy; scipy.test();" fails because # scipy/sparse/linalg/_eigen/arpack crashes. @@ -104,4 +104,4 @@ jobs: - name: Test scipy.datasets shell: cmd /C call {0} run: | - python dev.py test -s datasets + spin test -s datasets diff --git a/.spin/cmds.py b/.spin/cmds.py new file mode 100644 index 000000000000..0cea23d65941 --- /dev/null +++ b/.spin/cmds.py @@ -0,0 +1,1035 @@ +import contextlib +import os +import sys +import importlib +import importlib.util +import json +import traceback +import warnings +import math +import subprocess +from concurrent.futures.process import _MAX_WINDOWS_WORKERS + +import spin +import click +from spin import util +from spin.cmds import meson + +from pathlib import Path + +PROJECT_MODULE = "scipy" + +@click.option( + '--werror', default=False, is_flag=True, + help="Treat warnings as errors") +@click.option( + '--asan', default=False, is_flag=True, + help=("Build and run with AddressSanitizer support. " + "Note: the build system doesn't check whether " + "the project is already compiled with ASan. " + "If not, you need to do a clean build (delete " + "build and build-install directories).")) +@click.option( + '--debug', '-d', default=False, is_flag=True, help="Debug build") +@click.option( + '--release', '-r', default=False, is_flag=True, help="Release build") +@click.option( + '--parallel', '-j', default=None, metavar='N_JOBS', + help=("Number of parallel jobs for building. " + "This defaults to the number of available physical CPU cores")) +@click.option( + '--setup-args', '-C', default=[], multiple=True, + help=("Pass along one or more arguments to `meson setup` " + "Repeat the `-C` in case of multiple arguments.")) +@click.option( + '--show-build-log', default=False, is_flag=True, + help="Show build output rather than using a log file") +@click.option( + '--with-scipy-openblas', default=False, is_flag=True, + help=("If set, use the `scipy-openblas32` wheel installed into the " + "current environment as the BLAS/LAPACK to build against.")) +@click.option( + '--with-accelerate', default=False, is_flag=True, + help=("If set, use `Accelerate` as the BLAS/LAPACK to build against." + " Takes precedence over -with-scipy-openblas (macOS only)") +) +@click.option( + '--tags', default="runtime,python-runtime,tests,devel", + show_default=True, help="Install tags to be used by meson." +) +@spin.util.extend_command(spin.cmds.meson.build) +def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, + release, parallel, setup_args, show_build_log, + with_scipy_openblas, with_accelerate, tags, **kwargs): + """🔧 Build package with Meson/ninja and install + + MESON_ARGS are passed through e.g.: + + spin build -- -Dpkg_config_path=/lib64/pkgconfig + + The package is installed to build-install + + By default builds for release, to be able to use a debugger set CFLAGS + appropriately. For example, for linux use + + CFLAGS="-O0 -g" spin build + """ + MESON_ARGS = "meson_args" + MESON_COMPILE_ARGS = "meson_compile_args" + MESON_INSTALL_ARGS = "meson_install_args" + + meson_compile_args = tuple() + meson_install_args = tuple() + + if sys.platform == "cygwin": + # Cygwin only has netlib lapack, but can link against + # OpenBLAS rather than netlib blas at runtime. There is + # no libopenblas-devel to enable linking against + # openblas-specific functions or OpenBLAS Lapack + meson_args = meson_args + ("-Dlapack=lapack", "-Dblas=blas") + + if werror: + meson_args = meson_args + ("--werror", ) + + if debug or release: + if debug and release: + raise ValueError("Set at most one of `--debug` and `--release`!") + if debug: + buildtype = 'debug' + cflags_unwanted = ('-O1', '-O2', '-O3') + elif release: + buildtype = 'release' + cflags_unwanted = ('-O0', '-O1', '-O2') + meson_args = meson_args + (f"-Dbuildtype={buildtype}", ) + if 'CFLAGS' in os.environ.keys(): + # Check that CFLAGS doesn't contain something that supercedes -O0 + # for a plain debug build (conda envs tend to set -O2) + cflags = os.environ['CFLAGS'].split() + for flag in cflags_unwanted: + if flag in cflags: + raise ValueError(f"A {buildtype} build isn't possible, " + f"because CFLAGS contains `{flag}`." + "Please also check CXXFLAGS and FFLAGS.") + + if asan: + meson_args = meson_args + ('-Db_sanitize=address,undefined', ) + + if setup_args: + meson_args = meson_args + tuple([str(arg) for arg in setup_args]) + + if with_accelerate: + # on a mac you probably want to use accelerate over scipy_openblas + meson_args = meson_args + ("-Dblas=accelerate", ) + elif with_scipy_openblas: + configure_scipy_openblas() + os.environ['PKG_CONFIG_PATH'] = os.pathsep.join([ + os.getcwd(), + os.environ.get('PKG_CONFIG_PATH', '') + ]) + + if parallel is None: + # Use number of physical cores rather than ninja's default of 2N+2, + # to avoid out of memory issues (see gh-17941 and gh-18443) + n_cores = cpu_count(only_physical_cores=True) + jobs = n_cores + else: + jobs = parallel + + meson_install_args = meson_install_args + ("--tags=" + tags, ) + + if show_build_log: + verbose = show_build_log + + parent_callback(**{MESON_ARGS: meson_args, + MESON_COMPILE_ARGS: meson_compile_args, + MESON_INSTALL_ARGS: meson_install_args, + "jobs": jobs, + "verbose": verbose, + **kwargs}) + +@click.option( + '--durations', '-d', default=None, metavar="NUM_TESTS", + help="Show timing for the given number of slowest tests" +) +@click.option( + '--submodule', '-s', default=None, metavar='MODULE_NAME', + help="Submodule whose tests to run (cluster, constants, ...)") +@click.option( + '--mode', '-m', default='not slow', metavar='MODE', show_default=True, + help=("'fast', 'full', or something that could be passed to " + "`pytest -m` as a marker expression")) +@click.option( + '--parallel', '-j', default=1, metavar='N_JOBS', + help="Number of parallel jobs for testing" +) +@click.option( + '--array-api-backend', '-b', default=None, metavar='ARRAY_BACKEND', + multiple=True, + help=( + "Array API backend " + "('all', 'numpy', 'torch', 'cupy', 'array_api_strict', " + "'jax.numpy', 'dask.array')." + ) +) +@spin.util.extend_command(spin.cmds.meson.test) +def test(*, parent_callback, pytest_args, tests, coverage, + durations, submodule, mode, parallel, + array_api_backend, **kwargs): + """🔧 Run tests + + PYTEST_ARGS are passed through directly to pytest, e.g.: + + spin test -- --pdb + + To run tests on a directory or file: + + \b + spin test scipy/linalg + + To report the durations of the N slowest tests: + + spin test -- --durations=N + + To run tests that match a given pattern: + + \b + spin test -- -k "geometric" + spin test -- -k "geometric and not rgeometric" + + By default, spin will run `-m 'not slow'`. To run the full test suite, use + `spin test -m full` + + For more, see `pytest --help`. + """ # noqa: E501 + + build_dir = os.path.abspath(kwargs['build_dir']) + site_package_dir = get_site_packages(build_dir) + + if site_package_dir is None and coverage: + raise FileNotFoundError( + "SciPy build not found, please execute " + "``spin build`` before calling ``spin test --coverage``. " + "We need it to figure out whether ``lcov`` can be called or not.") + + if site_package_dir is not None: + with working_dir(site_package_dir): + sys.path.insert(0, site_package_dir) + os.environ['PYTHONPATH'] = \ + os.pathsep.join((site_package_dir, os.environ.get('PYTHONPATH', ''))) + was_built_with_gcov_flag = len(list(Path(build_dir).rglob("*.gcno"))) > 0 + if was_built_with_gcov_flag: + config = importlib.import_module("scipy.__config__").show(mode='dicts') + compilers_config = config['Compilers'] + cpp = compilers_config['c++']['name'] + c = compilers_config['c']['name'] + fortran = compilers_config['fortran']['name'] + if not (c == 'gcc' and cpp == 'gcc' and fortran == 'gcc'): + print("SciPy was built with --gcov flag which requires " + "LCOV while running tests.\nFurther, LCOV usage " + "requires GCC for C, C++ and Fortran codes in SciPy.\n" + "Compilers used currently are:\n" + f" C: {c}\n C++: {cpp}\n Fortran: {fortran}\n" + "Therefore, exiting without running tests.") + exit(1) # Exit because tests will give missing symbol error + + if submodule: + tests = PROJECT_MODULE + "." + submodule + + markexpr = mode + if (not pytest_args) and (not tests): + pytest_args = ('scipy',) + + if '-m' not in pytest_args: + if len(pytest_args) == 1 and not tests: + tests = pytest_args[0] + pytest_args = () + if markexpr != "full": + pytest_args = ('-m', markexpr) + pytest_args + + n_jobs = parallel + if (n_jobs != 1) and ('-n' not in pytest_args): + pytest_args = ('-n', str(n_jobs)) + pytest_args + + if tests and '--pyargs' not in pytest_args: + pytest_args += ('--pyargs', tests) + + if durations: + pytest_args += ('--durations', durations) + + if len(array_api_backend) != 0: + os.environ['SCIPY_ARRAY_API'] = json.dumps(list(array_api_backend)) + + parent_callback(**{"pytest_args": pytest_args, "tests": tests, + "coverage": coverage, **kwargs}) + +@click.option( + '--list-targets', '-t', default=False, is_flag=True, + help='List doc targets', + ) +@click.option( + '--parallel', '-j', default="auto", metavar='N_JOBS', + help="Number of parallel jobs" + ) +@click.option( + '--no-cache', default=False, is_flag=True, + help="Forces a full rebuild of the docs. Note that this may be " + \ + "needed in order to make docstring changes in C/Cython files " + \ + "show up." +) +@spin.util.extend_command(spin.cmds.meson.docs) +def docs(*, parent_callback, sphinx_target, clean, jobs, + list_targets, parallel, no_cache, **kwargs): + """📖 Build Sphinx documentation + + By default, SPHINXOPTS="-W", raising errors on warnings. + To build without raising on warnings: + + SPHINXOPTS="" spin docs + + To list all Sphinx targets: + + spin docs targets + + To build another Sphinx target: + + spin docs TARGET + + E.g., to build a zipfile of the html docs for distribution: + + spin docs dist + + """ + meson.docs.ignore_unknown_options = True + + if clean: # SciPy has its own mechanism to clear the previous docs build + cwd = os.getcwd() + os.chdir(os.path.join(cwd, "doc")) + subprocess.call(["make", "clean"], cwd=os.getcwd()) + clean = False + os.chdir(cwd) + + SPHINXOPTS = "-W" + if no_cache: + SPHINXOPTS += " -E" + + jobs = parallel + SPHINXOPTS = os.environ.get("SPHINXOPTS", "") + SPHINXOPTS + os.environ["SPHINXOPTS"] = SPHINXOPTS + + sphinx_target = "html" + + parent_callback(**{"sphinx_target": sphinx_target, + "clean": clean, "jobs": jobs, **kwargs}) + +def _set_pythonpath(pythonpath): + env = os.environ + env['PYTHONWARNINGS'] = env.get('PYTHONWARNINGS', 'all') + + if pythonpath: + for p in reversed(pythonpath.split(os.pathsep)): + sys.path.insert(0, p) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.python) +def python(*, parent_callback, pythonpath, **kwargs): + """🐍 Launch Python shell with PYTHONPATH set + + OPTIONS are passed through directly to Python, e.g.: + + spin python -c 'import sys; print(sys.path)' + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.ipython) +def ipython(*, parent_callback, pythonpath, **kwargs): + """💻 Launch IPython shell with PYTHONPATH set + + OPTIONS are passed through directly to IPython, e.g.: + + spin ipython -i myscript.py + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@click.option( + '--pythonpath', '-p', metavar='PYTHONPATH', default=None, + help='Paths to prepend to PYTHONPATH') +@spin.util.extend_command(spin.cmds.meson.shell) +def shell(*, parent_callback, pythonpath, **kwargs): + """💻 Launch shell with PYTHONPATH set + + SHELL_ARGS are passed through directly to the shell, e.g.: + + spin shell -- -c 'echo $PYTHONPATH' + + Ensure that your shell init file (e.g., ~/.zshrc) does not override + the PYTHONPATH. + """ + _set_pythonpath(pythonpath) + parent_callback(**kwargs) + +@contextlib.contextmanager +def working_dir(new_dir): + current_dir = os.getcwd() + try: + os.chdir(new_dir) + yield + finally: + os.chdir(current_dir) + +@click.command(context_settings={"ignore_unknown_options": True}) +@meson.build_dir_option +@click.pass_context +def mypy(ctx, build_dir=None): + """🦆 Run Mypy tests for SciPy + """ + click.secho( + "Invoking `build` prior to running mypy tests:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + try: + import mypy.api + except ImportError as e: + raise RuntimeError( + "Mypy not found. Please install it by running " + "pip install -r mypy_requirements.txt from the repo root" + ) from e + + build_dir = os.path.abspath(build_dir) + root = Path(build_dir).parent + install_dir = meson._get_site_packages(build_dir) + config = os.path.join(root, "mypy.ini") + check_path = PROJECT_MODULE + + with working_dir(install_dir): + os.environ['MYPY_FORCE_COLOR'] = '1' + click.secho(f"mypy.api.run --config-file {config} {check_path}", + bold=True, fg="bright_blue") + report, errors, status = mypy.api.run([ + "--config-file", + str(config), + check_path, + ]) + print(report, end='') + print(errors, end='', file=sys.stderr) + +@spin.util.extend_command(test, doc='') +def smoke_docs(*, parent_callback, pytest_args, **kwargs): + """🔧 Run doctests of objects in the public API. + + PYTEST_ARGS are passed through directly to pytest, e.g.: + + spin smoke-docs -- --pdb + + To run tests on a directory: + + \b + spin smoke-docs scipy/linalg + + To report the durations of the N slowest doctests: + + spin smoke-docs -- --durations=N + + To run doctests that match a given pattern: + + \b + spin smoke-docs -- -k "slogdet" + spin smoke-docs scipy/linalg -- -k "det and not slogdet" + + \b + Note: + ----- + + \b + - This command only runs doctests and skips everything under tests/ + - This command only doctests public objects: those which are accessible + from the top-level `__init__.py` file. + + """ # noqa: E501 + # prevent obscure error later; cf https://github.com/numpy/numpy/pull/26691/ + if not importlib.util.find_spec("scipy_doctest"): + raise ModuleNotFoundError("Please install scipy-doctest") + + tests = kwargs["tests"] + if kwargs["submodule"]: + tests = PROJECT_MODULE + "." + kwargs["submodule"] + + if not pytest_args and not tests: + pytest_args = ('scipy', ) + + # turn doctesting on: + doctest_args = ( + '--doctest-modules', + '--doctest-collect=api' + ) + + if not tests: + doctest_args += ('--doctest-collect=api', ) + + pytest_args = pytest_args + doctest_args + + parent_callback(**{"pytest_args": pytest_args, **kwargs}) + +@click.command() +@click.option( + '--verbose', '-v', default=False, is_flag=True, + help="more verbosity") +@click.option( + '--submodule', '-s', default=None, metavar='MODULE_NAME', + help="Submodule whose tests to run (cluster, constants, ...)") +@meson.build_dir_option +@click.pass_context +def refguide_check(ctx, build_dir=None, *args, **kwargs): + """:wrench: Run refguide check.""" + click.secho( + "Invoking `build` prior to running refguide-check:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + build_dir = os.path.abspath(build_dir) + root = Path(build_dir).parent + install_dir = meson._get_site_packages(build_dir) + + cmd = [f'{sys.executable}', + os.path.join(root, 'tools', 'refguide_check.py')] + + if ctx.params["verbose"]: + cmd += ['-vvv'] + + if ctx.params["submodule"]: + cmd += [ctx.params["submodule"]] + + os.environ['PYTHONPATH'] = install_dir + util.run(cmd) + +@click.command() +@click.argument( + 'pytest_args', nargs=-1, metavar='PYTEST-ARGS', required=False +) +@click.option( + '--tests', '-t', default=None, multiple=True, metavar='TESTS', + help='Specify *rst files to smoke test') +@click.option( + '--verbose', '-v', default=False, is_flag=True, help="verbosity") +@meson.build_dir_option +@click.pass_context +def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs): + """🔧 Run doctests of user-facing rst tutorials. + + To test all tutorials in the scipy doc/source/tutorial directory, use + + spin smoke-tutorials + + To run tests on a specific RST file: + + \b + spin smoke-tutorials doc/source/reference/stats.rst + spin smoke-tutorials -t doc/source/reference/stats.rst + + \b + Note: + ----- + + \b + - This command only runs doctests and skips everything under tests/ + - This command only doctests public objects: those which are accessible + from the top-level `__init__.py` file. + + """ # noqa: E501 + + click.secho( + "Invoking `build` prior to running tests for tutorials:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + meson._set_pythonpath(build_dir) + + cmd = ['pytest'] + if tests: + cmd += list(tests) + else: + cmd += ['doc/source/tutorial', '--doctest-glob=*rst'] + if verbose: + cmd += ['-v'] + + extra_argv = list(pytest_args[:]) if pytest_args else [] + if extra_argv and extra_argv[0] == '--': + extra_argv = extra_argv[1:] + cmd += extra_argv + + cmd_str = ' '.join(cmd) + click.secho(cmd_str, bold=True, fg="bright_blue") + util.run(cmd) + +@click.command() +@click.option( + '--fix', default=False, is_flag=True, + help='Attempt to auto-fix errors') +@click.option("--diff-against", default="main", help="Diff against " + "this branch and lint modified files. Use either " + "`--diff-against` or `--files`, but not both.") +@click.option("--files", default="", + help="Lint these files or directories; " + "use **/*.py to lint all files") +@click.option("--all", default=False, is_flag=True, + help="This overrides `--diff-against` and `--files` " + "to lint all local files (excluding subprojects).") +@click.option("--no-cython", default=True, is_flag=True, + help="Do not run cython-lint.") +@click.pass_context +def lint(ctx, fix, diff_against, files, all, no_cython): + """:dash: Run linter on modified files and check for + disallowed Unicode characters and possibly-invalid test names.""" + root = Path(__file__).parent.parent + + cmd_lint = [os.path.join(root, 'tools', 'lint.py'), + f'--diff-against={diff_against}'] + if files != "": + cmd_lint += [f'--files={files}'] + if all: + cmd_lint += ['--all'] + if no_cython: + cmd_lint += ['--no-cython'] + if fix: + cmd_lint += ['--fix'] + util.run(cmd_lint) + + cmd_unicode = [os.path.join(root, 'tools', 'check_unicode.py')] + util.run(cmd_unicode) + + cmd_check_test_name = [os.path.join(root, 'tools', 'check_test_name.py')] + util.run(cmd_check_test_name) + +# From scipy: benchmarks/benchmarks/common.py +def _set_mem_rlimit(max_mem=None): + """ + Set address space rlimit + """ + import resource + import psutil + + mem = psutil.virtual_memory() + + if max_mem is None: + max_mem = int(mem.total * 0.7) + cur_limit = resource.getrlimit(resource.RLIMIT_AS) + if cur_limit[0] > 0: + max_mem = min(max_mem, cur_limit[0]) + + try: + resource.setrlimit(resource.RLIMIT_AS, (max_mem, cur_limit[1])) + except ValueError: + # on macOS may raise: current limit exceeds maximum limit + pass + +def _run_asv(cmd): + # Always use ccache, if installed + PATH = os.environ['PATH'] + EXTRA_PATH = os.pathsep.join([ + '/usr/lib/ccache', '/usr/lib/f90cache', + '/usr/local/lib/ccache', '/usr/local/lib/f90cache' + ]) + env = os.environ + env['PATH'] = f'{EXTRA_PATH}{os.pathsep}{PATH}' + + # Control BLAS/LAPACK threads + env['OPENBLAS_NUM_THREADS'] = '1' + env['MKL_NUM_THREADS'] = '1' + + # Limit memory usage + try: + _set_mem_rlimit() + except (ImportError, RuntimeError): + pass + + util.run(cmd, cwd='benchmarks', env=env) + +def _commit_to_sha(commit): + p = util.run(['git', 'rev-parse', commit], output=False, echo=False) + if p.returncode != 0: + raise( + click.ClickException( + f'Could not find SHA matching commit `{commit}`' + ) + ) + + return p.stdout.decode('ascii').strip() + + +def _dirty_git_working_dir(): + # Changes to the working directory + p0 = util.run(['git', 'diff-files', '--quiet']) + + # Staged changes + p1 = util.run(['git', 'diff-index', '--quiet', '--cached', 'HEAD']) + + return (p0.returncode != 0 or p1.returncode != 0) + +@click.command() +@click.option( + '--tests', '-t', + default=None, metavar='TESTS', multiple=True, + help="Which tests to run" +) +@click.option( + '--submodule', '-s', default=None, metavar='SUBMODULE', + help="Submodule whose tests to run (cluster, constants, ...)") +@click.option( + '--compare', '-c', + is_flag=True, + default=False, + help="Compare benchmarks between the current branch and main " + "(unless other branches specified). " + "The benchmarks are each executed in a new isolated " + "environment." +) +@click.option( + '--verbose', '-v', is_flag=True, default=False +) +@click.option( + '--quick', '-q', is_flag=True, default=False, + help="Run each benchmark only once (timings won't be accurate)" +) +@click.argument( + 'commits', metavar='', + required=False, + nargs=-1 +) +@meson.build_dir_option +@click.pass_context +def bench(ctx, tests, submodule, compare, verbose, quick, + commits, build_dir=None, *args, **kwargs): + """:wrench: Run benchmarks. + + \b + ```python + Examples: + + $ spin bench -t integrate.SolveBVP + $ spin bench -t linalg.Norm + $ spin bench --compare main + ``` + """ + build_dir = os.path.abspath(build_dir) + if not commits: + commits = ('main', 'HEAD') + elif len(commits) == 1: + commits = commits + ('HEAD',) + elif len(commits) > 2: + raise click.ClickException( + 'Need a maximum of two revisions to compare' + ) + + bench_args = [] + if submodule: + submodule = (submodule, ) + else: + submodule = tuple() + for t in tests + submodule: + bench_args += ['--bench', t] + + if verbose: + bench_args = ['-v'] + bench_args + + if quick: + bench_args = ['--quick'] + bench_args + + if not compare: + # No comparison requested; we build and benchmark the current version + + click.secho( + "Invoking `build` prior to running benchmarks:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) + + meson._set_pythonpath(build_dir) + + p = util.run( + ['python', '-c', 'import scipy as sp; print(sp.__version__)'], + cwd='benchmarks', + echo=False, + output=False + ) + os.chdir('..') + + np_ver = p.stdout.strip().decode('ascii') + click.secho( + f'Running benchmarks on SciPy {np_ver}', + bold=True, fg="bright_green" + ) + cmd = [ + 'asv', 'run', '--dry-run', + '--show-stderr', '--python=same', + '--quick'] + bench_args + _run_asv(cmd) + else: + # Ensure that we don't have uncommited changes + commit_a, commit_b = [_commit_to_sha(c) for c in commits] + + if commit_b == 'HEAD' and _dirty_git_working_dir(): + click.secho( + "WARNING: you have uncommitted changes --- " + "these will NOT be benchmarked!", + fg="red" + ) + + cmd_compare = [ + 'asv', 'continuous', '--factor', '1.05', '--quick' + ] + bench_args + [commit_a, commit_b] + _run_asv(cmd_compare) + + +def configure_scipy_openblas(blas_variant='32'): + """Create scipy-openblas.pc and scipy/_distributor_init_local.py + + Requires a pre-installed scipy-openblas32 wheel from PyPI. + """ + basedir = os.getcwd() + pkg_config_fname = os.path.join(basedir, "scipy-openblas.pc") + + if os.path.exists(pkg_config_fname): + return None + + module_name = f"scipy_openblas{blas_variant}" + try: + openblas = importlib.import_module(module_name) + except ModuleNotFoundError: + raise RuntimeError(f"Importing '{module_name}' failed. " + "Make sure it is installed and reachable " + "by the current Python executable. You can " + f"install it via 'pip install {module_name}'.") + + local = os.path.join(basedir, "scipy", "_distributor_init_local.py") + with open(local, "w", encoding="utf8") as fid: + fid.write(f"import {module_name}\n") + + with open(pkg_config_fname, "w", encoding="utf8") as fid: + fid.write(openblas.get_pkg_config()) + +physical_cores_cache = None + +def _cpu_count_cgroup(os_cpu_count): + # Cgroup CPU bandwidth limit available in Linux since 2.6 kernel + cpu_max_fname = "/sys/fs/cgroup/cpu.max" + cfs_quota_fname = "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" + cfs_period_fname = "/sys/fs/cgroup/cpu/cpu.cfs_period_us" + if os.path.exists(cpu_max_fname): + # cgroup v2 + # https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html + with open(cpu_max_fname) as fh: + cpu_quota_us, cpu_period_us = fh.read().strip().split() + elif os.path.exists(cfs_quota_fname) and os.path.exists(cfs_period_fname): + # cgroup v1 + # https://www.kernel.org/doc/html/latest/scheduler/sched-bwc.html#management + with open(cfs_quota_fname) as fh: + cpu_quota_us = fh.read().strip() + with open(cfs_period_fname) as fh: + cpu_period_us = fh.read().strip() + else: + # No Cgroup CPU bandwidth limit (e.g. non-Linux platform) + cpu_quota_us = "max" + cpu_period_us = 100_000 # unused, for consistency with default values + + if cpu_quota_us == "max": + # No active Cgroup quota on a Cgroup-capable platform + return os_cpu_count + else: + cpu_quota_us = int(cpu_quota_us) + cpu_period_us = int(cpu_period_us) + if cpu_quota_us > 0 and cpu_period_us > 0: + return math.ceil(cpu_quota_us / cpu_period_us) + else: # pragma: no cover + # Setting a negative cpu_quota_us value is a valid way to disable + # cgroup CPU bandwidth limits + return os_cpu_count + + +def _cpu_count_affinity(os_cpu_count): + # Number of available CPUs given affinity settings + if hasattr(os, "sched_getaffinity"): + try: + return len(os.sched_getaffinity(0)) + except NotImplementedError: + pass + + # On PyPy and possibly other platforms, os.sched_getaffinity does not exist + # or raises NotImplementedError, let's try with the psutil if installed. + try: + import psutil + + p = psutil.Process() + if hasattr(p, "cpu_affinity"): + return len(p.cpu_affinity()) + + except ImportError: # pragma: no cover + if ( + sys.platform == "linux" + and os.environ.get("LOKY_MAX_CPU_COUNT") is None + ): + # PyPy does not implement os.sched_getaffinity on Linux which + # can cause severe oversubscription problems. Better warn the + # user in this particularly pathological case which can wreck + # havoc, typically on CI workers. + warnings.warn( + "Failed to inspect CPU affinity constraints on this system. " + "Please install psutil or explicitly set LOKY_MAX_CPU_COUNT.", + stacklevel=4 + ) + + # This can happen for platforms that do not implement any kind of CPU + # infinity such as macOS-based platforms. + return os_cpu_count + + +def _cpu_count_user(os_cpu_count): + """Number of user defined available CPUs""" + cpu_count_affinity = _cpu_count_affinity(os_cpu_count) + + cpu_count_cgroup = _cpu_count_cgroup(os_cpu_count) + + # User defined soft-limit passed as a loky specific environment variable. + cpu_count_loky = int(os.environ.get("LOKY_MAX_CPU_COUNT", os_cpu_count)) + + return min(cpu_count_affinity, cpu_count_cgroup, cpu_count_loky) + +def _count_physical_cores(): + """Return a tuple (number of physical cores, exception) + + If the number of physical cores is found, exception is set to None. + If it has not been found, return ("not found", exception). + + The number of physical cores is cached to avoid repeating subprocess calls. + """ + exception = None + + # First check if the value is cached + global physical_cores_cache + if physical_cores_cache is not None: + return physical_cores_cache, exception + + # Not cached yet, find it + try: + if sys.platform == "linux": + cpu_info = subprocess.run( + "lscpu --parse=core".split(), capture_output=True, text=True + ) + cpu_info = cpu_info.stdout.splitlines() + cpu_info = {line for line in cpu_info if not line.startswith("#")} + cpu_count_physical = len(cpu_info) + elif sys.platform == "win32": + cpu_info = subprocess.run( + "wmic CPU Get NumberOfCores /Format:csv".split(), + capture_output=True, + text=True, + ) + cpu_info = cpu_info.stdout.splitlines() + cpu_info = [ + l.split(",")[1] + for l in cpu_info + if (l and l != "Node,NumberOfCores") + ] + cpu_count_physical = sum(map(int, cpu_info)) + elif sys.platform == "darwin": + cpu_info = subprocess.run( + "sysctl -n hw.physicalcpu".split(), + capture_output=True, + text=True, + ) + cpu_info = cpu_info.stdout + cpu_count_physical = int(cpu_info) + else: + raise NotImplementedError(f"unsupported platform: {sys.platform}") + + # if cpu_count_physical < 1, we did not find a valid value + if cpu_count_physical < 1: + raise ValueError(f"found {cpu_count_physical} physical cores < 1") + + except Exception as e: + exception = e + cpu_count_physical = "not found" + + # Put the result in cache + physical_cores_cache = cpu_count_physical + + return cpu_count_physical, exception + +def cpu_count(only_physical_cores=False): + """Return the number of CPUs the current process can use. + + The returned number of CPUs accounts for: + * the number of CPUs in the system, as given by + ``multiprocessing.cpu_count``; + * the CPU affinity settings of the current process + (available on some Unix systems); + * Cgroup CPU bandwidth limit (available on Linux only, typically + set by docker and similar container orchestration systems); + * the value of the LOKY_MAX_CPU_COUNT environment variable if defined. + and is given as the minimum of these constraints. + + If ``only_physical_cores`` is True, return the number of physical cores + instead of the number of logical cores (hyperthreading / SMT). Note that + this option is not enforced if the number of usable cores is controlled in + any other way such as: process affinity, Cgroup restricted CPU bandwidth + or the LOKY_MAX_CPU_COUNT environment variable. If the number of physical + cores is not found, return the number of logical cores. + + Note that on Windows, the returned number of CPUs cannot exceed 61, see: + https://bugs.python.org/issue26903. + + It is also always larger or equal to 1. + """ + # Note: os.cpu_count() is allowed to return None in its docstring + os_cpu_count = os.cpu_count() or 1 + if sys.platform == "win32": + # On Windows, attempting to use more than 61 CPUs would result in a + # OS-level error. See https://bugs.python.org/issue26903. According to + # https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups + # it might be possible to go beyond with a lot of extra work but this + # does not look easy. + os_cpu_count = min(os_cpu_count, _MAX_WINDOWS_WORKERS) + + cpu_count_user = _cpu_count_user(os_cpu_count) + aggregate_cpu_count = max(min(os_cpu_count, cpu_count_user), 1) + + if not only_physical_cores: + return aggregate_cpu_count + + if cpu_count_user < os_cpu_count: + # Respect user setting + return max(cpu_count_user, 1) + + cpu_count_physical, exception = _count_physical_cores() + if cpu_count_physical != "not found": + return cpu_count_physical + + # Fallback to default behavior + if exception is not None: + # warns only the first time + warnings.warn( + "Could not find the number of physical cores for the " + f"following reason:\n{exception}\n" + "Returning the number of logical cores instead. You can " + "silence this warning by setting LOKY_MAX_CPU_COUNT to " + "the number of cores you want to use.", + stacklevel=2 + ) + traceback.print_tb(exception.__traceback__) + + return aggregate_cpu_count + +def get_site_packages(build_dir): + try: + return meson._get_site_packages(build_dir) + except FileNotFoundError: + return None diff --git a/environment.yml b/environment.yml index 5a6fe3f43d6c..b4984d3dd407 100644 --- a/environment.yml +++ b/environment.yml @@ -55,6 +55,7 @@ dependencies: - gmpy2 - threadpoolctl # For CLI + - spin - rich-click - click - doit>=0.36.0 diff --git a/pyproject.toml b/pyproject.toml index 1181de3fe34f..81b6d5dcab9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,9 @@ tracker = "https://github.com/scipy/scipy/issues" [tool.doit] dodoFile = "dev.py" +[tool.spin] +package = 'scipy' + [tool.meson-python.args] install = ['--skip-subprojects'] @@ -166,3 +169,26 @@ repair-wheel-command = "bash ./tools/wheels/repair_windows.sh {wheel} {dest_dir} PKG_CONFIG_PATH = "{project}" # do this instead (which will override this setting) # set CIBW_ENVIRONMENT_WINDOWS=PKG_CONFIG_PATH=PWD.replace('\\', '/') + +[tool.spin.commands] +"Build" = [ + ".spin/cmds.py:build", + ".spin/cmds.py:test", + ".spin/cmds.py:mypy", + ".spin/cmds.py:lint" +] +"Environments" = [ + "spin.cmds.meson.run", + ".spin/cmds.py:python", + ".spin/cmds.py:ipython", + ".spin/cmds.py:shell", + "spin.cmds.meson.gdb", + "spin.cmds.meson.lldb" +] +"Documentation" = [ + ".spin/cmds.py:docs", + ".spin/cmds.py:smoke_docs", + ".spin/cmds.py:refguide_check", + ".spin/cmds.py:smoke_tutorials" +] +"Metrics" = [".spin/cmds.py:bench"] From ece700766698cedfab7e1a10dee849cdf54e0516 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 25 Apr 2025 15:41:39 -0700 Subject: [PATCH 054/251] MAINT: stats.DiscreteDistribution: replace _lazywhere with apply_where --- scipy/stats/_distribution_infrastructure.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 4ce840434d12..261bd713cc9d 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -7,7 +7,8 @@ import numpy as np from numpy import inf -from scipy._lib._util import _rng_spawn, _RichResult, _lazywhere +from scipy._lib.array_api_extra import apply_where +from scipy._lib._util import _rng_spawn, _RichResult from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats from scipy.special._ufuncs import _log1mexp @@ -324,7 +325,7 @@ def get_numerical_endpoints(self, parameter_values): a, b = self.endpoints # If `a` (`b`) is a string - the name of the parameter that defines # the endpoint of the domain - then corresponding numerical values - # will be found in the `parameter_values` dictionary. + # will be found in the `parameter_values` dictionary. # If a callable, it will be executed with `parameter_values` passed as # keyword arguments, and it will return the numerical values. # Otherwise, it is itself the array of numerical values of the endpoint. @@ -1396,7 +1397,7 @@ def _generate_example(dist_family): >>> x = {x} >>> X.pdf(x), X.pmf(x) {X.pdf(x), X.pmf(x)} - + The cumulative distribution function, its complement, and the logarithm of these functions are evaluated similarly. @@ -3628,14 +3629,14 @@ def _ccdf2_addition(self, x, y, **params): a, _ = self._support(**params) ccdf_y = self._ccdf_dispatch(y, **params) _cdf, args = _kwargs2args(self._cdf_dispatch, kwargs=params) - cdf_xm1 = _lazywhere(x - 1 >= a, [x - 1] + args, _cdf, fillvalue=0) + cdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _cdf, fillvalue=0) return ccdf_y + cdf_xm1 def _logccdf2_addition(self, x, y, **params): a, _ = self._support(**params) logccdf_y = self._logccdf_dispatch(y, **params) _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) - logcdf_xm1 = _lazywhere(x - 1 >= a, [x - 1] + args, _logcdf, fillvalue=-np.inf) + logcdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _logcdf, fillvalue=-np.inf) return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) def _icdf_inversion(self, x, **params): @@ -3863,7 +3864,7 @@ def make_distribution(dist): ``moment``, and ``sample``. If defined, these methods must accept the parameters of the distribution as keyword arguments and also accept any positional-only arguments accepted by - the corresponding method of `ContinuousDistribution`. + the corresponding method of `ContinuousDistribution`. When multiple parameterizations are defined, these methods must accept all parameters from all parameterizations. The ``moment`` method must accept the ``order`` and ``kind`` arguments by position or keyword, but @@ -3943,7 +3944,7 @@ class or its methods for more information. ... ... @property ... def parameters(self): - ... return {"a": (-np.inf, np.inf), + ... return {"a": (-np.inf, np.inf), ... "b": {'endpoints':('a', np.inf), 'inclusive':(True, False)}} ... ... @property @@ -5493,7 +5494,7 @@ def _logccdf_dispatch(self, x, *args, method=None, **params): a, b = self._dist._support(**params) xl = np.maximum(-x, a) xr = np.minimum(x, b) - return self._dist._logccdf2_dispatch(xl, xr, *args, method=method, + return self._dist._logccdf2_dispatch(xl, xr, *args, method=method, **params).real def _ccdf_dispatch(self, x, *args, method=None, **params): From 3810c551533e5afdd2a677b9be87622cee9878a1 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 26 Apr 2025 11:12:37 +0200 Subject: [PATCH 055/251] ENH: signal: freqz_sos array API --- scipy/signal/_filter_design.py | 15 ++- scipy/signal/tests/test_filter_design.py | 159 ++++++++++++++--------- 2 files changed, 109 insertions(+), 65 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 1f7c6ab8900f..5965fa6869be 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -797,16 +797,16 @@ def group_delay(system, w=512, whole=False, fs=2*pi): return w, gd -def _validate_sos(sos): +def _validate_sos(sos, xp): """Helper to validate a SOS input""" - sos = np.asarray(sos) - sos = np.atleast_2d(sos) + sos = xp.asarray(sos) + sos = xpx.atleast_nd(sos, ndim=2, xp=xp) if sos.ndim != 2: raise ValueError('sos array must be 2D') n_sections, m = sos.shape if m != 6: raise ValueError('sos array must be shape (n_sections, 6)') - if not (sos[:, 3] == 1).all(): + if not xp.all(sos[:, 3] == 1): raise ValueError('sos[:, 3] should be all ones') return sos, n_sections @@ -925,13 +925,16 @@ def freqz_sos(sos, worN=512, whole=False, fs=2*pi): >>> plt.show() """ + xp = array_namespace(sos) + fs = _validate_fs(fs, allow_none=False) - sos, n_sections = _validate_sos(sos) + sos, n_sections = _validate_sos(sos, xp) if n_sections == 0: raise ValueError('Cannot compute frequencies with no sections') h = 1. - for row in sos: + for j in range(sos.shape[0]): + row = sos[j, :] w, rowh = freqz(row[:3], row[3:], worN=worN, whole=whole, fs=fs) h *= rowh return w, h diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 0635ce05c8b0..e00bc5a2ccd4 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -1016,7 +1016,7 @@ def test_fs_validation(self): class Testfreqz_sos: - def test_freqz_sos_basic(self): + def test_freqz_sos_basic(self, xp): # Compare the results of freqz and freqz_sos for a low order # Butterworth filter. @@ -1024,6 +1024,8 @@ def test_freqz_sos_basic(self): b, a = butter(4, 0.2) sos = butter(4, 0.2, output='sos') + b, a, sos = map(xp.asarray, (b, a, sos)) # XXX until butter is converted + w, h = freqz(b, a, worN=N) w2, h2 = freqz_sos(sos, worN=N) xp_assert_equal(w2, w) @@ -1031,132 +1033,155 @@ def test_freqz_sos_basic(self): b, a = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass') sos = ellip(3, 1, 30, (0.2, 0.3), btype='bandpass', output='sos') + b, a, sos = map(xp.asarray, (b, a, sos)) # XXX until ellip is converted + w, h = freqz(b, a, worN=N) w2, h2 = freqz_sos(sos, worN=N) xp_assert_equal(w2, w) xp_assert_close(h2, h, rtol=1e-10, atol=1e-14) + # must have at least one section - assert_raises(ValueError, freqz_sos, sos[:0]) + with assert_raises(ValueError): + freqz_sos(sos[:0, ...]) - def test_backward_compat(self): + def test_backward_compat(self, xp): # For backward compatibility, test if None act as a wrapper for default N = 500 sos = butter(4, 0.2, output='sos') + sos = xp.asarray(sos) # XXX until butter is converted w1, h1 = freqz_sos(sos, worN=N) w2, h2 = sosfreqz(sos, worN=N) assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - def test_freqz_sos_design(self): + @skip_xp_backends("dask.array", reason="float cannot be interpreted as in integer") + def test_freqz_sos_design(self, xp): # Compare freqz_sos output against expected values for different # filter types # from cheb2ord N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) sos = cheby2(N, 60, Wn, 'stop', output='sos') + sos = xp.asarray(sos) # XXX + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w <= 0.1]), np.asarray(0.), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.1]), xp.asarray(0.), atol=3.01, check_shape=False) - xp_assert_close(20 * np.log10(h[w >= 0.6]), np.asarray(0.), atol=3.01, + xp_assert_close(20 * xp.log10(h[w >= 0.6]), xp.asarray(0.), atol=3.01, check_shape=False) xp_assert_close(h[(w >= 0.2) & (w <= 0.5)], - np.asarray(0.), atol=1e-3, + xp.asarray(0.), atol=1e-3, check_shape=False) # <= -60 dB N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 150) sos = cheby2(N, 150, Wn, 'stop', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.abs(h)) - w /= np.pi - xp_assert_close(dB[w <= 0.1], np.asarray(0.0), atol=3.01, check_shape=False) - xp_assert_close(dB[w >= 0.6], np.asarray(0.0), atol=3.01, check_shape=False) - assert np.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) + dB = 20*xp.log10(xp.abs(h)) + w = w / xp.pi + xp_assert_close(dB[w <= 0.1], xp.asarray(0.0), atol=3.01, check_shape=False) + xp_assert_close(dB[w >= 0.6], xp.asarray(0.0), atol=3.01, check_shape=False) + assert xp.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) # from cheb1ord N, Wn = cheb1ord(0.2, 0.3, 3, 40) sos = cheby1(N, 3, Wn, 'low', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w <= 0.2]), np.asarray(0.0), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w <= 0.2]), xp.asarray(0.0), atol=3.01, check_shape=False) - xp_assert_close(h[w >= 0.3], np.asarray(0.0), atol=1e-2, + xp_assert_close(h[w >= 0.3], xp.asarray(0.0), atol=1e-2, check_shape=False) # <= -40 dB N, Wn = cheb1ord(0.2, 0.3, 1, 150) sos = cheby1(N, 1, Wn, 'low', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.abs(h)) + dB = 20*xp.log10(xp.abs(h)) w /= np.pi - xp_assert_close(dB[w <= 0.2], np.asarray(0.0), atol=1.01, - check_shape=False) - assert np.all(dB[w >= 0.3] < -149.9) + xp_assert_close(dB[w <= 0.2], xp.asarray(0.0), atol=1.01, check_shape=False) + assert xp.all(dB[w >= 0.3] < -149.9) # adapted from ellipord N, Wn = ellipord(0.3, 0.2, 3, 60) sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi - xp_assert_close(20 * np.log10(h[w >= 0.3]), np.asarray(0.0), atol=3.01, + h = xp.abs(h) + w = w / xp.pi + xp_assert_close(20 * xp.log10(h[w >= 0.3]), xp.asarray(0.0), atol=3.01, check_shape=False) - xp_assert_close(h[w <= 0.1], np.asarray(0.0), atol=1.5e-3, + xp_assert_close(h[w <= 0.1], xp.asarray(0.0), atol=1.5e-3, check_shape=False) # <= -60 dB (approx) # adapted from buttord N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 40) sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi + h = xp.abs(h) + w = w / xp.pi h014 = h[w <= 0.14] - xp_assert_close(h014, np.zeros_like(h014), atol=1e-2) # <= -40 dB + xp_assert_close(h014, xp.zeros_like(h014), atol=1e-2) # <= -40 dB h06 = h[w >= 0.6] - xp_assert_close(h06, np.zeros_like(h06), atol=1e-2) # <= -40 dB - h0205 = 20 * np.log10(h[(w >= 0.2) & (w <= 0.5)]) - xp_assert_close(h0205, np.zeros_like(h0205), atol=3.01) + xp_assert_close(h06, xp.zeros_like(h06), atol=1e-2) # <= -40 dB + h0205 = 20 * xp.log10(h[(w >= 0.2) & (w <= 0.5)]) + xp_assert_close(h0205, xp.zeros_like(h0205), atol=3.01) N, Wn = buttord([0.2, 0.5], [0.14, 0.6], 3, 100) sos = butter(N, Wn, 'band', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.maximum(np.abs(h), 1e-10)) - w /= np.pi + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi - assert np.all(dB[(w > 0) & (w <= 0.14)] < -99.9) - assert np.all(dB[w >= 0.6] < -99.9) + assert xp.all(dB[(w > 0) & (w <= 0.14)] < -99.9) + assert xp.all(dB[w >= 0.6] < -99.9) db0205 = dB[(w >= 0.2) & (w <= 0.5)] - xp_assert_close(db0205, np.zeros_like(db0205), atol=3.01) + xp_assert_close(db0205, xp.zeros_like(db0205), atol=3.01) - def test_freqz_sos_design_ellip(self): + def test_freqz_sos_design_ellip(self, xp): N, Wn = ellipord(0.3, 0.1, 3, 60) sos = ellip(N, 0.3, 60, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - h = np.abs(h) - w /= np.pi + h = xp.abs(h) + w = w / xp.pi - h03 = 20 * np.log10(h[w >= 0.3]) - xp_assert_close(h03, np.zeros_like(h03), atol=3.01) + h03 = 20 * xp.log10(h[w >= 0.3]) + xp_assert_close(h03, xp.zeros_like(h03), atol=3.01) h01 = h[w <= 0.1] - xp_assert_close(h01, np.zeros_like(h01), atol=1.5e-3) # <= -60 dB (approx) + xp_assert_close(h01, xp.zeros_like(h01), atol=1.5e-3) # <= -60 dB (approx) N, Wn = ellipord(0.3, 0.2, .5, 150) sos = ellip(N, .5, 150, Wn, 'high', output='sos') + sos = xp.asarray(sos) + w, h = freqz_sos(sos) - dB = 20*np.log10(np.maximum(np.abs(h), 1e-10)) - w /= np.pi + dB = 20*xp.log10(xp.maximum(xp.abs(h), xp.asarray(1e-10))) + w = w / xp.pi db03 = dB[w >= 0.3] - xp_assert_close(db03, np.zeros_like(db03), atol=.55) + xp_assert_close(db03, xp.zeros_like(db03), atol=.55) # Allow some numerical slop in the upper bound -150, so this is # a check that dB[w <= 0.2] is less than or almost equal to -150. - assert dB[w <= 0.2].max() < -150*(1 - 1e-12) + assert xp.max(dB[w <= 0.2]) < -150*(1 - 1e-12) @mpmath_check("0.10") - def test_freqz_sos_against_mp(self): + def test_freqz_sos_against_mp(self, xp): # Compare the result of freqz_sos applied to a high order Butterworth # filter against the result computed using mpmath. (signal.freqz fails # miserably with such high order filters.) @@ -1167,52 +1192,67 @@ def test_freqz_sos_against_mp(self): with mpmath.workdps(80): z_mp, p_mp, k_mp = mpsig.butter_lp(order, Wn) w_mp, h_mp = mpsig.zpkfreqz(z_mp, p_mp, k_mp, N) - w_mp = np.array([float(x) for x in w_mp]) - h_mp = np.array([complex(x) for x in h_mp]) + w_mp = xp.asarray([float(x) for x in w_mp]) + h_mp = xp.asarray([complex(x) for x in h_mp]) sos = butter(order, Wn, output='sos') + sos = xp.asarray(sos) w, h = freqz_sos(sos, worN=N) xp_assert_close(w, w_mp, rtol=1e-12, atol=1e-14) xp_assert_close(h, h_mp, rtol=1e-12, atol=1e-14) - def test_fs_param(self): + def test_fs_param(self, xp): fs = 900 - sos = [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, 1.0, -0.37256600288916636, 0.0], [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) # N = None, whole=False w1, h1 = freqz_sos(sos, fs=fs) w2, h2 = freqz_sos(sos) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 512, endpoint=False)) # N = None, whole=True w1, h1 = freqz_sos(sos, whole=True, fs=fs) w2, h2 = freqz_sos(sos, whole=True) xp_assert_close(h1, h2, atol=1e-27) - xp_assert_close(w1, np.linspace(0, fs, 512, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 512, endpoint=False)) # N = 5, whole=False w1, h1 = freqz_sos(sos, 5, fs=fs) w2, h2 = freqz_sos(sos, 5) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs/2, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs/2, 5, endpoint=False)) # N = 5, whole=True w1, h1 = freqz_sos(sos, 5, whole=True, fs=fs) w2, h2 = freqz_sos(sos, 5, whole=True) xp_assert_close(h1, h2) - xp_assert_close(w1, np.linspace(0, fs, 5, endpoint=False)) + xp_assert_close(w1, xp.linspace(0, fs, 5, endpoint=False)) + + @skip_xp_backends(np_only=True, reason="array-likes") + def test_fs_param2(self, xp): + fs = 900 + sos = xp.asarray( + [[0.03934683014103762, 0.07869366028207524, 0.03934683014103762, + 1.0, -0.37256600288916636, 0.0], + [1.0, 1.0, 0.0, 1.0, -0.9495739996946778, 0.45125966317124144]] + ) # w is an array_like - for w in ([123], (123,), np.array([123]), (50, 123, 230), - np.array([50, 123, 230])): + for w in ([123], (123,), xp.asarray([123]), (50, 123, 230), + xp.asarray([50, 123, 230])): w1, h1 = freqz_sos(sos, w, fs=fs) - w2, h2 = freqz_sos(sos, 2*pi*np.array(w)/fs) + w1, h1 = map(xp.asarray, (w1, h1)) + + w2, h2 = freqz_sos(sos, 2*pi*xp.asarray(w, dtype=sos.dtype)/fs) xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) + @skip_xp_backends(np_only=True, reason="numpy scalars") def test_w_or_N_types(self): # Measure at 7 (polyval) or 8 (fft) equally-spaced points for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), @@ -1235,6 +1275,7 @@ def test_w_or_N_types(self): assert_array_almost_equal(w_out, [8]) assert_array_almost_equal(h, [1]) + @skip_xp_backends(np_only=True) def test_fs_validation(self): sos = butter(4, 0.2, output='sos') with pytest.raises(ValueError, match="Sampling.*single scalar"): From a82a5782fbf1b348a07ca3e7999caf5ed52aa102 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Sat, 26 Apr 2025 10:15:31 +0100 Subject: [PATCH 056/251] MAINT: make `boost_math` a `subproject` (#21270) Addresses a part of gh-17751 Co-authored-by: Ralf Gommers --- .github/workflows/macos.yml | 6 ++++++ .gitmodules | 8 ++++---- LICENSES_bundled.txt | 8 ++++---- meson.build | 6 ++++++ scipy/_lib/_boost_utils.py | 9 --------- scipy/_lib/meson.build | 3 --- scipy/special/meson.build | 5 ++--- scipy/_lib/boost_math => subprojects/boost_math/math | 0 subprojects/boost_math/meson.build | 11 +++++++++++ 9 files changed, 33 insertions(+), 23 deletions(-) delete mode 100644 scipy/_lib/_boost_utils.py rename scipy/_lib/boost_math => subprojects/boost_math/math (100%) create mode 100644 subprojects/boost_math/meson.build diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 408e70ced09f..33d5da93faa1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -115,6 +115,12 @@ jobs: # optional test dependencies mamba install scikit-umfpack scikit-sparse + # configure system boost + mamba install libboost-headers=1.88.0 + export BOOST_INCLUDEDIR=${{ env.CONDA }}/envs/scipy-dev/include/boost + export BOOST_LIBRARYDIR=${{ env.CONDA }}/envs/scipy-dev/lib + rm -rf subprojects/boost_math # so will fail if system boost doesn't work + CC="ccache $CC" spin build - name: Test SciPy diff --git a/.gitmodules b/.gitmodules index 47bbe5273170..a9a6025be95b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,6 @@ path = scipy/_lib/unuran url = https://github.com/scipy/unuran.git shallow = true -[submodule "scipy/_lib/boost_math"] - path = scipy/_lib/boost_math - url = https://github.com/boostorg/math.git - shallow = true [submodule "scipy/_lib/array_api_compat"] path = scipy/_lib/array_api_compat url = https://github.com/data-apis/array-api-compat.git @@ -32,3 +28,7 @@ path = subprojects/highs url = https://github.com/scipy/HiGHs shallow = true +[submodule "subprojects/boost_math/math"] + path = subprojects/boost_math/math + url = https://github.com/boostorg/math.git + shallow = true diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index f32f9f112ddb..750a532a0037 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -257,9 +257,9 @@ License: MIT For details, see scipy/optimize/_highs/LICENCE Name: Boost -Files: scipy/_lib/boost_math/* +Files: subprojects/boost_math/math/* License: Boost Software License - Version 1.0 - For details, see scipy/_lib/boost_math/LICENSE.txt + For details, see subprojects/boost_math/math/LICENSE Name: Biasedurn Files: scipy/stats/biasedurn/* @@ -278,9 +278,9 @@ License 3-Clause BSD and scipy/stats/_rcont/logfactorial.c Name: array-api-compat -Files: scipy/_lib/array-api-compat/* +Files: scipy/_lib/array_api_compat/* License: MIT - For details, see scipy/_lib/array-api-compat/LICENCE + For details, see scipy/_lib/array_api_compat/LICENCE Name: Tempita Files: scipy/_build_utils/tempita/* diff --git a/meson.build b/meson.build index 371d983a59d1..a23eb64fbee5 100644 --- a/meson.build +++ b/meson.build @@ -153,4 +153,10 @@ if use_pythran xsimd_dep = dependency('xsimd', required: false) endif +boost_math_dep = dependency( + 'boost', + version : '1.88.0', + fallback : ['boost_math', 'boost_math_dep'], +) + subdir('scipy') diff --git a/scipy/_lib/_boost_utils.py b/scipy/_lib/_boost_utils.py deleted file mode 100644 index 2e5231fbaa81..000000000000 --- a/scipy/_lib/_boost_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -'''Helper functions to get location of header files.''' - -import pathlib - - -def _boost_dir(ret_path: bool = False) -> pathlib.Path | str: - '''Directory where root Boost/ directory lives.''' - p = pathlib.Path(__file__).parent / 'boost_math/include' - return p if ret_path else str(p) diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index ceb85728d4a5..fcfb2a8f98e5 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -1,7 +1,4 @@ fs = import('fs') -if not fs.exists('boost_math/README.md') - error('Missing the `boost` submodule! Run `git submodule update --init` to fix this.') -endif if not fs.exists('unuran/README.md') error('Missing the `unuran` submodule! Run `git submodule update --init` to fix this.') endif diff --git a/scipy/special/meson.build b/scipy/special/meson.build index e18876588aa6..40418816f0bb 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -162,10 +162,9 @@ py3.extension_module('_ufuncs_cxx', uf_cython_gen_cpp.process(cython_special[2]), # _ufuncs_cxx.pyx ], cpp_args: ufuncs_cxx_cpp_args, - include_directories: ['..', '../_lib/boost_math/include', '../_lib', - '../_build_utils/src'], + include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [np_dep, ellint_dep], + dependencies: [boost_math_dep, np_dep, ellint_dep], install: true, subdir: 'scipy/special', ) diff --git a/scipy/_lib/boost_math b/subprojects/boost_math/math similarity index 100% rename from scipy/_lib/boost_math rename to subprojects/boost_math/math diff --git a/subprojects/boost_math/meson.build b/subprojects/boost_math/meson.build new file mode 100644 index 000000000000..6b16ff260255 --- /dev/null +++ b/subprojects/boost_math/meson.build @@ -0,0 +1,11 @@ +project('boost-math', + version : '1.88.0', + meson_version: '>= 1.5.0', +) + +fs = import('fs') +if not fs.exists('math/README.md') + error('Missing the `boost_math` submodule! Run `git submodule update --init` to fix this.') +endif + +boost_math_dep = declare_dependency(include_directories: 'math/include') From be9af12d181e0f9fb0b78d99967434137a9c0e10 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 26 Apr 2025 11:19:05 +0200 Subject: [PATCH 057/251] TST: signal: skip a test which fails on jax.numpy --- scipy/signal/_filter_design.py | 5 ++++- scipy/signal/tests/test_filter_design.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 5965fa6869be..60a78df8900a 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -797,8 +797,11 @@ def group_delay(system, w=512, whole=False, fs=2*pi): return w, gd -def _validate_sos(sos, xp): +def _validate_sos(sos, xp=None): """Helper to validate a SOS input""" + if xp is None: + xp = np # backcompat, cf sosfilt, sosfiltfilt + sos = xp.asarray(sos) sos = xpx.atleast_nd(sos, ndim=2, xp=xp) if sos.ndim != 2: diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index e00bc5a2ccd4..0647dd69465c 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -564,6 +564,7 @@ def test_output(self, xp): assert_array_almost_equal(xp.real(H), xp.real(expected)) assert_array_almost_equal(xp.imag(H), xp.imag(expected)) + @skip_xp_backends("jax.numpy", reason="eigvals not available on CUDA") def test_freq_range(self, xp): # Test that freqresp() finds a reasonable frequency range. # 1st order low-pass filter: H(s) = 1 / (s + 1) @@ -640,6 +641,7 @@ def test_freq_range(self, xp): w, H = freqs_zpk(z, p, k, worN=n) assert_array_almost_equal(w, expected_w) + @skip_xp_backends("jax.numpy", reason="eigvals not available on CUDA") def test_vs_freqs(self, xp): b, a = cheby1(4, 5, 100, analog=True, output='ba') z, p, k = cheby1(4, 5, 100, analog=True, output='zpk') From 378ca7a6b6c068e8ff0f45644377d36977983fee Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 26 Apr 2025 16:59:17 +0200 Subject: [PATCH 058/251] BUG: add a fudge factor for jax in findfreqs Otherwise, xp.round(-1.5) is -2 or -1 depending on fp jitter. While this is a bad form, tests only catch the issue with jax.numpy on either GPU or CPU, and not any other backend. And the computation in findfreqs is prone to this kind of instability, yes. Am not going to change it right now because backwards compat though. --- scipy/signal/_filter_design.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 60a78df8900a..54e8886144ed 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -18,7 +18,7 @@ import scipy._lib.array_api_extra as xpx from scipy._lib._array_api import ( - array_namespace, xp_promote, xp_size, xp_default_dtype + array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax ) from scipy._lib.array_api_compat import numpy as np_compat @@ -149,8 +149,9 @@ def findfreqs(num, den, N, kind='ba'): xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5 ) + fudge = 1e-14 if is_jax(xp) else 0 lfreq = xp.round( - xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 + xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 - fudge ) w = _logspace(lfreq, hfreq, N, xp=xp) From bdc3b1a3663b432c3c9a3cfa1f82f95e2aeb889d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 10:35:59 +0200 Subject: [PATCH 059/251] MAINT: signal: allow sosfreqz -> freqz_sos for CuPy --- scipy/signal/_support_alternative_backends.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scipy/signal/_support_alternative_backends.py b/scipy/signal/_support_alternative_backends.py index 2cc8f6a8c950..7f2e0de94b01 100644 --- a/scipy/signal/_support_alternative_backends.py +++ b/scipy/signal/_support_alternative_backends.py @@ -22,6 +22,9 @@ # some cupyx.scipy.signal functions are incompatible with their scipy counterparts CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi'] +# freqz_sos is a sosfreqz rename, and cupy does not have the new name yet (in v13.x) +CUPY_RENAMES = {'freqz_sos': 'sosfreqz'} + def delegate_xp(delegator, module_name): def inner(func): @functools.wraps(func) @@ -35,10 +38,12 @@ def wrapper(*args, **kwds): # try delegating to a cupyx/jax namesake if is_cupy(xp) and func.__name__ not in CUPY_BLACKLIST: + func_name = CUPY_RENAMES.get(func.__name__, func.__name__) + # https://github.com/cupy/cupy/issues/8336 import importlib cupyx_module = importlib.import_module(f"cupyx.scipy.{module_name}") - cupyx_func = getattr(cupyx_module, func.__name__) + cupyx_func = getattr(cupyx_module, func_name) return cupyx_func(*args, **kwds) elif is_jax(xp) and func.__name__ in JAX_SIGNAL_FUNCS: spx = scipy_namespace_for(xp) From a2c8d1df3efbea0c29ee5df72a24db94dfd2e691 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 12:10:12 +0200 Subject: [PATCH 060/251] ENH: signal: make freqs et al float32/float64 default aware --- scipy/signal/_filter_design.py | 23 +++++++++---- scipy/signal/_polyutils.py | 2 +- scipy/signal/tests/test_filter_design.py | 43 +++++++++++++----------- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 54e8886144ed..000112178a48 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -72,7 +72,12 @@ def _logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None, *, xp): for a in (start, stop, base) ) base = xp.expand_dims(base) - y = xp.linspace(start, stop, num=num, endpoint=endpoint) + try: + result_dt = xp.result_type(start, stop, base) + except ValueError: + # all of start, stop and base are python scalars + result_dt = xp_default_dtype(xp) + y = xp.linspace(start, stop, num=num, endpoint=endpoint, dtype=result_dt) yp = xp.pow(base, y) if dtype is None: @@ -486,6 +491,7 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, b, a = map(xp.asarray, (b, a)) if xp.isdtype(a.dtype, 'integral'): a = xp.astype(a, xp_default_dtype(xp)) + res_dtype = xp.result_type(b, a) b = xpx.atleast_nd(b, ndim=1, xp=xp) a = xpx.atleast_nd(a, ndim=1, xp=xp) @@ -507,7 +513,7 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, # if include_nyquist is true and whole is false, w should # include end point w = xp.linspace(0, lastpoint, N, - endpoint=include_nyquist and not whole) + endpoint=include_nyquist and not whole, dtype=res_dtype) n_fft = N if whole else 2 * (N - 1) if include_nyquist else 2 * N if (xp_size(a) == 1 and (b.ndim == 1 or (b.shape[-1] == 1)) and n_fft >= b.shape[0] @@ -536,7 +542,10 @@ def freqz(b, a=1, worN=512, whole=False, plot=None, fs=2*pi, # Move the first axis of h to the end. h = xp.moveaxis(h, 0, -1) else: - w = xpx.atleast_nd(xp.asarray(worN), ndim=1, xp=xp) + if isinstance(worN, complex): + # backwards compat + worN = worN.real + w = xpx.atleast_nd(xp.asarray(worN, dtype=res_dtype), ndim=1, xp=xp) if xp.isdtype(w.dtype, 'integral'): w = xp.astype(w, xp_default_dtype(xp)) del worN @@ -643,6 +652,8 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): z = xpx.atleast_nd(z, ndim=1, xp=xp) p = xpx.atleast_nd(p, ndim=1, xp=xp) + res_dtype = xp.result_type(z, p) + res_dtype = xp.float64 if res_dtype in (xp.float64, xp.complex128) else xp.float32 fs = _validate_fs(fs, allow_none=False) @@ -653,9 +664,9 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): if worN is None: # For backwards compatibility - w = xp.linspace(0, lastpoint, 512, endpoint=False) + w = xp.linspace(0, lastpoint, 512, endpoint=False, dtype=res_dtype) elif _is_int_type(worN): - w = xp.linspace(0, lastpoint, worN, endpoint=False) + w = xp.linspace(0, lastpoint, worN, endpoint=False, dtype=res_dtype) else: w = xp.asarray(worN) if xp.isdtype(w.dtype, 'integral'): @@ -665,7 +676,7 @@ def freqz_zpk(z, p, k, worN=512, whole=False, fs=2*pi): zm1 = xp.exp(1j * w) func = _pu.npp_polyvalfromroots - h = xp.asarray(k) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp) + h = xp.asarray(k, dtype=res_dtype) * func(zm1, z, xp=xp) / func(zm1, p, xp=xp) w = w*(fs/(2*pi)) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py index 79680a2c933f..6a074dc3a730 100644 --- a/scipy/signal/_polyutils.py +++ b/scipy/signal/_polyutils.py @@ -21,7 +21,7 @@ def polyroots(coef, *, xp): # companion matrix n = coef.shape[0] - a = xp.eye(n - 1, n - 1, k=-1) + a = xp.eye(n - 1, n - 1, k=-1, dtype=coef.dtype) a[:, -1] = -xp.flip(coef[1:]) / coef[0] # non-symmetric eigenvalue problem is not in the spec but is available on e.g. torch diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 0647dd69465c..c3536a5e74ee 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -13,7 +13,7 @@ from pytest import raises as assert_raises from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, - assert_array_almost_equal, xp_size + assert_array_almost_equal, xp_size, xp_default_dtype, ) from numpy import array, spacing, sin, pi, sort @@ -702,7 +702,8 @@ def test_basic(self, xp): w, h = freqz(xp.ones(2), a, worN=0) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == xp.complex128 + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt def test_basic2(self, xp): t = xp.linspace(0, 1, 4, endpoint=False) @@ -819,7 +820,7 @@ def test_fft_wrapping(self, xp): assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=True) assert_array_almost_equal(w, expected_w) - assert_array_almost_equal(h, expected_h) + assert_array_almost_equal(h, expected_h, decimal=4) # half expected_w = xp.linspace(0, xp.pi, ii, endpoint=False) @@ -827,7 +828,7 @@ def test_fft_wrapping(self, xp): assert_array_almost_equal(w, expected_w) w, h = freqz(b, a, worN=ii, whole=False) assert_array_almost_equal(w, expected_w) - assert_array_almost_equal(h, expected_h) + assert_array_almost_equal(h, expected_h, decimal=4) def test_broadcasting1(self, xp): # Test broadcasting with worN an integer or a 1-D array, @@ -983,7 +984,8 @@ def test_nyquist(self, xp): w, h = freqz(xp.ones(2), a, worN=0, include_nyquist=True) assert w.shape == (0,) assert h.shape == (0,) - assert h.dtype == xp.complex128 + hdt = xp.complex128 if xp_default_dtype(xp) == xp.float64 else xp.complex64 + assert h.dtype == hdt w1, h1 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=True) w2, h2 = freqz(xp.asarray([1.0]), worN=8, whole = True, include_nyquist=False) @@ -1016,7 +1018,7 @@ def test_fs_validation(self): freqz([1.0], fs=None) -class Testfreqz_sos: +class TestFreqz_sos: def test_freqz_sos_basic(self, xp): # Compare the results of freqz and freqz_sos for a low order @@ -1066,16 +1068,19 @@ def test_freqz_sos_design(self, xp): N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 60) sos = cheby2(N, 60, Wn, 'stop', output='sos') sos = xp.asarray(sos) # XXX + zero = xp.asarray(0., dtype=xp.float64) w, h = freqz_sos(sos) h = xp.abs(h) w = w / xp.pi - xp_assert_close(20 * xp.log10(h[w <= 0.1]), xp.asarray(0.), atol=3.01, + xp_assert_close(20 * xp.log10(h[w <= 0.1]), + zero, atol=3.01, check_shape=False) - xp_assert_close(20 * xp.log10(h[w >= 0.6]), xp.asarray(0.), atol=3.01, + xp_assert_close(20 * xp.log10(h[w >= 0.6]), + zero, atol=3.01, check_shape=False) xp_assert_close(h[(w >= 0.2) & (w <= 0.5)], - xp.asarray(0.), atol=1e-3, + zero, atol=1e-3, check_shape=False) # <= -60 dB N, Wn = cheb2ord([0.1, 0.6], [0.2, 0.5], 3, 150) @@ -1085,8 +1090,8 @@ def test_freqz_sos_design(self, xp): w, h = freqz_sos(sos) dB = 20*xp.log10(xp.abs(h)) w = w / xp.pi - xp_assert_close(dB[w <= 0.1], xp.asarray(0.0), atol=3.01, check_shape=False) - xp_assert_close(dB[w >= 0.6], xp.asarray(0.0), atol=3.01, check_shape=False) + xp_assert_close(dB[w <= 0.1], zero, atol=3.01, check_shape=False) + xp_assert_close(dB[w >= 0.6], zero, atol=3.01, check_shape=False) assert xp.all(dB[(w >= 0.2) & (w <= 0.5)] < -149.9) # from cheb1ord @@ -1097,9 +1102,9 @@ def test_freqz_sos_design(self, xp): w, h = freqz_sos(sos) h = xp.abs(h) w = w / xp.pi - xp_assert_close(20 * xp.log10(h[w <= 0.2]), xp.asarray(0.0), atol=3.01, + xp_assert_close(20 * xp.log10(h[w <= 0.2]), zero, atol=3.01, check_shape=False) - xp_assert_close(h[w >= 0.3], xp.asarray(0.0), atol=1e-2, + xp_assert_close(h[w >= 0.3], zero, atol=1e-2, check_shape=False) # <= -40 dB N, Wn = cheb1ord(0.2, 0.3, 1, 150) @@ -1109,7 +1114,7 @@ def test_freqz_sos_design(self, xp): w, h = freqz_sos(sos) dB = 20*xp.log10(xp.abs(h)) w /= np.pi - xp_assert_close(dB[w <= 0.2], xp.asarray(0.0), atol=1.01, check_shape=False) + xp_assert_close(dB[w <= 0.2], zero, atol=1.01, check_shape=False) assert xp.all(dB[w >= 0.3] < -149.9) # adapted from ellipord @@ -1120,9 +1125,9 @@ def test_freqz_sos_design(self, xp): w, h = freqz_sos(sos) h = xp.abs(h) w = w / xp.pi - xp_assert_close(20 * xp.log10(h[w >= 0.3]), xp.asarray(0.0), atol=3.01, + xp_assert_close(20 * xp.log10(h[w >= 0.3]), zero, atol=3.01, check_shape=False) - xp_assert_close(h[w <= 0.1], xp.asarray(0.0), atol=1.5e-3, + xp_assert_close(h[w <= 0.1], zero, atol=1.5e-3, check_shape=False) # <= -60 dB (approx) # adapted from buttord @@ -1194,11 +1199,11 @@ def test_freqz_sos_against_mp(self, xp): with mpmath.workdps(80): z_mp, p_mp, k_mp = mpsig.butter_lp(order, Wn) w_mp, h_mp = mpsig.zpkfreqz(z_mp, p_mp, k_mp, N) - w_mp = xp.asarray([float(x) for x in w_mp]) - h_mp = xp.asarray([complex(x) for x in h_mp]) + w_mp = xp.asarray([float(x) for x in w_mp], dtype=xp.float64) + h_mp = xp.asarray([complex(x) for x in h_mp], dtype=xp.complex128) sos = butter(order, Wn, output='sos') - sos = xp.asarray(sos) + sos = xp.asarray(sos, dtype=xp.float64) w, h = freqz_sos(sos, worN=N) xp_assert_close(w, w_mp, rtol=1e-12, atol=1e-14) xp_assert_close(h, h_mp, rtol=1e-12, atol=1e-14) From 3573c8de8f3cbdd56a701ecd533aa2cdbc57642d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 15:25:02 +0200 Subject: [PATCH 061/251] MAINT: signal: correct the `get_window` delegator (#22895) --- scipy/signal/_delegators.py | 4 ++-- scipy/signal/_support_alternative_backends.py | 2 +- scipy/signal/tests/test_windows.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/scipy/signal/_delegators.py b/scipy/signal/_delegators.py index ee1d6d02666a..e0e5c877641b 100644 --- a/scipy/signal/_delegators.py +++ b/scipy/signal/_delegators.py @@ -172,8 +172,8 @@ def kaiser_beta_signature(a): def kaiserord_signature(ripple, width): return np -def get_window_signature(window, Nx, fftbins=True): - return np +def get_window_signature(window, Nx, fftbins=True, *, xp=None, device=None): + return np if xp is None else xp ################################# diff --git a/scipy/signal/_support_alternative_backends.py b/scipy/signal/_support_alternative_backends.py index 2cc8f6a8c950..03ea03a28626 100644 --- a/scipy/signal/_support_alternative_backends.py +++ b/scipy/signal/_support_alternative_backends.py @@ -20,7 +20,7 @@ ] # some cupyx.scipy.signal functions are incompatible with their scipy counterparts -CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi'] +CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi', 'get_window'] def delegate_xp(delegator, module_name): def inner(func): diff --git a/scipy/signal/tests/test_windows.py b/scipy/signal/tests/test_windows.py index 1dad96494b5a..489400e2a77e 100644 --- a/scipy/signal/tests/test_windows.py +++ b/scipy/signal/tests/test_windows.py @@ -10,7 +10,7 @@ from scipy.signal import windows, get_window, resample from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, array_namespace, is_torch, is_jax, is_cupy, - assert_array_almost_equal, SCIPY_DEVICE, + assert_array_almost_equal, SCIPY_DEVICE, is_numpy ) skip_xp_backends = pytest.mark.skip_xp_backends @@ -854,6 +854,15 @@ def test_lanczos(self, xp): xp_assert_close(get_window('lanczos', 6, xp=xp), get_window('sinc', 6, xp=xp)) + def test_xp_default(self, xp): + # no explicit xp= argument, default to numpy + win = get_window('lanczos', 6) + assert isinstance(win, np.ndarray) + + win = get_window('lanczos', 6, xp=xp) + if not is_numpy(xp): + assert not isinstance(win, np.ndarray) + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/2620") def test_windowfunc_basics(xp): From 55cae814e23208354bf16b84be47b5070b4c1c89 Mon Sep 17 00:00:00 2001 From: pratham-mcw Date: Sun, 27 Apr 2025 19:24:27 +0530 Subject: [PATCH 062/251] ENH: signal.convolve2d: Performance Enhancement on WoA (#22623) --- scipy/signal/_firfilter.cc | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scipy/signal/_firfilter.cc b/scipy/signal/_firfilter.cc index 6dc5443df36c..9636961b737a 100644 --- a/scipy/signal/_firfilter.cc +++ b/scipy/signal/_firfilter.cc @@ -29,7 +29,18 @@ typedef void (OneMultAddFunction) (char *, char *, int64_t, char **, int64_t); #define MAKE_ONEMULTADD(fname, type) \ static void fname ## _onemultadd(char *sum, char *term1, int64_t str, char **pvals, int64_t n) { \ type dsum = *(type*)sum; \ - for (int64_t k=0; k < n; k++) { \ + int64_t k = 0; \ + for (; k <= n - 4; k += 4) { \ + type tmp0 = *(type*)(term1 + (k + 0) * str); \ + type tmp1 = *(type*)(term1 + (k + 1) * str); \ + type tmp2 = *(type*)(term1 + (k + 2) * str); \ + type tmp3 = *(type*)(term1 + (k + 3) * str); \ + dsum += tmp0 * *(type*)pvals[k + 0] + \ + tmp1 * *(type*)pvals[k + 1] + \ + tmp2 * *(type*)pvals[k + 2] + \ + tmp3 * *(type*)pvals[k + 3]; \ + } \ + for (; k < n; k++) { \ type tmp = *(type*)(term1 + k * str); \ dsum += tmp * *(type*)pvals[k]; \ } \ From 63f69604a63e1ed80d5c41f8540e1b5ef6ddb9d3 Mon Sep 17 00:00:00 2001 From: Nick ODell Date: Mon, 28 Apr 2025 00:06:10 -0600 Subject: [PATCH 063/251] DOC: Document allowed NumPy / Python versions (#22891) [docs only] --- doc/source/dev/toolchain.rst | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/source/dev/toolchain.rst b/doc/source/dev/toolchain.rst index 7d4e7ce05cf9..01b08f88105c 100644 --- a/doc/source/dev/toolchain.rst +++ b/doc/source/dev/toolchain.rst @@ -59,6 +59,7 @@ mid-year release of SciPy. ================ ======================================================================= Date Pythons supported ================ ======================================================================= + 2025 Py3.11+ 2024 Py3.10+ 2023 Py3.9+ 2022 Py3.8+ @@ -80,13 +81,16 @@ needs to be written using what is common in all of those 4 `NumPy releases`_. .. dropdown:: Python and NumPy version support per SciPy version - The table shows the NumPy versions suitable for each major Python version. - This table does not distinguish SciPy patch versions (e.g. when a new Python - version is released, SciPy will generally issue a compatible patch version). + The table shows the NumPy and Python versions suitable for each minor SciPy + version. Note that not all patch versions for a particular minor version of + SciPy support all listed versions of Python. Only the most recent patch version + within each minor version is guaranteed to support all listed Python versions. ================= ======================== ======================= SciPy version Python versions NumPy versions ================= ======================== ======================= + 1.15 >=3.10, <3.14 >=1.23.5, <2.5.0 + 1.14 >=3.10, <3.14 >=1.23.5, <2.3.0 1.13 >=3.9, <3.13 >=1.22.4, <2.3.0 1.12 >=3.9, <3.13 >=1.22.4, <2.0.0 1.11 >=3.9, <3.13 >=1.21.6, <1.27.0 From cca0bdc534fcd5ac0c1bb2cf292ce23e0d69a351 Mon Sep 17 00:00:00 2001 From: Albert Steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 28 Apr 2025 05:39:05 -0400 Subject: [PATCH 064/251] MAINT: special: Add `xsf` as a submodule of SciPy (#22854) Closes #21649 Co-authored-by: Lucas Colley --- .gitmodules | 5 + LICENSES_bundled.txt | 122 +- meson.build | 9 + scipy/special/__init__.py | 10 - scipy/special/_gufuncs.cpp | 2 +- scipy/special/_special_ufuncs.cpp | 64 +- scipy/special/dd_real_wrappers.cpp | 2 +- scipy/special/meson.build | 82 +- scipy/special/sf_error.h | 2 +- scipy/special/stirling2.h | 4 +- scipy/special/xsf/.clang-format | 12 - scipy/special/xsf/airy.h | 567 -- scipy/special/xsf/alg.h | 11 - scipy/special/xsf/amos.h | 35 - scipy/special/xsf/amos/amos.h | 6245 ----------------- scipy/special/xsf/bessel.h | 1202 ---- scipy/special/xsf/beta.h | 15 - scipy/special/xsf/binom.h | 89 - scipy/special/xsf/cdflib.h | 100 - scipy/special/xsf/cephes/airy.h | 307 - scipy/special/xsf/cephes/bdtr.h | 262 - scipy/special/xsf/cephes/besselpoly.h | 51 - scipy/special/xsf/cephes/beta.h | 257 - scipy/special/xsf/cephes/cbrt.h | 131 - scipy/special/xsf/cephes/chbevl.h | 85 - scipy/special/xsf/cephes/chdtr.h | 194 - scipy/special/xsf/cephes/const.h | 87 - scipy/special/xsf/cephes/dd_real.h | 576 -- scipy/special/xsf/cephes/ellie.h | 293 - scipy/special/xsf/cephes/ellik.h | 251 - scipy/special/xsf/cephes/ellpe.h | 107 - scipy/special/xsf/cephes/ellpj.h | 162 - scipy/special/xsf/cephes/ellpk.h | 117 - scipy/special/xsf/cephes/erfinv.h | 76 - scipy/special/xsf/cephes/exp10.h | 130 - scipy/special/xsf/cephes/exp2.h | 122 - scipy/special/xsf/cephes/expn.h | 260 - scipy/special/xsf/cephes/fdtr.h | 223 - scipy/special/xsf/cephes/fresnl.h | 191 - scipy/special/xsf/cephes/gamma.h | 398 -- scipy/special/xsf/cephes/gdtr.h | 140 - scipy/special/xsf/cephes/hyp2f1.h | 596 -- scipy/special/xsf/cephes/hyperg.h | 361 - scipy/special/xsf/cephes/i0.h | 149 - scipy/special/xsf/cephes/i1.h | 158 - scipy/special/xsf/cephes/igam.h | 429 -- scipy/special/xsf/cephes/igam_asymp_coeff.h | 195 - scipy/special/xsf/cephes/igami.h | 313 - scipy/special/xsf/cephes/incbet.h | 386 - scipy/special/xsf/cephes/incbi.h | 293 - scipy/special/xsf/cephes/j0.h | 225 - scipy/special/xsf/cephes/j1.h | 198 - scipy/special/xsf/cephes/jv.h | 715 -- scipy/special/xsf/cephes/k0.h | 164 - scipy/special/xsf/cephes/k1.h | 163 - scipy/special/xsf/cephes/kn.h | 243 - scipy/special/xsf/cephes/kolmogorov.h | 1041 --- scipy/special/xsf/cephes/lanczos.h | 112 - scipy/special/xsf/cephes/nbdtr.h | 218 - scipy/special/xsf/cephes/ndtr.h | 275 - scipy/special/xsf/cephes/ndtri.h | 160 - scipy/special/xsf/cephes/owens_t.h | 352 - scipy/special/xsf/cephes/pdtr.h | 183 - scipy/special/xsf/cephes/poch.h | 85 - scipy/special/xsf/cephes/polevl.h | 167 - scipy/special/xsf/cephes/psi.h | 194 - scipy/special/xsf/cephes/rgamma.h | 111 - scipy/special/xsf/cephes/round.h | 74 - scipy/special/xsf/cephes/scipy_iv.h | 811 --- scipy/special/xsf/cephes/shichi.h | 248 - scipy/special/xsf/cephes/sici.h | 224 - scipy/special/xsf/cephes/sindg.h | 221 - scipy/special/xsf/cephes/spence.h | 127 - scipy/special/xsf/cephes/struve.h | 382 - scipy/special/xsf/cephes/tandg.h | 139 - scipy/special/xsf/cephes/trig.h | 58 - scipy/special/xsf/cephes/tukey.h | 80 - scipy/special/xsf/cephes/unity.h | 186 - scipy/special/xsf/cephes/yn.h | 118 - scipy/special/xsf/cephes/yv.h | 55 - scipy/special/xsf/cephes/zeta.h | 172 - scipy/special/xsf/cephes/zetac.h | 280 - scipy/special/xsf/config.h | 304 - scipy/special/xsf/digamma.h | 205 - scipy/special/xsf/dual.h | 670 -- scipy/special/xsf/ellip.h | 49 - scipy/special/xsf/erf.h | 94 - scipy/special/xsf/error.h | 57 - scipy/special/xsf/evalpoly.h | 47 - scipy/special/xsf/exp.h | 56 - scipy/special/xsf/expint.h | 266 - scipy/special/xsf/faddeeva.h | 1758 ----- scipy/special/xsf/fresnel.h | 424 -- scipy/special/xsf/gamma.h | 58 - scipy/special/xsf/hyp2f1.h | 694 -- scipy/special/xsf/iv_ratio.h | 173 - scipy/special/xsf/kelvin.h | 424 -- scipy/special/xsf/lambertw.h | 150 - scipy/special/xsf/legendre.h | 1064 --- scipy/special/xsf/log.h | 119 - scipy/special/xsf/log_exp.h | 87 - scipy/special/xsf/loggamma.h | 163 - scipy/special/xsf/mathieu.h | 233 - scipy/special/xsf/numbers.h | 18 - scipy/special/xsf/numpy.h | 1081 --- scipy/special/xsf/par_cyl.h | 667 -- scipy/special/xsf/recur.h | 90 - scipy/special/xsf/sici.h | 200 - scipy/special/xsf/specfun.h | 107 - scipy/special/xsf/specfun/specfun.h | 5894 ---------------- scipy/special/xsf/sph_bessel.h | 382 - scipy/special/xsf/sph_harm.h | 59 - scipy/special/xsf/sphd_wave.h | 407 -- scipy/special/xsf/stats.h | 182 - scipy/special/xsf/struve.h | 228 - .../special/xsf/third_party/kokkos/mdspan.hpp | 5674 --------------- scipy/special/xsf/tools.h | 427 -- scipy/special/xsf/trig.h | 164 - scipy/special/xsf/wright_bessel.h | 843 --- scipy/special/xsf/zeta.h | 406 -- scipy/special/xsf/zlog1.h | 35 - scipy/special/xsf_special.h | 4 +- scipy/special/xsf_wrappers.cpp | 111 +- subprojects/xsf | 1 + 124 files changed, 122 insertions(+), 47612 deletions(-) delete mode 100644 scipy/special/xsf/.clang-format delete mode 100644 scipy/special/xsf/airy.h delete mode 100644 scipy/special/xsf/alg.h delete mode 100644 scipy/special/xsf/amos.h delete mode 100644 scipy/special/xsf/amos/amos.h delete mode 100644 scipy/special/xsf/bessel.h delete mode 100644 scipy/special/xsf/beta.h delete mode 100644 scipy/special/xsf/binom.h delete mode 100644 scipy/special/xsf/cdflib.h delete mode 100644 scipy/special/xsf/cephes/airy.h delete mode 100644 scipy/special/xsf/cephes/bdtr.h delete mode 100644 scipy/special/xsf/cephes/besselpoly.h delete mode 100644 scipy/special/xsf/cephes/beta.h delete mode 100644 scipy/special/xsf/cephes/cbrt.h delete mode 100644 scipy/special/xsf/cephes/chbevl.h delete mode 100644 scipy/special/xsf/cephes/chdtr.h delete mode 100644 scipy/special/xsf/cephes/const.h delete mode 100644 scipy/special/xsf/cephes/dd_real.h delete mode 100644 scipy/special/xsf/cephes/ellie.h delete mode 100644 scipy/special/xsf/cephes/ellik.h delete mode 100644 scipy/special/xsf/cephes/ellpe.h delete mode 100644 scipy/special/xsf/cephes/ellpj.h delete mode 100644 scipy/special/xsf/cephes/ellpk.h delete mode 100644 scipy/special/xsf/cephes/erfinv.h delete mode 100644 scipy/special/xsf/cephes/exp10.h delete mode 100644 scipy/special/xsf/cephes/exp2.h delete mode 100644 scipy/special/xsf/cephes/expn.h delete mode 100644 scipy/special/xsf/cephes/fdtr.h delete mode 100644 scipy/special/xsf/cephes/fresnl.h delete mode 100644 scipy/special/xsf/cephes/gamma.h delete mode 100644 scipy/special/xsf/cephes/gdtr.h delete mode 100644 scipy/special/xsf/cephes/hyp2f1.h delete mode 100644 scipy/special/xsf/cephes/hyperg.h delete mode 100644 scipy/special/xsf/cephes/i0.h delete mode 100644 scipy/special/xsf/cephes/i1.h delete mode 100644 scipy/special/xsf/cephes/igam.h delete mode 100644 scipy/special/xsf/cephes/igam_asymp_coeff.h delete mode 100644 scipy/special/xsf/cephes/igami.h delete mode 100644 scipy/special/xsf/cephes/incbet.h delete mode 100644 scipy/special/xsf/cephes/incbi.h delete mode 100644 scipy/special/xsf/cephes/j0.h delete mode 100644 scipy/special/xsf/cephes/j1.h delete mode 100644 scipy/special/xsf/cephes/jv.h delete mode 100644 scipy/special/xsf/cephes/k0.h delete mode 100644 scipy/special/xsf/cephes/k1.h delete mode 100644 scipy/special/xsf/cephes/kn.h delete mode 100644 scipy/special/xsf/cephes/kolmogorov.h delete mode 100644 scipy/special/xsf/cephes/lanczos.h delete mode 100644 scipy/special/xsf/cephes/nbdtr.h delete mode 100644 scipy/special/xsf/cephes/ndtr.h delete mode 100644 scipy/special/xsf/cephes/ndtri.h delete mode 100644 scipy/special/xsf/cephes/owens_t.h delete mode 100644 scipy/special/xsf/cephes/pdtr.h delete mode 100644 scipy/special/xsf/cephes/poch.h delete mode 100644 scipy/special/xsf/cephes/polevl.h delete mode 100644 scipy/special/xsf/cephes/psi.h delete mode 100644 scipy/special/xsf/cephes/rgamma.h delete mode 100644 scipy/special/xsf/cephes/round.h delete mode 100644 scipy/special/xsf/cephes/scipy_iv.h delete mode 100644 scipy/special/xsf/cephes/shichi.h delete mode 100644 scipy/special/xsf/cephes/sici.h delete mode 100644 scipy/special/xsf/cephes/sindg.h delete mode 100644 scipy/special/xsf/cephes/spence.h delete mode 100644 scipy/special/xsf/cephes/struve.h delete mode 100644 scipy/special/xsf/cephes/tandg.h delete mode 100644 scipy/special/xsf/cephes/trig.h delete mode 100644 scipy/special/xsf/cephes/tukey.h delete mode 100644 scipy/special/xsf/cephes/unity.h delete mode 100644 scipy/special/xsf/cephes/yn.h delete mode 100644 scipy/special/xsf/cephes/yv.h delete mode 100644 scipy/special/xsf/cephes/zeta.h delete mode 100644 scipy/special/xsf/cephes/zetac.h delete mode 100644 scipy/special/xsf/config.h delete mode 100644 scipy/special/xsf/digamma.h delete mode 100644 scipy/special/xsf/dual.h delete mode 100644 scipy/special/xsf/ellip.h delete mode 100644 scipy/special/xsf/erf.h delete mode 100644 scipy/special/xsf/error.h delete mode 100644 scipy/special/xsf/evalpoly.h delete mode 100644 scipy/special/xsf/exp.h delete mode 100644 scipy/special/xsf/expint.h delete mode 100644 scipy/special/xsf/faddeeva.h delete mode 100644 scipy/special/xsf/fresnel.h delete mode 100644 scipy/special/xsf/gamma.h delete mode 100644 scipy/special/xsf/hyp2f1.h delete mode 100644 scipy/special/xsf/iv_ratio.h delete mode 100644 scipy/special/xsf/kelvin.h delete mode 100644 scipy/special/xsf/lambertw.h delete mode 100644 scipy/special/xsf/legendre.h delete mode 100644 scipy/special/xsf/log.h delete mode 100644 scipy/special/xsf/log_exp.h delete mode 100644 scipy/special/xsf/loggamma.h delete mode 100644 scipy/special/xsf/mathieu.h delete mode 100644 scipy/special/xsf/numbers.h delete mode 100644 scipy/special/xsf/numpy.h delete mode 100644 scipy/special/xsf/par_cyl.h delete mode 100644 scipy/special/xsf/recur.h delete mode 100644 scipy/special/xsf/sici.h delete mode 100644 scipy/special/xsf/specfun.h delete mode 100644 scipy/special/xsf/specfun/specfun.h delete mode 100644 scipy/special/xsf/sph_bessel.h delete mode 100644 scipy/special/xsf/sph_harm.h delete mode 100644 scipy/special/xsf/sphd_wave.h delete mode 100644 scipy/special/xsf/stats.h delete mode 100644 scipy/special/xsf/struve.h delete mode 100644 scipy/special/xsf/third_party/kokkos/mdspan.hpp delete mode 100644 scipy/special/xsf/tools.h delete mode 100644 scipy/special/xsf/trig.h delete mode 100644 scipy/special/xsf/wright_bessel.h delete mode 100644 scipy/special/xsf/zeta.h delete mode 100644 scipy/special/xsf/zlog1.h create mode 160000 subprojects/xsf diff --git a/.gitmodules b/.gitmodules index a9a6025be95b..3bc6eb422cc3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,7 +28,12 @@ path = subprojects/highs url = https://github.com/scipy/HiGHs shallow = true + [submodule "subprojects/boost_math/math"] path = subprojects/boost_math/math url = https://github.com/boostorg/math.git shallow = true + +[submodule "subprojects/xsf"] + path = subprojects/xsf + url = https://github.com/scipy/xsf.git diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 750a532a0037..8c12a9590cc2 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -91,118 +91,11 @@ Files: scipy/spatial/qhull/* License: Qhull license (BSD-like) For details, see scipy/spatial/qhull/COPYING.txt -Name: Cephes -Files: scipy/special/cephes/* -License: 3-clause BSD - Distributed under 3-clause BSD license with permission from the author, - see https://lists.debian.org/debian-legal/2004/12/msg00295.html - - Cephes Math Library Release 2.8: June, 2000 - Copyright 1984, 1995, 2000 by Stephen L. Moshier - - This software is derived from the Cephes Math Library and is - incorporated herein by permission of the author. - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Name: Faddeeva -Files: scipy/special/Faddeeva.* -License: MIT - Copyright (c) 2012 Massachusetts Institute of Technology - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - 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 AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Name: qd -Files: scipy/special/cephes/dd_*.[ch] -License: modified BSD license ("BSD-LBNL-License.doc") - This work was supported by the Director, Office of Science, Division - of Mathematical, Information, and Computational Sciences of the - U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and - DE-AC02-05CH11231. - - Copyright (c) 2003-2009, The Regents of the University of California, - through Lawrence Berkeley National Laboratory (subject to receipt of - any required approvals from U.S. Dept. of Energy) All rights reserved. - - 1. Redistribution and use in source and binary forms, with or - without modification, are permitted provided that the following - conditions are met: - - (1) Redistributions of source code must retain the copyright - notice, this list of conditions and the following disclaimer. - - (2) Redistributions in binary form must reproduce the copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - (3) Neither the name of the University of California, Lawrence - Berkeley National Laboratory, U.S. Dept. of Energy nor the names - of its contributors may be used to endorse or promote products - derived from this software without specific prior written - permission. - - 2. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - 3. You are under no obligation whatsoever to provide any bug fixes, - patches, or upgrades to the features, functionality or performance of - the source code ("Enhancements") to anyone; however, if you choose to - make your Enhancements available either publicly, or directly to - Lawrence Berkeley National Laboratory, without imposing a separate - written license agreement for such Enhancements, then you hereby grant - the following license: a non-exclusive, royalty-free perpetual license - to install, use, modify, prepare derivative works, incorporate into - other computer software, distribute, and sublicense such enhancements - or derivative works thereof, in binary and source code form. +Name: xsf +Files: scipy/subprojects/xsf/* +License: 3-Clause BSD + For details, see scipy/subprojects/xsf/LICENSE and + scipy/subprojects/xsf/LICENSES_bundled.txt Name: pypocketfft Files: scipy/fft/_pocketfft/[pocketfft.h, pypocketfft.cxx] @@ -287,11 +180,6 @@ Files: scipy/_build_utils/tempita/* License: MIT For details, see scipy/_build_utils/tempita/LICENCE.txt -Name: mdspan -Files: scipy/special/special/third_party/kokkos/mdspan.hpp -License: Apache License v2.0 with LLVM Exceptions - For details, see scipy/special/special/third_party/kokkos/mdspan.hpp - Name: Chebfun Files: scipy/interpolate/[_aaa.py, tests/test_aaa.py] License 3-Clause BSD diff --git a/meson.build b/meson.build index a23eb64fbee5..7c9fd45eab69 100644 --- a/meson.build +++ b/meson.build @@ -153,10 +153,19 @@ if use_pythran xsimd_dep = dependency('xsimd', required: false) endif +fs = import('fs') +if not fs.exists('subprojects/xsf/README.md') + error('Missing the `xsf` submodule! Run `git submodule update --init` to fix this.') +endif + +xsf = subproject('xsf') +xsf_dep = xsf.get_variable('xsf_dep') + boost_math_dep = dependency( 'boost', version : '1.88.0', fallback : ['boost_math', 'boost_math_dep'], ) + subdir('scipy') diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index c3922e797d06..0c2fe8809279 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -842,13 +842,3 @@ from scipy._lib._testutils import PytestTester test = PytestTester(__name__) del PytestTester - - -def _get_include(): - """This function is for development purposes only. - - This function could disappear or its behavior could change at any time. - """ - import os - return os.path.dirname(__file__) - diff --git a/scipy/special/_gufuncs.cpp b/scipy/special/_gufuncs.cpp index 11778ff5bb99..7eca3717b5bd 100644 --- a/scipy/special/_gufuncs.cpp +++ b/scipy/special/_gufuncs.cpp @@ -1,4 +1,4 @@ -#include "xsf/numpy.h" +#include #include "sf_error.h" #include "xsf_special.h" diff --git a/scipy/special/_special_ufuncs.cpp b/scipy/special/_special_ufuncs.cpp index 61abd14a0517..f13f9e5c45e8 100644 --- a/scipy/special/_special_ufuncs.cpp +++ b/scipy/special/_special_ufuncs.cpp @@ -1,40 +1,40 @@ -#include "xsf/numpy.h" +#include #include #include #include "sf_error.h" -#include "xsf/airy.h" -#include "xsf/alg.h" -#include "xsf/amos.h" -#include "xsf/bessel.h" -#include "xsf/beta.h" -#include "xsf/binom.h" -#include "xsf/digamma.h" -#include "xsf/ellip.h" -#include "xsf/erf.h" -#include "xsf/exp.h" -#include "xsf/expint.h" -#include "xsf/fresnel.h" -#include "xsf/gamma.h" -#include "xsf/hyp2f1.h" -#include "xsf/iv_ratio.h" -#include "xsf/kelvin.h" -#include "xsf/lambertw.h" -#include "xsf/legendre.h" -#include "xsf/log.h" -#include "xsf/log_exp.h" -#include "xsf/mathieu.h" -#include "xsf/par_cyl.h" -#include "xsf/specfun.h" -#include "xsf/sph_bessel.h" -#include "xsf/sph_harm.h" -#include "xsf/sphd_wave.h" -#include "xsf/stats.h" -#include "xsf/struve.h" -#include "xsf/trig.h" -#include "xsf/wright_bessel.h" -#include "xsf/zeta.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "xsf_special.h" // This is the extension module for the NumPy ufuncs in SciPy's special module. To create such a ufunc, call diff --git a/scipy/special/dd_real_wrappers.cpp b/scipy/special/dd_real_wrappers.cpp index ea07431af9ff..8d281303d6da 100644 --- a/scipy/special/dd_real_wrappers.cpp +++ b/scipy/special/dd_real_wrappers.cpp @@ -5,7 +5,7 @@ */ #include "dd_real_wrappers.h" -#include "xsf/cephes/dd_real.h" +#include using xsf::cephes::detail::double_double; diff --git a/scipy/special/meson.build b/scipy/special/meson.build index 40418816f0bb..eb840af3669f 100644 --- a/scipy/special/meson.build +++ b/scipy/special/meson.build @@ -54,7 +54,7 @@ endif py3.extension_module('_special_ufuncs', ['_special_ufuncs.cpp', '_special_ufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['..', '../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, cpp_args: ufuncs_cpp_args, install: true, @@ -64,7 +64,7 @@ py3.extension_module('_special_ufuncs', py3.extension_module('_gufuncs', ['_gufuncs.cpp', '_gufuncs_docs.cpp', 'sf_error.cc'], include_directories: ['..', '../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, cpp_args: ufuncs_cpp_args, install: true, @@ -73,7 +73,7 @@ py3.extension_module('_gufuncs', py3.extension_module('_specfun', [cython_gen_cpp.process('_specfun.pyx')], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, install: true, subdir: 'scipy/special' @@ -123,6 +123,7 @@ py3.extension_module('_ufuncs', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], dependencies: [ + xsf_dep, lapack_dep, npymath_lib, np_dep, @@ -164,7 +165,7 @@ py3.extension_module('_ufuncs_cxx', cpp_args: ufuncs_cxx_cpp_args, include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [boost_math_dep, np_dep, ellint_dep], + dependencies: [boost_math_dep, xsf_dep, np_dep, ellint_dep], install: true, subdir: 'scipy/special', ) @@ -175,7 +176,7 @@ py3.extension_module('_ellip_harm_2', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [lapack_dep, np_dep], + dependencies: [xsf_dep, lapack_dep, np_dep], install: true, subdir: 'scipy/special', ) @@ -192,7 +193,7 @@ py3.extension_module('cython_special', cpp_args: ['-DSP_SPECFUN_ERROR'], include_directories: ['..', '../_lib', '../_build_utils/src'], link_args: version_link_args, - dependencies: [np_dep, npymath_lib, lapack_dep], + dependencies: [xsf_dep, np_dep, npymath_lib, lapack_dep], link_with: cdflib_lib, install: true, subdir: 'scipy/special', @@ -208,7 +209,7 @@ py3.extension_module('_comb', py3.extension_module('_test_internal', [cython_gen.process('_test_internal.pyx'), 'dd_real_wrappers.cpp'], include_directories: ['../_lib', '../_build_utils/src'], - dependencies: [np_dep], + dependencies: [xsf_dep, np_dep], link_args: version_link_args, install: true, subdir: 'scipy/special', @@ -258,73 +259,6 @@ foreach npz_file: npz_files ) endforeach -# Headers for special functions in `xsf` library are included in -# both build and install dirs for development purposes. Not public! - -xsf_sources = [ - 'xsf/config.h', - 'xsf/error.h', - 'xsf/evalpoly.h', - 'xsf/lambertw.h', - 'xsf/binom.h', - 'xsf/trig.h', - 'xsf/zlog1.h', - 'xsf/loggamma.h', - 'xsf/digamma.h', - 'xsf/wright_bessel.h', - 'xsf/hyp2f1.h', - 'xsf/tools.h', - 'xsf/iv_ratio.h', - 'xsf/cdflib.h', - 'xsf/sici.h', - 'xsf/expint.h' -] - -xsf_cephes_sources = [ - 'xsf/cephes/const.h', - 'xsf/cephes/polevl.h', - 'xsf/cephes/beta.h', - 'xsf/cephes/gamma.h', - 'xsf/cephes/trig.h', - 'xsf/cephes/zeta.h', - 'xsf/cephes/psi.h', - 'xsf/cephes/lanczos.h', - 'xsf/cephes/rgamma.h', - 'xsf/cephes/chbevl.h', - 'xsf/cephes/poch.h', - 'xsf/cephes/hyp2f1.h', - 'xsf/cephes/besselpoly.h', - 'xsf/cephes/i0.h', - 'xsf/cephes/i1.h', - 'xsf/cephes/scipy_iv.h', - 'xsf/cephes/j0.h', - 'xsf/cephes/j1.h', - 'xsf/cephes/jv.h', - 'xsf/cephes/cbrt.h', - 'xsf/cephes/airy.h', - 'xsf/cephes/k0.h', - 'xsf/cephes/k1.h', - 'xsf/cephes/kn.h', - 'xsf/cephes/ndtr.h', - 'xsf/cephes/igam.h', - 'xsf/cephes/unity.h', - 'xsf/cephes/chdtr.h', - 'xsf/cephes/igami.h', - 'xsf/cephes/hyperg.h', - 'xsf/cephes/expn.h', - 'xsf/cephes/ellie.h', - 'xsf/cephes/ellik.h', - 'xsf/cephes/ellpe.h', - 'xsf/cephes/ellpk.h', - 'xsf/cephes/igam_asymp_coeff.h', - 'xsf/cephes/sici.h', - 'xsf/cephes/shichi.h', - 'xsf/cephes/sindg.h', - 'xsf/cephes/tandg.h' -] - -py3.install_sources(xsf_sources, subdir: 'scipy/special/xsf') -py3.install_sources(xsf_cephes_sources, subdir: 'scipy/special/xsf/cephes') python_sources = [ '__init__.py', diff --git a/scipy/special/sf_error.h b/scipy/special/sf_error.h index 0bc26b6db3d0..333e08983853 100644 --- a/scipy/special/sf_error.h +++ b/scipy/special/sf_error.h @@ -1,6 +1,6 @@ #pragma once -#include "xsf/error.h" +#include #ifdef __cplusplus extern "C" { diff --git a/scipy/special/stirling2.h b/scipy/special/stirling2.h index 20307a94d8ec..1f8d6e213a64 100644 --- a/scipy/special/stirling2.h +++ b/scipy/special/stirling2.h @@ -6,8 +6,8 @@ #include #include -#include "xsf/binom.h" -#include "xsf/lambertw.h" +#include +#include #include "sf_error.h" diff --git a/scipy/special/xsf/.clang-format b/scipy/special/xsf/.clang-format deleted file mode 100644 index 96eefe452059..000000000000 --- a/scipy/special/xsf/.clang-format +++ /dev/null @@ -1,12 +0,0 @@ -BasedOnStyle: LLVM -Standard: Cpp11 -UseTab: Never -IndentWidth: 4 -BreakBeforeBraces: Attach -Cpp11BracedListStyle: true -NamespaceIndentation: Inner -AlwaysBreakTemplateDeclarations: true -SpaceAfterCStyleCast: true -ColumnLimit: 120 -InsertNewlineAtEOF: true -AlignAfterOpenBracket: BlockIndent diff --git a/scipy/special/xsf/airy.h b/scipy/special/xsf/airy.h deleted file mode 100644 index 7bdec5340212..000000000000 --- a/scipy/special/xsf/airy.h +++ /dev/null @@ -1,567 +0,0 @@ -#pragma once - -#include "amos.h" -#include "cephes/airy.h" -#include "config.h" -#include "error.h" - -inline int cephes_airy(float xf, float *aif, float *aipf, float *bif, float *bipf) { - double ai; - double aip; - double bi; - double bip; - int res = xsf::cephes::airy(xf, &ai, &aip, &bi, &bip); - - *aif = ai; - *aipf = aip; - *bif = bi; - *bipf = bip; - return res; -} - -namespace xsf { -namespace detail { - - template - void itairy(T x, T &apt, T &bpt, T &ant, T &bnt) { - - // ====================================================== - // Purpose: Compute the integrals of Airy fnctions with - // respect to t from 0 and x ( x ≥ 0 ) - // Input : x --- Upper limit of the integral - // Output : APT --- Integration of Ai(t) from 0 and x - // BPT --- Integration of Bi(t) from 0 and x - // ANT --- Integration of Ai(-t) from 0 and x - // BNT --- Integration of Bi(-t) from 0 and x - // ====================================================== - - int k, l; - T fx, gx, r, su1, su2, su3, su4, su5, su6, xp6, xe, xr1, xr2; - - const T pi = 3.141592653589793; - const T c1 = 0.355028053887817; - const T c2 = 0.258819403792807; - const T sr3 = 1.732050807568877; - const T q0 = 1.0 / 3.0; - const T q1 = 2.0 / 3.0; - const T q2 = 1.4142135623730951; - const T eps = 1e-5; - static const T a[16] = {0.569444444444444, 0.891300154320988, 0.226624344493027e+01, - 0.798950124766861e+01, 0.360688546785343e+02, 0.198670292131169e+03, - 0.129223456582211e+04, 0.969483869669600e+04, 0.824184704952483e+05, - 0.783031092490225e+06, 0.822210493622814e+07, 0.945557399360556e+08, - 0.118195595640730e+10, 0.159564653040121e+11, 0.231369166433050e+12, - 0.358622522796969e+13}; - - if (x == 0.0) { - apt = 0.0; - bpt = 0.0; - ant = 0.0; - bnt = 0.0; - } else { - if (std::abs(x) <= 9.25) { - for (l = 0; l < 2; l++) { - x = x * std::pow(-1, l); - fx = x; - r = x; - for (k = 1; k < 41; k++) { - r = r * (3.0 * k - 2.0) / (3.0 * k + 1.0) * x / (3.0 * k) * x / (3.0 * k - 1.0) * x; - fx += r; - if (std::abs(r) < std::abs(fx) * eps) { - break; - } - } - gx = 0.5 * x * x; - r = gx; - for (k = 1; k < 41; k++) { - r = r * (3.0 * k - 1.0) / (3.0 * k + 2.0) * x / (3.0 * k) * x / (3.0 * k + 1.0) * x; - gx += r; - if (std::abs(r) < std::abs(gx) * eps) { - break; - } - } - ant = c1 * fx - c2 * gx; - bnt = sr3 * (c1 * fx + c2 * gx); - if (l == 0) { - apt = ant; - bpt = bnt; - } else { - ant = -ant; - bnt = -bnt; - x = -x; - } - } - } else { - xe = x * std::sqrt(x) / 1.5; - xp6 = 1.0 / std::sqrt(6.0 * pi * xe); - su1 = 1.0; - r = 1.0; - xr1 = 1.0 / xe; - for (k = 1; k < 17; k++) { - r = -r * xr1; - su1 += a[k - 1] * r; - } - su2 = 1.0; - r = 1.0; - for (k = 1; k < 17; k++) { - r = r * xr1; - su2 += a[k - 1] * r; - } - apt = q0 - std::exp(-xe) * xp6 * su1; - bpt = 2.0 * std::exp(xe) * xp6 * su2; - su3 = 1.0; - r = 1.0; - xr2 = 1.0 / (xe * xe); - for (k = 1; k < 9; k++) { - r = -r * xr2; - su3 += a[2 * k - 1] * r; - } - su4 = a[0] * xr1; - r = xr1; - for (k = 1; k < 8; k++) { - r = -r * xr2; - su4 += a[2 * k] * r; - } - su5 = su3 + su4; - su6 = su3 - su4; - ant = q1 - q2 * xp6 * (su5 * std::cos(xe) - su6 * std::sin(xe)); - bnt = q2 * xp6 * (su5 * std::sin(xe) + su6 * std::cos(xe)); - } - } - } - -} // namespace detail - -inline void airyb(double x, double *ai, double *bi, double *ad, double *bd) { - - // ======================================================= - // Purpose: Compute Airy functions and their derivatives - // Input: x --- Argument of Airy function - // Output: AI --- Ai(x) - // BI --- Bi(x) - // AD --- Ai'(x) - // BD --- Bi'(x) - // ======================================================= - - int k, km, km2, kmax; - double ck[52], dk[52]; - double xa, xq, xm, fx, r, gx, df, dg, sai, sad, sbi, sbd, xp1, xr2; - double xf, rp, xar, xe, xr1, xcs, xss, ssa, sda, ssb, sdb; - - const double eps = 1.0e-15; - const double pi = 3.141592653589793; - const double c1 = 0.355028053887817; - const double c2 = 0.258819403792807; - const double sr3 = 1.732050807568877; - - km2 = 0; - xa = fabs(x); - xq = sqrt(xa); - xm = 8.0; - if (x > 0.0) - xm = 5.0; - - if (x == 0.0) { - *ai = c1; - *bi = sr3 * c1; - *ad = -c2; - *bd = sr3 * c2; - return; - } - - if (xa <= xm) { - fx = 1.0; - r = 1.0; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k - 1.0) * x; - fx += r; - if (fabs(r) < fabs(fx) * eps) - break; - } - - gx = x; - r = x; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k + 1.0) * x; - gx += r; - if (fabs(r) < fabs(gx) * eps) - break; - } - - *ai = c1 * fx - c2 * gx; - *bi = sr3 * (c1 * fx + c2 * gx); - - df = 0.5 * x * x; - r = df; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k + 2.0) * x; - df += r; - if (fabs(r) < fabs(df) * eps) - break; - } - - dg = 1.0; - r = 1.0; - for (k = 1; k <= 40; k++) { - r = r * x / (3.0 * k) * x / (3.0 * k - 2.0) * x; - dg += r; - if (fabs(r) < fabs(dg) * eps) - break; - } - - *ad = c1 * df - c2 * dg; - *bd = sr3 * (c1 * df + c2 * dg); - } else { - km = (int) (24.5 - xa); - if (xa < 6.0) - km = 14; - if (xa > 15.0) - km = 10; - - if (x > 0.0) { - kmax = km; - } else { - // Choose cutoffs so that the remainder term in asymptotic - // expansion is epsilon size. The X<0 branch needs to be fast - // in order to make AIRYZO efficient - if (xa > 70.0) - km = 3; - if (xa > 500.0) - km = 2; - if (xa > 1000.0) - km = 1; - - km2 = km; - if (xa > 150.0) - km2 = 1; - if (xa > 3000.0) - km2 = 0; - kmax = 2 * km + 1; - } - xe = xa * xq / 1.5; - xr1 = 1.0 / xe; - xar = 1.0 / xq; - xf = sqrt(xar); - rp = 0.5641895835477563; - r = 1.0; - for (k = 1; k <= kmax; k++) { - r = r * (6.0 * k - 1.0) / 216.0 * (6.0 * k - 3.0) / k * (6.0 * k - 5.0) / (2.0 * k - 1.0); - ck[k - 1] = r; - dk[k - 1] = -(6.0 * k + 1.0) / (6.0 * k - 1.0) * r; - } - - if (x > 0.0) { - sai = 1.0; - sad = 1.0; - r = 1.0; - for (k = 1; k <= km; k++) { - r *= -xr1; - sai += ck[k - 1] * r; - sad += dk[k - 1] * r; - } - sbi = 1.0; - sbd = 1.0; - r = 1.0; - for (k = 1; k <= km; k++) { - r *= xr1; - sbi += ck[k - 1] * r; - sbd += dk[k - 1] * r; - } - xp1 = exp(-xe); - *ai = 0.5 * rp * xf * xp1 * sai; - *bi = rp * xf / xp1 * sbi; - *ad = -0.5 * rp / xf * xp1 * sad; - *bd = rp / xf / xp1 * sbd; - } else { - xcs = cos(xe + pi / 4.0); - xss = sin(xe + pi / 4.0); - ssa = 1.0; - sda = 1.0; - r = 1.0; - xr2 = 1.0 / (xe * xe); - for (k = 1; k <= km; k++) { - r *= -xr2; - ssa += ck[2 * k - 1] * r; - sda += dk[2 * k - 1] * r; - } - ssb = ck[0] * xr1; - sdb = dk[0] * xr1; - r = xr1; - for (k = 1; k <= km2; k++) { - r *= -xr2; - ssb += ck[2 * k] * r; - sdb += dk[2 * k] * r; - } - - *ai = rp * xf * (xss * ssa - xcs * ssb); - *bi = rp * xf * (xcs * ssa + xss * ssb); - *ad = -rp / xf * (xcs * sda + xss * sdb); - *bd = rp / xf * (xss * sda - xcs * sdb); - } - } - return; -} - -inline void airyzo(int nt, int kf, double *xa, double *xb, double *xc, double *xd) { - - // ======================================================== - // Purpose: Compute the first NT zeros of Airy functions - // Ai(x) and Ai'(x), a and a', and the associated - // values of Ai(a') and Ai'(a); and the first NT - // zeros of Airy functions Bi(x) and Bi'(x), b and - // b', and the associated values of Bi(b') and - // Bi'(b) - // Input : NT --- Total number of zeros - // KF --- Function code - // KF=1 for Ai(x) and Ai'(x) - // KF=2 for Bi(x) and Bi'(x) - // Output: XA(m) --- a, the m-th zero of Ai(x) or - // b, the m-th zero of Bi(x) - // XB(m) --- a', the m-th zero of Ai'(x) or - // b', the m-th zero of Bi'(x) - // XC(m) --- Ai(a') or Bi(b') - // XD(m) --- Ai'(a) or Bi'(b) - // ( m --- Serial number of zeros ) - // Routine called: AIRYB for computing Airy functions and - // their derivatives - // ======================================================= - - const double pi = 3.141592653589793; - int i; - double rt = 0.0, rt0, u = 0.0, u1 = 0.0, x, ai, bi, ad, bd, err; - - for (i = 1; i <= nt; ++i) { - rt0 = 0.0; - if (kf == 1) { - u = 3.0 * pi * (4.0 * i - 1) / 8.0; - u1 = 1 / (u * u); - } else if (kf == 2) { - if (i == 1) { - rt0 = -1.17371; - } else { - u = 3.0 * pi * (4.0 * i - 3.0) / 8.0; - u1 = 1 / (u * u); - } - } - - if (rt0 == 0) { - // DLMF 9.9.18 - rt0 = -pow(u * u, 1.0 / 3.0) * - (1.0 + - u1 * (5.0 / 48.0 + u1 * (-5.0 / 36.0 + u1 * (77125.0 / 82944.0 + u1 * (-108056875.0 / 6967296.0))))); - } - - while (1) { - x = rt0; - airyb(x, &ai, &bi, &ad, &bd); - - if (kf == 1) { - rt = rt0 - ai / ad; - } else if (kf == 2) { - rt = rt0 - bi / bd; - } - - err = fabs((rt - rt0) / rt); - if (err <= 1.0e-12) { - break; - } else { - rt0 = rt; - } - } - - xa[i - 1] = rt; - if (err > 1.0e-14) { - airyb(rt, &ai, &bi, &ad, &bd); - } - - if (kf == 1) { - xd[i - 1] = ad; - } else if (kf == 2) { - xd[i - 1] = bd; - } - } - - for (i = 1; i <= nt; ++i) { - rt0 = 0.0; - - if (kf == 1) { - if (i == 1) { - rt0 = -1.01879; - } else { - u = 3.0 * pi * (4.0 * i - 3.0) / 8.0; - u1 = 1 / (u * u); - } - } else if (kf == 2) { - if (i == 1) { - rt0 = -2.29444; - } else { - u = 3.0 * pi * (4.0 * i - 1.0) / 8.0; - u1 = 1 / (u * u); - } - } - - if (rt0 == 0) { - // DLMF 9.9.19 - rt0 = -pow(u * u, 1.0 / 3.0) * - (1.0 + u1 * (-7.0 / 48.0 + - u1 * (35.0 / 288.0 + u1 * (-181223.0 / 207360.0 + u1 * (18683371.0 / 1244160.0))))); - } - - while (1) { - x = rt0; - airyb(x, &ai, &bi, &ad, &bd); - - if (kf == 1) { - rt = rt0 - ad / (ai * x); - } else if (kf == 2) { - rt = rt0 - bd / (bi * x); - } - - err = fabs((rt - rt0) / rt); - if (err <= 1.0e-12) { - break; - } else { - rt0 = rt; - } - } - xb[i - 1] = rt; - - if (err > 1.0e-14) { - airyb(rt, &ai, &bi, &ad, &bd); - } - - if (kf == 1) { - xc[i - 1] = ai; - } else if (kf == 2) { - xc[i - 1] = bi; - } - } - return; -} - -template -void airy(std::complex z, std::complex &ai, std::complex &aip, std::complex &bi, std::complex &bip) { - int id = 0; - int ierr = 0; - int kode = 1; - int nz; - - ai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), ai); - - nz = 0; - bi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), bi); - - id = 1; - aip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), aip); - - nz = 0; - bip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airy:", ierr_to_sferr(nz, ierr), bip); -} - -template -void airye(std::complex z, std::complex &ai, std::complex &aip, std::complex &bi, std::complex &bip) { - int id = 0; - int kode = 2; /* Exponential scaling */ - int nz, ierr; - - ai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), ai); - - nz = 0; - bi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), bi); - - id = 1; - aip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), aip); - - nz = 0; - bip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), bip); -} - -template -void airye(T z, T &ai, T &aip, T &bi, T &bip) { - int id = 0; - int kode = 2; /* Exponential scaling */ - int nz, ierr; - std::complex cai, caip, cbi, cbip; - - cai.real(NAN); - cai.imag(NAN); - cbi.real(NAN); - cbi.imag(NAN); - caip.real(NAN); - caip.imag(NAN); - cbip.real(NAN); - cbip.real(NAN); - - if (z < 0) { - ai = NAN; - } else { - cai = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cai); - ai = std::real(cai); - } - - nz = 0; - cbi = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cbi); - bi = std::real(cbi); - - id = 1; - if (z < 0) { - aip = NAN; - } else { - caip = amos::airy(z, id, kode, &nz, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), caip); - aip = std::real(caip); - } - - nz = 0; - cbip = amos::biry(z, id, kode, &ierr); - set_error_and_nan("airye:", ierr_to_sferr(nz, ierr), cbip); - bip = std::real(cbip); -} - -template -void airy(T x, T &ai, T &aip, T &bi, T &bip) { - /* For small arguments, use Cephes as it's slightly faster. - * For large arguments, use AMOS as it's more accurate. - */ - if (x < -10 || x > 10) { - std::complex zai, zaip, zbi, zbip; - airy(std::complex(x), zai, zaip, zbi, zbip); - ai = std::real(zai); - aip = std::real(zaip); - bi = std::real(zbi); - bip = std::real(zbip); - } else { - cephes::airy(x, &ai, &aip, &bi, &bip); - } -} - -template -void itairy(T x, T &apt, T &bpt, T &ant, T &bnt) { - bool x_signbit = std::signbit(x); - if (x_signbit) { - x = -x; - } - - detail::itairy(x, apt, bpt, ant, bnt); - if (x_signbit) { /* negative limit -- switch signs and roles */ - T tmp = apt; - apt = -ant; - ant = -tmp; - - tmp = bpt; - bpt = -bnt; - bnt = -tmp; - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/alg.h b/scipy/special/xsf/alg.h deleted file mode 100644 index 28ea0ef5a52a..000000000000 --- a/scipy/special/xsf/alg.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "cephes/cbrt.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double cbrt(double x) { return cephes::detail::cbrt(x); } - -XSF_HOST_DEVICE inline float cbrt(float x) { return cbrt(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/amos.h b/scipy/special/xsf/amos.h deleted file mode 100644 index 4de81208bb99..000000000000 --- a/scipy/special/xsf/amos.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "amos/amos.h" -#include "error.h" - -namespace xsf { - -// -// Return sf_error equivalents for AMOS ierr values. -// 'ierr' refers to the last parameter of the AMOS functions -// airy(), besh(), besi(), besj(), besk(), besy(), and biry(). -// -inline sf_error_t ierr_to_sferr(int nz, int ierr) { - if (nz != 0) { - return SF_ERROR_UNDERFLOW; - } - - switch (ierr) { - case 1: - return SF_ERROR_DOMAIN; - case 2: - return SF_ERROR_OVERFLOW; - case 3: - return SF_ERROR_LOSS; - case 4: - return SF_ERROR_NO_RESULT; - case 5: /* Algorithm termination condition not met */ - return SF_ERROR_NO_RESULT; - case 6: /* Memory allocation failed */ - return SF_ERROR_MEMORY; - } - - return SF_ERROR_OK; -} -} // namespace xsf diff --git a/scipy/special/xsf/amos/amos.h b/scipy/special/xsf/amos/amos.h deleted file mode 100644 index fad49b25c6a0..000000000000 --- a/scipy/special/xsf/amos/amos.h +++ /dev/null @@ -1,6245 +0,0 @@ -/* - * - * This file is a C++ translation of the Fortran code written by - * D.E. Amos with the following original description: - * - * - * A Portable Package for Bessel Functions of a Complex Argument - * and Nonnegative Order - * - * This algorithm is a package of subroutines for computing Bessel - * functions and Airy functions. The routines are updated - * versions of those routines found in TOMS algorithm 644. - * - * Disclaimer: - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * ISSUED BY SANDIA LABORATORIES, - * A PRIME CONTRACTOR TO THE - * UNITED STATES DEPARTMENT OF ENERGY - * * * * * * * * * * * * * * NOTICE * * * * * * * * * * * * * * * - * THIS REPORT WAS PREPARED AS AN ACCOUNT OF WORK SPONSORED BY THE - * UNITED STATES GOVERNMENT. NEITHER THE UNITED STATES NOR THE - * UNITED STATES DEPARTMENT OF ENERGY, NOR ANY OF THEIR - * EMPLOYEES, NOR ANY OF THEIR CONTRACTORS, SUBCONTRACTORS, OR THEIR - * EMPLOYEES, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY - * LEGAL LIABILITY OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS - * OR USEFULNESS OF ANY INFORMATION, APPARATUS, PRODUCT OR PROCESS - * DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE - * PRIVATELY OWNED RIGHTS. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * THIS CODE HAS BEEN APPROVED FOR UNLIMITED RELEASE. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * - * - * The original Fortran code can be found at https://www.netlib.org/amos/ - * - * References: - * - * [1]: Abramowitz, M. and Stegun, I. A., Handbook of Mathematical - * Functions, NBS Applied Math Series 55, U.S. Dept. of Commerce, - * Washington, D.C., 1955 - * - * [2]: Amos, D. E., Algorithm 644, A Portable Package For Bessel - * Functions of a Complex Argument and Nonnegative Order, ACM - * Transactions on Mathematical Software, Vol. 12, No. 3, - * September 1986, Pages 265-273, DOI:10.1145/7921.214331 - * - * [3]: Amos, D. E., Remark on Algorithm 644, ACM Transactions on - * Mathematical Software, Vol. 16, No. 4, December 1990, Page - * 404, DOI:10.1145/98267.98299 - * - * [4]: Amos, D. E., A remark on Algorithm 644: "A portable package - * for Bessel functions of a complex argument and nonnegative order", - * ACM Transactions on Mathematical Software, Vol. 21, No. 4, - * December 1995, Pages 388-393, DOI:10.1145/212066.212078 - * - * [5]: Cody, W. J., Algorithm 665, MACHAR: A Subroutine to - * Dynamically Determine Machine Parameters, ACM Transactions on - * Mathematical Software, Vol. 14, No. 4, December 1988, Pages - * 303-311, DOI:10.1145/50063.51907 - * - */ - -/* - * Copyright (C) 2024 SciPy developers - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * a. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * b. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * c. Names of the SciPy Developers may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once - -#include - -#include -#include -#include // unique_ptr - -namespace xsf { -namespace amos { - -int acai(std::complex, double, int, int, int, std::complex *, double, double, double, double); -int acon(std::complex, double, int, int, int, std::complex *, double, double, double, double, double); -int asyi(std::complex, double, int, int, std::complex *, double, double, double, double); -int binu(std::complex, double fnu, int, int, std::complex *, double, double, double, double, double); -int bknu(std::complex, double, int, int, std::complex *, double, double, double); -int buni(std::complex, double, int, int, std::complex *, int, int *, double, double, double, double); -int bunk(std::complex, double, int, int, int, std::complex *, double, double, double); -double gamln(double); -int kscl(std::complex, double, int, std::complex *, std::complex, double *, double, double); -int mlri(std::complex, double, int, int, std::complex *, double); -void rati(std::complex, double, int, std::complex *, double); -int seri(std::complex, double, int, int, std::complex *, double, double, double); -int s1s2(std::complex, std::complex *, std::complex *, double, double, int *); -int uchk(std::complex, double, double); -void unhj(std::complex, double, int, double, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *); -void uni1(std::complex, double, int, int, std::complex *, int *, int *, double, double, double, double); -void uni2(std::complex, double, int, int, std::complex *, int *, int *, double, double, double, double); -void unik(std::complex, double, int, int, double, int *, std::complex *, std::complex *, std::complex *, std::complex *, std::complex *); -int unk1(std::complex, double, int, int, int, std::complex *, double, double, double); -int unk2(std::complex, double, int, int, int, std::complex *, double, double, double); -int uoik(std::complex, double, int, int, int, std::complex *, double, double, double); -int wrsk(std::complex, double, int, int, std::complex *, std::complex *, double, double, double); - - -constexpr double d1mach[5] = { - 2.2250738585072014e-308, /* np.finfo(np.float64).tiny */ - 1.7976931348623157e+308, /* np.finfo(np.float64).max */ - 1.1102230246251565e-16, /* 0.5 * np.finfo(np.float64).eps */ - 2.220446049250313e-16, /* np.finfo(np.float64).eps */ - 0.3010299956639812 /* np.log10(2) */ -}; - -constexpr double i1mach[16] = { - 5, /* standard input */ - 6, /* standard output */ - 7, /* standard punch */ - 0, /* standard error */ - 32, /* bits per integer */ - 4, /* sizeof(int); */ - 2, /* base for integers */ - 31, /* digits of integer base */ - 2147483647, /* LONG MAX 2**31 - 1 */ - 2, /* FLT_RADIX; */ - 24, /* FLT_MANT_DIG; */ - -126, /* FLT_MIN_EXP; */ - 128, /* FLT_MAX_EXP; */ - 53, /* DBL_MANT_DIG; */ - -1021, /* DBL_MIN_EXP; */ - 1024 /* DBL_MAX_EXP; */ -}; - -constexpr double zunhj_ar[14] = { - 1.00000000000000000e+00, 1.04166666666666667e-01, 8.35503472222222222e-02, 1.28226574556327160e-01, // 0 - 2.91849026464140464e-01, 8.81627267443757652e-01, 3.32140828186276754e+00, 1.49957629868625547e+01, // 4 - 7.89230130115865181e+01, 4.74451538868264323e+02, 3.20749009089066193e+03, 2.40865496408740049e+04, // 8 - 1.98923119169509794e+05, 1.79190200777534383e+06 // 12 -}; - -constexpr double zunhj_br[14] = { - 1.00000000000000000e+00, -1.45833333333333333e-01, -9.87413194444444444e-02, -1.43312053915895062e-01, // 0 - -3.17227202678413548e-01, -9.42429147957120249e-01, -3.51120304082635426e+00, -1.57272636203680451e+01, // 4 - -8.22814390971859444e+01, -4.92355370523670524e+02, -3.31621856854797251e+03, -2.48276742452085896e+04, // 8 - -2.04526587315129788e+05, -1.83844491706820990e+06 // 12 -}; - -constexpr double zunhj_c[105] = { - 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 - -4.01041666666666667e-01, 7.03125000000000000e-02, -1.02581259645061728e+00, 1.84646267361111111e+00, // 4 - -8.91210937500000000e-01, 7.32421875000000000e-02, 4.66958442342624743e+00, -1.12070026162229938e+01, // 8 - 8.78912353515625000e+00, -2.36408691406250000e+00, 1.12152099609375000e-01, -2.82120725582002449e+01, // 12 - 8.46362176746007346e+01, -9.18182415432400174e+01, 4.25349987453884549e+01, -7.36879435947963170e+00, // 16 - 2.27108001708984375e-01, 2.12570130039217123e+02, -7.65252468141181642e+02, 1.05999045252799988e+03, // 20 - -6.99579627376132541e+02, 2.18190511744211590e+02, -2.64914304869515555e+01, 5.72501420974731445e-01, // 24 - -1.91945766231840700e+03, 8.06172218173730938e+03, -1.35865500064341374e+04, 1.16553933368645332e+04, // 28 - -5.30564697861340311e+03, 1.20090291321635246e+03, -1.08090919788394656e+02, 1.72772750258445740e+00, // 32 - 2.02042913309661486e+04, -9.69805983886375135e+04, 1.92547001232531532e+05, -2.03400177280415534e+05, // 36 - 1.22200464983017460e+05, -4.11926549688975513e+04, 7.10951430248936372e+03, -4.93915304773088012e+02, // 40 - 6.07404200127348304e+00, -2.42919187900551333e+05, 1.31176361466297720e+06, -2.99801591853810675e+06, // 44 - 3.76327129765640400e+06, -2.81356322658653411e+06, 1.26836527332162478e+06, -3.31645172484563578e+05, // 48 - 4.52187689813627263e+04, -2.49983048181120962e+03, 2.43805296995560639e+01, 3.28446985307203782e+06, // 52 - -1.97068191184322269e+07, 5.09526024926646422e+07, -7.41051482115326577e+07, 6.63445122747290267e+07, // 56 - -3.75671766607633513e+07, 1.32887671664218183e+07, -2.78561812808645469e+06, 3.08186404612662398e+05, // 60 - -1.38860897537170405e+04, 1.10017140269246738e+02, -4.93292536645099620e+07, 3.25573074185765749e+08, // 64 - -9.39462359681578403e+08, 1.55359689957058006e+09, -1.62108055210833708e+09, 1.10684281682301447e+09, // 68 - -4.95889784275030309e+08, 1.42062907797533095e+08, -2.44740627257387285e+07, 2.24376817792244943e+06, // 72 - -8.40054336030240853e+04, 5.51335896122020586e+02, 8.14789096118312115e+08, -5.86648149205184723e+09, // 76 - 1.86882075092958249e+10, -3.46320433881587779e+10, 4.12801855797539740e+10, -3.30265997498007231e+10, // 80 - 1.79542137311556001e+10, -6.56329379261928433e+09, 1.55927986487925751e+09, -2.25105661889415278e+08, // 84 - 1.73951075539781645e+07, -5.49842327572288687e+05, 3.03809051092238427e+03, -1.46792612476956167e+10, // 88 - 1.14498237732025810e+11, -3.99096175224466498e+11, 8.19218669548577329e+11, -1.09837515608122331e+12, // 92 - 1.00815810686538209e+12, -6.45364869245376503e+11, 2.87900649906150589e+11, -8.78670721780232657e+10, // 96 - 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 - 1.82577554742931747e+04 // 104 -}; - -constexpr double zunhj_alfa[180] = { - -4.44444444444444444e-03, -9.22077922077922078e-04, -8.84892884892884893e-05, 1.65927687832449737e-04, // 0 - 2.46691372741792910e-04, 2.65995589346254780e-04, 2.61824297061500945e-04, 2.48730437344655609e-04, // 4 - 2.32721040083232098e-04, 2.16362485712365082e-04, 2.00738858762752355e-04, 1.86267636637545172e-04, // 8 - 1.73060775917876493e-04, 1.61091705929015752e-04, 1.50274774160908134e-04, 1.40503497391269794e-04, // 12 - 1.31668816545922806e-04, 1.23667445598253261e-04, 1.16405271474737902e-04, 1.09798298372713369e-04, // 16 - 1.03772410422992823e-04, 9.82626078369363448e-05, 9.32120517249503256e-05, 8.85710852478711718e-05, // 20 - 8.42963105715700223e-05, 8.03497548407791151e-05, 7.66981345359207388e-05, 7.33122157481777809e-05, // 24 - 7.01662625163141333e-05, 6.72375633790160292e-05, 6.93735541354588974e-04, 2.32241745182921654e-04, // 28 - -1.41986273556691197e-05, -1.16444931672048640e-04, -1.50803558053048762e-04, -1.55121924918096223e-04, // 32 - -1.46809756646465549e-04, -1.33815503867491367e-04, -1.19744975684254051e-04, -1.06184319207974020e-04, // 36 - -9.37699549891194492e-05, -8.26923045588193274e-05, -7.29374348155221211e-05, -6.44042357721016283e-05, // 40 - -5.69611566009369048e-05, -5.04731044303561628e-05, -4.48134868008882786e-05, -3.98688727717598864e-05, // 44 - -3.55400532972042498e-05, -3.17414256609022480e-05, -2.83996793904174811e-05, -2.54522720634870566e-05, // 48 - -2.28459297164724555e-05, -2.05352753106480604e-05, -1.84816217627666085e-05, -1.66519330021393806e-05, // 52 - -1.50179412980119482e-05, -1.35554031379040526e-05, -1.22434746473858131e-05, -1.10641884811308169e-05, // 56 - -3.54211971457743841e-04, -1.56161263945159416e-04, 3.04465503594936410e-05, 1.30198655773242693e-04, // 60 - 1.67471106699712269e-04, 1.70222587683592569e-04, 1.56501427608594704e-04, 1.36339170977445120e-04, // 64 - 1.14886692029825128e-04, 9.45869093034688111e-05, 7.64498419250898258e-05, 6.07570334965197354e-05, // 68 - 4.74394299290508799e-05, 3.62757512005344297e-05, 2.69939714979224901e-05, 1.93210938247939253e-05, // 72 - 1.30056674793963203e-05, 7.82620866744496661e-06, 3.59257485819351583e-06, 1.44040049814251817e-07, // 76 - -2.65396769697939116e-06, -4.91346867098485910e-06, -6.72739296091248287e-06, -8.17269379678657923e-06, // 80 - -9.31304715093561232e-06, -1.02011418798016441e-05, -1.08805962510592880e-05, -1.13875481509603555e-05, // 84 - -1.17519675674556414e-05, -1.19987364870944141e-05, 3.78194199201772914e-04, 2.02471952761816167e-04, // 88 - -6.37938506318862408e-05, -2.38598230603005903e-04, -3.10916256027361568e-04, -3.13680115247576316e-04, // 92 - -2.78950273791323387e-04, -2.28564082619141374e-04, -1.75245280340846749e-04, -1.25544063060690348e-04, // 96 - -8.22982872820208365e-05, -4.62860730588116458e-05, -1.72334302366962267e-05, 5.60690482304602267e-06, // 100 - 2.31395443148286800e-05, 3.62642745856793957e-05, 4.58006124490188752e-05, 5.24595294959114050e-05, // 104 - 5.68396208545815266e-05, 5.94349820393104052e-05, 6.06478527578421742e-05, 6.08023907788436497e-05, // 108 - 6.01577894539460388e-05, 5.89199657344698500e-05, 5.72515823777593053e-05, 5.52804375585852577e-05, // 112 - 5.31063773802880170e-05, 5.08069302012325706e-05, 4.84418647620094842e-05, 4.60568581607475370e-05, // 116 - -6.91141397288294174e-04, -4.29976633058871912e-04, 1.83067735980039018e-04, 6.60088147542014144e-04, // 120 - 8.75964969951185931e-04, 8.77335235958235514e-04, 7.49369585378990637e-04, 5.63832329756980918e-04, // 124 - 3.68059319971443156e-04, 1.88464535514455599e-04, 3.70663057664904149e-05, -8.28520220232137023e-05, // 128 - -1.72751952869172998e-04, -2.36314873605872983e-04, -2.77966150694906658e-04, -3.02079514155456919e-04, // 132 - -3.12594712643820127e-04, -3.12872558758067163e-04, -3.05678038466324377e-04, -2.93226470614557331e-04, // 136 - -2.77255655582934777e-04, -2.59103928467031709e-04, -2.39784014396480342e-04, -2.20048260045422848e-04, // 140 - -2.00443911094971498e-04, -1.81358692210970687e-04, -1.63057674478657464e-04, -1.45712672175205844e-04, // 144 - -1.29425421983924587e-04, -1.14245691942445952e-04, 1.92821964248775885e-03, 1.35592576302022234e-03, // 148 - -7.17858090421302995e-04, -2.58084802575270346e-03, -3.49271130826168475e-03, -3.46986299340960628e-03, // 152 - -2.82285233351310182e-03, -1.88103076404891354e-03, -8.89531718383947600e-04, 3.87912102631035228e-06, // 156 - 7.28688540119691412e-04, 1.26566373053457758e-03, 1.62518158372674427e-03, 1.83203153216373172e-03, // 160 - 1.91588388990527909e-03, 1.90588846755546138e-03, 1.82798982421825727e-03, 1.70389506421121530e-03, // 164 - 1.55097127171097686e-03, 1.38261421852276159e-03, 1.20881424230064774e-03, 1.03676532638344962e-03, // 168 - 8.71437918068619115e-04, 7.16080155297701002e-04, 5.72637002558129372e-04, 4.42089819465802277e-04, // 172 - 3.24724948503090564e-04, 2.20342042730246599e-04, 1.28412898401353882e-04, 4.82005924552095464e-05 // 176 -}; - -constexpr double zunhj_beta[210] = { - 1.79988721413553309e-02, 5.59964911064388073e-03, 2.88501402231132779e-03, 1.80096606761053941e-03, // 0 - 1.24753110589199202e-03, 9.22878876572938311e-04, 7.14430421727287357e-04, 5.71787281789704872e-04, // 4 - 4.69431007606481533e-04, 3.93232835462916638e-04, 3.34818889318297664e-04, 2.88952148495751517e-04, // 8 - 2.52211615549573284e-04, 2.22280580798883327e-04, 1.97541838033062524e-04, 1.76836855019718004e-04, // 12 - 1.59316899661821081e-04, 1.44347930197333986e-04, 1.31448068119965379e-04, 1.20245444949302884e-04, // 16 - 1.10449144504599392e-04, 1.01828770740567258e-04, 9.41998224204237509e-05, 8.74130545753834437e-05, // 20 - 8.13466262162801467e-05, 7.59002269646219339e-05, 7.09906300634153481e-05, 6.65482874842468183e-05, // 24 - 6.25146958969275078e-05, 5.88403394426251749e-05, -1.49282953213429172e-03, -8.78204709546389328e-04, // 28 - -5.02916549572034614e-04, -2.94822138512746025e-04, -1.75463996970782828e-04, -1.04008550460816434e-04, // 32 - -5.96141953046457895e-05, -3.12038929076098340e-05, -1.26089735980230047e-05, -2.42892608575730389e-07, // 36 - 8.05996165414273571e-06, 1.36507009262147391e-05, 1.73964125472926261e-05, 1.98672978842133780e-05, // 40 - 2.14463263790822639e-05, 2.23954659232456514e-05, 2.28967783814712629e-05, 2.30785389811177817e-05, // 44 - 2.30321976080909144e-05, 2.28236073720348722e-05, 2.25005881105292418e-05, 2.20981015361991429e-05, // 48 - 2.16418427448103905e-05, 2.11507649256220843e-05, 2.06388749782170737e-05, 2.01165241997081666e-05, // 52 - 1.95913450141179244e-05, 1.90689367910436740e-05, 1.85533719641636667e-05, 1.80475722259674218e-05, // 56 - 5.52213076721292790e-04, 4.47932581552384646e-04, 2.79520653992020589e-04, 1.52468156198446602e-04, // 60 - 6.93271105657043598e-05, 1.76258683069991397e-05, -1.35744996343269136e-05, -3.17972413350427135e-05, // 64 - -4.18861861696693365e-05, -4.69004889379141029e-05, -4.87665447413787352e-05, -4.87010031186735069e-05, // 68 - -4.74755620890086638e-05, -4.55813058138628452e-05, -4.33309644511266036e-05, -4.09230193157750364e-05, // 72 - -3.84822638603221274e-05, -3.60857167535410501e-05, -3.37793306123367417e-05, -3.15888560772109621e-05, // 76 - -2.95269561750807315e-05, -2.75978914828335759e-05, -2.58006174666883713e-05, -2.41308356761280200e-05, // 80 - -2.25823509518346033e-05, -2.11479656768912971e-05, -1.98200638885294927e-05, -1.85909870801065077e-05, // 84 - -1.74532699844210224e-05, -1.63997823854497997e-05, -4.74617796559959808e-04, -4.77864567147321487e-04, // 88 - -3.20390228067037603e-04, -1.61105016119962282e-04, -4.25778101285435204e-05, 3.44571294294967503e-05, // 92 - 7.97092684075674924e-05, 1.03138236708272200e-04, 1.12466775262204158e-04, 1.13103642108481389e-04, // 96 - 1.08651634848774268e-04, 1.01437951597661973e-04, 9.29298396593363896e-05, 8.40293133016089978e-05, // 100 - 7.52727991349134062e-05, 6.69632521975730872e-05, 5.92564547323194704e-05, 5.22169308826975567e-05, // 104 - 4.58539485165360646e-05, 4.01445513891486808e-05, 3.50481730031328081e-05, 3.05157995034346659e-05, // 108 - 2.64956119950516039e-05, 2.29363633690998152e-05, 1.97893056664021636e-05, 1.70091984636412623e-05, // 112 - 1.45547428261524004e-05, 1.23886640995878413e-05, 1.04775876076583236e-05, 8.79179954978479373e-06, // 116 - 7.36465810572578444e-04, 8.72790805146193976e-04, 6.22614862573135066e-04, 2.85998154194304147e-04, // 120 - 3.84737672879366102e-06, -1.87906003636971558e-04, -2.97603646594554535e-04, -3.45998126832656348e-04, // 124 - -3.53382470916037712e-04, -3.35715635775048757e-04, -3.04321124789039809e-04, -2.66722723047612821e-04, // 128 - -2.27654214122819527e-04, -1.89922611854562356e-04, -1.55058918599093870e-04, -1.23778240761873630e-04, // 132 - -9.62926147717644187e-05, -7.25178327714425337e-05, -5.22070028895633801e-05, -3.50347750511900522e-05, // 136 - -2.06489761035551757e-05, -8.70106096849767054e-06, 1.13698686675100290e-06, 9.16426474122778849e-06, // 140 - 1.56477785428872620e-05, 2.08223629482466847e-05, 2.48923381004595156e-05, 2.80340509574146325e-05, // 144 - 3.03987774629861915e-05, 3.21156731406700616e-05, -1.80182191963885708e-03, -2.43402962938042533e-03, // 148 - -1.83422663549856802e-03, -7.62204596354009765e-04, 2.39079475256927218e-04, 9.49266117176881141e-04, // 152 - 1.34467449701540359e-03, 1.48457495259449178e-03, 1.44732339830617591e-03, 1.30268261285657186e-03, // 156 - 1.10351597375642682e-03, 8.86047440419791759e-04, 6.73073208165665473e-04, 4.77603872856582378e-04, // 160 - 3.05991926358789362e-04, 1.60315694594721630e-04, 4.00749555270613286e-05, -5.66607461635251611e-05, // 164 - -1.32506186772982638e-04, -1.90296187989614057e-04, -2.32811450376937408e-04, -2.62628811464668841e-04, // 168 - -2.82050469867598672e-04, -2.93081563192861167e-04, -2.97435962176316616e-04, -2.96557334239348078e-04, // 172 - -2.91647363312090861e-04, -2.83696203837734166e-04, -2.73512317095673346e-04, -2.61750155806768580e-04, // 176 - 6.38585891212050914e-03, 9.62374215806377941e-03, 7.61878061207001043e-03, 2.83219055545628054e-03, // 180 - -2.09841352012720090e-03, -5.73826764216626498e-03, -7.70804244495414620e-03, -8.21011692264844401e-03, // 184 - -7.65824520346905413e-03, -6.47209729391045177e-03, -4.99132412004966473e-03, -3.45612289713133280e-03, // 188 - -2.01785580014170775e-03, -7.59430686781961401e-04, 2.84173631523859138e-04, 1.10891667586337403e-03, // 192 - 1.72901493872728771e-03, 2.16812590802684701e-03, 2.45357710494539735e-03, 2.61281821058334862e-03, // 196 - 2.67141039656276912e-03, 2.65203073395980430e-03, 2.57411652877287315e-03, 2.45389126236094427e-03, // 200 - 2.30460058071795494e-03, 2.13684837686712662e-03, 1.95896528478870911e-03, 1.77737008679454412e-03, // 204 - 1.59690280765839059e-03, 1.42111975664438546e-03 // 208 -}; - -constexpr double zunhj_gama[30] = { - 6.29960524947436582e-01, 2.51984209978974633e-01, 1.54790300415655846e-01, 1.10713062416159013e-01, // 0 - 8.57309395527394825e-02, 6.97161316958684292e-02, 5.86085671893713576e-02, 5.04698873536310685e-02, // 4 - 4.42600580689154809e-02, 3.93720661543509966e-02, 3.54283195924455368e-02, 3.21818857502098231e-02, // 8 - 2.94646240791157679e-02, 2.71581677112934479e-02, 2.51768272973861779e-02, 2.34570755306078891e-02, // 12 - 2.19508390134907203e-02, 2.06210828235646240e-02, 1.94388240897880846e-02, 1.83810633800683158e-02, // 16 - 1.74293213231963172e-02, 1.65685837786612353e-02, 1.57865285987918445e-02, 1.50729501494095594e-02, // 20 - 1.44193250839954639e-02, 1.38184805735341786e-02, 1.32643378994276568e-02, 1.27517121970498651e-02, // 24 - 1.22761545318762767e-02, 1.18338262398482403e-02 // 28 -}; - -constexpr double zunik_c[120] = { - 1.00000000000000000e+00, -2.08333333333333333e-01, 1.25000000000000000e-01, 3.34201388888888889e-01, // 0 - -4.01041666666666667e-01, 7.03125000000000000e-02, -1.02581259645061728e+00, 1.84646267361111111e+00, // 4 - -8.91210937500000000e-01, 7.32421875000000000e-02, 4.66958442342624743e+00, -1.12070026162229938e+01, // 8 - 8.78912353515625000e+00, -2.36408691406250000e+00, 1.12152099609375000e-01, -2.82120725582002449e+01, // 12 - 8.46362176746007346e+01, -9.18182415432400174e+01, 4.25349987453884549e+01, -7.36879435947963170e+00, // 16 - 2.27108001708984375e-01, 2.12570130039217123e+02, -7.65252468141181642e+02, 1.05999045252799988e+03, // 20 - -6.99579627376132541e+02, 2.18190511744211590e+02, -2.64914304869515555e+01, 5.72501420974731445e-01, // 24 - -1.91945766231840700e+03, 8.06172218173730938e+03, -1.35865500064341374e+04, 1.16553933368645332e+04, // 28 - -5.30564697861340311e+03, 1.20090291321635246e+03, -1.08090919788394656e+02, 1.72772750258445740e+00, // 32 - 2.02042913309661486e+04, -9.69805983886375135e+04, 1.92547001232531532e+05, -2.03400177280415534e+05, // 36 - 1.22200464983017460e+05, -4.11926549688975513e+04, 7.10951430248936372e+03, -4.93915304773088012e+02, // 40 - 6.07404200127348304e+00, -2.42919187900551333e+05, 1.31176361466297720e+06, -2.99801591853810675e+06, // 44 - 3.76327129765640400e+06, -2.81356322658653411e+06, 1.26836527332162478e+06, -3.31645172484563578e+05, // 48 - 4.52187689813627263e+04, -2.49983048181120962e+03, 2.43805296995560639e+01, 3.28446985307203782e+06, // 52 - -1.97068191184322269e+07, 5.09526024926646422e+07, -7.41051482115326577e+07, 6.63445122747290267e+07, // 56 - -3.75671766607633513e+07, 1.32887671664218183e+07, -2.78561812808645469e+06, 3.08186404612662398e+05, // 60 - -1.38860897537170405e+04, 1.10017140269246738e+02, -4.93292536645099620e+07, 3.25573074185765749e+08, // 64 - -9.39462359681578403e+08, 1.55359689957058006e+09, -1.62108055210833708e+09, 1.10684281682301447e+09, // 68 - -4.95889784275030309e+08, 1.42062907797533095e+08, -2.44740627257387285e+07, 2.24376817792244943e+06, // 72 - -8.40054336030240853e+04, 5.51335896122020586e+02, 8.14789096118312115e+08, -5.86648149205184723e+09, // 76 - 1.86882075092958249e+10, -3.46320433881587779e+10, 4.12801855797539740e+10, -3.30265997498007231e+10, // 80 - 1.79542137311556001e+10, -6.56329379261928433e+09, 1.55927986487925751e+09, -2.25105661889415278e+08, // 84 - 1.73951075539781645e+07, -5.49842327572288687e+05, 3.03809051092238427e+03, -1.46792612476956167e+10, // 88 - 1.14498237732025810e+11, -3.99096175224466498e+11, 8.19218669548577329e+11, -1.09837515608122331e+12, // 92 - 1.00815810686538209e+12, -6.45364869245376503e+11, 2.87900649906150589e+11, -8.78670721780232657e+10, // 96 - 1.76347306068349694e+10, -2.16716498322379509e+09, 1.43157876718888981e+08, -3.87183344257261262e+06, // 100 - 1.82577554742931747e+04, 2.86464035717679043e+11, -2.40629790002850396e+12, 9.10934118523989896e+12, // 104 - -2.05168994109344374e+13, 3.05651255199353206e+13, -3.16670885847851584e+13, 2.33483640445818409e+13, // 108 - -1.23204913055982872e+13, 4.61272578084913197e+12, -1.19655288019618160e+12, 2.05914503232410016e+11, // 112 - -2.18229277575292237e+10, 1.24700929351271032e+09, -2.91883881222208134e+07, 1.18838426256783253e+05 // 116 -}; - -constexpr double dgamln_gln[100] = { - 0.00000000000000000e+00, 0.00000000000000000e+00, 6.93147180559945309e-01, 1.79175946922805500e+00, // 0 - 3.17805383034794562e+00, 4.78749174278204599e+00, 6.57925121201010100e+00, 8.52516136106541430e+00, // 4 - 1.06046029027452502e+01, 1.28018274800814696e+01, 1.51044125730755153e+01, 1.75023078458738858e+01, // 8 - 1.99872144956618861e+01, 2.25521638531234229e+01, 2.51912211827386815e+01, 2.78992713838408916e+01, // 12 - 3.06718601060806728e+01, 3.35050734501368889e+01, 3.63954452080330536e+01, 3.93398841871994940e+01, // 16 - 4.23356164607534850e+01, 4.53801388984769080e+01, 4.84711813518352239e+01, 5.16066755677643736e+01, // 20 - 5.47847293981123192e+01, 5.80036052229805199e+01, 6.12617017610020020e+01, 6.45575386270063311e+01, // 24 - 6.78897431371815350e+01, 7.12570389671680090e+01, 7.46582363488301644e+01, 7.80922235533153106e+01, // 28 - 8.15579594561150372e+01, 8.50544670175815174e+01, 8.85808275421976788e+01, 9.21361756036870925e+01, // 32 - 9.57196945421432025e+01, 9.93306124547874269e+01, 1.02968198614513813e+02, 1.06631760260643459e+02, // 36 - 1.10320639714757395e+02, 1.14034211781461703e+02, 1.17771881399745072e+02, 1.21533081515438634e+02, // 40 - 1.25317271149356895e+02, 1.29123933639127215e+02, 1.32952575035616310e+02, 1.36802722637326368e+02, // 44 - 1.40673923648234259e+02, 1.44565743946344886e+02, 1.48477766951773032e+02, 1.52409592584497358e+02, // 48 - 1.56360836303078785e+02, 1.60331128216630907e+02, 1.64320112263195181e+02, 1.68327445448427652e+02, // 52 - 1.72352797139162802e+02, 1.76395848406997352e+02, 1.80456291417543771e+02, 1.84533828861449491e+02, // 56 - 1.88628173423671591e+02, 1.92739047287844902e+02, 1.96866181672889994e+02, 2.01009316399281527e+02, // 60 - 2.05168199482641199e+02, 2.09342586752536836e+02, 2.13532241494563261e+02, 2.17736934113954227e+02, // 64 - 2.21956441819130334e+02, 2.26190548323727593e+02, 2.30439043565776952e+02, 2.34701723442818268e+02, // 68 - 2.38978389561834323e+02, 2.43268849002982714e+02, 2.47572914096186884e+02, 2.51890402209723194e+02, // 72 - 2.56221135550009525e+02, 2.60564940971863209e+02, 2.64921649798552801e+02, 2.69291097651019823e+02, // 76 - 2.73673124285693704e+02, 2.78067573440366143e+02, 2.82474292687630396e+02, 2.86893133295426994e+02, // 80 - 2.91323950094270308e+02, 2.95766601350760624e+02, 3.00220948647014132e+02, 3.04686856765668715e+02, // 84 - 3.09164193580146922e+02, 3.13652829949879062e+02, 3.18152639620209327e+02, 3.22663499126726177e+02, // 88 - 3.27185287703775217e+02, 3.31717887196928473e+02, 3.36261181979198477e+02, 3.40815058870799018e+02, // 92 - 3.45379407062266854e+02, 3.49954118040770237e+02, 3.54539085519440809e+02, 3.59134205369575399e+02 // 96 -}; - -constexpr double dgamln_cf[22] = { - 8.33333333333333333e-02, -2.77777777777777778e-03, 7.93650793650793651e-04, -5.95238095238095238e-04, // 0 - 8.41750841750841751e-04, -1.91752691752691753e-03, 6.41025641025641026e-03, -2.95506535947712418e-02, // 4 - 1.79644372368830573e-01, -1.39243221690590112e+00, 1.34028640441683920e+01, -1.56848284626002017e+02, // 8 - 2.19310333333333333e+03, -3.61087712537249894e+04, 6.91472268851313067e+05, -1.52382215394074162e+07, // 12 - 3.82900751391414141e+08, -1.08822660357843911e+10, 3.47320283765002252e+11, -1.23696021422692745e+13, // 16 - 4.88788064793079335e+14, -2.13203339609193739e+16 // 20 -}; - - -inline int acai( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double rl, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZACAI - //***REFER TO ZAIRY - // - // ZACAI APPLIES THE ANALYTIC CONTINUATION FORMULA - // - // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) - // MP=PI*MR*std::complex(0.0,1.0) - // - // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT - // HALF Z PLANE FOR USE WITH ZAIRY WHERE FNU=1/3 OR 2/3 AND N=1. - // ZACAI IS THE SAME AS ZACON WITH THE PARTS FOR LARGER ORDERS AND - // RECURRENCE REMOVED. A RECURSIVE CALL TO ZACON CAN RESULT IF ZACON - // IS CALLED FROM ZAIRY. - // - //***ROUTINES CALLED ZASYI,ZBKNU,ZMLRI,ZSERI,ZS1S2,D1MACH,AZABS - //***END PROLOGUE ZACAI - - std::complex csgn, cspn, c1, c2, zn, cy[2]; - double arg, ascle, az, cpn, dfnu, fmr, sgn, spn, yy; - int inu, iuf, nn, nw; - double pi = 3.14159265358979324; - int nz = 0; - zn = -z; - az = std::abs(z); - nn = n; - dfnu = fnu + (n-1); - if ((az > 2.0) && (az*az*0.25 > dfnu+1.0)) { - /* 20 */ - if (az >= rl) { - // - // ASYMPTOTIC EXPANSION FOR LARGE Z FOR THE I FUNCTION - // - nw = asyi(zn, fnu, kode, nn, y, rl, tol, elim, alim); - } else { - // - // MILLER ALGORITHM NORMALIZED BY THE SERIES FOR THE I FUNCTION - // - nw = mlri(zn, fnu, kode, nn, y, tol); - } - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - } else{ - // - // POWER SERIES FOR THE I FUNCTION - // - seri(zn, fnu, kode, nn, y, tol, elim, alim); - } - /* 40 */ - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION - // - nw = bknu(zn, fnu, kode, 1, &cy[0], tol, elim, alim); - if (nw != 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - fmr = mr; - sgn = (fmr < 0.0 ? pi : -pi); - csgn = std::complex(0.0, sgn); - if (kode != 1) { - yy = -std::imag(zn); - cpn = cos(yy); - spn = sin(yy); - csgn *= std::complex(cpn, spn); - } - // - // CALCULATE CSPN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - arg = (fnu - inu)*sgn; - cpn = cos(arg); - spn = sin(arg); - cspn = std::complex(cpn, spn); - if (inu % 2 == 1) { cspn = -cspn; } - c1 = cy[0]; - c2 = y[0]; - if (kode != 1) { - iuf = 0; - ascle = 1e3 * d1mach[0] / tol; - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - } - y[0] = cspn*c1 + csgn*c2; - return nz; -} - - -inline int acon( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double rl, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZACON - //***REFER TO ZBESK,ZBESH - // - // ZACON APPLIES THE ANALYTIC CONTINUATION FORMULA - // - // K(FNU,ZN*EXP(MP))=K(FNU,ZN)*EXP(-MP*FNU) - MP*I(FNU,ZN) - // MP=PI*MR*std::complex(0.0,1.0) - // - // TO CONTINUE THE K FUNCTION FROM THE RIGHT HALF TO THE LEFT - // HALF Z PLANE - // - //***ROUTINES CALLED ZBINU,ZBKNU,ZS1S2,D1MACH,AZABS,ZMLT - //***END PROLOGUE ZACON - - std::complex ck, cs, cscl, cscr, csgn, cspn, c1, c2, rz, sc1, sc2 = 0.0,\ - st, s1, s2, zn; - double arg, ascle, as2, bscle, c1i, c1m, c1r, fmr, sgn, yy; - int i, inu, iuf, kflag, nn, nw, nz; - double pi = 3.14159265358979324; - std::complex cy[2] = { 0.0 }; - std::complex css[3] = { 0.0 }; - std::complex csr[3] = { 0.0 }; - double bry[3] = { 0.0 }; - - nz = 0; - zn = -z; - nn = n; - nw = binu(zn, fnu, kode, nn, y, rl, fnul, tol, elim, alim); - if (nw >= 0) { - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE FOR THE K FUNCTION - // - nn = (n > 2 ? 2 : n); - nw = bknu(zn, fnu, kode, nn, cy, tol, elim, alim); - if (nw == 0) { - s1 = cy[0]; - fmr = mr; - sgn = ( fmr < 0 ? pi : -pi ); - csgn = std::complex(0.0, sgn); - if (kode != 1) { - yy = -std::imag(zn); - csgn *= std::complex(cos(yy), sin(yy)); - } - inu = (int)fnu; - arg = (fnu - inu)*sgn; - cspn = std::complex(cos(arg), sin(arg)); - if (inu % 2 == 1) { cspn = -cspn; } - iuf = 0; - c1 = s1; - c2 = y[0]; - ascle = 1e3*d1mach[0]/tol; - if (kode != 1) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc1 = c1; - } - y[0] = cspn*c1 + csgn*c2; - if (n == 1) { return nz; } - cspn = -cspn; - s2 = cy[1]; - c1 = s2; - c2 = y[1]; - if (kode != 1) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc2 = c1; - } - y[1] = cspn*c1 + csgn*c2; - if (n == 2) { return nz; } - cspn = -cspn; - rz = 2.0 / zn; - ck = (fnu + 1.0)*rz; - // - // SCALE NEAR EXPONENT EXTREMES DURING RECURRENCE ON K FUNCTIONS - // - cscl = 1.0 / tol; - cscr = tol; - css[0] = cscl; - css[1] = 1.0; - css[2] = cscr; - csr[0] = cscr; - csr[1] = 1.0; - csr[2] = cscl; - bry[0] = ascle; - bry[1] = 1.0 / ascle; - bry[2] = d1mach[1]; - as2 = std::abs(s2); - kflag = 2; - if (as2 <= bry[0] ) { - kflag = 1; - } else { - if (as2 >= bry[1]) { - kflag = 3; - } - } - bscle = bry[kflag-1]; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - cs = csr[kflag-1]; - for (i = 3; i < (n+1); i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - c1 = s2*cs; - st = c1; - c2 = y[i-1]; - if (kode != 1) { - if (iuf >= 0) { - nw = s1s2(zn, &c1, &c2, ascle, alim, &iuf); - nz += nw; - sc1 = sc2; - sc2 = c1; - if (iuf == 3){ - iuf = -4; - s1 = sc1 * css[kflag-1]; - s2 = sc2 * css[kflag-1]; - st = sc2; - } - } - } - y[i-1] = cspn*c1 + csgn*c2; - ck += rz; - cspn = -cspn; - if (kflag < 3) { - c1r = fabs(std::real(c1)); - c1i = fabs(std::imag(c1)); - c1m = fmax(c1r, c1i); - if (c1m > bscle) { - kflag += 1; - bscle = bry[kflag-1]; - s1 *= cs; - s2 = st; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - cs = csr[kflag-1]; - } - } - } - return nz; - } - } - nz = -1; - if (nw == -2) { nz = -2; } - return nz; -} - - -inline std::complex airy( - std::complex z, - int id, - int kode, - int *nz, - int *ierr -) { - - //***BEGIN PROLOGUE ZAIRY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE AIRY FUNCTIONS AI(Z) AND DAI(Z) FOR COMPLEX Z - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZAIRY COMPUTES THE COMPLEX AIRY FUNCTION AI(Z) OR - // ITS DERIVATIVE DAI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON - // KODE=2, A SCALING OPTION CEXP(ZTA)*AI(Z) OR CEXP(ZTA)* - // DAI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL DECAY IN - // -PI/3.LT.ARG(Z).LT.PI/3 AND THE EXPONENTIAL GROWTH IN - // PI/3.LT.ABS(ARG(Z)).LT.PI WHERE ZTA=(2/3)*Z*CSQRT(Z). - // - // WHILE THE AIRY FUNCTIONS AI(Z) AND DAI(Z)/DZ ARE ANALYTIC IN - // THE WHOLE Z PLANE, THE CORRESPONDING SCALED FUNCTIONS DEFINED - // FOR KODE=2 HAVE A CUT ALONG THE NEGATIVE REAL AXIS. - // DEFINITIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF - // MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI) - // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // AI=AI(Z) ON ID=0 OR - // AI=DAI(Z)/DZ ON ID=1 - // = 2 RETURNS - // AI=CEXP(ZTA)*AI(Z) ON ID=0 OR - // AI=CEXP(ZTA)*DAI(Z)/DZ ON ID=1 WHERE - // ZTA=(2/3)*Z*CSQRT(Z) - // - // OUTPUT AIR,AII ARE DOUBLE PRECISION - // AIR,AII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND - // KODE - // NZ - UNDERFLOW INDICATOR - // NZ= 0 , NORMAL RETURN - // NZ= 1 , AI=std::complex(0.0D0,0.0D0) DUE TO UNDERFLOW IN - // -PI/3.LT.ARG(Z).LT.PI/3 ON KODE=1 - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(ZTA) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED - // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION - // PRODUCE LESS THAN HALF OF MACHINE ACCURACY - // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION - // COMPLETE LOSS OF ACCURACY BY ARGUMENT - // REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // AI AND DAI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE K BESSEL - // FUNCTIONS BY - // - // AI(Z)=C*SQRT(Z)*K(1/3,ZTA) , DAI(Z)=-C*Z*K(2/3,ZTA) - // C=1.0/(PI*SQRT(3.0)) - // ZTA=(2/3)*Z**(3/2) - // - // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES - // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF - // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), - // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR - // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN - // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT - // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE - // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA - // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, - // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE - // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE - // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- - // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- - // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN - // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN - // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, - // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE - // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER - // MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACAI,ZBKNU,AZEXP,AZSQRT,I1MACH,D1MACH - //***END PROLOGUE ZAIRY - - std::complex ai, csq, cy[1], s1, s2, trm1, trm2, zta, z3; - double aa, ad, ak, alim, atrm, az, az3, bk, ck, dig, dk, d1, d2,\ - elim, fid, fnu, rl, r1m5, sfac, tol, zi, zr, bb, alaz; - int iflag, k, k1, k2, mr, nn; - double tth = 2. / 3.; - double c1 = 0.35502805388781723926; /* 1/(Gamma(2/3) * 3**(2/3)) */ - double c2 = 0.25881940379280679841; /* 1/(Gamma(1/3) * 3**(1/3)) */ - double coef = 0.18377629847393068317; /* 1 / (sqrt(3) * PI) */ - - *ierr = 0; - *nz = 0; - ai = 0.; - if ((id < 0) || (id > 1)) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (*ierr != 0) return 0.; - az = std::abs(z); - tol = d1mach[3]; - fid = id; - - if (az <= 1.0) { - // - // POWER SERIES FOR ABS(Z) <= 1. - // - s1 = 1.0; - s2 = 1.0; - if (az < tol) { - aa = 1e3*d1mach[0]; - s1 = 0.; - if (id != 1) { - if (az > aa) { s1 = c2 * z; } - ai = c1 - s1; - return ai; - } - ai = -c2; - aa = sqrt(aa); - if (az > aa) { s1 = z * z * 0.5; } - ai += s1 * c1; - return ai; - } - aa = az*az; - if (aa >= tol/az) { - trm1 = 1.0; - trm2 = 1.0; - atrm = 1.0; - z3 = z*z*z; - az3 = az * aa; - ak = 2.0 + fid; - bk = 3.0 - fid - fid; - ck = 4.0 - fid; - dk = 3.0 + fid + fid; - d1 = ak * dk; - d2 = bk * ck; - ad = (d1 > d2 ? d2 : d1); - ak = 24.0 + 9.0*fid; - bk = 30.0 - 9.0*fid; - for (int k = 1; k < 26; k++) - { - trm1 *= z3/d1; - s1 += trm1; - trm2 *= z3/d2; - s2 += trm2; - atrm *= az3 / ad; - d1 += ak; - d2 += bk; - ad = (d1 > d2 ? d2 : d1); - if (atrm < tol*ad) { break; } - ak += 18.0; - bk += 18.0; - } - } - if (id != 1) { - ai = s1*c1 - z*s2*c2; - if (kode == 1) { return ai; } - zta = z*std::sqrt(z)*tth; - ai *= std::exp(zta); - return ai; - } - ai = -s2*c2; - if (az > tol) { ai += z*z*s1*c1/(1. + fid); } - if (kode == 1) { return ai; } - zta = z*std::sqrt(z)*tth; - return ai*std::exp(zta); - } - // - // CASE FOR ABS(Z) > 1.0 - // - fnu = (1.0 + fid) / 3.0; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM) < EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM) > EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = (aa > 18.0 ? 18.0 : aa); - aa *= 2.303; - alim = elim + (-aa > -41.45 ? -aa : -41.45); - rl = 1.2*dig + 3.0; - alaz = log(az); - // - // TEST FOR RANGE - // - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = (aa > bb ? bb : aa); - aa = pow(aa, tth); - if (az > aa) { - *ierr = 4; - *nz = 0; - return 0.; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - csq = std::sqrt(z); - zta = z * csq * tth; - // - // RE(ZTA) <= 0 WHEN RE(Z) < 0, ESPECIALLY WHEN IM(Z) IS SMALL - // - iflag = 0; - sfac = 1.0; - zi = std::imag(z); - zr = std::real(z); - ak = std::imag(zta); - if (zr < 0.0) { - bk = std::real(zta); - ck = -fabs(bk); - zta = std::complex(ck, ak); - } - if ((zi == 0.0) && (zr <= 0.0)) { - zta = std::complex(0.0, ak); - } - aa = std::real(zta); - if ((aa < 0.0) || (zr <= 0.0)) { - if (kode != 2) { - // - // OVERFLOW TEST - // - if (aa <= -alim) { - aa = -aa + 0.25*alaz; - iflag = 1; - sfac = tol; - if (aa > elim) { - /* 270 */ - *nz = 0; - *ierr = 2; - return ai; - } - } - } - // - // CBKNU AND CACAI RETURN EXP(ZTA)*K(FNU,ZTA) ON KODE=2 - // - mr = 1; - if (zi < 0.0) { mr = -1; } - nn = acai(zta, fnu, kode, mr, 1, &cy[0], rl, tol, elim, alim); - if (nn < 0) { - if (nn == -1) { - *nz = 1; - return 0.; - } else { - *nz = 0; - *ierr = 5; - return 0.; - } - } - *nz += nn; - } else { - if (kode != 2) { - // - // UNDERFLOW TEST - // - if (aa >= alim) { - aa = -aa - 0.25 * alaz; - iflag = 2; - sfac = 1.0 / tol; - if (aa < -elim) { - *nz = 1; - return 0.; - } - } - } - *nz = bknu(zta, fnu, kode, 1, &cy[0], tol, elim, alim); - } - s1 = cy[0]*coef; - - if (iflag == 0) { - if (id != 1) { - return csq *s1; - } - return (-z*s1); - } - s1 *= sfac; - if (id != 1) { - s1 *= csq; - return (s1/sfac); - } - s1 *= -z; - return (s1/sfac); -} - - -inline int asyi( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double rl, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZASYI - //***REFER TO ZBESI,ZBESK - // - // ZASYI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY - // MEANS OF THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z) IN THE - // REGION CABS(Z).GT.MAX(RL,FNU*FNU/2). NZ=0 IS A NORMAL RETURN. - // NZ.LT.0 INDICATES AN OVERFLOW ON KODE=1. - // - //***ROUTINES CALLED D1MACH,AZABS,ZDIV,AZEXP,ZMLT,AZSQRT - //***END PROLOGUE ZASYI - - std::complex ak1, ck, cs1, cs2, cz, dk, ez, p1, rz, s2; - double aa, acz, aez, ak, arg, arm, atol, az, bb, bk, dfnu; - double dnu2, fdn, rtr1, s, sgn, sqk, x, yy; - int ib, il, inu, j, jl, k, koded, m, nn; - double pi = 3.14159265358979324; - double rpi = 0.159154943091895336; /* (1 / pi) */ - int nz = 0; - az = std::abs(z); - x = std::real(z); - arm = 1e3*d1mach[0]; - rtr1 = sqrt(arm); - il = (n > 2 ? 2 : n); - dfnu = fnu + (n - il); - // OVERFLOW TEST - ak1 = std::sqrt(rpi / z); - cz = z; - if (kode == 2) { cz = std::complex(0.0, std::imag(z)); } - acz = std::real(cz); - if (fabs(acz) <= elim) { - dnu2 = dfnu + dfnu; - koded = 1; - if (!((fabs(acz) > alim) && (n > 2))) { - koded = 0; - ak1 *= std::exp(cz); - } - fdn = 0.; - if (dnu2 > rtr1) { fdn = dnu2 * dnu2; } - ez = z * 8.; - // WHEN Z IS IMAGINARY, THE ERROR TEST MUST BE MADE - // RELATIVE TO THE FIRST RECIPROCAL POWER SINCE THIS - // IS THE LEADING TERM OF THE EXPANSION FOR THE - // IMAGINARY PART. - aez = 8. * az; - s = tol / aez; - jl = (int)(rl + rl) + 2; - yy = std::imag(z); - p1 = 0.; - if (yy != 0.) { - inu = (int)fnu; - arg = (fnu - inu) * pi; - inu += n - il; - ak = -sin(arg); - bk = cos(arg); - if (yy < 0.) { bk = -bk; } - p1 = std::complex(ak, bk); - if (inu % 2 == 1) { p1 = -p1; } - } - for (int k = 1; k < (il+1); k++) - { - sqk = fdn - 1.; - atol = s*fabs(sqk); - sgn = 1.; - cs1 = 1.; - cs2 = 1.; - ck = 1.; - ak = 0.; - aa = 1.; - bb = aez; - dk = ez; - j = 1; - for (j = 1; j < (jl+1); j++) - { - ck *= sqk / dk; - cs2 += ck; - sgn = -sgn; - cs1 += ck*sgn; - dk += ez; - aa *= fabs(sqk) / bb; - bb += aez; - ak += 8.; - sqk -= ak; - if (aa <= atol) { break; } - } - if ((j == (jl+1)) && (aa > atol)) { return -2; } - - /* 50 */ - s2 = cs1; - if (x + x < elim) { s2 += p1*cs2*std::exp(-z-z); } - fdn += 8. * dfnu + 4.; - p1 = -p1; - m = n - il + k; - y[m - 1] = s2 * ak1; - } - if (n <= 2) { return nz; } - nn = n; - k = nn - 2; - ak = k; - rz = 2. / z; - ib = 3; - for (int i = ib; i < (nn+1); i++) - { - y[k-1] = (ak + fnu)*rz*y[k] + y[k+1]; - ak -= 1.; - k -=1; - } - if (koded == 0) { return nz; } - ck = std::exp(cz); - for (int i = 0; i < (nn + 1); i++) { y[i] *= ck; } - /* 90 */ - return nz; - } - /* 100 */ - return -1; -} - - -inline int besh( - std::complex z, - double fnu, - int kode, - int m, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESH - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS H-BESSEL FUNCTIONS,BESSEL FUNCTIONS OF COMPLEX ARGUMENT, - // BESSEL FUNCTIONS OF THIRD KIND,HANKEL FUNCTIONS - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE H-BESSEL FUNCTIONS OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZBESH COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // HANKEL (BESSEL) FUNCTIONS CY(J)=H(M,FNU+J-1,Z) FOR KINDS M=1 - // OR 2, REAL, NONNEGATIVE ORDERS FNU+J-1, J=1,...,N, AND COMPLEX - // Z.NE.std::complex(0.0,0.0) IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. - // ON KODE=2, ZBESH RETURNS THE SCALED HANKEL FUNCTIONS - // - // CY(I)=EXP(-MM*Z*I)*H(M,FNU+J-1,Z) MM=3-2*M, I**2=-1. - // - // WHICH REMOVES THE EXPONENTIAL BEHAVIOR IN BOTH THE UPPER AND - // LOWER HALF PLANES. DEFINITIONS AND NOTATION ARE FOUND IN THE - // NBS HANDBOOK OF MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PT.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL H FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(J)=H(M,FNU+J-1,Z), J=1,...,N - // = 2 RETURNS - // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) - // J=1,...,N , I**2=-1 - // M - KIND OF HANKEL FUNCTION, M=1 OR 2 - // N - NUMBER OF MEMBERS IN THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(J)=H(M,FNU+J-1,Z) OR - // CY(J)=H(M,FNU+J-1,Z)*EXP(-I*Z*(3-2M)) J=1,...,N - // DEPENDING ON KODE, I**2=-1. - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE - // TO UNDERFLOW, CY(J)=std::complex(0.0D0,0.0D0) - // J=1,...,NZ WHEN Y.GT.0.0 AND M=1 OR - // Y.LT.0.0 AND M=2. FOR THE COMPLMENTARY - // HALF PLANES, NZ STATES ONLY THE NUMBER - // OF UNDERFLOWS. - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU TOO - // LARGE OR CABS(Z) TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE RELATION - // - // H(M,FNU,Z)=(1/MP)*EXP(-MP*FNU)*K(FNU,Z*EXP(-MP)) - // MP=MM*HPI*I, MM=3-2*M, HPI=PI/2, I**2=-1 - // - // FOR M=1 OR 2 WHERE THE K BESSEL FUNCTION IS COMPUTED FOR THE - // RIGHT HALF PLANE RE(Z).GE.0.0. THE K FUNCTION IS CONTINUED - // TO THE LEFT HALF PLANE BY THE RELATION - // - // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) - // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 - // - // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // EXPONENTIAL DECAY OF H(M,FNU,Z) OCCURS IN THE UPPER HALF Z - // PLANE FOR M=1 AND THE LOWER HALF Z PLANE FOR M=2. EXPONENTIAL - // GROWTH OCCURS IN THE COMPLEMENTARY HALF PLANES. SCALING - // BY EXP(-MM*Z*I) REMOVES THE EXPONENTIAL BEHAVIOR IN THE - // WHOLE Z PLANE FOR Z TO INFINITY. - // - // FOR NEGATIVE ORDERS,THE FORMULAE - // - // H(1,-FNU,Z) = H(1,FNU,Z)*CEXP( PI*FNU*I) - // H(2,-FNU,Z) = H(2,FNU,Z)*CEXP(-PI*FNU*I) - // I**2=-1 - // - // CAN BE USED. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0D-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,AZABS,I1MACH,D1MACH - //***END PROLOGUE ZBESH - - std::complex zn, zt, csgn; - double aa, alim, aln, arg, az, cpn, dig, elim, fmm, fn, fnul, - rhpi, rl, r1m5, sgn, spn, tol, ufl, xn, xx, yn, yy, - bb, ascle, rtol, atol; - int i, inu, inuh, ir, k, k1, k2, mm, mr, nn, nuf, nw, nz; - - double hpi = 1.57079632679489662; /* 0.5 PI */ - - nz = 0; - xx = std::real(z); - yy = std::imag(z); - *ierr = 0; - - if ((xx == 0.0) && (yy == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if ((m < 1) || (m > 2)) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - nn = n; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2*dig + 3.0; - fn = fnu + (nn - 1); - mm = 3 - m - m; - fmm = mm; - zn = z * std::complex(0.0, -fmm); - xn = std::real(zn); - yn = std::imag(zn); - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - bb = d1mach[1] * 0.5; - aa = fmin(0.5 / tol, bb); - if ((az > aa) || (fn > aa)){ *ierr =4; return 0; } /* GO TO 260 */ - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE - // - ufl = d1mach[0] * 1.0e3; - if (az < ufl) { *ierr = 2; return 0; } /* GO TO 230 */ - if (fnu <= fnul) { - // - // Untangling GOTOs with explicit conditions - // - if ((fn > 1.0) && (fn <= 2.0) && (az <= tol)) { - /* Failed through all checks */ - arg = 0.5 * az; - aln = -fn * log(arg); - if (aln > elim) { *ierr = 2; return 0; } /* GO TO 230 */ - /* GO TO 70 */ - } else if ((fn > 1.0) && (fn <= 2.0) && (az > tol)) { - /* Failed all but the az > tol hence do nothing and GO TO 70 */ - } else if ((fn > 1.0) && (fn > 2.0)) { - /* GO TO 60 */ - nuf = uoik(zn, fnu, kode, 2, nn, cy, tol, elim, alim); - if (nuf < 0) { *ierr = 2; return 0; } /* GO TO 230 */ - nz += nuf; - nn -= nuf; - // - // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK - // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I - // - if (nn == 0) { - /* GO TO 140 */ - if (xn < 0.0) { *ierr = 2; return 0; } /* GO TO 230 */ - return nz; - } - /* GO TO 70 */ - } else { - /* Passed the first hence GO TO 70 */ - } - - /* GO TO 70 */ - // - // More GOTOs untangling - // - if ((xn < 0.0) || ((xn == 0.0) && (yn < 0.0) && (m == 2))) { - /* GO TO 80 */ - mr = -mm; - nw = acon(zn, fnu, kode, mr, nn, cy, rl, fnul, tol, elim, alim); - if (nw < 0) { - /* GO TO 240 */ - if (nw == -1) { *ierr = 2; return 0; } /* GO TO 230 */ - *ierr = 5; - return 0; - } - nz = nw; - /* GO TO 110 */ - } else { - // - // RIGHT HALF PLANE COMPUTATION, XN >= 0. .AND. (XN.NE.0. .OR. - // YN >= 0. .OR. M=1) - // - nz = bknu(zn, fnu, kode, nn, cy, tol, elim, alim); - /* GO TO 110 */ - } - } else { - /* GO TO 90 */ - // - // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU > FNUL - // - mr = 0; - if (!((xn >= 0.0) && ((xn != 0.0) || (yn >= 0.0) || (m != 2)))) { - mr = -mm; - if ((xn == 0.0) && (yn < 0.0)) { zn = -zn; } - } - /* GO TO 100 */ - nw = bunk(zn, fnu, kode, mr, nn, cy, tol, elim, alim); - if (nw < 0) { - /* GO TO 240 */ - if (nw == -1) { *ierr = 2; return 0; } /* GO TO 230 */ - *ierr = 5; - return 0; - } - nz += nw; - } - /* 110 */ - // - // H(M,FNU,Z) = -FMM*(I/HPI)*(ZT**FNU)*K(FNU,-Z*ZT) - // ZT=EXP(-FMM*HPI*I) = std::complex(0.0,-FMM), FMM=3-2*M, M=1,2 - // - sgn = (-fmm < 0 ? -hpi : hpi); - // - // CALCULATE EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - inuh = inu / 2; - ir = inu - 2 * inuh; - arg = (fnu - (inu - ir)) * sgn; - rhpi = 1.0 / sgn; - cpn = rhpi * cos(arg); - spn = -rhpi * sin(arg); - csgn = std::complex(spn, cpn); - if (inuh % 2 == 1) { csgn = -csgn; } - zt = std::complex(0.0, -fmm); - rtol = 1.0 / tol; - ascle = ufl * rtol; - for (i = 1; i < (nn+1); i++) { - zn = cy[i-1]; - atol = 1.0; - if (fmax(fabs(std::real(zn)), fabs(std::imag(zn))) <= ascle) { - zn *= rtol; - atol = tol; - } - zn *= csgn; - cy[i-1] = zn * atol; - csgn *= zt; - } - return nz; -} - - -inline int besi( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESI - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS I-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, - // MODIFIED BESSEL FUNCTION OF THE FIRST KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE I-BESSEL FUNCTIONS OF COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, ZBESI COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(J)=I(FNU+J-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, ZBESI RETURNS THE SCALED - // FUNCTIONS - // - // CY(J)=EXP(-ABS(X))*I(FNU+J-1,Z) J = 1,...,N , X=REAL(Z) - // - // WITH THE EXPONENTIAL GROWTH REMOVED IN BOTH THE LEFT AND - // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL I FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(J)=I(FNU+J-1,Z), J=1,...,N - // = 2 RETURNS - // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)), J=1,...,N - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(J)=I(FNU+J-1,Z) OR - // CY(J)=I(FNU+J-1,Z)*EXP(-ABS(X)) J=1,...,N - // DEPENDING ON KODE, X=REAL(Z) - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET TO ZERO - // TO UNDERFLOW, CY(J)=std::complex(0.0D0,0.0D0) - // J = N-NZ+1,...,N - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) TOO - // LARGE ON KODE=1 - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE POWER SERIES FOR - // SMALL CABS(Z), THE ASYMPTOTIC EXPANSION FOR LARGE CABS(Z), - // THE MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN AND A - // NEUMANN SERIES FOR IMTERMEDIATE MAGNITUDES, AND THE - // UNIFORM ASYMPTOTIC EXPANSIONS FOR I(FNU,Z) AND J(FNU,Z) - // FOR LARGE ORDERS. BACKWARD RECURRENCE IS USED TO GENERATE - // SEQUENCES OR REDUCE ORDERS WHEN NECESSARY. - // - // THE CALCULATIONS ABOVE ARE DONE IN THE RIGHT HALF PLANE AND - // CONTINUED INTO THE LEFT HALF PLANE BY THE FORMULA - // - // I(FNU,Z*EXP(M*PI)) = EXP(M*PI*FNU)*I(FNU,Z) REAL(Z).GT.0.0 - // M = +I OR -I, I**2=-1 - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // I(-FNU,Z) = I(FNU,Z) + (2/PI)*SIN(PI*FNU)*K(FNU,Z) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE - // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE - // INTEGER,THE MAGNITUDE OF I(-FNU,Z)=I(FNU,Z) IS A LARGE - // NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, - // K(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF - // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY - // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN - // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, - // LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,I1MACH,D1MACH - //***END PROLOGUE ZBESI - - std::complex csgn, zn; - double aa, alim, arg, atol, ascle, az, bb, dig, elim, fn, fnul, rl, rtol,\ - r1m5, tol, xx, yy; - int i, inu, k, k1, k2, nn, nz; - double pi = 3.14159265358979324; - - *ierr = 0; - nz = 0; - if (fnu < 0.0) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - xx = std::real(z); - yy = std::imag(z); - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - rl = 1.2 * dig + 3.0; - fnul = 10.0 + 6.0 * (dig - 3.0); - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - fn = fnu + (n - 1); - aa = 0.5 / tol; - bb = i1mach[8]*0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - zn = z; - csgn = 1.0; - if (xx < 0.0) { - zn = -z; - // - // CALCULATE CSGN=EXP(FNU*PI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - inu = (int)fnu; - arg = (fnu - inu)*pi; - if (yy < 0.0) { arg = -arg; } - csgn = std::complex(cos(arg), sin(arg)); - if (inu % 2 == 1) { csgn = -csgn; } - } - /* 40 */ - nz = binu(zn, fnu, kode, n, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -2) { - *ierr = 5; - return 0; - } - *ierr = 2; - return 0; - } - if (xx > 0.0) { return nz; } - // - // ANALYTIC CONTINUATION TO THE LEFT HALF PLANE - // - nn = n - nz; - if (nn == 0) { return nz; } - rtol = 1.0 / tol; - ascle = d1mach[0]*rtol*1e3; - for (i = 1; i < (nn+1); i++) - { - zn = cy[i-1]; - atol = 1.0; - if (fmax(fabs(std::real(zn)), fabs(std::imag(zn))) <= ascle) { - zn *= rtol; - atol = tol; - } - cy[i-1] = atol*(zn*csgn); - csgn = -csgn; - } - *ierr = 0; - return nz; -} - - -inline int besj( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESJ - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS J-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, - // BESSEL FUNCTION OF FIRST KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE J-BESSEL FUNCTION OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, CBESJ COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(I)=J(FNU+I-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESJ RETURNS THE SCALED - // FUNCTIONS - // - // CY(I)=EXP(-ABS(Y))*J(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) - // - // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND - // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL J FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=J(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)), I=1,...,N - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=J(FNU+I-1,Z) OR - // CY(I)=J(FNU+I-1,Z)EXP(-ABS(Y)) I=1,...,N - // DEPENDING ON KODE, Y=AIMAG(Z). - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW, - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , LAST NZ COMPONENTS OF CY SET ZERO DUE - // TO UNDERFLOW, CY(I)=std::complex(0.0D0,0.0D0), - // I = N-NZ+1,...,N - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, AIMAG(Z) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE FORMULA - // - // J(FNU,Z)=EXP( FNU*PI*I/2)*I(FNU,-I*Z) AIMAG(Z).GE.0.0 - // - // J(FNU,Z)=EXP(-FNU*PI*I/2)*I(FNU, I*Z) AIMAG(Z).LT.0.0 - // - // WHERE I**2 = -1 AND I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // J(-FNU,Z) = J(FNU,Z)*COS(PI*FNU) - Y(FNU,Z)*SIN(PI*FNU) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO INTEGERS, THE - // THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE POSITIVE - // INTEGER,THE MAGNITUDE OF J(-FNU,Z)=J(FNU,Z)*COS(PI*FNU) IS A - // LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS NOT AN INTEGER, - // Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A LARGE POSITIVE POWER OF - // TEN AND THE MOST THAT THE SECOND TERM CAN BE REDUCED IS BY - // UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, WIDE CHANGES CAN - // OCCUR WITHIN UNIT ROUNDOFF OF A LARGE INTEGER FOR FNU. HERE, - // LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,I1MACH,D1MACH - //***END PROLOGUE ZBESJ - - std::complex ci, csgn, zn; - double aa, alim, arg, dig, elim, fnul, rl, r1, r1m5, r2, - tol, yy, az, fn, bb, ascle, rtol, atol; - int i, inu, inuh, ir, k1, k2, nl, nz, k; - double hpi = 1.570796326794896619; - - *ierr = 0; - nz = 0; - if (fnu < 0.0) *ierr = 1; - if (kode < 1 || kode > 2) *ierr = 1; - if (n < 1) *ierr = 1; - if (*ierr != 0) return nz; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2*dig + 3.0; - // - // TEST FOR PROPER RANGE - // - yy = std::imag(z); - az = std::abs(z); - fn = fnu + (n - 1); - - aa = 0.5 / tol; - bb = d1mach[1] * 0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // CALCULATE CSGN = EXP(FNU*HPI*I) TO MINIMIZE LOSSES OF SIGNIFICANCE - // WHEN FNU IS LARGE - // - ci.imag(1); - inu = (int)fnu; - inuh = inu / 2; - ir = inu - 2*inuh; - arg = (fnu - (inu - ir)) * hpi; - r1 = cos(arg); - r2 = sin(arg); - csgn = std::complex(r1, r2); - if (inuh % 2 == 1) { csgn = -csgn; } - // - // ZN IS IN THE RIGHT HALF PLANE - // - zn = -z * ci; - if (yy < 0.0) { - zn = -zn; - csgn = conj(csgn); - ci = conj(ci); - } - nz = binu(zn, fnu, kode, n, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -2) { *ierr = 5; return 0; } - *ierr = 2; - return 0; - } - nl = n - nz; - if (nl == 0) { return nz; } - rtol = 1.0 / tol; - ascle = d1mach[0]*rtol*1e3; - for (i = 1; i < (nl+1); i++) - { - zn = cy[i-1]; - aa = std::real(zn); - bb = std::imag(zn); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - zn *= rtol; - atol = tol; - } - cy[i-1] = atol*(zn * csgn); - csgn = csgn * ci; - } - return nz; -} - - -inline int besk( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESK - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS K-BESSEL FUNCTION,COMPLEX BESSEL FUNCTION, - // MODIFIED BESSEL FUNCTION OF THE SECOND KIND, - // BESSEL FUNCTION OF THE THIRD KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE K-BESSEL FUNCTIONS OF COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // - // ON KODE=1, CBESK COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(J)=K(FNU+J-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+J-1, J=1,...,N AND COMPLEX Z.NE.std::complex(0.0,0.0) - // IN THE CUT PLANE -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESK - // RETURNS THE SCALED K FUNCTIONS, - // - // CY(J)=EXP(Z)*K(FNU+J-1,Z) , J=1,...,N, - // - // WHICH REMOVE THE EXPONENTIAL BEHAVIOR IN BOTH THE LEFT AND - // RIGHT HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND - // NOTATION ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL - // FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL K FUNCTION, FNU.GE.0.0D0 - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=K(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=K(FNU+I-1,Z), I=1,...,N OR - // CY(I)=K(FNU+I-1,Z)*EXP(Z), I=1,...,N - // DEPENDING ON KODE - // NZ - NUMBER OF COMPONENTS SET TO ZERO DUE TO UNDERFLOW. - // NZ= 0 , NORMAL RETURN - // NZ.GT.0 , FIRST NZ COMPONENTS OF CY SET TO ZERO DUE - // TO UNDERFLOW, CY(I)=std::complex(0.0D0,0.0D0), - // I=1,...,N WHEN X.GE.0.0. WHEN X.LT.0.0 - // NZ STATES ONLY THE NUMBER OF UNDERFLOWS - // IN THE SEQUENCE. - // - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS - // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // EQUATIONS OF THE REFERENCE ARE IMPLEMENTED FOR SMALL ORDERS - // DNU AND DNU+1.0 IN THE RIGHT HALF PLANE X.GE.0.0. FORWARD - // RECURRENCE GENERATES HIGHER ORDERS. K IS CONTINUED TO THE LEFT - // HALF PLANE BY THE RELATION - // - // K(FNU,Z*EXP(MP)) = EXP(-MP*FNU)*K(FNU,Z)-MP*I(FNU,Z) - // MP=MR*PI*I, MR=+1 OR -1, RE(Z).GT.0, I**2=-1 - // - // WHERE I(FNU,Z) IS THE I BESSEL FUNCTION. - // - // FOR LARGE ORDERS, FNU.GT.FNUL, THE K FUNCTION IS COMPUTED - // BY MEANS OF ITS UNIFORM ASYMPTOTIC EXPANSIONS. - // - // FOR NEGATIVE ORDERS, THE FORMULA - // - // K(-FNU,Z) = K(FNU,Z) - // - // CAN BE USED. - // - // CBESK ASSUMES THAT A SIGNIFICANT DIGIT SINH(X) FUNCTION IS - // AVAILABLE. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983. - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZACON,ZBKNU,ZBUNK,ZUOIK,AZABS,I1MACH,D1MACH - //***END PROLOGUE ZBESK - - double xx = std::real(z); - double yy = std::imag(z); - double aa, alim, aln, arg, az, dig, elim, fn, fnul, rl, r1m5, tol, ufl, bb; - int k, k1, k2, mr, nn, nuf, nw, nz; - - *ierr = 0; - nz = 0; - - if ((yy == 0.0) && (xx == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if (kode < 1 || kode > 2) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - - nn = n; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM).LT.EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM).GT.EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU - // - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - fnul = 10.0 + 6.0 * (dig - 3.0); - rl = 1.2 * dig + 3.0; - // - // TEST FOR PROPER RANGE - // - az = std::abs(z); - fn = fnu + (nn - 1); - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = fmin(aa, bb); - if ((az > aa) || (fn > aa)) { - *ierr = 4; - return 0; - } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - if (fn > aa) { *ierr = 3; } - // - // OVERFLOW TEST ON THE LAST MEMBER OF THE SEQUENCE - // - ufl = d1mach[0] * 1.0E+3; - if (az < ufl) { - *ierr = 2; - return 0; - } - if (fnu <= fnul) { - if (fn > 1.0) { - if (fn <= 2.0) { - if (az <= tol) { - arg = 0.5 * az; - aln = -fn * log(arg); - if (aln > elim) { *ierr = 2; return 0; } - } - /* GO TO 60 */ - } else { - nuf = uoik(z, fnu, kode, 2, nn, cy, tol, elim, alim); - if (nuf < 0) { *ierr = 2; return 0; } - nz += nuf; - nn -= nuf; - // - // HERE NN=N OR NN=0 SINCE NUF=0,NN, OR -1 ON RETURN FROM CUOIK - // IF NUF=NN, THEN CY(I)=CZERO FOR ALL I - // - if (nn == 0) { - if (xx < 0.0) { *ierr = 2; return 0; } - return nz; - } - } - } - - /* 60 */ - if (xx >= 0.0) { - // - // RIGHT HALF PLANE COMPUTATION, REAL(Z) >= 0. - // - nw = bknu(z, fnu, kode, nn, cy, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - return nw; - } - /* 70 */ - // - // LEFT HALF PLANE COMPUTATION - // PI/2 < ARG(Z) <= PI AND -PI < ARG(Z) < -PI/2. - // - if (nz != 0) { *ierr = 2; return 0; } - mr = 1; - if (yy < 0.0) { mr = -1; } - nw = acon(z, fnu, kode, mr, nn, cy, rl, fnul, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - return nw; - } - - /* 80 */ - // - // UNIFORM ASYMPTOTIC EXPANSIONS FOR FNU > FNUL - // - mr = 0; - if (xx < 0.0) { - mr = 1; - if (yy < 0.0) { mr = -1; } - } - nw = bunk(z, fnu, kode, mr, nn, cy, tol, elim, alim); - if (nw < 0) { - if (nw == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0; - } - nz += nw; - return nz; -} - - -inline int besy( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - int *ierr -) { - - //***BEGIN PROLOGUE ZBESY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS Y-BESSEL FUNCTION,BESSEL FUNCTION OF COMPLEX ARGUMENT, - // BESSEL FUNCTION OF SECOND KIND - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE Y-BESSEL FUNCTION OF A COMPLEX ARGUMENT - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // - // ON KODE=1, CBESY COMPUTES AN N MEMBER SEQUENCE OF COMPLEX - // BESSEL FUNCTIONS CY(I)=Y(FNU+I-1,Z) FOR REAL, NONNEGATIVE - // ORDERS FNU+I-1, I=1,...,N AND COMPLEX Z IN THE CUT PLANE - // -PI.LT.ARG(Z).LE.PI. ON KODE=2, CBESY RETURNS THE SCALED - // FUNCTIONS - // - // CY(I)=EXP(-ABS(Y))*Y(FNU+I-1,Z) I = 1,...,N , Y=AIMAG(Z) - // - // WHICH REMOVE THE EXPONENTIAL GROWTH IN BOTH THE UPPER AND - // LOWER HALF PLANES FOR Z TO INFINITY. DEFINITIONS AND NOTATION - // ARE FOUND IN THE NBS HANDBOOK OF MATHEMATICAL FUNCTIONS - // (REF. 1). - // - // INPUT ZR,ZI,FNU ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI), Z.NE.std::complex(0.0D0,0.0D0), - // -PI.LT.ARG(Z).LE.PI - // FNU - ORDER OF INITIAL Y FUNCTION, FNU.GE.0.0D0 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // CY(I)=Y(FNU+I-1,Z), I=1,...,N - // = 2 RETURNS - // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)), I=1,...,N - // WHERE Y=AIMAG(Z) - // N - NUMBER OF MEMBERS OF THE SEQUENCE, N.GE.1 - // CWRKR, - DOUBLE PRECISION WORK VECTORS OF DIMENSION AT - // CWRKI AT LEAST N - // - // OUTPUT CYR,CYI ARE DOUBLE PRECISION - // CYR,CYI- DOUBLE PRECISION VECTORS WHOSE FIRST N COMPONENTS - // CONTAIN REAL AND IMAGINARY PARTS FOR THE SEQUENCE - // CY(I)=Y(FNU+I-1,Z) OR - // CY(I)=Y(FNU+I-1,Z)*EXP(-ABS(Y)) I=1,...,N - // DEPENDING ON KODE. - // NZ - NZ=0 , A NORMAL RETURN - // NZ.GT.0 , NZ COMPONENTS OF CY SET TO ZERO DUE TO - // UNDERFLOW (GENERALLY ON KODE=2) - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, FNU IS - // TOO LARGE OR CABS(Z) IS TOO SMALL OR BOTH - // IERR=3, CABS(Z) OR FNU+N-1 LARGE - COMPUTATION DONE - // BUT LOSSES OF SIGNIFCANCE BY ARGUMENT - // REDUCTION PRODUCE LESS THAN HALF OF MACHINE - // ACCURACY - // IERR=4, CABS(Z) OR FNU+N-1 TOO LARGE - NO COMPUTA- - // TION BECAUSE OF COMPLETE LOSSES OF SIGNIFI- - // CANCE BY ARGUMENT REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // IERR=6, Memory allocation failed. - // - //***LONG DESCRIPTION - // - // THE COMPUTATION IS CARRIED OUT BY THE FORMULA - // - // Y(FNU,Z)=0.5*(H(1,FNU,Z)-H(2,FNU,Z))/I - // - // WHERE I**2 = -1 AND THE HANKEL BESSEL FUNCTIONS H(1,FNU,Z) - // AND H(2,FNU,Z) ARE CALCULATED IN CBESH. - // - // FOR NEGATIVE ORDERS,THE FORMULA - // - // Y(-FNU,Z) = Y(FNU,Z)*COS(PI*FNU) + J(FNU,Z)*SIN(PI*FNU) - // - // CAN BE USED. HOWEVER,FOR LARGE ORDERS CLOSE TO HALF ODD - // INTEGERS THE FUNCTION CHANGES RADICALLY. WHEN FNU IS A LARGE - // POSITIVE HALF ODD INTEGER,THE MAGNITUDE OF Y(-FNU,Z)=J(FNU,Z)* - // SIN(PI*FNU) IS A LARGE NEGATIVE POWER OF TEN. BUT WHEN FNU IS - // NOT A HALF ODD INTEGER, Y(FNU,Z) DOMINATES IN MAGNITUDE WITH A - // LARGE POSITIVE POWER OF TEN AND THE MOST THAT THE SECOND TERM - // CAN BE REDUCED IS BY UNIT ROUNDOFF FROM THE COEFFICIENT. THUS, - // WIDE CHANGES CAN OCCUR WITHIN UNIT ROUNDOFF OF A LARGE HALF - // ODD INTEGER. HERE, LARGE MEANS FNU.GT.CABS(Z). - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z OR FNU+N-1 IS - // LARGE, LOSSES OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. - // CONSEQUENTLY, IF EITHER ONE EXCEEDS U1=SQRT(0.5/UR), THEN - // LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR FLAG - // IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // IF EITHER IS LARGER THAN U2=0.5/UR, THEN ALL SIGNIFICANCE IS - // LOST AND IERR=4. IN ORDER TO USE THE INT FUNCTION, ARGUMENTS - // MUST BE FURTHER RESTRICTED NOT TO EXCEED THE LARGEST MACHINE - // INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF Z AND FNU+N-1 IS - // RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, AND U3 - // ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE PRECISION - // ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE PRECISION - // ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMITING IN - // THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT ONE CAN EXPECT - // TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, NO DIGITS - // IN SINGLE AND ONLY 7 DIGITS IN DOUBLE PRECISION ARITHMETIC. - // SIMILAR CONSIDERATIONS HOLD FOR OTHER MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBESH,I1MACH,D1MACH - //***END PROLOGUE ZBESY - - std::complex c1, c2, hci, st; - double elim, exr, exi, ey, tay, xx, yy, ascle, rtol, atol, tol, aa, bb, r1m5; - int i, k, k1, k2, nz, nz1, nz2; - - xx = std::real(z); - yy = std::imag(z); - *ierr = 0; - nz = 0; - - if ((xx == 0.0) && (yy == 0.0)) { *ierr = 1; } - if (fnu < 0.0) { *ierr = 1; } - if ((kode < 1) || (kode > 2)) { *ierr = 1; } - if (n < 1) { *ierr = 1; } - if (*ierr != 0) { return nz; } - - hci = std::complex(0.0, 0.5); - nz1 = besh(z, fnu, kode, 1, n, cy, ierr); - if ((*ierr != 0) && (*ierr != 3)) { return 0; } - - auto cwrk = std::unique_ptr[]> - {new (std::nothrow) std::complex[n]}; - if (cwrk == nullptr) { - *ierr = 6; // Memory allocation failed. - return 0; - } - - nz2 = besh(z, fnu, kode, 2, n, cwrk.get(), ierr); - if ((*ierr != 0) && (*ierr != 3)) { return 0; } - - nz = (nz1 > nz2 ? nz2 : nz1); - if (kode != 2) { - for (i = 1; i < (n+1); i++) - { - cy[i-1] = hci * (cwrk[i-1] - cy[i-1]); - } - return nz; - } - - tol = fmax(d1mach[3], 1e-18); - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - // - // ELIM IS THE APPROXIMATE EXPONENTIAL UNDER- AND OVERFLOW LIMIT - // - elim = 2.303 * (k*r1m5 - 3.0); - exr = cos(xx); - exi = sin(xx); - ey = 0.0; - tay = fabs(yy + yy); - if (tay < elim) { ey = exp(-tay); } - if (yy < 0.0) { - /* 90 */ - c1 = std::complex(exr, exi); - c2 = ey*std::complex(exr, -exi); - } else { - c1 = ey*std::complex(exr, exi); - c2 = std::complex(exr, -exi); - } - - nz = 0; - rtol = 1.0 / tol; - ascle = 1e3*d1mach[0]*rtol; - for (i = 1; i< (n+1); i++) - { - aa = std::real(cwrk[i-1]); - bb = std::imag(cwrk[i-1]); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - aa *= rtol; - bb *= rtol; - atol = tol; - } - - st = std::complex(aa, bb) * c2 * atol; - aa = std::real(cy[i-1]); - bb = std::imag(cy[i-1]); - atol = 1.0; - if (fmax(fabs(aa), fabs(bb)) <= ascle) { - aa *= rtol; - bb *= rtol; - atol = tol; - } - - st -= std::complex(aa, bb) * c1 * atol; - cy[i-1] = st*hci; - if ((st == 0.0) && (ey == 0.0)) { nz += 1; } - } - - return nz; -} - - -inline int binu( - std::complex z, - double fnu, - int kode, - int n, - std::complex *cy, - double rl, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBINU - //***REFER TO ZBESH,ZBESI,ZBESJ,ZBESK,ZAIRY,ZBIRY - // - // ZBINU COMPUTES THE I FUNCTION IN THE RIGHT HALF Z PLANE - // - //***ROUTINES CALLED AZABS,ZASYI,ZBUNI,ZMLRI,ZSERI,ZUOIK,ZWRSK - //***END PROLOGUE ZBINU - - std::complex cw[2] = { 0. }; - double az, dfnu; - int inw, nlast, nn, nui, nw, nz; - - nz = 0; - az = std::abs(z); - nn = n; - dfnu = fnu + n - 1; - if ((az <= 2.) || (az*az*0.25 <= (dfnu + 1.0))) { - /* GOTO 10 */ - nw = seri(z,fnu, kode, n, cy, tol, elim, alim); - inw = abs(nw); - nz += inw; - nn -= inw; - if (nn == 0) { return nz; } - if (nw >= 0) { return nz; } - dfnu = fnu + nn - 1; - } - /* GOTO 30 conditions*/ - // - // ASYMPTOTIC EXPANSION FOR LARGE Z - // - if (az < rl) { - /* 40 */ - if (dfnu <= 1.0) { - /* 70 */ - // - // MILLER ALGORITHM NORMALIZED BY THE SERIES - // - nw = mlri(z, fnu, kode, n, cy, tol); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; - } - /* GO TO 50 */ - } else { - if ((dfnu <= 1.0) || (az+az >= dfnu*dfnu)) { - /* 30 */ - // - // ASYMPTOTIC EXPANSION FOR LARGE Z - // - nw = asyi(z, fnu, kode, n, cy, rl, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; - } - /* GO TO 50 */ - } - /* 50 */ - // - // OVERFLOW AND UNDERFLOW TEST ON I SEQUENCE FOR MILLER ALGORITHM - // - nw = uoik(z, fnu, kode, 1, nn, cy, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - nz += nw; - nn -= nw; - if (nn == 0) { return nz; } - dfnu = fnu + (nn -1); - /* GOTO 110s handled here */ - if ((dfnu > fnul) || (az > fnul)) { - // - // INCREMENT FNU+NN-1 UP TO FNUL, COMPUTE AND RECUR BACKWARD - // - nui = (int)(fnul-dfnu) + 1; - nui = (nui > 0 ? nui : 0); - nw = buni(z, fnu, kode, nn, cy, nui, &nlast, fnul, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - nz += nw; - if (nlast == 0) { return nz; } - nn = nlast; - } - /* 60 */ - if (az <= rl) { - /* 70 */ - nw = mlri(z, fnu, kode, n, cy, tol); - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - return nz; - } - /* 80 */ - // - // MILLER ALGORITHM NORMALIZED BY THE WRONSKIAN - // - // - // OVERFLOW TEST ON K FUNCTIONS USED IN WRONSKIAN - // - nw = uoik(z, fnu, kode, 2, 2, cw, tol, elim, alim); - if (nw < 0) { - nz = nn; - /* 90 */ - for (int i=0; i < nn; i++) { cy[i] = 0.0; } - return nz; - } - /* 100 */ - if (nw > 0) { - return -1; - } - nw = wrsk(z, fnu, kode, nn, cy, cw, tol, elim, alim); - if (nw < 0) { - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - return nz; -} - - -inline std::complex biry( - std::complex z, - int id, - int kode, - int *ierr -) { - - //***BEGIN PROLOGUE ZBIRY - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 890801 (YYMMDD) - //***CATEGORY NO. B5K - //***KEYWORDS AIRY FUNCTION,BESSEL FUNCTIONS OF ORDER ONE THIRD - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE AIRY FUNCTIONS BI(Z) AND DBI(Z) FOR COMPLEX Z - //***DESCRIPTION - // - // ***A DOUBLE PRECISION ROUTINE*** - // ON KODE=1, CBIRY COMPUTES THE COMPLEX AIRY FUNCTION BI(Z) OR - // ITS DERIVATIVE DBI(Z)/DZ ON ID=0 OR ID=1 RESPECTIVELY. ON - // KODE=2, A SCALING OPTION CEXP(-AXZTA)*BI(Z) OR CEXP(-AXZTA)* - // DBI(Z)/DZ IS PROVIDED TO REMOVE THE EXPONENTIAL BEHAVIOR IN - // BOTH THE LEFT AND RIGHT HALF PLANES WHERE - // ZTA=(2/3)*Z*CSQRT(Z)=std::complex(XZTA,YZTA) AND AXZTA=ABS(XZTA). - // DEFINITIONS AND NOTATION ARE FOUND IN THE NBS HANDBOOK OF - // MATHEMATICAL FUNCTIONS (REF. 1). - // - // INPUT ZR,ZI ARE DOUBLE PRECISION - // ZR,ZI - Z=std::complex(ZR,ZI) - // ID - ORDER OF DERIVATIVE, ID=0 OR ID=1 - // KODE - A PARAMETER TO INDICATE THE SCALING OPTION - // KODE= 1 RETURNS - // BI=BI(Z) ON ID=0 OR - // BI=DBI(Z)/DZ ON ID=1 - // = 2 RETURNS - // BI=CEXP(-AXZTA)*BI(Z) ON ID=0 OR - // BI=CEXP(-AXZTA)*DBI(Z)/DZ ON ID=1 WHERE - // ZTA=(2/3)*Z*CSQRT(Z)=std::complex(XZTA,YZTA) - // AND AXZTA=ABS(XZTA) - // - // OUTPUT BIR,BII ARE DOUBLE PRECISION - // BIR,BII- COMPLEX ANSWER DEPENDING ON THE CHOICES FOR ID AND - // KODE - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN - COMPUTATION COMPLETED - // IERR=1, INPUT ERROR - NO COMPUTATION - // IERR=2, OVERFLOW - NO COMPUTATION, REAL(Z) - // TOO LARGE ON KODE=1 - // IERR=3, CABS(Z) LARGE - COMPUTATION COMPLETED - // LOSSES OF SIGNIFCANCE BY ARGUMENT REDUCTION - // PRODUCE LESS THAN HALF OF MACHINE ACCURACY - // IERR=4, CABS(Z) TOO LARGE - NO COMPUTATION - // COMPLETE LOSS OF ACCURACY BY ARGUMENT - // REDUCTION - // IERR=5, ERROR - NO COMPUTATION, - // ALGORITHM TERMINATION CONDITION NOT MET - // - //***LONG DESCRIPTION - // - // BI AND DBI ARE COMPUTED FOR CABS(Z).GT.1.0 FROM THE I BESSEL - // FUNCTIONS BY - // - // BI(Z)=C*SQRT(Z)*( I(-1/3,ZTA) + I(1/3,ZTA) ) - // DBI(Z)=C * Z * ( I(-2/3,ZTA) + I(2/3,ZTA) ) - // C=1.0/SQRT(3.0) - // ZTA=(2/3)*Z**(3/2) - // - // WITH THE POWER SERIES FOR CABS(Z).LE.1.0. - // - // IN MOST COMPLEX VARIABLE COMPUTATION, ONE MUST EVALUATE ELE- - // MENTARY FUNCTIONS. WHEN THE MAGNITUDE OF Z IS LARGE, LOSSES - // OF SIGNIFICANCE BY ARGUMENT REDUCTION OCCUR. CONSEQUENTLY, IF - // THE MAGNITUDE OF ZETA=(2/3)*Z**1.5 EXCEEDS U1=SQRT(0.5/UR), - // THEN LOSSES EXCEEDING HALF PRECISION ARE LIKELY AND AN ERROR - // FLAG IERR=3 IS TRIGGERED WHERE UR=DMAX1(D1MACH(4),1.0D-18) IS - // DOUBLE PRECISION UNIT ROUNDOFF LIMITED TO 18 DIGITS PRECISION. - // ALSO, IF THE MAGNITUDE OF ZETA IS LARGER THAN U2=0.5/UR, THEN - // ALL SIGNIFICANCE IS LOST AND IERR=4. IN ORDER TO USE THE INT - // FUNCTION, ZETA MUST BE FURTHER RESTRICTED NOT TO EXCEED THE - // LARGEST INTEGER, U3=I1MACH(9). THUS, THE MAGNITUDE OF ZETA - // MUST BE RESTRICTED BY MIN(U2,U3). ON 32 BIT MACHINES, U1,U2, - // AND U3 ARE APPROXIMATELY 2.0E+3, 4.2E+6, 2.1E+9 IN SINGLE - // PRECISION ARITHMETIC AND 1.3E+8, 1.8E+16, 2.1E+9 IN DOUBLE - // PRECISION ARITHMETIC RESPECTIVELY. THIS MAKES U2 AND U3 LIMIT- - // ING IN THEIR RESPECTIVE ARITHMETICS. THIS MEANS THAT THE MAG- - // NITUDE OF Z CANNOT EXCEED 3.1E+4 IN SINGLE AND 2.1E+6 IN - // DOUBLE PRECISION ARITHMETIC. THIS ALSO MEANS THAT ONE CAN - // EXPECT TO RETAIN, IN THE WORST CASES ON 32 BIT MACHINES, - // NO DIGITS IN SINGLE PRECISION AND ONLY 7 DIGITS IN DOUBLE - // PRECISION ARITHMETIC. SIMILAR CONSIDERATIONS HOLD FOR OTHER - // MACHINES. - // - // THE APPROXIMATE RELATIVE ERROR IN THE MAGNITUDE OF A COMPLEX - // BESSEL FUNCTION CAN BE EXPRESSED BY P*10**S WHERE P=MAX(UNIT - // ROUNDOFF,1.0E-18) IS THE NOMINAL PRECISION AND 10**S REPRE- - // SENTS THE INCREASE IN ERROR DUE TO ARGUMENT REDUCTION IN THE - // ELEMENTARY FUNCTIONS. HERE, S=MAX(1,ABS(LOG10(CABS(Z))), - // ABS(LOG10(FNU))) APPROXIMATELY (I.E. S=MAX(1,ABS(EXPONENT OF - // CABS(Z),ABS(EXPONENT OF FNU)) ). HOWEVER, THE PHASE ANGLE MAY - // HAVE ONLY ABSOLUTE ACCURACY. THIS IS MOST LIKELY TO OCCUR WHEN - // ONE COMPONENT (IN ABSOLUTE VALUE) IS LARGER THAN THE OTHER BY - // SEVERAL ORDERS OF MAGNITUDE. IF ONE COMPONENT IS 10**K LARGER - // THAN THE OTHER, THEN ONE CAN EXPECT ONLY MAX(ABS(LOG10(P))-K, - // 0) SIGNIFICANT DIGITS; OR, STATED ANOTHER WAY, WHEN K EXCEEDS - // THE EXPONENT OF P, NO SIGNIFICANT DIGITS REMAIN IN THE SMALLER - // COMPONENT. HOWEVER, THE PHASE ANGLE RETAINS ABSOLUTE ACCURACY - // BECAUSE, IN COMPLEX ARITHMETIC WITH PRECISION P, THE SMALLER - // COMPONENT WILL NOT (AS A RULE) DECREASE BELOW P TIMES THE - // MAGNITUDE OF THE LARGER COMPONENT. IN THESE EXTREME CASES, - // THE PRINCIPAL PHASE ANGLE IS ON THE ORDER OF +P, -P, PI/2-P, - // OR -PI/2+P. - // - //***REFERENCES HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ - // AND I. A. STEGUN, NBS AMS SERIES 55, U.S. DEPT. OF - // COMMERCE, 1955. - // - // COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // AND LARGE ORDER BY D. E. AMOS, SAND83-0643, MAY, 1983 - // - // A SUBROUTINE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, SAND85- - // 1018, MAY, 1985 - // - // A PORTABLE PACKAGE FOR BESSEL FUNCTIONS OF A COMPLEX - // ARGUMENT AND NONNEGATIVE ORDER BY D. E. AMOS, TRANS. - // MATH. SOFTWARE, 1986 - // - //***ROUTINES CALLED ZBINU,AZABS,ZDIV,AZSQRT,D1MACH,I1MACH - //***END PROLOGUE ZBIRY - - std::complex bi, csq, s1, s2, trm1, trm2, zta, z3; - double aa, ad, ak, alim, atrm, az, az3, bb, bk, ck, dig, dk, d1, d2,\ - elim, fid, fmr, fnu, fnul, rl, r1m5, sfac, tol, zi, zr; - int k, k1, k2, nz; - std::complex cy[2] = { 0.0 }; - double tth = 2. / 3.; - double c1 = 0.614926627446000735150922369; /* 1/( 3**(1/6) Gamma(2/3)) */ - double c2 = 0.448288357353826357914823710; /* 3**(1/6) / Gamma(1/3) */ - double coef = 0.577350269189625764509148780; /* sqrt( 1 / 3) */ - double pi = 3.141592653589793238462643383; - - *ierr = 0; - nz = 0; - if ((id < 0) || (id > 1)) { *ierr= 1; } - if ((kode < 1) || (kode > 2)) { *ierr= 1; } - if ( *ierr != 0) { return 0.0;} - az = std::abs(z); - tol = fmax(d1mach[3], 1e-18); - fid = id; - if (az <= 1.0) { - // - // POWER SERIES FOR ABS(Z) <= 1. - // - s1 = 1.0; - s2 = 1.0; - if (az < tol) { - aa = c1*(1.0 - fid) + fid*c2; - return aa; - } - aa = az*az; - if (aa >= tol/az) { - trm1 = 1.0; - trm2 = 1.0; - atrm = 1.0; - z3 = z*z*z; - az3 = az * aa; - ak = 2.0 + fid; - bk = 3.0 - fid - fid; - ck = 4.0 - fid; - dk = 3.0 + fid + fid; - d1 = ak * dk; - d2 = bk * ck; - ad = fmin(d1,d2); - ak = 24.0 + 9.0*fid; - bk = 30.0 - 9.0*fid; - for (k = 1; k < 26; k++) - { - trm1 *= z3/d1; - s1 += trm1; - trm2 *= z3/d2; - s2 += trm2; - atrm *= az3 / ad; - d1 += ak; - d2 += bk; - ad = fmin(d1, d2); - if (atrm < tol*ad) { break; } - ak += 18.0; - bk += 18.0; - } - /* 30 */ - } - /* 40 */ - if (id != 1) { - bi = s1*c1 + z*s2*c2; - if (kode == 1) { return bi; } - zta = z*std::sqrt(z)*tth; - aa = -fabs(std::real(zta)); - bi *= exp(aa); - return bi; - } - /* 50 */ - bi = s2*c2; - if (az > tol) { bi += z*z*s1*c1/(1.0 + fid ); } - if (kode == 1) { return bi; } - zta = z*std::sqrt(z)*tth; - aa = -fabs(std::real(zta)); - bi *= exp(aa); - return bi; - } - /* 70 */ - // - // CASE FOR ABS(Z) > 1.0 - // - fnu = (1.0 + fid) / 3.0; - // - // SET PARAMETERS RELATED TO MACHINE CONSTANTS. - // TOL IS THE APPROXIMATE UNIT ROUNDOFF LIMITED TO 1.0E-18. - // ELIM IS THE APPROXIMATE EXPONENTIAL OVER- AND UNDERFLOW LIMIT. - // EXP(-ELIM) < EXP(-ALIM)=EXP(-ELIM)/TOL AND - // EXP(ELIM) > EXP(ALIM)=EXP(ELIM)*TOL ARE INTERVALS NEAR - // UNDERFLOW AND OVERFLOW LIMITS WHERE SCALED ARITHMETIC IS DONE. - // RL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC EXPANSION FOR LARGE Z. - // DIG = NUMBER OF BASE 10 DIGITS IN TOL = 10**(-DIG). - // FNUL IS THE LOWER BOUNDARY OF THE ASYMPTOTIC SERIES FOR LARGE FNU. - // - k1 = i1mach[14]; - k2 = i1mach[15]; - r1m5 = d1mach[4]; - k = ( abs(k1) > abs(k2) ? abs(k2) : abs(k1) ); - elim = 2.303 * (k*r1m5 - 3.0); - k1 = i1mach[13] - 1; - aa = r1m5*k1; - dig = fmin(aa, 18.0); - aa *= 2.303; - alim = elim + fmax(-aa, -41.45); - rl = 1.2*dig + 3.0; - fnul = 10.0 + 6.0*(dig - 3.0); - // - // TEST FOR RANGE - // - aa = 0.5 / tol; - bb = i1mach[8] * 0.5; - aa = fmin(aa, bb); - aa = pow(aa, tth); - if (az > aa) { *ierr = 4; return 0.0; } - aa = sqrt(aa); - if (az > aa) { *ierr = 3; } - csq = std::sqrt(z); - zta = z*csq*tth; - // - // RE(ZTA) <= 0 WHEN RE(Z) < 0, ESPECIALLY WHEN IM(Z) IS SMALL - // - sfac = 1.0; - zi = std::imag(z); - zr = std::real(z); - ak = std::imag(zta); - if (zr < 0.0) { - bk = std::real(zta); - ck = -fabs(bk); - zta = std::complex(ck, ak); - } - /* 80 */ - if ((zi == 0.0) && (zr <= 0.0)) { zta = std::complex(0.0, ak); } - /* 90 */ - aa = std::real(zta); - if (kode != 2) { - // - // OVERFLOW TEST - // - bb = fabs(aa); - if (bb >= alim) { - bb += 0.25*log(az); - sfac = tol; - if (bb > elim) { *ierr = 2; return 0.0; } - } - } - /* 100 */ - fmr = 0.0; - if ((aa < 0.0) || (zr <= 0.0)) { - fmr = pi; - if (zi < 0.0) { fmr = -pi; } - zta = -zta; - } - /* 110 */ - // - // AA=FACTOR FOR ANALYTIC CONTINUATION OF I(FNU,ZTA) - // KODE=2 RETURNS EXP(-ABS(XZTA))*I(FNU,ZTA) FROM CBINU - // - nz = binu(zta, fnu, kode, 1, cy, rl, fnul, tol, elim, alim); - if (nz < 0) { - if (nz == -1) { - *ierr = 2; - } else { - *ierr = 5; - } - return 0.0; - } - aa = fmr*fnu; - z3 = sfac; - s1 = cy[0] * std::complex(cos(aa), sin(aa)) * z3; - fnu = (2.0 - fid) / 3.0; - nz = binu(zta, fnu, kode, 2, cy, rl, fnul, tol, elim, alim); - cy[0] *= z3; - cy[1] *= z3; - // - // BACKWARD RECUR ONE STEP FOR ORDERS -1/3 OR -2/3 - // - s2 = cy[0] * (fnu+fnu) / zta + cy[1]; - aa = fmr * (fnu - 1.0); - s1 = (s1 + s2*std::complex(cos(aa), sin(aa)))*coef; - if (id != 1) { - s1 *= csq; - bi = s1 / sfac; - return bi; - } - /* 120 */ - s1 *= z; - bi = s1 / sfac; - return bi; -} - - -inline int bknu( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBKNU - //***REFER TO ZBESI,ZBESK,ZAIRY,ZBESH - // - // ZBKNU COMPUTES THE K BESSEL FUNCTION IN THE RIGHT HALF Z PLANE. - // - //***ROUTINES CALLED DGAMLN,I1MACH,D1MACH,ZKSCL,ZSHCH,ZUCHK,AZABS,ZDIV, - // AZEXP,AZLOG,ZMLT,AZSQRT - //***END PROLOGUE ZBKNU - - std::complex cch, ck, coef, crsc, cs, cscl, csh, cz,\ - f, fmu, p, pt, p1, p2, q, rz, smu, st, s1, s2, zd; - double aa, ak, ascle, a1, a2, bb, bk, caz, dnu, dnu2, etest, fc, fhs,\ - fk, fks, g1, g2, p2i, p2m, p2r, rk, s, tm, t1, t2, xx, yy,\ - elm, xd, yd, alas, as; - int iflag, inu, k, kflag, kk, koded, j, ic, inub, i = 1; - std::complex cy[2]; - - int kmax =30; - double r1 = 2.; - double pi = 3.14159265358979324; - double rthpi = 1.25331413731550025; - double spi = 1.90985931710274403; - double hpi = 1.57079632679489662; - double fpi = 1.89769999331517738; - double tth = 2. / 3.; - double cc[8] = { - 5.77215664901532861e-01, -4.20026350340952355e-02, - -4.21977345555443367e-02, 7.21894324666309954e-03, - -2.15241674114950973e-04, -2.01348547807882387e-05, - 1.13302723198169588e-06, 6.11609510448141582e-09 - }; - xx = std::real(z); - yy = std::imag(z); - caz = std::abs(z); - cscl = 1. / tol; - crsc = tol; - std::complex css[3] = {cscl, 1., crsc}; - std::complex csr[3] = {crsc, 1., cscl}; - double bry[3] = {1e3*d1mach[0]/tol, tol/(1e3*d1mach[0]), d1mach[1]}; - int nz = 0; - iflag = 0; - koded = kode; - rz = 2. / z; - inu = (int)(fnu + 0.5); - dnu = fnu - inu; - // Definitions for silencing initialization warnings. - s1 = 0.0; - s2 = 0.0; - ck = 0.0; - dnu2 = 0.0; - if (fabs(dnu) != 0.5) { - if (fabs(dnu) > tol) { dnu2 = dnu * dnu; } - if (caz <= r1) { - // - // SERIES FOR ABS(Z) <= R1 - // - fc = 1.; - smu = std::log(rz); - fmu = smu * dnu; - csh = std::sinh(fmu); - cch = std::cosh(fmu); - if (dnu != 0.0) { - fc = dnu * pi; - fc *= 1. / sin(fc); - smu = csh / dnu; - } - a2 = 1. + dnu; - // - // GAM(1-Z)*GAM(1+Z)=PI*Z/SIN(PI*Z), T1=1/GAM(1-DNU), T2=1/GAM(1+DNU) - // - t2 = exp(-gamln(a2)); - t1 = 1. / (t2*fc); - if (fabs(dnu) <= 0.1) { - // - // SERIES FOR F0 TO RESOLVE INDETERMINACY FOR SMALL ABS(DNU) - // - ak = 1.; - s = cc[0]; - for (int k = 2; k < 9; k++) - { - ak *= dnu2; - tm = cc[k-1] * ak; - s += tm; - if (fabs(tm) < tol) { break; } - } - g1 = -s; - } else { - g1 = (t1-t2) / (dnu+dnu); - } - g2 = 0.5 * (t1+t2); - f = fc*(g1*cch + smu*g2); - pt = std::exp(fmu); - p = (0.5 / t2) * pt; - q = (0.5 / t1) / pt; - s1 = f; - s2 = p; - ak = 1.0; - a1 = 1.0; - ck = 1.0; - bk = 1.0 - dnu2; - if ((inu <= 0) && (n <= 1)) { - // - // GENERATE K(FNU,Z), 0.0D0 <= FNU < 0.5D0 AND N=1 - // - if (caz >= tol) { - cz = z * z * 0.25; - t1 = 0.25 * caz * caz; - do { - f = (f*ak + p + q) / bk; - p = p / (ak-dnu); - q = q / (ak+dnu); - rk = 1.0 / ak; - ck *= cz * rk; - s1 += ck * f; - a1 *= t1 * rk; - bk += ak + ak + 1.0; - ak += 1.0; - } while (a1 > tol); - } - y[0] = s1; - if (koded == 1) { return nz; } - y[0] = s1 * std::exp(z); - return nz; - } - // - // GENERATE K(DNU,Z) AND K(DNU+1,Z) FOR FORWARD RECURRENCE - // - if (caz >= tol) { - cz = z * z * 0.25; - t1 = 0.25 * caz * caz; - do { - f = (f*ak + p + q) / bk; - p *= 1.0 / (ak - dnu); - q *= 1.0 / (ak + dnu); - rk = 1. / ak; - ck *= cz * rk; - s1 += ck * f; - s2 += ck * (p - f*ak); - a1 *= t1 * rk; - bk += ak + ak + 1.0; - ak += 1.0; - } while (a1 > tol); - } - kflag = 2; - bk = std::real(smu); - a1 = fnu + 1.; - ak = a1 * fabs(bk); - if (ak > alim) { kflag = 3; } - p2 = s2 * css[kflag-1]; - s2 = p2 * rz; - s1 *= css[kflag-1]; - if (koded != 1) { - f = std::exp(z); - s1 *= f; - s2 *= f; - } - goto L100; - } - } - // - // IFLAG=0 MEANS NO UNDERFLOW OCCURRED - // IFLAG=1 MEANS AN UNDERFLOW OCCURRED- COMPUTATION PROCEEDS WITH - // KODED=2 AND A TEST FOR ON SCALE VALUES IS MADE DURING FORWARD - // RECURSION - // - coef = rthpi / std::sqrt(z); - kflag = 2; - if (koded != 2) { - if (xx > alim) { - koded = 2; - iflag = 1; - kflag = 2; - } else { - a1 = exp(-xx)*std::real(css[kflag-1]); - pt = a1*std::complex(cos(yy), -sin(yy)); - coef *= pt; - } - } - - if (fabs(dnu) == 0.5) { - s1 = coef; - s2 = coef; - goto L100; - } -// -// MILLER ALGORITHM FOR ABS(Z) > R1 -// - ak = fabs(cos(pi*dnu)); - if (ak == 0.) { - s1 = coef; - s2 = coef; - goto L100; - } - fhs = fabs(0.25 - dnu2); - if (fhs == 0.) { - s1 = coef; - s2 = coef; - goto L100; - } -// -// COMPUTE R2=F(E). IF ABS(Z) >= R2, USE FORWARD RECURRENCE TO -// DETERMINE THE BACKWARD INDEX K. R2=F(E) IS A STRAIGHT LINE ON -// 12 <= E <= 60. E IS COMPUTED FROM 2**(-E)=B**(1-DIGITS(0.0_dp))= -// TOL WHERE B IS THE BASE OF THE ARITHMETIC. -// - t1 = (i1mach[13] - 1)*d1mach[4]*(log(10)/log(2)); - t1 = fmin(fmax(t1, 12.0), 60.0); - t2 = tth * t1 - 6.0; - if (xx == 0.) { - t1 = hpi; - } else { - t1 = fabs(atan(yy/xx)); - } - if (t2 <= caz) { - // - // FORWARD RECURRENCE LOOP WHEN ABS(Z) >= R2 - // - etest = ak / (pi*caz*tol); - fk = 1.0; - if (etest < 1.0) { goto L80; } - fks = 2.0; - rk = caz + caz + 2.0; - a1 = 0.0; - a2 = 1.0; - for (i = 1; i < (kmax+1); i++) - { - ak = fhs / fks; - bk = rk / (fk + 1.0); - tm = a2; - a2 = bk * a2 - ak * a1; - a1 = tm; - rk += 2.; - fks += fk + fk + 2.0; - fhs += fk + fk; - fk += 1.0; - tm = fabs(a2)*fk; - if (etest < tm) { - /* goto 160 */ - break; - } - if (i == kmax) { - /* Didn't break so goes to 310 */ - return -2; - } - } - - /* 160 */ - fk += spi * t1 * sqrt(t2/caz); - fhs = fabs(0.25 - dnu2); - } else { - // - // COMPUTE BACKWARD INDEX K FOR ABS(Z) < R2 - // - a2 = sqrt(caz); - ak *= fpi / (tol*sqrt(a2)); - aa = 3.0 * t1 / (1.0 + caz); - bb = 14.7 * t1 / (28.0 + caz); - ak = (log(ak) + caz*cos(aa)/(1.0 + 0.008*caz)) / cos(bb); - fk = 0.12125 * ak * ak / caz + 1.5; - } -L80: - // - // BACKWARD RECURRENCE LOOP FOR MILLER ALGORITHM - // - k = (int)fk; - fk = (double)k; - fks = fk * fk; - p1 = 0.0; - p2 = tol; - cs = p2; - for (i=1; i < (k+1); i++) - { - a1 = fks - fk; - a2 = (fks+fk) / (a1+fhs); - rk = 2.0 / (fk + 1.); - t1 = (fk + xx) * rk; - t2 = yy * rk; - pt = p2; - p2 = (p2 * std::complex(t1, t2) - p1) * a2; - p1 = pt; - cs += p2; - fks = a1 - fk + 1.0; - fk -= 1.0; - } - - // - // COMPUTE (P2/CS)=(P2/ABS(CS))*(CONJG(CS)/ABS(CS)) FOR BETTER SCALING - // - tm = std::abs(cs); - pt = 1.0 / tm; - s1 = pt * p2; - cs = conj(cs) * pt; - s1 *= coef * cs; - if ((inu <= 0) && (n <= 1)) { - zd = z; - if (iflag == 1) { goto L190; } - goto L130; - } - // - // COMPUTE P1/P2=(P1/ABS(P2)*CONJG(P2)/ABS(P2) FOR SCALING - // - tm = std::abs(p2); - pt = 1.0 / tm; - p1 = pt * p1; - p2 = conj(p2) * pt; - pt = p1 * p2; - s2 = s1 * (1. + (dnu+0.5 - pt)/z); - // - // FORWARD RECURSION ON THE THREE TERM RECURSION RELATION WITH - // SCALING NEAR EXPONENT EXTREMES ON KFLAG=1 OR KFLAG=3 - // -L100: - ck = (dnu + 1.)*rz; - if (n == 1) { inu -= 1; } - if (inu <= 0) { - if (n <= 1) { s1 = s2; } - zd = z; - if (iflag == 1) { goto L190; } - goto L130; - } - inub = 1; - if (iflag == 1) { goto L160; } -L110: - p1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = inub; i < inu+1; i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - ck += rz; - if (kflag < 3) { - p2 = s2*p1; - p2m = fmax(fabs(std::real(p2)), fabs(std::imag(p2))); - if (p2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= p1; - s2 = p2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - p1 = csr[kflag-1]; - } - } - } - if (n == 1) { s1 = s2; } - -L130: - y[0] = s1 * csr[kflag-1]; - if (n == 1) { return nz; } - y[1] = s2 * csr[kflag-1]; - if (n == 2) { return nz; } - kk = 2; -L140: - kk += 1; - if (kk > n) { return nz; } - p1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = kk; i < (n+1); i++) - { - p2 = s2; - s2 = ck*s2 + s1; - s1 = p2; - ck += rz; - p2 = s2*p1; - y[i-1] = p2; - if (kflag < 3) { - p2m = fmax(fabs(std::real(p2)), fabs(std::imag(p2))); - if (p2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= p1; - s2 = p2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - p1 = csr[kflag-1]; - } - } - } - return nz; -// -// IFLAG=1 CASES, FORWARD RECURRENCE ON SCALED VALUES ON UNDERFLOW -// -L160: - elm = exp(-elim); - ascle = bry[0]; - zd = z; - xd = xx; - yd = yy; - ic = -1; - j = 2; - for (i = 1; i < (inu+1); i++) - { - st = s2; - s2 = ck*s2 + s1; - s1 = st; - ck += rz; - as = std::abs(s2); - alas = log(as); - p2r = alas - xd; - if (p2r >= -elim) { - p2 = -zd + std::log(s2); - p2r = std::real(p2); - p2i = std::imag(p2); - p2m = exp(p2r) / tol; - p1 = p2m * std::complex(cos(p2i), sin(p2i)); - if (!(uchk(p1, ascle, tol))) { - j = 3 - j; - cy[j-1] = p1; - if (ic == i-1) { goto L180; } - ic = i; - continue; - } - } - if (alas >= 0.5 * elim) { - xd -= elim; - s1 *= elm; - s2 *= elm; - zd = std::complex(xd, yd); - } - } - if (n == 1) { s1 = s2; } - goto L190; -L180: - kflag = 1; - inub = i + 1; - s2 = cy[j-1]; - j = 3 - j; - s1 = cy[j-1]; - if (inub <= inu) { goto L110; } - if (n == 1) { s1 = s2; } - goto L130; -L190: - y[0] = s1; - if (n != 1) { y[1] = s2; } - ascle = bry[0]; - nz = kscl(zd, fnu, n, &y[0], rz, &ascle, tol, elim); - inu = n - nz; - if (inu <= 0) { return nz; } - kk = nz + 1; - s1 = y[kk-1]; - y[kk-1] = s1 * csr[0]; - if (inu == 1) { return nz; } - kk = nz + 2; - s2 = y[kk-1]; - y[kk-1] = s2 * csr[0]; - if (inu == 2) { return nz; } - t2 = fnu + (kk-1); - ck = t2 * rz; - kflag = 1; - goto L140; -} - - -inline int buni( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int nui, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBUNI - //***REFER TO ZBESI,ZBESK - // - // ZBUNI COMPUTES THE I BESSEL FUNCTION FOR LARGE CABS(Z).GT. - // FNUL AND FNU+N-1.LT.FNUL. THE ORDER IS INCREASED FROM - // FNU+N-1 GREATER THAN FNUL BY ADDING NUI AND COMPUTING - // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR I(FNU,Z) - // ON IFORM=1 AND THE EXPANSION FOR J(FNU,Z) ON IFORM=2 - // - //***ROUTINES CALLED ZUNI1,ZUNI2,AZABS,D1MACH - //***END PROLOGUE ZBUNI - - std::complex cscl, cscr, rz, st, s1, s2; - double ax, ay, dfnu, fnui, gnu, xx, yy, ascle, str, sti, stm; - int i, iflag, iform, k, nl, nw, nz; - std::complex cy[2] = { 0.0 }; - double bry[3] = { 0.0 }; - - nz = 0; - xx = std::real(z); - yy = std::imag(z); - ax = fabs(xx) + sqrt(3.); - ay = fabs(yy); - iform = 1; - if (ay > ax) { iform = 2; } - if (nui == 0) { - if (iform != 2) { - uni1(z, fnu, kode, n, y, &nw, nlast, fnul, tol, elim, alim); - } else { - uni2(z, fnu, kode, n, y, &nw, nlast, fnul, tol, elim, alim); - } - if (nw < 0) { - nz = -1; - if (nw == -2) { nz = -2; } - return nz; - } - return nw; - } - - fnui = nui; - dfnu = fnu + (n - 1); - gnu = dfnu + fnui; - if (iform != 2) { - // - // ASYMPTOTIC EXPANSION FOR I(FNU,Z) FOR LARGE FNU APPLIED IN - // -PI/3 <= ARG(Z) <= PI/3 - // - uni1(z, gnu, kode, 2, cy, &nw, nlast, fnul, tol, elim, alim); - } else { - uni2(z, gnu, kode, 2, cy, &nw, nlast, fnul, tol, elim, alim); - } - if (nw >= 0) { - if (nw != 0) { *nlast = n; return nz; } - ay = std::abs(cy[0]); - // - // SCALE BACKWARD RECURRENCE, BRY(3) IS DEFINED BUT NEVER USED - // - bry[0] = 1e3*d1mach[0] / tol; - bry[1] = tol / 1e3*d1mach[0]; - bry[2] = bry[1]; - iflag = 2; - ascle = bry[1]; - ax = 1.0; - cscl = ax; - if (ay <= bry[0]) { - iflag = 1; - ascle = bry[0]; - ax = 1.0 / tol; - cscl = ax; - } else { - if (ay >= bry[1]) { - iflag = 3; - ascle = bry[2]; - ax = tol; - cscl = ax; - - } - } - ay = 1.0 / ax; - cscr = ay; - s1 = cy[1] * cscl; - s2 = cy[0] * cscl; - rz = 2.0 / z; - for (i = 1; i < (nui+1); i++) - { - st = s2; - s2 = (dfnu +fnui)*rz*st + s1; - s1 = st; - fnui -= 1.0; - if (iflag < 3) { - st = s2 * cscr; - str = fabs(std::real(st)); - sti = fabs(std::imag(st)); - stm = fmax(str, sti); - if (stm > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cscr; - s2 = st; - ax *= tol; - ay = 1.0 / ax; - cscl = ax; - cscr = ay; - s1 *= cscl; - s2 *= cscl; - } - } - } - y[n-1] = s2*cscr; - if (n == 1) { return nz; } - nl = n-1; - fnui = nl; - k = nl; - for (i = 0; i < (nl+1); i++) - { - st = s2; - s2 = (fnu + fnui)*rz*s2 + s1; - s1 = st; - st = s2 * cscr; - y[k-1] = st; - fnui -= 1.0; - k -= 1; - if (iflag < 3) { - st = s2 * cscr; - str = fabs(std::real(st)); - sti = fabs(std::imag(st)); - stm = fmax(str, sti); - if (stm > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cscr; - s2 = st; - ax *= tol; - ay = 1.0 / ax; - cscl = ax; - cscr = ay; - s1 *= cscl; - s2 *= cscl; - } - } - } - return nz; - } - nz = -1; - if (nw == -2) { nz = -2; } - return nz; -} - - -inline int bunk( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZBUNK - //***REFER TO ZBESK,ZBESH - // - // ZBUNK COMPUTES THE K BESSEL FUNCTION FOR FNU.GT.FNUL. - // ACCORDING TO THE UNIFORM ASYMPTOTIC EXPANSION FOR K(FNU,Z) - // IN ZUNK1 AND THE EXPANSION FOR H(2,FNU,Z) IN ZUNK2 - // - //***ROUTINES CALLED ZUNK1,ZUNK2 - //***END PROLOGUE ZBUNK - - double ax, ay; - - int nz = 0; - ax = fabs(std::real(z)) * 1.7321; - ay = fabs(std::imag(z)); - - if (ay <= ax) { - // - // Asymptotic expansion for K(FNU,Z) for large FNU applied in - // -PI/3 <= ARG(Z) <= PI/3 - // - nz = unk1(z, fnu, kode, mr, n, y, tol, elim, alim); - } else { - // - // Asymptotic expansion for H(2, FNU, Z*EXP(M*HPI)) for large FNU - // applied in PI/3 < ABS(ARG(Z)) <= PI/2 where M = +I or -I and HPI = PI/2 - // - nz = unk2(z, fnu, kode, mr, n, y, tol, elim, alim); - } - return nz; -} - - -inline double gamln(double z) { - - //***BEGIN PROLOGUE DGAMLN - //***DATE WRITTEN 830501 (YYMMDD) - //***REVISION DATE 830501 (YYMMDD) - //***CATEGORY NO. B5F - //***KEYWORDS GAMMA FUNCTION,LOGARITHM OF GAMMA FUNCTION - //***AUTHOR AMOS, DONALD E., SANDIA NATIONAL LABORATORIES - //***PURPOSE TO COMPUTE THE LOGARITHM OF THE GAMMA FUNCTION - //***DESCRIPTION - // - // **** A DOUBLE PRECISION ROUTINE **** - // DGAMLN COMPUTES THE NATURAL LOG OF THE GAMMA FUNCTION FOR - // Z.GT.0. THE ASYMPTOTIC EXPANSION IS USED TO GENERATE VALUES - // GREATER THAN ZMIN WHICH ARE ADJUSTED BY THE RECURSION - // G(Z+1)=Z*G(Z) FOR Z.LE.ZMIN. THE FUNCTION WAS MADE AS - // PORTABLE AS POSSIBLE BY COMPUTIMG ZMIN FROM THE NUMBER OF BASE - // 10 DIGITS IN A WORD, RLN=AMAX1(-ALOG10(R1MACH(4)),0.5E-18) - // LIMITED TO 18 DIGITS OF (RELATIVE) ACCURACY. - // - // SINCE INTEGER ARGUMENTS ARE COMMON, A TABLE LOOK UP ON 100 - // VALUES IS USED FOR SPEED OF EXECUTION. - // - // DESCRIPTION OF ARGUMENTS - // - // INPUT Z IS D0UBLE PRECISION - // Z - ARGUMENT, Z.GT.0.0D0 - // - // OUTPUT DGAMLN IS DOUBLE PRECISION - // DGAMLN - NATURAL LOG OF THE GAMMA FUNCTION AT Z.NE.0.0D0 - // IERR - ERROR FLAG - // IERR=0, NORMAL RETURN, COMPUTATION COMPLETED - // IERR=1, Z.LE.0.0D0, NO COMPUTATION - // - // - //***REFERENCES COMPUTATION OF BESSEL FUNCTIONS OF COMPLEX ARGUMENT - // BY D. E. AMOS, SAND83-0083, MAY, 1983. - //***ROUTINES CALLED I1MACH,D1MACH - //***END PROLOGUE DGAMLN - - int i1m, mz; - double fln, fz, rln, s, tlg, trm, tst, t1, wdtol, zdmy, zinc, zm, zmin, zp, zsq; - const double con = 1.83787706640934548; /* LN(2*PI) */ - int nz = 0; - if (z > 0.0) { - if (z <= 101.0) { - nz = (int)z; - fz = z - nz; - if (fz <= 0.0) { - if (nz <= 100) { - return dgamln_gln[nz-1]; - } - } - } - wdtol = fmax(d1mach[3], 1e-18); - i1m = i1mach[13]; - rln = d1mach[4]*i1m; - fln = fmax(fmin(rln, 20.), 3.0) - 3.0; - zm = 1.8 + 0.3875*fln; - mz = ((int)zm) + 1; - zmin = mz; - zdmy = z; - zinc = 0.0; - if (z < zmin){ - zinc = zmin - nz; - zdmy = z + zinc; - } - zp = 1. / zdmy; - t1 = dgamln_cf[0]*zp; - s = t1; - if (zp >= wdtol) { - zsq = zp*zp; - tst = t1*wdtol; - for (int i = 2; i < 23; i++) - { - zp *= zsq; - trm = dgamln_cf[i-1] * zp; - if (fabs(trm) < tst) { break; } - s += trm; - } - } - - if (zinc == 0.) { - tlg = log(z); - return z*(tlg-1.0) + 0.5*(con - tlg) + s; - } - zp = 1.0; - nz = (int)zinc; - for (int i = 0; i < nz; i++) - { - zp *= (z + i); - } - tlg = log(zdmy); - return zdmy*(tlg-1.0) - log(zp) + 0.5*(con-tlg) + s; - } - // Zero or negative argument - return NAN; -} - - -inline int mlri( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol -) { - - //***BEGIN PROLOGUE ZMLRI - //***REFER TO ZBESI,ZBESK - // - // ZMLRI COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY THE - // MILLER ALGORITHM NORMALIZED BY A NEUMANN SERIES. - // - //***ROUTINES CALLED DGAMLN,D1MACH,AZABS,AZEXP,AZLOG,ZMLT - //***END PROLOGUE ZMLRI - - std::complex ck, cnorm, pt, p1, p2, rz, sum; - double ack, ak, ap, at, az, bk, fkap, fkk, flam, fnf, rho,\ - rho2, scle, tfnf, tst, x; - int i, iaz, ifnu, inu, itime, k, kk, km, m, nz; - scle = d1mach[0] / tol; - nz = 0; - az = std::abs(z); - x = std::real(z); - iaz = (int)az; - ifnu = (int)fnu; - inu = ifnu + n - 1; - at = iaz + 1; - ck = at / z; - rz = 2. / z; - p1 = 0.; - p2 = 1.; - ack = (at + 1.0) / az; - rho = ack + sqrt(ack*ack - 1.); - rho2 = rho * rho; - tst = (rho2 + rho2) / ((rho2 - 1.0)*(rho - 1.0)); - tst /= tol; - // - // COMPUTE RELATIVE TRUNCATION ERROR INDEX FOR SERIES - // - ak = at; - i = 1; - for (i = 1; i < 81; i++ ) - { - pt = p2; - p2 = p1 - ck * p2; - p1 = pt; - ck += rz; - ap = std::abs(p2); - if (ap > tst*ak*ak) { break; } - ak += 1.0; - if (i == 80) { - /* Exhausted loop without break */ - return -2; - } - } - i += 1; - k = 0; - if (inu >= iaz) { - // - // COMPUTE RELATIVE TRUNCATION ERROR FOR RATIOS - // - p1 = 0.0; - p2 = 1.0; - at = inu + 1; - ck = at / z; - ack = at / az; - tst = sqrt(ack / tol); - itime = 1; - k = 1; - for (k = 1; k < 81; k++ ) - { - pt = p2; - p2 = p1 - ck * p2; - p1 = pt; - ck += rz; - ap = std::abs(p2); - if (ap >= tst) { - if (itime == 2) { break; } - ack = std::abs(ck); - flam = ack + sqrt(ack*ack - 1.0); - fkap = ap / std::abs(p1); - rho = fmin(flam, fkap); - tst *= sqrt(rho / (rho*rho - 1.0)); - itime = 2; - } - if (k == 80) { - /* Exhausted loop without break */ - return -2; - } - } - } - // - // BACKWARD RECURRENCE AND SUM NORMALIZING RELATION - // - k += 1; - kk = fmax(i+iaz, k+inu); - fkk = kk; - p1 = 0.0; - // - // SCALE P2 AND SUM BY SCLE - // - p2 = scle; - fnf = fnu - ifnu; - tfnf = fnf + fnf; - bk = gamln(fkk + tfnf + 1.0) - gamln(fkk + 1.0) - gamln(tfnf + 1.0); - bk = exp(bk); - sum = 0.; - km = kk - inu; - for (i = 1; i < (km+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - } - y[n-1] = p2; - if (n != 1) { - for (i = 2; i < (n+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - m = n - i + 1; - y[m-1] = p2; - } - } - if (ifnu > 0) { - for (i = 1; i < (ifnu+1); i++) - { - pt = p2; - p2 = p1 + (fkk + fnf)*rz*p2; - p1 = pt; - ak = 1. - tfnf / (fkk+tfnf); - ack = bk*ak; - sum += (ack + bk)*p1; - bk = ack; - fkk -= 1.; - } - } - pt = z; - if (kode == 2) { pt -= x; } - p1 = -fnf * std::log(rz) + pt; - ap = gamln(1. + fnf); - pt = p1 - ap; - // - // THE DIVISION EXP(PT)/(SUM+P2) IS ALTERED TO AVOID OVERFLOW - // IN THE DENOMINATOR BY SQUARING LARGE QUANTITIES - // - p2 += sum; - ap = std::abs(p2); - p1 = 1. / ap; - ck = std::exp(pt) * p1; - pt = conj(p2)*p1; - cnorm = ck * pt; - for (int i = 0; i < n; i++) { y[i] *= cnorm; } - return nz; -} - - -inline int kscl( - std::complex zr, - double fnu, - int n, - std::complex *y, - std::complex rz, - double *ascle, - double tol, - double elim -) { - - //***BEGIN PROLOGUE ZKSCL - //***REFER TO ZBESK - // - // SET K FUNCTIONS TO ZERO ON UNDERFLOW, CONTINUE RECURRENCE - // ON SCALED FUNCTIONS UNTIL TWO MEMBERS COME ON SCALE, THEN - // RETURN WITH MIN(NZ+2,N) VALUES SCALED BY 1/TOL. - // - //***ROUTINES CALLED ZUCHK,AZABS,AZLOG - //***END PROLOGUE ZKSCL - - std::complex cy[2] = { 0. }; - double as, acs, alas, fn, zri, xx; - std::complex s1, s2, cs, ck, zd; - int nz = 0; - int ic = 0; - int nn = ( n > 2 ? 2 : n ); - int kk = 0; - int i; - double elm = exp(-elim); - xx = std::real(zr); - - for (i = 1; i < (nn + 1); i++) - { - s1 = y[i-1]; - cy[i-1] = s1; - as = std::abs(s1); - acs = -std::real(zr) + log(as); - nz += 1; - y[i-1] = 0.; - if (acs < -elim) { - continue; - } - cs = -zr + std::log(s1); - cs = (exp(std::real(cs))/tol)*(cos(std::imag(cs)) + sin(std::imag(cs)*std::complex(0, 1))); - if (!uchk(cs, *ascle, tol)) { - y[i-1] = cs; - nz -= 1; - ic = i; - } - } - if (n == 1) { - return nz; - } - if (ic <= 1) { - y[0] = 0.; - nz = 2; - } - if (n == 2) { - return nz; - } - if (nz == 0) { - return nz; - } - - fn = fnu + 1.; - ck = fn*rz; - s1 = cy[0]; - s2 = cy[1]; - zri = std::imag(zr); - zd = zr; - for (i = 3; i < (n+1); i++) - { - kk = i; - cs = s2; - s2 *= ck; - s2 += s1; - s1 = cs; - ck += rz; - as = std::abs(s2); - alas = log(as); - acs = alas - xx; - nz += 1; - y[i-1] = 0.; - if (acs >= -elim) { - cs = std::log(s2); - cs -= zd; - cs = (exp(std::real(cs))/tol)*std::complex(cos(std::imag(cs)), sin(std::imag(cs))); - if (!uchk(cs, *ascle, tol)) { - y[i-1] = cs; - nz -= 1; - if (ic == kk-1) { - nz = kk - 2; - for (int i = 0; i < nz; i++) { y[i] = 0.; } - return nz; - } - ic = kk; - continue; - } - } - if (alas >= 0.5*elim){ - xx -= elim; - zd = std::complex(xx, zri); - s1 *= elm; - s2 *= elm; - } - } - nz = n; - if (ic == n) { - nz = n-1; - } - - for (int i = 0; i < nz; i++) { y[i] = 0.; } - return nz; -} - - -inline void rati( - std::complex z, - double fnu, - int n, - std::complex *cy, - double tol -) { - - //***BEGIN PROLOGUE ZRATI - //***REFER TO ZBESI,ZBESK,ZBESH - // - // ZRATI COMPUTES RATIOS OF I BESSEL FUNCTIONS BY BACKWARD - // RECURRENCE. THE STARTING INDEX IS DETERMINED BY FORWARD - // RECURRENCE AS DESCRIBED IN J. RES. OF NAT. BUR. OF STANDARDS-B, - // MATHEMATICAL SCIENCES, VOL 77B, P111-114, SEPTEMBER, 1973, - // BESSEL FUNCTIONS I AND J OF COMPLEX ARGUMENT AND INTEGER ORDER, - // BY D. J. SOOKNE. - // - //***ROUTINES CALLED AZABS,ZDIV - //***END PROLOGUE ZRATI - - std::complex cdfnu, pt, p1, p2, rz, t1; - double ak, amagz, ap1, ap2, arg, az, dfnu, fdnu, flam, fnup, rap1, rho, test, test1; - int i, id, idnu, inu, itime, k, kk, magz; - - az = std::abs(z); - inu = (int)fnu; - idnu = inu + n - 1; - fdnu = idnu; - magz = az; - amagz = magz + 1; - fnup = fmax(amagz, fdnu); - id = idnu - magz - 1; - itime = 1; - k = 1; - rz = 2.0 / z; - t1 = fnup * rz; - p2 = -t1; - p1 = 1.0; - t1 += rz; - if (id > 0) { - id = 0; - } - ap2 = std::abs(p2); - ap1 = std::abs(p1); - - // - // THE OVERFLOW TEST ON K(FNU+I-1,Z) BEFORE THE CALL TO CBKNX - // GUARANTEES THAT P2 IS ON SCALE. SCALE TEST1 AND ALL SUBSEQUENT P2 - // VALUES BY AP1 TO ENSURE THAT AN OVERFLOW DOES NOT OCCUR PREMATURELY. - // - arg = (ap2 + ap2) / (ap1 * tol); - test1 = sqrt(arg); - test = test1; - rap1 = 1.0 / ap1; - p1 *= rap1; - p2 *= rap1; - ap2 *= rap1; - - while (1) { - k += 1; - ap1 = ap2; - pt = p2; - p2 = p1 - t1*p2; - p1 = pt; - t1 += rz; - ap2 = std::abs(p2); - if (ap1 > test) { break; } - if (itime != 2) { - ak = std::abs(t1)*0.5; - flam = ak + sqrt(ak*ak - 1.0); - rho = fmin(ap2/ap1, flam); - test = test1*sqrt(rho / (rho*rho - 1.0)); - itime = 2; - } - } - kk = k + 1 - id; - ak = kk; - dfnu = fnu + n - 1; - cdfnu = dfnu; - t1 = ak; - p1 = 1.0 / ap2; - p2 = 0.0; - - for (i = 1; i < (kk+1); i++) { - pt = p1; - p1 = rz*(cdfnu+t1)*p1 + p2; - p2 = pt; - t1 -= 1.0; - } - - if (p1 == 0.) { - p1 = std::complex(0, 0); - } - - cy[n-1] = p2 / p1; - if (n == 1) { return; } - k = n - 1; - ak = k; - t1 = ak; - cdfnu = fnu*rz; - - for (i = 2; i < (n+1); i++) { - pt = cdfnu + t1*rz*cy[k]; - if (pt == 0.0) { - pt = std::complex(tol, tol); - } - cy[k-1] = 1.0 / pt; - t1 -= 1.0; - k -= 1; - } - return; -} - - -inline int seri( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZSERI - //***REFER TO ZBESI,ZBESK - // - // ZSERI COMPUTES THE I BESSEL FUNCTION FOR REAL(Z).GE.0.0 BY - // MEANS OF THE POWER SERIES FOR LARGE CABS(Z) IN THE - // REGION CABS(Z).LE.2*SQRT(FNU+1). NZ=0 IS A NORMAL RETURN. - // NZ.GT.0 MEANS THAT THE LAST NZ COMPONENTS WERE SET TO ZERO - // DUE TO UNDERFLOW. NZ.LT.0 MEANS UNDERFLOW OCCURRED, BUT THE - // CONDITION CABS(Z).LE.2*SQRT(FNU+1) WAS VIOLATED AND THE - // COMPUTATION MUST BE COMPLETED IN ANOTHER ROUTINE WITH N=N-ABS(NZ). - // - //***ROUTINES CALLED DGAMLN,D1MACH,ZUCHK,AZABS,ZDIV,AZLOG,ZMLT - //***END PROLOGUE ZSERI - - std::complex ak1, ck, coef, crsc, cz, half_z, rz, s1, s2, w[2]; - double aa, acz, ak, arm, ascle, atol, az, dfnu, fnup, rak1,\ - rs, rtr1, s, ss, x; - int ib, iflag, il, k, l, m, nn; - - int nz = 0; - az = std::abs(z); - if (az == 0.0) { - y[0] = 0.0; - if (fnu == 0.) { y[0] = 1.0; } - if (n == 1) { return nz; } - for (int i = 1; i < n; i++) { y[i] = 0.0; } - return nz; - } - x = std::real(z); - arm = 1e3*d1mach[0]; - rtr1 = sqrt(arm); - crsc = 1.0; - iflag = 0; - if (az >= arm) { - half_z = 0.5*z; - cz = 0.0; - if (az > rtr1) { cz = half_z*half_z; } - acz = std::abs(cz); - nn = n; - ck = std::log(half_z); -L10: - dfnu = fnu + (nn-1); - fnup = dfnu + 1.0; - // - // UNDERFLOW TEST - // - ak1 = ck * dfnu; - ak = gamln(fnup); - ak1 -= ak; - if (kode == 2) { ak1 -= x; } - rak1 = std::real(ak1); - if (rak1 > -elim) { goto L30; } -L20: - nz += 1; - y[nn - 1] = 0.0; - // - // RETURN WITH NZ < 0 IF ABS(Z*Z/4) > FNU+N-NZ-1 COMPLETE - // THE CALCULATION IN CBINU WITH N=N-ABS(NZ) - // - if (acz > dfnu) { return -nz; } - nn -= 1; - if (nn == 0) { return nz; } - goto L10; -L30: - if (rak1 <= -alim) { - iflag = 1; - ss = 1.0 / tol; - crsc = tol; - ascle = arm * ss; - } - ak = std::imag(ak1); - aa = exp(rak1); - if (iflag == 1) { aa *= ss; } - coef = aa * std::complex(cos(ak), sin(ak)); - atol = tol * acz / fnup; - il = (nn > 2 ? 2 : nn); - for (int i = 1; i < (il +1); i++) - { - dfnu = fnu + (nn-i); - fnup = dfnu + 1.0; - s1 = 1.0; - if (acz >= tol*fnup) { - ak1 = 1.0; - ak = fnup + 2.0; - s = fnup; - aa = 2.0; - while (1) { - rs = 1.0 / s; - ak1 *= cz; - ak1 *= rs; - s1 += ak1; - s += ak; - ak += 2.0; - aa *= acz; - aa *= rs; - if (aa <= atol) { break; } - } - } - s2 = s1 * coef; - w[i-1] = s2; - if (iflag != 0) { - if (uchk(s2, ascle, tol)) { goto L20; } - } - m = nn - i + 1; - y[m-1] = s2 * crsc; - if (i != il) { coef *= dfnu / half_z; } - } - if (nn <= 2) { return nz; } - k = nn - 2; - ak = k; - rz = 2.0 / z; - if (iflag == 1) { goto L80; } - ib = 3; -L60: - for (int i = ib; i < (nn+1); i++) - { - y[k-1] = (ak+fnu)*rz*y[k] + y[k+1]; - ak -= 1.0; - k -= 1; - } - return nz; -L80: - // - // RECUR BACKWARD WITH SCALED VALUES - // - // - // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION ABOVE THE - // UNDERFLOW LIMIT = ASCLE = D1MACH(1)*SS*1000 - // - s1 = w[0]; - s2 = w[1]; - l = 3; - for (int l = 3; l < (nn+1); l++) - { - ck = s2; - s2 = s1 + (ak+fnu)*rz*s2; - s1= ck; - ck = s2*crsc; - y[k-1] = ck; - ak -= 1.0; - k -= 1; - if (std::abs(ck) > ascle) { goto L100; } - } - return nz; -L100: - ib = l+1; - if (ib > nn) { return nz; } - goto L60; - } - nz = n; - if (fnu == 0.0) { nz -= 1; } - y[0] = 0.0; - if (fnu == 0.) { y[0] = 1.0; } - if (n == 1) { return nz; } - for (int i = 1; i < n; i++) { y[i] = 0.0; } - return nz; -} - - -inline int s1s2( - std::complex zr, - std::complex *s1, - std::complex *s2, - double ascle, - double alim, - int *iuf -) { - - //***BEGIN PROLOGUE ZS1S2 - //***REFER TO ZBESK,ZAIRY - // - // ZS1S2 TESTS FOR A POSSIBLE UNDERFLOW RESULTING FROM THE - // ADDITION OF THE I AND K FUNCTIONS IN THE ANALYTIC CON- - // TINUATION FORMULA WHERE S1=K FUNCTION AND S2=I FUNCTION. - // ON KODE=1 THE I AND K FUNCTIONS ARE DIFFERENT ORDERS OF - // MAGNITUDE, BUT FOR KODE=2 THEY CAN BE OF THE SAME ORDER - // OF MAGNITUDE AND THE MAXIMUM MUST BE AT LEAST ONE - // PRECISION ABOVE THE UNDERFLOW LIMIT. - // - //***ROUTINES CALLED AZABS,AZEXP,AZLOG - //***END PROLOGUE ZS1S2 - - std::complex c1, s1d; - double aa, aln, as1, as2, xx; - int nz = 0; - as1 = std::abs(*s1); - as2 = std::abs(*s2); - aa = std::real(*s1); - aln = std::imag(*s1); - - if ((aa != 0.) || (aln != 0.)) { - if (as1 != 0.){ - xx = std::real(zr); - aln = -xx - xx + log(as1); - s1d = *s1; - *s1 = 0.; - as1 = 0.; - if (aln >= -alim) { - c1 = std::log(s1d) - zr - zr; - *s1 = std::exp(c1); - as1 = std::abs(*s1); - *iuf += 1; - } - } - } - aa = fmax(as1, as2); - if (aa > ascle) { - return nz; - } - *s1 = 0.; - *s2 = 0.; - *iuf = 0; - return 1; -} - - -inline int uchk( - std::complex y, - double ascle, - double tol -) { - - //***BEGIN PROLOGUE ZUCHK - //***REFER TO ZSERI,ZUOIK,ZUNK1,ZUNK2,ZUNI1,ZUNI2,ZKSCL - // - // Y ENTERS AS A SCALED QUANTITY WHOSE MAGNITUDE IS GREATER THAN - // EXP(-ALIM)=ASCLE=1.0E+3*D1MACH(1)/TOL. THE TEST IS MADE TO SEE - // IF THE MAGNITUDE OF THE REAL OR IMAGINARY PART WOULD UNDERFLOW - // WHEN Y IS SCALED (BY TOL) TO ITS PROPER VALUE. Y IS ACCEPTED - // IF THE UNDERFLOW IS AT LEAST ONE PRECISION BELOW THE MAGNITUDE - // OF THE LARGEST COMPONENT; OTHERWISE THE PHASE ANGLE DOES NOT HAVE - // ABSOLUTE ACCURACY AND AN UNDERFLOW IS ASSUMED. - // - //***ROUTINES CALLED (NONE) - //***END PROLOGUE ZUCHK - - double yr = fabs(std::real(y)); - double yi = fabs(std::imag(y)); - double ss = fmax(yr, yi); - double st = fmin(yr, yi); - if (st > ascle) { - return 0; - } else { - st /= tol; - if (ss < st) { - return 1; - } else { - return 0; - } - } -} - - -inline void unhj( - std::complex z, - double fnu, - int ipmtr, - double tol, - std::complex *phi, - std::complex *arg, - std::complex *zeta1, - std::complex *zeta2, - std::complex *asum, - std::complex *bsum -) { - - //***BEGIN PROLOGUE ZUNHJ - //***REFER TO ZBESI,ZBESK - // - // REFERENCES - // HANDBOOK OF MATHEMATICAL FUNCTIONS BY M. ABRAMOWITZ AND I.A. - // STEGUN, AMS55, NATIONAL BUREAU OF STANDARDS, 1965, CHAPTER 9. - // - // ASYMPTOTICS AND SPECIAL FUNCTIONS BY F.W.J. OLVER, ACADEMIC - // PRESS, N.Y., 1974, PAGE 420 - // - // ABSTRACT - // ZUNHJ COMPUTES PARAMETERS FOR BESSEL FUNCTIONS C(FNU,Z) = - // J(FNU,Z), Y(FNU,Z) OR H(I,FNU,Z) I=1,2 FOR LARGE ORDERS FNU - // BY MEANS OF THE UNIFORM ASYMPTOTIC EXPANSION - // - // C(FNU,Z)=C1*PHI*( ASUM*AIRY(ARG) + C2*BSUM*DAIRY(ARG) ) - // - // FOR PROPER CHOICES OF C1, C2, AIRY AND DAIRY WHERE AIRY IS - // AN AIRY FUNCTION AND DAIRY IS ITS DERIVATIVE. - // - // (2/3)*FNU*ZETA**1.5 = ZETA1-ZETA2, - // - // ZETA1=0.5*FNU*CLOG((1+W)/(1-W)), ZETA2=FNU*W FOR SCALING - // PURPOSES IN AIRY FUNCTIONS FROM CAIRY OR CBIRY. - // - // MCONJ=SIGN OF AIMAG(Z), BUT IS AMBIGUOUS WHEN Z IS REAL AND - // MUST BE SPECIFIED. IPMTR=0 RETURNS ALL PARAMETERS. IPMTR= - // 1 COMPUTES ALL EXCEPT ASUM AND BSUM. - // - //***ROUTINES CALLED AZABS,ZDIV,AZLOG,AZSQRT,D1MACH - //***END PROLOGUE ZUNHJ - - std::complex cfnu, przth, ptfn, rtzta, rzth, suma, sumb; - std::complex tfn, t2, w, w2, za, zb, zc, zeta, zth; - double ang, atol, aw2, azth, btol, fn13, fn23, pp, rfn13; - double rfnu, rfnu2, wi, wr, zci, zcr, zetai, zetar, zthi; - double zthr, asumr, asumi, bsumr, bsumi, test, ac; - double ex1 = 1./3.; - double ex2 = 2./3.; - double hpi = 1.57079632679489662; - double pi = 3.14159265358979324; - double thpi = 4.71238898038468986; - int ias, ibs, j, ju, k, kmax, kp1, ks, l, lrp1, l1, l2, m; - /* array vars */ - std::complex cr[14] = { 0. }; - std::complex dr[14] = { 0. }; - std::complex up[14] = { 0. }; - std::complex p[30] = { 0. }; - double ap[30] = { 0. }; - - rfnu = 1. / fnu; - // - // OVERFLOW TEST (Z/FNU TOO SMALL) - // - test = d1mach[0] * 1e3; - ac = fnu*test; - if ((fabs(std::real(z)) <= ac) && (fabs(std::imag(z)) <= ac)) { - *zeta1 = 2.0*fabs(log(test)) + fnu; - *zeta2 = fnu; - *phi = 1.; - *arg = 1.; - return; - } - zb = z*rfnu; - rfnu2 = rfnu*rfnu; - // - // COMPUTE IN THE FOURTH QUADRANT - // - fn13 = pow(fnu, ex1); - fn23 = fn13 * fn13; - rfn13 = 1.0/fn13; - w2 = 1.0 - zb*zb; - /* AMOS AZSQRT and C CSQRT differs when imaginary 0.0 swaps sign */ - w2 = 1.0 - zb*zb; - if (std::imag(w2) == -0.0) { w2 = std::real(w2); } - aw2 = std::abs(w2); - if (aw2 <= 0.25) { - // - // POWER SERIES FOR ABS(W2) <= 0.25 - // - k = 1; - p[0] = 1.; - suma = zunhj_gama[0]; - ap[0] = 1.; - if (aw2 >= tol) { - for (k = 2; k < 31; k++) - { - p[k-1] = p[k-2]*w2; - suma += p[k-1]*zunhj_gama[k-1]; - ap[k-1] = ap[k-2]*aw2; - if (ap[k-1] < tol) { break; } - } - } - /* Check for exhausted loop */ - if (k == 31) { k = 30; } - - kmax = k; - zeta = w2*suma; - *arg = zeta*fn23; - za = std::sqrt(suma); - *zeta2 = std::sqrt(w2)*fnu; - *zeta1 = (*zeta2) * (1. + zeta*za*ex2); - za = za + za; - *phi = std::sqrt(za)*rfn13; - if (ipmtr == 1) { return; } - // - // SUM SERIES FOR ASUM AND BSUM - // - sumb = 0.0; - for (k = 1; k < (kmax+1); k++) { - sumb += p[k-1]*zunhj_beta[k-1]; - } - *asum = 0.0; - *bsum = sumb; - l1 = 0; - l2 = 30; - btol = tol * (fabs(std::real(*bsum)) + fabs(std::imag(*bsum))); - atol = tol; - pp = 1.0; - ias = 0; - ibs = 0; - if (rfnu2 < tol) { - /* 110 */ - *asum += 1.; - *bsum *= rfnu*rfn13; - /* 120 */ - return; - } - for (int is = 2; is < 8; is++) - { - atol /= rfnu2; - pp *= rfnu2; - if (ias != 1) { - suma = 0.0; - for (k = 1; k < (kmax+1); k++) - { - m = l1 + k; - suma += p[k-1]*zunhj_alfa[m-1]; - if (ap[k-1] < atol) { break; } - } - *asum += suma*pp; - if (pp < tol) { ias = 1; } - } - if (ibs != 1) { - sumb = 0.0; - for (k = 1; k < (kmax+1); k++) - { - m = l2 + k; - sumb += p[k-1]*zunhj_beta[m-1]; - if (ap[k-1] < atol) { break; } - } - *bsum += sumb*pp; - if (pp < btol) { ibs = 1; } - } - if ((ias == 1) && (ibs == 1)) { break; } - l1 += 30; - l2 += 30; - } - *asum += 1.; - *bsum *= rfnu*rfn13; - return; - } else { - // - // ABS(W2) > 0.25 - w = std::sqrt(w2); - wr = std::real(w); - wi = std::imag(w); - if (wr < 0) { wr = 0.;} - if (wi < 0) { wi = 0.;} - za = (1. + w) / zb; - zc = std::log(za); - zcr = std::real(zc); - zci = std::imag(zc); - if (zci < 0) { zci = 0.;} - if (zci > hpi) { zci = hpi;} - if (zcr < 0) { zcr = 0.;} - zc = std::complex(zcr, zci); - zth = (zc-w)*1.5; - cfnu = fnu; - *zeta1 = zc*cfnu; - *zeta2 = w*cfnu; - azth = std::abs(zth); - zthr = std::real(zth); - zthi = std::imag(zth); - ang = thpi; - if ((zthr < 0.) || (zthi >= 0.)) { - ang = hpi; - if (zthr != 0.) { - ang = atan(zthi/zthr); - if (zthr < 0.) { ang += pi; } - } - } - pp = pow(azth, ex2); - ang *= ex2; - zetar = pp * cos(ang); - zetai = pp * sin(ang); - if (zetai < 0.) { zetai = 0.; } - zeta = std::complex(zetar, zetai); - *arg = zeta*fn23; - rtzta = zth / zeta; - za = rtzta / w; - *phi = std::sqrt(za + za) * rfn13; - if (ipmtr == 1) { return; } - tfn = rfnu / w; - rzth = rfnu / zth; - zc = rzth * zunhj_ar[1]; - t2 = 1. / w2; - up[1] = (t2*zunhj_c[1] + zunhj_c[2])*tfn; - *bsum = up[1] + zc; - *asum = 0.; - - if (rfnu < tol) { - *asum += 1.; - *bsum *= -rfn13 / rtzta; - return; - } - - przth = rzth; - ptfn = tfn; - up[0] = 1.0; - pp = 1.0; - bsumr = std::real(*bsum); - bsumi = std::imag(*bsum); - btol = tol * (fabs(bsumr) + fabs(bsumi)); - ks = 0; - kp1 = 2; - l = 3; - ias = 0; - ibs = 0; - - for (int lr = 2; lr < 13; lr += 2) - { - lrp1 = lr + 1; - // - // COMPUTE TWO ADDITIONAL CR, DR, AND UP FOR TWO MORE TERMS IN - // NEXT SUMA AND SUMB - // - for (k = lr; k < (lrp1+1); k++) - { - ks += 1; - kp1 += 1; - l += 1; - za = zunhj_c[l-1]; - for (j = 2; j < (kp1+1); j++) - { - l += 1; - za = za*t2 + zunhj_c[l-1]; - } - ptfn *= tfn; - up[kp1-1] = ptfn*za; - cr[ks-1] = przth*zunhj_br[ks]; - przth *= rzth; - dr[ks-1] = przth*zunhj_ar[ks+1]; - } - pp *= rfnu2; - if (ias != 1) { - suma = up[lr]; - ju = lrp1; - for (int jr = 1; jr < lrp1; jr++) - { - ju -= 1; - suma += cr[jr-1] * up[ju-1]; - } - *asum += suma; - asumr = std::real(*asum); - asumi = std::imag(*asum); - test = fabs(asumr) + fabs(asumi); - if ((pp < tol) && (test < tol)) { ias = 1; } - } - if (ibs != 1) { - sumb = up[lr+1] + up[lr]*zc; - ju = lrp1; - for (int jr = 1; jr < lrp1; jr++) - { - ju -= 1; - sumb += dr[jr-1] * up[ju-1]; - } - *bsum += sumb; - bsumr = std::real(*bsum); - bsumi = std::imag(*bsum); - test = fabs(bsumr) + fabs(bsumi); - if ((pp < tol) && (test < tol)) { ibs = 1; } - } - if ((ias == 1) && (ibs == 1)) { break; } - } - *asum += 1.; - *bsum *= -rfn13 / rtzta; - return; - } -} - - -inline void uni1( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int *nz, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNI1 - //***REFER TO ZBESI,ZBESK - // - // ZUNI1 COMPUTES I(FNU,Z) BY MEANS OF THE UNIFORM ASYMPTOTIC - // EXPANSION FOR I(FNU,Z) IN -PI/3.LE.ARG Z.LE.PI/3. - // - // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC - // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. - // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER - // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. - // Y(I)=CZERO FOR I=NLAST+1,N - // - //***ROUTINES CALLED ZUCHK,ZUNIK,ZUOIK,D1MACH,AZABS - //***END PROLOGUE ZUNI1 - - std::complex c2, phi, rz, sum, s1, s2, zeta1 = 0, zeta2 = 0; - double aphi, ascle, c1r, crsc, cscl, fn, rs1; - int i, iflag, init, k, m, nd, nn, nuf; - std::complex cwrk[16] = { 0. }; - std::complex cy[2] = { 0. }; - *nz = 0; - nd = n; - *nlast = 0; - // - // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN - // MAGNITUDE ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, - // EXP(ALIM)=EXP(ELIM)*TOL - // - cscl = 1.0 / tol; - crsc = tol; - double css[3] = {cscl, 1., crsc}; - double csr[3] = {crsc, 1., cscl}; - double bry[3] = {1e3*d1mach[0]/tol, 0., 0.}; - bry[1] = 1.0 / bry[0]; - bry[2] = d1mach[1]; - // - // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER - // - fn = fmax(fnu, 1.0); - init = 0; - unik(z, fn, 1, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - if (kode != 1) { - s1 = -zeta1 + fn*(fn / (z + zeta2)); - } else { - s1 = -zeta1 + zeta2 ; - } - - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0) { - *nz = -1; - return; - } - *nz = n; - for (i = 0; i < n; i++) { y[i] = 0.0; } - } -L30: - nn = ( nd > 2 ? 2 : nd); - for (i = 1; i < (nn+1); i++) - { - fn = fnu + nd - i; - init = 0; - unik(z, fn, 1, 0, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - if (kode != 1) { - s1 = -zeta1 + fn*(fn / (z + zeta2)) + std::complex(0.0, std::imag(z)); - } else { - s1 = -zeta1 + zeta2; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L110; } - if (i == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L110; } - if (i == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (i == 1) { iflag = 3; } } - } - /* 60 */ - // - // SCALE S1 IF CABS(S1) < ASCLE - // - s2 = phi*sum; - s1 = exp(std::real(s1))*css[iflag-1]*std::complex(cos(std::imag(s1)), sin(std::imag(s1))); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { goto L110; } } - /* 70 */ - cy[i-1] = s2; - m = nd - i + 1; - y[m-1] = s2*csr[iflag-1]; - } - /* 80 */ - if (nd > 2) { - rz = 1.0 / z; - s1 = cy[0]; - s2 = cy[1]; - c1r = csr[iflag-1]; - ascle = bry[iflag-1]; - k = nd - 2; - fn = k; - for (i = 3; i < (nd+1); i++) - { - c2 = s2; - s2 = s1 + (fnu + fn)*rz*c2; - s1 = c2; - c2 = s2*c1r; - y[k-1] = c2; - k -= 1; - fn -= 1.0; - if (iflag >= 3) { continue; } - if (fmax(fabs(std::real(c2)), fabs(std::imag(c2))) <= ascle) { continue; } - iflag += 1; - ascle = bry[iflag-1]; - s1 *= c1r; - s2 = c2; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - c1r = csr[iflag-1]; - } - /* 90 */ - } - /* 100 */ - return; -L110: - if (rs1 > 0.0) { *nz = -1; return; } - y[nd - 1] = 0.0; - *nz += 1; - nd -= 1; - if (nd == 0) { return; } - nuf = uoik(z, fnu, kode, 1, nd, y, tol, elim, alim); - if (nuf < 0) { *nz = -1; return; } - nd -= nuf; - *nz += nuf; - if (nd == 0) { return; } - fn = fnu + nd - 1; - if (fn >= fnul) { goto L30; } - *nlast = nd; - return; -} - - -inline void uni2( - std::complex z, - double fnu, - int kode, - int n, - std::complex *y, - int *nz, - int *nlast, - double fnul, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNI2 - //***REFER TO ZBESI,ZBESK - // - // ZUNI2 COMPUTES I(FNU,Z) IN THE RIGHT HALF PLANE BY MEANS OF - // UNIFORM ASYMPTOTIC EXPANSION FOR J(FNU,ZN) WHERE ZN IS Z*I - // OR -Z*I AND ZN IS IN THE RIGHT HALF PLANE ALSO. - // - // FNUL IS THE SMALLEST ORDER PERMITTED FOR THE ASYMPTOTIC - // EXPANSION. NLAST=0 MEANS ALL OF THE Y VALUES WERE SET. - // NLAST.NE.0 IS THE NUMBER LEFT TO BE COMPUTED BY ANOTHER - // FORMULA FOR ORDERS FNU TO FNU+NLAST-1 BECAUSE FNU+NLAST-1.LT.FNUL. - // Y(I)=CZERO FOR I=NLAST+1,N - // - //***ROUTINES CALLED ZAIRY,ZUCHK,ZUNHJ,ZUOIK,D1MACH,AZABS - //***END PROLOGUE ZUNI2 - - std::complex ai, arg, asum, bsum, cfn, cid, crsc, cscl, c1, c2, dai, phi, rz,\ - s1, s2, zb, zeta1, zeta2, zn, zar; - double aarg, ang, aphi, ascle, ay, c2i, c2m, c2r, fn, rs1, yy; - int i, iflag, in, inu, j, k, nai, nd, ndai, nn, nuf, idum; - double hpi = 1.57079632679489662; /* 0.5 pi */ - double aic = 1.265512123484645396; /* log(2 sqrt(pi)) */ - std::complex cip[4] = { 1.0, std::complex(0, 1), -1.0, -std::complex(0, 1) }; - std::complex ci = std::complex(0, 1); - // - // COMPUTED VALUES WITH EXPONENTS BETWEEN ALIM AND ELIM IN MAGNITUDE - // ARE SCALED TO KEEP INTERMEDIATE ARITHMETIC ON SCALE, - // EXP(ALIM) = EXP(ELIM)*TOL - // - cscl = 1.0 / tol; - crsc = tol; - std::complex csr[3] = { crsc, 1.0, cscl }; - std::complex css[3] = { cscl, 1.0, crsc }; - double bry[3] = { 1e3*d1mach[0]/tol, 0.0, 0.0 }; - std::complex cy[2] = { 0.0 }; - yy = std::imag(z); - *nz = 0; - nd = n; - *nlast = 0; - // - // ZN IS IN THE RIGHT HALF PLANE AFTER ROTATION BY CI OR -CI - // - zn = -z * ci; - zb = z; - cid = -ci; - inu = (int)fnu; - ang = hpi * (fnu - inu); - c2 = std::complex(cos(ang), sin(ang)); - zar = c2; - in = inu + n - 1; - in = in % 4; - c2 *= cip[in]; - if (yy <= 0.0) { - zn = conj(-zn); - zb = conj(zb); - cid = -cid; - c2 = conj(c2); - } - // - // CHECK FOR UNDERFLOW AND OVERFLOW ON FIRST MEMBER - // - fn = fmax(fnu, 1.0); - unhj(zn, fn, 0, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - if (kode != 1) { - cfn = fnu; - s1 = -zeta1 + cfn*(cfn/(zb + zeta2)); - } else { - s1 = -zeta1 + zeta2; - } - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0.) { - *nz = -1; - return; - } - for (i = 0; i < n; i++) { - y[i] = 0.0; - } - return; - } -L10: - nn = (nd > 2 ? 2 : nd); - i = 1; - for (i = 1; i < (nn+1); i++) - { - fn = fnu + (nd-i); - unhj(zn, fn, 0, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - if (kode != 1) { - cfn = fn; - ay = fabs(yy); - s1 = -zeta1 + cfn*(cfn/(zb + zeta2)) + ay*std::complex(0, 1); - } else { - s1 = -zeta1 + zeta2; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L50; } - if (i == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi); - aarg = std::abs(arg); - rs1 += log(aphi) - 0.25*log(aarg) - aic; - if (fabs(rs1) > elim) { goto L50; } - if (i == 1) { iflag = 1; } - if (rs1 >= 0.0){ if (i== 1) { iflag = 3; }} - } - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - ai = airy(arg, 0, 2, &nai, &idum); - dai = airy(arg, 1, 2, &ndai, &idum); - s2 = phi * (ai*asum + dai*bsum); - c2r = std::exp(std::real(s1))*std::real(css[iflag-1]); - c2i = std::imag(s1); - s1 = c2r*std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { goto L50; } } - if (yy <= 0.0) { s2 = conj(s2); } - j = nd - i + 1; - s2 *= c2; - cy[i-1] = s2; - y[j-1] = s2*csr[iflag-1]; - c2 *= cid; - } - if (nd > 2) { - rz = 2.0 / z; - bry[1] = 1.0 / bry[0]; - bry[2] = d1mach[1]; - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[iflag-1]; - ascle = bry[iflag-1]; - k = nd - 2; - fn = k; - for (i = 3; i < (nd+1); i++) { - c2 = s2; - s2 = s1 + (fnu+fn)*rz*s2; - s1 = c2; - c2 = s2*c1; - y[k-1] = c2; - k -= 1; - fn -= 1.0; - if (iflag < 3) { - c2r = fabs(std::real(c2)); - c2i = fabs(std::imag(c2)); - c2m = fmax(c2r, c2i); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - c1 = csr[iflag-1]; - } - } - } - } - return; -L50: - if (rs1 <= 0.0) { - // - // SET UNDERFLOW AND UPDATE PARAMETERS - // - y[nd-1] = 0.0; - nz += 1; - nd -= 1; - if (nd == 0) { return; } - nuf = uoik(z, fnu, kode, 1, nd, y, tol, elim, alim); - if (nuf >= 0) { - nd -= nuf; - nz += nuf; - if (nd == 0) { return; } - fn = fnu + nd - 1; - if (fn >= fnul) { - // The following was commented out in the original F77 code - // C FN = CIDI - // C J = NUF + 1 - // C K = MOD(J,4) + 1 - // C S1R = CIPR(K) - // C S1I = CIPI(K) - // C IF (FN.LT.0.0D0) S1I = -S1I - // C STR = C2R*S1R - C2I*S1I - // C C2I = C2R*S1I + C2I*S1R - // C C2R = STR - in = (inu + nd - 1) % 4; - c2 = zar*cip[in]; - if (yy <= 0.0) { c2 = conj(c2); } - goto L10; - } - *nlast = nd; - return; - } - } - *nz = -1; - return; -} - - -inline void unik( - std::complex zr, - double fnu, - int ikflg, - int ipmtr, - double tol, - int *init, - std::complex *phi, - std::complex *zeta1, - std::complex *zeta2, - std::complex *total, - std::complex *cwrk -) { - - //***BEGIN PROLOGUE ZUNIK - //***REFER TO ZBESI,ZBESK - // - // ZUNIK COMPUTES PARAMETERS FOR THE UNIFORM ASYMPTOTIC - // EXPANSIONS OF THE I AND K FUNCTIONS ON IKFLG= 1 OR 2 - // RESPECTIVELY BY - // - // W(FNU,ZR) = PHI*EXP(ZETA)*SUM - // - // WHERE ZETA=-ZETA1 + ZETA2 OR - // ZETA1 - ZETA2 - // - // THE FIRST CALL MUST HAVE INIT=0. SUBSEQUENT CALLS WITH THE - // SAME ZR AND FNU WILL RETURN THE I OR K FUNCTION ON IKFLG= - // 1 OR 2 WITH NO CHANGE IN INIT. CWRK IS A COMPLEX WORK - // ARRAY. IPMTR=0 COMPUTES ALL PARAMETERS. IPMTR=1 COMPUTES PHI, - // ZETA1,ZETA2. - // - //***ROUTINES CALLED ZDIV,AZLOG,AZSQRT,D1MACH - //***END PROLOGUE ZUNIK - - std::complex cfn, crfn, s, sr, t, t2, zn; - double ac, rfn, test, tstr, tsti; - int i, j, k, l; - /* ( 1/sqrt(2 PI), sqrt(PI/2) ) */ - double con[2] = { 3.98942280401432678e-01, 1.25331413731550025 }; - - if (*init == 0) { - rfn = 1. / fnu; - crfn = rfn; - - tstr = std::real(zr); - tsti = std::imag(zr); - test = d1mach[0] * 1e3; - ac = fnu * test; - if ((fabs(tstr) <= ac) && (fabs(tsti) <= ac)) { - ac = 2.0 * fabs(log(test)) + fnu; - *zeta1 = ac; - *zeta2 = fnu; - *phi = 1.0; - } - t = zr * crfn; - s = 1.0 + t*t; - sr = std::sqrt(s); - cfn = fnu; - zn = (1. + sr) / t; - *zeta1 = cfn * std::log(zn); - *zeta2 = cfn * sr; - t = 1.0 / sr; - sr = t*crfn; - cwrk[15] = std::sqrt(sr); - *phi = cwrk[15]*con[ikflg-1]; - if (ipmtr != 0) { return; } - t2 = 1. / s; - cwrk[0] = 1.; - crfn = 1.; - ac = 1.; - l = 1; - k = 2; - for (k = 2; k < 16; k++) - { - s = 0.0; - for (j = 1; j < (k+1); j++) - { - l += 1; - s = s*t2 + zunik_c[l-1]; - } - crfn *= sr; - cwrk[k-1] = crfn*s; - ac *= rfn; - tstr = std::real(cwrk[k-1]); - tsti = std::imag(cwrk[k-1]); - test = fabs(tstr) + fabs(tsti); - if ((ac < tol) && (test < tol)) { - break; - } - } - /* Guard against exhausted loop */ - if (k == 16) { k-=1; } - *init = k; - } - - *total = 0.0; - t = 1.0; - if (ikflg != 2) { - - for (i = 0; i < (*init); i++) { - *total += cwrk[i]; - } - *phi = cwrk[15] * con[0]; - - } else { - - for (i = 1; i < (*init + 1); i++) { - *total += t * cwrk[i-1]; - t = -t; - } - *phi = cwrk[15] * con[1]; - - } - return; -} - - -inline int unk1( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNK1 - //***REFER TO ZBESK - // - // ZUNK1 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE - // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE - // UNIFORM ASYMPTOTIC EXPANSION. - // MR INDICATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. - // NZ=-1 MEANS AN OVERFLOW WILL OCCUR - // - //***ROUTINES CALLED ZKSCL,ZS1S2,ZUCHK,ZUNIK,D1MACH,AZABS - //***END PROLOGUE ZUNK1 - - std::complex cfn, ck, crsc, cs, cscl, csgn, cspn, c1, c2, rz, s1, s2, zr,\ - phid, zeta1d = 0.0, zeta2d = 0.0, sumd; - double ang, aphi, asc, ascle, c2i, c2m, c2r, fmr, fn, fnf, rs1, sgn, x; - int i, ib, iflag = 0, ifn, il, inu, iuf, k, kdflg, kflag, kk, m, nw, nz, j,\ - jc, ipard, initd, ic; - - cscl = 1.0 / tol; - crsc = tol; - std::complex css[3] = {cscl, 1.0, crsc }; - std::complex csr[3] = {crsc, 1.0, cscl }; - std::complex cwrk[3][16] = {{ 0.0 }}; - std::complex phi[2] = { 0.0 }; - std::complex sum[2] = { 0.0 }; - std::complex zeta1[2] = { 0.0 }; - std::complex zeta2[2] = { 0.0 }; - std::complex cy[2] = { 0.0 }; - double bry[3] = { 1e3*d1mach[0] / tol, tol / 1e3*d1mach[0], d1mach[1]}; - int init[2] = { 0 }; - double pi = 3.14159265358979324; - - kdflg = 1; - kflag = 1; - fn = fnu; - nz = 0; - x = std::real(z); - zr = z; - if (x < 0.0) { zr = -z; } - j = 2; - for (i = 1; i < (n+1); i++) - { - j = 3 - j; /* j flip flops between 1, 2 */ - jc = j - 1; /* dummy index for 0-indexing */ - fn = fnu + (i - 1); - init[jc] = 0; - unik(zr, fn, 2, 0, tol, &init[jc], &phi[jc], &zeta1[jc], &zeta2[jc], &sum[jc], &cwrk[jc][0]); - if (kode != 1) { - cfn = fn; - s1 = zeta1[jc] - cfn*(cfn / (zr + zeta2[jc])); - } else { - s1 = zeta1[jc] - zeta2[jc]; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (kdflg == 1) { kflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi[jc]); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L10; } - if (kdflg == 1) { kflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { kflag = 3; } } - } - - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - s2 = phi[jc]*sum[jc]; - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r)*std::real(css[kflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (!((kflag == 1) && (uchk(s2, bry[0], tol)))) { - cy[kdflg-1] = s2; - y[i-1] = s2*csr[kflag-1]; - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - } -L10: - if (rs1 > 0.0 ) { return -1; } - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - nz += 1; - if (i > 1) { - if (y[i-2] != 0.0) { - y[i-2] = 0.0; - nz += 1; - } - } - } - /* Check for exhausted loop */ - if (i == (n+1)) { i = n; } - - rz = 2.0 / zr; - ck = fn * rz; - ib = i + 1; - if (n >= ib) { - // - // TEST LAST MEMBER FOR UNDERFLOW AND OVERFLOW, SET SEQUENCE TO ZERO - // ON UNDERFLOW - // - fn = fnu + (n-1); - ipard = 1; - if (mr != 0) { ipard = 0; } - initd = 0; - unik(zr, fn, 2, ipard, tol, &initd, &phid, &zeta1d, &zeta2d, &sumd, &cwrk[2][0]); - if (kode != 1) { - cfn = fn; - s1 = zeta1d - cfn*(cfn / (zr + zeta2d)); - } else { - s1 = zeta1d - zeta2d; - } - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (fabs(rs1) < alim) { goto L50; } - // - // REFINE ESTIMATE AND TEST - // - aphi = std::abs(phid); - rs1 += log(aphi); - if (fabs(rs1) < elim) { goto L50; } - } - if (rs1 > 0.0) { return -1; } - // - // FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW - // - if (x < 0.0) { return -1; } - nz = n; - for (i = 0; i < (n+1); i++) { y[i] = 0.0; } - return nz; -L50: - // - // RECUR FORWARD FOR REMAINDER OF THE SEQUENCE - // - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = ib; i < (n+1); i++) - { - c2 = s2; - s2 = ck*s2 + s1; - s1 = c2; - ck += rz; - c2 = s2*c1; - y[i-1] = c2; - if (kflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - c1 = csr[kflag-1]; - } - } - } - } - if (mr == 0) { return nz; } - // - // ANALYTIC CONTINUATION FOR RE(Z) < 0.0 - // - nz = 0; - fmr = mr; - sgn = (fmr < 0.0 ? pi : -pi ); - // - // CSPN AND CSGN ARE COEFF OF K AND I FUNCIONS RESP. - // - csgn = std::complex(0.0, sgn); - inu = (int)fnu; - fnf = fnu - inu; - ifn = inu + n - 1; - ang = fnf * sgn; - cspn = std::complex(cos(ang), sin(ang)); - if (ifn % 2 == 1) { cspn = -cspn; } - asc = bry[0]; - kk = n; - iuf = 0; - kdflg = 1; - ib -= 1; - ic = ib - 1; - k = 1; - for (k = 1; k < (n+1); k++) - { - fn = fnu + (kk-1); - // - // LOGIC TO SORT OUT CASES WHOSE PARAMETERS WERE SET FOR THE K - // FUNCTION ABOVE - // - m = 3; - if (n > 2) { goto L80; } -L70: - initd = init[j-1]; - phid = phi[j -1]; - zeta1d = zeta1[j-1]; - zeta2d = zeta2[j-1]; - sumd = sum[j-1]; - m = j; - j = 3 - j; - goto L90; -L80: - if (!((kk == n) && (ib < n))) { - if ((kk == ib) || (kk == ic)){ goto L70; } - initd = 0; - } -L90: - unik(zr, fn, 1, 0, tol, &initd, &phid, &zeta1d, &zeta2d, &sumd, &cwrk[m-1][0]); - if (kode != 1) { - cfn = fn; - s1 = -zeta1d + cfn * (cfn/(zr + zeta2d)); - } else { - s1 = -zeta1d + zeta2d; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { goto L110; } - if (kdflg == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phid); - rs1 += log(aphi); - if (fabs(rs1) > elim) { goto L110; } - if (kdflg == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { iflag = 3; } } - } - - s2 = csgn * phid * sumd; - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[iflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { s2 = 0.0; } } -L100: - cy[kdflg -1] = s2; - c2 = s2; - s2 *= csr[iflag-1]; - // - // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N - // - s1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &s1, &s2, asc, alim, &iuf); - nz += nw; - } - y[kk-1] = s1*cspn + s2; - kk -= 1; - cspn = -cspn; - if (c2 == 0.0) { - kdflg = 1; - continue; - } - if (kdflg == 2) { goto L130; } - kdflg = 2; - continue; -L110: - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L100; - } - /* If loop is exhausted */ - if (k == n+1) { k -= 1; } -L130: - il = n-k; - if (il == 0) { return nz; }; - s1 = cy[0]; - s2 = cy[1]; - cs = csr[iflag-1]; - ascle = bry[iflag-1]; - fn = inu + il; - for (i = 1; i < (il+1); i++) - { - c2 = s2; - s2 = s1 + (fn + fnf) * rz * s2; - s1 = c2; - fn -= 1.0; - c2 = s2 * cs; - ck = c2; - c1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &c1, &c2, asc, alim, &iuf); - nz = nz + nw; - } - y[kk-1] = c1 * cspn + c2; - kk -= 1; - cspn = -cspn; - if (iflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cs; - s2 = ck; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - cs = csr[iflag-1]; - } - } - } - return nz; -} - - -inline int unk2( - std::complex z, - double fnu, - int kode, - int mr, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUNK2 - //***REFER TO ZBESK - // - // ZUNK2 COMPUTES K(FNU,Z) AND ITS ANALYTIC CONTINUATION FROM THE - // RIGHT HALF PLANE TO THE LEFT HALF PLANE BY MEANS OF THE - // UNIFORM ASYMPTOTIC EXPANSIONS FOR H(KIND,FNU,ZN) AND J(FNU,ZN) - // WHERE ZN IS IN THE RIGHT HALF PLANE, KIND=(3-MR)/2, MR=+1 OR - // -1. HERE ZN=ZR*I OR -ZR*I WHERE ZR=Z IF Z IS IN THE RIGHT - // HALF PLANE OR ZR=-Z IF Z IS IN THE LEFT HALF PLANE. MR INDIC- - // ATES THE DIRECTION OF ROTATION FOR ANALYTIC CONTINUATION. - // NZ=-1 MEANS AN OVERFLOW WILL OCCUR - // - //***ROUTINES CALLED ZAIRY,ZKSCL,ZS1S2,ZUCHK,ZUNHJ,D1MACH,AZABS - //***END PROLOGUE ZUNK2 - - std::complex ai, cfn, ck, cs, csgn, cspn, c1, c2, dai, rz, s1, s2,\ - zb, zn, zr, phid, argd, zeta1d, zeta2d, asumd, bsumd; - double aarg, ang, aphi, asc, ascle, car, cpn, c2i, c2m, c2r, crsc, cscl,\ - fmr, fn, fnf, rs1, sar, sgn, spn, x, yy; - int i, ib, iflag = 0, ifn, il, in, inu, iuf, k, kdflg, kflag, kk, nai, ndai,\ - nw, nz, idum, j, ipard, ic; - - std::complex cr1 = std::complex(1.0, 1.73205080756887729); /* 1 + sqrt(3)i */ - std::complex cr2 = std::complex(-0.5, -8.66025403784438647e-1); /* 0.5 cr1 */ - double hpi = 1.57079632679489662; /* 0.5 pi */ - double pi = 3.14159265358979324; - double aic = 1.26551212348464539; /* log(2 sqrt(pi)) */ - std::complex cip[4] = {1.0, -std::complex(0, 1), -1.0, std::complex(0, 1)}; - cscl = 1.0 / tol; - crsc = tol; - std::complex css[3] = {cscl, 1.0, crsc }; - std::complex csr[3] = {crsc, 1.0, cscl }; - std::complex phi[2] = { 0.0 }; - std::complex arg[2] = { 0.0 }; - std::complex zeta1[2] = { 0.0 }; - std::complex zeta2[2] = { 0.0 }; - std::complex asum[2] = { 0.0 }; - std::complex bsum[2] = { 0.0 }; - std::complex cy[2] = { 0.0 }; - double bry[3] = { (1.0 + 1e3*d1mach[0] / tol), 1.0/(1.0 + 1e3*d1mach[0] / tol), d1mach[1]}; - - kdflg = 1; - kflag = 1; - fn = fnu; - nz = 0; - // - // EXP(-ALIM)=EXP(-ELIM)/TOL=APPROX. ONE PRECISION GREATER THAN - // THE UNDERFLOW LIMIT - // - x = std::real(z); - zr = z; - if (x < 0.0) { zr = -z; } - yy = std::imag(zr); - zn = -zr*std::complex(0, 1); - zb = zr; - inu = (int)fnu; - fnf = fnu - inu; - ang = -hpi * fnf; - car = cos(ang); - sar = sin(ang); - cpn = hpi * car; - spn = hpi * sar; - c2 = std::complex(spn, -cpn); - kk = (inu % 4) + 1; - cs = cr1 * c2 * cip[kk - 1]; - if (yy <= 0.0) { - zn = conj(-zn); - zb = conj(zb); - } - // - // K(FNU,Z) IS COMPUTED FROM H(2,FNU,-I*Z) WHERE Z IS IN THE FIRST - // QUADRANT. FOURTH QUADRANT VALUES (YY <= 0.0_dp) ARE COMPUTED BY - // CONJUGATION SINCE THE K FUNCTION IS REAL ON THE POSITIVE REAL AXIS - // - j = 2; - for (i = 1; i < (n+1); i++) - { - j = 3 - j; - fn = fnu + (i-1); - unhj(zn, fn, 0, tol, &phi[j-1], &arg[j-1], &zeta1[j-1], &zeta2[j-1], &asum[j-1], &bsum[j-1]); - if (kode != 1) { - cfn = fn; - s1 = zeta1[j-1] - cfn*(cfn/(zb + zeta2[j-1])); - } else { - s1 = zeta1[j-1] - zeta2[j-1]; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (kdflg == 1) { kflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phi[j-1]); - aarg = std::abs(arg[j-1]); - rs1 += log(aphi) - 0.25 * log(aarg) - aic; - if (fabs(rs1) > elim) { - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - if (kdflg == 1) { kflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) { kflag = 3; } } - } - // - // SCALE S1 TO KEEP INTERMEDIATE ARITHMETIC ON SCALE NEAR - // EXPONENT EXTREMES - // - c2 = arg[j-1] * cr2; - ai = airy(c2, 0, 2, &nai, &idum); - dai = airy(c2, 1, 2, &ndai, &idum); - s2 = cs * phi[j-1] * (ai*asum[j-1] + cr2*dai*bsum[j-1]); - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[kflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (kflag == 1) { - if (uchk(s2, bry[0], tol)) { - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - } - if (yy <= 0.0) { s2 = conj(s2); } - cy[kdflg-1] = s2; - y[i-1] = s2 * csr[kflag-1]; - cs *= -std::complex(0, 1); - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - /* GO TO 70 */ - if (rs1 > 0.0) { return -1; } - /* FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW */ - if (x < 0.0) { return -1; } - kdflg = 1; - y[i-1] = 0.0; - cs *= -std::complex(0, 1); - nz += 1; - if (i != 1) { if (y[i-2] != 0.0) { y[i-2] = 0.0;nz += 1; } } - continue; - } - /* Check for exhausted loop */ - if (i == n+1) { i = n; } - - rz = 2.0 / zr; - ck = fn * rz; - ib = i + 1; - if (n >= ib) { - fn = fnu + (n - 1); - ipard = 1; - if (mr != 0) { ipard = 0; } - unhj(zn, fn, ipard, tol, &phid, &argd, &zeta1d, &zeta2d, &asumd, &bsumd); - if (kode != 1) { - cfn = fn; - s1 = zeta1d - cfn * (cfn / (zb + zeta2d)); - } else { - s1 = zeta1d - zeta2d; - } - rs1 = std::real(s1); - if (fabs(rs1) <= elim) { - if (fabs(rs1) < alim) { goto L120; } - // - // REFINE ESTIMATE AND TEST - // - aphi = std::abs(phid); - aarg = std::abs(argd); - rs1 += log(aphi) - 0.25 * log(aarg) - aic; - if (fabs(rs1) < elim) { goto L120; } - } - if (rs1 > 0.0) { return -1; } - // - // FOR X < 0.0, THE I FUNCTION TO BE ADDED WILL OVERFLOW - // - if (x < 0.0) { return -1; } - nz = n; - for (i = 0; i < n; i++) { y[i] = 0.0; } - return nz; -L120: - // - // SCALED FORWARD RECURRENCE FOR REMAINDER OF THE SEQUENCE - // - s1 = cy[0]; - s2 = cy[1]; - c1 = csr[kflag-1]; - ascle = bry[kflag-1]; - for (i = ib; i < (n+1); i++) - { - c2 = s2; - s2 = ck * s2 + s1; - s1 = c2; - ck += rz; - c2 = s2 * c1; - y[i-1] = c2; - if (kflag < 3) { - c2m = fmax(fabs(std::real(c2)), fabs(std::imag(c2))); - if (c2m > ascle) { - kflag += 1; - ascle = bry[kflag-1]; - s1 *= c1; - s2 = c2; - s1 *= css[kflag-1]; - s2 *= css[kflag-1]; - c1 = csr[kflag-1]; - } - } - } - } - if (mr == 0) { return nz; } - // - // ANALYTIC CONTINUATION FOR RE(Z) < 0.0 - // - nz = 0; - fmr = mr; - sgn = ( fmr < 0.0 ? pi : -pi); - // - // CSPN AND CSGN ARE COEFF OF K AND I FUNCTIONS RESP. - // - csgn = std::complex(0.0, sgn); - if (yy <= 0.0) { csgn = -csgn; } - ifn = inu + n - 1; - ang = fnf*sgn; - cspn = std::complex(cos(ang), sin(ang)); - if (ifn % 2 == 1) { cspn = -cspn; } - // - // CS=COEFF OF THE J FUNCTION TO GET THE I FUNCTION. I(FNU,Z) IS - // COMPUTED FROM EXP(I*FNU*HPI)*J(FNU,-I*Z) WHERE Z IS IN THE FIRST - // QUADRANT. FOURTH QUADRANT VALUES (YY <= 0.0_dp) ARE COMPUTED BY - // CONJUGATION SINCE THE I FUNCTION IS REAL ON THE POSITIVE REAL AXIS - // - cs = std::complex(car, -sar) * csgn; - in = (ifn % 4) + 1; - c2 = cip[in-1]; - cs *= conj(c2); - asc = bry[0]; - iuf = 0; - kk = n; - kdflg = 1; - ib -= 1; - ic = ib - 1; - for (k = 1; k < (n+1); k++) { - fn = fnu + (kk-1); - if (n > 2) { goto L175; } -L172: - phid = phi[j-1]; - argd = arg[j-1]; - zeta1d = zeta1[j-1]; - zeta2d = zeta2[j-1]; - asumd = asum[j-1]; - bsumd = bsum[j-1]; - j = 3 - j; - goto L210; -L175: - if (!((kk == n) && (ib < n))) { - if ((kk == ib) || (kk == ic)) { goto L172; } - unhj(zn, fn, 0, tol, &phid, &argd, &zeta1d, &zeta2d, &asumd, &bsumd); - } -L210: - if (kode != 1) { - cfn = fn; - s1 = -zeta1d + cfn * (cfn/(zb + zeta2d)); - } else { - s1 = -zeta1d + zeta2d; - } - // - // TEST FOR UNDERFLOW AND OVERFLOW - // - rs1 = std::real(s1); - if (fabs(rs1) > elim) { - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L250; - } - if (kdflg == 1) { iflag = 2; } - if (fabs(rs1) >= alim) { - // - // REFINE TEST AND SCALE - // - aphi = std::abs(phid); - aarg = std::abs(argd); - rs1 += log(aphi) - 0.25f * log(aarg) - aic; - if (fabs(rs1) > elim) { - if (rs1 > 0.0) { return -1; } - s2 = 0.0; - goto L250; - } - if (kdflg == 1) { iflag = 1; } - if (rs1 >= 0.0) { if (kdflg == 1) {iflag = 3;} } - } - - ai = airy(argd, 0, 2, &nai, &idum); - dai = airy(argd, 1, 2, &ndai, &idum); - s2 = cs * phid * (ai*asumd + dai*bsumd); - c2r = std::real(s1); - c2i = std::imag(s1); - c2m = exp(c2r) * std::real(css[iflag-1]); - s1 = c2m * std::complex(cos(c2i), sin(c2i)); - s2 *= s1; - if (iflag == 1) { if (uchk(s2, bry[0], tol)) { s2 = 0.0; } } -L250: - if (yy <= 0.0) { s2 = conj(s2); } - cy[kdflg-1] = s2; - c2 = s2; - s2 *= csr[iflag-1]; - // - // ADD I AND K FUNCTIONS, K SEQUENCE IN Y(I), I=1,N - // - s1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &s1, &s2, asc, alim, &iuf); - nz += nw; - } - y[kk-1] = s1 * cspn + s2; - kk -= 1; - cspn = -cspn; - cs *= -std::complex(0, 1); - if (c2 == 0.0) { - kdflg = 1; - continue; - } - if (kdflg == 2) { break; } - kdflg = 2; - continue; - } - /* Check for exhausted loop */ - if (k == n+1) { k = n; } - - il = n - k; - if (il == 0) { return nz; } - // - // RECUR BACKWARD FOR REMAINDER OF I SEQUENCE AND ADD IN THE - // K FUNCTIONS, SCALING THE I SEQUENCE DURING RECURRENCE TO KEEP - // INTERMEDIATE ARITHMETIC ON SCALE NEAR EXPONENT EXTREMES. - // - s1 = cy[0]; - s2 = cy[1]; - cs = csr[iflag-1]; - ascle = bry[iflag-1]; - fn = inu + il; - for (i = 1; i < (il+1); i++) { - c2 = s2; - s2 = s1 + (fn + fnf) * rz * c2; - s1 = c2; - fn -= 1.0; - c2 = s2 * cs; - ck = c2; - c1 = y[kk-1]; - if (kode != 1) { - nw = s1s2(zr, &c1, &c2, asc, alim, &iuf); - nz = nz + nw; - } - y[kk-1] = c1 * cspn + c2; - kk -= 1; - cspn = -cspn; - if (iflag < 3) { - c2m = fmax(fabs(std::real(ck)), fabs(std::imag(ck))); - if (c2m > ascle) { - iflag += 1; - ascle = bry[iflag-1]; - s1 *= cs; - s2 = ck; - s1 *= css[iflag-1]; - s2 *= css[iflag-1]; - cs = csr[iflag-1]; - } - } - } - return nz; -} - - -inline int uoik( - std::complex z, - double fnu, - int kode, - int ikflg, - int n, - std::complex *y, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZUOIK - //***REFER TO ZBESI,ZBESK,ZBESH - // - // ZUOIK COMPUTES THE LEADING TERMS OF THE UNIFORM ASYMPTOTIC - // EXPANSIONS FOR THE I AND K FUNCTIONS AND COMPARES THEM - // (IN LOGARITHMIC FORM) TO ALIM AND ELIM FOR OVER AND UNDERFLOW - // WHERE ALIM.LT.ELIM. IF THE MAGNITUDE, BASED ON THE LEADING - // EXPONENTIAL, IS LESS THAN ALIM OR GREATER THAN -ALIM, THEN - // THE RESULT IS ON SCALE. IF NOT, THEN A REFINED TEST USING OTHER - // MULTIPLIERS (IN LOGARITHMIC FORM) IS MADE BASED ON ELIM. HERE - // EXP(-ELIM)=SMALLEST MACHINE NUMBER*1.0E+3 AND EXP(-ALIM)= - // EXP(-ELIM)/TOL - // - // IKFLG=1 MEANS THE I SEQUENCE IS TESTED - // =2 MEANS THE K SEQUENCE IS TESTED - // NUF = 0 MEANS THE LAST MEMBER OF THE SEQUENCE IS ON SCALE - // =-1 MEANS AN OVERFLOW WOULD OCCUR - // IKFLG=1 AND NUF.GT.0 MEANS THE LAST NUF Y VALUES WERE SET TO ZERO - // THE FIRST N-NUF VALUES MUST BE SET BY ANOTHER ROUTINE - // IKFLG=2 AND NUF.EQ.N MEANS ALL Y VALUES WERE SET TO ZERO - // IKFLG=2 AND 0.LT.NUF.LT.N NOT CONSIDERED. Y MUST BE SET BY - // ANOTHER ROUTINE - // - //***ROUTINES CALLED ZUCHK,ZUNHJ,ZUNIK,D1MACH,AZABS,AZLOG - //***END PROLOGUE ZUOIK - - std::complex arg, asum, bsum, cz, phi, sum, zb, zeta1; - std::complex zeta2, zn, zr; - double aarg, aphi, ascle, ax, ay, fnn, gnn, gnu, rcz, x, yy; - int iform, init, nn; - double aic = 1.265512123484645396; - std::complex cwrk[16] = { 0. }; - - int nuf = 0; - nn = n; - x = std::real(z); - zr = z; - if (x < 0.) { zr = -z; } - zb = zr; - yy = std::imag(zr); - ax = fabs(x) * sqrt(3.); - ay = fabs(yy); - iform = 1; - if (ay > ax) { iform = 2; } - gnu = fmax(fnu, 1.); - if (ikflg != 1) { - fnn = nn; - gnn = fnu + fnn -1; - gnu = fmax(gnn, fnn); - } - - if (iform != 2) { - init = 0; - unik(zr, gnu, ikflg, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - cz = -zeta1 + zeta2; - } else { - zn = -zr * std::complex(0, 1); - if (yy <= 0.) { - zn = conj(zn); - } - unhj(zn, gnu, 1, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - cz = zeta2 - zeta1; - aarg = std::abs(arg); - } - if (kode == 2) { cz -= zb; } - if (ikflg == 2) { cz = -cz; } - aphi = std::abs(phi); - rcz = std::real(cz); - - /* OVERFLOW TEST */ - if (rcz > elim) { return -1; } - if (rcz >= alim) { - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > elim) { return -1; } - } else { - /* UNDERFLOW TEST */ - if (rcz >= -elim) { - if (rcz > -alim) { - /* pass */ - } else { - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > -elim) { - /* goto 30 */ - ascle = 1e3*d1mach[0] / tol; - cz += std::log(phi); - if (iform != 1) { cz -= 0.25*log(arg) + aic;} - ax = exp(rcz) / tol; - ay = std::imag(cz); - cz = ax*std::exp(ay); - if (uchk(cz, ascle, tol)) { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } else { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } - } else { - for (int i = 0; i < nn; i++){ y[i] = 0.; } - return nn; - } - } - if ((ikflg == 2) || (n == 1)) { return nuf; } - /* 140 */ - while (1) { - gnu = fnu + (nn -1); - if (iform != 2) { - init = 0; - unik(zr, gnu, ikflg, 1, tol, &init, &phi, &zeta1, &zeta2, &sum, &cwrk[0]); - cz = zeta2 - zeta1; - } else { - unhj(zn, gnu, 1, tol, &phi, &arg, &zeta1, &zeta2, &asum, &bsum); - cz = zeta2 - zeta1; - aarg = std::abs(phi); - } - if (kode == 2) { cz -= zb; } - - /* 170 */ - aphi = std::abs(phi); - rcz = std::real(cz); - - if (rcz >= -elim) { - if (rcz > -alim) { return nuf; } - rcz += log(aphi); - if (iform == 2) { rcz -= 0.25*log(aarg) + aic; } - if (rcz > -elim) { - ascle = 1e3 * d1mach[0] / tol; - cz = std::log(phi); - if (iform != 1) { cz -= 0.25*std::log(arg) + aic; } - ax = exp(rcz)/tol; - ay = std::imag(cz); - cz = ax*(cos(ay)+sin(ay*std::complex(0, 1))); - if (!(uchk(cz, ascle, tol))) { return nuf; } - } - } - - y[nn-1] = 0.; - nn -= 1; - nuf += 1; - if (nn == 0) { return nuf; } - } - return -1; -} - - -inline int wrsk( - std::complex zr, - double fnu, - int kode, - int n, - std::complex *y, - std::complex *cw, - double tol, - double elim, - double alim -) { - - //***BEGIN PROLOGUE ZWRSK - //***REFER TO ZBESI,ZBESK - // - // ZWRSK COMPUTES THE I BESSEL FUNCTION FOR RE(Z).GE.0.0 BY - // NORMALIZING THE I FUNCTION RATIOS FROM ZRATI BY THE WRONSKIAN - // - //***ROUTINES CALLED D1MACH,ZBKNU,ZRATI,AZABS - //***END PROLOGUE ZWRSK - - std::complex cinu, cscl, ct, c1, c2, rct, st; - double act, acw, ascle, yy; - int i, nw, nz; - - // - // I(FNU+I-1,Z) BY BACKWARD RECURRENCE FOR RATIOS - // Y(I)=I(FNU+I,Z)/I(FNU+I-1,Z) FROM CRATI NORMALIZED BY THE - // WRONSKIAN WITH K(FNU,Z) AND K(FNU+1,Z) FROM CBKNU. - // - nz = 0; - nw = bknu(zr, fnu, kode, 2, cw, tol, elim, alim); - if (nw != 0) { - /* 50 */ - nz = -1; - if (nw == -2) { - nz = -2; - } - return nz; - } - rati(zr, fnu, n, y, tol); - // - // RECUR FORWARD ON I(FNU+1,Z) = R(FNU,Z)*I(FNU,Z), - // R(FNU+J-1,Z)=Y(J), J=1,...,N - // - cinu = 1.0; - if (kode != 1) { - yy = std::imag(zr); - cinu = std::complex(cos(yy), sin(yy)); - } - // - // ON LOW EXPONENT MACHINES THE K FUNCTIONS CAN BE CLOSE TO BOTH THE - // UNDER AND OVERFLOW LIMITS AND THE NORMALIZATION MUST BE SCALED TO - // PREVENT OVER OR UNDERFLOW. CUOIK HAS DETERMINED THAT THE RESULT - // IS ON SCALE. - // - acw = std::abs(cw[1]); - ascle = 1e3*d1mach[0]/tol; - cscl = 1.0; - - if (acw <= ascle) { - cscl = 1.0 / tol; - } else { - ascle = 1.0 / ascle; - if (acw >= ascle) { - cscl = tol; - } - } - - c1 = cw[0]*cscl; - c2 = cw[1]*cscl; - st = y[0]; - // - // CINU=CINU*(CONJG(CT)/ABS(CT))*(1.0_dp/ABS(CT) PREVENTS - // UNDER- OR OVERFLOW PREMATURELY BY SQUARING ABS(CT) - // - ct = zr * (c2 + st*c1); - act = std::abs(ct); - rct = 1.0 / act; - ct = conj(ct)*rct; - cinu *= ct*rct; - y[0] = cinu*cscl; - if (n == 1) { return nz; } - for (i = 2; i < (n+1); i++) { - cinu *= st; - st = y[i-1]; - y[i-1] = cinu*cscl; - } - return nz; -} -} -} diff --git a/scipy/special/xsf/bessel.h b/scipy/special/xsf/bessel.h deleted file mode 100644 index 74473e8dbbc8..000000000000 --- a/scipy/special/xsf/bessel.h +++ /dev/null @@ -1,1202 +0,0 @@ -#pragma once - -#include "amos.h" -#include "cephes/besselpoly.h" -#include "cephes/i0.h" -#include "cephes/i1.h" -#include "cephes/jv.h" -#include "cephes/k0.h" -#include "cephes/k1.h" -#include "cephes/scipy_iv.h" -#include "cephes/yv.h" -#include "error.h" -#include "specfun.h" -#include "trig.h" - -extern "C" double cephes_iv(double v, double x); - -namespace xsf { -namespace detail { - - template - std::complex rotate(std::complex z, T v) { - T c = cospi(v); - T s = sinpi(v); - - return {c * std::real(z) - s * std::imag(z), s * std::real(z) + c * std::imag(z)}; - } - - template - std::complex rotate_jy(std::complex j, std::complex y, T v) { - T c = cospi(v); - T s = sinpi(v); - - return c * j - s * y; - } - - template - int reflect_jy(std::complex *jy, T v) { - /* NB: Y_v may be huge near negative integers -- so handle exact - * integers carefully - */ - int i; - if (v != floor(v)) - return 0; - - i = v - 16384.0 * floor(v / 16384.0); - if (i & 1) { - *jy = -(*jy); - } - return 1; - } - - template - int reflect_i(std::complex *ik, T v) { - if (v != floor(v)) { - return 0; - } - - return 1; /* I is symmetric for integer v */ - } - - template - std::complex rotate_i(std::complex i, std::complex k, T v) { - T s = std::sin(v * M_PI) * (2 / M_PI); - - return i + s * k; - } - - template - void itika(T x, T *ti, T *tk) { - - // ======================================================= - // Purpose: Integrate modified Bessel functions I0(t) and - // K0(t) with respect to t from 0 to x - // Input : x --- Upper limit of the integral ( x ≥ 0 ) - // Output: TI --- Integration of I0(t) from 0 to x - // TK --- Integration of K0(t) from 0 to x - // ======================================================= - - int k; - T rc1, rc2, e0, b1, b2, rs, r, tw, x2; - static const T a[10] = { - 0.625, - 1.0078125, - 2.5927734375, - 9.1868591308594, - 4.1567974090576e+1, - 2.2919635891914e+2, - 1.491504060477e+3, - 1.1192354495579e+4, - 9.515939374212e+4, - 9.0412425769041e+5 - }; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - - if (x == 0.0) { - *ti = 0.0; - *tk = 0.0; - return; - } else if (x < 20.0) { - x2 = x * x; - *ti = 1.0; - r = 1.0; - for (k = 1; k <= 50; k++) { - r = 0.25 * r * (2 * k - 1.0) / (2 * k + 1.0) / (k * k) * x2; - *ti += r; - if (fabs(r / (*ti)) < 1.0e-12) { - break; - } - } - *ti *= x; - } else { - x2 = 0.0; - *ti = 1.0; - r = 1.0; - for (k = 1; k <= 10; k++) { - r = r / x; - *ti += a[k - 1] * r; - } - rc1 = 1.0 / sqrt(2.0 * pi * x); - *ti = rc1 * exp(x) * (*ti); - } - - if (x < 12.0) { - e0 = el + log(x / 2.0); - b1 = 1.0 - e0; - b2 = 0.0; - rs = 0.0; - r = 1.0; - tw = 0.0; - for (k = 1; k <= 50; k++) { - r = 0.25 * r * (2 * k - 1.0) / (2 * k + 1.0) / (k * k) * x2; - b1 += r * (1.0 / (2 * k + 1) - e0); - rs += 1.0 / k; - b2 += r * rs; - *tk = b1 + b2; - if (fabs((*tk - tw) / (*tk)) < 1.0e-12) { - break; - } - tw = *tk; - } - *tk *= x; - } else { - *tk = 1.0; - r = 1.0; - for (k = 1; k <= 10; k++) { - r = -r / x; - *tk = *tk + a[k - 1] * r; - } - rc2 = sqrt(pi / (2.0 * x)); - *tk = pi / 2.0 - rc2 * (*tk) * exp(-x); - } - return; - } - - template - void itjya(T x, T *tj, T *ty) { - int k; - T a[18], a0, a1, af, bf, bg, r, r2, rc, rs, ty1, ty2, x2, xp; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - const T eps = 1.0e-12; - - if (x == 0.0) { - *tj = 0.0; - *ty = 0.0; - } else if (x <= 20.0) { - x2 = x * x; - *tj = x; - r = x; - for (k = 1; k < 61; k++) { - r = -0.25 * r * (2.0 * k - 1.0) / (2.0 * k + 1.0) / (k * k) * x2; - *tj += r; - if (fabs(r) < fabs(*tj) * eps) { - break; - } - } - ty1 = (el + log(x / 2.0)) * (*tj); - rs = 0.0; - ty2 = 1.0; - r = 1.0; - for (k = 1; k < 61; k++) { - r = -0.25 * r * (2.0 * k - 1.0) / (2.0 * k + 1.0) / (k * k) * x2; - rs += 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k + 1.0)); - ty2 += r2; - if (fabs(r2) < fabs(ty2) * eps) { - break; - } - } - *ty = (ty1 - x * ty2) * 2.0 / pi; - } else { - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - - for (int k = 1; k <= 16; k++) { - af = ((1.5 * (k + 0.5) * (k + 5.0 / 6.0) * a1 - 0.5 * (k + 0.5) * (k + 0.5) * (k - 0.5) * a0)) / - (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - bf = 1.0; - r = 1.0; - for (int k = 1; k <= 8; k++) { - r = -r / (x * x); - bf += a[2 * k - 1] * r; - } - bg = a[0] / x; - r = 1.0 / x; - for (int k = 1; k <= 8; k++) { - r = -1.0 / (x * x); - bg += a[2 * k] * r; - } - xp = x + 0.25 * pi; - rc = sqrt(2.0 / (pi * x)); - *tj = 1.0 - rc * (bf * cos(xp) + bg * sin(xp)); - *ty = rc * (bg * cos(xp) - bf * sin(xp)); - } - return; - } - - template - void ittika(T x, T *tti, T *ttk) { - - // ========================================================= - // Purpose: Integrate [I0(t)-1]/t with respect to t from 0 - // to x, and K0(t)/t with respect to t from x to ∞ - // Input : x --- Variable in the limits ( x ≥ 0 ) - // Output: TTI --- Integration of [I0(t)-1]/t from 0 to x - // TTK --- Integration of K0(t)/t from x to ∞ - // ========================================================= - - int k; - T b1, e0, r, r2, rc, rs; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - static const T c[8] = {1.625, 4.1328125, 1.45380859375, 6.553353881835, - 3.6066157150269, 2.3448727161884, 1.7588273098916, 1.4950639538279}; - - if (x == 0.0) { - *tti = 0.0; - *ttk = 1.0e+300; - return; - } - if (x < 40.0) { - *tti = 1.0; - r = 1.0; - for (int k = 2; k <= 50; k++) { - r = 0.25 * r * (k - 1.0) / (k * k * k) * x * x; - *tti += r; - if (fabs(r / (*tti)) < 1.0e-12) { - break; - } - } - *tti *= 0.125 * x * x; - } else { - *tti = 1.0; - r = 1.0; - for (int k = 1; k <= 8; k++) { - r = r / x; - *tti += c[k - 1] * r; - } - rc = x * sqrt(2.0 * pi * x); - *tti = (*tti) * exp(x) / rc; - } - if (x <= 12.0) { - e0 = (0.5 * log(x / 2.0) + el) * log(x / 2.0) + pi * pi / 24.0 + 0.5 * el * el; - b1 = 1.5 - (el + log(x / 2.0)); - rs = 1.0; - r = 1.0; - for (k = 2; k <= 50; k++) { - r = 0.25 * r * (k - 1.0) / (k * k * k) * x * x; - rs += 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k) - (el + log(x / 2.0))); - b1 += r2; - if (fabs(r2 / b1) < 1.0e-12) { - break; - } - } - *ttk = e0 - 0.125 * x * x * b1; - } else { - *ttk = 1.0; - r = 1.0; - for (k = 1; k <= 8; k++) { - r = -r / x; - *ttk += c[k - 1] * r; - } - rc = x * sqrt(2.0 / (pi * x)); - *ttk = (*ttk) * exp(-x) / rc; - } - return; - } - - template - void ittjya(T x, T *ttj, T *tty) { - - // ========================================================= - // Purpose: Integrate [1-J0(t)]/t with respect to t from 0 - // to x, and Y0(t)/t with respect to t from x to ∞ - // Input : x --- Variable in the limits ( x ≥ 0 ) - // Output: TTJ --- Integration of [1-J0(t)]/t from 0 to x - // TTY --- Integration of Y0(t)/t from x to ∞ - // ========================================================= - - int k, l; - T a0, bj0, bj1, by0, by1, e0, b1, g0, g1, px, qx, r, r0, r1, r2, rs, t, vt, xk; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - - if (x == 0.0) { - *ttj = 0.0; - *tty = -1.0e+300; - } else if (x <= 20.0) { - *ttj = 1.0; - r = 1.0; - for (k = 2; k <= 100; k++) { - r = -0.25 * r * (k - 1.0) / (k * k * k) * x * x; - *ttj += r; - if (fabs(r) < fabs(*ttj) * 1.0e-12) { - break; - } - } - *ttj *= 0.125 * x * x; - e0 = 0.5 * (pi * pi / 6.0 - el * el) - (0.5 * log(x / 2.0) + el) * log(x / 2.0); - b1 = el + log(x / 2.0) - 1.5; - rs = 1.0; - r = -1.0; - for (k = 2; k <= 100; k++) { - r = -0.25 * r * (k - 1.0) / (k * k * k) * x * x; - rs = rs + 1.0 / k; - r2 = r * (rs + 1.0 / (2.0 * k) - (el + log(x / 2.0))); - b1 = b1 + r2; - if (fabs(r2) < fabs(b1) * 1.0e-12) { - break; - } - } - *tty = 2.0 / pi * (e0 + 0.125 * x * x * b1); - } else { - a0 = sqrt(2.0 / (pi * x)); - bj0 = 0.0; - by0 = 0.0; - bj1 = 0.0; - for (l = 0; l <= 1; l++) { - vt = 4.0 * l * l; - px = 1.0; - r = 1.0; - for (k = 1; k <= 14; k++) { - r = -0.0078125 * r * (vt - pow((4.0 * k - 3.0), 2)) / (x * k) * (vt - pow((4.0 * k - 1.0), 2)) / - ((2.0 * k - 1.0) * x); - px += r; - if (fabs(r) < fabs(px) * 1.0e-12) { - break; - } - } - qx = 1.0; - r = 1.0; - for (k = 1; k <= 14; k++) { - r = -0.0078125 * r * (vt - pow((4.0 * k - 1.0), 2)) / (x * k) * (vt - pow((4.0 * k + 1.0), 2)) / - ((2.0 * k + 1.0) * x); - qx += r; - if (fabs(r) < fabs(qx) * 1.0e-12) { - break; - } - } - qx = 0.125 * (vt - 1.0) / x * qx; - xk = x - (0.25 + 0.5 * l) * pi; - bj1 = a0 * (px * cos(xk) - qx * sin(xk)); - by1 = a0 * (px * sin(xk) + qx * cos(xk)); - if (l == 0) { - bj0 = bj1; - by0 = by1; - } - } - t = 2.0 / x; - g0 = 1.0; - r0 = 1.0; - for (k = 1; k <= 10; k++) { - r0 = -r0 * k * k * t * t; - g0 += r0; - } - g1 = 1.0; - r1 = 1.0; - for (k = 1; k <= 10; k++) { - r1 = -r1 * k * (k + 1.0) * t * t; - g1 += r1; - } - *ttj = 2.0 * g1 * bj0 / (x * x) - g0 * bj1 / x + el + log(x / 2.0); - *tty = 2.0 * g1 * by0 / (x * x) - g0 * by1 / x; - } - return; - } - -} // namespace detail - -/* Integrals of bessel functions */ - -/* int(j0(t),t=0..x) */ -/* int(y0(t),t=0..x) */ - -template -void it1j0y0(T x, T &j0int, T &y0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::itjya(x, &j0int, &y0int); - if (flag) { - j0int = -j0int; - y0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -/* int((1-j0(t))/t,t=0..x) */ -/* int(y0(t)/t,t=x..inf) */ - -template -void it2j0y0(T x, T &j0int, T &y0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::ittjya(x, &j0int, &y0int); - if (flag) { - y0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -/* Integrals of modified bessel functions */ - -template -void it1i0k0(T x, T &i0int, T &k0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::itika(x, &i0int, &k0int); - if (flag) { - i0int = -i0int; - k0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -template -void it2i0k0(T x, T &i0int, T &k0int) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::ittika(x, &i0int, &k0int); - if (flag) { - k0int = std::numeric_limits::quiet_NaN(); /* domain error */ - } -} - -template -void rctj(T x, int *nm, OutputVec1 rj, OutputVec2 dj) { - - // ======================================================== - // Purpose: Compute Riccati-Bessel functions of the first - // kind and their derivatives - // Input: x --- Argument of Riccati-Bessel function - // n --- Order of jn(x) ( n = 0,1,2,... ) - // Output: RJ(n) --- x·jn(x) - // DJ(n) --- [x·jn(x)]' - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // ======================================================== - - int n = rj.extent(0) - 1; - - int k, m; - T cs, f, f0, f1, rj0, rj1; - - *nm = n; - if (fabs(x) < 1.0e-100) { - for (int k = 0; k <= n; k++) { - rj[k] = 0.0; - dj[k] = 0.0; - } - dj[0] = 1.0; - return; - } - rj[0] = sin(x); - rj[1] = rj[0] / x - cos(x); - rj0 = rj[0]; - rj1 = rj[1]; - cs = 0.0; - f = 0.0; - - if (n >= 2) { - m = specfun::msta1(x, 200); - if (m < n) { - *nm = m; - } else { - m = specfun::msta2(x, n, 15); - } - - f0 = 0.0; - f1 = 1.0e-100; - - for (k = m; k >= 0; k--) { - f = (2.0 * k + 3.0) * f1 / x - f0; - if (k <= *nm) { - rj[k] = f; - } - f0 = f1; - f1 = f; - } - cs = (fabs(rj0) > fabs(rj1) ? rj0 / f : rj1 / f0); - for (k = 0; k <= *nm; k++) { - rj[k] = cs * rj[k]; - } - } - dj[0] = cos(x); - for (int k = 1; k <= *nm; k++) { - dj[k] = -k * rj[k] / x + rj[k - 1]; - } -} - -template -void rctj(T x, OutputVec1 rj, OutputVec2 dj) { - int nm; - rctj(x, &nm, rj, dj); -} - -template -void rcty(T x, int *nm, OutputVec1 ry, OutputVec2 dy) { - - // ======================================================== - // Purpose: Compute Riccati-Bessel functions of the second - // kind and their derivatives - // Input: x --- Argument of Riccati-Bessel function - // n --- Order of yn(x) - // Output: RY(n) --- x·yn(x) - // DY(n) --- [x·yn(x)]' - // NM --- Highest order computed - // ======================================================== - - int n = ry.extent(0) - 1; - - int k; - T rf0, rf1, rf2; - *nm = n; - if (x < 1.0e-60) { - for (k = 0; k <= n; k++) { - ry[k] = -1.0e+300; - dy[k] = 1.0e+300; - } - ry[0] = -1.0; - dy[0] = 0.0; - return; - } - - ry[0] = -cos(x); - ry[1] = ry[0] / x - sin(x); - rf0 = ry[0]; - rf1 = ry[1]; - - for (k = 2; k <= n; k++) { - rf2 = (2.0 * k - 1.0) * rf1 / x - rf0; - if (fabs(rf2) > 1.0e+300) { - break; - } - ry[k] = rf2; - rf0 = rf1; - rf1 = rf2; - } - - *nm = k - 1; - dy[0] = sin(x); - for (k = 1; k <= *nm; k++) { - dy[k] = -k * ry[k] / x + ry[k - 1]; - } - return; -} - -template -void rcty(T x, OutputVec1 ry, OutputVec2 dy) { - int nm; - rcty(x, &nm, ry, dy); -} - -inline std::complex cyl_bessel_je(double v, std::complex z) { - int n = 1; - int kode = 2; - int nz, ierr; - int sign = 1; - std::complex cy_j, cy_y; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_j; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("jve:", ierr_to_sferr(nz, ierr), cy_j); - if (sign == -1) { - if (!detail::reflect_jy(&cy_j, v)) { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("jve(yve):", ierr_to_sferr(nz, ierr), cy_y); - cy_j = detail::rotate_jy(cy_j, cy_y, v); - } - } - return cy_j; -} - -inline std::complex cyl_bessel_je(float v, std::complex x) { - return static_cast>(cyl_bessel_je(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_je(T v, T x) { - if (v != floor(v) && x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_je(v, std::complex(x))); -} - -inline double cyl_bessel_y0(double x) { return cephes::y0(x); } - -inline float cyl_bessel_y0(float x) { return cyl_bessel_y0(static_cast(x)); } - -inline double cyl_bessel_y1(double x) { return cephes::y1(x); } - -inline float cyl_bessel_y1(float x) { return cyl_bessel_y1(static_cast(x)); } - -inline std::complex cyl_bessel_ye(double v, std::complex z) { - int n = 1; - int kode = 2; - int nz, ierr; - int sign = 1; - std::complex cy_y, cy_j; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_y; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("yve:", ierr_to_sferr(nz, ierr), cy_y); - if (ierr == 2) { - if (z.real() >= 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(INFINITY); - cy_y.imag(0); - } - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_y, v)) { - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("yv(jv):", ierr_to_sferr(nz, ierr), cy_j); - cy_y = detail::rotate_jy(cy_y, cy_j, -v); - } - } - return cy_y; -} - -inline std::complex cyl_bessel_ye(float v, std::complex x) { - return static_cast>(cyl_bessel_ye(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ye(T v, T x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_ye(v, std::complex(x))); -} - -inline std::complex cyl_bessel_ie(double v, std::complex z) { - int n = 1; - int kode = 2; - int sign = 1; - int nz, ierr; - std::complex cy, cy_k; - - cy.real(NAN); - cy.imag(NAN); - cy_k.real(NAN); - cy_k.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besi(z, v, kode, n, &cy, &ierr); - set_error_and_nan("ive:", ierr_to_sferr(nz, ierr), cy); - - if (sign == -1) { - if (!detail::reflect_i(&cy, v)) { - nz = amos::besk(z, v, kode, n, &cy_k, &ierr); - set_error_and_nan("ive(kv):", ierr_to_sferr(nz, ierr), cy_k); - /* adjust scaling to match zbesi */ - cy_k = detail::rotate(cy_k, -z.imag() / M_PI); - if (z.real() > 0) { - cy_k.real(cy_k.real() * exp(-2 * z.real())); - cy_k.imag(cy_k.imag() * exp(-2 * z.real())); - } - /* v -> -v */ - cy = detail::rotate_i(cy, cy_k, v); - } - } - - return cy; -} - -inline std::complex cyl_bessel_ie(float v, std::complex x) { - return static_cast>(cyl_bessel_ie(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ie(T v, T x) { - if (v != floor(v) && x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - return std::real(cyl_bessel_ie(v, std::complex(x))); -} - -inline std::complex cyl_bessel_ke(double v, std::complex z) { - std::complex cy{NAN, NAN}; - if (std::isnan(v) || std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return cy; - } - - if (v < 0) { - /* K_v == K_{-v} even for non-integer v */ - v = -v; - } - - int n = 1; - int kode = 2; - int ierr; - int nz = amos::besk(z, v, kode, n, &cy, &ierr); - set_error_and_nan("kve:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - if (std::real(z) >= 0 && std::imag(z) == 0) { - /* overflow */ - cy = INFINITY; - } - } - - return cy; -} - -inline std::complex cyl_bessel_ke(float v, std::complex x) { - return static_cast>(cyl_bessel_ke(static_cast(v), static_cast>(x)) - ); -} - -template -T cyl_bessel_ke(T v, T x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - return std::numeric_limits::infinity(); - } - - return std::real(cyl_bessel_ke(v, std::complex(x))); -} - -inline std::complex cyl_hankel_1e(double v, std::complex z) { - int n = 1; - int kode = 2; - int m = 1; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel1e:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, v); - } - return cy; -} - -inline std::complex cyl_hankel_1e(float v, std::complex z) { - return static_cast>(cyl_hankel_1e(static_cast(v), static_cast>(z)) - ); -} - -inline std::complex cyl_hankel_2e(double v, std::complex z) { - int n = 1; - int kode = 2; - int m = 2; - int nz, ierr; - int sign = 1; - - std::complex cy{NAN, NAN}; - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel2e:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, -v); - } - return cy; -} - -inline std::complex cyl_hankel_2e(float v, std::complex z) { - return static_cast>(cyl_hankel_2e(static_cast(v), static_cast>(z)) - ); -} - -inline double cyl_bessel_j0(double x) { return cephes::j0(x); } - -inline float cyl_bessel_j0(float x) { return cyl_bessel_j0(static_cast(x)); } - -inline double cyl_bessel_j1(double x) { return cephes::j1(x); } - -inline float cyl_bessel_j1(float x) { return cyl_bessel_j1(static_cast(x)); } - -inline std::complex cyl_bessel_j(double v, std::complex z) { - int n = 1; - int kode = 1; - int nz, ierr; - int sign = 1; - std::complex cy_j, cy_y; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_j; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - set_error_and_nan("jv:", ierr_to_sferr(nz, ierr), cy_j); - if (ierr == 2) { - /* overflow */ - cy_j = cyl_bessel_je(v, z); - cy_j.real(cy_j.real() * INFINITY); - cy_j.imag(cy_j.imag() * INFINITY); - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_j, v)) { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("jv(yv):", ierr_to_sferr(nz, ierr), cy_y); - cy_j = detail::rotate_jy(cy_j, cy_y, v); - } - } - return cy_j; -} - -inline std::complex cyl_bessel_j(float v, std::complex x) { - return static_cast>(cyl_bessel_j(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_j(T v, T x) { - if (v != static_cast(v) && x < 0) { - set_error("jv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - std::complex res = cyl_bessel_j(v, std::complex(x)); - if (std::real(res) != std::real(res)) { - /* AMOS returned NaN, possibly due to overflow */ - return cephes::jv(v, x); - } - - return std::real(res); -} - -inline std::complex cyl_bessel_y(double v, std::complex z) { - int n = 1; - int kode = 1; - int nz, ierr; - int sign = 1; - std::complex cy_y, cy_j; - - cy_j.real(NAN); - cy_j.imag(NAN); - cy_y.real(NAN); - cy_y.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy_y; - } - if (v < 0) { - v = -v; - sign = -1; - } - - if (z.real() == 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(-INFINITY); - cy_y.imag(0); - set_error("yv", SF_ERROR_OVERFLOW, NULL); - } else { - nz = amos::besy(z, v, kode, n, &cy_y, &ierr); - set_error_and_nan("yv:", ierr_to_sferr(nz, ierr), cy_y); - if (ierr == 2) { - if (z.real() >= 0 && z.imag() == 0) { - /* overflow */ - cy_y.real(-INFINITY); - cy_y.imag(0); - } - } - } - - if (sign == -1) { - if (!detail::reflect_jy(&cy_y, v)) { - nz = amos::besj(z, v, kode, n, &cy_j, &ierr); - // F_FUNC(zbesj,ZBESJ)(CADDR(z), &v, &kode, &n, CADDR(cy_j), &nz, &ierr); - set_error_and_nan("yv(jv):", ierr_to_sferr(nz, ierr), cy_j); - cy_y = detail::rotate_jy(cy_y, cy_j, -v); - } - } - return cy_y; -} - -inline std::complex cyl_bessel_y(float v, std::complex x) { - return static_cast>(cyl_bessel_y(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_y(T v, T x) { - if (x < 0) { - set_error("yv", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - std::complex res = cyl_bessel_y(v, std::complex(x)); - if (std::real(res) != std::real(res)) { - return cephes::yv(v, x); - } - - return std::real(res); -} - -inline double cyl_bessel_i(double v, double x) { return cephes::iv(v, x); } - -inline float cyl_bessel_i(float v, float x) { return cyl_bessel_i(static_cast(v), static_cast(x)); } - -inline double cyl_bessel_i0(double x) { return cephes::i0(x); } - -inline float cyl_bessel_i0(float x) { return cyl_bessel_i0(static_cast(x)); } - -inline double cyl_bessel_i0e(double x) { return cephes::i0e(x); } - -inline float cyl_bessel_i0e(float x) { return cyl_bessel_i0e(static_cast(x)); } - -inline double cyl_bessel_i1(double x) { return cephes::i1(x); } - -inline float cyl_bessel_i1(float x) { return cyl_bessel_i1(static_cast(x)); } - -inline double cyl_bessel_i1e(double x) { return cephes::i1e(x); } - -inline float cyl_bessel_i1e(float x) { return cyl_bessel_i1e(static_cast(x)); } - -inline std::complex cyl_bessel_i(double v, std::complex z) { - int n = 1; - int kode = 1; - int sign = 1; - int nz, ierr; - std::complex cy, cy_k; - - cy.real(NAN); - cy.imag(NAN); - cy_k.real(NAN); - cy_k.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besi(z, v, kode, n, &cy, &ierr); - set_error_and_nan("iv:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - /* overflow */ - if (z.imag() == 0 && (z.real() >= 0 || v == floor(v))) { - if (z.real() < 0 && v / 2 != floor(v / 2)) - cy.real(-INFINITY); - else - cy.real(INFINITY); - cy.imag(0); - } else { - cy = cyl_bessel_ie(v * sign, z); - cy.real(cy.real() * INFINITY); - cy.imag(cy.imag() * INFINITY); - } - } - - if (sign == -1) { - if (!detail::reflect_i(&cy, v)) { - nz = amos::besk(z, v, kode, n, &cy_k, &ierr); - set_error_and_nan("iv(kv):", ierr_to_sferr(nz, ierr), cy_k); - cy = detail::rotate_i(cy, cy_k, v); - } - } - - return cy; -} - -inline std::complex cyl_bessel_i(float v, std::complex x) { - return static_cast>(cyl_bessel_i(static_cast(v), static_cast>(x))); -} - -inline std::complex cyl_bessel_k(double v, std::complex z) { - std::complex cy(NAN, NAN); - if (std::isnan(v) || std::isnan(std::real(z)) || isnan(std::imag(z))) { - return cy; - } - - if (v < 0) { - /* K_v == K_{-v} even for non-integer v */ - v = -v; - } - - int n = 1; - int kode = 1; - int ierr; - int nz = amos::besk(z, v, kode, n, &cy, &ierr); - set_error_and_nan("kv:", ierr_to_sferr(nz, ierr), cy); - if (ierr == 2) { - if (std::real(z) >= 0 && std::imag(z) == 0) { - /* overflow */ - cy = INFINITY; - } - } - - return cy; -} - -inline std::complex cyl_bessel_k(float v, std::complex x) { - return static_cast>(cyl_bessel_k(static_cast(v), static_cast>(x))); -} - -template -T cyl_bessel_k(T v, T z) { - if (z < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (z == 0) { - return std::numeric_limits::infinity(); - } - - if (z > 710 * (1 + std::abs(v))) { - /* Underflow. See uniform expansion https://dlmf.nist.gov/10.41 - * This condition is not a strict bound (it can underflow earlier), - * rather, we are here working around a restriction in AMOS. - */ - return 0; - } - - return std::real(cyl_bessel_k(v, std::complex(z))); -} - -inline double cyl_bessel_k0(double x) { return cephes::k0(x); } - -inline float cyl_bessel_k0(float x) { return cyl_bessel_k0(static_cast(x)); } - -inline double cyl_bessel_k0e(double x) { return cephes::k0e(x); } - -inline float cyl_bessel_k0e(float x) { return cyl_bessel_k0e(static_cast(x)); } - -inline double cyl_bessel_k1(double x) { return cephes::k1(x); } - -inline float cyl_bessel_k1(float x) { return cyl_bessel_k1(static_cast(x)); } - -inline double cyl_bessel_k1e(double x) { return cephes::k1e(x); } - -inline float cyl_bessel_k1e(float x) { return cyl_bessel_k1e(static_cast(x)); } - -inline std::complex cyl_hankel_1(double v, std::complex z) { - int n = 1; - int kode = 1; - int m = 1; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel1:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, v); - } - return cy; -} - -inline std::complex cyl_hankel_1(float v, std::complex z) { - return static_cast>(cyl_hankel_1(static_cast(v), static_cast>(z))); -} - -inline std::complex cyl_hankel_2(double v, std::complex z) { - int n = 1; - int kode = 1; - int m = 2; - int nz, ierr; - int sign = 1; - std::complex cy; - - cy.real(NAN); - cy.imag(NAN); - - if (std::isnan(v) || std::isnan(z.real()) || std::isnan(z.imag())) { - return cy; - } - if (v == 0 && z == 0.0) { - cy.real(NAN); - cy.imag(INFINITY); - return cy; - } - if (v < 0) { - v = -v; - sign = -1; - } - nz = amos::besh(z, v, kode, m, n, &cy, &ierr); - set_error_and_nan("hankel2:", ierr_to_sferr(nz, ierr), cy); - if (sign == -1) { - cy = detail::rotate(cy, -v); - } - return cy; -} - -inline std::complex cyl_hankel_2(float v, std::complex z) { - return static_cast>(cyl_hankel_2(static_cast(v), static_cast>(z))); -} - -inline double besselpoly(double a, double lambda, double nu) { return cephes::besselpoly(a, lambda, nu); } - -inline float besselpoly(float a, float lambda, float nu) { - return besselpoly(static_cast(a), static_cast(lambda), static_cast(nu)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/beta.h b/scipy/special/xsf/beta.h deleted file mode 100644 index 83aa80694e29..000000000000 --- a/scipy/special/xsf/beta.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "cephes/beta.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double beta(double a, double b) { return cephes::beta(a, b); } - -XSF_HOST_DEVICE inline float beta(float a, float b) { return beta(static_cast(a), static_cast(b)); } - -XSF_HOST_DEVICE inline double betaln(double a, double b) { return cephes::lbeta(a, b); } - -XSF_HOST_DEVICE inline float betaln(float a, float b) { return betaln(static_cast(a), static_cast(b)); } - -} // namespace xsf diff --git a/scipy/special/xsf/binom.h b/scipy/special/xsf/binom.h deleted file mode 100644 index 6a9b9ead9d7d..000000000000 --- a/scipy/special/xsf/binom.h +++ /dev/null @@ -1,89 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * - * Original authors: Pauli Virtanen, Eric Moore - */ - -// Binomial coefficient - -#pragma once - -#include "config.h" - -#include "cephes/beta.h" -#include "cephes/gamma.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double binom(double n, double k) { - double kx, nx, num, den, dk, sgn; - - if (n < 0) { - nx = std::floor(n); - if (n == nx) { - // Undefined - return std::numeric_limits::quiet_NaN(); - } - } - - kx = std::floor(k); - if (k == kx && (std::abs(n) > 1E-8 || n == 0)) { - /* Integer case: use multiplication formula for less rounding - * error for cases where the result is an integer. - * - * This cannot be used for small nonzero n due to loss of - * precision. */ - nx = std::floor(n); - if (nx == n && kx > nx / 2 && nx > 0) { - // Reduce kx by symmetry - kx = nx - kx; - } - - if (kx >= 0 && kx < 20) { - num = 1.0; - den = 1.0; - for (int i = 1; i < 1 + static_cast(kx); i++) { - num *= i + n - kx; - den *= i; - if (std::abs(num) > 1E50) { - num /= den; - den = 1.0; - } - } - return num / den; - } - } - - // general case - if (n >= 1E10 * k and k > 0) { - // avoid under/overflows intermediate results - return std::exp(-cephes::lbeta(1 + n - k, 1 + k) - std::log(n + 1)); - } - if (k > 1E8 * std::abs(n)) { - // avoid loss of precision - num = cephes::Gamma(1 + n) / std::abs(k) + cephes::Gamma(1 + n) * n / (2 * k * k); // + ... - num /= M_PI * std::pow(std::abs(k), n); - if (k > 0) { - kx = std::floor(k); - if (static_cast(kx) == kx) { - dk = k - kx; - sgn = (static_cast(kx) % 2 == 0) ? 1 : -1; - } else { - dk = k; - sgn = 1; - } - return num * std::sin((dk - n) * M_PI) * sgn; - } - kx = std::floor(k); - if (static_cast(kx) == kx) { - return 0; - } - return num * std::sin(k * M_PI); - } - return 1 / (n + 1) / cephes::beta(1 + n - k, 1 + k); -} - -XSF_HOST_DEVICE inline float binom(float n, float k) { - return binom(static_cast(n), static_cast(k)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/cdflib.h b/scipy/special/xsf/cdflib.h deleted file mode 100644 index 1ce5550efb6d..000000000000 --- a/scipy/special/xsf/cdflib.h +++ /dev/null @@ -1,100 +0,0 @@ - -#pragma once - -#include "cephes/igam.h" -#include "config.h" -#include "error.h" -#include "tools.h" - -namespace xsf { - -XSF_HOST_DEVICE inline double gdtrib(double a, double p, double x) { - if (std::isnan(p) || std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (!((0 <= p) && (p <= 1))) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter p is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (!(a > 0) || std::isinf(a)) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter a is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (!(x >= 0) || std::isinf(x)) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Input parameter x is out of range"); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - if (p == 0.0) { - set_error("gdtrib", SF_ERROR_DOMAIN, "Indeterminate result for (x, p) == (0, 0)."); - return std::numeric_limits::quiet_NaN(); - } - /* gdtrib(a, p, x) tends to 0 as x -> 0 when p > 0 */ - return 0.0; - } - if (p == 0.0) { - /* gdtrib(a, p, x) tends to infinity as p -> 0 from the right when x > 0. */ - set_error("gdtrib", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - if (p == 1.0) { - /* gdtrib(a, p, x) tends to 0 as p -> 1.0 from the left when x > 0. */ - return 0.0; - } - double q = 1.0 - p; - auto func = [a, p, q, x](double b) { - if (p <= q) { - return cephes::igam(b, a * x) - p; - } - return q - cephes::igamc(b, a * x); - }; - double lower_bound = std::numeric_limits::min(); - double upper_bound = std::numeric_limits::max(); - /* To explain the magic constants used below: - * 1.0 is the initial guess for the root. -0.875 is the initial step size - * for the leading bracket endpoint if the bracket search will proceed to the - * left, likewise 7.0 is the initial step size when the bracket search will - * proceed to the right. 0.125 is the scale factor for a left moving bracket - * search and 8.0 the scale factor for a right moving bracket search. These - * constants are chosen so that: - * - * 1. The scale factor and bracket endpoints remain powers of 2, allowing for - * exact arithmetic, preventing roundoff error from causing numerical catastrophe - * which could lead to unexpected results. - * 2. The bracket sizes remain constant in a relative sense. Each candidate bracket - * will contain roughly the same number of floating point values. This means that - * the number of necessary function evaluations in the worst case scenario for - * Chandrupatla's algorithm will remain constant. - * - * false specifies that the function is not decreasing. 342 is equal to - * max(ceil(log_8(DBL_MAX)), ceil(log_(1/8)(DBL_MIN))). An upper bound for the - * number of iterations needed in this bracket search to check all normalized - * floating point values. - */ - auto [xl, xr, f_xl, f_xr, bracket_status] = detail::bracket_root_for_cdf_inversion( - func, 1.0, lower_bound, upper_bound, -0.875, 7.0, 0.125, 8, false, 342 - ); - if (bracket_status == 1) { - set_error("gdtrib", SF_ERROR_UNDERFLOW, NULL); - return 0.0; - } - if (bracket_status == 2) { - set_error("gdtrib", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - if (bracket_status >= 3) { - set_error("gdtrib", SF_ERROR_OTHER, "Computational Error"); - return std::numeric_limits::quiet_NaN(); - } - auto [result, root_status] = detail::find_root_chandrupatla( - func, xl, xr, f_xl, f_xr, std::numeric_limits::epsilon(), 1e-100, 100 - ); - if (root_status) { - /* The root finding return should only fail if there's a bug in our code. */ - set_error("gdtrib", SF_ERROR_OTHER, "Computational Error, (%.17g, %.17g, %.17g)", a, p, x); - return std::numeric_limits::quiet_NaN(); - } - return result; -} - -} // namespace xsf diff --git a/scipy/special/xsf/cephes/airy.h b/scipy/special/xsf/cephes/airy.h deleted file mode 100644 index 8db31fa9b383..000000000000 --- a/scipy/special/xsf/cephes/airy.h +++ /dev/null @@ -1,307 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* airy.c - * - * Airy function - * - * - * - * SYNOPSIS: - * - * double x, ai, aip, bi, bip; - * int airy(); - * - * airy( x, _&ai, _&aip, _&bi, _&bip ); - * - * - * - * DESCRIPTION: - * - * Solution of the differential equation - * - * y"(x) = xy. - * - * The function returns the two independent solutions Ai, Bi - * and their first derivatives Ai'(x), Bi'(x). - * - * Evaluation is by power series summation for small x, - * by rational minimax approximations for large x. - * - * - * - * ACCURACY: - * Error criterion is absolute when function <= 1, relative - * when function > 1, except * denotes relative error criterion. - * For large negative x, the absolute error increases as x^1.5. - * For large positive x, the relative error increases as x^1.5. - * - * Arithmetic domain function # trials peak rms - * IEEE -10, 0 Ai 10000 1.6e-15 2.7e-16 - * IEEE 0, 10 Ai 10000 2.3e-14* 1.8e-15* - * IEEE -10, 0 Ai' 10000 4.6e-15 7.6e-16 - * IEEE 0, 10 Ai' 10000 1.8e-14* 1.5e-15* - * IEEE -10, 10 Bi 30000 4.2e-15 5.3e-16 - * IEEE -10, 10 Bi' 30000 4.9e-15 7.3e-16 - * - */ -/* airy.c */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double airy_c1 = 0.35502805388781723926; - constexpr double airy_c2 = 0.258819403792806798405; - constexpr double MAXAIRY = 103.892; - - constexpr double airy_AN[8] = { - 3.46538101525629032477E-1, 1.20075952739645805542E1, 7.62796053615234516538E1, 1.68089224934630576269E2, - 1.59756391350164413639E2, 7.05360906840444183113E1, 1.40264691163389668864E1, 9.99999999999999995305E-1, - }; - - constexpr double airy_AD[8] = { - 5.67594532638770212846E-1, 1.47562562584847203173E1, 8.45138970141474626562E1, 1.77318088145400459522E2, - 1.64234692871529701831E2, 7.14778400825575695274E1, 1.40959135607834029598E1, 1.00000000000000000470E0, - }; - - constexpr double airy_APN[8] = { - 6.13759184814035759225E-1, 1.47454670787755323881E1, 8.20584123476060982430E1, 1.71184781360976385540E2, - 1.59317847137141783523E2, 6.99778599330103016170E1, 1.39470856980481566958E1, 1.00000000000000000550E0, - }; - - constexpr double airy_APD[8] = { - 3.34203677749736953049E-1, 1.11810297306158156705E1, 7.11727352147859965283E1, 1.58778084372838313640E2, - 1.53206427475809220834E2, 6.86752304592780337944E1, 1.38498634758259442477E1, 9.99999999999999994502E-1, - }; - - constexpr double airy_BN16[5] = { - -2.53240795869364152689E-1, 5.75285167332467384228E-1, -3.29907036873225371650E-1, - 6.44404068948199951727E-2, -3.82519546641336734394E-3, - }; - - constexpr double airy_BD16[5] = { - /* 1.00000000000000000000E0, */ - -7.15685095054035237902E0, 1.06039580715664694291E1, -5.23246636471251500874E0, - 9.57395864378383833152E-1, -5.50828147163549611107E-2, - }; - - constexpr double airy_BPPN[5] = { - 4.65461162774651610328E-1, -1.08992173800493920734E0, 6.38800117371827987759E-1, - -1.26844349553102907034E-1, 7.62487844342109852105E-3, - }; - - constexpr double airy_BPPD[5] = { - /* 1.00000000000000000000E0, */ - -8.70622787633159124240E0, 1.38993162704553213172E1, -7.14116144616431159572E0, - 1.34008595960680518666E0, -7.84273211323341930448E-2, - }; - - constexpr double airy_AFN[9] = { - -1.31696323418331795333E-1, -6.26456544431912369773E-1, -6.93158036036933542233E-1, - -2.79779981545119124951E-1, -4.91900132609500318020E-2, -4.06265923594885404393E-3, - -1.59276496239262096340E-4, -2.77649108155232920844E-6, -1.67787698489114633780E-8, - }; - - constexpr double airy_AFD[9] = { - /* 1.00000000000000000000E0, */ - 1.33560420706553243746E1, 3.26825032795224613948E1, 2.67367040941499554804E1, - 9.18707402907259625840E0, 1.47529146771666414581E0, 1.15687173795188044134E-1, - 4.40291641615211203805E-3, 7.54720348287414296618E-5, 4.51850092970580378464E-7, - }; - - constexpr double airy_AGN[11] = { - 1.97339932091685679179E-2, 3.91103029615688277255E-1, 1.06579897599595591108E0, 9.39169229816650230044E-1, - 3.51465656105547619242E-1, 6.33888919628925490927E-2, 5.85804113048388458567E-3, 2.82851600836737019778E-4, - 6.98793669997260967291E-6, 8.11789239554389293311E-8, 3.41551784765923618484E-10, - }; - - constexpr double airy_AGD[10] = { - /* 1.00000000000000000000E0, */ - 9.30892908077441974853E0, 1.98352928718312140417E1, 1.55646628932864612953E1, 5.47686069422975497931E0, - 9.54293611618961883998E-1, 8.64580826352392193095E-2, 4.12656523824222607191E-3, 1.01259085116509135510E-4, - 1.17166733214413521882E-6, 4.91834570062930015649E-9, - }; - - constexpr double airy_APFN[9] = { - 1.85365624022535566142E-1, 8.86712188052584095637E-1, 9.87391981747398547272E-1, - 4.01241082318003734092E-1, 7.10304926289631174579E-2, 5.90618657995661810071E-3, - 2.33051409401776799569E-4, 4.08718778289035454598E-6, 2.48379932900442457853E-8, - }; - - constexpr double airy_APFD[9] = { - /* 1.00000000000000000000E0, */ - 1.47345854687502542552E1, 3.75423933435489594466E1, 3.14657751203046424330E1, - 1.09969125207298778536E1, 1.78885054766999417817E0, 1.41733275753662636873E-1, - 5.44066067017226003627E-3, 9.39421290654511171663E-5, 5.65978713036027009243E-7, - }; - - constexpr double airy_APGN[11] = { - -3.55615429033082288335E-2, -6.37311518129435504426E-1, -1.70856738884312371053E0, - -1.50221872117316635393E0, -5.63606665822102676611E-1, -1.02101031120216891789E-1, - -9.48396695961445269093E-3, -4.60325307486780994357E-4, -1.14300836484517375919E-5, - -1.33415518685547420648E-7, -5.63803833958893494476E-10, - }; - - constexpr double airy_APGD[11] = { - /* 1.00000000000000000000E0, */ - 9.85865801696130355144E0, 2.16401867356585941885E1, 1.73130776389749389525E1, 6.17872175280828766327E0, - 1.08848694396321495475E0, 9.95005543440888479402E-2, 4.78468199683886610842E-3, 1.18159633322838625562E-4, - 1.37480673554219441465E-6, 5.79912514929147598821E-9, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int airy(double x, double *ai, double *aip, double *bi, double *bip) { - double z, zz, t, f, g, uf, ug, k, zeta, theta; - int domflg; - - domflg = 0; - if (x > detail::MAXAIRY) { - *ai = 0; - *aip = 0; - *bi = std::numeric_limits::infinity(); - *bip = std::numeric_limits::infinity(); - return (-1); - } - - if (x < -2.09) { - domflg = 15; - t = std::sqrt(-x); - zeta = -2.0 * x * t / 3.0; - t = std::sqrt(t); - k = detail::SQRT1OPI / t; - z = 1.0 / zeta; - zz = z * z; - uf = 1.0 + zz * polevl(zz, detail::airy_AFN, 8) / p1evl(zz, detail::airy_AFD, 9); - ug = z * polevl(zz, detail::airy_AGN, 10) / p1evl(zz, detail::airy_AGD, 10); - theta = zeta + 0.25 * M_PI; - f = std::sin(theta); - g = std::cos(theta); - *ai = k * (f * uf - g * ug); - *bi = k * (g * uf + f * ug); - uf = 1.0 + zz * polevl(zz, detail::airy_APFN, 8) / p1evl(zz, detail::airy_APFD, 9); - ug = z * polevl(zz, detail::airy_APGN, 10) / p1evl(zz, detail::airy_APGD, 10); - k = detail::SQRT1OPI * t; - *aip = -k * (g * uf + f * ug); - *bip = k * (f * uf - g * ug); - return (0); - } - - if (x >= 2.09) { /* cbrt(9) */ - domflg = 5; - t = std::sqrt(x); - zeta = 2.0 * x * t / 3.0; - g = std::exp(zeta); - t = std::sqrt(t); - k = 2.0 * t * g; - z = 1.0 / zeta; - f = polevl(z, detail::airy_AN, 7) / polevl(z, detail::airy_AD, 7); - *ai = detail::SQRT1OPI * f / k; - k = -0.5 * detail::SQRT1OPI * t / g; - f = polevl(z, detail::airy_APN, 7) / polevl(z, detail::airy_APD, 7); - *aip = f * k; - - if (x > 8.3203353) { /* zeta > 16 */ - f = z * polevl(z, detail::airy_BN16, 4) / p1evl(z, detail::airy_BD16, 5); - k = detail::SQRT1OPI * g; - *bi = k * (1.0 + f) / t; - f = z * polevl(z, detail::airy_BPPN, 4) / p1evl(z, detail::airy_BPPD, 5); - *bip = k * t * (1.0 + f); - return (0); - } - } - - f = 1.0; - g = x; - t = 1.0; - uf = 1.0; - ug = x; - k = 1.0; - z = x * x * x; - while (t > detail::MACHEP) { - uf *= z; - k += 1.0; - uf /= k; - ug *= z; - k += 1.0; - ug /= k; - uf /= k; - f += uf; - k += 1.0; - ug /= k; - g += ug; - t = std::abs(uf / f); - } - uf = detail::airy_c1 * f; - ug = detail::airy_c2 * g; - if ((domflg & 1) == 0) { - *ai = uf - ug; - } - if ((domflg & 2) == 0) { - *bi = detail::SQRT3 * (uf + ug); - } - - /* the deriviative of ai */ - k = 4.0; - uf = x * x / 2.0; - ug = z / 3.0; - f = uf; - g = 1.0 + ug; - uf /= 3.0; - t = 1.0; - - while (t > detail::MACHEP) { - uf *= z; - ug /= k; - k += 1.0; - ug *= z; - uf /= k; - f += uf; - k += 1.0; - ug /= k; - uf /= k; - g += ug; - k += 1.0; - t = std::abs(ug / g); - } - - uf = detail::airy_c1 * f; - ug = detail::airy_c2 * g; - if ((domflg & 4) == 0) { - *aip = uf - ug; - } - if ((domflg & 8) == 0) { - *bip = detail::SQRT3 * (uf + ug); - }; - return (0); - } - - inline int airy(float xf, float *aif, float *aipf, float *bif, float *bipf) { - double ai; - double aip; - double bi; - double bip; - int res = cephes::airy(xf, &ai, &aip, &bi, &bip); - - *aif = ai; - *aipf = aip; - *bif = bi; - *bipf = bip; - return res; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/bdtr.h b/scipy/special/xsf/cephes/bdtr.h deleted file mode 100644 index 3487c06c20a6..000000000000 --- a/scipy/special/xsf/cephes/bdtr.h +++ /dev/null @@ -1,262 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* bdtr.c - * - * Binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, bdtr(); - * - * y = bdtr( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms 0 through k of the Binomial - * probability density: - * - * k - * -- ( n ) j n-j - * > ( ) p (1-p) - * -- ( j ) - * j=0 - * - * The terms are not summed directly; instead the incomplete - * beta integral is employed, according to the formula - * - * y = bdtr( k, n, p ) = incbet( n-k, k+1, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between 0.001 and 1: - * IEEE 0,100 100000 4.3e-15 2.6e-16 - * See also incbet.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * bdtr domain k < 0 0.0 - * n < k - * x < 0, x > 1 - */ -/* bdtrc() - * - * Complemented binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, bdtrc(); - * - * y = bdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 through n of the Binomial - * probability density: - * - * n - * -- ( n ) j n-j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not summed directly; instead the incomplete - * beta integral is employed, according to the formula - * - * y = bdtrc( k, n, p ) = incbet( k+1, n-k, p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p). - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between 0.001 and 1: - * IEEE 0,100 100000 6.7e-15 8.2e-16 - * For p between 0 and .001: - * IEEE 0,100 100000 1.5e-13 2.7e-15 - * - * ERROR MESSAGES: - * - * message condition value returned - * bdtrc domain x<0, x>1, n 1 - */ - -/* bdtr() */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double bdtrc(double k, int n, double p) { - double dk, dn; - double fk = std::floor(k); - - if (std::isnan(p) || std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (p < 0.0 || p > 1.0 || n < fk) { - set_error("bdtrc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (fk < 0) { - return 1.0; - } - - if (fk == n) { - return 0.0; - } - - dn = n - fk; - if (k == 0) { - if (p < .01) - dk = -expm1(dn * std::log1p(-p)); - else - dk = 1.0 - std::pow(1.0 - p, dn); - } else { - dk = fk + 1; - dk = incbet(dk, dn, p); - } - return dk; - } - - XSF_HOST_DEVICE inline double bdtr(double k, int n, double p) { - double dk, dn; - double fk = std::floor(k); - - if (std::isnan(p) || std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (p < 0.0 || p > 1.0 || fk < 0 || n < fk) { - set_error("bdtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (fk == n) { - return 1.0; - } - - dn = n - fk; - if (fk == 0) { - dk = std::pow(1.0 - p, dn); - } else { - dk = fk + 1.; - dk = incbet(dn, dk, 1.0 - p); - } - return dk; - } - - XSF_HOST_DEVICE inline double bdtri(double k, int n, double y) { - double p, dn, dk; - double fk = std::floor(k); - - if (std::isnan(k)) { - return std::numeric_limits::quiet_NaN(); - } - - if (y < 0.0 || y > 1.0 || fk < 0.0 || n <= fk) { - set_error("bdtri", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - dn = n - fk; - - if (fk == n) { - return 1.0; - } - - if (fk == 0) { - if (y > 0.8) { - p = -expm1(std::log1p(y - 1.0) / dn); - } else { - p = 1.0 - std::pow(y, 1.0 / dn); - } - } else { - dk = fk + 1; - p = incbet(dn, dk, 0.5); - if (p > 0.5) { - p = incbi(dk, dn, 1.0 - y); - } else { - p = 1.0 - incbi(dn, dk, y); - } - } - return p; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/besselpoly.h b/scipy/special/xsf/cephes/besselpoly.h deleted file mode 100644 index d113b8b7d0f4..000000000000 --- a/scipy/special/xsf/cephes/besselpoly.h +++ /dev/null @@ -1,51 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * - * This was not part of the original cephes library. - */ -#pragma once - -#include "../config.h" -#include "gamma.h" - -namespace xsf { -namespace cephes { - namespace detail { - - constexpr double besselpoly_EPS = 1.0e-17; - } - - XSF_HOST_DEVICE inline double besselpoly(double a, double lambda, double nu) { - - int m, factor = 0; - double Sm, relerr, Sol; - double sum = 0.0; - - /* Special handling for a = 0.0 */ - if (a == 0.0) { - if (nu == 0.0) { - return 1.0 / (lambda + 1); - } else { - return 0.0; - } - } - /* Special handling for negative and integer nu */ - if ((nu < 0) && (std::floor(nu) == nu)) { - nu = -nu; - factor = static_cast(nu) % 2; - } - Sm = std::exp(nu * std::log(a)) / (Gamma(nu + 1) * (lambda + nu + 1)); - m = 0; - do { - sum += Sm; - Sol = Sm; - Sm *= -a * a * (lambda + nu + 1 + 2 * m) / ((nu + m + 1) * (m + 1) * (lambda + nu + 1 + 2 * m + 2)); - m++; - relerr = std::abs((Sm - Sol) / Sm); - } while (relerr > detail::besselpoly_EPS && m < 1000); - if (!factor) - return sum; - else - return -sum; - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/beta.h b/scipy/special/xsf/cephes/beta.h deleted file mode 100644 index 437262793e8f..000000000000 --- a/scipy/special/xsf/cephes/beta.h +++ /dev/null @@ -1,257 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* beta.c - * - * Beta function - * - * - * - * SYNOPSIS: - * - * double a, b, y, beta(); - * - * y = beta( a, b ); - * - * - * - * DESCRIPTION: - * - * - - - * | (a) | (b) - * beta( a, b ) = -----------. - * - - * | (a+b) - * - * For large arguments the logarithm of the function is - * evaluated using lgam(), then exponentiated. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 8.1e-14 1.1e-14 - * - * ERROR MESSAGES: - * - * message condition value returned - * beta overflow log(beta) > MAXLOG 0.0 - * a or b <0 integer 0.0 - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "gamma.h" -#include "rgamma.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE double beta(double, double); - XSF_HOST_DEVICE double lbeta(double, double); - - namespace detail { - constexpr double beta_ASYMP_FACTOR = 1e6; - - /* - * Asymptotic expansion for ln(|B(a, b)|) for a > ASYMP_FACTOR*max(|b|, 1). - */ - XSF_HOST_DEVICE inline double lbeta_asymp(double a, double b, int *sgn) { - double r = lgam_sgn(b, sgn); - r -= b * std::log(a); - - r += b * (1 - b) / (2 * a); - r += b * (1 - b) * (1 - 2 * b) / (12 * a * a); - r += -b * b * (1 - b) * (1 - b) / (12 * a * a * a); - - return r; - } - - /* - * Special case for a negative integer argument - */ - - XSF_HOST_DEVICE inline double beta_negint(int a, double b) { - int sgn; - if (b == static_cast(b) && 1 - a - b > 0) { - sgn = (static_cast(b) % 2 == 0) ? 1 : -1; - return sgn * xsf::cephes::beta(1 - a - b, b); - } else { - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - } - - XSF_HOST_DEVICE inline double lbeta_negint(int a, double b) { - double r; - if (b == static_cast(b) && 1 - a - b > 0) { - r = xsf::cephes::lbeta(1 - a - b, b); - return r; - } else { - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - } - } // namespace detail - - XSF_HOST_DEVICE inline double beta(double a, double b) { - double y; - int sign = 1; - - if (a <= 0.0) { - if (a == std::floor(a)) { - if (a == static_cast(a)) { - return detail::beta_negint(static_cast(a), b); - } else { - goto overflow; - } - } - } - - if (b <= 0.0) { - if (b == std::floor(b)) { - if (b == static_cast(b)) { - return detail::beta_negint(static_cast(b), a); - } else { - goto overflow; - } - } - } - - if (std::abs(a) < std::abs(b)) { - y = a; - a = b; - b = y; - } - - if (std::abs(a) > detail::beta_ASYMP_FACTOR * std::abs(b) && a > detail::beta_ASYMP_FACTOR) { - /* Avoid loss of precision in lgam(a + b) - lgam(a) */ - y = detail::lbeta_asymp(a, b, &sign); - return sign * std::exp(y); - } - - y = a + b; - if (std::abs(y) > detail::MAXGAM || std::abs(a) > detail::MAXGAM || std::abs(b) > detail::MAXGAM) { - int sgngam; - y = detail::lgam_sgn(y, &sgngam); - sign *= sgngam; /* keep track of the sign */ - y = detail::lgam_sgn(b, &sgngam) - y; - sign *= sgngam; - y = detail::lgam_sgn(a, &sgngam) + y; - sign *= sgngam; - if (y > detail::MAXLOG) { - goto overflow; - } - return (sign * std::exp(y)); - } - - y = rgamma(y); - a = Gamma(a); - b = Gamma(b); - if (std::isinf(y)) { - goto overflow; - } - - if (std::abs(std::abs(a*y) - 1.0) > std::abs(std::abs(b*y) - 1.0)) { - y = b * y; - y *= a; - } else { - y = a * y; - y *= b; - } - - return (y); - - overflow: - set_error("beta", SF_ERROR_OVERFLOW, NULL); - return (sign * std::numeric_limits::infinity()); - } - - /* Natural log of |beta|. */ - - XSF_HOST_DEVICE inline double lbeta(double a, double b) { - double y; - int sign; - - sign = 1; - - if (a <= 0.0) { - if (a == std::floor(a)) { - if (a == static_cast(a)) { - return detail::lbeta_negint(static_cast(a), b); - } else { - goto over; - } - } - } - - if (b <= 0.0) { - if (b == std::floor(b)) { - if (b == static_cast(b)) { - return detail::lbeta_negint(static_cast(b), a); - } else { - goto over; - } - } - } - - if (std::abs(a) < std::abs(b)) { - y = a; - a = b; - b = y; - } - - if (std::abs(a) > detail::beta_ASYMP_FACTOR * std::abs(b) && a > detail::beta_ASYMP_FACTOR) { - /* Avoid loss of precision in lgam(a + b) - lgam(a) */ - y = detail::lbeta_asymp(a, b, &sign); - return y; - } - - y = a + b; - if (std::abs(y) > detail::MAXGAM || std::abs(a) > detail::MAXGAM || std::abs(b) > detail::MAXGAM) { - int sgngam; - y = detail::lgam_sgn(y, &sgngam); - sign *= sgngam; /* keep track of the sign */ - y = detail::lgam_sgn(b, &sgngam) - y; - sign *= sgngam; - y = detail::lgam_sgn(a, &sgngam) + y; - sign *= sgngam; - return (y); - } - - y = rgamma(y); - a = Gamma(a); - b = Gamma(b); - if (std::isinf(y)) { - over: - set_error("lbeta", SF_ERROR_OVERFLOW, NULL); - return (sign * std::numeric_limits::infinity()); - } - - if (std::abs(std::abs(a*y) - 1.0) > std::abs(std::abs(b*y) - 1.0)) { - y = b * y; - y *= a; - } else { - y = a * y; - y *= b; - } - - if (y < 0) { - y = -y; - } - - return (std::log(y)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/cbrt.h b/scipy/special/xsf/cephes/cbrt.h deleted file mode 100644 index 3e9fbd4eab45..000000000000 --- a/scipy/special/xsf/cephes/cbrt.h +++ /dev/null @@ -1,131 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* cbrt.c - * - * Cube root - * - * - * - * SYNOPSIS: - * - * double x, y, cbrt(); - * - * y = cbrt( x ); - * - * - * - * DESCRIPTION: - * - * Returns the cube root of the argument, which may be negative. - * - * Range reduction involves determining the power of 2 of - * the argument. A polynomial of degree 2 applied to the - * mantissa, and multiplication by the cube root of 1, 2, or 4 - * approximates the root to within about 0.1%. Then Newton's - * iteration is used three times to converge to an accurate - * result. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1e308 30000 1.5e-16 5.0e-17 - * - */ -/* cbrt.c */ - -/* - * Cephes Math Library Release 2.2: January, 1991 - * Copyright 1984, 1991 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double CBRT2 = 1.2599210498948731647672; - constexpr double CBRT4 = 1.5874010519681994747517; - constexpr double CBRT2I = 0.79370052598409973737585; - constexpr double CBRT4I = 0.62996052494743658238361; - - XSF_HOST_DEVICE inline double cbrt(double x) { - int e, rem, sign; - double z; - - if (!std::isfinite(x)) { - return x; - } - if (x == 0) { - return (x); - } - if (x > 0) { - sign = 1; - } else { - sign = -1; - x = -x; - } - - z = x; - /* extract power of 2, leaving - * mantissa between 0.5 and 1 - */ - x = std::frexp(x, &e); - - /* Approximate cube root of number between .5 and 1, - * peak relative error = 9.2e-6 - */ - x = (((-1.3466110473359520655053e-1 * x + 5.4664601366395524503440e-1) * x - 9.5438224771509446525043e-1) * - x + - 1.1399983354717293273738e0) * - x + - 4.0238979564544752126924e-1; - - /* exponent divided by 3 */ - if (e >= 0) { - rem = e; - e /= 3; - rem -= 3 * e; - if (rem == 1) { - x *= CBRT2; - } else if (rem == 2) { - x *= CBRT4; - } - } - /* argument less than 1 */ - else { - e = -e; - rem = e; - e /= 3; - rem -= 3 * e; - if (rem == 1) { - x *= CBRT2I; - } else if (rem == 2) { - x *= CBRT4I; - } - e = -e; - } - - /* multiply by power of 2 */ - x = std::ldexp(x, e); - - /* Newton iteration */ - x -= (x - (z / (x * x))) * 0.33333333333333333333; - x -= (x - (z / (x * x))) * 0.33333333333333333333; - - if (sign < 0) - x = -x; - return (x); - } - } // namespace detail - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/chbevl.h b/scipy/special/xsf/cephes/chbevl.h deleted file mode 100644 index caaa74fc7b81..000000000000 --- a/scipy/special/xsf/cephes/chbevl.h +++ /dev/null @@ -1,85 +0,0 @@ -/* chbevl.c - * - * Evaluate Chebyshev series - * - * - * - * SYNOPSIS: - * - * int N; - * double x, y, coef[N], chebevl(); - * - * y = chbevl( x, coef, N ); - * - * - * - * DESCRIPTION: - * - * Evaluates the series - * - * N-1 - * - ' - * y = > coef[i] T (x/2) - * - i - * i=0 - * - * of Chebyshev polynomials Ti at argument x/2. - * - * Coefficients are stored in reverse order, i.e. the zero - * order term is last in the array. Note N is the number of - * coefficients, not the order. - * - * If coefficients are for the interval a to b, x must - * have been transformed to x -> 2(2x - b - a)/(b-a) before - * entering the routine. This maps x from (a, b) to (-1, 1), - * over which the Chebyshev polynomials are defined. - * - * If the coefficients are for the inverted interval, in - * which (a, b) is mapped to (1/b, 1/a), the transformation - * required is x -> 2(2ab/x - b - a)/(b-a). If b is infinity, - * this becomes x -> 4a/x - 1. - * - * - * - * SPEED: - * - * Taking advantage of the recurrence properties of the - * Chebyshev polynomials, the routine requires one more - * addition per loop than evaluating a nested polynomial of - * the same degree. - * - */ -/* chbevl.c */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE double chbevl(double x, const double array[], int n) { - double b0, b1, b2; - const double *p; - int i; - - p = array; - b0 = *p++; - b1 = 0.0; - i = n - 1; - - do { - b2 = b1; - b1 = b0; - b0 = x * b1 - b2 + *p++; - } while (--i); - - return (0.5 * (b0 - b2)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/chdtr.h b/scipy/special/xsf/cephes/chdtr.h deleted file mode 100644 index 68f2fbe52b12..000000000000 --- a/scipy/special/xsf/cephes/chdtr.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* chdtr.c - * - * Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double df, x, y, chdtr(); - * - * y = chdtr( df, x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the left hand tail (from 0 to x) - * of the Chi square probability density function with - * v degrees of freedom. - * - * - * inf. - * - - * 1 | | v/2-1 -t/2 - * P( x | v ) = ----------- | t e dt - * v/2 - | | - * 2 | (v/2) - - * x - * - * where x is the Chi-square variable. - * - * The incomplete Gamma integral is used, according to the - * formula - * - * y = chdtr( v, x ) = igam( v/2.0, x/2.0 ). - * - * - * The arguments must both be positive. - * - * - * - * ACCURACY: - * - * See igam(). - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtr domain x < 0 or v < 1 0.0 - */ -/* chdtrc() - * - * Complemented Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double v, x, y, chdtrc(); - * - * y = chdtrc( v, x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the right hand tail (from x to - * infinity) of the Chi square probability density function - * with v degrees of freedom: - * - * - * inf. - * - - * 1 | | v/2-1 -t/2 - * P( x | v ) = ----------- | t e dt - * v/2 - | | - * 2 | (v/2) - - * x - * - * where x is the Chi-square variable. - * - * The incomplete Gamma integral is used, according to the - * formula - * - * y = chdtr( v, x ) = igamc( v/2.0, x/2.0 ). - * - * - * The arguments must both be positive. - * - * - * - * ACCURACY: - * - * See igamc(). - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtrc domain x < 0 or v < 1 0.0 - */ -/* chdtri() - * - * Inverse of complemented Chi-square distribution - * - * - * - * SYNOPSIS: - * - * double df, x, y, chdtri(); - * - * x = chdtri( df, y ); - * - * - * - * - * DESCRIPTION: - * - * Finds the Chi-square argument x such that the integral - * from x to infinity of the Chi-square density is equal - * to the given cumulative probability y. - * - * This is accomplished using the inverse Gamma integral - * function and the relation - * - * x/2 = igamci( df/2, y ); - * - * - * - * - * ACCURACY: - * - * See igami.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * chdtri domain y < 0 or y > 1 0.0 - * v < 1 - * - */ - -/* chdtr() */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double chdtrc(double df, double x) { - if (x < 0.0) { - set_error("chdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igamc(df / 2.0, x / 2.0)); - } - - XSF_HOST_DEVICE inline double chdtr(double df, double x) { - - if ((x < 0.0)) { /* || (df < 1.0) ) */ - set_error("chdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igam(df / 2.0, x / 2.0)); - } - - XSF_HOST_DEVICE double chdtri(double df, double y) { - double x; - - if ((y < 0.0) || (y > 1.0)) { /* || (df < 1.0) ) */ - set_error("chdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - x = igamci(0.5 * df, y); - return (2.0 * x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/const.h b/scipy/special/xsf/cephes/const.h deleted file mode 100644 index d7b162c5efc8..000000000000 --- a/scipy/special/xsf/cephes/const.h +++ /dev/null @@ -1,87 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - * - * Since we support only IEEE-754 floating point numbers, conditional logic - * supporting other arithmetic types has been removed. - */ - -/* - * - * - * const.c - * - * Globally declared constants - * - * - * - * SYNOPSIS: - * - * extern double nameofconstant; - * - * - * - * - * DESCRIPTION: - * - * This file contains a number of mathematical constants and - * also some needed size parameters of the computer arithmetic. - * The values are supplied as arrays of hexadecimal integers - * for IEEE arithmetic, and in a normal decimal scientific notation for - * other machines. The particular notation used is determined - * by a symbol (IBMPC, or UNK) defined in the include file - * mconf.h. - * - * The default size parameters are as follows. - * - * For UNK mode: - * MACHEP = 1.38777878078144567553E-17 2**-56 - * MAXLOG = 8.8029691931113054295988E1 log(2**127) - * MINLOG = -8.872283911167299960540E1 log(2**-128) - * - * For IEEE arithmetic (IBMPC): - * MACHEP = 1.11022302462515654042E-16 2**-53 - * MAXLOG = 7.09782712893383996843E2 log(2**1024) - * MINLOG = -7.08396418532264106224E2 log(2**-1022) - * - * The global symbols for mathematical constants are - * SQ2OPI = 7.9788456080286535587989E-1 sqrt( 2/pi ) - * LOGSQ2 = 3.46573590279972654709E-1 log(2)/2 - * THPIO4 = 2.35619449019234492885 3*pi/4 - * - * These lists are subject to change. - */ -/* const.c */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -namespace xsf { -namespace cephes { - namespace detail { - constexpr std::uint64_t MAXITER = 500; - constexpr double MACHEP = 1.11022302462515654042E-16; // 2**-53 - constexpr double MAXLOG = 7.09782712893383996732E2; // log(DBL_MAX) - constexpr double MINLOG = -7.451332191019412076235E2; // log 2**-1022 - constexpr double SQRT1OPI = 5.64189583547756286948E-1; // sqrt( 1/pi) - constexpr double SQRT2OPI = 7.9788456080286535587989E-1; // sqrt( 2/pi ) - constexpr double SQRT2PI = 0.79788456080286535587989; // sqrt(2pi) - constexpr double LOGSQ2 = 3.46573590279972654709E-1; // log(2)/2 - constexpr double THPIO4 = 2.35619449019234492885; // 3*pi/4 - constexpr double SQRT3 = 1.732050807568877293527; // sqrt(3) - constexpr double PI180 = 1.74532925199432957692E-2; // pi/180 - constexpr double SQRTPI = 2.50662827463100050242E0; // sqrt(pi) - constexpr double LOGPI = 1.14472988584940017414; // log(pi) - constexpr double MAXGAM = 171.624376956302725; - constexpr double LOGSQRT2PI = 0.9189385332046727; // log(sqrt(pi)) - - // Following two added by SciPy developers. - // Euler's constant - constexpr double SCIPY_EULER = 0.577215664901532860606512090082402431; - // e as long double - constexpr long double SCIPY_El = 2.718281828459045235360287471352662498L; - } // namespace detail -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/dd_real.h b/scipy/special/xsf/cephes/dd_real.h deleted file mode 100644 index 5217a3460119..000000000000 --- a/scipy/special/xsf/cephes/dd_real.h +++ /dev/null @@ -1,576 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - * - * The parts of the qd double-double floating point package used in SciPy - * have been reworked in a more modern C++ style using operator overloading. - */ - -/* - * include/double2.h - * - * This work was supported by the Director, Office of Science, Division - * of Mathematical, Information, and Computational Sciences of the - * U.S. Department of Energy under contract numbers DE-AC03-76SF00098 and - * DE-AC02-05CH11231. - * - * Copyright (c) 2003-2009, The Regents of the University of California, - * through Lawrence Berkeley National Laboratory (subject to receipt of - * any required approvals from U.S. Dept. of Energy) All rights reserved. - * - * By downloading or using this software you are agreeing to the modified - * BSD license "BSD-LBNL-License.doc" (see LICENSE.txt). - */ -/* - * Double-double precision (>= 106-bit significand) floating point - * arithmetic package based on David Bailey's Fortran-90 double-double - * package, with some changes. See - * - * http://www.nersc.gov/~dhbailey/mpdist/mpdist.html - * - * for the original Fortran-90 version. - * - * Overall structure is similar to that of Keith Brigg's C++ double-double - * package. See - * - * http://www-epidem.plansci.cam.ac.uk/~kbriggs/doubledouble.html - * - * for more details. In particular, the fix for x86 computers is borrowed - * from his code. - * - * Yozo Hida - */ - -/* - * This code taken from v2.3.18 of the qd package. - */ - -#pragma once - -#include "../config.h" - -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double __DD_SPLITTER = 134217729.0; // = 2^27 + 1 - constexpr double __DD_SPLIT_THRESH = 6.69692879491417e+299; // = 2^996 - - /************************************************************************* - * The basic routines taking double arguments, returning 1 (or 2) doubles - *************************************************************************/ - - /* volatile is used below to prevent aggressive optimizations which may change - * the result of the error calculations. These volatiles wer e included in the - * original C code and may perhaps still be useful, e.g. if someone compiles with - * --ffastmath. - */ - - /* Computes fl(a+b) and err(a+b). Assumes |a| >= |b|. */ - XSF_HOST_DEVICE inline double quick_two_sum(double a, double b, double *err) { - volatile double s = a + b; - volatile double c = s - a; - *err = b - c; - return s; - } - - /* Computes fl(a+b) and err(a+b). */ - XSF_HOST_DEVICE inline double two_sum(double a, double b, double *err) { - volatile double s = a + b; - volatile double c = s - a; - volatile double d = b - c; - volatile double e = s - c; - *err = (a - e) + d; - return s; - } - - /* Computes fl(a*b) and err(a*b). */ - XSF_HOST_DEVICE inline double two_prod(double a, double b, double *err) { - volatile double p = a * b; - *err = std::fma(a, b, -p); - return p; - } - - /* Computes fl(a*a) and err(a*a). Faster than the above method. */ - XSF_HOST_DEVICE inline double two_sqr(double a, double *err) { - volatile double p = a * a; - *err = std::fma(a, a, -p); - return p; - } - - /* Computes the nearest integer to d. */ - XSF_HOST_DEVICE inline double two_nint(double d) { - if (d == std::floor(d)) { - return d; - } - return std::floor(d + 0.5); - } - - struct double_double { - double hi, lo; - - double_double() = default; - double_double(double high, double low) : hi(high), lo(low) {} - explicit double_double(double high) : hi(high), lo(0.0) {} - - XSF_HOST_DEVICE explicit operator double() const { return hi; } - XSF_HOST_DEVICE explicit operator int() const { return static_cast(hi); } - }; - - // Arithmetic operations - - XSF_HOST_DEVICE inline double_double operator-(const double_double &x) { - return double_double(-x.hi, -x.lo); - } - - XSF_HOST_DEVICE inline double_double operator+(const double_double &lhs, const double_double &rhs) { - /* This one satisfies IEEE style error bound, - due to K. Briggs and W. Kahan. */ - double s1, s2, t1, t2; - - s1 = two_sum(lhs.hi, rhs.hi, &s2); - t1 = two_sum(lhs.lo, rhs.lo, &t2); - s2 += t1; - s1 = quick_two_sum(s1, s2, &s2); - s2 += t2; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator+(const double_double &lhs, const double rhs) { - double s1, s2; - s1 = two_sum(lhs.hi, rhs, &s2); - s2 += lhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator+(const double lhs, const double_double &rhs) { - double s1, s2; - s1 = two_sum(lhs, rhs.hi, &s2); - s2 += rhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator-(const double_double &lhs, const double_double &rhs) { - return lhs + (-rhs); - } - - XSF_HOST_DEVICE inline double_double operator-(const double_double &lhs, const double rhs) { - double s1, s2; - s1 = two_sum(lhs.hi, -rhs, &s2); - s2 += lhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator-(const double lhs, const double_double &rhs) { - double s1, s2; - s1 = two_sum(lhs, -rhs.hi, &s2); - s2 -= rhs.lo; - s1 = quick_two_sum(s1, s2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double operator*(const double_double &lhs, const double_double &rhs) { - double p1, p2; - p1 = two_prod(lhs.hi, rhs.hi, &p2); - p2 += (lhs.hi * rhs.lo + lhs.lo * rhs.hi); - p1 = quick_two_sum(p1, p2, &p2); - return double_double(p1, p2); - } - - XSF_HOST_DEVICE inline double_double operator*(const double_double &lhs, const double rhs) { - double p1, p2, e1, e2; - p1 = two_prod(lhs.hi, rhs, &e1); - p2 = two_prod(lhs.lo, rhs, &e2); - p1 = quick_two_sum(p1, e2 + p2 + e1, &e1); - return double_double(p1, e1); - } - - XSF_HOST_DEVICE inline double_double operator*(const double lhs, const double_double &rhs) { - double p1, p2, e1, e2; - p1 = two_prod(lhs, rhs.hi, &e1); - p2 = two_prod(lhs, rhs.lo, &e2); - p1 = quick_two_sum(p1, e2 + p2 + e1, &e1); - return double_double(p1, e1); - } - - XSF_HOST_DEVICE inline double_double operator/(const double_double &lhs, const double_double &rhs) { - double q1, q2, q3; - double_double r; - - q1 = lhs.hi / rhs.hi; /* approximate quotient */ - - r = lhs - rhs * q1; - - q2 = r.hi / rhs.hi; - r = r - rhs * q2; - - q3 = r.hi / rhs.hi; - - q1 = quick_two_sum(q1, q2, &q2); - r = double_double(q1, q2) + q3; - return r; - } - - XSF_HOST_DEVICE inline double_double operator/(const double_double &lhs, const double rhs) { - return lhs / double_double(rhs); - } - - XSF_HOST_DEVICE inline double_double operator/(const double lhs, const double_double &rhs) { - return double_double(lhs) / rhs; - } - - XSF_HOST_DEVICE inline bool operator==(const double_double &lhs, const double_double &rhs) { - return (lhs.hi == rhs.hi && lhs.lo == rhs.lo); - } - - XSF_HOST_DEVICE inline bool operator==(const double_double &lhs, const double rhs) { - return (lhs.hi == rhs && lhs.lo == 0.0); - } - - XSF_HOST_DEVICE inline bool operator==(const double lhs, const double_double &rhs) { - return (lhs == rhs.hi) && (rhs.lo == 0.0); - } - - XSF_HOST_DEVICE inline bool operator!=(const double_double &lhs, const double_double &rhs) { - return (lhs.hi != rhs.hi) || (lhs.lo != rhs.lo); - } - - XSF_HOST_DEVICE inline bool operator!=(const double_double &lhs, const double rhs) { - return (lhs.hi != rhs) || (lhs.lo != 0.0); - } - - XSF_HOST_DEVICE inline bool operator!=(const double lhs, const double_double &rhs) { - return (rhs.hi != lhs) || (rhs.lo != 0.0); - } - - XSF_HOST_DEVICE inline bool operator<(const double_double &lhs, const double_double &rhs) { - if (lhs.hi < rhs.hi) { - return true; - } - if (lhs.hi > rhs.hi) { - return false; - } - return lhs.lo < rhs.lo; - } - - XSF_HOST_DEVICE inline bool operator<(const double_double &lhs, const double rhs) { - if (lhs.hi < rhs) { - return true; - } - if (lhs.hi > rhs) { - return false; - } - return lhs.lo < 0.0; - } - - template - XSF_HOST_DEVICE bool operator>(const double_double &lhs, const T &rhs) { - return rhs < lhs; - } - - XSF_HOST_DEVICE inline bool operator<(const double lhs, const double_double &rhs) { return rhs > lhs; } - - XSF_HOST_DEVICE inline bool operator>(const double lhs, const double_double &rhs) { return rhs < lhs; } - - XSF_HOST_DEVICE inline bool operator<=(const double_double &lhs, const double_double &rhs) { - if (lhs.hi < rhs.hi) { - return true; - } - if (lhs.hi > rhs.hi) { - return false; - } - return lhs.lo <= rhs.lo; - } - - XSF_HOST_DEVICE inline bool operator<=(const double_double &lhs, const double rhs) { - if (lhs.hi < rhs) { - return true; - } - if (lhs.hi > rhs) { - return false; - } - return lhs.lo <= 0.0; - } - - template - XSF_HOST_DEVICE bool operator>=(const double_double &lhs, const T &rhs) { - return rhs <= lhs; - } - - XSF_HOST_DEVICE inline bool operator>=(const double lhs, const double_double &rhs) { return rhs <= lhs; } - - XSF_HOST_DEVICE inline bool operator<=(const double lhs, const double_double &rhs) { return rhs >= lhs; } - - // Math functions - - XSF_HOST_DEVICE inline double_double mul_pwr2(const double_double &lhs, double rhs) { - /* double-double * double, where double is a power of 2. */ - return double_double(lhs.hi * rhs, lhs.lo * rhs); - } - - XSF_HOST_DEVICE inline bool isfinite(const double_double &a) { return std::isfinite(a.hi); } - - XSF_HOST_DEVICE inline bool isinf(const double_double &a) { return std::isinf(a.hi); } - - XSF_HOST_DEVICE inline double_double round(const double_double &a) { - double hi = two_nint(a.hi); - double lo; - - if (hi == a.hi) { - /* High word is an integer already. Round the low word.*/ - lo = two_nint(a.lo); - - /* Renormalize. This is needed if a.hi = some integer, a.lo = 1/2.*/ - hi = quick_two_sum(hi, lo, &lo); - } else { - /* High word is not an integer. */ - lo = 0.0; - if (std::abs(hi - a.hi) == 0.5 && a.lo < 0.0) { - /* There is a tie in the high word, consult the low word - to break the tie. */ - hi -= 1.0; /* NOTE: This does not cause INEXACT. */ - } - } - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double floor(const double_double &a) { - double hi = std::floor(a.hi); - double lo = 0.0; - - if (hi == a.hi) { - /* High word is integer already. Round the low word. */ - lo = std::floor(a.lo); - hi = quick_two_sum(hi, lo, &lo); - } - - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double ceil(const double_double &a) { - double hi = std::ceil(a.hi); - double lo = 0.0; - - if (hi == a.hi) { - /* High word is integer already. Round the low word. */ - lo = std::ceil(a.lo); - hi = quick_two_sum(hi, lo, &lo); - } - - return double_double(hi, lo); - } - - XSF_HOST_DEVICE inline double_double trunc(const double_double &a) { - return (a.hi >= 0.0) ? floor(a) : ceil(a); - } - - XSF_HOST_DEVICE inline double_double abs(const double_double &a) { return (a.hi < 0.0 ? -a : a); } - - XSF_HOST_DEVICE inline double_double fmod(const double_double &lhs, const double_double &rhs) { - double_double n = trunc(lhs / rhs); - return lhs - rhs * n; - } - - XSF_HOST_DEVICE inline double_double remainder(const double_double &lhs, const double_double &rhs) { - double_double n = round(lhs / rhs); - return lhs - rhs * n; - } - - XSF_HOST_DEVICE inline std::pair divrem(const double_double &lhs, - const double_double &rhs) { - double_double n = round(lhs / rhs); - double_double remainder = lhs - n * rhs; - return {n, remainder}; - } - - XSF_HOST_DEVICE inline double_double fma( - const double_double &a, const double_double &b, const double_double &c) { - // TODO: make an accurate fma - return a * b + c; - } - - XSF_HOST_DEVICE inline double_double square(const double_double &a) { - double p1, p2; - double s1, s2; - p1 = two_sqr(a.hi, &p2); - p2 += 2.0 * a.hi * a.lo; - p2 += a.lo * a.lo; - s1 = quick_two_sum(p1, p2, &s2); - return double_double(s1, s2); - } - - XSF_HOST_DEVICE inline double_double square(const double a) { - double p1, p2; - p1 = two_sqr(a, &p2); - return double_double(p1, p2); - } - - XSF_HOST_DEVICE inline double_double ldexp(const double_double &a, int expt) { - // float128 * (2.0 ^ expt) - return double_double(std::ldexp(a.hi, expt), std::ldexp(a.lo, expt)); - } - - XSF_HOST_DEVICE inline double_double frexp(const double_double &a, int *expt) { - // r"""return b and l s.t. 0.5<=|b|<1 and 2^l == a - // 0.5<=|b[0]|<1.0 or |b[0]| == 1.0 and b[0]*b[1]<0 - // """ - int exponent; - double man = std::frexp(a.hi, &exponent); - double b1 = std::ldexp(a.lo, -exponent); - if (std::abs(man) == 0.5 && man * b1 < 0) { - man *= 2; - b1 *= 2; - exponent -= 1; - } - *expt = exponent; - return double_double(man, b1); - } - - // Numeric limits - - XSF_HOST_DEVICE inline double_double quiet_NaN() { - return double_double(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - } - - XSF_HOST_DEVICE inline double_double infinity() { - return double_double(std::numeric_limits::infinity(), std::numeric_limits::infinity()); - } - - const double_double inv_fact[] = {double_double(1.66666666666666657e-01, 9.25185853854297066e-18), - double_double(4.16666666666666644e-02, 2.31296463463574266e-18), - double_double(8.33333333333333322e-03, 1.15648231731787138e-19), - double_double(1.38888888888888894e-03, -5.30054395437357706e-20), - double_double(1.98412698412698413e-04, 1.72095582934207053e-22), - double_double(2.48015873015873016e-05, 2.15119478667758816e-23), - double_double(2.75573192239858925e-06, -1.85839327404647208e-22), - double_double(2.75573192239858883e-07, 2.37677146222502973e-23), - double_double(2.50521083854417202e-08, -1.44881407093591197e-24), - double_double(2.08767569878681002e-09, -1.20734505911325997e-25), - double_double(1.60590438368216133e-10, 1.25852945887520981e-26), - double_double(1.14707455977297245e-11, 2.06555127528307454e-28), - double_double(7.64716373181981641e-13, 7.03872877733453001e-30), - double_double(4.77947733238738525e-14, 4.39920548583408126e-31), - double_double(2.81145725434552060e-15, 1.65088427308614326e-31)}; - - // Math constants - const double_double E = double_double(2.718281828459045091e+00, 1.445646891729250158e-16); - const double_double LOG2 = double_double(6.931471805599452862e-01, 2.319046813846299558e-17); - const double EPS = 4.93038065763132e-32; // 2^-104 - - /* Exponential. Computes exp(x) in double-double precision. */ - XSF_HOST_DEVICE inline double_double exp(const double_double &a) { - /* Strategy: We first reduce the size of x by noting that - - exp(kr + m * log(2)) = 2^m * exp(r)^k - - where m and k are integers. By choosing m appropriately - we can make |kr| <= log(2) / 2 = 0.347. Then exp(r) is - evaluated using the familiar Taylor series. Reducing the - argument substantially speeds up the convergence. */ - - constexpr double k = 512.0; - constexpr double inv_k = 1.0 / k; - double m; - double_double r, s, t, p; - int i = 0; - - if (a.hi <= -709.0) { - return double_double(0.0); - } - - if (a.hi >= 709.0) { - return infinity(); - } - - if (a == 0.0) { - return double_double(1.0); - } - - if (a == 1.0) { - return E; - } - - m = std::floor(a.hi / LOG2.hi + 0.5); - r = mul_pwr2(double_double(a) - LOG2 * m, inv_k); - - p = square(r); - s = r + mul_pwr2(p, 0.5); - p = p * r; - t = p * inv_fact[0]; - do { - s = s + t; - p = p * r; - ++i; - t = p * inv_fact[i]; - } while ((std::abs(static_cast(t)) > inv_k * EPS) && i < 5); - - s = s + t; - - for (int j = 0; j < 9; j++) { - s = mul_pwr2(s, 2.0) + square(s); - } - s = s + 1.0; - - return ldexp(s, static_cast(m)); - } - - /* Logarithm. Computes log(x) in double-double precision. - This is a natural logarithm (i.e., base e). */ - XSF_HOST_DEVICE inline double_double log(const double_double &a) { - /* Strategy. The Taylor series for log converges much more - slowly than that of exp, due to the lack of the factorial - term in the denominator. Hence this routine instead tries - to determine the root of the function - - f(x) = exp(x) - a - - using Newton iteration. The iteration is given by - - x' = x - f(x)/f'(x) - = x - (1 - a * exp(-x)) - = x + a * exp(-x) - 1. - - Only one iteration is needed, since Newton's iteration - approximately doubles the number of digits per iteration. */ - double_double x; - - if (a == 1.0) { - return double_double(0.0); - } - - if (a.hi <= 0.0) { - return quiet_NaN(); - } - - x = double_double(std::log(a.hi)); /* Initial approximation */ - - /* x = x + a * exp(-x) - 1.0; */ - x = x + a * exp(-x) - 1.0; - return x; - } - - XSF_HOST_DEVICE inline double_double log1p(const double_double &a) { - double_double ans; - double la, elam1, ll; - if (a.hi <= -1.0) { - return -infinity(); - } - la = std::log1p(a.hi); - elam1 = xsf::cephes::expm1(la); - ll = std::log1p(a.lo / (1 + a.hi)); - if (a.hi > 0) { - ll -= (elam1 - a.hi) / (elam1 + 1); - } - ans = double_double(la) + ll; - return ans; - } - } // namespace detail - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellie.h b/scipy/special/xsf/cephes/ellie.h deleted file mode 100644 index a455599b4a95..000000000000 --- a/scipy/special/xsf/cephes/ellie.h +++ /dev/null @@ -1,293 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellie.c - * - * Incomplete elliptic integral of the second kind - * - * - * - * SYNOPSIS: - * - * double phi, m, y, ellie(); - * - * y = ellie( phi, m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * phi - * - - * | | - * | 2 - * E(phi_\m) = | sqrt( 1 - m sin t ) dt - * | - * | | - * - - * 0 - * - * of amplitude phi and modulus m, using the arithmetic - - * geometric mean algorithm. - * - * - * - * ACCURACY: - * - * Tested at random arguments with phi in [-10, 10] and m in - * [0, 1]. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -10,10 150000 3.3e-15 1.4e-16 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987, 1993 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -/* Copyright 2014, Eric W. Moore */ - -/* Incomplete elliptic integral of second kind */ -#pragma once - -#include "../config.h" -#include "const.h" -#include "ellpe.h" -#include "ellpk.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - namespace detail { - - /* To calculate legendre's incomplete elliptical integral of the second kind for - * negative m, we use a power series in phi for small m*phi*phi, an asymptotic - * series in m for large m*phi*phi* and the relation to Carlson's symmetric - * integrals, R_F(x,y,z) and R_D(x,y,z). - * - * E(phi, m) = sin(phi) * R_F(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) - * - m * sin(phi)^3 * R_D(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) / 3 - * - * = R_F(c-1, c-m, c) - m * R_D(c-1, c-m, c) / 3 - * - * where c = csc(phi)^2. We use the second form of this for (approximately) - * phi > 1/(sqrt(DBL_MAX) ~ 1e-154, where csc(phi)^2 overflows. Elsewhere we - * use the first form, accounting for the smallness of phi. - * - * The algorithm used is described in Carlson, B. C. Numerical computation of - * real or complex elliptic integrals. (1994) https://arxiv.org/abs/math/9409227 - * Most variable names reflect Carlson's usage. - * - * In this routine, we assume m < 0 and 0 > phi > pi/2. - */ - XSF_HOST_DEVICE inline double ellie_neg_m(double phi, double m) { - double x, y, z, x1, y1, z1, ret, Q; - double A0f, Af, Xf, Yf, Zf, E2f, E3f, scalef; - double A0d, Ad, seriesn, seriesd, Xd, Yd, Zd, E2d, E3d, E4d, E5d, scaled; - int n = 0; - double mpp = (m * phi) * phi; - - if (-mpp < 1e-6 && phi < -m) { - return phi + (mpp * phi * phi / 30.0 - mpp * mpp / 40.0 - mpp / 6.0) * phi; - } - - if (-mpp > 1e6) { - double sm = std::sqrt(-m); - double sp = std::sin(phi); - double cp = std::cos(phi); - - double a = -cosm1(phi); - double b1 = std::log(4 * sp * sm / (1 + cp)); - double b = -(0.5 + b1) / 2.0 / m; - double c = (0.75 + cp / sp / sp - b1) / 16.0 / m / m; - return (a + b + c) * sm; - } - - if (phi > 1e-153 && m > -1e200) { - double s = std::sin(phi); - double csc2 = 1.0 / s / s; - scalef = 1.0; - scaled = m / 3.0; - x = 1.0 / std::tan(phi) / std::tan(phi); - y = csc2 - m; - z = csc2; - } else { - scalef = phi; - scaled = mpp * phi / 3.0; - x = 1.0; - y = 1 - mpp; - z = 1.0; - } - - if (x == y && x == z) { - return (scalef + scaled / x) / std::sqrt(x); - } - - A0f = (x + y + z) / 3.0; - Af = A0f; - A0d = (x + y + 3.0 * z) / 5.0; - Ad = A0d; - x1 = x; - y1 = y; - z1 = z; - seriesd = 0.0; - seriesn = 1.0; - /* Carlson gives 1/pow(3*r, 1.0/6.0) for this constant. if r == eps, - * it is ~338.38. */ - - /* N.B. This will evaluate its arguments multiple times. */ - Q = 400.0 * std::fmax(std::abs(A0f - x), std::fmax(std::abs(A0f - y), std::abs(A0f - z))); - - while (Q > std::abs(Af) && Q > std::abs(Ad) && n <= 100) { - double sx = std::sqrt(x1); - double sy = std::sqrt(y1); - double sz = std::sqrt(z1); - double lam = sx * sy + sx * sz + sy * sz; - seriesd += seriesn / (sz * (z1 + lam)); - x1 = (x1 + lam) / 4.0; - y1 = (y1 + lam) / 4.0; - z1 = (z1 + lam) / 4.0; - Af = (x1 + y1 + z1) / 3.0; - Ad = (Ad + lam) / 4.0; - n += 1; - Q /= 4.0; - seriesn /= 4.0; - } - - Xf = (A0f - x) / Af / (1 << 2 * n); - Yf = (A0f - y) / Af / (1 << 2 * n); - Zf = -(Xf + Yf); - - E2f = Xf * Yf - Zf * Zf; - E3f = Xf * Yf * Zf; - - ret = scalef * (1.0 - E2f / 10.0 + E3f / 14.0 + E2f * E2f / 24.0 - 3.0 * E2f * E3f / 44.0) / sqrt(Af); - - Xd = (A0d - x) / Ad / (1 << 2 * n); - Yd = (A0d - y) / Ad / (1 << 2 * n); - Zd = -(Xd + Yd) / 3.0; - - E2d = Xd * Yd - 6.0 * Zd * Zd; - E3d = (3 * Xd * Yd - 8.0 * Zd * Zd) * Zd; - E4d = 3.0 * (Xd * Yd - Zd * Zd) * Zd * Zd; - E5d = Xd * Yd * Zd * Zd * Zd; - - ret -= scaled * - (1.0 - 3.0 * E2d / 14.0 + E3d / 6.0 + 9.0 * E2d * E2d / 88.0 - 3.0 * E4d / 22.0 - - 9.0 * E2d * E3d / 52.0 + 3.0 * E5d / 26.0) / - (1 << 2 * n) / Ad / sqrt(Ad); - ret -= 3.0 * scaled * seriesd; - return ret; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double ellie(double phi, double m) { - double a, b, c, e, temp; - double lphi, t, E, denom, npio2; - int d, mod, sign; - - if (std::isnan(phi) || std::isnan(m)) - return std::numeric_limits::quiet_NaN(); - if (m > 1.0) - return std::numeric_limits::quiet_NaN(); - ; - if (std::isinf(phi)) - return phi; - if (std::isinf(m)) - return -m; - if (m == 0.0) - return (phi); - lphi = phi; - npio2 = std::floor(lphi / M_PI_2); - if (std::fmod(std::abs(npio2), 2.0) == 1.0) - npio2 += 1; - lphi = lphi - npio2 * M_PI_2; - if (lphi < 0.0) { - lphi = -lphi; - sign = -1; - } else { - sign = 1; - } - a = 1.0 - m; - E = ellpe(m); - if (a == 0.0) { - temp = std::sin(lphi); - goto done; - } - if (a > 1.0) { - temp = detail::ellie_neg_m(lphi, m); - goto done; - } - - if (lphi < 0.135) { - double m11 = (((((-7.0 / 2816.0) * m + (5.0 / 1056.0)) * m - (7.0 / 2640.0)) * m + (17.0 / 41580.0)) * m - - (1.0 / 155925.0)) * - m; - double m9 = ((((-5.0 / 1152.0) * m + (1.0 / 144.0)) * m - (1.0 / 360.0)) * m + (1.0 / 5670.0)) * m; - double m7 = ((-m / 112.0 + (1.0 / 84.0)) * m - (1.0 / 315.0)) * m; - double m5 = (-m / 40.0 + (1.0 / 30)) * m; - double m3 = -m / 6.0; - double p2 = lphi * lphi; - - temp = ((((m11 * p2 + m9) * p2 + m7) * p2 + m5) * p2 + m3) * p2 * lphi + lphi; - goto done; - } - t = std::tan(lphi); - b = std::sqrt(a); - /* Thanks to Brian Fitzgerald - * for pointing out an instability near odd multiples of pi/2. */ - if (std::abs(t) > 10.0) { - /* Transform the amplitude */ - e = 1.0 / (b * t); - /* ... but avoid multiple recursions. */ - if (std::abs(e) < 10.0) { - e = std::atan(e); - temp = E + m * std::sin(lphi) * std::sin(e) - ellie(e, m); - goto done; - } - } - c = std::sqrt(m); - a = 1.0; - d = 1; - e = 0.0; - mod = 0; - - while (std::abs(c / a) > detail::MACHEP) { - temp = b / a; - lphi = lphi + atan(t * temp) + mod * M_PI; - denom = 1 - temp * t * t; - if (std::abs(denom) > 10 * detail::MACHEP) { - t = t * (1.0 + temp) / denom; - mod = (lphi + M_PI_2) / M_PI; - } else { - t = std::tan(lphi); - mod = static_cast(std::floor((lphi - std::atan(t)) / M_PI)); - } - c = (a - b) / 2.0; - temp = std::sqrt(a * b); - a = (a + b) / 2.0; - b = temp; - d += d; - e += c * std::sin(lphi); - } - - temp = E / ellpk(1.0 - m); - temp *= (std::atan(t) + mod * M_PI) / (d * a); - temp += e; - - done: - - if (sign < 0) - temp = -temp; - temp += npio2 * E; - return (temp); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellik.h b/scipy/special/xsf/cephes/ellik.h deleted file mode 100644 index c05b3ec76c2e..000000000000 --- a/scipy/special/xsf/cephes/ellik.h +++ /dev/null @@ -1,251 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellik.c - * - * Incomplete elliptic integral of the first kind - * - * - * - * SYNOPSIS: - * - * double phi, m, y, ellik(); - * - * y = ellik( phi, m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * - * phi - * - - * | | - * | dt - * F(phi | m) = | ------------------ - * | 2 - * | | sqrt( 1 - m sin t ) - * - - * 0 - * - * of amplitude phi and modulus m, using the arithmetic - - * geometric mean algorithm. - * - * - * - * - * ACCURACY: - * - * Tested at random points with m in [0, 1] and phi as indicated. - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -10,10 200000 7.4e-16 1.0e-16 - * - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -/* Copyright 2014, Eric W. Moore */ - -/* Incomplete elliptic integral of first kind */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "ellpk.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* To calculate legendre's incomplete elliptical integral of the first kind for - * negative m, we use a power series in phi for small m*phi*phi, an asymptotic - * series in m for large m*phi*phi* and the relation to Carlson's symmetric - * integral of the first kind. - * - * F(phi, m) = sin(phi) * R_F(cos(phi)^2, 1 - m * sin(phi)^2, 1.0) - * = R_F(c-1, c-m, c) - * - * where c = csc(phi)^2. We use the second form of this for (approximately) - * phi > 1/(sqrt(DBL_MAX) ~ 1e-154, where csc(phi)^2 overflows. Elsewhere we - * use the first form, accounting for the smallness of phi. - * - * The algorithm used is described in Carlson, B. C. Numerical computation of - * real or complex elliptic integrals. (1994) https://arxiv.org/abs/math/9409227 - * Most variable names reflect Carlson's usage. - * - * In this routine, we assume m < 0 and 0 > phi > pi/2. - */ - XSF_HOST_DEVICE inline double ellik_neg_m(double phi, double m) { - double x, y, z, x1, y1, z1, A0, A, Q, X, Y, Z, E2, E3, scale; - int n = 0; - double mpp = (m * phi) * phi; - - if (-mpp < 1e-6 && phi < -m) { - return phi + (-mpp * phi * phi / 30.0 + 3.0 * mpp * mpp / 40.0 + mpp / 6.0) * phi; - } - - if (-mpp > 4e7) { - double sm = std::sqrt(-m); - double sp = std::sin(phi); - double cp = std::cos(phi); - - double a = std::log(4 * sp * sm / (1 + cp)); - double b = -(1 + cp / sp / sp - a) / 4 / m; - return (a + b) / sm; - } - - if (phi > 1e-153 && m > -1e305) { - double s = std::sin(phi); - double csc2 = 1.0 / (s * s); - scale = 1.0; - x = 1.0 / (std::tan(phi) * std::tan(phi)); - y = csc2 - m; - z = csc2; - } else { - scale = phi; - x = 1.0; - y = 1 - m * scale * scale; - z = 1.0; - } - - if (x == y && x == z) { - return scale / std::sqrt(x); - } - - A0 = (x + y + z) / 3.0; - A = A0; - x1 = x; - y1 = y; - z1 = z; - /* Carlson gives 1/pow(3*r, 1.0/6.0) for this constant. if r == eps, - * it is ~338.38. */ - Q = 400.0 * std::fmax(std::abs(A0 - x), std::fmax(std::abs(A0 - y), std::abs(A0 - z))); - - while (Q > std::abs(A) && n <= 100) { - double sx = std::sqrt(x1); - double sy = std::sqrt(y1); - double sz = std::sqrt(z1); - double lam = sx * sy + sx * sz + sy * sz; - x1 = (x1 + lam) / 4.0; - y1 = (y1 + lam) / 4.0; - z1 = (z1 + lam) / 4.0; - A = (x1 + y1 + z1) / 3.0; - n += 1; - Q /= 4; - } - X = (A0 - x) / A / (1 << 2 * n); - Y = (A0 - y) / A / (1 << 2 * n); - Z = -(X + Y); - - E2 = X * Y - Z * Z; - E3 = X * Y * Z; - - return scale * (1.0 - E2 / 10.0 + E3 / 14.0 + E2 * E2 / 24.0 - 3.0 * E2 * E3 / 44.0) / sqrt(A); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double ellik(double phi, double m) { - double a, b, c, e, temp, t, K, denom, npio2; - int d, mod, sign; - - if (std::isnan(phi) || std::isnan(m)) - return std::numeric_limits::quiet_NaN(); - if (m > 1.0) - return std::numeric_limits::quiet_NaN(); - if (std::isinf(phi) || std::isinf(m)) { - if (std::isinf(m) && std::isfinite(phi)) - return 0.0; - else if (std::isinf(phi) && std::isfinite(m)) - return phi; - else - return std::numeric_limits::quiet_NaN(); - } - if (m == 0.0) - return (phi); - a = 1.0 - m; - if (a == 0.0) { - if (std::abs(phi) >= (double) M_PI_2) { - set_error("ellik", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } - /* DLMF 19.6.8, and 4.23.42 */ - return std::asinh(std::tan(phi)); - } - npio2 = floor(phi / M_PI_2); - if (std::fmod(std::abs(npio2), 2.0) == 1.0) - npio2 += 1; - if (npio2 != 0.0) { - K = ellpk(a); - phi = phi - npio2 * M_PI_2; - } else - K = 0.0; - if (phi < 0.0) { - phi = -phi; - sign = -1; - } else - sign = 0; - if (a > 1.0) { - temp = detail::ellik_neg_m(phi, m); - goto done; - } - b = std::sqrt(a); - t = std::tan(phi); - if (std::abs(t) > 10.0) { - /* Transform the amplitude */ - e = 1.0 / (b * t); - /* ... but avoid multiple recursions. */ - if (std::abs(e) < 10.0) { - e = std::atan(e); - if (npio2 == 0) - K = ellpk(a); - temp = K - ellik(e, m); - goto done; - } - } - a = 1.0; - c = std::sqrt(m); - d = 1; - mod = 0; - - while (std::abs(c / a) > detail::MACHEP) { - temp = b / a; - phi = phi + atan(t * temp) + mod * M_PI; - denom = 1.0 - temp * t * t; - if (std::abs(denom) > 10 * detail::MACHEP) { - t = t * (1.0 + temp) / denom; - mod = (phi + M_PI_2) / M_PI; - } else { - t = std::tan(phi); - mod = static_cast(std::floor((phi - std::atan(t)) / M_PI)); - } - c = (a - b) / 2.0; - temp = std::sqrt(a * b); - a = (a + b) / 2.0; - b = temp; - d += d; - } - - temp = (std::atan(t) + mod * M_PI) / (d * a); - - done: - if (sign < 0) - temp = -temp; - temp += npio2 * K; - return (temp); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpe.h b/scipy/special/xsf/cephes/ellpe.h deleted file mode 100644 index bc7c51f11acb..000000000000 --- a/scipy/special/xsf/cephes/ellpe.h +++ /dev/null @@ -1,107 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpe.c - * - * Complete elliptic integral of the second kind - * - * - * - * SYNOPSIS: - * - * double m, y, ellpe(); - * - * y = ellpe( m ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * pi/2 - * - - * | | 2 - * E(m) = | sqrt( 1 - m sin t ) dt - * | | - * - - * 0 - * - * Where m = 1 - m1, using the approximation - * - * P(x) - x log x Q(x). - * - * Though there are no singularities, the argument m1 is used - * internally rather than m for compatibility with ellpk(). - * - * E(1) = 1; E(0) = pi/2. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 1 10000 2.1e-16 7.3e-17 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * ellpe domain x<0, x>1 0.0 - * - */ - -/* ellpe.c */ - -/* Elliptic integral of second kind */ - -/* - * Cephes Math Library, Release 2.1: February, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - * - * Feb, 2002: altered by Travis Oliphant - * so that it is called with argument m - * (which gets immediately converted to m1 = 1-m) - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ellpe_P[] = {1.53552577301013293365E-4, 2.50888492163602060990E-3, 8.68786816565889628429E-3, - 1.07350949056076193403E-2, 7.77395492516787092951E-3, 7.58395289413514708519E-3, - 1.15688436810574127319E-2, 2.18317996015557253103E-2, 5.68051945617860553470E-2, - 4.43147180560990850618E-1, 1.00000000000000000299E0}; - - constexpr double ellpe_Q[] = {3.27954898576485872656E-5, 1.00962792679356715133E-3, 6.50609489976927491433E-3, - 1.68862163993311317300E-2, 2.61769742454493659583E-2, 3.34833904888224918614E-2, - 4.27180926518931511717E-2, 5.85936634471101055642E-2, 9.37499997197644278445E-2, - 2.49999999999888314361E-1}; - - } // namespace detail - - XSF_HOST_DEVICE inline double ellpe(double x) { - x = 1.0 - x; - if (x <= 0.0) { - if (x == 0.0) - return (1.0); - set_error("ellpe", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (x > 1.0) { - return ellpe(1.0 - 1 / x) * std::sqrt(x); - } - return (polevl(x, detail::ellpe_P, 10) - std::log(x) * (x * polevl(x, detail::ellpe_Q, 9))); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpj.h b/scipy/special/xsf/cephes/ellpj.h deleted file mode 100644 index 7221b7b9232d..000000000000 --- a/scipy/special/xsf/cephes/ellpj.h +++ /dev/null @@ -1,162 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpj.c - * - * Jacobian Elliptic Functions - * - * - * - * SYNOPSIS: - * - * double u, m, sn, cn, dn, phi; - * int ellpj(); - * - * ellpj( u, m, _&sn, _&cn, _&dn, _&phi ); - * - * - * - * DESCRIPTION: - * - * - * Evaluates the Jacobian elliptic functions sn(u|m), cn(u|m), - * and dn(u|m) of parameter m between 0 and 1, and real - * argument u. - * - * These functions are periodic, with quarter-period on the - * real axis equal to the complete elliptic integral - * ellpk(m). - * - * Relation to incomplete elliptic integral: - * If u = ellik(phi,m), then sn(u|m) = sin(phi), - * and cn(u|m) = cos(phi). Phi is called the amplitude of u. - * - * Computation is by means of the arithmetic-geometric mean - * algorithm, except when m is within 1e-9 of 0 or 1. In the - * latter case with m close to 1, the approximation applies - * only for phi < pi/2. - * - * ACCURACY: - * - * Tested at random points with u between 0 and 10, m between - * 0 and 1. - * - * Absolute error (* = relative error): - * arithmetic function # trials peak rms - * IEEE phi 10000 9.2e-16* 1.4e-16* - * IEEE sn 50000 4.1e-15 4.6e-16 - * IEEE cn 40000 3.6e-15 4.4e-16 - * IEEE dn 10000 1.3e-12 1.8e-14 - * - * Peak error observed in consistency check using addition - * theorem for sn(u+v) was 4e-16 (absolute). Also tested by - * the above relation to the incomplete elliptic integral. - * Accuracy deteriorates when u is large. - * - */ - -/* ellpj.c */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Scipy changes: - * - 07-18-2016: improve evaluation of dn near quarter periods - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline int ellpj(double u, double m, double *sn, double *cn, double *dn, double *ph) { - double ai, b, phi, t, twon, dnfac; - double a[9], c[9]; - int i; - - /* Check for special cases */ - if (m < 0.0 || m > 1.0 || std::isnan(m)) { - set_error("ellpj", SF_ERROR_DOMAIN, NULL); - *sn = std::numeric_limits::quiet_NaN(); - *cn = std::numeric_limits::quiet_NaN(); - *ph = std::numeric_limits::quiet_NaN(); - *dn = std::numeric_limits::quiet_NaN(); - return (-1); - } - if (m < 1.0e-9) { - t = std::sin(u); - b = std::cos(u); - ai = 0.25 * m * (u - t * b); - *sn = t - ai * b; - *cn = b + ai * t; - *ph = u - ai; - *dn = 1.0 - 0.5 * m * t * t; - return (0); - } - if (m >= 0.9999999999) { - ai = 0.25 * (1.0 - m); - b = std::cosh(u); - t = std::tanh(u); - phi = 1.0 / b; - twon = b * std::sinh(u); - *sn = t + ai * (twon - u) / (b * b); - *ph = 2.0 * std::atan(exp(u)) - M_PI_2 + ai * (twon - u) / b; - ai *= t * phi; - *cn = phi - ai * (twon - u); - *dn = phi + ai * (twon + u); - return (0); - } - - /* A. G. M. scale. See DLMF 22.20(ii) */ - a[0] = 1.0; - b = std::sqrt(1.0 - m); - c[0] = std::sqrt(m); - twon = 1.0; - i = 0; - - while (std::abs(c[i] / a[i]) > detail::MACHEP) { - if (i > 7) { - set_error("ellpj", SF_ERROR_OVERFLOW, NULL); - goto done; - } - ai = a[i]; - ++i; - c[i] = (ai - b) / 2.0; - t = std::sqrt(ai * b); - a[i] = (ai + b) / 2.0; - b = t; - twon *= 2.0; - } - - done: - /* backward recurrence */ - phi = twon * a[i] * u; - do { - t = c[i] * std::sin(phi) / a[i]; - b = phi; - phi = (std::asin(t) + phi) / 2.0; - } while (--i); - - *sn = std::sin(phi); - t = std::cos(phi); - *cn = t; - dnfac = std::cos(phi - b); - /* See discussion after DLMF 22.20.5 */ - if (std::abs(dnfac) < 0.1) { - *dn = std::sqrt(1 - m * (*sn) * (*sn)); - } else { - *dn = t / dnfac; - } - *ph = phi; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ellpk.h b/scipy/special/xsf/cephes/ellpk.h deleted file mode 100644 index 39ebf7e80b19..000000000000 --- a/scipy/special/xsf/cephes/ellpk.h +++ /dev/null @@ -1,117 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ellpk.c - * - * Complete elliptic integral of the first kind - * - * - * - * SYNOPSIS: - * - * double m1, y, ellpk(); - * - * y = ellpk( m1 ); - * - * - * - * DESCRIPTION: - * - * Approximates the integral - * - * - * - * pi/2 - * - - * | | - * | dt - * K(m) = | ------------------ - * | 2 - * | | sqrt( 1 - m sin t ) - * - - * 0 - * - * where m = 1 - m1, using the approximation - * - * P(x) - log x Q(x). - * - * The argument m1 is used internally rather than m so that the logarithmic - * singularity at m = 1 will be shifted to the origin; this - * preserves maximum accuracy. - * - * K(0) = pi/2. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1 30000 2.5e-16 6.8e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * ellpk domain x<0, x>1 0.0 - * - */ - -/* ellpk.c */ - -/* - * Cephes Math Library, Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ellpk_P[] = {1.37982864606273237150E-4, 2.28025724005875567385E-3, 7.97404013220415179367E-3, - 9.85821379021226008714E-3, 6.87489687449949877925E-3, 6.18901033637687613229E-3, - 8.79078273952743772254E-3, 1.49380448916805252718E-2, 3.08851465246711995998E-2, - 9.65735902811690126535E-2, 1.38629436111989062502E0}; - - constexpr double ellpk_Q[] = {2.94078955048598507511E-5, 9.14184723865917226571E-4, 5.94058303753167793257E-3, - 1.54850516649762399335E-2, 2.39089602715924892727E-2, 3.01204715227604046988E-2, - 3.73774314173823228969E-2, 4.88280347570998239232E-2, 7.03124996963957469739E-2, - 1.24999999999870820058E-1, 4.99999999999999999821E-1}; - - constexpr double ellpk_C1 = 1.3862943611198906188E0; /* log(4) */ - - } // namespace detail - - XSF_HOST_DEVICE inline double ellpk(double x) { - - if (x < 0.0) { - set_error("ellpk", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (x > 1.0) { - if (std::isinf(x)) { - return 0.0; - } - return ellpk(1 / x) / std::sqrt(x); - } - - if (x > detail::MACHEP) { - return (polevl(x, detail::ellpk_P, 10) - std::log(x) * polevl(x, detail::ellpk_Q, 10)); - } else { - if (x == 0.0) { - set_error("ellpk", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } else { - return (detail::ellpk_C1 - 0.5 * std::log(x)); - } - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/erfinv.h b/scipy/special/xsf/cephes/erfinv.h deleted file mode 100644 index a7e88e08a528..000000000000 --- a/scipy/special/xsf/cephes/erfinv.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "ndtri.h" - -namespace xsf { -namespace cephes { - - /* - * Inverse of the error function. - * - * Computes the inverse of the error function on the restricted domain - * -1 < y < 1. This restriction ensures the existence of a unique result - * such that erf(erfinv(y)) = y. - */ - XSF_HOST_DEVICE inline double erfinv(double y) { - constexpr double domain_lb = -1; - constexpr double domain_ub = 1; - - constexpr double thresh = 1e-7; - - /* - * For small arguments, use the Taylor expansion - * erf(y) = 2/\sqrt{\pi} (y - y^3 / 3 + O(y^5)), y\to 0 - * where we only retain the linear term. - * Otherwise, y + 1 loses precision for |y| << 1. - */ - if ((-thresh < y) && (y < thresh)) { - return y / M_2_SQRTPI; - } - if ((domain_lb < y) && (y < domain_ub)) { - return ndtri(0.5 * (y + 1)) * M_SQRT1_2; - } else if (y == domain_lb) { - return -std::numeric_limits::infinity(); - } else if (y == domain_ub) { - return std::numeric_limits::infinity(); - } else if (std::isnan(y)) { - set_error("erfinv", SF_ERROR_DOMAIN, NULL); - return y; - } else { - set_error("erfinv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - - /* - * Inverse of the complementary error function. - * - * Computes the inverse of the complimentary error function on the restricted - * domain 0 < y < 2. This restriction ensures the existence of a unique result - * such that erfc(erfcinv(y)) = y. - */ - XSF_HOST_DEVICE inline double erfcinv(double y) { - constexpr double domain_lb = 0; - constexpr double domain_ub = 2; - - if ((domain_lb < y) && (y < domain_ub)) { - return -ndtri(0.5 * y) * M_SQRT1_2; - } else if (y == domain_lb) { - return std::numeric_limits::infinity(); - } else if (y == domain_ub) { - return -std::numeric_limits::infinity(); - } else if (std::isnan(y)) { - set_error("erfcinv", SF_ERROR_DOMAIN, NULL); - return y; - } else { - set_error("erfcinv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/exp10.h b/scipy/special/xsf/cephes/exp10.h deleted file mode 100644 index 56e8e62866c9..000000000000 --- a/scipy/special/xsf/cephes/exp10.h +++ /dev/null @@ -1,130 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* exp10.c - * - * Base 10 exponential function - * (Common antilogarithm) - * - * - * - * SYNOPSIS: - * - * double x, y, exp10(); - * - * y = exp10( x ); - * - * - * - * DESCRIPTION: - * - * Returns 10 raised to the x power. - * - * Range reduction is accomplished by expressing the argument - * as 10**x = 2**n 10**f, with |f| < 0.5 log10(2). - * The Pade' form - * - * 1 + 2x P(x**2)/( Q(x**2) - P(x**2) ) - * - * is used to approximate 10**f. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -307,+307 30000 2.2e-16 5.5e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * exp10 underflow x < -MAXL10 0.0 - * exp10 overflow x > MAXL10 INFINITY - * - * IEEE arithmetic: MAXL10 = 308.2547155599167. - * - */ - -/* - * Cephes Math Library Release 2.2: January, 1991 - * Copyright 1984, 1991 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double exp10_P[] = { - 4.09962519798587023075E-2, - 1.17452732554344059015E1, - 4.06717289936872725516E2, - 2.39423741207388267439E3, - }; - - constexpr double exp10_Q[] = { - /* 1.00000000000000000000E0, */ - 8.50936160849306532625E1, - 1.27209271178345121210E3, - 2.07960819286001865907E3, - }; - - /* static double LOG102 = 3.01029995663981195214e-1; */ - constexpr double exp10_LOG210 = 3.32192809488736234787e0; - constexpr double exp10_LG102A = 3.01025390625000000000E-1; - constexpr double exp10_LG102B = 4.60503898119521373889E-6; - - /* static double MAXL10 = 38.230809449325611792; */ - constexpr double exp10_MAXL10 = 308.2547155599167; - - } // namespace detail - - XSF_HOST_DEVICE inline double exp10(double x) { - double px, xx; - short n; - - if (std::isnan(x)) { - return (x); - } - if (x > detail::exp10_MAXL10) { - return (std::numeric_limits::infinity()); - } - - if (x < -detail::exp10_MAXL10) { /* Would like to use MINLOG but can't */ - set_error("exp10", SF_ERROR_UNDERFLOW, NULL); - return (0.0); - } - - /* Express 10**x = 10**g 2**n - * = 10**g 10**( n log10(2) ) - * = 10**( g + n log10(2) ) - */ - px = std::floor(detail::exp10_LOG210 * x + 0.5); - n = px; - x -= px * detail::exp10_LG102A; - x -= px * detail::exp10_LG102B; - - /* rational approximation for exponential - * of the fractional part: - * 10**x = 1 + 2x P(x**2)/( Q(x**2) - P(x**2) ) - */ - xx = x * x; - px = x * polevl(xx, detail::exp10_P, 3); - x = px / (p1evl(xx, detail::exp10_Q, 3) - px); - x = 1.0 + std::ldexp(x, 1); - - /* multiply by power of 2 */ - x = std::ldexp(x, n); - - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/exp2.h b/scipy/special/xsf/cephes/exp2.h deleted file mode 100644 index 144fd26bc8ca..000000000000 --- a/scipy/special/xsf/cephes/exp2.h +++ /dev/null @@ -1,122 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* exp2.c - * - * Base 2 exponential function - * - * - * - * SYNOPSIS: - * - * double x, y, exp2(); - * - * y = exp2( x ); - * - * - * - * DESCRIPTION: - * - * Returns 2 raised to the x power. - * - * Range reduction is accomplished by separating the argument - * into an integer k and fraction f such that - * x k f - * 2 = 2 2. - * - * A Pade' form - * - * 1 + 2x P(x**2) / (Q(x**2) - x P(x**2) ) - * - * approximates 2**x in the basic range [-0.5, 0.5]. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -1022,+1024 30000 1.8e-16 5.4e-17 - * - * - * See exp.c for comments on error amplification. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * exp underflow x < -MAXL2 0.0 - * exp overflow x > MAXL2 INFINITY - * - * For IEEE arithmetic, MAXL2 = 1024. - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" - -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double exp2_P[] = { - 2.30933477057345225087E-2, - 2.02020656693165307700E1, - 1.51390680115615096133E3, - }; - - constexpr double exp2_Q[] = { - /* 1.00000000000000000000E0, */ - 2.33184211722314911771E2, - 4.36821166879210612817E3, - }; - - constexpr double exp2_MAXL2 = 1024.0; - constexpr double exp2_MINL2 = -1024.0; - - } // namespace detail - - XSF_HOST_DEVICE inline double exp2(double x) { - double px, xx; - short n; - - if (std::isnan(x)) { - return (x); - } - if (x > detail::exp2_MAXL2) { - return (std::numeric_limits::infinity()); - } - - if (x < detail::exp2_MINL2) { - return (0.0); - } - - xx = x; /* save x */ - /* separate into integer and fractional parts */ - px = std::floor(x + 0.5); - n = px; - x = x - px; - - /* rational approximation - * exp2(x) = 1 + 2xP(xx)/(Q(xx) - P(xx)) - * where xx = x**2 - */ - xx = x * x; - px = x * polevl(xx, detail::exp2_P, 2); - x = px / (p1evl(xx, detail::exp2_Q, 2) - px); - x = 1.0 + std::ldexp(x, 1); - - /* scale by power of 2 */ - x = std::ldexp(x, n); - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/expn.h b/scipy/special/xsf/cephes/expn.h deleted file mode 100644 index 8b0b07eab7a9..000000000000 --- a/scipy/special/xsf/cephes/expn.h +++ /dev/null @@ -1,260 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* expn.c - * - * Exponential integral En - * - * - * - * SYNOPSIS: - * - * int n; - * double x, y, expn(); - * - * y = expn( n, x ); - * - * - * - * DESCRIPTION: - * - * Evaluates the exponential integral - * - * inf. - * - - * | | -xt - * | e - * E (x) = | ---- dt. - * n | n - * | | t - * - - * 1 - * - * - * Both n and x must be nonnegative. - * - * The routine employs either a power series, a continued - * fraction, or an asymptotic formula depending on the - * relative values of n and x. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 10000 1.7e-15 3.6e-16 - * - */ - -/* expn.c */ - -/* Cephes Math Library Release 1.1: March, 1985 - * Copyright 1985 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 */ - -/* Sources - * [1] NIST, "The Digital Library of Mathematical Functions", dlmf.nist.gov - */ - -/* Scipy changes: - * - 09-10-2016: improved asymptotic expansion for large n - */ - -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "rgamma.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int expn_nA = 13; - constexpr double expn_A0[] = {1.00000000000000000}; - constexpr double expn_A1[] = {1.00000000000000000}; - constexpr double expn_A2[] = {-2.00000000000000000, 1.00000000000000000}; - constexpr double expn_A3[] = {6.00000000000000000, -8.00000000000000000, 1.00000000000000000}; - constexpr double expn_A4[] = {-24.0000000000000000, 58.0000000000000000, -22.0000000000000000, - 1.00000000000000000}; - constexpr double expn_A5[] = {120.000000000000000, -444.000000000000000, 328.000000000000000, - -52.0000000000000000, 1.00000000000000000}; - constexpr double expn_A6[] = {-720.000000000000000, 3708.00000000000000, -4400.00000000000000, - 1452.00000000000000, -114.000000000000000, 1.00000000000000000}; - constexpr double expn_A7[] = {5040.00000000000000, -33984.0000000000000, 58140.0000000000000, - -32120.0000000000000, 5610.00000000000000, -240.000000000000000, - 1.00000000000000000}; - constexpr double expn_A8[] = {-40320.0000000000000, 341136.000000000000, -785304.000000000000, - 644020.000000000000, -195800.000000000000, 19950.0000000000000, - -494.000000000000000, 1.00000000000000000}; - constexpr double expn_A9[] = {362880.000000000000, -3733920.00000000000, 11026296.0000000000, - -12440064.0000000000, 5765500.00000000000, -1062500.00000000000, - 67260.0000000000000, -1004.00000000000000, 1.00000000000000000}; - constexpr double expn_A10[] = {-3628800.00000000000, 44339040.0000000000, -162186912.000000000, - 238904904.000000000, -155357384.000000000, 44765000.0000000000, - -5326160.00000000000, 218848.000000000000, -2026.00000000000000, - 1.00000000000000000}; - constexpr double expn_A11[] = {39916800.0000000000, -568356480.000000000, 2507481216.00000000, - -4642163952.00000000, 4002695088.00000000, -1648384304.00000000, - 314369720.000000000, -25243904.0000000000, 695038.000000000000, - -4072.00000000000000, 1.00000000000000000}; - constexpr double expn_A12[] = {-479001600.000000000, 7827719040.00000000, -40788301824.0000000, - 92199790224.0000000, -101180433024.000000, 56041398784.0000000, - -15548960784.0000000, 2051482776.00000000, -114876376.000000000, - 2170626.00000000000, -8166.00000000000000, 1.00000000000000000}; - constexpr const double *expn_A[] = {expn_A0, expn_A1, expn_A2, expn_A3, expn_A4, expn_A5, expn_A6, - expn_A7, expn_A8, expn_A9, expn_A10, expn_A11, expn_A12}; - constexpr int expn_Adegs[] = {0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; - - /* Asymptotic expansion for large n, DLMF 8.20(ii) */ - XSF_HOST_DEVICE double expn_large_n(int n, double x) { - int k; - double p = n; - double lambda = x / p; - double multiplier = 1 / p / (lambda + 1) / (lambda + 1); - double fac = 1; - double res = 1; /* A[0] = 1 */ - double expfac, term; - - expfac = std::exp(-lambda * p) / (lambda + 1) / p; - if (expfac == 0) { - set_error("expn", SF_ERROR_UNDERFLOW, NULL); - return 0; - } - - /* Do the k = 1 term outside the loop since A[1] = 1 */ - fac *= multiplier; - res += fac; - - for (k = 2; k < expn_nA; k++) { - fac *= multiplier; - term = fac * polevl(lambda, expn_A[k], expn_Adegs[k]); - res += term; - if (std::abs(term) < MACHEP * std::abs(res)) { - break; - } - } - - return expfac * res; - } - } // namespace detail - - XSF_HOST_DEVICE double expn(int n, double x) { - double ans, r, t, yk, xk; - double pk, pkm1, pkm2, qk, qkm1, qkm2; - double psi, z; - int i, k; - constexpr double big = 1.44115188075855872E+17; - - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } else if (n < 0 || x < 0) { - set_error("expn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x > detail::MAXLOG) { - return (0.0); - } - - if (x == 0.0) { - if (n < 2) { - set_error("expn", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else { - return (1.0 / (n - 1.0)); - } - } - - if (n == 0) { - return (std::exp(-x) / x); - } - - /* Asymptotic expansion for large n, DLMF 8.20(ii) */ - if (n > 50) { - ans = detail::expn_large_n(n, x); - return ans; - } - - /* Continued fraction, DLMF 8.19.17 */ - if (x > 1.0) { - k = 1; - pkm2 = 1.0; - qkm2 = x; - pkm1 = 1.0; - qkm1 = x + n; - ans = pkm1 / qkm1; - - do { - k += 1; - if (k & 1) { - yk = 1.0; - xk = n + (k - 1) / 2; - } else { - yk = x; - xk = k / 2; - } - pk = pkm1 * yk + pkm2 * xk; - qk = qkm1 * yk + qkm2 * xk; - if (qk != 0) { - r = pk / qk; - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if (std::abs(pk) > big) { - pkm2 /= big; - pkm1 /= big; - qkm2 /= big; - qkm1 /= big; - } - } while (t > detail::MACHEP); - - ans *= std::exp(-x); - return ans; - } - - /* Power series expansion, DLMF 8.19.8 */ - psi = -detail::SCIPY_EULER - std::log(x); - for (i = 1; i < n; i++) { - psi = psi + 1.0 / i; - } - - z = -x; - xk = 0.0; - yk = 1.0; - pk = 1.0 - n; - if (n == 1) { - ans = 0.0; - } else { - ans = 1.0 / pk; - } - do { - xk += 1.0; - yk *= z / xk; - pk += 1.0; - if (pk != 0.0) { - ans += yk / pk; - } - if (ans != 0.0) - t = std::abs(yk / ans); - else - t = 1.0; - } while (t > detail::MACHEP); - k = xk; - t = n; - r = n - 1; - ans = (std::pow(z, r) * psi * rgamma(t)) - ans; - return ans; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/fdtr.h b/scipy/special/xsf/cephes/fdtr.h deleted file mode 100644 index 27cd992e02d8..000000000000 --- a/scipy/special/xsf/cephes/fdtr.h +++ /dev/null @@ -1,223 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* fdtr.c - * - * F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, y, fdtr(); - * - * y = fdtr( df1, df2, x ); - * - * DESCRIPTION: - * - * Returns the area from zero to x under the F density - * function (also known as Snedcor's density or the - * variance ratio density). This is the density - * of x = (u1/df1)/(u2/df2), where u1 and u2 are random - * variables having Chi square distributions with df1 - * and df2 degrees of freedom, respectively. - * - * The incomplete beta integral is used, according to the - * formula - * - * P(x) = incbet( df1/2, df2/2, (df1*x/(df2 + df1*x) ). - * - * - * The arguments a and b are greater than zero, and x is - * nonnegative. - * - * ACCURACY: - * - * Tested at random points (a,b,x). - * - * x a,b Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0,1 0,100 100000 9.8e-15 1.7e-15 - * IEEE 1,5 0,100 100000 6.5e-15 3.5e-16 - * IEEE 0,1 1,10000 100000 2.2e-11 3.3e-12 - * IEEE 1,5 1,10000 100000 1.1e-11 1.7e-13 - * See also incbet.c. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtr domain a<0, b<0, x<0 0.0 - * - */ - -/* fdtrc() - * - * Complemented F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, y, fdtrc(); - * - * y = fdtrc( df1, df2, x ); - * - * DESCRIPTION: - * - * Returns the area from x to infinity under the F density - * function (also known as Snedcor's density or the - * variance ratio density). - * - * - * inf. - * - - * 1 | | a-1 b-1 - * 1-P(x) = ------ | t (1-t) dt - * B(a,b) | | - * - - * x - * - * - * The incomplete beta integral is used, according to the - * formula - * - * P(x) = incbet( df2/2, df1/2, (df2/(df2 + df1*x) ). - * - * - * ACCURACY: - * - * Tested at random points (a,b,x) in the indicated intervals. - * x a,b Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0,1 1,100 100000 3.7e-14 5.9e-16 - * IEEE 1,5 1,100 100000 8.0e-15 1.6e-15 - * IEEE 0,1 1,10000 100000 1.8e-11 3.5e-13 - * IEEE 1,5 1,10000 100000 2.0e-11 3.0e-12 - * See also incbet.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtrc domain a<0, b<0, x<0 0.0 - * - */ - -/* fdtri() - * - * Inverse of F distribution - * - * - * - * SYNOPSIS: - * - * double df1, df2; - * double x, p, fdtri(); - * - * x = fdtri( df1, df2, p ); - * - * DESCRIPTION: - * - * Finds the F density argument x such that the integral - * from -infinity to x of the F density is equal to the - * given probability p. - * - * This is accomplished using the inverse beta integral - * function and the relations - * - * z = incbi( df2/2, df1/2, p ) - * x = df2 (1-z) / (df1 z). - * - * Note: the following relations hold for the inverse of - * the uncomplemented F distribution: - * - * z = incbi( df1/2, df2/2, p ) - * x = df2 z / (df1 (1-z)). - * - * ACCURACY: - * - * Tested at random points (a,b,p). - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * For p between .001 and 1: - * IEEE 1,100 100000 8.3e-15 4.7e-16 - * IEEE 1,10000 100000 2.1e-11 1.4e-13 - * For p between 10^-6 and 10^-3: - * IEEE 1,100 50000 1.3e-12 8.4e-15 - * IEEE 1,10000 50000 3.0e-12 4.8e-14 - * See also fdtrc.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * fdtri domain p <= 0 or p > 1 NaN - * v < 1 - * - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double fdtrc(double a, double b, double x) { - double w; - - if ((a <= 0.0) || (b <= 0.0) || (x < 0.0)) { - set_error("fdtrc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - w = b / (b + a * x); - return incbet(0.5 * b, 0.5 * a, w); - } - - XSF_HOST_DEVICE inline double fdtr(double a, double b, double x) { - double w; - - if ((a <= 0.0) || (b <= 0.0) || (x < 0.0)) { - set_error("fdtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - w = a * x; - w = w / (b + w); - return incbet(0.5 * a, 0.5 * b, w); - } - - XSF_HOST_DEVICE inline double fdtri(double a, double b, double y) { - double w, x; - - if ((a <= 0.0) || (b <= 0.0) || (y <= 0.0) || (y > 1.0)) { - set_error("fdtri", SF_ERROR_DOMAIN, NULL); - return NAN; - } - y = 1.0 - y; - /* Compute probability for x = 0.5. */ - w = incbet(0.5 * b, 0.5 * a, 0.5); - /* If that is greater than y, then the solution w < .5. - * Otherwise, solve at 1-y to remove cancellation in (b - b*w). */ - if (w > y || y < 0.001) { - w = incbi(0.5 * b, 0.5 * a, y); - x = (b - b * w) / (a * w); - } else { - w = incbi(0.5 * a, 0.5 * b, 1.0 - y); - x = b * w / (a * (1.0 - w)); - } - return x; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/fresnl.h b/scipy/special/xsf/cephes/fresnl.h deleted file mode 100644 index 6ac27ffa884a..000000000000 --- a/scipy/special/xsf/cephes/fresnl.h +++ /dev/null @@ -1,191 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* fresnl.c - * - * Fresnel integral - * - * - * - * SYNOPSIS: - * - * double x, S, C; - * void fresnl(); - * - * fresnl( x, _&S, _&C ); - * - * - * DESCRIPTION: - * - * Evaluates the Fresnel integrals - * - * x - * - - * | | - * C(x) = | cos(pi/2 t**2) dt, - * | | - * - - * 0 - * - * x - * - - * | | - * S(x) = | sin(pi/2 t**2) dt. - * | | - * - - * 0 - * - * - * The integrals are evaluated by a power series for x < 1. - * For x >= 1 auxiliary functions f(x) and g(x) are employed - * such that - * - * C(x) = 0.5 + f(x) sin( pi/2 x**2 ) - g(x) cos( pi/2 x**2 ) - * S(x) = 0.5 - f(x) cos( pi/2 x**2 ) - g(x) sin( pi/2 x**2 ) - * - * - * - * ACCURACY: - * - * Relative error. - * - * Arithmetic function domain # trials peak rms - * IEEE S(x) 0, 10 10000 2.0e-15 3.2e-16 - * IEEE C(x) 0, 10 10000 1.8e-15 3.3e-16 - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* S(x) for small x */ - constexpr double fresnl_sn[6] = { - -2.99181919401019853726E3, 7.08840045257738576863E5, -6.29741486205862506537E7, - 2.54890880573376359104E9, -4.42979518059697779103E10, 3.18016297876567817986E11, - }; - - constexpr double fresnl_sd[6] = { - /* 1.00000000000000000000E0, */ - 2.81376268889994315696E2, 4.55847810806532581675E4, 5.17343888770096400730E6, - 4.19320245898111231129E8, 2.24411795645340920940E10, 6.07366389490084639049E11, - }; - - /* C(x) for small x */ - constexpr double fresnl_cn[6] = { - -4.98843114573573548651E-8, 9.50428062829859605134E-6, -6.45191435683965050962E-4, - 1.88843319396703850064E-2, -2.05525900955013891793E-1, 9.99999999999999998822E-1, - }; - - constexpr double fresnl_cd[7] = { - 3.99982968972495980367E-12, 9.15439215774657478799E-10, 1.25001862479598821474E-7, - 1.22262789024179030997E-5, 8.68029542941784300606E-4, 4.12142090722199792936E-2, - 1.00000000000000000118E0, - }; - - /* Auxiliary function f(x) */ - constexpr double fresnl_fn[10] = { - 4.21543555043677546506E-1, 1.43407919780758885261E-1, 1.15220955073585758835E-2, - 3.45017939782574027900E-4, 4.63613749287867322088E-6, 3.05568983790257605827E-8, - 1.02304514164907233465E-10, 1.72010743268161828879E-13, 1.34283276233062758925E-16, - 3.76329711269987889006E-20, - }; - - constexpr double fresnl_fd[10] = { - /* 1.00000000000000000000E0, */ - 7.51586398353378947175E-1, 1.16888925859191382142E-1, 6.44051526508858611005E-3, - 1.55934409164153020873E-4, 1.84627567348930545870E-6, 1.12699224763999035261E-8, - 3.60140029589371370404E-11, 5.88754533621578410010E-14, 4.52001434074129701496E-17, - 1.25443237090011264384E-20, - }; - - /* Auxiliary function g(x) */ - constexpr double fresnl_gn[11] = { - 5.04442073643383265887E-1, 1.97102833525523411709E-1, 1.87648584092575249293E-2, - 6.84079380915393090172E-4, 1.15138826111884280931E-5, 9.82852443688422223854E-8, - 4.45344415861750144738E-10, 1.08268041139020870318E-12, 1.37555460633261799868E-15, - 8.36354435630677421531E-19, 1.86958710162783235106E-22, - }; - - constexpr double fresnl_gd[11] = { - /* 1.00000000000000000000E0, */ - 1.47495759925128324529E0, 3.37748989120019970451E-1, 2.53603741420338795122E-2, - 8.14679107184306179049E-4, 1.27545075667729118702E-5, 1.04314589657571990585E-7, - 4.60680728146520428211E-10, 1.10273215066240270757E-12, 1.38796531259578871258E-15, - 8.39158816283118707363E-19, 1.86958710162783236342E-22, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int fresnl(double xxa, double *ssa, double *cca) { - double f, g, cc, ss, c, s, t, u; - double x, x2; - - if (std::isinf(xxa)) { - cc = 0.5; - ss = 0.5; - goto done; - } - - x = std::abs(xxa); - x2 = x * x; - if (x2 < 2.5625) { - t = x2 * x2; - ss = x * x2 * polevl(t, detail::fresnl_sn, 5) / p1evl(t, detail::fresnl_sd, 6); - cc = x * polevl(t, detail::fresnl_cn, 5) / polevl(t, detail::fresnl_cd, 6); - goto done; - } - - if (x > 36974.0) { - /* - * http://functions.wolfram.com/GammaBetaErf/FresnelC/06/02/ - * http://functions.wolfram.com/GammaBetaErf/FresnelS/06/02/ - */ - cc = 0.5 + 1 / (M_PI * x) * sinpi(x * x / 2); - ss = 0.5 - 1 / (M_PI * x) * cospi(x * x / 2); - goto done; - } - - /* Asymptotic power series auxiliary functions - * for large argument - */ - x2 = x * x; - t = M_PI * x2; - u = 1.0 / (t * t); - t = 1.0 / t; - f = 1.0 - u * polevl(u, detail::fresnl_fn, 9) / p1evl(u, detail::fresnl_fd, 10); - g = t * polevl(u, detail::fresnl_gn, 10) / p1evl(u, detail::fresnl_gd, 11); - - c = cospi(x2 / 2); - s = sinpi(x2 / 2); - t = M_PI * x; - cc = 0.5 + (f * s - g * c) / t; - ss = 0.5 - (f * c + g * s) / t; - - done: - if (xxa < 0.0) { - cc = -cc; - ss = -ss; - } - - *cca = cc; - *ssa = ss; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/gamma.h b/scipy/special/xsf/cephes/gamma.h deleted file mode 100644 index 1ede1571a67e..000000000000 --- a/scipy/special/xsf/cephes/gamma.h +++ /dev/null @@ -1,398 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, Gamma(); - * - * y = Gamma( x ); - * - * - * - * DESCRIPTION: - * - * Returns Gamma function of the argument. The result is - * correctly signed. - * - * Arguments |x| <= 34 are reduced by recurrence and the function - * approximated by a rational function of degree 6/7 in the - * interval (2,3). Large arguments are handled by Stirling's - * formula. Large negative arguments are made positive using - * a reflection formula. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -170,-33 20000 2.3e-15 3.3e-16 - * IEEE -33, 33 20000 9.4e-16 2.2e-16 - * IEEE 33, 171.6 20000 2.3e-15 3.2e-16 - * - * Error for arguments outside the test range will be larger - * owing to error amplification by the exponential function. - * - */ - -/* lgam() - * - * Natural logarithm of Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, lgam(); - * - * y = lgam( x ); - * - * - * - * DESCRIPTION: - * - * Returns the base e (2.718...) logarithm of the absolute - * value of the Gamma function of the argument. - * - * For arguments greater than 13, the logarithm of the Gamma - * function is approximated by the logarithmic version of - * Stirling's formula using a polynomial approximation of - * degree 4. Arguments between -33 and +33 are reduced by - * recurrence to the interval [2,3] of a rational approximation. - * The cosecant reflection formula is employed for arguments - * less than -33. - * - * Arguments greater than MAXLGM return INFINITY and an error - * message. MAXLGM = 2.556348e305 for IEEE arithmetic. - * - * - * - * ACCURACY: - * - * - * arithmetic domain # trials peak rms - * IEEE 0, 3 28000 5.4e-16 1.1e-16 - * IEEE 2.718, 2.556e305 40000 3.5e-16 8.3e-17 - * The error criterion was relative when the function magnitude - * was greater than one but absolute when it was less than one. - * - * The following test used the relative error criterion, though - * at certain points the relative error could be much higher than - * indicated. - * IEEE -200, -4 10000 4.8e-16 1.3e-16 - * - */ - -/* - * Cephes Math Library Release 2.2: July, 1992 - * Copyright 1984, 1987, 1989, 1992 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - namespace detail { - constexpr double gamma_P[] = {1.60119522476751861407E-4, 1.19135147006586384913E-3, 1.04213797561761569935E-2, - 4.76367800457137231464E-2, 2.07448227648435975150E-1, 4.94214826801497100753E-1, - 9.99999999999999996796E-1}; - - constexpr double gamma_Q[] = {-2.31581873324120129819E-5, 5.39605580493303397842E-4, -4.45641913851797240494E-3, - 1.18139785222060435552E-2, 3.58236398605498653373E-2, -2.34591795718243348568E-1, - 7.14304917030273074085E-2, 1.00000000000000000320E0}; - - /* Stirling's formula for the Gamma function */ - constexpr double gamma_STIR[5] = { - 7.87311395793093628397E-4, -2.29549961613378126380E-4, -2.68132617805781232825E-3, - 3.47222221605458667310E-3, 8.33333333333482257126E-2, - }; - - constexpr double MAXSTIR = 143.01608; - - /* Gamma function computed by Stirling's formula. - * The polynomial STIR is valid for 33 <= x <= 172. - */ - XSF_HOST_DEVICE inline double stirf(double x) { - double y, w, v; - - if (x >= MAXGAM) { - return (std::numeric_limits::infinity()); - } - w = 1.0 / x; - w = 1.0 + w * xsf::cephes::polevl(w, gamma_STIR, 4); - y = std::exp(x); - if (x > MAXSTIR) { /* Avoid overflow in pow() */ - v = std::pow(x, 0.5 * x - 0.25); - y = v * (v / y); - } else { - y = std::pow(x, x - 0.5) / y; - } - y = SQRTPI * y * w; - return (y); - } - } // namespace detail - - XSF_HOST_DEVICE inline double Gamma(double x) { - double p, q, z; - int i; - int sgngam = 1; - - if (!std::isfinite(x)) { - if (x > 0) { - // gamma(+inf) = +inf - return x; - } - // gamma(NaN) and gamma(-inf) both should equal NaN. - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - /* For pole at zero, value depends on sign of zero. - * +inf when approaching from right, -inf when approaching - * from left. */ - return std::copysign(std::numeric_limits::infinity(), x); - } - - q = std::abs(x); - - if (q > 33.0) { - if (x < 0.0) { - p = std::floor(q); - if (p == q) { - // x is a negative integer. This is a pole. - set_error("Gamma", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::quiet_NaN()); - } - i = p; - if ((i & 1) == 0) { - sgngam = -1; - } - z = q - p; - if (z > 0.5) { - p += 1.0; - z = q - p; - } - z = q * sinpi(z); - if (z == 0.0) { - return (sgngam * std::numeric_limits::infinity()); - } - z = std::abs(z); - z = M_PI / (z * detail::stirf(q)); - } else { - z = detail::stirf(x); - } - return (sgngam * z); - } - - z = 1.0; - while (x >= 3.0) { - x -= 1.0; - z *= x; - } - - while (x < 0.0) { - if (x > -1.E-9) { - goto small; - } - z /= x; - x += 1.0; - } - - while (x < 2.0) { - if (x < 1.e-9) { - goto small; - } - z /= x; - x += 1.0; - } - - if (x == 2.0) { - return (z); - } - - x -= 2.0; - p = polevl(x, detail::gamma_P, 6); - q = polevl(x, detail::gamma_Q, 7); - return (z * p / q); - - small: - if (x == 0.0) { - /* For this to have happened, x must have started as a negative integer. */ - set_error("Gamma", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::quiet_NaN()); - } else - return (z / ((1.0 + 0.5772156649015329 * x) * x)); - } - - namespace detail { - /* A[]: Stirling's formula expansion of log Gamma - * B[], C[]: log Gamma function between 2 and 3 - */ - constexpr double gamma_A[] = {8.11614167470508450300E-4, -5.95061904284301438324E-4, 7.93650340457716943945E-4, - -2.77777777730099687205E-3, 8.33333333333331927722E-2}; - - constexpr double gamma_B[] = {-1.37825152569120859100E3, -3.88016315134637840924E4, -3.31612992738871184744E5, - -1.16237097492762307383E6, -1.72173700820839662146E6, -8.53555664245765465627E5}; - - constexpr double gamma_C[] = { - /* 1.00000000000000000000E0, */ - -3.51815701436523470549E2, -1.70642106651881159223E4, -2.20528590553854454839E5, - -1.13933444367982507207E6, -2.53252307177582951285E6, -2.01889141433532773231E6}; - - /* log( sqrt( 2*pi ) ) */ - constexpr double LS2PI = 0.91893853320467274178; - - constexpr double MAXLGM = 2.556348e305; - - /* Disable optimizations for this function on 32 bit systems when compiling with GCC. - * We've found that enabling optimizations can result in degraded precision - * for this asymptotic approximation in that case. */ -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC push_options -#pragma GCC optimize("00") -#endif - XSF_HOST_DEVICE inline double lgam_large_x(double x) { - double q = (x - 0.5) * std::log(x) - x + LS2PI; - if (x > 1.0e8) { - return (q); - } - double p = 1.0 / (x * x); - p = ((7.9365079365079365079365e-4 * p - 2.7777777777777777777778e-3) * p + 0.0833333333333333333333) / x; - return q + p; - } -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC pop_options -#endif - - XSF_HOST_DEVICE inline double lgam_sgn(double x, int *sign) { - double p, q, u, w, z; - int i; - - *sign = 1; - - if (!std::isfinite(x)) { - return x; - } - - if (x < -34.0) { - q = -x; - w = lgam_sgn(q, sign); - p = std::floor(q); - if (p == q) { - lgsing: - set_error("lgam", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); - } - i = p; - if ((i & 1) == 0) { - *sign = -1; - } else { - *sign = 1; - } - z = q - p; - if (z > 0.5) { - p += 1.0; - z = p - q; - } - z = q * sinpi(z); - if (z == 0.0) { - goto lgsing; - } - /* z = log(M_PI) - log( z ) - w; */ - z = LOGPI - std::log(z) - w; - return (z); - } - - if (x < 13.0) { - z = 1.0; - p = 0.0; - u = x; - while (u >= 3.0) { - p -= 1.0; - u = x + p; - z *= u; - } - while (u < 2.0) { - if (u == 0.0) { - goto lgsing; - } - z /= u; - p += 1.0; - u = x + p; - } - if (z < 0.0) { - *sign = -1; - z = -z; - } else { - *sign = 1; - } - if (u == 2.0) { - return (std::log(z)); - } - p -= 2.0; - x = x + p; - p = x * polevl(x, gamma_B, 5) / p1evl(x, gamma_C, 6); - return (std::log(z) + p); - } - - if (x > MAXLGM) { - return (*sign * std::numeric_limits::infinity()); - } - - if (x >= 1000.0) { - return lgam_large_x(x); - } - - q = (x - 0.5) * std::log(x) - x + LS2PI; - p = 1.0 / (x * x); - return q + polevl(p, gamma_A, 4) / x; - } - } // namespace detail - - /* Logarithm of Gamma function */ - XSF_HOST_DEVICE inline double lgam(double x) { - int sign; - return detail::lgam_sgn(x, &sign); - } - - /* Sign of the Gamma function */ - XSF_HOST_DEVICE inline double gammasgn(double x) { - double fx; - - if (std::isnan(x)) { - return x; - } - if (x > 0) { - return 1.0; - } - if (x == 0) { - return std::copysign(1.0, x); - } - if (std::isinf(x)) { - // x > 0 case handled, so x must be negative infinity. - return std::numeric_limits::quiet_NaN(); - } - fx = std::floor(x); - if (x - fx == 0.0) { - return std::numeric_limits::quiet_NaN(); - } - // sign of gamma for x in (-n, -n+1) for positive integer n is (-1)^n. - if (static_cast(fx) % 2) { - return -1.0; - } - return 1.0; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/gdtr.h b/scipy/special/xsf/cephes/gdtr.h deleted file mode 100644 index 5123e1690443..000000000000 --- a/scipy/special/xsf/cephes/gdtr.h +++ /dev/null @@ -1,140 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* gdtr.c - * - * Gamma distribution function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, gdtr(); - * - * y = gdtr( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Returns the integral from zero to x of the Gamma probability - * density function: - * - * - * x - * b - - * a | | b-1 -at - * y = ----- | t e dt - * - | | - * | (b) - - * 0 - * - * The incomplete Gamma integral is used, according to the - * relation - * - * y = igam( b, ax ). - * - * - * ACCURACY: - * - * See igam(). - * - * ERROR MESSAGES: - * - * message condition value returned - * gdtr domain x < 0 0.0 - * - */ -/* gdtrc.c - * - * Complemented Gamma distribution function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, gdtrc(); - * - * y = gdtrc( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Returns the integral from x to infinity of the Gamma - * probability density function: - * - * - * inf. - * b - - * a | | b-1 -at - * y = ----- | t e dt - * - | | - * | (b) - - * x - * - * The incomplete Gamma integral is used, according to the - * relation - * - * y = igamc( b, ax ). - * - * - * ACCURACY: - * - * See igamc(). - * - * ERROR MESSAGES: - * - * message condition value returned - * gdtrc domain x < 0 0.0 - * - */ - -/* gdtr() */ - -/* - * Cephes Math Library Release 2.3: March,1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double gdtr(double a, double b, double x) { - - if (x < 0.0) { - sf_error("gdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igam(b, a * x)); - } - - XSF_HOST_DEVICE inline double gdtrc(double a, double b, double x) { - - if (x < 0.0) { - set_error("gdtrc", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - return (igamc(b, a * x)); - } - - XSF_HOST_DEVICE inline double gdtri(double a, double b, double y) { - - if ((y < 0.0) || (y > 1.0) || (a <= 0.0) || (b < 0.0)) { - sf_error("gdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - return (igamci(b, 1.0 - y) / a); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/hyp2f1.h b/scipy/special/xsf/cephes/hyp2f1.h deleted file mode 100644 index f9ec54bb2032..000000000000 --- a/scipy/special/xsf/cephes/hyp2f1.h +++ /dev/null @@ -1,596 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* hyp2f1.c - * - * Gauss hypergeometric function F - * 2 1 - * - * - * SYNOPSIS: - * - * double a, b, c, x, y, hyp2f1(); - * - * y = hyp2f1( a, b, c, x ); - * - * - * DESCRIPTION: - * - * - * hyp2f1( a, b, c, x ) = F ( a, b; c; x ) - * 2 1 - * - * inf. - * - a(a+1)...(a+k) b(b+1)...(b+k) k+1 - * = 1 + > ----------------------------- x . - * - c(c+1)...(c+k) (k+1)! - * k = 0 - * - * Cases addressed are - * Tests and escapes for negative integer a, b, or c - * Linear transformation if c - a or c - b negative integer - * Special case c = a or c = b - * Linear transformation for x near +1 - * Transformation for x < -0.5 - * Psi function expansion if x > 0.5 and c - a - b integer - * Conditionally, a recurrence on c to make c-a-b > 0 - * - * x < -1 AMS 15.3.7 transformation applied (Travis Oliphant) - * valid for b,a,c,(b-a) != integer and (c-a),(c-b) != negative integer - * - * x >= 1 is rejected (unless special cases are present) - * - * The parameters a, b, c are considered to be integer - * valued if they are within 1.0e-14 of the nearest integer - * (1.0e-13 for IEEE arithmetic). - * - * ACCURACY: - * - * - * Relative error (-1 < x < 1): - * arithmetic domain # trials peak rms - * IEEE -1,7 230000 1.2e-11 5.2e-14 - * - * Several special cases also tested with a, b, c in - * the range -7 to 7. - * - * ERROR MESSAGES: - * - * A "partial loss of precision" message is printed if - * the internally estimated relative error exceeds 1^-12. - * A "singularity" message is printed on overflow or - * in cases not addressed (such as x < -1). - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "rgamma.h" -#include "psi.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double hyp2f1_EPS = 1.0e-13; - - constexpr double hyp2f1_ETHRESH = 1.0e-12; - constexpr std::uint64_t hyp2f1_MAXITER = 10000; - - /* hys2f1 and hyp2f1ra depend on each other, so we need this prototype */ - XSF_HOST_DEVICE double hyp2f1ra(double a, double b, double c, double x, double *loss); - - /* Defining power series expansion of Gauss hypergeometric function */ - /* The `loss` parameter estimates loss of significance */ - XSF_HOST_DEVICE double hys2f1(double a, double b, double c, double x, double *loss) { - double f, g, h, k, m, s, u, umax; - std::uint64_t i; - int ib, intflag = 0; - - if (std::abs(b) > std::abs(a)) { - /* Ensure that |a| > |b| ... */ - f = b; - b = a; - a = f; - } - - ib = std::round(b); - - if (std::abs(b - ib) < hyp2f1_EPS && ib <= 0 && std::abs(b) < std::abs(a)) { - /* .. except when `b` is a smaller negative integer */ - f = b; - b = a; - a = f; - intflag = 1; - } - - if ((std::abs(a) > std::abs(c) + 1 || intflag) && std::abs(c - a) > 2 && std::abs(a) > 2) { - /* |a| >> |c| implies that large cancellation error is to be expected. - * - * We try to reduce it with the recurrence relations - */ - return hyp2f1ra(a, b, c, x, loss); - } - - i = 0; - umax = 0.0; - f = a; - g = b; - h = c; - s = 1.0; - u = 1.0; - k = 0.0; - do { - if (std::abs(h) < hyp2f1_EPS) { - *loss = 1.0; - return std::numeric_limits::infinity(); - } - m = k + 1.0; - u = u * ((f + k) * (g + k) * x / ((h + k) * m)); - s += u; - k = std::abs(u); /* remember largest term summed */ - if (k > umax) - umax = k; - k = m; - if (++i > hyp2f1_MAXITER) { /* should never happen */ - *loss = 1.0; - return (s); - } - } while (s == 0 || std::abs(u / s) > MACHEP); - - /* return estimated relative error */ - *loss = (MACHEP * umax) / fabs(s) + (MACHEP * i); - - return (s); - } - - /* Apply transformations for |x| near 1 then call the power series */ - XSF_HOST_DEVICE double hyt2f1(double a, double b, double c, double x, double *loss) { - double p, q, r, s, t, y, w, d, err, err1; - double ax, id, d1, d2, e, y1; - int i, aid, sign; - - int ia, ib, neg_int_a = 0, neg_int_b = 0; - - ia = std::round(a); - ib = std::round(b); - - if (a <= 0 && std::abs(a - ia) < hyp2f1_EPS) { /* a is a negative integer */ - neg_int_a = 1; - } - - if (b <= 0 && std::abs(b - ib) < hyp2f1_EPS) { /* b is a negative integer */ - neg_int_b = 1; - } - - err = 0.0; - s = 1.0 - x; - if (x < -0.5 && !(neg_int_a || neg_int_b)) { - if (b > a) - y = std::pow(s, -a) * hys2f1(a, c - b, c, -x / s, &err); - - else - y = std::pow(s, -b) * hys2f1(c - a, b, c, -x / s, &err); - - goto done; - } - - d = c - a - b; - id = std::round(d); /* nearest integer to d */ - - if (x > 0.9 && !(neg_int_a || neg_int_b)) { - if (std::abs(d - id) > MACHEP) { - int sgngam; - - /* test for integer c-a-b */ - /* Try the power series first */ - y = hys2f1(a, b, c, x, &err); - if (err < hyp2f1_ETHRESH) { - goto done; - } - /* If power series fails, then apply AMS55 #15.3.6 */ - q = hys2f1(a, b, 1.0 - d, s, &err); - sign = 1; - w = lgam_sgn(d, &sgngam); - sign *= sgngam; - w -= lgam_sgn(c - a, &sgngam); - sign *= sgngam; - w -= lgam_sgn(c - b, &sgngam); - sign *= sgngam; - q *= sign * std::exp(w); - r = std::pow(s, d) * hys2f1(c - a, c - b, d + 1.0, s, &err1); - sign = 1; - w = lgam_sgn(-d, &sgngam); - sign *= sgngam; - w -= lgam_sgn(a, &sgngam); - sign *= sgngam; - w -= lgam_sgn(b, &sgngam); - sign *= sgngam; - r *= sign * std::exp(w); - y = q + r; - - q = std::abs(q); /* estimate cancellation error */ - r = std::abs(r); - if (q > r) { - r = q; - } - err += err1 + (MACHEP * r) / y; - - y *= xsf::cephes::Gamma(c); - goto done; - } else { - /* Psi function expansion, AMS55 #15.3.10, #15.3.11, #15.3.12 - * - * Although AMS55 does not explicitly state it, this expansion fails - * for negative integer a or b, since the psi and Gamma functions - * involved have poles. - */ - - if (id >= 0.0) { - e = d; - d1 = d; - d2 = 0.0; - aid = id; - } else { - e = -d; - d1 = 0.0; - d2 = d; - aid = -id; - } - - ax = std::log(s); - - /* sum for t = 0 */ - y = xsf::cephes::psi(1.0) + xsf::cephes::psi(1.0 + e) - xsf::cephes::psi(a + d1) - - xsf::cephes::psi(b + d1) - ax; - y *= xsf::cephes::rgamma(e + 1.0); - - p = (a + d1) * (b + d1) * s * xsf::cephes::rgamma(e + 2.0); /* Poch for t=1 */ - t = 1.0; - do { - r = xsf::cephes::psi(1.0 + t) + xsf::cephes::psi(1.0 + t + e) - - xsf::cephes::psi(a + t + d1) - xsf::cephes::psi(b + t + d1) - ax; - q = p * r; - y += q; - p *= s * (a + t + d1) / (t + 1.0); - p *= (b + t + d1) / (t + 1.0 + e); - t += 1.0; - if (t > hyp2f1_MAXITER) { /* should never happen */ - set_error("hyp2f1", SF_ERROR_SLOW, NULL); - *loss = 1.0; - return std::numeric_limits::quiet_NaN(); - } - } while (y == 0 || std::abs(q / y) > hyp2f1_EPS); - - if (id == 0.0) { - y *= xsf::cephes::Gamma(c) / (xsf::cephes::Gamma(a) * xsf::cephes::Gamma(b)); - goto psidon; - } - - y1 = 1.0; - - if (aid == 1) - goto nosum; - - t = 0.0; - p = 1.0; - for (i = 1; i < aid; i++) { - r = 1.0 - e + t; - p *= s * (a + t + d2) * (b + t + d2) / r; - t += 1.0; - p /= t; - y1 += p; - } - nosum: - p = xsf::cephes::Gamma(c); - y1 *= xsf::cephes::Gamma(e) * p * - (xsf::cephes::rgamma(a + d1) * xsf::cephes::rgamma(b + d1)); - - y *= p * (xsf::cephes::rgamma(a + d2) * xsf::cephes::rgamma(b + d2)); - if ((aid & 1) != 0) - y = -y; - - q = std::pow(s, id); /* s to the id power */ - if (id > 0.0) - y *= q; - else - y1 *= q; - - y += y1; - psidon: - goto done; - } - } - - /* Use defining power series if no special cases */ - y = hys2f1(a, b, c, x, &err); - - done: - *loss = err; - return (y); - } - - /* - 15.4.2 Abramowitz & Stegun. - */ - XSF_HOST_DEVICE double hyp2f1_neg_c_equal_bc(double a, double b, double x) { - double k; - double collector = 1; - double sum = 1; - double collector_max = 1; - - if (!(std::abs(b) < 1e5)) { - return std::numeric_limits::quiet_NaN(); - } - - for (k = 1; k <= -b; k++) { - collector *= (a + k - 1) * x / k; - collector_max = std::fmax(std::abs(collector), collector_max); - sum += collector; - } - - if (1e-16 * (1 + collector_max / std::abs(sum)) > 1e-7) { - return std::numeric_limits::quiet_NaN(); - } - - return sum; - } - - /* - * Evaluate hypergeometric function by two-term recurrence in `a`. - * - * This avoids some of the loss of precision in the strongly alternating - * hypergeometric series, and can be used to reduce the `a` and `b` parameters - * to smaller values. - * - * AMS55 #15.2.10 - */ - XSF_HOST_DEVICE double hyp2f1ra(double a, double b, double c, double x, double *loss) { - double f2, f1, f0; - int n; - double t, err, da; - - /* Don't cross c or zero */ - if ((c < 0 && a <= c) || (c >= 0 && a >= c)) { - da = std::round(a - c); - } else { - da = std::round(a); - } - t = a - da; - - *loss = 0; - - XSF_ASSERT(da != 0); - - if (std::abs(da) > hyp2f1_MAXITER) { - /* Too expensive to compute this value, so give up */ - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - *loss = 1.0; - return std::numeric_limits::quiet_NaN(); - } - - if (da < 0) { - /* Recurse down */ - f2 = 0; - f1 = hys2f1(t, b, c, x, &err); - *loss += err; - f0 = hys2f1(t - 1, b, c, x, &err); - *loss += err; - t -= 1; - for (n = 1; n < -da; ++n) { - f2 = f1; - f1 = f0; - f0 = -(2 * t - c - t * x + b * x) / (c - t) * f1 - t * (x - 1) / (c - t) * f2; - t -= 1; - } - } else { - /* Recurse up */ - f2 = 0; - f1 = hys2f1(t, b, c, x, &err); - *loss += err; - f0 = hys2f1(t + 1, b, c, x, &err); - *loss += err; - t += 1; - for (n = 1; n < da; ++n) { - f2 = f1; - f1 = f0; - f0 = -((2 * t - c - t * x + b * x) * f1 + (c - t) * f2) / (t * (x - 1)); - t += 1; - } - } - - return f0; - } - } // namespace detail - - XSF_HOST_DEVICE double hyp2f1(double a, double b, double c, double x) { - double d, d1, d2, e; - double p, q, r, s, y, ax; - double ia, ib, ic, id, err; - double t1; - int i, aid; - int neg_int_a = 0, neg_int_b = 0; - int neg_int_ca_or_cb = 0; - - err = 0.0; - ax = std::abs(x); - s = 1.0 - x; - ia = std::round(a); /* nearest integer to a */ - ib = std::round(b); - - if (x == 0.0) { - return 1.0; - } - - d = c - a - b; - id = std::round(d); - - if ((a == 0 || b == 0) && c != 0) { - return 1.0; - } - - if (a <= 0 && std::abs(a - ia) < detail::hyp2f1_EPS) { /* a is a negative integer */ - neg_int_a = 1; - } - - if (b <= 0 && std::abs(b - ib) < detail::hyp2f1_EPS) { /* b is a negative integer */ - neg_int_b = 1; - } - - if (d <= -1 && !(std::abs(d - id) > detail::hyp2f1_EPS && s < 0) && !(neg_int_a || neg_int_b)) { - return std::pow(s, d) * hyp2f1(c - a, c - b, c, x); - } - if (d <= 0 && x == 1 && !(neg_int_a || neg_int_b)) - goto hypdiv; - - if (ax < 1.0 || x == -1.0) { - /* 2F1(a,b;b;x) = (1-x)**(-a) */ - if (std::abs(b - c) < detail::hyp2f1_EPS) { /* b = c */ - if (neg_int_b) { - y = detail::hyp2f1_neg_c_equal_bc(a, b, x); - } else { - y = std::pow(s, -a); /* s to the -a power */ - } - goto hypdon; - } - if (std::abs(a - c) < detail::hyp2f1_EPS) { /* a = c */ - y = std::pow(s, -b); /* s to the -b power */ - goto hypdon; - } - } - - if (c <= 0.0) { - ic = std::round(c); /* nearest integer to c */ - if (std::abs(c - ic) < detail::hyp2f1_EPS) { /* c is a negative integer */ - /* check if termination before explosion */ - if (neg_int_a && (ia > ic)) - goto hypok; - if (neg_int_b && (ib > ic)) - goto hypok; - goto hypdiv; - } - } - - if (neg_int_a || neg_int_b) /* function is a polynomial */ - goto hypok; - - t1 = std::abs(b - a); - if (x < -2.0 && std::abs(t1 - round(t1)) > detail::hyp2f1_EPS) { - /* This transform has a pole for b-a integer, and - * may produce large cancellation errors for |1/x| close 1 - */ - p = hyp2f1(a, 1 - c + a, 1 - b + a, 1.0 / x); - q = hyp2f1(b, 1 - c + b, 1 - a + b, 1.0 / x); - p *= std::pow(-x, -a); - q *= std::pow(-x, -b); - t1 = Gamma(c); - s = t1 * Gamma(b - a) * (rgamma(b) * rgamma(c - a)); - y = t1 * Gamma(a - b) * (rgamma(a) * rgamma(c - b)); - return s * p + y * q; - } else if (x < -1.0) { - if (std::abs(a) < std::abs(b)) { - return std::pow(s, -a) * hyp2f1(a, c - b, c, x / (x - 1)); - } else { - return std::pow(s, -b) * hyp2f1(b, c - a, c, x / (x - 1)); - } - } - - if (ax > 1.0) /* series diverges */ - goto hypdiv; - - p = c - a; - ia = std::round(p); /* nearest integer to c-a */ - if ((ia <= 0.0) && (std::abs(p - ia) < detail::hyp2f1_EPS)) /* negative int c - a */ - neg_int_ca_or_cb = 1; - - r = c - b; - ib = std::round(r); /* nearest integer to c-b */ - if ((ib <= 0.0) && (std::abs(r - ib) < detail::hyp2f1_EPS)) /* negative int c - b */ - neg_int_ca_or_cb = 1; - - id = std::round(d); /* nearest integer to d */ - q = std::abs(d - id); - - /* Thanks to Christian Burger - * for reporting a bug here. */ - if (std::abs(ax - 1.0) < detail::hyp2f1_EPS) { /* |x| == 1.0 */ - if (x > 0.0) { - if (neg_int_ca_or_cb) { - if (d >= 0.0) - goto hypf; - else - goto hypdiv; - } - if (d <= 0.0) - goto hypdiv; - y = Gamma(c) * Gamma(d) * (rgamma(p) * rgamma(r)); - goto hypdon; - } - if (d <= -1.0) - goto hypdiv; - } - - /* Conditionally make d > 0 by recurrence on c - * AMS55 #15.2.27 - */ - if (d < 0.0) { - /* Try the power series first */ - y = detail::hyt2f1(a, b, c, x, &err); - if (err < detail::hyp2f1_ETHRESH) - goto hypdon; - /* Apply the recurrence if power series fails */ - err = 0.0; - aid = 2 - id; - e = c + aid; - d2 = hyp2f1(a, b, e, x); - d1 = hyp2f1(a, b, e + 1.0, x); - q = a + b + 1.0; - for (i = 0; i < aid; i++) { - r = e - 1.0; - y = (e * (r - (2.0 * e - q) * x) * d2 + (e - a) * (e - b) * x * d1) / (e * r * s); - e = r; - d1 = d2; - d2 = y; - } - goto hypdon; - } - - if (neg_int_ca_or_cb) { - goto hypf; /* negative integer c-a or c-b */ - } - - hypok: - y = detail::hyt2f1(a, b, c, x, &err); - - hypdon: - if (err > detail::hyp2f1_ETHRESH) { - set_error("hyp2f1", SF_ERROR_LOSS, NULL); - /* printf( "Estimated err = %.2e\n", err ); */ - } - return (y); - - /* The transformation for c-a or c-b negative integer - * AMS55 #15.3.3 - */ - hypf: - y = std::pow(s, d) * detail::hys2f1(c - a, c - b, c, x, &err); - goto hypdon; - - /* The alarm exit */ - hypdiv: - set_error("hyp2f1", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/hyperg.h b/scipy/special/xsf/cephes/hyperg.h deleted file mode 100644 index 18ebcff69ba4..000000000000 --- a/scipy/special/xsf/cephes/hyperg.h +++ /dev/null @@ -1,361 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* hyperg.c - * - * Confluent hypergeometric function - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, hyperg(); - * - * y = hyperg( a, b, x ); - * - * - * - * DESCRIPTION: - * - * Computes the confluent hypergeometric function - * - * 1 2 - * a x a(a+1) x - * F ( a,b;x ) = 1 + ---- + --------- + ... - * 1 1 b 1! b(b+1) 2! - * - * Many higher transcendental functions are special cases of - * this power series. - * - * As is evident from the formula, b must not be a negative - * integer or zero unless a is an integer with 0 >= a > b. - * - * The routine attempts both a direct summation of the series - * and an asymptotic expansion. In each case error due to - * roundoff, cancellation, and nonconvergence is estimated. - * The result with smaller estimated error is returned. - * - * - * - * ACCURACY: - * - * Tested at random points (a, b, x), all three variables - * ranging from 0 to 30. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 1.8e-14 1.1e-15 - * - * Larger errors can be observed when b is near a negative - * integer or zero. Certain combinations of arguments yield - * serious cancellation error in the power series summation - * and also are not in the region of near convergence of the - * asymptotic series. An error message is printed if the - * self-estimated relative error is greater than 1.0e-12. - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "rgamma.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* the `type` parameter determines what converging factor to use */ - XSF_HOST_DEVICE inline double hyp2f0(double a, double b, double x, int type, double *err) { - double a0, alast, t, tlast, maxt; - double n, an, bn, u, sum, temp; - - an = a; - bn = b; - a0 = 1.0e0; - alast = 1.0e0; - sum = 0.0; - n = 1.0e0; - t = 1.0e0; - tlast = 1.0e9; - maxt = 0.0; - - do { - if (an == 0) - goto pdone; - if (bn == 0) - goto pdone; - - u = an * (bn * x / n); - - /* check for blowup */ - temp = std::abs(u); - if ((temp > 1.0) && (maxt > (std::numeric_limits::max() / temp))) - goto error; - - a0 *= u; - t = std::abs(a0); - - /* terminating condition for asymptotic series: - * the series is divergent (if a or b is not a negative integer), - * but its leading part can be used as an asymptotic expansion - */ - if (t > tlast) - goto ndone; - - tlast = t; - sum += alast; /* the sum is one term behind */ - alast = a0; - - if (n > 200) - goto ndone; - - an += 1.0e0; - bn += 1.0e0; - n += 1.0e0; - if (t > maxt) - maxt = t; - } while (t > MACHEP); - - pdone: /* series converged! */ - - /* estimate error due to roundoff and cancellation */ - *err = std::abs(MACHEP * (n + maxt)); - - alast = a0; - goto done; - - ndone: /* series did not converge */ - - /* The following "Converging factors" are supposed to improve accuracy, - * but do not actually seem to accomplish very much. */ - - n -= 1.0; - x = 1.0 / x; - - switch (type) { /* "type" given as subroutine argument */ - case 1: - alast *= (0.5 + (0.125 + 0.25 * b - 0.5 * a + 0.25 * x - 0.25 * n) / x); - break; - - case 2: - alast *= 2.0 / 3.0 - b + 2.0 * a + x - n; - break; - - default:; - } - - /* estimate error due to roundoff, cancellation, and nonconvergence */ - *err = MACHEP * (n + maxt) + std::abs(a0); - - done: - sum += alast; - return (sum); - - /* series blew up: */ - error: - *err = std::numeric_limits::infinity(); - set_error("hyperg", SF_ERROR_NO_RESULT, NULL); - return (sum); - } - - /* asymptotic formula for hypergeometric function: - * - * ( -a - * -- ( |z| - * | (b) ( -------- 2f0( a, 1+a-b, -1/x ) - * ( -- - * ( | (b-a) - * - * - * x a-b ) - * e |x| ) - * + -------- 2f0( b-a, 1-a, 1/x ) ) - * -- ) - * | (a) ) - */ - - XSF_HOST_DEVICE inline double hy1f1a(double a, double b, double x, double *err) { - double h1, h2, t, u, temp, acanc, asum, err1, err2; - - if (x == 0) { - acanc = 1.0; - asum = std::numeric_limits::infinity(); - goto adone; - } - temp = std::log(std::abs(x)); - t = x + temp * (a - b); - u = -temp * a; - - if (b > 0) { - temp = xsf::cephes::lgam(b); - t += temp; - u += temp; - } - - h1 = hyp2f0(a, a - b + 1, -1.0 / x, 1, &err1); - - temp = std::exp(u) * xsf::cephes::rgamma(b - a); - h1 *= temp; - err1 *= temp; - - h2 = hyp2f0(b - a, 1.0 - a, 1.0 / x, 2, &err2); - - if (a < 0) - temp = std::exp(t) * xsf::cephes::rgamma(a); - else - temp = std::exp(t - xsf::cephes::lgam(a)); - - h2 *= temp; - err2 *= temp; - - if (x < 0.0) - asum = h1; - else - asum = h2; - - acanc = std::abs(err1) + std::abs(err2); - - if (b < 0) { - temp = xsf::cephes::Gamma(b); - asum *= temp; - acanc *= std::abs(temp); - } - - if (asum != 0.0) - acanc /= std::abs(asum); - - if (acanc != acanc) - /* nan */ - acanc = 1.0; - - if (std::isinf(asum)) - /* infinity */ - acanc = 0; - - acanc *= 30.0; /* fudge factor, since error of asymptotic formula - * often seems this much larger than advertised */ - adone: - *err = acanc; - return (asum); - } - - /* Power series summation for confluent hypergeometric function */ - XSF_HOST_DEVICE inline double hy1f1p(double a, double b, double x, double *err) { - double n, a0, sum, t, u, temp, maxn; - double an, bn, maxt; - double y, c, sumc; - - /* set up for power series summation */ - an = a; - bn = b; - a0 = 1.0; - sum = 1.0; - c = 0.0; - n = 1.0; - t = 1.0; - maxt = 0.0; - *err = 1.0; - - maxn = 200.0 + 2 * fabs(a) + 2 * fabs(b); - - while (t > MACHEP) { - if (bn == 0) { /* check bn first since if both */ - sf_error("hyperg", SF_ERROR_SINGULAR, NULL); - return (std::numeric_limits::infinity()); /* an and bn are zero it is */ - } - if (an == 0) /* a singularity */ - return (sum); - if (n > maxn) { - /* too many terms; take the last one as error estimate */ - c = std::abs(c) + std::abs(t) * 50.0; - goto pdone; - } - u = x * (an / (bn * n)); - - /* check for blowup */ - temp = std::abs(u); - if ((temp > 1.0) && (maxt > (std::numeric_limits::max() / temp))) { - *err = 1.0; /* blowup: estimate 100% error */ - return sum; - } - - a0 *= u; - - y = a0 - c; - sumc = sum + y; - c = (sumc - sum) - y; - sum = sumc; - - t = std::abs(a0); - - an += 1.0; - bn += 1.0; - n += 1.0; - } - - pdone: - - /* estimate error due to roundoff and cancellation */ - if (sum != 0.0) { - *err = std::abs(c / sum); - } else { - *err = std::abs(c); - } - - if (*err != *err) { - /* nan */ - *err = 1.0; - } - - return (sum); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double hyperg(double a, double b, double x) { - double asum, psum, acanc, pcanc, temp; - - /* See if a Kummer transformation will help */ - temp = b - a; - if (std::abs(temp) < 0.001 * std::abs(a)) - return (exp(x) * hyperg(temp, b, -x)); - - /* Try power & asymptotic series, starting from the one that is likely OK */ - if (std::abs(x) < 10 + std::abs(a) + std::abs(b)) { - psum = detail::hy1f1p(a, b, x, &pcanc); - if (pcanc < 1.0e-15) - goto done; - asum = detail::hy1f1a(a, b, x, &acanc); - } else { - psum = detail::hy1f1a(a, b, x, &pcanc); - if (pcanc < 1.0e-15) - goto done; - asum = detail::hy1f1p(a, b, x, &acanc); - } - - /* Pick the result with less estimated error */ - - if (acanc < pcanc) { - pcanc = acanc; - psum = asum; - } - - done: - if (pcanc > 1.0e-12) - set_error("hyperg", SF_ERROR_LOSS, NULL); - - return (psum); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/i0.h b/scipy/special/xsf/cephes/i0.h deleted file mode 100644 index f61e7b12fd22..000000000000 --- a/scipy/special/xsf/cephes/i0.h +++ /dev/null @@ -1,149 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* i0.c - * - * Modified Bessel function of order zero - * - * - * - * SYNOPSIS: - * - * double x, y, i0(); - * - * y = i0( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of order zero of the - * argument. - * - * The function is defined as i0(x) = j0( ix ). - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 5.8e-16 1.4e-16 - * - */ -/* i0e.c - * - * Modified Bessel function of order zero, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, i0e(); - * - * y = i0e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of order zero of the argument. - * - * The function is defined as i0e(x) = exp(-|x|) j0( ix ). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 5.4e-16 1.2e-16 - * See i0(). - * - */ - -/* i0.c */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "chbevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for exp(-x) I0(x) - * in the interval [0,8]. - * - * lim(x->0){ exp(-x) I0(x) } = 1. - */ - constexpr double i0_A[] = { - -4.41534164647933937950E-18, 3.33079451882223809783E-17, -2.43127984654795469359E-16, - 1.71539128555513303061E-15, -1.16853328779934516808E-14, 7.67618549860493561688E-14, - -4.85644678311192946090E-13, 2.95505266312963983461E-12, -1.72682629144155570723E-11, - 9.67580903537323691224E-11, -5.18979560163526290666E-10, 2.65982372468238665035E-9, - -1.30002500998624804212E-8, 6.04699502254191894932E-8, -2.67079385394061173391E-7, - 1.11738753912010371815E-6, -4.41673835845875056359E-6, 1.64484480707288970893E-5, - -5.75419501008210370398E-5, 1.88502885095841655729E-4, -5.76375574538582365885E-4, - 1.63947561694133579842E-3, -4.32430999505057594430E-3, 1.05464603945949983183E-2, - -2.37374148058994688156E-2, 4.93052842396707084878E-2, -9.49010970480476444210E-2, - 1.71620901522208775349E-1, -3.04682672343198398683E-1, 6.76795274409476084995E-1}; - - /* Chebyshev coefficients for exp(-x) sqrt(x) I0(x) - * in the inverted interval [8,infinity]. - * - * lim(x->inf){ exp(-x) sqrt(x) I0(x) } = 1/sqrt(2pi). - */ - constexpr double i0_B[] = { - -7.23318048787475395456E-18, -4.83050448594418207126E-18, 4.46562142029675999901E-17, - 3.46122286769746109310E-17, -2.82762398051658348494E-16, -3.42548561967721913462E-16, - 1.77256013305652638360E-15, 3.81168066935262242075E-15, -9.55484669882830764870E-15, - -4.15056934728722208663E-14, 1.54008621752140982691E-14, 3.85277838274214270114E-13, - 7.18012445138366623367E-13, -1.79417853150680611778E-12, -1.32158118404477131188E-11, - -3.14991652796324136454E-11, 1.18891471078464383424E-11, 4.94060238822496958910E-10, - 3.39623202570838634515E-9, 2.26666899049817806459E-8, 2.04891858946906374183E-7, - 2.89137052083475648297E-6, 6.88975834691682398426E-5, 3.36911647825569408990E-3, - 8.04490411014108831608E-1}; - } // namespace detail - - XSF_HOST_DEVICE inline double i0(double x) { - double y; - - if (x < 0) - x = -x; - if (x <= 8.0) { - y = (x / 2.0) - 2.0; - return (std::exp(x) * chbevl(y, detail::i0_A, 30)); - } - - return (std::exp(x) * chbevl(32.0 / x - 2.0, detail::i0_B, 25) / sqrt(x)); - } - - XSF_HOST_DEVICE inline double i0e(double x) { - double y; - - if (x < 0) - x = -x; - if (x <= 8.0) { - y = (x / 2.0) - 2.0; - return (chbevl(y, detail::i0_A, 30)); - } - - return (chbevl(32.0 / x - 2.0, detail::i0_B, 25) / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/i1.h b/scipy/special/xsf/cephes/i1.h deleted file mode 100644 index 49e2690391cf..000000000000 --- a/scipy/special/xsf/cephes/i1.h +++ /dev/null @@ -1,158 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* i1.c - * - * Modified Bessel function of order one - * - * - * - * SYNOPSIS: - * - * double x, y, i1(); - * - * y = i1( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of order one of the - * argument. - * - * The function is defined as i1(x) = -i j1( ix ). - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.9e-15 2.1e-16 - * - * - */ -/* i1e.c - * - * Modified Bessel function of order one, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, i1e(); - * - * y = i1e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of order one of the argument. - * - * The function is defined as i1(x) = -i exp(-|x|) j1( ix ). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 2.0e-15 2.0e-16 - * See i1(). - * - */ - -/* i1.c 2 */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1985, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "chbevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for exp(-x) I1(x) / x - * in the interval [0,8]. - * - * lim(x->0){ exp(-x) I1(x) / x } = 1/2. - */ - - constexpr double i1_A[] = { - 2.77791411276104639959E-18, -2.11142121435816608115E-17, 1.55363195773620046921E-16, - -1.10559694773538630805E-15, 7.60068429473540693410E-15, -5.04218550472791168711E-14, - 3.22379336594557470981E-13, -1.98397439776494371520E-12, 1.17361862988909016308E-11, - -6.66348972350202774223E-11, 3.62559028155211703701E-10, -1.88724975172282928790E-9, - 9.38153738649577178388E-9, -4.44505912879632808065E-8, 2.00329475355213526229E-7, - -8.56872026469545474066E-7, 3.47025130813767847674E-6, -1.32731636560394358279E-5, - 4.78156510755005422638E-5, -1.61760815825896745588E-4, 5.12285956168575772895E-4, - -1.51357245063125314899E-3, 4.15642294431288815669E-3, -1.05640848946261981558E-2, - 2.47264490306265168283E-2, -5.29459812080949914269E-2, 1.02643658689847095384E-1, - -1.76416518357834055153E-1, 2.52587186443633654823E-1}; - - /* Chebyshev coefficients for exp(-x) sqrt(x) I1(x) - * in the inverted interval [8,infinity]. - * - * lim(x->inf){ exp(-x) sqrt(x) I1(x) } = 1/sqrt(2pi). - */ - constexpr double i1_B[] = { - 7.51729631084210481353E-18, 4.41434832307170791151E-18, -4.65030536848935832153E-17, - -3.20952592199342395980E-17, 2.96262899764595013876E-16, 3.30820231092092828324E-16, - -1.88035477551078244854E-15, -3.81440307243700780478E-15, 1.04202769841288027642E-14, - 4.27244001671195135429E-14, -2.10154184277266431302E-14, -4.08355111109219731823E-13, - -7.19855177624590851209E-13, 2.03562854414708950722E-12, 1.41258074366137813316E-11, - 3.25260358301548823856E-11, -1.89749581235054123450E-11, -5.58974346219658380687E-10, - -3.83538038596423702205E-9, -2.63146884688951950684E-8, -2.51223623787020892529E-7, - -3.88256480887769039346E-6, -1.10588938762623716291E-4, -9.76109749136146840777E-3, - 7.78576235018280120474E-1}; - - } // namespace detail - - XSF_HOST_DEVICE inline double i1(double x) { - double y, z; - - z = std::abs(x); - if (z <= 8.0) { - y = (z / 2.0) - 2.0; - z = chbevl(y, detail::i1_A, 29) * z * std::exp(z); - } else { - z = std::exp(z) * chbevl(32.0 / z - 2.0, detail::i1_B, 25) / std::sqrt(z); - } - if (x < 0.0) - z = -z; - return (z); - } - - /* i1e() */ - - XSF_HOST_DEVICE inline double i1e(double x) { - double y, z; - - z = std::abs(x); - if (z <= 8.0) { - y = (z / 2.0) - 2.0; - z = chbevl(y, detail::i1_A, 29) * z; - } else { - z = chbevl(32.0 / z - 2.0, detail::i1_B, 25) / std::sqrt(z); - } - if (x < 0.0) - z = -z; - return (z); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igam.h b/scipy/special/xsf/cephes/igam.h deleted file mode 100644 index 356ebd13ce72..000000000000 --- a/scipy/special/xsf/cephes/igam.h +++ /dev/null @@ -1,429 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* igam.c - * - * Incomplete Gamma integral - * - * - * - * SYNOPSIS: - * - * double a, x, y, igam(); - * - * y = igam( a, x ); - * - * DESCRIPTION: - * - * The function is defined by - * - * x - * - - * 1 | | -t a-1 - * igam(a,x) = ----- | e t dt. - * - | | - * | (a) - - * 0 - * - * - * In this implementation both arguments must be positive. - * The integral is evaluated by either a power series or - * continued fraction expansion, depending on the relative - * values of a and x. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 200000 3.6e-14 2.9e-15 - * IEEE 0,100 300000 9.9e-14 1.5e-14 - */ -/* igamc() - * - * Complemented incomplete Gamma integral - * - * - * - * SYNOPSIS: - * - * double a, x, y, igamc(); - * - * y = igamc( a, x ); - * - * DESCRIPTION: - * - * The function is defined by - * - * - * igamc(a,x) = 1 - igam(a,x) - * - * inf. - * - - * 1 | | -t a-1 - * = ----- | e t dt. - * - | | - * | (a) - - * x - * - * - * In this implementation both arguments must be positive. - * The integral is evaluated by either a power series or - * continued fraction expansion, depending on the relative - * values of a and x. - * - * ACCURACY: - * - * Tested at random a, x. - * a x Relative error: - * arithmetic domain domain # trials peak rms - * IEEE 0.5,100 0,100 200000 1.9e-14 1.7e-15 - * IEEE 0.01,0.5 0,100 200000 1.4e-13 1.6e-15 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Sources - * [1] "The Digital Library of Mathematical Functions", dlmf.nist.gov - * [2] Maddock et. al., "Incomplete Gamma Functions", - * https://www.boost.org/doc/libs/1_61_0/libs/math/doc/html/math_toolkit/sf_gamma/igamma.html - */ - -/* Scipy changes: - * - 05-01-2016: added asymptotic expansion for igam to improve the - * a ~ x regime. - * - 06-19-2016: additional series expansion added for igamc to - * improve accuracy at small arguments. - * - 06-24-2016: better choice of domain for the asymptotic series; - * improvements in accuracy for the asymptotic series when a and x - * are very close. - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "igam_asymp_coeff.h" -#include "lanczos.h" -#include "ndtr.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int igam_MAXITER = 2000; - constexpr int IGAM = 1; - constexpr int IGAMC = 0; - constexpr double igam_SMALL = 20; - constexpr double igam_LARGE = 200; - constexpr double igam_SMALLRATIO = 0.3; - constexpr double igam_LARGERATIO = 4.5; - - constexpr double igam_big = 4.503599627370496e15; - constexpr double igam_biginv = 2.22044604925031308085e-16; - - /* Compute - * - * x^a * exp(-x) / gamma(a) - * - * corrected from (15) and (16) in [2] by replacing exp(x - a) with - * exp(a - x). - */ - XSF_HOST_DEVICE inline double igam_fac(double a, double x) { - double ax, fac, res, num; - - if (std::abs(a - x) > 0.4 * std::abs(a)) { - ax = a * std::log(x) - x - xsf::cephes::lgam(a); - if (ax < -MAXLOG) { - set_error("igam", SF_ERROR_UNDERFLOW, NULL); - return 0.0; - } - return std::exp(ax); - } - - fac = a + xsf::cephes::lanczos_g - 0.5; - res = std::sqrt(fac / std::exp(1)) / xsf::cephes::lanczos_sum_expg_scaled(a); - - if ((a < 200) && (x < 200)) { - res *= std::exp(a - x) * std::pow(x / fac, a); - } else { - num = x - a - xsf::cephes::lanczos_g + 0.5; - res *= std::exp(a * xsf::cephes::log1pmx(num / fac) + x * (0.5 - xsf::cephes::lanczos_g) / fac); - } - - return res; - } - - /* Compute igamc using DLMF 8.9.2. */ - XSF_HOST_DEVICE inline double igamc_continued_fraction(double a, double x) { - int i; - double ans, ax, c, yc, r, t, y, z; - double pk, pkm1, pkm2, qk, qkm1, qkm2; - - ax = igam_fac(a, x); - if (ax == 0.0) { - return 0.0; - } - - /* continued fraction */ - y = 1.0 - a; - z = x + y + 1.0; - c = 0.0; - pkm2 = 1.0; - qkm2 = x; - pkm1 = x + 1.0; - qkm1 = z * x; - ans = pkm1 / qkm1; - - for (i = 0; i < igam_MAXITER; i++) { - c += 1.0; - y += 1.0; - z += 2.0; - yc = y * c; - pk = pkm1 * z - pkm2 * yc; - qk = qkm1 * z - qkm2 * yc; - if (qk != 0) { - r = pk / qk; - t = std::abs((ans - r) / r); - ans = r; - } else - t = 1.0; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - if (std::abs(pk) > igam_big) { - pkm2 *= igam_biginv; - pkm1 *= igam_biginv; - qkm2 *= igam_biginv; - qkm1 *= igam_biginv; - } - if (t <= MACHEP) { - break; - } - } - - return (ans * ax); - } - - /* Compute igam using DLMF 8.11.4. */ - XSF_HOST_DEVICE inline double igam_series(double a, double x) { - int i; - double ans, ax, c, r; - - ax = igam_fac(a, x); - if (ax == 0.0) { - return 0.0; - } - - /* power series */ - r = a; - c = 1.0; - ans = 1.0; - - for (i = 0; i < igam_MAXITER; i++) { - r += 1.0; - c *= x / r; - ans += c; - if (c <= MACHEP * ans) { - break; - } - } - - return (ans * ax / a); - } - - /* Compute igamc using DLMF 8.7.3. This is related to the series in - * igam_series but extra care is taken to avoid cancellation. - */ - XSF_HOST_DEVICE inline double igamc_series(double a, double x) { - int n; - double fac = 1; - double sum = 0; - double term, logx; - - for (n = 1; n < igam_MAXITER; n++) { - fac *= -x / n; - term = fac / (a + n); - sum += term; - if (std::abs(term) <= MACHEP * std::abs(sum)) { - break; - } - } - - logx = std::log(x); - term = -xsf::cephes::expm1(a * logx - xsf::cephes::lgam1p(a)); - return term - std::exp(a * logx - xsf::cephes::lgam(a)) * sum; - } - - /* Compute igam/igamc using DLMF 8.12.3/8.12.4. */ - XSF_HOST_DEVICE inline double asymptotic_series(double a, double x, int func) { - int k, n, sgn; - int maxpow = 0; - double lambda = x / a; - double sigma = (x - a) / a; - double eta, res, ck, ckterm, term, absterm; - double absoldterm = std::numeric_limits::infinity(); - double etapow[detail::igam_asymp_coeff_N] = {1}; - double sum = 0; - double afac = 1; - - if (func == detail::IGAM) { - sgn = -1; - } else { - sgn = 1; - } - - if (lambda > 1) { - eta = std::sqrt(-2 * xsf::cephes::log1pmx(sigma)); - } else if (lambda < 1) { - eta = -std::sqrt(-2 * xsf::cephes::log1pmx(sigma)); - } else { - eta = 0; - } - res = 0.5 * xsf::cephes::erfc(sgn * eta * std::sqrt(a / 2)); - - for (k = 0; k < igam_asymp_coeff_K; k++) { - ck = igam_asymp_coeff_d[k][0]; - for (n = 1; n < igam_asymp_coeff_N; n++) { - if (n > maxpow) { - etapow[n] = eta * etapow[n - 1]; - maxpow += 1; - } - ckterm = igam_asymp_coeff_d[k][n] * etapow[n]; - ck += ckterm; - if (std::abs(ckterm) < MACHEP * std::abs(ck)) { - break; - } - } - term = ck * afac; - absterm = std::abs(term); - if (absterm > absoldterm) { - break; - } - sum += term; - if (absterm < MACHEP * std::abs(sum)) { - break; - } - absoldterm = absterm; - afac /= a; - } - res += sgn * std::exp(-0.5 * a * eta * eta) * sum / std::sqrt(2 * M_PI * a); - - return res; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double igamc(double a, double x); - - XSF_HOST_DEVICE inline double igam(double a, double x) { - double absxma_a; - - if (std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0 || a < 0) { - set_error("gammainc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else if (a == 0) { - if (x > 0) { - return 1; - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (x == 0) { - /* Zero integration limit */ - return 0; - } else if (std::isinf(a)) { - if (std::isinf(x)) { - return std::numeric_limits::quiet_NaN(); - } - return 0; - } else if (std::isinf(x)) { - return 1; - } - - /* Asymptotic regime where a ~ x; see [2]. */ - absxma_a = std::abs(x - a) / a; - if ((a > detail::igam_SMALL) && (a < detail::igam_LARGE) && (absxma_a < detail::igam_SMALLRATIO)) { - return detail::asymptotic_series(a, x, detail::IGAM); - } else if ((a > detail::igam_LARGE) && (absxma_a < detail::igam_LARGERATIO / std::sqrt(a))) { - return detail::asymptotic_series(a, x, detail::IGAM); - } - - if ((x > 1.0) && (x > a)) { - return (1.0 - igamc(a, x)); - } - - return detail::igam_series(a, x); - } - - XSF_HOST_DEVICE double igamc(double a, double x) { - double absxma_a; - - if (std::isnan(a) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0 || a < 0) { - set_error("gammaincc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else if (a == 0) { - if (x > 0) { - return 0; - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (x == 0) { - return 1; - } else if (std::isinf(a)) { - if (std::isinf(x)) { - return std::numeric_limits::quiet_NaN(); - } - return 1; - } else if (std::isinf(x)) { - return 0; - } - - /* Asymptotic regime where a ~ x; see [2]. */ - absxma_a = std::abs(x - a) / a; - if ((a > detail::igam_SMALL) && (a < detail::igam_LARGE) && (absxma_a < detail::igam_SMALLRATIO)) { - return detail::asymptotic_series(a, x, detail::IGAMC); - } else if ((a > detail::igam_LARGE) && (absxma_a < detail::igam_LARGERATIO / std::sqrt(a))) { - return detail::asymptotic_series(a, x, detail::IGAMC); - } - - /* Everywhere else; see [2]. */ - if (x > 1.1) { - if (x < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_continued_fraction(a, x); - } - } else if (x <= 0.5) { - if (-0.4 / std::log(x) < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_series(a, x); - } - } else { - if (x * 1.1 < a) { - return 1.0 - detail::igam_series(a, x); - } else { - return detail::igamc_series(a, x); - } - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igam_asymp_coeff.h b/scipy/special/xsf/cephes/igam_asymp_coeff.h deleted file mode 100644 index 98404c65ebca..000000000000 --- a/scipy/special/xsf/cephes/igam_asymp_coeff.h +++ /dev/null @@ -1,195 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ - -/* This file was automatically generated by _precomp/gammainc.py. - * Do not edit it manually! - */ -#pragma once - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int igam_asymp_coeff_K = 25; - constexpr int igam_asymp_coeff_N = 25; - - static const double igam_asymp_coeff_d[igam_asymp_coeff_K][igam_asymp_coeff_N] = { - {-3.3333333333333333e-1, 8.3333333333333333e-2, -1.4814814814814815e-2, 1.1574074074074074e-3, - 3.527336860670194e-4, -1.7875514403292181e-4, 3.9192631785224378e-5, -2.1854485106799922e-6, - -1.85406221071516e-6, 8.296711340953086e-7, -1.7665952736826079e-7, 6.7078535434014986e-9, - 1.0261809784240308e-8, -4.3820360184533532e-9, 9.1476995822367902e-10, -2.551419399494625e-11, - -5.8307721325504251e-11, 2.4361948020667416e-11, -5.0276692801141756e-12, 1.1004392031956135e-13, - 3.3717632624009854e-13, -1.3923887224181621e-13, 2.8534893807047443e-14, -5.1391118342425726e-16, - -1.9752288294349443e-15}, - {-1.8518518518518519e-3, -3.4722222222222222e-3, 2.6455026455026455e-3, -9.9022633744855967e-4, - 2.0576131687242798e-4, -4.0187757201646091e-7, -1.8098550334489978e-5, 7.6491609160811101e-6, - -1.6120900894563446e-6, 4.6471278028074343e-9, 1.378633446915721e-7, -5.752545603517705e-8, - 1.1951628599778147e-8, -1.7543241719747648e-11, -1.0091543710600413e-9, 4.1627929918425826e-10, - -8.5639070264929806e-11, 6.0672151016047586e-14, 7.1624989648114854e-12, -2.9331866437714371e-12, - 5.9966963656836887e-13, -2.1671786527323314e-16, -4.9783399723692616e-14, 2.0291628823713425e-14, - -4.13125571381061e-15}, - {4.1335978835978836e-3, -2.6813271604938272e-3, 7.7160493827160494e-4, 2.0093878600823045e-6, - -1.0736653226365161e-4, 5.2923448829120125e-5, -1.2760635188618728e-5, 3.4235787340961381e-8, - 1.3721957309062933e-6, -6.298992138380055e-7, 1.4280614206064242e-7, -2.0477098421990866e-10, - -1.4092529910867521e-8, 6.228974084922022e-9, -1.3670488396617113e-9, 9.4283561590146782e-13, - 1.2872252400089318e-10, -5.5645956134363321e-11, 1.1975935546366981e-11, -4.1689782251838635e-15, - -1.0940640427884594e-12, 4.6622399463901357e-13, -9.905105763906906e-14, 1.8931876768373515e-17, - 8.8592218725911273e-15}, - {6.4943415637860082e-4, 2.2947209362139918e-4, -4.6918949439525571e-4, 2.6772063206283885e-4, - -7.5618016718839764e-5, -2.3965051138672967e-7, 1.1082654115347302e-5, -5.6749528269915966e-6, - 1.4230900732435884e-6, -2.7861080291528142e-11, -1.6958404091930277e-7, 8.0994649053880824e-8, - -1.9111168485973654e-8, 2.3928620439808118e-12, 2.0620131815488798e-9, -9.4604966618551322e-10, - 2.1541049775774908e-10, -1.388823336813903e-14, -2.1894761681963939e-11, 9.7909989511716851e-12, - -2.1782191880180962e-12, 6.2088195734079014e-17, 2.126978363279737e-13, -9.3446887915174333e-14, - 2.0453671226782849e-14}, - {-8.618882909167117e-4, 7.8403922172006663e-4, -2.9907248030319018e-4, -1.4638452578843418e-6, - 6.6414982154651222e-5, -3.9683650471794347e-5, 1.1375726970678419e-5, 2.5074972262375328e-10, - -1.6954149536558306e-6, 8.9075075322053097e-7, -2.2929348340008049e-7, 2.956794137544049e-11, - 2.8865829742708784e-8, -1.4189739437803219e-8, 3.4463580499464897e-9, -2.3024517174528067e-13, - -3.9409233028046405e-10, 1.8602338968504502e-10, -4.356323005056618e-11, 1.2786001016296231e-15, - 4.6792750266579195e-12, -2.1492464706134829e-12, 4.9088156148096522e-13, -6.3385914848915603e-18, - -5.0453320690800944e-14}, - {-3.3679855336635815e-4, -6.9728137583658578e-5, 2.7727532449593921e-4, -1.9932570516188848e-4, - 6.7977804779372078e-5, 1.419062920643967e-7, -1.3594048189768693e-5, 8.0184702563342015e-6, - -2.2914811765080952e-6, -3.252473551298454e-10, 3.4652846491085265e-7, -1.8447187191171343e-7, - 4.8240967037894181e-8, -1.7989466721743515e-14, -6.3061945000135234e-9, 3.1624176287745679e-9, - -7.8409242536974293e-10, 5.1926791652540407e-15, 9.3589442423067836e-11, -4.5134262161632782e-11, - 1.0799129993116827e-11, -3.661886712685252e-17, -1.210902069055155e-12, 5.6807435849905643e-13, - -1.3249659916340829e-13}, - {5.3130793646399222e-4, -5.9216643735369388e-4, 2.7087820967180448e-4, 7.9023532326603279e-7, - -8.1539693675619688e-5, 5.6116827531062497e-5, -1.8329116582843376e-5, -3.0796134506033048e-9, - 3.4651553688036091e-6, -2.0291327396058604e-6, 5.7887928631490037e-7, 2.338630673826657e-13, - -8.8286007463304835e-8, 4.7435958880408128e-8, -1.2545415020710382e-8, 8.6496488580102925e-14, - 1.6846058979264063e-9, -8.5754928235775947e-10, 2.1598224929232125e-10, -7.6132305204761539e-16, - -2.6639822008536144e-11, 1.3065700536611057e-11, -3.1799163902367977e-12, 4.7109761213674315e-18, - 3.6902800842763467e-13}, - {3.4436760689237767e-4, 5.1717909082605922e-5, -3.3493161081142236e-4, 2.812695154763237e-4, - -1.0976582244684731e-4, -1.2741009095484485e-7, 2.7744451511563644e-5, -1.8263488805711333e-5, - 5.7876949497350524e-6, 4.9387589339362704e-10, -1.0595367014026043e-6, 6.1667143761104075e-7, - -1.7562973359060462e-7, -1.2974473287015439e-12, 2.695423606288966e-8, -1.4578352908731271e-8, - 3.887645959386175e-9, -3.8810022510194121e-17, -5.3279941738772867e-10, 2.7437977643314845e-10, - -6.9957960920705679e-11, 2.5899863874868481e-17, 8.8566890996696381e-12, -4.403168815871311e-12, - 1.0865561947091654e-12}, - {-6.5262391859530942e-4, 8.3949872067208728e-4, -4.3829709854172101e-4, -6.969091458420552e-7, - 1.6644846642067548e-4, -1.2783517679769219e-4, 4.6299532636913043e-5, 4.5579098679227077e-9, - -1.0595271125805195e-5, 6.7833429048651666e-6, -2.1075476666258804e-6, -1.7213731432817145e-11, - 3.7735877416110979e-7, -2.1867506700122867e-7, 6.2202288040189269e-8, 6.5977038267330006e-16, - -9.5903864974256858e-9, 5.2132144922808078e-9, -1.3991589583935709e-9, 5.382058999060575e-16, - 1.9484714275467745e-10, -1.0127287556389682e-10, 2.6077347197254926e-11, -5.0904186999932993e-18, - -3.3721464474854592e-12}, - {-5.9676129019274625e-4, -7.2048954160200106e-5, 6.7823088376673284e-4, -6.4014752602627585e-4, - 2.7750107634328704e-4, 1.8197008380465151e-7, -8.4795071170685032e-5, 6.105192082501531e-5, - -2.1073920183404862e-5, -8.8585890141255994e-10, 4.5284535953805377e-6, -2.8427815022504408e-6, - 8.7082341778646412e-7, 3.6886101871706965e-12, -1.5344695190702061e-7, 8.862466778790695e-8, - -2.5184812301826817e-8, -1.0225912098215092e-14, 3.8969470758154777e-9, -2.1267304792235635e-9, - 5.7370135528051385e-10, -1.887749850169741e-19, -8.0931538694657866e-11, 4.2382723283449199e-11, - -1.1002224534207726e-11}, - {1.3324454494800656e-3, -1.9144384985654775e-3, 1.1089369134596637e-3, 9.932404122642299e-7, - -5.0874501293093199e-4, 4.2735056665392884e-4, -1.6858853767910799e-4, -8.1301893922784998e-9, - 4.5284402370562147e-5, -3.127053674781734e-5, 1.044986828530338e-5, 4.8435226265680926e-11, - -2.1482565873456258e-6, 1.329369701097492e-6, -4.0295693092101029e-7, -1.7567877666323291e-13, - 7.0145043163668257e-8, -4.040787734999483e-8, 1.1474026743371963e-8, 3.9642746853563325e-18, - -1.7804938269892714e-9, 9.7480262548731646e-10, -2.6405338676507616e-10, 5.794875163403742e-18, - 3.7647749553543836e-11}, - {1.579727660730835e-3, 1.6251626278391582e-4, -2.0633421035543276e-3, 2.1389686185689098e-3, - -1.0108559391263003e-3, -3.9912705529919201e-7, 3.6235025084764691e-4, -2.8143901463712154e-4, - 1.0449513336495887e-4, 2.1211418491830297e-9, -2.5779417251947842e-5, 1.7281818956040463e-5, - -5.6413773872904282e-6, -1.1024320105776174e-11, 1.1223224418895175e-6, -6.8693396379526735e-7, - 2.0653236975414887e-7, 4.6714772409838506e-14, -3.5609886164949055e-8, 2.0470855345905963e-8, - -5.8091738633283358e-9, -1.332821287582869e-16, 9.0354604391335133e-10, -4.9598782517330834e-10, - 1.3481607129399749e-10}, - {-4.0725121195140166e-3, 6.4033628338080698e-3, -4.0410161081676618e-3, -2.183732802866233e-6, - 2.1740441801254639e-3, -1.9700440518418892e-3, 8.3595469747962458e-4, 1.9445447567109655e-8, - -2.5779387120421696e-4, 1.9009987368139304e-4, -6.7696499937438965e-5, -1.4440629666426572e-10, - 1.5712512518742269e-5, -1.0304008744776893e-5, 3.304517767401387e-6, 7.9829760242325709e-13, - -6.4097794149313004e-7, 3.8894624761300056e-7, -1.1618347644948869e-7, -2.816808630596451e-15, - 1.9878012911297093e-8, -1.1407719956357511e-8, 3.2355857064185555e-9, 4.1759468293455945e-20, - -5.0423112718105824e-10}, - {-5.9475779383993003e-3, -5.4016476789260452e-4, 8.7910413550767898e-3, -9.8576315587856125e-3, - 5.0134695031021538e-3, 1.2807521786221875e-6, -2.0626019342754683e-3, 1.7109128573523058e-3, - -6.7695312714133799e-4, -6.9011545676562133e-9, 1.8855128143995902e-4, -1.3395215663491969e-4, - 4.6263183033528039e-5, 4.0034230613321351e-11, -1.0255652921494033e-5, 6.612086372797651e-6, - -2.0913022027253008e-6, -2.0951775649603837e-13, 3.9756029041993247e-7, -2.3956211978815887e-7, - 7.1182883382145864e-8, 8.925574873053455e-16, -1.2101547235064676e-8, 6.9350618248334386e-9, - -1.9661464453856102e-9}, - {1.7402027787522711e-2, -2.9527880945699121e-2, 2.0045875571402799e-2, 7.0289515966903407e-6, - -1.2375421071343148e-2, 1.1976293444235254e-2, -5.4156038466518525e-3, -6.3290893396418616e-8, - 1.8855118129005065e-3, -1.473473274825001e-3, 5.5515810097708387e-4, 5.2406834412550662e-10, - -1.4357913535784836e-4, 9.9181293224943297e-5, -3.3460834749478311e-5, -3.5755837291098993e-12, - 7.1560851960630076e-6, -4.5516802628155526e-6, 1.4236576649271475e-6, 1.8803149082089664e-14, - -2.6623403898929211e-7, 1.5950642189595716e-7, -4.7187514673841102e-8, -6.5107872958755177e-17, - 7.9795091026746235e-9}, - {3.0249124160905891e-2, 2.4817436002649977e-3, -4.9939134373457022e-2, 5.9915643009307869e-2, - -3.2483207601623391e-2, -5.7212968652103441e-6, 1.5085251778569354e-2, -1.3261324005088445e-2, - 5.5515262632426148e-3, 3.0263182257030016e-8, -1.7229548406756723e-3, 1.2893570099929637e-3, - -4.6845138348319876e-4, -1.830259937893045e-10, 1.1449739014822654e-4, -7.7378565221244477e-5, - 2.5625836246985201e-5, 1.0766165333192814e-12, -5.3246809282422621e-6, 3.349634863064464e-6, - -1.0381253128684018e-6, -5.608909920621128e-15, 1.9150821930676591e-7, -1.1418365800203486e-7, - 3.3654425209171788e-8}, - {-9.9051020880159045e-2, 1.7954011706123486e-1, -1.2989606383463778e-1, -3.1478872752284357e-5, - 9.0510635276848131e-2, -9.2828824411184397e-2, 4.4412112839877808e-2, 2.7779236316835888e-7, - -1.7229543805449697e-2, 1.4182925050891573e-2, -5.6214161633747336e-3, -2.39598509186381e-9, - 1.6029634366079908e-3, -1.1606784674435773e-3, 4.1001337768153873e-4, 1.8365800754090661e-11, - -9.5844256563655903e-5, 6.3643062337764708e-5, -2.076250624489065e-5, -1.1806020912804483e-13, - 4.2131808239120649e-6, -2.6262241337012467e-6, 8.0770620494930662e-7, 6.0125912123632725e-16, - -1.4729737374018841e-7}, - {-1.9994542198219728e-1, -1.5056113040026424e-2, 3.6470239469348489e-1, -4.6435192311733545e-1, - 2.6640934719197893e-1, 3.4038266027147191e-5, -1.3784338709329624e-1, 1.276467178337056e-1, - -5.6213828755200985e-2, -1.753150885483011e-7, 1.9235592956768113e-2, -1.5088821281095315e-2, - 5.7401854451350123e-3, 1.0622382710310225e-9, -1.5335082692563998e-3, 1.0819320643228214e-3, - -3.7372510193945659e-4, -6.6170909729031985e-12, 8.4263617380909628e-5, -5.5150706827483479e-5, - 1.7769536448348069e-5, 3.8827923210205533e-14, -3.53513697488768e-6, 2.1865832130045269e-6, - -6.6812849447625594e-7}, - {7.2438608504029431e-1, -1.3918010932653375, 1.0654143352413968, 1.876173868950258e-4, - -8.2705501176152696e-1, 8.9352433347828414e-1, -4.4971003995291339e-1, -1.6107401567546652e-6, - 1.9235590165271091e-1, -1.6597702160042609e-1, 6.8882222681814333e-2, 1.3910091724608687e-8, - -2.146911561508663e-2, 1.6228980898865892e-2, -5.9796016172584256e-3, -1.1287469112826745e-10, - 1.5167451119784857e-3, -1.0478634293553899e-3, 3.5539072889126421e-4, 8.1704322111801517e-13, - -7.7773013442452395e-5, 5.0291413897007722e-5, -1.6035083867000518e-5, 1.2469354315487605e-14, - 3.1369106244517615e-6}, - {1.6668949727276811, 1.165462765994632e-1, -3.3288393225018906, 4.4692325482864037, - -2.6977693045875807, -2.600667859891061e-4, 1.5389017615694539, -1.4937962361134612, - 6.8881964633233148e-1, 1.3077482004552385e-6, -2.5762963325596288e-1, 2.1097676102125449e-1, - -8.3714408359219882e-2, -7.7920428881354753e-9, 2.4267923064833599e-2, -1.7813678334552311e-2, - 6.3970330388900056e-3, 4.9430807090480523e-11, -1.5554602758465635e-3, 1.0561196919903214e-3, - -3.5277184460472902e-4, 9.3002334645022459e-14, 7.5285855026557172e-5, -4.8186515569156351e-5, - 1.5227271505597605e-5}, - {-6.6188298861372935, 1.3397985455142589e+1, -1.0789350606845146e+1, -1.4352254537875018e-3, - 9.2333694596189809, -1.0456552819547769e+1, 5.5105526029033471, 1.2024439690716742e-5, - -2.5762961164755816, 2.3207442745387179, -1.0045728797216284, -1.0207833290021914e-7, - 3.3975092171169466e-1, -2.6720517450757468e-1, 1.0235252851562706e-1, 8.4329730484871625e-10, - -2.7998284958442595e-2, 2.0066274144976813e-2, -7.0554368915086242e-3, 1.9402238183698188e-12, - 1.6562888105449611e-3, -1.1082898580743683e-3, 3.654545161310169e-4, -5.1290032026971794e-11, - -7.6340103696869031e-5}, - {-1.7112706061976095e+1, -1.1208044642899116, 3.7131966511885444e+1, -5.2298271025348962e+1, - 3.3058589696624618e+1, 2.4791298976200222e-3, -2.061089403411526e+1, 2.088672775145582e+1, - -1.0045703956517752e+1, -1.2238783449063012e-5, 4.0770134274221141, -3.473667358470195, - 1.4329352617312006, 7.1359914411879712e-8, -4.4797257159115612e-1, 3.4112666080644461e-1, - -1.2699786326594923e-1, -2.8953677269081528e-10, 3.3125776278259863e-2, -2.3274087021036101e-2, - 8.0399993503648882e-3, -1.177805216235265e-9, -1.8321624891071668e-3, 1.2108282933588665e-3, - -3.9479941246822517e-4}, - {7.389033153567425e+1, -1.5680141270402273e+2, 1.322177542759164e+2, 1.3692876877324546e-2, - -1.2366496885920151e+2, 1.4620689391062729e+2, -8.0365587724865346e+1, -1.1259851148881298e-4, - 4.0770132196179938e+1, -3.8210340013273034e+1, 1.719522294277362e+1, 9.3519707955168356e-7, - -6.2716159907747034, 5.1168999071852637, -2.0319658112299095, -4.9507215582761543e-9, - 5.9626397294332597e-1, -4.4220765337238094e-1, 1.6079998700166273e-1, -2.4733786203223402e-8, - -4.0307574759979762e-2, 2.7849050747097869e-2, -9.4751858992054221e-3, 6.419922235909132e-6, - 2.1250180774699461e-3}, - {2.1216837098382522e+2, 1.3107863022633868e+1, -4.9698285932871748e+2, 7.3121595266969204e+2, - -4.8213821720890847e+2, -2.8817248692894889e-2, 3.2616720302947102e+2, -3.4389340280087117e+2, - 1.7195193870816232e+2, 1.4038077378096158e-4, -7.52594195897599e+1, 6.651969984520934e+1, - -2.8447519748152462e+1, -7.613702615875391e-7, 9.5402237105304373, -7.5175301113311376, - 2.8943997568871961, -4.6612194999538201e-7, -8.0615149598794088e-1, 5.8483006570631029e-1, - -2.0845408972964956e-1, 1.4765818959305817e-4, 5.1000433863753019e-2, -3.3066252141883665e-2, - 1.5109265210467774e-2}, - {-9.8959643098322368e+2, 2.1925555360905233e+3, -1.9283586782723356e+3, -1.5925738122215253e-1, - 1.9569985945919857e+3, -2.4072514765081556e+3, 1.3756149959336496e+3, 1.2920735237496668e-3, - -7.525941715948055e+2, 7.3171668742208716e+2, -3.4137023466220065e+2, -9.9857390260608043e-6, - 1.3356313181291573e+2, -1.1276295161252794e+2, 4.6310396098204458e+1, -7.9237387133614756e-6, - -1.4510726927018646e+1, 1.1111771248100563e+1, -4.1690817945270892, 3.1008219800117808e-3, - 1.1220095449981468, -7.6052379926149916e-1, 3.6262236505085254e-1, 2.216867741940747e-1, - 4.8683443692930507e-1}}; - - } // namespace detail -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/igami.h b/scipy/special/xsf/cephes/igami.h deleted file mode 100644 index ff82c35f682b..000000000000 --- a/scipy/special/xsf/cephes/igami.h +++ /dev/null @@ -1,313 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "igam.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - XSF_HOST_DEVICE double find_inverse_s(double p, double q) { - /* - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - * - * See equation 32. - */ - double s, t; - constexpr double a[4] = {0.213623493715853, 4.28342155967104, 11.6616720288968, 3.31125922108741}; - constexpr double b[5] = {0.3611708101884203e-1, 1.27364489782223, 6.40691597760039, 6.61053765625462, 1}; - - if (p < 0.5) { - t = std::sqrt(-2 * std::log(p)); - } else { - t = std::sqrt(-2 * std::log(q)); - } - s = t - polevl(t, a, 3) / polevl(t, b, 4); - if (p < 0.5) - s = -s; - return s; - } - - XSF_HOST_DEVICE inline double didonato_SN(double a, double x, unsigned N, double tolerance) { - /* - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - * - * See equation 34. - */ - double sum = 1.0; - - if (N >= 1) { - unsigned i; - double partial = x / (a + 1); - - sum += partial; - for (i = 2; i <= N; ++i) { - partial *= x / (a + i); - sum += partial; - if (partial < tolerance) { - break; - } - } - } - return sum; - } - - XSF_HOST_DEVICE inline double find_inverse_gamma(double a, double p, double q) { - /* - * In order to understand what's going on here, you will - * need to refer to: - * - * Computation of the Incomplete Gamma Function Ratios and their Inverse - * ARMIDO R. DIDONATO and ALFRED H. MORRIS, JR. - * ACM Transactions on Mathematical Software, Vol. 12, No. 4, - * December 1986, Pages 377-393. - */ - double result; - - if (a == 1) { - if (q > 0.9) { - result = -std::log1p(-p); - } else { - result = -std::log(q); - } - } else if (a < 1) { - double g = xsf::cephes::Gamma(a); - double b = q * g; - - if ((b > 0.6) || ((b >= 0.45) && (a >= 0.3))) { - /* DiDonato & Morris Eq 21: - * - * There is a slight variation from DiDonato and Morris here: - * the first form given here is unstable when p is close to 1, - * making it impossible to compute the inverse of Q(a,x) for small - * q. Fortunately the second form works perfectly well in this case. - */ - double u; - if ((b * q > 1e-8) && (q > 1e-5)) { - u = std::pow(p * g * a, 1 / a); - } else { - u = std::exp((-q / a) - SCIPY_EULER); - } - result = u / (1 - (u / (a + 1))); - } else if ((a < 0.3) && (b >= 0.35)) { - /* DiDonato & Morris Eq 22: */ - double t = std::exp(-SCIPY_EULER - b); - double u = t * std::exp(t); - result = t * std::exp(u); - } else if ((b > 0.15) || (a >= 0.3)) { - /* DiDonato & Morris Eq 23: */ - double y = -std::log(b); - double u = y - (1 - a) * std::log(y); - result = y - (1 - a) * std::log(u) - std::log(1 + (1 - a) / (1 + u)); - } else if (b > 0.1) { - /* DiDonato & Morris Eq 24: */ - double y = -std::log(b); - double u = y - (1 - a) * std::log(y); - result = y - (1 - a) * std::log(u) - - std::log((u * u + 2 * (3 - a) * u + (2 - a) * (3 - a)) / (u * u + (5 - a) * u + 2)); - } else { - /* DiDonato & Morris Eq 25: */ - double y = -std::log(b); - double c1 = (a - 1) * std::log(y); - double c1_2 = c1 * c1; - double c1_3 = c1_2 * c1; - double c1_4 = c1_2 * c1_2; - double a_2 = a * a; - double a_3 = a_2 * a; - - double c2 = (a - 1) * (1 + c1); - double c3 = (a - 1) * (-(c1_2 / 2) + (a - 2) * c1 + (3 * a - 5) / 2); - double c4 = (a - 1) * ((c1_3 / 3) - (3 * a - 5) * c1_2 / 2 + (a_2 - 6 * a + 7) * c1 + - (11 * a_2 - 46 * a + 47) / 6); - double c5 = (a - 1) * (-(c1_4 / 4) + (11 * a - 17) * c1_3 / 6 + (-3 * a_2 + 13 * a - 13) * c1_2 + - (2 * a_3 - 25 * a_2 + 72 * a - 61) * c1 / 2 + - (25 * a_3 - 195 * a_2 + 477 * a - 379) / 12); - - double y_2 = y * y; - double y_3 = y_2 * y; - double y_4 = y_2 * y_2; - result = y + c1 + (c2 / y) + (c3 / y_2) + (c4 / y_3) + (c5 / y_4); - } - } else { - /* DiDonato and Morris Eq 31: */ - double s = find_inverse_s(p, q); - - double s_2 = s * s; - double s_3 = s_2 * s; - double s_4 = s_2 * s_2; - double s_5 = s_4 * s; - double ra = std::sqrt(a); - - double w = a + s * ra + (s_2 - 1) / 3; - w += (s_3 - 7 * s) / (36 * ra); - w -= (3 * s_4 + 7 * s_2 - 16) / (810 * a); - w += (9 * s_5 + 256 * s_3 - 433 * s) / (38880 * a * ra); - - if ((a >= 500) && (std::abs(1 - w / a) < 1e-6)) { - result = w; - } else if (p > 0.5) { - if (w < 3 * a) { - result = w; - } else { - double D = std::fmax(2, a * (a - 1)); - double lg = xsf::cephes::lgam(a); - double lb = std::log(q) + lg; - if (lb < -D * 2.3) { - /* DiDonato and Morris Eq 25: */ - double y = -lb; - double c1 = (a - 1) * std::log(y); - double c1_2 = c1 * c1; - double c1_3 = c1_2 * c1; - double c1_4 = c1_2 * c1_2; - double a_2 = a * a; - double a_3 = a_2 * a; - - double c2 = (a - 1) * (1 + c1); - double c3 = (a - 1) * (-(c1_2 / 2) + (a - 2) * c1 + (3 * a - 5) / 2); - double c4 = (a - 1) * ((c1_3 / 3) - (3 * a - 5) * c1_2 / 2 + (a_2 - 6 * a + 7) * c1 + - (11 * a_2 - 46 * a + 47) / 6); - double c5 = - (a - 1) * (-(c1_4 / 4) + (11 * a - 17) * c1_3 / 6 + (-3 * a_2 + 13 * a - 13) * c1_2 + - (2 * a_3 - 25 * a_2 + 72 * a - 61) * c1 / 2 + - (25 * a_3 - 195 * a_2 + 477 * a - 379) / 12); - - double y_2 = y * y; - double y_3 = y_2 * y; - double y_4 = y_2 * y_2; - result = y + c1 + (c2 / y) + (c3 / y_2) + (c4 / y_3) + (c5 / y_4); - } else { - /* DiDonato and Morris Eq 33: */ - double u = -lb + (a - 1) * std::log(w) - std::log(1 + (1 - a) / (1 + w)); - result = -lb + (a - 1) * std::log(u) - std::log(1 + (1 - a) / (1 + u)); - } - } - } else { - double z = w; - double ap1 = a + 1; - double ap2 = a + 2; - if (w < 0.15 * ap1) { - /* DiDonato and Morris Eq 35: */ - double v = std::log(p) + xsf::cephes::lgam(ap1); - z = std::exp((v + w) / a); - s = std::log1p(z / ap1 * (1 + z / ap2)); - z = std::exp((v + z - s) / a); - s = std::log1p(z / ap1 * (1 + z / ap2)); - z = std::exp((v + z - s) / a); - s = std::log1p(z / ap1 * (1 + z / ap2 * (1 + z / (a + 3)))); - z = std::exp((v + z - s) / a); - } - - if ((z <= 0.01 * ap1) || (z > 0.7 * ap1)) { - result = z; - } else { - /* DiDonato and Morris Eq 36: */ - double ls = std::log(didonato_SN(a, z, 100, 1e-4)); - double v = std::log(p) + xsf::cephes::lgam(ap1); - z = std::exp((v + z - ls) / a); - result = z * (1 - (a * std::log(z) - z - v + ls) / (a - z)); - } - } - } - return result; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double igamci(double a, double q); - - XSF_HOST_DEVICE inline double igami(double a, double p) { - int i; - double x, fac, f_fp, fpp_fp; - - if (std::isnan(a) || std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - ; - } else if ((a < 0) || (p < 0) || (p > 1)) { - set_error("gammaincinv", SF_ERROR_DOMAIN, NULL); - } else if (p == 0.0) { - return 0.0; - } else if (p == 1.0) { - return std::numeric_limits::infinity(); - } else if (p > 0.9) { - return igamci(a, 1 - p); - } - - x = detail::find_inverse_gamma(a, p, 1 - p); - /* Halley's method */ - for (i = 0; i < 3; i++) { - fac = detail::igam_fac(a, x); - if (fac == 0.0) { - return x; - } - f_fp = (igam(a, x) - p) * x / fac; - /* The ratio of the first and second derivatives simplifies */ - fpp_fp = -1.0 + (a - 1) / x; - if (std::isinf(fpp_fp)) { - /* Resort to Newton's method in the case of overflow */ - x = x - f_fp; - } else { - x = x - f_fp / (1.0 - 0.5 * f_fp * fpp_fp); - } - } - - return x; - } - - XSF_HOST_DEVICE inline double igamci(double a, double q) { - int i; - double x, fac, f_fp, fpp_fp; - - if (std::isnan(a) || std::isnan(q)) { - return std::numeric_limits::quiet_NaN(); - } else if ((a < 0.0) || (q < 0.0) || (q > 1.0)) { - set_error("gammainccinv", SF_ERROR_DOMAIN, NULL); - } else if (q == 0.0) { - return std::numeric_limits::infinity(); - } else if (q == 1.0) { - return 0.0; - } else if (q > 0.9) { - return igami(a, 1 - q); - } - - x = detail::find_inverse_gamma(a, 1 - q, q); - for (i = 0; i < 3; i++) { - fac = detail::igam_fac(a, x); - if (fac == 0.0) { - return x; - } - f_fp = (igamc(a, x) - q) * x / (-fac); - fpp_fp = -1.0 + (a - 1) / x; - if (std::isinf(fpp_fp)) { - x = x - f_fp; - } else { - x = x - f_fp / (1.0 - 0.5 * f_fp * fpp_fp); - } - } - - return x; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/incbet.h b/scipy/special/xsf/cephes/incbet.h deleted file mode 100644 index aa56dfd58d5d..000000000000 --- a/scipy/special/xsf/cephes/incbet.h +++ /dev/null @@ -1,386 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* incbet.c - * - * Incomplete beta integral - * - * - * SYNOPSIS: - * - * double a, b, x, y, incbet(); - * - * y = incbet( a, b, x ); - * - * - * DESCRIPTION: - * - * Returns incomplete beta integral of the arguments, evaluated - * from zero to x. The function is defined as - * - * x - * - - - * | (a+b) | | a-1 b-1 - * ----------- | t (1-t) dt. - * - - | | - * | (a) | (b) - - * 0 - * - * The domain of definition is 0 <= x <= 1. In this - * implementation a and b are restricted to positive values. - * The integral from x to 1 may be obtained by the symmetry - * relation - * - * 1 - incbet( a, b, x ) = incbet( b, a, 1-x ). - * - * The integral is evaluated by a continued fraction expansion - * or, when b*x is small, by a power series. - * - * ACCURACY: - * - * Tested at uniformly distributed random points (a,b,x) with a and b - * in "domain" and x between 0 and 1. - * Relative error - * arithmetic domain # trials peak rms - * IEEE 0,5 10000 6.9e-15 4.5e-16 - * IEEE 0,85 250000 2.2e-13 1.7e-14 - * IEEE 0,1000 30000 5.3e-12 6.3e-13 - * IEEE 0,10000 250000 9.3e-11 7.1e-12 - * IEEE 0,100000 10000 8.7e-10 4.8e-11 - * Outputs smaller than the IEEE gradual underflow threshold - * were excluded from these statistics. - * - * ERROR MESSAGES: - * message condition value returned - * incbet domain x<0, x>1 0.0 - * incbet underflow 0.0 - */ - -/* - * Cephes Math Library, Release 2.3: March, 1995 - * Copyright 1984, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "beta.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Compute (u * v) * w, disabling optimizations for gcc on 32 bit systems. - * Used below in incbet_pseries to prevent aggressive optimizations from - * degrading accuracy. - */ -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC push_options -#pragma GCC optimize("00") -#endif - XSF_HOST_DEVICE inline double triple_product(double u, double v, double w) { - return (u * v) * w; - } -#if defined(__GNUC__) && defined(__i386__) -#pragma GCC pop_options -#endif - - constexpr double incbet_big = 4.503599627370496e15; - constexpr double incbet_biginv = 2.22044604925031308085e-16; - - /* Power series for incomplete beta integral. - * Use when b*x is small and x not too close to 1. */ - - XSF_HOST_DEVICE inline double incbet_pseries(double a, double b, double x) { - double s, t, u, v, n, t1, z, ai; - - ai = 1.0 / a; - u = (1.0 - b) * x; - v = u / (a + 1.0); - t1 = v; - t = u; - n = 2.0; - s = 0.0; - z = MACHEP * ai; - while (std::abs(v) > z) { - u = (n - b) * x / n; - t *= u; - v = t / (a + n); - s += v; - n += 1.0; - } - s += t1; - s += ai; - - u = a * std::log(x); - if ((a + b) < MAXGAM && std::abs(u) < MAXLOG) { - t = 1.0 / beta(a, b); - s = triple_product(s, t, std::pow(x, a)); // (s * t) * std::pow(x, a) - } else { - t = -lbeta(a, b) + u + std::log(s); - if (t < MINLOG) { - s = 0.0; - } else { - s = std::exp(t); - } - } - return (s); - } - - /* Continued fraction expansion #1 for incomplete beta integral */ - XSF_HOST_DEVICE inline double incbcf(double a, double b, double x) { - double xk, pk, pkm1, pkm2, qk, qkm1, qkm2; - double k1, k2, k3, k4, k5, k6, k7, k8; - double r, t, ans, thresh; - int n; - - k1 = a; - k2 = a + b; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = b - 1.0; - k7 = k4; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - ans = 1.0; - r = 1.0; - n = 0; - thresh = 3.0 * MACHEP; - do { - - xk = -(x * k1 * k2) / (k3 * k4); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = (x * k5 * k6) / (k7 * k8); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if (qk != 0) { - r = pk / qk; - } - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - if (t < thresh) { - goto cdone; - } - - k1 += 1.0; - k2 += 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 -= 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((std::abs(qk) + std::abs(pk)) > incbet_big) { - pkm2 *= incbet_biginv; - pkm1 *= incbet_biginv; - qkm2 *= incbet_biginv; - qkm1 *= incbet_biginv; - } - if ((std::abs(qk) < incbet_biginv) || (fabs(pk) < incbet_biginv)) { - pkm2 *= incbet_big; - pkm1 *= incbet_big; - qkm2 *= incbet_big; - qkm1 *= incbet_big; - } - } while (++n < 300); - - cdone: - return (ans); - } - - /* Continued fraction expansion #2 for incomplete beta integral */ - XSF_HOST_DEVICE inline double incbd(double a, double b, double x) { - double xk, pk, pkm1, pkm2, qk, qkm1, qkm2; - double k1, k2, k3, k4, k5, k6, k7, k8; - double r, t, ans, z, thresh; - int n; - - k1 = a; - k2 = b - 1.0; - k3 = a; - k4 = a + 1.0; - k5 = 1.0; - k6 = a + b; - k7 = a + 1.0; - ; - k8 = a + 2.0; - - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = 1.0; - qkm1 = 1.0; - z = x / (1.0 - x); - ans = 1.0; - r = 1.0; - n = 0; - thresh = 3.0 * MACHEP; - do { - - xk = -(z * k1 * k2) / (k3 * k4); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - xk = (z * k5 * k6) / (k7 * k8); - pk = pkm1 + pkm2 * xk; - qk = qkm1 + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - if (qk != 0) - r = pk / qk; - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - if (t < thresh) { - goto cdone; - } - - k1 += 1.0; - k2 -= 1.0; - k3 += 2.0; - k4 += 2.0; - k5 += 1.0; - k6 += 1.0; - k7 += 2.0; - k8 += 2.0; - - if ((std::abs(qk) + std::abs(pk)) > incbet_big) { - pkm2 *= incbet_biginv; - pkm1 *= incbet_biginv; - qkm2 *= incbet_biginv; - qkm1 *= incbet_biginv; - } - if ((std::abs(qk) < incbet_biginv) || (std::abs(pk) < incbet_biginv)) { - pkm2 *= incbet_big; - pkm1 *= incbet_big; - qkm2 *= incbet_big; - qkm1 *= incbet_big; - } - } while (++n < 300); - cdone: - return (ans); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double incbet(double aa, double bb, double xx) { - double a, b, t, x, xc, w, y; - int flag; - - if (aa <= 0.0 || bb <= 0.0) - goto domerr; - - if ((xx <= 0.0) || (xx >= 1.0)) { - if (xx == 0.0) - return (0.0); - if (xx == 1.0) - return (1.0); - domerr: - set_error("incbet", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - flag = 0; - if ((bb * xx) <= 1.0 && xx <= 0.95) { - t = detail::incbet_pseries(aa, bb, xx); - goto done; - } - - w = 1.0 - xx; - - /* Reverse a and b if x is greater than the mean. */ - if (xx > (aa / (aa + bb))) { - flag = 1; - a = bb; - b = aa; - xc = xx; - x = w; - } else { - a = aa; - b = bb; - xc = w; - x = xx; - } - - if (flag == 1 && (b * x) <= 1.0 && x <= 0.95) { - t = detail::incbet_pseries(a, b, x); - goto done; - } - - /* Choose expansion for better convergence. */ - y = x * (a + b - 2.0) - (a - 1.0); - if (y < 0.0) { - w = detail::incbcf(a, b, x); - } else { - w = detail::incbd(a, b, x) / xc; - } - - /* Multiply w by the factor - * a b _ _ _ - * x (1-x) | (a+b) / ( a | (a) | (b) ) . */ - - y = a * std::log(x); - t = b * std::log(xc); - if ((a + b) < detail::MAXGAM && std::abs(y) < detail::MAXLOG && std::abs(t) < detail::MAXLOG) { - t = std::pow(xc, b); - t *= std::pow(x, a); - t /= a; - t *= w; - t *= 1.0 / beta(a, b); - goto done; - } - /* Resort to logarithms. */ - y += t - lbeta(a, b); - y += std::log(w / a); - if (y < detail::MINLOG) { - t = 0.0; - } else { - t = exp(y); - } - done: - if (flag == 1) { - if (t <= detail::MACHEP) { - t = 1.0 - detail::MACHEP; - } else { - t = 1.0 - t; - } - } - return (t); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/incbi.h b/scipy/special/xsf/cephes/incbi.h deleted file mode 100644 index 3fd747c16f3e..000000000000 --- a/scipy/special/xsf/cephes/incbi.h +++ /dev/null @@ -1,293 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* incbi() - * - * Inverse of incomplete beta integral - * - * - * - * SYNOPSIS: - * - * double a, b, x, y, incbi(); - * - * x = incbi( a, b, y ); - * - * - * - * DESCRIPTION: - * - * Given y, the function finds x such that - * - * incbet( a, b, x ) = y . - * - * The routine performs interval halving or Newton iterations to find the - * root of incbet(a,b,x) - y = 0. - * - * - * ACCURACY: - * - * Relative error: - * x a,b - * arithmetic domain domain # trials peak rms - * IEEE 0,1 .5,10000 50000 5.8e-12 1.3e-13 - * IEEE 0,1 .25,100 100000 1.8e-13 3.9e-15 - * IEEE 0,1 0,5 50000 1.1e-12 5.5e-15 - * VAX 0,1 .5,100 25000 3.5e-14 1.1e-15 - * With a and b constrained to half-integer or integer values: - * IEEE 0,1 .5,10000 50000 5.8e-12 1.1e-13 - * IEEE 0,1 .5,100 100000 1.7e-14 7.9e-16 - * With a = .5, b constrained to half-integer or integer values: - * IEEE 0,1 .5,10000 10000 8.3e-11 1.0e-11 - */ - -/* - * Cephes Math Library Release 2.4: March,1996 - * Copyright 1984, 1996 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "gamma.h" -#include "incbet.h" -#include "ndtri.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double incbi(double aa, double bb, double yy0) { - double a, b, y0, d, y, x, x0, x1, lgm, yp, di, dithresh, yl, yh, xt; - int i, rflg, dir, nflg; - - i = 0; - if (yy0 <= 0) { - return (0.0); - } - if (yy0 >= 1.0) { - return (1.0); - } - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - nflg = 0; - - if (aa <= 1.0 || bb <= 1.0) { - dithresh = 1.0e-6; - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - x = a / (a + b); - y = incbet(a, b, x); - goto ihalve; - } else { - dithresh = 1.0e-4; - } - /* approximation to inverse function */ - - yp = -ndtri(yy0); - - if (yy0 > 0.5) { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - yp = -yp; - } else { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } - - lgm = (yp * yp - 3.0) / 6.0; - x = 2.0 / (1.0 / (2.0 * a - 1.0) + 1.0 / (2.0 * b - 1.0)); - d = yp * std::sqrt(x + lgm) / x - - (1.0 / (2.0 * b - 1.0) - 1.0 / (2.0 * a - 1.0)) * (lgm + 5.0 / 6.0 - 2.0 / (3.0 * x)); - d = 2.0 * d; - if (d < detail::MINLOG) { - x = 1.0; - goto under; - } - x = a / (a + b * std::exp(d)); - y = incbet(a, b, x); - yp = (y - y0) / y0; - if (std::abs(yp) < 0.2) { - goto newt; - } - - /* Resort to interval halving if not close enough. */ - ihalve: - - dir = 0; - di = 0.5; - for (i = 0; i < 100; i++) { - if (i != 0) { - x = x0 + di * (x1 - x0); - if (x == 1.0) { - x = 1.0 - detail::MACHEP; - } - if (x == 0.0) { - di = 0.5; - x = x0 + di * (x1 - x0); - if (x == 0.0) { - goto under; - } - } - y = incbet(a, b, x); - yp = (x1 - x0) / (x1 + x0); - if (std::abs(yp) < dithresh) { - goto newt; - } - yp = (y - y0) / y0; - if (std::abs(yp) < dithresh) { - goto newt; - } - } - if (y < y0) { - x0 = x; - yl = y; - if (dir < 0) { - dir = 0; - di = 0.5; - } else if (dir > 3) { - di = 1.0 - (1.0 - di) * (1.0 - di); - } else if (dir > 1) { - di = 0.5 * di + 0.5; - } else { - di = (y0 - y) / (yh - yl); - } - dir += 1; - if (x0 > 0.75) { - if (rflg == 1) { - rflg = 0; - a = aa; - b = bb; - y0 = yy0; - } else { - rflg = 1; - a = bb; - b = aa; - y0 = 1.0 - yy0; - } - x = 1.0 - x; - y = incbet(a, b, x); - x0 = 0.0; - yl = 0.0; - x1 = 1.0; - yh = 1.0; - goto ihalve; - } - } else { - x1 = x; - if (rflg == 1 && x1 < detail::MACHEP) { - x = 0.0; - goto done; - } - yh = y; - if (dir > 0) { - dir = 0; - di = 0.5; - } else if (dir < -3) { - di = di * di; - } else if (dir < -1) { - di = 0.5 * di; - } else { - di = (y - y0) / (yh - yl); - } - dir -= 1; - } - } - set_error("incbi", SF_ERROR_LOSS, NULL); - if (x0 >= 1.0) { - x = 1.0 - detail::MACHEP; - goto done; - } - if (x <= 0.0) { - under: - set_error("incbi", SF_ERROR_UNDERFLOW, NULL); - x = 0.0; - goto done; - } - - newt: - - if (nflg) { - goto done; - } - nflg = 1; - lgm = lgam(a + b) - lgam(a) - lgam(b); - - for (i = 0; i < 8; i++) { - /* Compute the function at this point. */ - if (i != 0) - y = incbet(a, b, x); - if (y < yl) { - x = x0; - y = yl; - } else if (y > yh) { - x = x1; - y = yh; - } else if (y < y0) { - x0 = x; - yl = y; - } else { - x1 = x; - yh = y; - } - if (x == 1.0 || x == 0.0) { - break; - } - /* Compute the derivative of the function at this point. */ - d = (a - 1.0) * std::log(x) + (b - 1.0) * std::log(1.0 - x) + lgm; - if (d < detail::MINLOG) { - goto done; - } - if (d > detail::MAXLOG) { - break; - } - d = std::exp(d); - /* Compute the step to the next approximation of x. */ - d = (y - y0) / d; - xt = x - d; - if (xt <= x0) { - y = (x - x0) / (x1 - x0); - xt = x0 + 0.5 * y * (x - x0); - if (xt <= 0.0) { - break; - } - } - if (xt >= x1) { - y = (x1 - x) / (x1 - x0); - xt = x1 - 0.5 * y * (x1 - x); - if (xt >= 1.0) - break; - } - x = xt; - if (std::abs(d / x) < 128.0 * detail::MACHEP) { - goto done; - } - } - /* Did not converge. */ - dithresh = 256.0 * detail::MACHEP; - goto ihalve; - - done: - - if (rflg) { - if (x <= detail::MACHEP) { - x = 1.0 - detail::MACHEP; - } else { - x = 1.0 - x; - } - } - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/j0.h b/scipy/special/xsf/cephes/j0.h deleted file mode 100644 index 29236ef966e0..000000000000 --- a/scipy/special/xsf/cephes/j0.h +++ /dev/null @@ -1,225 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* j0.c - * - * Bessel function of order zero - * - * - * - * SYNOPSIS: - * - * double x, y, j0(); - * - * y = j0( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order zero of the argument. - * - * The domain is divided into the intervals [0, 5] and - * (5, infinity). In the first interval the following rational - * approximation is used: - * - * - * 2 2 - * (w - r ) (w - r ) P (w) / Q (w) - * 1 2 3 8 - * - * 2 - * where w = x and the two r's are zeros of the function. - * - * In the second interval, the Hankel asymptotic expansion - * is employed with two rational functions of degree 6/6 - * and 7/7. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 60000 4.2e-16 1.1e-16 - * - */ -/* y0.c - * - * Bessel function of the second kind, order zero - * - * - * - * SYNOPSIS: - * - * double x, y, y0(); - * - * y = y0( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of the second kind, of order - * zero, of the argument. - * - * The domain is divided into the intervals [0, 5] and - * (5, infinity). In the first interval a rational approximation - * R(x) is employed to compute - * y0(x) = R(x) + 2 * log(x) * j0(x) / M_PI. - * Thus a call to j0() is required. - * - * In the second interval, the Hankel asymptotic expansion - * is employed with two rational functions of degree 6/6 - * and 7/7. - * - * - * - * ACCURACY: - * - * Absolute error, when y0(x) < 1; else relative error: - * - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.3e-15 1.6e-16 - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ - -/* Note: all coefficients satisfy the relative error criterion - * except YP, YQ which are designed for absolute error. */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double j0_PP[7] = { - 7.96936729297347051624E-4, 8.28352392107440799803E-2, 1.23953371646414299388E0, 5.44725003058768775090E0, - 8.74716500199817011941E0, 5.30324038235394892183E0, 9.99999999999999997821E-1, - }; - - constexpr double j0_PQ[7] = { - 9.24408810558863637013E-4, 8.56288474354474431428E-2, 1.25352743901058953537E0, 5.47097740330417105182E0, - 8.76190883237069594232E0, 5.30605288235394617618E0, 1.00000000000000000218E0, - }; - - constexpr double j0_QP[8] = { - -1.13663838898469149931E-2, -1.28252718670509318512E0, -1.95539544257735972385E1, -9.32060152123768231369E1, - -1.77681167980488050595E2, -1.47077505154951170175E2, -5.14105326766599330220E1, -6.05014350600728481186E0, - }; - - constexpr double j0_QQ[7] = { - /* 1.00000000000000000000E0, */ - 6.43178256118178023184E1, 8.56430025976980587198E2, 3.88240183605401609683E3, 7.24046774195652478189E3, - 5.93072701187316984827E3, 2.06209331660327847417E3, 2.42005740240291393179E2, - }; - - constexpr double j0_YP[8] = { - 1.55924367855235737965E4, -1.46639295903971606143E7, 5.43526477051876500413E9, - -9.82136065717911466409E11, 8.75906394395366999549E13, -3.46628303384729719441E15, - 4.42733268572569800351E16, -1.84950800436986690637E16, - }; - - constexpr double j0_YQ[7] = { - /* 1.00000000000000000000E0, */ - 1.04128353664259848412E3, 6.26107330137134956842E5, 2.68919633393814121987E8, 8.64002487103935000337E10, - 2.02979612750105546709E13, 3.17157752842975028269E15, 2.50596256172653059228E17, - }; - - /* 5.783185962946784521175995758455807035071 */ - constexpr double j0_DR1 = 5.78318596294678452118E0; - - /* 30.47126234366208639907816317502275584842 */ - constexpr double j0_DR2 = 3.04712623436620863991E1; - - constexpr double j0_RP[4] = { - -4.79443220978201773821E9, - 1.95617491946556577543E12, - -2.49248344360967716204E14, - 9.70862251047306323952E15, - }; - - constexpr double j0_RQ[8] = { - /* 1.00000000000000000000E0, */ - 4.99563147152651017219E2, 1.73785401676374683123E5, 4.84409658339962045305E7, 1.11855537045356834862E10, - 2.11277520115489217587E12, 3.10518229857422583814E14, 3.18121955943204943306E16, 1.71086294081043136091E18, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double j0(double x) { - double w, z, p, q, xn; - - if (x < 0) { - x = -x; - } - - if (x <= 5.0) { - z = x * x; - if (x < 1.0e-5) { - return (1.0 - z / 4.0); - } - - p = (z - detail::j0_DR1) * (z - detail::j0_DR2); - p = p * polevl(z, detail::j0_RP, 3) / p1evl(z, detail::j0_RQ, 8); - return (p); - } - - w = 5.0 / x; - q = 25.0 / (x * x); - p = polevl(q, detail::j0_PP, 6) / polevl(q, detail::j0_PQ, 6); - q = polevl(q, detail::j0_QP, 7) / p1evl(q, detail::j0_QQ, 7); - xn = x - M_PI_4; - p = p * std::cos(xn) - w * q * std::sin(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - - /* y0() 2 */ - /* Bessel function of second kind, order zero */ - - /* Rational approximation coefficients YP[], YQ[] are used here. - * The function computed is y0(x) - 2 * log(x) * j0(x) / M_PI, - * whose value at x = 0 is 2 * ( log(0.5) + EUL ) / M_PI - * = 0.073804295108687225. - */ - - XSF_HOST_DEVICE inline double y0(double x) { - double w, z, p, q, xn; - - if (x <= 5.0) { - if (x == 0.0) { - set_error("y0", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("y0", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = x * x; - w = polevl(z, detail::j0_YP, 7) / p1evl(z, detail::j0_YQ, 7); - w += M_2_PI * std::log(x) * j0(x); - return (w); - } - - w = 5.0 / x; - z = 25.0 / (x * x); - p = polevl(z, detail::j0_PP, 6) / polevl(z, detail::j0_PQ, 6); - q = polevl(z, detail::j0_QP, 7) / p1evl(z, detail::j0_QQ, 7); - xn = x - M_PI_4; - p = p * std::sin(xn) + w * q * std::cos(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/j1.h b/scipy/special/xsf/cephes/j1.h deleted file mode 100644 index 46532249550d..000000000000 --- a/scipy/special/xsf/cephes/j1.h +++ /dev/null @@ -1,198 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* j1.c - * - * Bessel function of order one - * - * - * - * SYNOPSIS: - * - * double x, y, j1(); - * - * y = j1( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order one of the argument. - * - * The domain is divided into the intervals [0, 8] and - * (8, infinity). In the first interval a 24 term Chebyshev - * expansion is used. In the second, the asymptotic - * trigonometric representation is employed using two - * rational functions of degree 5/5. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 2.6e-16 1.1e-16 - * - * - */ -/* y1.c - * - * Bessel function of second kind of order one - * - * - * - * SYNOPSIS: - * - * double x, y, y1(); - * - * y = y1( x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of the second kind of order one - * of the argument. - * - * The domain is divided into the intervals [0, 8] and - * (8, infinity). In the first interval a 25 term Chebyshev - * expansion is used, and a call to j1() is required. - * In the second, the asymptotic trigonometric representation - * is employed using two rational functions of degree 5/5. - * - * - * - * ACCURACY: - * - * Absolute error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.0e-15 1.3e-16 - * - * (error criterion relative when |y1| > 1). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 2000 by Stephen L. Moshier - */ - -/* - * #define PIO4 .78539816339744830962 - * #define THPIO4 2.35619449019234492885 - * #define SQ2OPI .79788456080286535588 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double j1_RP[4] = { - -8.99971225705559398224E8, - 4.52228297998194034323E11, - -7.27494245221818276015E13, - 3.68295732863852883286E15, - }; - - constexpr double j1_RQ[8] = { - /* 1.00000000000000000000E0, */ - 6.20836478118054335476E2, 2.56987256757748830383E5, 8.35146791431949253037E7, 2.21511595479792499675E10, - 4.74914122079991414898E12, 7.84369607876235854894E14, 8.95222336184627338078E16, 5.32278620332680085395E18, - }; - - constexpr double j1_PP[7] = { - 7.62125616208173112003E-4, 7.31397056940917570436E-2, 1.12719608129684925192E0, 5.11207951146807644818E0, - 8.42404590141772420927E0, 5.21451598682361504063E0, 1.00000000000000000254E0, - }; - - constexpr double j1_PQ[7] = { - 5.71323128072548699714E-4, 6.88455908754495404082E-2, 1.10514232634061696926E0, 5.07386386128601488557E0, - 8.39985554327604159757E0, 5.20982848682361821619E0, 9.99999999999999997461E-1, - }; - - constexpr double j1_QP[8] = { - 5.10862594750176621635E-2, 4.98213872951233449420E0, 7.58238284132545283818E1, 3.66779609360150777800E2, - 7.10856304998926107277E2, 5.97489612400613639965E2, 2.11688757100572135698E2, 2.52070205858023719784E1, - }; - - constexpr double j1_QQ[7] = { - /* 1.00000000000000000000E0, */ - 7.42373277035675149943E1, 1.05644886038262816351E3, 4.98641058337653607651E3, 9.56231892404756170795E3, - 7.99704160447350683650E3, 2.82619278517639096600E3, 3.36093607810698293419E2, - }; - - constexpr double j1_YP[6] = { - 1.26320474790178026440E9, -6.47355876379160291031E11, 1.14509511541823727583E14, - -8.12770255501325109621E15, 2.02439475713594898196E17, -7.78877196265950026825E17, - }; - - constexpr double j1_YQ[8] = { - /* 1.00000000000000000000E0, */ - 5.94301592346128195359E2, 2.35564092943068577943E5, 7.34811944459721705660E7, 1.87601316108706159478E10, - 3.88231277496238566008E12, 6.20557727146953693363E14, 6.87141087355300489866E16, 3.97270608116560655612E18, - }; - - constexpr double j1_Z1 = 1.46819706421238932572E1; - constexpr double j1_Z2 = 4.92184563216946036703E1; - - } // namespace detail - - XSF_HOST_DEVICE inline double j1(double x) { - double w, z, p, q, xn; - - w = x; - if (x < 0) { - return -j1(-x); - } - - if (w <= 5.0) { - z = x * x; - w = polevl(z, detail::j1_RP, 3) / p1evl(z, detail::j1_RQ, 8); - w = w * x * (z - detail::j1_Z1) * (z - detail::j1_Z2); - return (w); - } - - w = 5.0 / x; - z = w * w; - p = polevl(z, detail::j1_PP, 6) / polevl(z, detail::j1_PQ, 6); - q = polevl(z, detail::j1_QP, 7) / p1evl(z, detail::j1_QQ, 7); - xn = x - detail::THPIO4; - p = p * std::cos(xn) - w * q * std::sin(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } - - XSF_HOST_DEVICE inline double y1(double x) { - double w, z, p, q, xn; - - if (x <= 5.0) { - if (x == 0.0) { - set_error("y1", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } else if (x <= 0.0) { - set_error("y1", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = x * x; - w = x * (polevl(z, detail::j1_YP, 5) / p1evl(z, detail::j1_YQ, 8)); - w += M_2_PI * (j1(x) * std::log(x) - 1.0 / x); - return (w); - } - - w = 5.0 / x; - z = w * w; - p = polevl(z, detail::j1_PP, 6) / polevl(z, detail::j1_PQ, 6); - q = polevl(z, detail::j1_QP, 7) / p1evl(z, detail::j1_QQ, 7); - xn = x - detail::THPIO4; - p = p * std::sin(xn) + w * q * std::cos(xn); - return (p * detail::SQRT2OPI / std::sqrt(x)); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/jv.h b/scipy/special/xsf/cephes/jv.h deleted file mode 100644 index db5272f27fb4..000000000000 --- a/scipy/special/xsf/cephes/jv.h +++ /dev/null @@ -1,715 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* jv.c - * - * Bessel function of noninteger order - * - * - * - * SYNOPSIS: - * - * double v, x, y, jv(); - * - * y = jv( v, x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order v of the argument, - * where v is real. Negative x is allowed if v is an integer. - * - * Several expansions are included: the ascending power - * series, the Hankel expansion, and two transitional - * expansions for large v. If v is not too large, it - * is reduced by recurrence to a region of best accuracy. - * The transitional expansions give 12D accuracy for v > 500. - * - * - * - * ACCURACY: - * Results for integer v are indicated by *, where x and v - * both vary from -125 to +125. Otherwise, - * x ranges from 0 to 125, v ranges as indicated by "domain." - * Error criterion is absolute, except relative when |jv()| > 1. - * - * arithmetic v domain x domain # trials peak rms - * IEEE 0,125 0,125 100000 4.6e-15 2.2e-16 - * IEEE -125,0 0,125 40000 5.4e-11 3.7e-13 - * IEEE 0,500 0,500 20000 4.4e-15 4.0e-16 - * Integer v: - * IEEE -125,125 -125,125 50000 3.5e-15* 1.9e-16* - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1989, 1992, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "airy.h" -#include "cbrt.h" -#include "rgamma.h" -#include "j0.h" -#include "j1.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double jv_BIG = 1.44115188075855872E+17; - - /* Reduce the order by backward recurrence. - * AMS55 #9.1.27 and 9.1.73. - */ - - XSF_HOST_DEVICE inline double jv_recur(double *n, double x, double *newn, int cancel) { - double pkm2, pkm1, pk, qkm2, qkm1; - - /* double pkp1; */ - double k, ans, qk, xk, yk, r, t, kf; - constexpr double big = jv_BIG; - int nflag, ctr; - int miniter, maxiter; - - /* Continued fraction for Jn(x)/Jn-1(x) - * AMS 9.1.73 - * - * x -x^2 -x^2 - * ------ --------- --------- ... - * 2 n + 2(n+1) + 2(n+2) + - * - * Compute it with the simplest possible algorithm. - * - * This continued fraction starts to converge when (|n| + m) > |x|. - * Hence, at least |x|-|n| iterations are necessary before convergence is - * achieved. There is a hard limit set below, m <= 30000, which is chosen - * so that no branch in `jv` requires more iterations to converge. - * The exact maximum number is (500/3.6)^2 - 500 ~ 19000 - */ - - maxiter = 22000; - miniter = std::abs(x) - std::abs(*n); - if (miniter < 1) { - miniter = 1; - } - - if (*n < 0.0) { - nflag = 1; - } else { - nflag = 0; - } - - fstart: - pkm2 = 0.0; - qkm2 = 1.0; - pkm1 = x; - qkm1 = *n + *n; - xk = -x * x; - yk = qkm1; - ans = 0.0; /* ans=0.0 ensures that t=1.0 in the first iteration */ - ctr = 0; - do { - yk += 2.0; - pk = pkm1 * yk + pkm2 * xk; - qk = qkm1 * yk + qkm2 * xk; - pkm2 = pkm1; - pkm1 = pk; - qkm2 = qkm1; - qkm1 = qk; - - /* check convergence */ - if (qk != 0 && ctr > miniter) - r = pk / qk; - else - r = 0.0; - - if (r != 0) { - t = std::abs((ans - r) / r); - ans = r; - } else { - t = 1.0; - } - - if (++ctr > maxiter) { - set_error("jv", SF_ERROR_UNDERFLOW, NULL); - goto done; - } - if (t < MACHEP) { - goto done; - } - - /* renormalize coefficients */ - if (std::abs(pk) > big) { - pkm2 /= big; - pkm1 /= big; - qkm2 /= big; - qkm1 /= big; - } - } while (t > MACHEP); - - done: - if (ans == 0) - ans = 1.0; - - /* Change n to n-1 if n < 0 and the continued fraction is small */ - if (nflag > 0) { - if (std::abs(ans) < 0.125) { - nflag = -1; - *n = *n - 1.0; - goto fstart; - } - } - - kf = *newn; - - /* backward recurrence - * 2k - * J (x) = --- J (x) - J (x) - * k-1 x k k+1 - */ - - pk = 1.0; - pkm1 = 1.0 / ans; - k = *n - 1.0; - r = 2 * k; - do { - pkm2 = (pkm1 * r - pk * x) / x; - /* pkp1 = pk; */ - pk = pkm1; - pkm1 = pkm2; - r -= 2.0; - /* - * t = fabs(pkp1) + fabs(pk); - * if( (k > (kf + 2.5)) && (fabs(pkm1) < 0.25*t) ) - * { - * k -= 1.0; - * t = x*x; - * pkm2 = ( (r*(r+2.0)-t)*pk - r*x*pkp1 )/t; - * pkp1 = pk; - * pk = pkm1; - * pkm1 = pkm2; - * r -= 2.0; - * } - */ - k -= 1.0; - } while (k > (kf + 0.5)); - - /* Take the larger of the last two iterates - * on the theory that it may have less cancellation error. - */ - - if (cancel) { - if ((kf >= 0.0) && (std::abs(pk) > std::abs(pkm1))) { - k += 1.0; - pkm2 = pk; - } - } - *newn = k; - return (pkm2); - } - - /* Ascending power series for Jv(x). - * AMS55 #9.1.10. - */ - - XSF_HOST_DEVICE inline double jv_jvs(double n, double x) { - double t, u, y, z, k; - int ex, sgngam; - - z = -x * x / 4.0; - u = 1.0; - y = u; - k = 1.0; - t = 1.0; - - while (t > MACHEP) { - u *= z / (k * (n + k)); - y += u; - k += 1.0; - if (y != 0) - t = std::abs(u / y); - } - t = std::frexp(0.5 * x, &ex); - ex = ex * n; - if ((ex > -1023) && (ex < 1023) && (n > 0.0) && (n < (MAXGAM - 1.0))) { - t = std::pow(0.5 * x, n) * xsf::cephes::rgamma(n + 1.0); - y *= t; - } else { - t = n * std::log(0.5 * x) - lgam_sgn(n + 1.0, &sgngam); - if (y < 0) { - sgngam = -sgngam; - y = -y; - } - t += std::log(y); - if (t < -MAXLOG) { - return (0.0); - } - if (t > MAXLOG) { - set_error("Jv", SF_ERROR_OVERFLOW, NULL); - return (std::numeric_limits::infinity()); - } - y = sgngam * std::exp(t); - } - return (y); - } - - /* Hankel's asymptotic expansion - * for large x. - * AMS55 #9.2.5. - */ - - XSF_HOST_DEVICE inline double jv_hankel(double n, double x) { - double t, u, z, k, sign, conv; - double p, q, j, m, pp, qq; - int flag; - - m = 4.0 * n * n; - j = 1.0; - z = 8.0 * x; - k = 1.0; - p = 1.0; - u = (m - 1.0) / z; - q = u; - sign = 1.0; - conv = 1.0; - flag = 0; - t = 1.0; - pp = 1.0e38; - qq = 1.0e38; - - while (t > MACHEP) { - k += 2.0; - j += 1.0; - sign = -sign; - u *= (m - k * k) / (j * z); - p += sign * u; - k += 2.0; - j += 1.0; - u *= (m - k * k) / (j * z); - q += sign * u; - t = std::abs(u / p); - if (t < conv) { - conv = t; - qq = q; - pp = p; - flag = 1; - } - /* stop if the terms start getting larger */ - if ((flag != 0) && (t > conv)) { - goto hank1; - } - } - - hank1: - u = x - (0.5 * n + 0.25) * M_PI; - t = std::sqrt(2.0 / (M_PI * x)) * (pp * std::cos(u) - qq * std::sin(u)); - return (t); - } - - /* Asymptotic expansion for transition region, - * n large and x close to n. - * AMS55 #9.3.23. - */ - - constexpr double jv_PF2[] = {-9.0000000000000000000e-2, 8.5714285714285714286e-2}; - - constexpr double jv_PF3[] = {1.3671428571428571429e-1, -5.4920634920634920635e-2, -4.4444444444444444444e-3}; - - constexpr double jv_PF4[] = {1.3500000000000000000e-3, -1.6036054421768707483e-1, 4.2590187590187590188e-2, - 2.7330447330447330447e-3}; - - constexpr double jv_PG1[] = {-2.4285714285714285714e-1, 1.4285714285714285714e-2}; - - constexpr double jv_PG2[] = {-9.0000000000000000000e-3, 1.9396825396825396825e-1, -1.1746031746031746032e-2}; - - constexpr double jv_PG3[] = {1.9607142857142857143e-2, -1.5983694083694083694e-1, 6.3838383838383838384e-3}; - - XSF_HOST_DEVICE inline double jv_jnt(double n, double x) { - double z, zz, z3; - double cbn, n23, cbtwo; - double ai, aip, bi, bip; /* Airy functions */ - double nk, fk, gk, pp, qq; - double F[5], G[4]; - int k; - - cbn = cbrt(n); - z = (x - n) / cbn; - cbtwo = cbrt(2.0); - - /* Airy function */ - zz = -cbtwo * z; - xsf::cephes::airy(zz, &ai, &aip, &bi, &bip); - - /* polynomials in expansion */ - zz = z * z; - z3 = zz * z; - F[0] = 1.0; - F[1] = -z / 5.0; - F[2] = xsf::cephes::polevl(z3, jv_PF2, 1) * zz; - F[3] = xsf::cephes::polevl(z3, jv_PF3, 2); - F[4] = xsf::cephes::polevl(z3, jv_PF4, 3) * z; - G[0] = 0.3 * zz; - G[1] = xsf::cephes::polevl(z3, jv_PG1, 1); - G[2] = xsf::cephes::polevl(z3, jv_PG2, 2) * z; - G[3] = xsf::cephes::polevl(z3, jv_PG3, 2) * zz; - - pp = 0.0; - qq = 0.0; - nk = 1.0; - n23 = cbrt(n * n); - - for (k = 0; k <= 4; k++) { - fk = F[k] * nk; - pp += fk; - if (k != 4) { - gk = G[k] * nk; - qq += gk; - } - nk /= n23; - } - - fk = cbtwo * ai * pp / cbn + cbrt(4.0) * aip * qq / n; - return (fk); - } - - /* Asymptotic expansion for large n. - * AMS55 #9.3.35. - */ - - constexpr double jv_lambda[] = {1.0, - 1.041666666666666666666667E-1, - 8.355034722222222222222222E-2, - 1.282265745563271604938272E-1, - 2.918490264641404642489712E-1, - 8.816272674437576524187671E-1, - 3.321408281862767544702647E+0, - 1.499576298686255465867237E+1, - 7.892301301158651813848139E+1, - 4.744515388682643231611949E+2, - 3.207490090890661934704328E+3}; - - constexpr double jv_mu[] = {1.0, - -1.458333333333333333333333E-1, - -9.874131944444444444444444E-2, - -1.433120539158950617283951E-1, - -3.172272026784135480967078E-1, - -9.424291479571202491373028E-1, - -3.511203040826354261542798E+0, - -1.572726362036804512982712E+1, - -8.228143909718594444224656E+1, - -4.923553705236705240352022E+2, - -3.316218568547972508762102E+3}; - - constexpr double jv_P1[] = {-2.083333333333333333333333E-1, 1.250000000000000000000000E-1}; - - constexpr double jv_P2[] = {3.342013888888888888888889E-1, -4.010416666666666666666667E-1, - 7.031250000000000000000000E-2}; - - constexpr double jv_P3[] = {-1.025812596450617283950617E+0, 1.846462673611111111111111E+0, - -8.912109375000000000000000E-1, 7.324218750000000000000000E-2}; - - constexpr double jv_P4[] = {4.669584423426247427983539E+0, -1.120700261622299382716049E+1, - 8.789123535156250000000000E+0, -2.364086914062500000000000E+0, - 1.121520996093750000000000E-1}; - - constexpr double jv_P5[] = {-2.8212072558200244877E1, 8.4636217674600734632E1, -9.1818241543240017361E1, - 4.2534998745388454861E1, -7.3687943594796316964E0, 2.27108001708984375E-1}; - - constexpr double jv_P6[] = {2.1257013003921712286E2, -7.6525246814118164230E2, 1.0599904525279998779E3, - -6.9957962737613254123E2, 2.1819051174421159048E2, -2.6491430486951555525E1, - 5.7250142097473144531E-1}; - - constexpr double jv_P7[] = {-1.9194576623184069963E3, 8.0617221817373093845E3, -1.3586550006434137439E4, - 1.1655393336864533248E4, -5.3056469786134031084E3, 1.2009029132163524628E3, - -1.0809091978839465550E2, 1.7277275025844573975E0}; - - XSF_HOST_DEVICE inline double jv_jnx(double n, double x) { - double zeta, sqz, zz, zp, np; - double cbn, n23, t, z, sz; - double pp, qq, z32i, zzi; - double ak, bk, akl, bkl; - int sign, doa, dob, nflg, k, s, tk, tkp1, m; - double u[8]; - double ai, aip, bi, bip; - - /* Test for x very close to n. Use expansion for transition region if so. */ - cbn = cbrt(n); - z = (x - n) / cbn; - if (std::abs(z) <= 0.7) { - return (jv_jnt(n, x)); - } - - z = x / n; - zz = 1.0 - z * z; - if (zz == 0.0) { - return (0.0); - } - - if (zz > 0.0) { - sz = std::sqrt(zz); - t = 1.5 * (std::log((1.0 + sz) / z) - sz); /* zeta ** 3/2 */ - zeta = cbrt(t * t); - nflg = 1; - } else { - sz = std::sqrt(-zz); - t = 1.5 * (sz - std::acos(1.0 / z)); - zeta = -cbrt(t * t); - nflg = -1; - } - z32i = std::abs(1.0 / t); - sqz = cbrt(t); - - /* Airy function */ - n23 = cbrt(n * n); - t = n23 * zeta; - - xsf::cephes::airy(t, &ai, &aip, &bi, &bip); - - /* polynomials in expansion */ - u[0] = 1.0; - zzi = 1.0 / zz; - u[1] = xsf::cephes::polevl(zzi, jv_P1, 1) / sz; - u[2] = xsf::cephes::polevl(zzi, jv_P2, 2) / zz; - u[3] = xsf::cephes::polevl(zzi, jv_P3, 3) / (sz * zz); - pp = zz * zz; - u[4] = xsf::cephes::polevl(zzi, jv_P4, 4) / pp; - u[5] = xsf::cephes::polevl(zzi, jv_P5, 5) / (pp * sz); - pp *= zz; - u[6] = xsf::cephes::polevl(zzi, jv_P6, 6) / pp; - u[7] = xsf::cephes::polevl(zzi, jv_P7, 7) / (pp * sz); - - pp = 0.0; - qq = 0.0; - np = 1.0; - /* flags to stop when terms get larger */ - doa = 1; - dob = 1; - akl = std::numeric_limits::infinity(); - bkl = std::numeric_limits::infinity(); - - for (k = 0; k <= 3; k++) { - tk = 2 * k; - tkp1 = tk + 1; - zp = 1.0; - ak = 0.0; - bk = 0.0; - for (s = 0; s <= tk; s++) { - if (doa) { - if ((s & 3) > 1) - sign = nflg; - else - sign = 1; - ak += sign * jv_mu[s] * zp * u[tk - s]; - } - - if (dob) { - m = tkp1 - s; - if (((m + 1) & 3) > 1) - sign = nflg; - else - sign = 1; - bk += sign * jv_lambda[s] * zp * u[m]; - } - zp *= z32i; - } - - if (doa) { - ak *= np; - t = std::abs(ak); - if (t < akl) { - akl = t; - pp += ak; - } else - doa = 0; - } - - if (dob) { - bk += jv_lambda[tkp1] * zp * u[0]; - bk *= -np / sqz; - t = std::abs(bk); - if (t < bkl) { - bkl = t; - qq += bk; - } else - dob = 0; - } - if (np < MACHEP) - break; - np /= n * n; - } - - /* normalizing factor ( 4*zeta/(1 - z**2) )**1/4 */ - t = 4.0 * zeta / zz; - t = sqrt(sqrt(t)); - - t *= ai * pp / cbrt(n) + aip * qq / (n23 * n); - return (t); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double jv(double n, double x) { - double k, q, t, y, an; - int i, sign, nint; - - nint = 0; /* Flag for integer n */ - sign = 1; /* Flag for sign inversion */ - an = std::abs(n); - y = std::floor(an); - if (y == an) { - nint = 1; - i = an - 16384.0 * std::floor(an / 16384.0); - if (n < 0.0) { - if (i & 1) - sign = -sign; - n = an; - } - if (x < 0.0) { - if (i & 1) - sign = -sign; - x = -x; - } - if (n == 0.0) - return (j0(x)); - if (n == 1.0) - return (sign * j1(x)); - } - - if ((x < 0.0) && (y != an)) { - set_error("Jv", SF_ERROR_DOMAIN, NULL); - y = std::numeric_limits::quiet_NaN(); - goto done; - } - - if (x == 0 && n < 0 && !nint) { - set_error("Jv", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity() * rgamma(n + 1); - } - - y = std::abs(x); - - if (y * y < std::abs(n + 1) * detail::MACHEP) { - return std::pow(0.5 * x, n) * rgamma(n + 1); - } - - k = 3.6 * std::sqrt(y); - t = 3.6 * std::sqrt(an); - if ((y < t) && (an > 21.0)) { - return (sign * detail::jv_jvs(n, x)); - } - if ((an < k) && (y > 21.0)) - return (sign * detail::jv_hankel(n, x)); - - if (an < 500.0) { - /* Note: if x is too large, the continued fraction will fail; but then the - * Hankel expansion can be used. */ - if (nint != 0) { - k = 0.0; - q = detail::jv_recur(&n, x, &k, 1); - if (k == 0.0) { - y = j0(x) / q; - goto done; - } - if (k == 1.0) { - y = j1(x) / q; - goto done; - } - } - - if (an > 2.0 * y) - goto rlarger; - - if ((n >= 0.0) && (n < 20.0) && (y > 6.0) && (y < 20.0)) { - /* Recur backwards from a larger value of n */ - rlarger: - k = n; - - y = y + an + 1.0; - if (y < 30.0) - y = 30.0; - y = n + std::floor(y - n); - q = detail::jv_recur(&y, x, &k, 0); - y = detail::jv_jvs(y, x) * q; - goto done; - } - - if (k <= 30.0) { - k = 2.0; - } else if (k < 90.0) { - k = (3 * k) / 4; - } - if (an > (k + 3.0)) { - if (n < 0.0) { - k = -k; - } - q = n - std::floor(n); - k = std::floor(k) + q; - if (n > 0.0) { - q = detail::jv_recur(&n, x, &k, 1); - } else { - t = k; - k = n; - q = detail::jv_recur(&t, x, &k, 1); - k = t; - } - if (q == 0.0) { - y = 0.0; - goto done; - } - } else { - k = n; - q = 1.0; - } - - /* boundary between convergence of - * power series and Hankel expansion - */ - y = std::abs(k); - if (y < 26.0) - t = (0.0083 * y + 0.09) * y + 12.9; - else - t = 0.9 * y; - - if (x > t) - y = detail::jv_hankel(k, x); - else - y = detail::jv_jvs(k, x); - if (n > 0.0) - y /= q; - else - y *= q; - } - - else { - /* For large n, use the uniform expansion or the transitional expansion. - * But if x is of the order of n**2, these may blow up, whereas the - * Hankel expansion will then work. - */ - if (n < 0.0) { - set_error("jv", SF_ERROR_LOSS, NULL); - y = std::numeric_limits::quiet_NaN(); - goto done; - } - t = x / n; - t /= n; - if (t > 0.3) - y = detail::jv_hankel(n, x); - else - y = detail::jv_jnx(n, x); - } - - done: - return (sign * y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/k0.h b/scipy/special/xsf/cephes/k0.h deleted file mode 100644 index f617b93c7300..000000000000 --- a/scipy/special/xsf/cephes/k0.h +++ /dev/null @@ -1,164 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* k0.c - * - * Modified Bessel function, third kind, order zero - * - * - * - * SYNOPSIS: - * - * double x, y, k0(); - * - * y = k0( x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of the third kind - * of order zero of the argument. - * - * The range is partitioned into the two intervals [0,8] and - * (8, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Tested at 2000 random points between 0 and 8. Peak absolute - * error (relative when K0 > 1) was 1.46e-14; rms, 4.26e-15. - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.2e-15 1.6e-16 - * - * ERROR MESSAGES: - * - * message condition value returned - * K0 domain x <= 0 INFINITY - * - */ -/* k0e() - * - * Modified Bessel function, third kind, order zero, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, k0e(); - * - * y = k0e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of the third kind of order zero of the argument. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.4e-15 1.4e-16 - * See k0(). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "chbevl.h" -#include "i0.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Chebyshev coefficients for K0(x) + log(x/2) I0(x) - * in the interval [0,2]. The odd order coefficients are all - * zero; only the even order coefficients are listed. - * - * lim(x->0){ K0(x) + log(x/2) I0(x) } = -EUL. - */ - - constexpr double k0_A[] = {1.37446543561352307156E-16, 4.25981614279661018399E-14, 1.03496952576338420167E-11, - 1.90451637722020886025E-9, 2.53479107902614945675E-7, 2.28621210311945178607E-5, - 1.26461541144692592338E-3, 3.59799365153615016266E-2, 3.44289899924628486886E-1, - -5.35327393233902768720E-1}; - - /* Chebyshev coefficients for exp(x) sqrt(x) K0(x) - * in the inverted interval [2,infinity]. - * - * lim(x->inf){ exp(x) sqrt(x) K0(x) } = sqrt(pi/2). - */ - constexpr double k0_B[] = { - 5.30043377268626276149E-18, -1.64758043015242134646E-17, 5.21039150503902756861E-17, - -1.67823109680541210385E-16, 5.51205597852431940784E-16, -1.84859337734377901440E-15, - 6.34007647740507060557E-15, -2.22751332699166985548E-14, 8.03289077536357521100E-14, - -2.98009692317273043925E-13, 1.14034058820847496303E-12, -4.51459788337394416547E-12, - 1.85594911495471785253E-11, -7.95748924447710747776E-11, 3.57739728140030116597E-10, - -1.69753450938905987466E-9, 8.57403401741422608519E-9, -4.66048989768794782956E-8, - 2.76681363944501510342E-7, -1.83175552271911948767E-6, 1.39498137188764993662E-5, - -1.28495495816278026384E-4, 1.56988388573005337491E-3, -3.14481013119645005427E-2, - 2.44030308206595545468E0}; - - } // namespace detail - - XSF_HOST_DEVICE inline double k0(double x) { - double y, z; - - if (x == 0.0) { - set_error("k0", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k0", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = chbevl(y, detail::k0_A, 10) - std::log(0.5 * x) * i0(x); - return (y); - } - z = 8.0 / x - 2.0; - y = std::exp(-x) * chbevl(z, detail::k0_B, 25) / std::sqrt(x); - return (y); - } - - XSF_HOST_DEVICE double inline k0e(double x) { - double y; - - if (x == 0.0) { - set_error("k0e", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k0e", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = chbevl(y, detail::k0_A, 10) - std::log(0.5 * x) * i0(x); - return (y * exp(x)); - } - - y = chbevl(8.0 / x - 2.0, detail::k0_B, 25) / std::sqrt(x); - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/k1.h b/scipy/special/xsf/cephes/k1.h deleted file mode 100644 index 96594fd9c345..000000000000 --- a/scipy/special/xsf/cephes/k1.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* k1.c - * - * Modified Bessel function, third kind, order one - * - * - * - * SYNOPSIS: - * - * double x, y, k1(); - * - * y = k1( x ); - * - * - * - * DESCRIPTION: - * - * Computes the modified Bessel function of the third kind - * of order one of the argument. - * - * The range is partitioned into the two intervals [0,2] and - * (2, infinity). Chebyshev polynomial expansions are employed - * in each interval. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 1.2e-15 1.6e-16 - * - * ERROR MESSAGES: - * - * message condition value returned - * k1 domain x <= 0 INFINITY - * - */ -/* k1e.c - * - * Modified Bessel function, third kind, order one, - * exponentially scaled - * - * - * - * SYNOPSIS: - * - * double x, y, k1e(); - * - * y = k1e( x ); - * - * - * - * DESCRIPTION: - * - * Returns exponentially scaled modified Bessel function - * of the third kind of order one of the argument: - * - * k1e(x) = exp(x) * k1(x). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 7.8e-16 1.2e-16 - * See k1(). - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "chbevl.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Chebyshev coefficients for x(K1(x) - log(x/2) I1(x)) - * in the interval [0,2]. - * - * lim(x->0){ x(K1(x) - log(x/2) I1(x)) } = 1. - */ - - constexpr double k1_A[] = { - -7.02386347938628759343E-18, -2.42744985051936593393E-15, -6.66690169419932900609E-13, - -1.41148839263352776110E-10, -2.21338763073472585583E-8, -2.43340614156596823496E-6, - -1.73028895751305206302E-4, -6.97572385963986435018E-3, -1.22611180822657148235E-1, - -3.53155960776544875667E-1, 1.52530022733894777053E0}; - - /* Chebyshev coefficients for exp(x) sqrt(x) K1(x) - * in the interval [2,infinity]. - * - * lim(x->inf){ exp(x) sqrt(x) K1(x) } = sqrt(pi/2). - */ - constexpr double k1_B[] = { - -5.75674448366501715755E-18, 1.79405087314755922667E-17, -5.68946255844285935196E-17, - 1.83809354436663880070E-16, -6.05704724837331885336E-16, 2.03870316562433424052E-15, - -7.01983709041831346144E-15, 2.47715442448130437068E-14, -8.97670518232499435011E-14, - 3.34841966607842919884E-13, -1.28917396095102890680E-12, 5.13963967348173025100E-12, - -2.12996783842756842877E-11, 9.21831518760500529508E-11, -4.19035475934189648750E-10, - 2.01504975519703286596E-9, -1.03457624656780970260E-8, 5.74108412545004946722E-8, - -3.50196060308781257119E-7, 2.40648494783721712015E-6, -1.93619797416608296024E-5, - 1.95215518471351631108E-4, -2.85781685962277938680E-3, 1.03923736576817238437E-1, - 2.72062619048444266945E0}; - - } // namespace detail - - XSF_HOST_DEVICE inline double k1(double x) { - double y, z; - - if (x == 0.0) { - set_error("k1", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k1", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - z = 0.5 * x; - - if (x <= 2.0) { - y = x * x - 2.0; - y = std::log(z) * i1(x) + chbevl(y, detail::k1_A, 11) / x; - return (y); - } - - return (std::exp(-x) * chbevl(8.0 / x - 2.0, detail::k1_B, 25) / std::sqrt(x)); - } - - XSF_HOST_DEVICE double k1e(double x) { - double y; - - if (x == 0.0) { - set_error("k1e", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } else if (x < 0.0) { - set_error("k1e", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x <= 2.0) { - y = x * x - 2.0; - y = std::log(0.5 * x) * i1(x) + chbevl(y, detail::k1_A, 11) / x; - return (y * exp(x)); - } - - return (chbevl(8.0 / x - 2.0, detail::k1_B, 25) / std::sqrt(x)); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/kn.h b/scipy/special/xsf/cephes/kn.h deleted file mode 100644 index 31bc9fd7f735..000000000000 --- a/scipy/special/xsf/cephes/kn.h +++ /dev/null @@ -1,243 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* kn.c - * - * Modified Bessel function, third kind, integer order - * - * - * - * SYNOPSIS: - * - * double x, y, kn(); - * int n; - * - * y = kn( n, x ); - * - * - * - * DESCRIPTION: - * - * Returns modified Bessel function of the third kind - * of order n of the argument. - * - * The range is partitioned into the two intervals [0,9.55] and - * (9.55, infinity). An ascending power series is used in the - * low range, and an asymptotic expansion in the high range. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,30 90000 1.8e-8 3.0e-10 - * - * Error is high only near the crossover point x = 9.55 - * between the two expansions used. - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1988, 2000 by Stephen L. Moshier - */ - -/* - * Algorithm for Kn. - * n-1 - * -n - (n-k-1)! 2 k - * K (x) = 0.5 (x/2) > -------- (-x /4) - * n - k! - * k=0 - * - * inf. 2 k - * n n - (x /4) - * + (-1) 0.5(x/2) > {p(k+1) + p(n+k+1) - 2log(x/2)} --------- - * - k! (n+k)! - * k=0 - * - * where p(m) is the psi function: p(1) = -EUL and - * - * m-1 - * - - * p(m) = -EUL + > 1/k - * - - * k=1 - * - * For large x, - * 2 2 2 - * u-1 (u-1 )(u-3 ) - * K (z) = sqrt(pi/2z) exp(-z) { 1 + ------- + ------------ + ...} - * v 1 2 - * 1! (8z) 2! (8z) - * asymptotically, where - * - * 2 - * u = 4 v . - * - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int kn_MAXFAC = 31; - - } - - XSF_HOST_DEVICE inline double kn(int nn, double x) { - double k, kf, nk1f, nkf, zn, t, s, z0, z; - double ans, fn, pn, pk, zmn, tlg, tox; - int i, n; - - if (nn < 0) - n = -nn; - else - n = nn; - - if (n > detail::kn_MAXFAC) { - overf: - set_error("kn", SF_ERROR_OVERFLOW, NULL); - return (std::numeric_limits::infinity()); - } - - if (x <= 0.0) { - if (x < 0.0) { - set_error("kn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } else { - set_error("kn", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - } - - if (x > 9.55) - goto asymp; - - ans = 0.0; - z0 = 0.25 * x * x; - fn = 1.0; - pn = 0.0; - zmn = 1.0; - tox = 2.0 / x; - - if (n > 0) { - /* compute factorial of n and psi(n) */ - pn = -detail::SCIPY_EULER; - k = 1.0; - for (i = 1; i < n; i++) { - pn += 1.0 / k; - k += 1.0; - fn *= k; - } - - zmn = tox; - - if (n == 1) { - ans = 1.0 / x; - } else { - nk1f = fn / n; - kf = 1.0; - s = nk1f; - z = -z0; - zn = 1.0; - for (i = 1; i < n; i++) { - nk1f = nk1f / (n - i); - kf = kf * i; - zn *= z; - t = nk1f * zn / kf; - s += t; - if ((std::numeric_limits::max() - std::abs(t)) < std::abs(s)) { - goto overf; - } - if ((tox > 1.0) && ((std::numeric_limits::max() / tox) < zmn)) { - goto overf; - } - zmn *= tox; - } - s *= 0.5; - t = std::abs(s); - if ((zmn > 1.0) && ((std::numeric_limits::max() / zmn) < t)) { - goto overf; - } - if ((t > 1.0) && ((std::numeric_limits::max() / t) < zmn)) { - goto overf; - } - ans = s * zmn; - } - } - - tlg = 2.0 * log(0.5 * x); - pk = -detail::SCIPY_EULER; - if (n == 0) { - pn = pk; - t = 1.0; - } else { - pn = pn + 1.0 / n; - t = 1.0 / fn; - } - s = (pk + pn - tlg) * t; - k = 1.0; - do { - t *= z0 / (k * (k + n)); - pk += 1.0 / k; - pn += 1.0 / (k + n); - s += (pk + pn - tlg) * t; - k += 1.0; - } while (fabs(t / s) > detail::MACHEP); - - s = 0.5 * s / zmn; - if (n & 1) { - s = -s; - } - ans += s; - - return (ans); - - /* Asymptotic expansion for Kn(x) */ - /* Converges to 1.4e-17 for x > 18.4 */ - - asymp: - - if (x > detail::MAXLOG) { - set_error("kn", SF_ERROR_UNDERFLOW, NULL); - return (0.0); - } - k = n; - pn = 4.0 * k * k; - pk = 1.0; - z0 = 8.0 * x; - fn = 1.0; - t = 1.0; - s = t; - nkf = std::numeric_limits::infinity(); - i = 0; - do { - z = pn - pk * pk; - t = t * z / (fn * z0); - nk1f = std::abs(t); - if ((i >= n) && (nk1f > nkf)) { - goto adone; - } - nkf = nk1f; - s += t; - fn += 1.0; - pk += 2.0; - i += 1; - } while (std::abs(t / s) > detail::MACHEP); - - adone: - ans = std::exp(-x) * std::sqrt(M_PI / (2.0 * x)) * s; - return (ans); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/kolmogorov.h b/scipy/special/xsf/cephes/kolmogorov.h deleted file mode 100644 index 20fba8b9d427..000000000000 --- a/scipy/special/xsf/cephes/kolmogorov.h +++ /dev/null @@ -1,1041 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* File altered for inclusion in cephes module for Python: - * Main loop commented out.... */ -/* Travis Oliphant Nov. 1998 */ - -/* Re Kolmogorov statistics, here is Birnbaum and Tingey's (actually it was already present - * in Smirnov's paper) formula for the - * distribution of D+, the maximum of all positive deviations between a - * theoretical distribution function P(x) and an empirical one Sn(x) - * from n samples. - * - * + - * D = sup [P(x) - S (x)] - * n -inf < x < inf n - * - * - * [n(1-d)] - * + - v-1 n-v - * Pr{D > d} = > C d (d + v/n) (1 - d - v/n) - * n - n v - * v=0 - * - * (also equals the following sum, but note the terms may be large and alternating in sign) - * See Smirnov 1944, Dwass 1959 - * n - * - v-1 n-v - * = 1 - > C d (d + v/n) (1 - d - v/n) - * - n v - * v=[n(1-d)]+1 - * - * [n(1-d)] is the largest integer not exceeding n(1-d). - * nCv is the number of combinations of n things taken v at a time. - - * Sources: - * [1] Smirnov, N.V. "Approximate laws of distribution of random variables from empirical data" - * Usp. Mat. Nauk, 1944. http://mi.mathnet.ru/umn8798 - * [2] Birnbaum, Z. W. and Tingey, Fred H. - * "One-Sided Confidence Contours for Probability Distribution Functions", - * Ann. Math. Statist. 1951. https://doi.org/10.1214/aoms/1177729550 - * [3] Dwass, Meyer, "The Distribution of a Generalized $\mathrm{D}^+_n$ Statistic", - * Ann. Math. Statist., 1959. https://doi.org/10.1214/aoms/1177706085 - * [4] van Mulbregt, Paul, "Computing the Cumulative Distribution Function and Quantiles of the One-sided - Kolmogorov-Smirnov Statistic" - * http://arxiv.org/abs/1802.06966 - * [5] van Mulbregt, Paul, "Computing the Cumulative Distribution Function and Quantiles of the limit of the Two-sided - Kolmogorov-Smirnov Statistic" - * https://arxiv.org/abs/1803.00426 - * - */ - -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "dd_real.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* ************************************************************************ */ - /* Algorithm Configuration */ - - constexpr int KOLMOG_MAXITER = 500; - - /* - * Kolmogorov Two-sided: - * Switchover between the two series to compute K(x) - * 0 <= x <= KOLMOG_CUTOVER and - * KOLMOG_CUTOVER < x < infty - */ - constexpr double KOLMOG_CUTOVER = 0.82; - - /* - * Smirnov One-sided: - * n larger than SMIRNOV_MAX_COMPUTE_N will result in an approximation - */ - constexpr int SMIRNOV_MAX_COMPUTE_N = 1000000; - - /* - * Use the upper sum formula, if the number of terms is at most SM_UPPER_MAX_TERMS, - * and n is at least SM_UPPERSUM_MIN_N - * Don't use the upper sum if lots of terms are involved as the series alternates - * sign and the terms get much bigger than 1. - */ - constexpr int SM_UPPER_MAX_TERMS = 3; - constexpr int SM_UPPERSUM_MIN_N = 10; - - /* ************************************************************************ */ - /* ************************************************************************ */ - - /* exp() of anything below this returns 0 */ - constexpr int MIN_EXPABLE = (-708 - 38); - - /* Class to hold the CDF, SF and PDF, which are computed simultaneously */ - struct ThreeProbs { - double sf; - double cdf; - double pdf; - }; - - constexpr double _xtol = std::numeric_limits::epsilon(); - constexpr double _rtol = 2 * _xtol; - - XSF_HOST_DEVICE inline bool _within_tol(double x, double y, double atol, double rtol) { - double diff = std::abs(x - y); - bool result = (diff <= (atol + rtol * std::abs(y))); - return result; - } - - /* ************************************************************************ */ - /* Kolmogorov : Two-sided **************************** */ - /* ************************************************************************ */ - - XSF_HOST_DEVICE inline ThreeProbs _kolmogorov(double x) { - double P = 1.0; - double D = 0; - double sf, cdf, pdf; - - if (std::isnan(x)) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - if (x <= 0) { - return {1.0, 0.0, 0}; - } - /* x <= 0.040611972203751713 */ - if (x <= M_PI / std::sqrt(-MIN_EXPABLE * 8)) { - return {1.0, 0.0, 0}; - } - - P = 1.0; - if (x <= KOLMOG_CUTOVER) { - /* - * u = e^(-pi^2/(8x^2)) - * w = sqrt(2pi)/x - * P = w*u * (1 + u^8 + u^24 + u^48 + ...) - */ - double w = std::sqrt(2 * M_PI) / x; - double logu8 = -M_PI * M_PI / (x * x); /* log(u^8) */ - double u = std::exp(logu8 / 8); - if (u == 0) { - /* - * P = w*u, but u < 1e-308, and w > 1, - * so compute as logs, then exponentiate - */ - double logP = logu8 / 8 + std::log(w); - P = std::exp(logP); - } else { - /* Just unroll the loop, 3 iterations */ - double u8 = std::exp(logu8); - double u8cub = std::pow(u8, 3); - P = 1 + u8cub * P; - D = 5 * 5 + u8cub * D; - P = 1 + u8 * u8 * P; - D = 3 * 3 + u8 * u8 * D; - P = 1 + u8 * P; - D = 1 * 1 + u8 * D; - - D = M_PI * M_PI / 4 / (x * x) * D - P; - D *= w * u / x; - P = w * u * P; - } - cdf = P; - sf = 1 - P; - pdf = D; - } else { - /* - * v = e^(-2x^2) - * P = 2 (v - v^4 + v^9 - v^16 + ...) - * = 2v(1 - v^3*(1 - v^5*(1 - v^7*(1 - ...))) - */ - double logv = -2 * x * x; - double v = std::exp(logv); - /* - * Want q^((2k-1)^2)(1-q^(4k-1)) / q(1-q^3) < epsilon to break out of loop. - * With KOLMOG_CUTOVER ~ 0.82, k <= 4. Just unroll the loop, 4 iterations - */ - double vsq = v * v; - double v3 = std::pow(v, 3); - double vpwr; - - vpwr = v3 * v3 * v; /* v**7 */ - P = 1 - vpwr * P; /* P <- 1 - (1-v**(2k-1)) * P */ - D = 3 * 3 - vpwr * D; - - vpwr = v3 * vsq; - P = 1 - vpwr * P; - D = 2 * 2 - vpwr * D; - - vpwr = v3; - P = 1 - vpwr * P; - D = 1 * 1 - vpwr * D; - - P = 2 * v * P; - D = 8 * v * x * D; - sf = P; - cdf = 1 - sf; - pdf = D; - } - pdf = std::fmax(0, pdf); - cdf = std::clamp(cdf, 0.0, 1.0); - sf = std::clamp(sf, 0.0, 1.0); - return {sf, cdf, pdf}; - } - - /* Find x such kolmogorov(x)=psf, kolmogc(x)=pcdf */ - XSF_HOST_DEVICE inline double _kolmogi(double psf, double pcdf) { - double x, t; - double xmin = 0; - double xmax = std::numeric_limits::infinity(); - int iterations; - double a = xmin, b = xmax; - - if (!(psf >= 0.0 && pcdf >= 0.0 && pcdf <= 1.0 && psf <= 1.0)) { - set_error("kolmogi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (std::abs(1.0 - pcdf - psf) > 4 * std::numeric_limits::epsilon()) { - set_error("kolmogi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (pcdf == 0.0) { - return 0.0; - } - if (psf == 0.0) { - return std::numeric_limits::infinity(); - } - - if (pcdf <= 0.5) { - /* p ~ (sqrt(2pi)/x) *exp(-pi^2/8x^2). Generate lower and upper bounds */ - double logpcdf = std::log(pcdf); - /* Now that 1 >= x >= sqrt(p) */ - /* Iterate twice: x <- pi/(sqrt(8) sqrt(log(sqrt(2pi)) - log(x) - log(pdf))) */ - a = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + logpcdf / 2 - LOGSQRT2PI))); - b = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + 0 - LOGSQRT2PI))); - a = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + std::log(a) - LOGSQRT2PI))); - b = M_PI / (2 * M_SQRT2 * std::sqrt(-(logpcdf + std::log(b) - LOGSQRT2PI))); - x = (a + b) / 2.0; - } else { - /* - * Based on the approximation p ~ 2 exp(-2x^2) - * Found that needed to replace psf with a slightly smaller number in the second element - * as otherwise _kolmogorov(b) came back as a very small number but with - * the same sign as _kolmogorov(a) - * kolmogi(0.5) = 0.82757355518990772 - * so (1-q^(-(4-1)*2*x^2)) = (1-exp(-6*0.8275^2) ~ (1-exp(-4.1) - */ - constexpr double jiggerb = 256 * std::numeric_limits::epsilon(); - double pba = psf / (1.0 - std::exp(-4)) / 2, pbb = psf * (1 - jiggerb) / 2; - double q0; - a = std::sqrt(-0.5 * std::log(pba)); - b = std::sqrt(-0.5 * std::log(pbb)); - /* - * Use inversion of - * p = q - q^4 + q^9 - q^16 + ...: - * q = p + p^4 + 4p^7 - p^9 + 22p^10 - 13p^12 + 140*p^13 ... - */ - { - double p = psf / 2.0; - double p2 = p * p; - double p3 = p * p * p; - q0 = 1 + p3 * (1 + p3 * (4 + p2 * (-1 + p * (22 + p2 * (-13 + 140 * p))))); - q0 *= p; - } - x = std::sqrt(-std::log(q0) / 2); - if (x < a || x > b) { - x = (a + b) / 2; - } - } - XSF_ASSERT(a <= b); - - iterations = 0; - do { - double x0 = x; - ThreeProbs probs = _kolmogorov(x0); - double df = ((pcdf < 0.5) ? (pcdf - probs.cdf) : (probs.sf - psf)); - double dfdx; - - if (std::abs(df) == 0) { - break; - } - /* Update the bracketing interval */ - if (df > 0 && x > a) { - a = x; - } else if (df < 0 && x < b) { - b = x; - } - - dfdx = -probs.pdf; - if (std::abs(dfdx) <= 0.0) { - x = (a + b) / 2; - t = x0 - x; - } else { - t = df / dfdx; - x = x0 - t; - } - - /* - * Check out-of-bounds. - * Not expecting this to happen often --- kolmogorov is convex near x=infinity and - * concave near x=0, and we should be approaching from the correct side. - * If out-of-bounds, replace x with a midpoint of the bracket. - */ - if (x >= a && x <= b) { - if (_within_tol(x, x0, _xtol, _rtol)) { - break; - } - if ((x == a) || (x == b)) { - x = (a + b) / 2.0; - /* If the bracket is already so small ... */ - if (x == a || x == b) { - break; - } - } - } else { - x = (a + b) / 2.0; - if (_within_tol(x, x0, _xtol, _rtol)) { - break; - } - } - - if (++iterations > KOLMOG_MAXITER) { - set_error("kolmogi", SF_ERROR_SLOW, NULL); - break; - } - } while (1); - return (x); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double kolmogorov(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogorov(x).sf; - } - - XSF_HOST_DEVICE inline double kolmogc(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogorov(x).cdf; - } - - XSF_HOST_DEVICE inline double kolmogp(double x) { - if (std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (x <= 0) { - return -0.0; - } - return -detail::_kolmogorov(x).pdf; - } - - /* Functional inverse of Kolmogorov survival statistic for two-sided test. - * Finds x such that kolmogorov(x) = p. - */ - XSF_HOST_DEVICE inline double kolmogi(double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogi(p, 1 - p); - } - - /* Functional inverse of Kolmogorov cumulative statistic for two-sided test. - * Finds x such that kolmogc(x) = p = (or kolmogorov(x) = 1-p). - */ - XSF_HOST_DEVICE inline double kolmogci(double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_kolmogi(1 - p, p); - } - - namespace detail { - - /* ************************************************************************ */ - /* ********** Smirnov : One-sided ***************************************** */ - /* ************************************************************************ */ - - XSF_HOST_DEVICE inline double nextPowerOf2(double x) { - double q = std::ldexp(x, 1 - std::numeric_limits::digits); - double L = std::abs(q + x); - if (L == 0) { - L = std::abs(x); - } else { - int Lint = (int) (L); - if (Lint == L) { - L = Lint; - } - } - return L; - } - - XSF_HOST_DEVICE inline double modNX(int n, double x, int *pNXFloor, double *pNX) { - /* - * Compute floor(n*x) and remainder *exactly*. - * If remainder is too close to 1 (E.g. (1, -std::numeric_limits::epsilon()/2)) - * round up and adjust */ - double_double alphaD, nxD, nxfloorD; - int nxfloor; - double alpha; - - nxD = static_cast(n) * double_double(x); - nxfloorD = floor(nxD); - alphaD = nxD - nxfloorD; - alpha = alphaD.hi; - nxfloor = static_cast(nxfloorD); - XSF_ASSERT(alpha >= 0); - XSF_ASSERT(alpha <= 1); - if (alpha == 1) { - nxfloor += 1; - alpha = 0; - } - XSF_ASSERT(alpha < 1.0); - *pNX = static_cast(nxD); - *pNXFloor = nxfloor; - return alpha; - } - - /* - * The binomial coefficient C overflows a 64 bit double, as the 11-bit - * exponent is too small. - * Store C as (Cman:double_double, Cexpt:int). - * I.e a Mantissa/significand, and an exponent. - * Cman lies between 0.5 and 1, and the exponent has >=32-bit. - */ - XSF_HOST_DEVICE inline void updateBinomial(double_double *Cman, int *Cexpt, int n, int j) { - int expt; - double_double rat = double_double(n - j) / (j + 1.0); - double_double man2 = *Cman * rat; - man2 = frexp(man2, &expt); - XSF_ASSERT(man2 != 0.0); - *Cexpt += expt; - *Cman = man2; - } - - XSF_HOST_DEVICE double_double pow_D(const double_double &a, int m) { - /* - * Using dd_npwr() here would be quite time-consuming. - * Tradeoff accuracy-time by using pow(). - */ - - double ans, r, adj; - if (m <= 0) { - if (m == 0) { - return double_double(1.0); - } - return 1.0 / pow_D(a, -m); - } - if (a == 0.0) { - return double_double(0.0); - } - ans = std::pow(a.hi, m); - r = a.lo / a.hi; - adj = m * r; - if (std::abs(adj) > 1e-8) { - if (std::abs(adj) < 1e-4) { - /* Take 1st two terms of Taylor Series for (1+r)^m */ - adj += (m * r) * ((m - 1) / 2.0 * r); - } else { - /* Take exp of scaled log */ - adj = xsf::cephes::expm1(m * std::log1p(r)); - } - } - return double_double(ans) + ans * adj; - } - - XSF_HOST_DEVICE inline double pow2(double a, double b, int m) { - return static_cast(pow_D(double_double(a) + b, m)); - } - - /* - * Not 1024 as too big. Want _MAX_EXPONENT < 1023-52 so as to keep both - * elements of the double_double normalized - */ - constexpr int SM_MAX_EXPONENT = 960; - - XSF_HOST_DEVICE double_double pow2Scaled_D(const double_double &a, int m, int *pExponent) { - /* Compute a^m = significand*2^expt and return as (significand, expt) */ - double_double ans, y; - int ansE, yE; - int maxExpt = SM_MAX_EXPONENT; - int q, r, y2mE, y2rE, y2mqE; - double_double y2r, y2m, y2mq; - - if (m <= 0) { - int aE1, aE2; - if (m == 0) { - *pExponent = 0.0; - return double_double(1.0); - } - ans = pow2Scaled_D(a, -m, &aE1); - ans = frexp(1.0 / ans, &aE2); - ansE = -aE1 + aE2; - *pExponent = ansE; - return ans; - } - y = frexp(a, &yE); - if (m == 1) { - *pExponent = yE; - return y; - } - /* - * y ^ maxExpt >= 2^{-960} - * => maxExpt = 960 / log2(y.x[0]) = 708 / log(y.x[0]) - * = 665/((1-y.x[0] + y.x[0]^2/2 - ...) - * <= 665/(1-y.x[0]) - * Quick check to see if we might need to break up the exponentiation - */ - if (m * (y.hi - 1) / y.hi < -SM_MAX_EXPONENT * M_LN2) { - /* Now do it carefully, calling log() */ - double lg2y = std::log(y.hi) / M_LN2; - double lgAns = m * lg2y; - if (lgAns <= -SM_MAX_EXPONENT) { - maxExpt = static_cast(nextPowerOf2(-SM_MAX_EXPONENT / lg2y + 1) / 2); - } - } - if (m <= maxExpt) { - double_double ans1 = pow_D(y, m); - ans = frexp(ans1, &ansE); - ansE += m * yE; - *pExponent = ansE; - return ans; - } - - q = m / maxExpt; - r = m % maxExpt; - /* y^m = (y^maxExpt)^q * y^r */ - y2r = pow2Scaled_D(y, r, &y2rE); - y2m = pow2Scaled_D(y, maxExpt, &y2mE); - y2mq = pow2Scaled_D(y2m, q, &y2mqE); - ans = frexp(y2r * y2mq, &ansE); - y2mqE += y2mE * q; - ansE += y2mqE + y2rE; - ansE += m * yE; - *pExponent = ansE; - return ans; - } - - XSF_HOST_DEVICE inline double_double pow4_D(double a, double b, double c, double d, int m) { - /* Compute ((a+b)/(c+d)) ^ m */ - double_double A, C, X; - if (m <= 0) { - if (m == 0) { - return double_double(1.0); - } - return pow4_D(c, d, a, b, -m); - } - A = double_double(a) + b; - C = double_double(c) + d; - if (A == 0.0) { - return (C == 0.0) ? quiet_NaN() : double_double(0.0); - } - if (C == 0.0) { - return ((A < 0) ? -infinity() : infinity()); - } - X = A / C; - return pow_D(X, m); - } - - XSF_HOST_DEVICE inline double pow4(double a, double b, double c, double d, int m) { - double_double ret = pow4_D(a, b, c, d, m); - return static_cast(ret); - } - - XSF_HOST_DEVICE inline double_double logpow4_D(double a, double b, double c, double d, int m) { - /* - * Compute log(((a+b)/(c+d)) ^ m) - * == m * log((a+b)/(c+d)) - * == m * log( 1 + (a+b-c-d)/(c+d)) - */ - double_double ans; - double_double A, C, X; - if (m == 0) { - return double_double(0.0); - } - A = double_double(a) + b; - C = double_double(c) + d; - if (A == 0.0) { - return ((C == 0.0) ? double_double(0.0) : -infinity()); - } - if (C == 0.0) { - return infinity(); - } - X = A / C; - XSF_ASSERT(X.hi >= 0); - if (0.5 <= X.hi && X.hi <= 1.5) { - double_double A1 = A - C; - double_double X1 = A1 / C; - ans = log1p(X1); - } else { - ans = log(X); - } - ans = m * ans; - return ans; - } - - XSF_HOST_DEVICE inline double logpow4(double a, double b, double c, double d, int m) { - double_double ans = logpow4_D(a, b, c, d, m); - return static_cast(ans); - } - - /* - * Compute a single term in the summation, A_v(n, x): - * A_v(n, x) = Binomial(n,v) * (1-x-v/n)^(n-v) * (x+v/n)^(v-1) - */ - XSF_HOST_DEVICE inline void computeAv(int n, double x, int v, const double_double &Cman, int Cexpt, - double_double *pt1, double_double *pt2, double_double *pAv) { - int t1E, t2E, ansE; - double_double Av; - double_double t2x = double_double(n - v) / n - x; /* 1 - x - v/n */ - double_double t2 = pow2Scaled_D(t2x, n - v, &t2E); - double_double t1x = double_double(v) / n + x; /* x + v/n */ - double_double t1 = pow2Scaled_D(t1x, v - 1, &t1E); - double_double ans = t1 * t2; - ans = ans * Cman; - ansE = Cexpt + t1E + t2E; - Av = ldexp(ans, ansE); - *pAv = Av; - *pt1 = t1; - *pt2 = t2; - } - - XSF_HOST_DEVICE inline ThreeProbs _smirnov(int n, double x) { - double nx, alpha; - double_double AjSum = double_double(0.0); - double_double dAjSum = double_double(0.0); - double cdf, sf, pdf; - - int bUseUpperSum; - int nxfl, n1mxfl, n1mxceil; - - if (!(n > 0 && x >= 0.0 && x <= 1.0)) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - if (n == 1) { - return {1 - x, x, 1.0}; - } - if (x == 0.0) { - return {1.0, 0.0, 1.0}; - } - if (x == 1.0) { - return {0.0, 1.0, 0.0}; - } - - alpha = modNX(n, x, &nxfl, &nx); - n1mxfl = n - nxfl - (alpha == 0 ? 0 : 1); - n1mxceil = n - nxfl; - /* - * If alpha is 0, don't actually want to include the last term - * in either the lower or upper summations. - */ - if (alpha == 0) { - n1mxfl -= 1; - n1mxceil += 1; - } - - /* Special case: x <= 1/n */ - if (nxfl == 0 || (nxfl == 1 && alpha == 0)) { - double t = pow2(1, x, n - 1); - pdf = (nx + 1) * t / (1 + x); - cdf = x * t; - sf = 1 - cdf; - /* Adjust if x=1/n *exactly* */ - if (nxfl == 1) { - XSF_ASSERT(alpha == 0); - pdf -= 0.5; - } - return {sf, cdf, pdf}; - } - /* Special case: x is so big, the sf underflows double64 */ - if (-2 * n * x * x < MINLOG) { - return {0, 1, 0}; - } - /* Special case: x >= 1 - 1/n */ - if (nxfl >= n - 1) { - sf = pow2(1, -x, n); - cdf = 1 - sf; - pdf = n * sf / (1 - x); - return {sf, cdf, pdf}; - } - /* Special case: n is so big, take too long to compute */ - if (n > SMIRNOV_MAX_COMPUTE_N) { - /* p ~ e^(-(6nx+1)^2 / 18n) */ - double logp = -std::pow(6.0 * n * x + 1, 2) / 18.0 / n; - /* Maximise precision for small p-value. */ - if (logp < -M_LN2) { - sf = std::exp(logp); - cdf = 1 - sf; - } else { - cdf = -xsf::cephes::expm1(logp); - sf = 1 - cdf; - } - pdf = (6.0 * n * x + 1) * 2 * sf / 3; - return {sf, cdf, pdf}; - } - { - /* - * Use the upper sum if n is large enough, and x is small enough and - * the number of terms is going to be small enough. - * Otherwise it just drops accuracy, about 1.6bits * nUpperTerms - */ - int nUpperTerms = n - n1mxceil + 1; - bUseUpperSum = (nUpperTerms <= 1 && x < 0.5); - bUseUpperSum = (bUseUpperSum || ((n >= SM_UPPERSUM_MIN_N) && (nUpperTerms <= SM_UPPER_MAX_TERMS) && - (x <= 0.5 / std::sqrt(n)))); - } - { - int start = 0, step = 1, nTerms = n1mxfl + 1; - int j, firstJ = 0; - int vmid = n / 2; - double_double Cman = double_double(1.0); - int Cexpt = 0; - double_double Aj, dAj, t1, t2, dAjCoeff; - double_double oneOverX = double_double(1.0) / x; - - if (bUseUpperSum) { - start = n; - step = -1; - nTerms = n - n1mxceil + 1; - - t1 = pow4_D(1, x, 1, 0, n - 1); - t2 = double_double(1.0); - Aj = t1; - - dAjCoeff = (n - 1) / (double_double(1.0) + x); - dAjCoeff = dAjCoeff + oneOverX; - } else { - t1 = oneOverX; - t2 = pow4_D(1, -x, 1, 0, n); - Aj = t2 / x; - - dAjCoeff = (-1 - double_double(n - 1) * x) / (double_double(1.0) - x); - dAjCoeff = dAjCoeff / x; - dAjCoeff = dAjCoeff + oneOverX; - } - - dAj = Aj * dAjCoeff; - AjSum = AjSum + Aj; - dAjSum = dAjSum + dAj; - - updateBinomial(&Cman, &Cexpt, n, 0); - firstJ++; - - for (j = firstJ; j < nTerms; j += 1) { - int v = start + j * step; - - computeAv(n, x, v, Cman, Cexpt, &t1, &t2, &Aj); - - if (isfinite(Aj) && (Aj != 0.0)) { - /* coeff = 1/x + (j-1)/(x+j/n) - (n-j)/(1-x-j/n) */ - dAjCoeff = (n * (v - 1)) / (double_double(nxfl + v) + alpha) - - ((n - v) * n) / (double_double(n - nxfl - v) - alpha); - dAjCoeff = dAjCoeff + oneOverX; - dAj = Aj * dAjCoeff; - - XSF_ASSERT(isfinite(Aj)); - AjSum = AjSum + Aj; - dAjSum = dAjSum + dAj; - } - /* Safe to terminate early? */ - if (Aj != 0.0) { - if (((4 * (nTerms - j) * std::abs(static_cast(Aj))) < - (std::numeric_limits::epsilon() * static_cast(AjSum))) && - (j != nTerms - 1)) { - break; - } - } else if (j > vmid) { - XSF_ASSERT(Aj == 0.0); - break; - } - updateBinomial(&Cman, &Cexpt, n, j); - } - XSF_ASSERT(isfinite(AjSum)); - XSF_ASSERT(isfinite(dAjSum)); - { - double_double derivD = x * dAjSum; - double_double probD = x * AjSum; - double deriv = static_cast(derivD); - double prob = static_cast(probD); - - XSF_ASSERT(nx != 1 || alpha > 0); - if (step < 0) { - cdf = prob; - sf = 1 - prob; - pdf = deriv; - } else { - cdf = 1 - prob; - sf = prob; - pdf = -deriv; - } - } - } - pdf = std::fmax(0, pdf); - cdf = std::clamp(cdf, 0.0, 1.0); - sf = std::clamp(sf, 0.0, 1.0); - return {sf, cdf, pdf}; - } - - /* - * Functional inverse of Smirnov distribution - * finds x such that smirnov(n, x) = psf; smirnovc(n, x) = pcdf). - */ - XSF_HOST_DEVICE inline double _smirnovi(int n, double psf, double pcdf) { - /* - * Need to use a bracketing NR algorithm here and be very careful - * about the starting point. - */ - double x, logpcdf; - int iterations = 0; - double a = 0, b = 1; - double maxlogpcdf, psfrootn; - double dx, dxold; - - if (!(n > 0 && psf >= 0.0 && pcdf >= 0.0 && pcdf <= 1.0 && psf <= 1.0)) { - set_error("smirnovi", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::abs(1.0 - pcdf - psf) > 4 * std::numeric_limits::epsilon()) { - set_error("smirnovi", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - /* STEP 1: Handle psf==0, or pcdf == 0 */ - if (pcdf == 0.0) { - return 0.0; - } - if (psf == 0.0) { - return 1.0; - } - /* STEP 2: Handle n=1 */ - if (n == 1) { - return pcdf; - } - - /* STEP 3 Handle psf *very* close to 0. Correspond to (n-1)/n < x < 1 */ - psfrootn = std::pow(psf, 1.0 / n); - /* xmin > 1 - 1.0 / n */ - if (n < 150 && n * psfrootn <= 1) { - /* Solve exactly. */ - x = 1 - psfrootn; - return x; - } - - logpcdf = (pcdf < 0.5 ? std::log(pcdf) : std::log1p(-psf)); - - /* - * STEP 4 Find bracket and initial estimate for use in N-R - * 4(a) Handle 0 < x <= 1/n: pcdf = x * (1+x)^*(n-1) - */ - maxlogpcdf = logpow4(1, 0.0, n, 0, 1) + logpow4(n, 1, n, 0, n - 1); - if (logpcdf <= maxlogpcdf) { - double xmin = pcdf / SCIPY_El; - double xmax = pcdf; - double P1 = pow4(n, 1, n, 0, n - 1) / n; - double R = pcdf / P1; - double z0 = R; - /* - * Do one iteration of N-R solving: z*e^(z-1) = R, with z0=pcdf/P1 - * z <- z - (z exp(z-1) - pcdf)/((z+1)exp(z-1)) - * If z_0 = R, z_1 = R(1-exp(1-R))/(R+1) - */ - if (R >= 1) { - /* - * R=1 is OK; - * R>1 can happen due to truncation error for x = (1-1/n)+-eps - */ - R = 1; - x = R / n; - return x; - } - z0 = (z0 * z0 + R * std::exp(1 - z0)) / (1 + z0); - x = z0 / n; - a = xmin * (1 - 4 * std::numeric_limits::epsilon()); - a = std::fmax(a, 0); - b = xmax * (1 + 4 * std::numeric_limits::epsilon()); - b = std::fmin(b, 1.0 / n); - x = std::clamp(x, a, b); - } else { - /* 4(b) : 1/n < x < (n-1)/n */ - double xmin = 1 - psfrootn; - double logpsf = (psf < 0.5 ? std::log(psf) : std::log1p(-pcdf)); - double xmax = std::sqrt(-logpsf / (2.0L * n)); - double xmax6 = xmax - 1.0L / (6 * n); - a = xmin; - b = xmax; - /* Allow for a little rounding error */ - a *= 1 - 4 * std::numeric_limits::epsilon(); - b *= 1 + 4 * std::numeric_limits::epsilon(); - a = std::fmax(xmin, 1.0 / n); - b = std::fmin(xmax, 1 - 1.0 / n); - x = xmax6; - } - if (x < a || x > b) { - x = (a + b) / 2; - } - XSF_ASSERT(x < 1); - - /* - * Skip computing fa, fb as that takes cycles and the exact values - * are not needed. - */ - - /* STEP 5 Run N-R. - * smirnov should be well-enough behaved for NR starting at this location. - * Use smirnov(n, x)-psf, or pcdf - smirnovc(n, x), whichever has smaller p. - */ - dxold = b - a; - dx = dxold; - do { - double dfdx, x0 = x, deltax, df; - XSF_ASSERT(x < 1); - XSF_ASSERT(x > 0); - { - ThreeProbs probs = _smirnov(n, x0); - df = ((pcdf < 0.5) ? (pcdf - probs.cdf) : (probs.sf - psf)); - dfdx = -probs.pdf; - } - if (df == 0) { - return x; - } - /* Update the bracketing interval */ - if (df > 0 && x > a) { - a = x; - } else if (df < 0 && x < b) { - b = x; - } - - if (dfdx == 0) { - /* - * x was not within tolerance, but now we hit a 0 derivative. - * This implies that x >> 1/sqrt(n), and even then |smirnovp| >= |smirnov| - * so this condition is unexpected. Do a bisection step. - */ - x = (a + b) / 2; - deltax = x0 - x; - } else { - deltax = df / dfdx; - x = x0 - deltax; - } - /* - * Check out-of-bounds. - * Not expecting this to happen ofen --- smirnov is convex near x=1 and - * concave near x=0, and we should be approaching from the correct side. - * If out-of-bounds, replace x with a midpoint of the bracket. - * Also check fast enough convergence. - */ - if ((a <= x) && (x <= b) && - (std::abs(2 * deltax) <= std::abs(dxold) || - std::abs(dxold) < 256 * std::numeric_limits::epsilon())) { - dxold = dx; - dx = deltax; - } else { - dxold = dx; - dx = dx / 2; - x = (a + b) / 2; - deltax = x0 - x; - } - /* - * Note that if psf is close to 1, f(x) -> 1, f'(x) -> -1. - * => abs difference |x-x0| is approx |f(x)-p| >= std::numeric_limits::epsilon(), - * => |x-x0|/x >= std::numeric_limits::epsilon()/x. - * => cannot use a purely relative criteria as it will fail for x close to 0. - */ - if (_within_tol(x, x0, (psf < 0.5 ? 0 : _xtol), _rtol)) { - break; - } - if (++iterations > KOLMOG_MAXITER) { - set_error("smirnovi", SF_ERROR_SLOW, NULL); - return (x); - } - } while (1); - return x; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double smirnov(int n, double d) { - if (std::isnan(d)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnov(n, d).sf; - } - - XSF_HOST_DEVICE inline double smirnovc(int n, double d) { - if (std::isnan(d)) { - return NAN; - } - return detail::_smirnov(n, d).cdf; - } - - /* - * Derivative of smirnov(n, d) - * One interior point of discontinuity at d=1/n. - */ - XSF_HOST_DEVICE inline double smirnovp(int n, double d) { - if (!(n > 0 && d >= 0.0 && d <= 1.0)) { - return (std::numeric_limits::quiet_NaN()); - } - if (n == 1) { - /* Slope is always -1 for n=1, even at d = 1.0 */ - return -1.0; - } - if (d == 1.0) { - return -0.0; - } - /* - * If d is 0, the derivative is discontinuous, but approaching - * from the right the limit is -1 - */ - if (d == 0.0) { - return -1.0; - } - return -detail::_smirnov(n, d).pdf; - } - - XSF_HOST_DEVICE inline double smirnovi(int n, double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnovi(n, p, 1 - p); - } - - XSF_HOST_DEVICE inline double smirnovci(int n, double p) { - if (std::isnan(p)) { - return std::numeric_limits::quiet_NaN(); - } - return detail::_smirnovi(n, 1 - p, p); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/lanczos.h b/scipy/special/xsf/cephes/lanczos.h deleted file mode 100644 index a8cbbe1d693f..000000000000 --- a/scipy/special/xsf/cephes/lanczos.h +++ /dev/null @@ -1,112 +0,0 @@ -/* (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ - -/* Both lanczos.h and lanczos.c were formed from Boost's lanczos.hpp - * - * Scipy changes: - * - 06-22-2016: Removed all code not related to double precision and - * ported to c for use in Cephes. Note that the order of the - * coefficients is reversed to match the behavior of polevl. - */ - -/* - * Optimal values for G for each N are taken from - * https://web.viu.ca/pughg/phdThesis/phdThesis.pdf, - * as are the theoretical error bounds. - * - * Constants calculated using the method described by Godfrey - * https://my.fit.edu/~gabdo/gamma.txt and elaborated by Toth at - * https://www.rskey.org/gamma.htm using NTL::RR at 1000 bit precision. - */ - -/* - * Lanczos Coefficients for N=13 G=6.024680040776729583740234375 - * Max experimental error (with arbitrary precision arithmetic) 1.196214e-17 - * Generated with compiler: Microsoft Visual C++ version 8.0 on Win32 at Mar 23 2006 - * - * Use for double precision. - */ - -#pragma once - -#include "../config.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double lanczos_num[] = { - 2.506628274631000270164908177133837338626, 210.8242777515793458725097339207133627117, - 8071.672002365816210638002902272250613822, 186056.2653952234950402949897160456992822, - 2876370.628935372441225409051620849613599, 31426415.58540019438061423162831820536287, - 248874557.8620541565114603864132294232163, 1439720407.311721673663223072794912393972, - 6039542586.35202800506429164430729792107, 17921034426.03720969991975575445893111267, - 35711959237.35566804944018545154716670596, 42919803642.64909876895789904700198885093, - 23531376880.41075968857200767445163675473}; - - constexpr double lanczos_denom[] = {1, 66, 1925, 32670, 357423, 2637558, 13339535, - 45995730, 105258076, 150917976, 120543840, 39916800, 0}; - - constexpr double lanczos_sum_expg_scaled_num[] = { - 0.006061842346248906525783753964555936883222, 0.5098416655656676188125178644804694509993, - 19.51992788247617482847860966235652136208, 449.9445569063168119446858607650988409623, - 6955.999602515376140356310115515198987526, 75999.29304014542649875303443598909137092, - 601859.6171681098786670226533699352302507, 3481712.15498064590882071018964774556468, - 14605578.08768506808414169982791359218571, 43338889.32467613834773723740590533316085, - 86363131.28813859145546927288977868422342, 103794043.1163445451906271053616070238554, - 56906521.91347156388090791033559122686859}; - - constexpr double lanczos_sum_expg_scaled_denom[] = { - 1, 66, 1925, 32670, 357423, 2637558, 13339535, 45995730, 105258076, 150917976, 120543840, 39916800, 0}; - - constexpr double lanczos_sum_near_1_d[] = { - 0.3394643171893132535170101292240837927725e-9, -0.2499505151487868335680273909354071938387e-8, - 0.8690926181038057039526127422002498960172e-8, -0.1933117898880828348692541394841204288047e-7, - 0.3075580174791348492737947340039992829546e-7, -0.2752907702903126466004207345038327818713e-7, - -0.1515973019871092388943437623825208095123e-5, 0.004785200610085071473880915854204301886437, - -0.1993758927614728757314233026257810172008, 1.483082862367253753040442933770164111678, - -3.327150580651624233553677113928873034916, 2.208709979316623790862569924861841433016}; - - constexpr double lanczos_sum_near_2_d[] = { - 0.1009141566987569892221439918230042368112e-8, -0.7430396708998719707642735577238449585822e-8, - 0.2583592566524439230844378948704262291927e-7, -0.5746670642147041587497159649318454348117e-7, - 0.9142922068165324132060550591210267992072e-7, -0.8183698410724358930823737982119474130069e-7, - -0.4506604409707170077136555010018549819192e-5, 0.01422519127192419234315002746252160965831, - -0.5926941084905061794445733628891024027949, 4.408830289125943377923077727900630927902, - -9.8907772644920670589288081640128194231, 6.565936202082889535528455955485877361223}; - - XSF_HOST_DEVICE double lanczos_sum(double x) { return ratevl(x, lanczos_num, 12, lanczos_denom, 12); } - - XSF_HOST_DEVICE double lanczos_sum_near_1(double dx) { - double result = 0; - unsigned k; - - for (k = 1; k <= 12; ++k) { - result += (-lanczos_sum_near_1_d[k - 1] * dx) / (k * dx + k * k); - } - return result; - } - - XSF_HOST_DEVICE double lanczos_sum_near_2(double dx) { - double result = 0; - double x = dx + 2; - unsigned k; - - for (k = 1; k <= 12; ++k) { - result += (-lanczos_sum_near_2_d[k - 1] * dx) / (x + k * x + k * k - 1); - } - return result; - } - } // namespace detail - - constexpr double lanczos_g = 6.024680040776729583740234375; - XSF_HOST_DEVICE double lanczos_sum_expg_scaled(double x) { - return ratevl(x, detail::lanczos_sum_expg_scaled_num, 12, detail::lanczos_sum_expg_scaled_denom, 12); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/nbdtr.h b/scipy/special/xsf/cephes/nbdtr.h deleted file mode 100644 index 9fc50d35cb81..000000000000 --- a/scipy/special/xsf/cephes/nbdtr.h +++ /dev/null @@ -1,218 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* nbdtr.c - * - * Negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtr(); - * - * y = nbdtr( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms 0 through k of the negative - * binomial distribution: - * - * k - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=0 - * - * In a sequence of Bernoulli trials, this is the probability - * that k or fewer failures precede the nth success. - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtr( k, n, p ) = incbet( n, k+1, p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.7e-13 8.8e-15 - * See also incbet.c. - * - */ -/* nbdtrc.c - * - * Complemented negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtrc(); - * - * y = nbdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the negative - * binomial distribution: - * - * inf - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtrc( k, n, p ) = incbet( k+1, n, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * Tested at random points (a,b,p), with p between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.7e-13 8.8e-15 - * See also incbet.c. - */ - -/* nbdtrc - * - * Complemented negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtrc(); - * - * y = nbdtrc( k, n, p ); - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the negative - * binomial distribution: - * - * inf - * -- ( n+j-1 ) n j - * > ( ) p (1-p) - * -- ( j ) - * j=k+1 - * - * The terms are not computed individually; instead the incomplete - * beta integral is employed, according to the formula - * - * y = nbdtrc( k, n, p ) = incbet( k+1, n, 1-p ). - * - * The arguments must be positive, with p ranging from 0 to 1. - * - * ACCURACY: - * - * See incbet.c. - */ -/* nbdtri - * - * Functional inverse of negative binomial distribution - * - * - * - * SYNOPSIS: - * - * int k, n; - * double p, y, nbdtri(); - * - * p = nbdtri( k, n, y ); - * - * DESCRIPTION: - * - * Finds the argument p such that nbdtr(k,n,p) is equal to y. - * - * ACCURACY: - * - * Tested at random points (a,b,y), with y between 0 and 1. - * - * a,b Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,100 100000 1.5e-14 8.5e-16 - * See also incbi.c. - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "incbet.h" -#include "incbi.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double nbdtrc(int k, int n, double p) { - double dk, dn; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - dk = k + 1; - dn = n; - return (incbet(dk, dn, 1.0 - p)); - } - - XSF_HOST_DEVICE inline double nbdtr(int k, int n, double p) { - double dk, dn; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - dk = k + 1; - dn = n; - return (incbet(dn, dk, p)); - } - - XSF_HOST_DEVICE inline double nbdtri(int k, int n, double p) { - double dk, dn, w; - - if ((p < 0.0) || (p > 1.0)) { - goto domerr; - } - if (k < 0) { - domerr: - set_error("nbdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - dk = k + 1; - dn = n; - w = incbi(dn, dk, p); - return (w); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ndtr.h b/scipy/special/xsf/cephes/ndtr.h deleted file mode 100644 index a3611d26ba44..000000000000 --- a/scipy/special/xsf/cephes/ndtr.h +++ /dev/null @@ -1,275 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ndtr.c - * - * Normal distribution function - * - * - * - * SYNOPSIS: - * - * double x, y, ndtr(); - * - * y = ndtr( x ); - * - * - * - * DESCRIPTION: - * - * Returns the area under the Gaussian probability density - * function, integrated from minus infinity to x: - * - * x - * - - * 1 | | 2 - * ndtr(x) = --------- | exp( - t /2 ) dt - * sqrt(2pi) | | - * - - * -inf. - * - * = ( 1 + erf(z) ) / 2 - * = erfc(z) / 2 - * - * where z = x/sqrt(2). Computation is via the functions - * erf and erfc. - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -13,0 30000 3.4e-14 6.7e-15 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * erfc underflow x > 37.519379347 0.0 - * - */ -/* erf.c - * - * Error function - * - * - * - * SYNOPSIS: - * - * double x, y, erf(); - * - * y = erf( x ); - * - * - * - * DESCRIPTION: - * - * The integral is - * - * x - * - - * 2 | | 2 - * erf(x) = -------- | exp( - t ) dt. - * sqrt(pi) | | - * - - * 0 - * - * For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise - * erf(x) = 1 - erfc(x). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,1 30000 3.7e-16 1.0e-16 - * - */ -/* erfc.c - * - * Complementary error function - * - * - * - * SYNOPSIS: - * - * double x, y, erfc(); - * - * y = erfc( x ); - * - * - * - * DESCRIPTION: - * - * - * 1 - erf(x) = - * - * inf. - * - - * 2 | | 2 - * erfc(x) = -------- | exp( - t ) dt - * sqrt(pi) | | - * - - * x - * - * - * For small x, erfc(x) = 1 - erf(x); otherwise rational - * approximations are computed. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,26.6417 30000 5.7e-14 1.5e-14 - */ - -/* - * Cephes Math Library Release 2.2: June, 1992 - * Copyright 1984, 1987, 1988, 1992 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double ndtr_P[] = {2.46196981473530512524E-10, 5.64189564831068821977E-1, 7.46321056442269912687E0, - 4.86371970985681366614E1, 1.96520832956077098242E2, 5.26445194995477358631E2, - 9.34528527171957607540E2, 1.02755188689515710272E3, 5.57535335369399327526E2}; - - constexpr double ndtr_Q[] = { - /* 1.00000000000000000000E0, */ - 1.32281951154744992508E1, 8.67072140885989742329E1, 3.54937778887819891062E2, 9.75708501743205489753E2, - 1.82390916687909736289E3, 2.24633760818710981792E3, 1.65666309194161350182E3, 5.57535340817727675546E2}; - - constexpr double ndtr_R[] = {5.64189583547755073984E-1, 1.27536670759978104416E0, 5.01905042251180477414E0, - 6.16021097993053585195E0, 7.40974269950448939160E0, 2.97886665372100240670E0}; - - constexpr double ndtr_S[] = { - /* 1.00000000000000000000E0, */ - 2.26052863220117276590E0, 9.39603524938001434673E0, 1.20489539808096656605E1, - 1.70814450747565897222E1, 9.60896809063285878198E0, 3.36907645100081516050E0}; - - constexpr double ndtr_T[] = {9.60497373987051638749E0, 9.00260197203842689217E1, 2.23200534594684319226E3, - 7.00332514112805075473E3, 5.55923013010394962768E4}; - - constexpr double ndtr_U[] = { - /* 1.00000000000000000000E0, */ - 3.35617141647503099647E1, 5.21357949780152679795E2, 4.59432382970980127987E3, 2.26290000613890934246E4, - 4.92673942608635921086E4}; - - constexpr double ndtri_UTHRESH = 37.519379347; - - } // namespace detail - - XSF_HOST_DEVICE inline double erf(double x); - - XSF_HOST_DEVICE inline double erfc(double a) { - double p, q, x, y, z; - - if (std::isnan(a)) { - set_error("erfc", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (a < 0.0) { - x = -a; - } else { - x = a; - } - - if (x < 1.0) { - return 1.0 - erf(a); - } - - z = -a * a; - - if (z < -detail::MAXLOG) { - goto under; - } - - z = std::exp(z); - - if (x < 8.0) { - p = polevl(x, detail::ndtr_P, 8); - q = p1evl(x, detail::ndtr_Q, 8); - } else { - p = polevl(x, detail::ndtr_R, 5); - q = p1evl(x, detail::ndtr_S, 6); - } - y = (z * p) / q; - - if (a < 0) { - y = 2.0 - y; - } - - if (y != 0.0) { - return y; - } - - under: - set_error("erfc", SF_ERROR_UNDERFLOW, NULL); - if (a < 0) { - return 2.0; - } else { - return 0.0; - } - } - - XSF_HOST_DEVICE inline double erf(double x) { - double y, z; - - if (std::isnan(x)) { - set_error("erf", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0.0) { - return -erf(-x); - } - - if (std::abs(x) > 1.0) { - return (1.0 - erfc(x)); - } - z = x * x; - - y = x * polevl(z, detail::ndtr_T, 4) / p1evl(z, detail::ndtr_U, 5); - return y; - } - - XSF_HOST_DEVICE inline double ndtr(double a) { - double x, y, z; - - if (std::isnan(a)) { - set_error("ndtr", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - x = a * M_SQRT1_2; - z = std::abs(x); - - if (z < 1.0) { - y = 0.5 + 0.5 * erf(x); - } else { - y = 0.5 * erfc(z); - if (x > 0) { - y = 1.0 - y; - } - } - - return y; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/ndtri.h b/scipy/special/xsf/cephes/ndtri.h deleted file mode 100644 index 49f25833f3e0..000000000000 --- a/scipy/special/xsf/cephes/ndtri.h +++ /dev/null @@ -1,160 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* ndtri.c - * - * Inverse of Normal distribution function - * - * - * - * SYNOPSIS: - * - * double x, y, ndtri(); - * - * x = ndtri( y ); - * - * - * - * DESCRIPTION: - * - * Returns the argument, x, for which the area under the - * Gaussian probability density function (integrated from - * minus infinity to x) is equal to y. - * - * - * For small arguments 0 < y < exp(-2), the program computes - * z = sqrt( -2.0 * log(y) ); then the approximation is - * x = z - log(z)/z - (1/z) P(1/z) / Q(1/z). - * There are two rational functions P/Q, one for 0 < y < exp(-32) - * and the other for y up to exp(-2). For larger arguments, - * w = y - 0.5, and x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)). - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0.125, 1 20000 7.2e-16 1.3e-16 - * IEEE 3e-308, 0.135 50000 4.6e-16 9.8e-17 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * ndtri domain x < 0 NAN - * ndtri domain x > 1 NAN - * - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* approximation for 0 <= |y - 0.5| <= 3/8 */ - constexpr double ndtri_P0[5] = { - -5.99633501014107895267E1, 9.80010754185999661536E1, -5.66762857469070293439E1, - 1.39312609387279679503E1, -1.23916583867381258016E0, - }; - - constexpr double ndtri_Q0[8] = { - /* 1.00000000000000000000E0, */ - 1.95448858338141759834E0, 4.67627912898881538453E0, 8.63602421390890590575E1, -2.25462687854119370527E2, - 2.00260212380060660359E2, -8.20372256168333339912E1, 1.59056225126211695515E1, -1.18331621121330003142E0, - }; - - /* Approximation for interval z = sqrt(-2 log y ) between 2 and 8 - * i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14. - */ - constexpr double ndtri_P1[9] = { - 4.05544892305962419923E0, 3.15251094599893866154E1, 5.71628192246421288162E1, - 4.40805073893200834700E1, 1.46849561928858024014E1, 2.18663306850790267539E0, - -1.40256079171354495875E-1, -3.50424626827848203418E-2, -8.57456785154685413611E-4, - }; - - constexpr double ndtri_Q1[8] = { - /* 1.00000000000000000000E0, */ - 1.57799883256466749731E1, 4.53907635128879210584E1, 4.13172038254672030440E1, - 1.50425385692907503408E1, 2.50464946208309415979E0, -1.42182922854787788574E-1, - -3.80806407691578277194E-2, -9.33259480895457427372E-4, - }; - - /* Approximation for interval z = sqrt(-2 log y ) between 8 and 64 - * i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890. - */ - - constexpr double ndtri_P2[9] = { - 3.23774891776946035970E0, 6.91522889068984211695E0, 3.93881025292474443415E0, - 1.33303460815807542389E0, 2.01485389549179081538E-1, 1.23716634817820021358E-2, - 3.01581553508235416007E-4, 2.65806974686737550832E-6, 6.23974539184983293730E-9, - }; - - constexpr double ndtri_Q2[8] = { - /* 1.00000000000000000000E0, */ - 6.02427039364742014255E0, 3.67983563856160859403E0, 1.37702099489081330271E0, 2.16236993594496635890E-1, - 1.34204006088543189037E-2, 3.28014464682127739104E-4, 2.89247864745380683936E-6, 6.79019408009981274425E-9, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double ndtri(double y0) { - double x, y, z, y2, x0, x1; - int code; - - if (y0 == 0.0) { - return -std::numeric_limits::infinity(); - } - if (y0 == 1.0) { - return std::numeric_limits::infinity(); - } - if (y0 < 0.0 || y0 > 1.0) { - set_error("ndtri", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - code = 1; - y = y0; - if (y > (1.0 - 0.13533528323661269189)) { /* 0.135... = exp(-2) */ - y = 1.0 - y; - code = 0; - } - - if (y > 0.13533528323661269189) { - y = y - 0.5; - y2 = y * y; - x = y + y * (y2 * polevl(y2, detail::ndtri_P0, 4) / p1evl(y2, detail::ndtri_Q0, 8)); - x = x * detail::SQRTPI; - return (x); - } - - x = std::sqrt(-2.0 * std::log(y)); - x0 = x - std::log(x) / x; - - z = 1.0 / x; - if (x < 8.0) { /* y > exp(-32) = 1.2664165549e-14 */ - x1 = z * polevl(z, detail::ndtri_P1, 8) / p1evl(z, detail::ndtri_Q1, 8); - } else { - x1 = z * polevl(z, detail::ndtri_P2, 8) / p1evl(z, detail::ndtri_Q2, 8); - } - x = x0 - x1; - if (code != 0) { - x = -x; - } - return (x); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/owens_t.h b/scipy/special/xsf/cephes/owens_t.h deleted file mode 100644 index 3a30c24a1dfb..000000000000 --- a/scipy/special/xsf/cephes/owens_t.h +++ /dev/null @@ -1,352 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* Copyright Benjamin Sobotta 2012 - * - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ - -/* - * Reference: - * Mike Patefield, David Tandy - * FAST AND ACCURATE CALCULATION OF OWEN'S T-FUNCTION - * Journal of Statistical Software, 5 (5), 1-25 - */ -#pragma once - -#include "../config.h" - -#include "ndtr.h" -#include "unity.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int owens_t_SELECT_METHOD[] = { - 0, 0, 1, 12, 12, 12, 12, 12, 12, 12, 12, 15, 15, 15, 8, 0, 1, 1, 2, 2, 4, 4, 13, 13, - 14, 14, 15, 15, 15, 8, 1, 1, 2, 2, 2, 4, 4, 14, 14, 14, 14, 15, 15, 15, 9, 1, 1, 2, - 4, 4, 4, 4, 6, 6, 15, 15, 15, 15, 15, 9, 1, 2, 2, 4, 4, 5, 5, 7, 7, 16, 16, 16, - 11, 11, 10, 1, 2, 4, 4, 4, 5, 5, 7, 7, 16, 16, 16, 11, 11, 11, 1, 2, 3, 3, 5, 5, - 7, 7, 16, 16, 16, 16, 16, 11, 11, 1, 2, 3, 3, 5, 5, 17, 17, 17, 17, 16, 16, 16, 11, 11}; - - constexpr double owens_t_HRANGE[] = {0.02, 0.06, 0.09, 0.125, 0.26, 0.4, 0.6, - 1.6, 1.7, 2.33, 2.4, 3.36, 3.4, 4.8}; - - constexpr double owens_t_ARANGE[] = {0.025, 0.09, 0.15, 0.36, 0.5, 0.9, 0.99999}; - - constexpr double owens_t_ORD[] = {2, 3, 4, 5, 7, 10, 12, 18, 10, 20, 30, 0, 4, 7, 8, 20, 0, 0}; - - constexpr int owens_t_METHODS[] = {1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5, 6}; - - constexpr double owens_t_C[] = { - 1.0, - -1.0, - 1.0, - -0.9999999999999998, - 0.9999999999999839, - -0.9999999999993063, - 0.9999999999797337, - -0.9999999995749584, - 0.9999999933226235, - -0.9999999188923242, - 0.9999992195143483, - -0.9999939351372067, - 0.9999613559769055, - -0.9997955636651394, - 0.9990927896296171, - -0.9965938374119182, - 0.9891001713838613, - -0.9700785580406933, - 0.9291143868326319, - -0.8542058695956156, - 0.737965260330301, - -0.585234698828374, - 0.4159977761456763, - -0.25882108752419436, - 0.13755358251638927, - -0.060795276632595575, - 0.021633768329987153, - -0.005934056934551867, - 0.0011743414818332946, - -0.0001489155613350369, - 9.072354320794358e-06, - }; - - constexpr double owens_t_PTS[] = { - 0.35082039676451715489E-02, 0.31279042338030753740E-01, 0.85266826283219451090E-01, - 0.16245071730812277011E+00, 0.25851196049125434828E+00, 0.36807553840697533536E+00, - 0.48501092905604697475E+00, 0.60277514152618576821E+00, 0.71477884217753226516E+00, - 0.81475510988760098605E+00, 0.89711029755948965867E+00, 0.95723808085944261843E+00, - 0.99178832974629703586E+00}; - - constexpr double owens_t_WTS[] = { - 0.18831438115323502887E-01, 0.18567086243977649478E-01, 0.18042093461223385584E-01, - 0.17263829606398753364E-01, 0.16243219975989856730E-01, 0.14994592034116704829E-01, - 0.13535474469662088392E-01, 0.11886351605820165233E-01, 0.10070377242777431897E-01, - 0.81130545742299586629E-02, 0.60419009528470238773E-02, 0.38862217010742057883E-02, - 0.16793031084546090448E-02}; - - XSF_HOST_DEVICE inline int get_method(double h, double a) { - int ihint, iaint, i; - - ihint = 14; - iaint = 7; - - for (i = 0; i < 14; i++) { - if (h <= owens_t_HRANGE[i]) { - ihint = i; - break; - } - } - - for (i = 0; i < 7; i++) { - if (a <= owens_t_ARANGE[i]) { - iaint = i; - break; - } - } - return owens_t_SELECT_METHOD[iaint * 15 + ihint]; - } - - XSF_HOST_DEVICE inline double owens_t_norm1(double x) { return xsf::cephes::erf(x / std::sqrt(2)) / 2; } - - XSF_HOST_DEVICE inline double owens_t_norm2(double x) { - return xsf::cephes::erfc(x / std::sqrt(2)) / 2; - } - - XSF_HOST_DEVICE inline double owensT1(double h, double a, double m) { - int j = 1; - int jj = 1; - - double hs = -0.5 * h * h; - double dhs = std::exp(hs); - double as = a * a; - double aj = a / (2 * M_PI); - double dj = xsf::cephes::expm1(hs); - double gj = hs * dhs; - - double val = std::atan(a) / (2 * M_PI); - - while (1) { - val += dj * aj / jj; - - if (m <= j) { - break; - } - j++; - jj += 2; - aj *= as; - dj = gj - dj; - gj *= hs / j; - } - - return val; - } - - XSF_HOST_DEVICE inline double owensT2(double h, double a, double ah, double m) { - int i = 1; - int maxi = 2 * m + 1; - double hs = h * h; - double as = -a * a; - double y = 1.0 / hs; - double val = 0.0; - double vi = a * std::exp(-0.5 * ah * ah) / std::sqrt(2 * M_PI); - double z = (xsf::cephes::ndtr(ah) - 0.5) / h; - - while (1) { - val += z; - if (maxi <= i) { - break; - } - z = y * (vi - i * z); - vi *= as; - i += 2; - } - val *= std::exp(-0.5 * hs) / std::sqrt(2 * M_PI); - - return val; - } - - XSF_HOST_DEVICE inline double owensT3(double h, double a, double ah) { - double aa, hh, y, vi, zi, result; - int i; - - aa = a * a; - hh = h * h; - y = 1 / hh; - - vi = a * std::exp(-ah * ah / 2) / std::sqrt(2 * M_PI); - zi = owens_t_norm1(ah) / h; - result = 0; - - for (i = 0; i <= 30; i++) { - result += zi * owens_t_C[i]; - zi = y * ((2 * i + 1) * zi - vi); - vi *= aa; - } - - result *= std::exp(-hh / 2) / std::sqrt(2 * M_PI); - - return result; - } - - XSF_HOST_DEVICE inline double owensT4(double h, double a, double m) { - double maxi, hh, naa, ai, yi, result; - int i; - - maxi = 2 * m + 1; - hh = h * h; - naa = -a * a; - - i = 1; - ai = a * std::exp(-hh * (1 - naa) / 2) / (2 * M_PI); - yi = 1; - result = 0; - - while (1) { - result += ai * yi; - - if (maxi <= i) { - break; - } - - i += 2; - yi = (1 - hh * yi) / i; - ai *= naa; - } - - return result; - } - - XSF_HOST_DEVICE inline double owensT5(double h, double a) { - double result, r, aa, nhh; - int i; - - result = 0; - r = 0; - aa = a * a; - nhh = -0.5 * h * h; - - for (i = 1; i < 14; i++) { - r = 1 + aa * owens_t_PTS[i - 1]; - result += owens_t_WTS[i - 1] * std::exp(nhh * r) / r; - } - - result *= a; - - return result; - } - - XSF_HOST_DEVICE inline double owensT6(double h, double a) { - double normh, y, r, result; - - normh = owens_t_norm2(h); - y = 1 - a; - r = std::atan2(y, (1 + a)); - result = normh * (1 - normh) / 2; - - if (r != 0) { - result -= r * std::exp(-y * h * h / (2 * r)) / (2 * M_PI); - } - - return result; - } - - XSF_HOST_DEVICE inline double owens_t_dispatch(double h, double a, double ah) { - int index, meth_code; - double m, result; - - if (h == 0) { - return std::atan(a) / (2 * M_PI); - } - if (a == 0) { - return 0; - } - if (a == 1) { - return owens_t_norm2(-h) * owens_t_norm2(h) / 2; - } - - index = get_method(h, a); - m = owens_t_ORD[index]; - meth_code = owens_t_METHODS[index]; - - switch (meth_code) { - case 1: - result = owensT1(h, a, m); - break; - case 2: - result = owensT2(h, a, ah, m); - break; - case 3: - result = owensT3(h, a, ah); - break; - case 4: - result = owensT4(h, a, m); - break; - case 5: - result = owensT5(h, a); - break; - case 6: - result = owensT6(h, a); - break; - default: - result = std::numeric_limits::quiet_NaN(); - } - - return result; - } - - } // namespace detail - - XSF_HOST_DEVICE inline double owens_t(double h, double a) { - double result, fabs_a, fabs_ah, normh, normah; - - if (std::isnan(h) || std::isnan(a)) { - return std::numeric_limits::quiet_NaN(); - } - - /* exploit that T(-h,a) == T(h,a) */ - h = std::abs(h); - - /* - * Use equation (2) in the paper to remap the arguments such that - * h >= 0 and 0 <= a <= 1 for the call of the actual computation - * routine. - */ - fabs_a = std::abs(a); - fabs_ah = fabs_a * h; - - if (fabs_a == std::numeric_limits::infinity()) { - /* See page 13 in the paper */ - result = 0.5 * detail::owens_t_norm2(h); - } else if (h == std::numeric_limits::infinity()) { - result = 0; - } else if (fabs_a <= 1) { - result = detail::owens_t_dispatch(h, fabs_a, fabs_ah); - } else { - if (fabs_ah <= 0.67) { - normh = detail::owens_t_norm1(h); - normah = detail::owens_t_norm1(fabs_ah); - result = 0.25 - normh * normah - detail::owens_t_dispatch(fabs_ah, (1 / fabs_a), h); - } else { - normh = detail::owens_t_norm2(h); - normah = detail::owens_t_norm2(fabs_ah); - result = (normh + normah) / 2 - normh * normah - detail::owens_t_dispatch(fabs_ah, (1 / fabs_a), h); - } - } - - if (a < 0) { - /* exploit that T(h,-a) == -T(h,a) */ - return -result; - } - - return result; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/pdtr.h b/scipy/special/xsf/cephes/pdtr.h deleted file mode 100644 index 909e5eb1f90d..000000000000 --- a/scipy/special/xsf/cephes/pdtr.h +++ /dev/null @@ -1,183 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* pdtr.c - * - * Poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtr(); - * - * y = pdtr( k, m ); - * - * - * - * DESCRIPTION: - * - * Returns the sum of the first k terms of the Poisson - * distribution: - * - * k j - * -- -m m - * > e -- - * -- j! - * j=0 - * - * The terms are not summed directly; instead the incomplete - * Gamma integral is employed, according to the relation - * - * y = pdtr( k, m ) = igamc( k+1, m ). - * - * The arguments must both be nonnegative. - * - * - * - * ACCURACY: - * - * See igamc(). - * - */ -/* pdtrc() - * - * Complemented poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtrc(); - * - * y = pdtrc( k, m ); - * - * - * - * DESCRIPTION: - * - * Returns the sum of the terms k+1 to infinity of the Poisson - * distribution: - * - * inf. j - * -- -m m - * > e -- - * -- j! - * j=k+1 - * - * The terms are not summed directly; instead the incomplete - * Gamma integral is employed, according to the formula - * - * y = pdtrc( k, m ) = igam( k+1, m ). - * - * The arguments must both be nonnegative. - * - * - * - * ACCURACY: - * - * See igam.c. - * - */ -/* pdtri() - * - * Inverse Poisson distribution - * - * - * - * SYNOPSIS: - * - * int k; - * double m, y, pdtr(); - * - * m = pdtri( k, y ); - * - * - * - * - * DESCRIPTION: - * - * Finds the Poisson variable x such that the integral - * from 0 to x of the Poisson density is equal to the - * given probability y. - * - * This is accomplished using the inverse Gamma integral - * function and the relation - * - * m = igamci( k+1, y ). - * - * - * - * - * ACCURACY: - * - * See igami.c. - * - * ERROR MESSAGES: - * - * message condition value returned - * pdtri domain y < 0 or y >= 1 0.0 - * k < 0 - * - */ - -/* - * Cephes Math Library Release 2.3: March, 1995 - * Copyright 1984, 1987, 1995 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "igam.h" -#include "igami.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double pdtrc(double k, double m) { - double v; - - if (k < 0.0 || m < 0.0) { - set_error("pdtrc", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (m == 0.0) { - return 0.0; - } - v = std::floor(k) + 1; - return (igam(v, m)); - } - - XSF_HOST_DEVICE inline double pdtr(double k, double m) { - double v; - - if (k < 0 || m < 0) { - set_error("pdtr", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - if (m == 0.0) { - return 1.0; - } - v = std::floor(k) + 1; - return (igamc(v, m)); - } - - XSF_HOST_DEVICE inline double pdtri(int k, double y) { - double v; - - if ((k < 0) || (y < 0.0) || (y >= 1.0)) { - set_error("pdtri", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - v = k + 1; - v = igamci(v, y); - return (v); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/poch.h b/scipy/special/xsf/cephes/poch.h deleted file mode 100644 index add3a995f388..000000000000 --- a/scipy/special/xsf/cephes/poch.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Pochhammer symbol (a)_m = gamma(a + m) / gamma(a) - */ - -#pragma once - -#include "../config.h" -#include "gamma.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - XSF_HOST_DEVICE inline double is_nonpos_int(double x) { - return x <= 0 && x == std::ceil(x) && std::abs(x) < 1e13; - } - } // namespace detail - - XSF_HOST_DEVICE inline double poch(double a, double m) { - double r = 1.0; - - /* - * 1. Reduce magnitude of `m` to |m| < 1 by using recurrence relations. - * - * This may end up in over/underflow, but then the function itself either - * diverges or goes to zero. In case the remainder goes to the opposite - * direction, we end up returning 0*INF = NAN, which is OK. - */ - - /* Recurse down */ - while (m >= 1.0) { - if (a + m == 1) { - break; - } - m -= 1.0; - r *= (a + m); - if (!std::isfinite(r) || r == 0) { - break; - } - } - - /* Recurse up */ - while (m <= -1.0) { - if (a + m == 0) { - break; - } - r /= (a + m); - m += 1.0; - if (!std::isfinite(r) || r == 0) { - break; - } - } - - /* - * 2. Evaluate function with reduced `m` - * - * Now either `m` is not big, or the `r` product has over/underflown. - * If so, the function itself does similarly. - */ - - if (m == 0) { - /* Easy case */ - return r; - } else if (a > 1e4 && std::abs(m) <= 1) { - /* Avoid loss of precision */ - return r * std::pow(a, m) * - (1 + m * (m - 1) / (2 * a) + m * (m - 1) * (m - 2) * (3 * m - 1) / (24 * a * a) + - m * m * (m - 1) * (m - 1) * (m - 2) * (m - 3) / (48 * a * a * a)); - } - - /* Check for infinity */ - if (detail::is_nonpos_int(a + m) && !detail::is_nonpos_int(a) && a + m != m) { - return std::numeric_limits::infinity(); - } - - /* Check for zero */ - if (!detail::is_nonpos_int(a + m) && detail::is_nonpos_int(a)) { - return 0; - } - - return r * std::exp(lgam(a + m) - lgam(a)) * gammasgn(a + m) * gammasgn(a); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/polevl.h b/scipy/special/xsf/cephes/polevl.h deleted file mode 100644 index 912a506cfb6c..000000000000 --- a/scipy/special/xsf/cephes/polevl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* polevl.c - * p1evl.c - * - * Evaluate polynomial - * - * - * - * SYNOPSIS: - * - * int N; - * double x, y, coef[N+1], polevl[]; - * - * y = polevl( x, coef, N ); - * - * - * - * DESCRIPTION: - * - * Evaluates polynomial of degree N: - * - * 2 N - * y = C + C x + C x +...+ C x - * 0 1 2 N - * - * Coefficients are stored in reverse order: - * - * coef[0] = C , ..., coef[N] = C . - * N 0 - * - * The function p1evl() assumes that c_N = 1.0 so that coefficent - * is omitted from the array. Its calling arguments are - * otherwise the same as polevl(). - * - * - * SPEED: - * - * In the interest of speed, there are no checks for out - * of bounds arithmetic. This routine is used by most of - * the functions in the library. Depending on available - * equipment features, the user may wish to rewrite the - * program in microcode or assembly language. - * - */ - -/* - * Cephes Math Library Release 2.1: December, 1988 - * Copyright 1984, 1987, 1988 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ - -/* Sources: - * [1] Holin et. al., "Polynomial and Rational Function Evaluation", - * https://www.boost.org/doc/libs/1_61_0/libs/math/doc/html/math_toolkit/roots/rational.html - */ - -/* Scipy changes: - * - 06-23-2016: add code for evaluating rational functions - */ - -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - XSF_HOST_DEVICE inline double polevl(double x, const double coef[], int N) { - double ans; - int i; - const double *p; - - p = coef; - ans = *p++; - i = N; - - do { - ans = ans * x + *p++; - } while (--i); - - return (ans); - } - - /* p1evl() */ - /* N - * Evaluate polynomial when coefficient of x is 1.0. - * That is, C_{N} is assumed to be 1, and that coefficient - * is not included in the input array coef. - * coef must have length N and contain the polynomial coefficients - * stored as - * coef[0] = C_{N-1} - * coef[1] = C_{N-2} - * ... - * coef[N-2] = C_1 - * coef[N-1] = C_0 - * Otherwise same as polevl. - */ - - XSF_HOST_DEVICE inline double p1evl(double x, const double coef[], int N) { - double ans; - const double *p; - int i; - - p = coef; - ans = x + *p++; - i = N - 1; - - do - ans = ans * x + *p++; - while (--i); - - return (ans); - } - - /* Evaluate a rational function. See [1]. */ - - /* The function ratevl is only used once in cephes/lanczos.h. */ - XSF_HOST_DEVICE inline double ratevl(double x, const double num[], int M, const double denom[], int N) { - int i, dir; - double y, num_ans, denom_ans; - double absx = std::abs(x); - const double *p; - - if (absx > 1) { - /* Evaluate as a polynomial in 1/x. */ - dir = -1; - p = num + M; - y = 1 / x; - } else { - dir = 1; - p = num; - y = x; - } - - /* Evaluate the numerator */ - num_ans = *p; - p += dir; - for (i = 1; i <= M; i++) { - num_ans = num_ans * y + *p; - p += dir; - } - - /* Evaluate the denominator */ - if (absx > 1) { - p = denom + N; - } else { - p = denom; - } - - denom_ans = *p; - p += dir; - for (i = 1; i <= N; i++) { - denom_ans = denom_ans * y + *p; - p += dir; - } - - if (absx > 1) { - i = M - N; - return std::pow(x, i) * num_ans / denom_ans; - } else { - return num_ans / denom_ans; - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/psi.h b/scipy/special/xsf/cephes/psi.h deleted file mode 100644 index c028e9ea14e0..000000000000 --- a/scipy/special/xsf/cephes/psi.h +++ /dev/null @@ -1,194 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* psi.c - * - * Psi (digamma) function - * - * - * SYNOPSIS: - * - * double x, y, psi(); - * - * y = psi( x ); - * - * - * DESCRIPTION: - * - * d - - * psi(x) = -- ln | (x) - * dx - * - * is the logarithmic derivative of the gamma function. - * For integer x, - * n-1 - * - - * psi(n) = -EUL + > 1/k. - * - - * k=1 - * - * This formula is used for 0 < n <= 10. If x is negative, it - * is transformed to a positive argument by the reflection - * formula psi(1-x) = psi(x) + pi cot(pi x). - * For general positive x, the argument is made greater than 10 - * using the recurrence psi(x+1) = psi(x) + 1/x. - * Then the following asymptotic expansion is applied: - * - * inf. B - * - 2k - * psi(x) = log(x) - 1/2x - > ------- - * - 2k - * k=1 2k x - * - * where the B2k are Bernoulli numbers. - * - * ACCURACY: - * Relative error (except absolute when |psi| < 1): - * arithmetic domain # trials peak rms - * IEEE 0,30 30000 1.3e-15 1.4e-16 - * IEEE -30,0 40000 1.5e-15 2.2e-16 - * - * ERROR MESSAGES: - * message condition value returned - * psi singularity x integer <=0 INFINITY - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 1992, 2000 by Stephen L. Moshier - */ - -/* - * Code for the rational approximation on [1, 2] is: - * - * (C) Copyright John Maddock 2006. - * Use, modification and distribution are subject to the - * Boost Software License, Version 1.0. (See accompanying file - * LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - namespace detail { - constexpr double psi_A[] = {8.33333333333333333333E-2, -2.10927960927960927961E-2, 7.57575757575757575758E-3, - -4.16666666666666666667E-3, 3.96825396825396825397E-3, -8.33333333333333333333E-3, - 8.33333333333333333333E-2}; - - constexpr float psi_Y = 0.99558162689208984f; - - constexpr double psi_root1 = 1569415565.0 / 1073741824.0; - constexpr double psi_root2 = (381566830.0 / 1073741824.0) / 1073741824.0; - constexpr double psi_root3 = 0.9016312093258695918615325266959189453125e-19; - - constexpr double psi_P[] = {-0.0020713321167745952, -0.045251321448739056, -0.28919126444774784, - -0.65031853770896507, -0.32555031186804491, 0.25479851061131551}; - constexpr double psi_Q[] = {-0.55789841321675513e-6, - 0.0021284987017821144, - 0.054151797245674225, - 0.43593529692665969, - 1.4606242909763515, - 2.0767117023730469, - 1.0}; - - XSF_HOST_DEVICE double digamma_imp_1_2(double x) { - /* - * Rational approximation on [1, 2] taken from Boost. - * - * Now for the approximation, we use the form: - * - * digamma(x) = (x - root) * (Y + R(x-1)) - * - * Where root is the location of the positive root of digamma, - * Y is a constant, and R is optimised for low absolute error - * compared to Y. - * - * Maximum Deviation Found: 1.466e-18 - * At double precision, max error found: 2.452e-17 - */ - double r, g; - - g = x - psi_root1; - g -= psi_root2; - g -= psi_root3; - r = xsf::cephes::polevl(x - 1.0, psi_P, 5) / xsf::cephes::polevl(x - 1.0, psi_Q, 6); - - return g * psi_Y + g * r; - } - - XSF_HOST_DEVICE double psi_asy(double x) { - double y, z; - - if (x < 1.0e17) { - z = 1.0 / (x * x); - y = z * xsf::cephes::polevl(z, psi_A, 6); - } else { - y = 0.0; - } - - return std::log(x) - (0.5 / x) - y; - } - } // namespace detail - - XSF_HOST_DEVICE double psi(double x) { - double y = 0.0; - double q, r; - int i, n; - - if (std::isnan(x)) { - return x; - } else if (x == std::numeric_limits::infinity()) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x == 0) { - set_error("psi", SF_ERROR_SINGULAR, NULL); - return std::copysign(std::numeric_limits::infinity(), -x); - } else if (x < 0.0) { - /* argument reduction before evaluating tan(pi * x) */ - r = std::modf(x, &q); - if (r == 0.0) { - set_error("psi", SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::quiet_NaN(); - } - y = -M_PI / std::tan(M_PI * r); - x = 1.0 - x; - } - - /* check for positive integer up to 10 */ - if ((x <= 10.0) && (x == std::floor(x))) { - n = static_cast(x); - for (i = 1; i < n; i++) { - y += 1.0 / i; - } - y -= detail::SCIPY_EULER; - return y; - } - - /* use the recurrence relation to move x into [1, 2] */ - if (x < 1.0) { - y -= 1.0 / x; - x += 1.0; - } else if (x < 10.0) { - while (x > 2.0) { - x -= 1.0; - y += 1.0 / x; - } - } - if ((1.0 <= x) && (x <= 2.0)) { - y += detail::digamma_imp_1_2(x); - return y; - } - - /* x is large, use the asymptotic series */ - y += detail::psi_asy(x); - return y; - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/rgamma.h b/scipy/special/xsf/cephes/rgamma.h deleted file mode 100644 index 97f29b33ab50..000000000000 --- a/scipy/special/xsf/cephes/rgamma.h +++ /dev/null @@ -1,111 +0,0 @@ -/* rgamma.c - * - * Reciprocal Gamma function - * - * - * - * SYNOPSIS: - * - * double x, y, rgamma(); - * - * y = rgamma( x ); - * - * - * - * DESCRIPTION: - * - * Returns one divided by the Gamma function of the argument. - * - * The function is approximated by a Chebyshev expansion in - * the interval [0,1]. Range reduction is by recurrence - * for arguments between -34.034 and +34.84425627277176174. - * 0 is returned for positive arguments outside this - * range. For arguments less than -34.034 the cosecant - * reflection formula is applied; lograrithms are employed - * to avoid unnecessary overflow. - * - * The reciprocal Gamma function has no singularities, - * but overflow and underflow may occur for large arguments. - * These conditions return either INFINITY or 0 with - * appropriate sign. - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE -30,+30 30000 1.1e-15 2.0e-16 - * For arguments less than -34.034 the peak error is on the - * order of 5e-15 (DEC), excepting overflow or underflow. - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "chbevl.h" -#include "const.h" -#include "gamma.h" -#include "trig.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Chebyshev coefficients for reciprocal Gamma function - * in interval 0 to 1. Function is 1/(x Gamma(x)) - 1 - */ - - constexpr double rgamma_R[] = { - 3.13173458231230000000E-17, -6.70718606477908000000E-16, 2.20039078172259550000E-15, - 2.47691630348254132600E-13, -6.60074100411295197440E-12, 5.13850186324226978840E-11, - 1.08965386454418662084E-9, -3.33964630686836942556E-8, 2.68975996440595483619E-7, - 2.96001177518801696639E-6, -8.04814124978471142852E-5, 4.16609138709688864714E-4, - 5.06579864028608725080E-3, -6.41925436109158228810E-2, -4.98558728684003594785E-3, - 1.27546015610523951063E-1}; - - } // namespace detail - - XSF_HOST_DEVICE double rgamma(double x) { - double w, y, z; - - if (x == 0) { - // This case is separate from below to get correct sign for zero. - return x; - } - - if (x < 0 && x == std::floor(x)) { - // Gamma poles. - return 0.0; - } - - if (std::abs(x) > 4.0) { - return 1.0 / Gamma(x); - } - - z = 1.0; - w = x; - - while (w > 1.0) { /* Downward recurrence */ - w -= 1.0; - z *= w; - } - while (w < 0.0) { /* Upward recurrence */ - z /= w; - w += 1.0; - } - if (w == 0.0) /* Nonpositive integer */ - return (0.0); - if (w == 1.0) /* Other integer */ - return (1.0 / z); - - y = w * (1.0 + chbevl(4.0 * w - 2.0, detail::rgamma_R, 16)) / z; - return (y); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/round.h b/scipy/special/xsf/cephes/round.h deleted file mode 100644 index 6e88c5a19747..000000000000 --- a/scipy/special/xsf/cephes/round.h +++ /dev/null @@ -1,74 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* round.c - * - * Round double to nearest or even integer valued double - * - * - * - * SYNOPSIS: - * - * double x, y, round(); - * - * y = round(x); - * - * - * - * DESCRIPTION: - * - * Returns the nearest integer to x as a double precision - * floating point result. If x ends in 0.5 exactly, the - * nearest even integer is chosen. - * - * - * - * ACCURACY: - * - * If x is greater than 1/(2*MACHEP), its closest machine - * representation is already an integer, so rounding does - * not change it. - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - double round(double x) { - double y, r; - - /* Largest integer <= x */ - y = std::floor(x); - - /* Fractional part */ - r = x - y; - - /* Round up to nearest. */ - if (r > 0.5) { - goto rndup; - } - - /* Round to even */ - if (r == 0.5) { - r = y - 2.0 * std::floor(0.5 * y); - if (r == 1.0) { - rndup: - y += 1.0; - } - } - - /* Else round down. */ - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/scipy_iv.h b/scipy/special/xsf/cephes/scipy_iv.h deleted file mode 100644 index fe0c631e3458..000000000000 --- a/scipy/special/xsf/cephes/scipy_iv.h +++ /dev/null @@ -1,811 +0,0 @@ -/* 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::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::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::quiet_NaN(); - if (Kv_p != NULL) - *Kv_p = std::numeric_limits::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::infinity(); - } else { - Kv = std::numeric_limits::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::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::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::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::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::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 diff --git a/scipy/special/xsf/cephes/shichi.h b/scipy/special/xsf/cephes/shichi.h deleted file mode 100644 index fcdd2d798646..000000000000 --- a/scipy/special/xsf/cephes/shichi.h +++ /dev/null @@ -1,248 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* shichi.c - * - * Hyperbolic sine and cosine integrals - * - * - * - * SYNOPSIS: - * - * double x, Chi, Shi, shichi(); - * - * shichi( x, &Chi, &Shi ); - * - * - * DESCRIPTION: - * - * Approximates the integrals - * - * x - * - - * | | cosh t - 1 - * Chi(x) = eul + ln x + | ----------- dt, - * | | t - * - - * 0 - * - * x - * - - * | | sinh t - * Shi(x) = | ------ dt - * | | t - * - - * 0 - * - * where eul = 0.57721566490153286061 is Euler's constant. - * The integrals are evaluated by power series for x < 8 - * and by Chebyshev expansions for x between 8 and 88. - * For large x, both functions approach exp(x)/2x. - * Arguments greater than 88 in magnitude return INFINITY. - * - * - * ACCURACY: - * - * Test interval 0 to 88. - * Relative error: - * arithmetic function # trials peak rms - * IEEE Shi 30000 6.9e-16 1.6e-16 - * Absolute error, except relative when |Chi| > 1: - * IEEE Chi 30000 8.4e-16 1.4e-16 - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "chbevl.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* x exp(-x) shi(x), inverted interval 8 to 18 */ - constexpr double shichi_S1[] = { - 1.83889230173399459482E-17, -9.55485532279655569575E-17, 2.04326105980879882648E-16, - 1.09896949074905343022E-15, -1.31313534344092599234E-14, 5.93976226264314278932E-14, - -3.47197010497749154755E-14, -1.40059764613117131000E-12, 9.49044626224223543299E-12, - -1.61596181145435454033E-11, -1.77899784436430310321E-10, 1.35455469767246947469E-9, - -1.03257121792819495123E-9, -3.56699611114982536845E-8, 1.44818877384267342057E-7, - 7.82018215184051295296E-7, -5.39919118403805073710E-6, -3.12458202168959833422E-5, - 8.90136741950727517826E-5, 2.02558474743846862168E-3, 2.96064440855633256972E-2, - 1.11847751047257036625E0}; - - /* x exp(-x) shi(x), inverted interval 18 to 88 */ - constexpr double shichi_S2[] = { - -1.05311574154850938805E-17, 2.62446095596355225821E-17, 8.82090135625368160657E-17, - -3.38459811878103047136E-16, -8.30608026366935789136E-16, 3.93397875437050071776E-15, - 1.01765565969729044505E-14, -4.21128170307640802703E-14, -1.60818204519802480035E-13, - 3.34714954175994481761E-13, 2.72600352129153073807E-12, 1.66894954752839083608E-12, - -3.49278141024730899554E-11, -1.58580661666482709598E-10, -1.79289437183355633342E-10, - 1.76281629144264523277E-9, 1.69050228879421288846E-8, 1.25391771228487041649E-7, - 1.16229947068677338732E-6, 1.61038260117376323993E-5, 3.49810375601053973070E-4, - 1.28478065259647610779E-2, 1.03665722588798326712E0}; - - /* x exp(-x) chin(x), inverted interval 8 to 18 */ - constexpr double shichi_C1[] = { - -8.12435385225864036372E-18, 2.17586413290339214377E-17, 5.22624394924072204667E-17, - -9.48812110591690559363E-16, 5.35546311647465209166E-15, -1.21009970113732918701E-14, - -6.00865178553447437951E-14, 7.16339649156028587775E-13, -2.93496072607599856104E-12, - -1.40359438136491256904E-12, 8.76302288609054966081E-11, -4.40092476213282340617E-10, - -1.87992075640569295479E-10, 1.31458150989474594064E-8, -4.75513930924765465590E-8, - -2.21775018801848880741E-7, 1.94635531373272490962E-6, 4.33505889257316408893E-6, - -6.13387001076494349496E-5, -3.13085477492997465138E-4, 4.97164789823116062801E-4, - 2.64347496031374526641E-2, 1.11446150876699213025E0}; - - /* x exp(-x) chin(x), inverted interval 18 to 88 */ - constexpr double shichi_C2[] = { - 8.06913408255155572081E-18, -2.08074168180148170312E-17, -5.98111329658272336816E-17, - 2.68533951085945765591E-16, 4.52313941698904694774E-16, -3.10734917335299464535E-15, - -4.42823207332531972288E-15, 3.49639695410806959872E-14, 6.63406731718911586609E-14, - -3.71902448093119218395E-13, -1.27135418132338309016E-12, 2.74851141935315395333E-12, - 2.33781843985453438400E-11, 2.71436006377612442764E-11, -2.56600180000355990529E-10, - -1.61021375163803438552E-9, -4.72543064876271773512E-9, -3.00095178028681682282E-9, - 7.79387474390914922337E-8, 1.06942765566401507066E-6, 1.59503164802313196374E-5, - 3.49592575153777996871E-4, 1.28475387530065247392E-2, 1.03665693917934275131E0}; - - /* - * Evaluate 3F0(a1, a2, a3; z) - * - * The series is only asymptotic, so this requires z large enough. - */ - XSF_HOST_DEVICE inline double hyp3f0(double a1, double a2, double a3, double z) { - int n, maxiter; - double err, sum, term, m; - - m = std::pow(z, -1.0 / 3); - if (m < 50) { - maxiter = m; - } else { - maxiter = 50; - } - - term = 1.0; - sum = term; - for (n = 0; n < maxiter; ++n) { - term *= (a1 + n) * (a2 + n) * (a3 + n) * z / (n + 1); - sum += term; - if (std::abs(term) < 1e-13 * std::abs(sum) || term == 0) { - break; - } - } - - err = std::abs(term); - - if (err > 1e-13 * std::abs(sum)) { - return std::numeric_limits::quiet_NaN(); - } - - return sum; - } - - } // namespace detail - - /* Sine and cosine integrals */ - XSF_HOST_DEVICE inline int shichi(double x, double *si, double *ci) { - double k, z, c, s, a, b; - short sign; - - if (x < 0.0) { - sign = -1; - x = -x; - } else { - sign = 0; - } - - if (x == 0.0) { - *si = 0.0; - *ci = -std::numeric_limits::infinity(); - return (0); - } - - if (x >= 8.0) { - goto chb; - } - - if (x >= 88.0) { - goto asymp; - } - - z = x * x; - - /* Direct power series expansion */ - a = 1.0; - s = 1.0; - c = 0.0; - k = 2.0; - - do { - a *= z / k; - c += a / k; - k += 1.0; - a /= k; - s += a / k; - k += 1.0; - } while (std::abs(a / s) > detail::MACHEP); - - s *= x; - goto done; - - chb: - /* Chebyshev series expansions */ - if (x < 18.0) { - a = (576.0 / x - 52.0) / 10.0; - k = std::exp(x) / x; - s = k * chbevl(a, detail::shichi_S1, 22); - c = k * chbevl(a, detail::shichi_C1, 23); - goto done; - } - - if (x <= 88.0) { - a = (6336.0 / x - 212.0) / 70.0; - k = std::exp(x) / x; - s = k * chbevl(a, detail::shichi_S2, 23); - c = k * chbevl(a, detail::shichi_C2, 24); - goto done; - } - - asymp: - if (x > 1000) { - *si = std::numeric_limits::infinity(); - *ci = std::numeric_limits::infinity(); - } else { - /* Asymptotic expansions - * http://functions.wolfram.com/GammaBetaErf/CoshIntegral/06/02/ - * http://functions.wolfram.com/GammaBetaErf/SinhIntegral/06/02/0001/ - */ - a = detail::hyp3f0(0.5, 1, 1, 4.0 / (x * x)); - b = detail::hyp3f0(1, 1, 1.5, 4.0 / (x * x)); - *si = std::cosh(x) / x * a + std::sinh(x) / (x * x) * b; - *ci = std::sinh(x) / x * a + std::cosh(x) / (x * x) * b; - } - if (sign) { - *si = -*si; - } - return 0; - - done: - if (sign) { - s = -s; - } - - *si = s; - - *ci = detail::SCIPY_EULER + std::log(x) + c; - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/sici.h b/scipy/special/xsf/cephes/sici.h deleted file mode 100644 index c22612ccc9ab..000000000000 --- a/scipy/special/xsf/cephes/sici.h +++ /dev/null @@ -1,224 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* sici.c - * - * Sine and cosine integrals - * - * - * - * SYNOPSIS: - * - * double x, Ci, Si, sici(); - * - * sici( x, &Si, &Ci ); - * - * - * DESCRIPTION: - * - * Evaluates the integrals - * - * x - * - - * | cos t - 1 - * Ci(x) = eul + ln x + | --------- dt, - * | t - * - - * 0 - * x - * - - * | sin t - * Si(x) = | ----- dt - * | t - * - - * 0 - * - * where eul = 0.57721566490153286061 is Euler's constant. - * The integrals are approximated by rational functions. - * For x > 8 auxiliary functions f(x) and g(x) are employed - * such that - * - * Ci(x) = f(x) sin(x) - g(x) cos(x) - * Si(x) = pi/2 - f(x) cos(x) - g(x) sin(x) - * - * - * ACCURACY: - * Test interval = [0,50]. - * Absolute error, except relative when > 1: - * arithmetic function # trials peak rms - * IEEE Si 30000 4.4e-16 7.3e-17 - * IEEE Ci 30000 6.9e-16 5.1e-17 - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double sici_SN[] = { - -8.39167827910303881427E-11, 4.62591714427012837309E-8, -9.75759303843632795789E-6, - 9.76945438170435310816E-4, -4.13470316229406538752E-2, 1.00000000000000000302E0, - }; - - constexpr double sici_SD[] = { - 2.03269266195951942049E-12, 1.27997891179943299903E-9, 4.41827842801218905784E-7, - 9.96412122043875552487E-5, 1.42085239326149893930E-2, 9.99999999999999996984E-1, - }; - - constexpr double sici_CN[] = { - 2.02524002389102268789E-11, -1.35249504915790756375E-8, 3.59325051419993077021E-6, - -4.74007206873407909465E-4, 2.89159652607555242092E-2, -1.00000000000000000080E0, - }; - - constexpr double sici_CD[] = { - 4.07746040061880559506E-12, 3.06780997581887812692E-9, 1.23210355685883423679E-6, - 3.17442024775032769882E-4, 5.10028056236446052392E-2, 4.00000000000000000080E0, - }; - - constexpr double sici_FN4[] = { - 4.23612862892216586994E0, 5.45937717161812843388E0, 1.62083287701538329132E0, 1.67006611831323023771E-1, - 6.81020132472518137426E-3, 1.08936580650328664411E-4, 5.48900223421373614008E-7, - }; - - constexpr double sici_FD4[] = { - /* 1.00000000000000000000E0, */ - 8.16496634205391016773E0, 7.30828822505564552187E0, 1.86792257950184183883E0, 1.78792052963149907262E-1, - 7.01710668322789753610E-3, 1.10034357153915731354E-4, 5.48900252756255700982E-7, - }; - - constexpr double sici_FN8[] = { - 4.55880873470465315206E-1, 7.13715274100146711374E-1, 1.60300158222319456320E-1, - 1.16064229408124407915E-2, 3.49556442447859055605E-4, 4.86215430826454749482E-6, - 3.20092790091004902806E-8, 9.41779576128512936592E-11, 9.70507110881952024631E-14, - }; - - constexpr double sici_FD8[] = { - /* 1.00000000000000000000E0, */ - 9.17463611873684053703E-1, 1.78685545332074536321E-1, 1.22253594771971293032E-2, - 3.58696481881851580297E-4, 4.92435064317881464393E-6, 3.21956939101046018377E-8, - 9.43720590350276732376E-11, 9.70507110881952025725E-14, - }; - - constexpr double sici_GN4[] = { - 8.71001698973114191777E-2, 6.11379109952219284151E-1, 3.97180296392337498885E-1, 7.48527737628469092119E-2, - 5.38868681462177273157E-3, 1.61999794598934024525E-4, 1.97963874140963632189E-6, 7.82579040744090311069E-9, - }; - - constexpr double sici_GD4[] = { - /* 1.00000000000000000000E0, */ - 1.64402202413355338886E0, 6.66296701268987968381E-1, 9.88771761277688796203E-2, 6.22396345441768420760E-3, - 1.73221081474177119497E-4, 2.02659182086343991969E-6, 7.82579218933534490868E-9, - }; - - constexpr double sici_GN8[] = { - 6.97359953443276214934E-1, 3.30410979305632063225E-1, 3.84878767649974295920E-2, - 1.71718239052347903558E-3, 3.48941165502279436777E-5, 3.47131167084116673800E-7, - 1.70404452782044526189E-9, 3.85945925430276600453E-12, 3.14040098946363334640E-15, - }; - - constexpr double sici_GD8[] = { - /* 1.00000000000000000000E0, */ - 1.68548898811011640017E0, 4.87852258695304967486E-1, 4.67913194259625806320E-2, - 1.90284426674399523638E-3, 3.68475504442561108162E-5, 3.57043223443740838771E-7, - 1.72693748966316146736E-9, 3.87830166023954706752E-12, 3.14040098946363335242E-15, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline int sici(double x, double *si, double *ci) { - double z, c, s, f, g; - short sign; - - if (x < 0.0) { - sign = -1; - x = -x; - } else { - sign = 0; - } - - if (x == 0.0) { - *si = 0.0; - *ci = -std::numeric_limits::infinity(); - return (0); - } - - if (x > 1.0e9) { - if (std::isinf(x)) { - if (sign == -1) { - *si = -M_PI_2; - *ci = std::numeric_limits::quiet_NaN(); - } else { - *si = M_PI_2; - *ci = 0; - } - return 0; - } - *si = M_PI_2 - std::cos(x) / x; - *ci = std::sin(x) / x; - } - - if (x > 4.0) { - goto asympt; - } - - z = x * x; - s = x * polevl(z, detail::sici_SN, 5) / polevl(z, detail::sici_SD, 5); - c = z * polevl(z, detail::sici_CN, 5) / polevl(z, detail::sici_CD, 5); - - if (sign) { - s = -s; - } - *si = s; - *ci = detail::SCIPY_EULER + std::log(x) + c; /* real part if x < 0 */ - return (0); - - /* The auxiliary functions are: - * - * - * *si = *si - M_PI_2; - * c = cos(x); - * s = sin(x); - * - * t = *ci * s - *si * c; - * a = *ci * c + *si * s; - * - * *si = t; - * *ci = -a; - */ - - asympt: - - s = std::sin(x); - c = std::cos(x); - z = 1.0 / (x * x); - if (x < 8.0) { - f = polevl(z, detail::sici_FN4, 6) / (x * p1evl(z, detail::sici_FD4, 7)); - g = z * polevl(z, detail::sici_GN4, 7) / p1evl(z, detail::sici_GD4, 7); - } else { - f = polevl(z, detail::sici_FN8, 8) / (x * p1evl(z, detail::sici_FD8, 8)); - g = z * polevl(z, detail::sici_GN8, 8) / p1evl(z, detail::sici_GD8, 9); - } - *si = M_PI_2 - f * c - g * s; - if (sign) { - *si = -(*si); - } - *ci = f * s - g * c; - - return (0); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/sindg.h b/scipy/special/xsf/cephes/sindg.h deleted file mode 100644 index 63adb1698f4c..000000000000 --- a/scipy/special/xsf/cephes/sindg.h +++ /dev/null @@ -1,221 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* sindg.c - * - * Circular sine of angle in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, sindg(); - * - * y = sindg( x ); - * - * - * - * DESCRIPTION: - * - * Range reduction is into intervals of 45 degrees. - * - * Two polynomial approximating functions are employed. - * Between 0 and pi/4 the sine is approximated by - * x + x**3 P(x**2). - * Between pi/4 and pi/2 the cosine is represented as - * 1 - x**2 P(x**2). - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE +-1000 30000 2.3e-16 5.6e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * sindg total loss x > 1.0e14 (IEEE) 0.0 - * - */ -/* cosdg.c - * - * Circular cosine of angle in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, cosdg(); - * - * y = cosdg( x ); - * - * - * - * DESCRIPTION: - * - * Range reduction is into intervals of 45 degrees. - * - * Two polynomial approximating functions are employed. - * Between 0 and pi/4 the cosine is approximated by - * 1 - x**2 P(x**2). - * Between pi/4 and pi/2 the sine is represented as - * x + x**3 P(x**2). - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE +-1000 30000 2.1e-16 5.7e-17 - * See also sin(). - * - */ - -/* Cephes Math Library Release 2.0: April, 1987 - * Copyright 1985, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double sincof[] = {1.58962301572218447952E-10, -2.50507477628503540135E-8, - 2.75573136213856773549E-6, -1.98412698295895384658E-4, - 8.33333333332211858862E-3, -1.66666666666666307295E-1}; - - constexpr double coscof[] = {1.13678171382044553091E-11, -2.08758833757683644217E-9, 2.75573155429816611547E-7, - -2.48015872936186303776E-5, 1.38888888888806666760E-3, -4.16666666666666348141E-2, - 4.99999999999999999798E-1}; - - constexpr double sindg_lossth = 1.0e14; - - } // namespace detail - - XSF_HOST_DEVICE inline double sindg(double x) { - double y, z, zz; - int j, sign; - - /* make argument positive but save the sign */ - sign = 1; - if (x < 0) { - x = -x; - sign = -1; - } - - if (x > detail::sindg_lossth) { - set_error("sindg", SF_ERROR_NO_RESULT, NULL); - return (0.0); - } - - y = std::floor(x / 45.0); /* integer part of x/M_PI_4 */ - - /* strip high bits of integer part to prevent integer overflow */ - z = std::ldexp(y, -4); - z = std::floor(z); /* integer part of y/8 */ - z = y - std::ldexp(z, 4); /* y - 16 * (y/16) */ - - j = z; /* convert to integer for tests on the phase angle */ - /* map zeros to origin */ - if (j & 1) { - j += 1; - y += 1.0; - } - j = j & 07; /* octant modulo 360 degrees */ - /* reflect in x axis */ - if (j > 3) { - sign = -sign; - j -= 4; - } - - z = x - y * 45.0; /* x mod 45 degrees */ - z *= detail::PI180; /* multiply by pi/180 to convert to radians */ - zz = z * z; - - if ((j == 1) || (j == 2)) { - y = 1.0 - zz * polevl(zz, detail::coscof, 6); - } else { - y = z + z * (zz * polevl(zz, detail::sincof, 5)); - } - - if (sign < 0) - y = -y; - - return (y); - } - - XSF_HOST_DEVICE inline double cosdg(double x) { - double y, z, zz; - int j, sign; - - /* make argument positive */ - sign = 1; - if (x < 0) - x = -x; - - if (x > detail::sindg_lossth) { - set_error("cosdg", SF_ERROR_NO_RESULT, NULL); - return (0.0); - } - - y = std::floor(x / 45.0); - z = std::ldexp(y, -4); - z = std::floor(z); /* integer part of y/8 */ - z = y - std::ldexp(z, 4); /* y - 16 * (y/16) */ - - /* integer and fractional part modulo one octant */ - j = z; - if (j & 1) { /* map zeros to origin */ - j += 1; - y += 1.0; - } - j = j & 07; - if (j > 3) { - j -= 4; - sign = -sign; - } - - if (j > 1) - sign = -sign; - - z = x - y * 45.0; /* x mod 45 degrees */ - z *= detail::PI180; /* multiply by pi/180 to convert to radians */ - - zz = z * z; - - if ((j == 1) || (j == 2)) { - y = z + z * (zz * polevl(zz, detail::sincof, 5)); - } else { - y = 1.0 - zz * polevl(zz, detail::coscof, 6); - } - - if (sign < 0) - y = -y; - - return (y); - } - - /* Degrees, minutes, seconds to radians: */ - - /* 1 arc second, in radians = 4.848136811095359935899141023579479759563533023727e-6 */ - - namespace detail { - constexpr double sindg_P64800 = 4.848136811095359935899141023579479759563533023727e-6; - } - - XSF_HOST_DEVICE inline double radian(double d, double m, double s) { - return (((d * 60.0 + m) * 60.0 + s) * detail::sindg_P64800); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/spence.h b/scipy/special/xsf/cephes/spence.h deleted file mode 100644 index 6d908ada9380..000000000000 --- a/scipy/special/xsf/cephes/spence.h +++ /dev/null @@ -1,127 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* spence.c - * - * Dilogarithm - * - * - * - * SYNOPSIS: - * - * double x, y, spence(); - * - * y = spence( x ); - * - * - * - * DESCRIPTION: - * - * Computes the integral - * - * x - * - - * | | log t - * spence(x) = - | ----- dt - * | | t - 1 - * - - * 1 - * - * for x >= 0. A rational approximation gives the integral in - * the interval (0.5, 1.5). Transformation formulas for 1/x - * and 1-x are employed outside the basic expansion range. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,4 30000 3.9e-15 5.4e-16 - * - * - */ - -/* spence.c */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1985, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "polevl.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double spence_A[8] = { - 4.65128586073990045278E-5, 7.31589045238094711071E-3, 1.33847639578309018650E-1, 8.79691311754530315341E-1, - 2.71149851196553469920E0, 4.25697156008121755724E0, 3.29771340985225106936E0, 1.00000000000000000126E0, - }; - - constexpr double spence_B[8] = { - 6.90990488912553276999E-4, 2.54043763932544379113E-2, 2.82974860602568089943E-1, 1.41172597751831069617E0, - 3.63800533345137075418E0, 5.03278880143316990390E0, 3.54771340985225096217E0, 9.99999999999999998740E-1, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double spence(double x) { - double w, y, z; - int flag; - - if (x < 0.0) { - set_error("spence", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (x == 1.0) { - return (0.0); - } - - if (x == 0.0) { - return (M_PI * M_PI / 6.0); - } - - flag = 0; - - if (x > 2.0) { - x = 1.0 / x; - flag |= 2; - } - - if (x > 1.5) { - w = (1.0 / x) - 1.0; - flag |= 2; - } else if (x < 0.5) { - w = -x; - flag |= 1; - } else { - w = x - 1.0; - } - - y = -w * polevl(w, detail::spence_A, 7) / polevl(w, detail::spence_B, 7); - - if (flag & 1) { - y = (M_PI * M_PI) / 6.0 - std::log(x) * std::log(1.0 - x) - y; - } - - if (flag & 2) { - z = std::log(x); - y = -0.5 * z * z - y; - } - - return (y); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/struve.h b/scipy/special/xsf/cephes/struve.h deleted file mode 100644 index 3ed5bae25d51..000000000000 --- a/scipy/special/xsf/cephes/struve.h +++ /dev/null @@ -1,382 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Compute the Struve function. - * - * Notes - * ----- - * - * We use three expansions for the Struve function discussed in [1]: - * - * - power series - * - expansion in Bessel functions - * - asymptotic large-z expansion - * - * Rounding errors are estimated based on the largest terms in the sums. - * - * ``struve_convergence.py`` plots the convergence regions of the different - * expansions. - * - * (i) - * - * Looking at the error in the asymptotic expansion, one finds that - * it's not worth trying if z ~> 0.7 * v + 12 for v > 0. - * - * (ii) - * - * The Bessel function expansion tends to fail for |z| >~ |v| and is not tried - * there. - * - * For Struve H it covers the quadrant v > z where the power series may fail to - * produce reasonable results. - * - * (iii) - * - * The three expansions together cover for Struve H the region z > 0, v real. - * - * They also cover Struve L, except that some loss of precision may occur around - * the transition region z ~ 0.7 |v|, v < 0, |v| >> 1 where the function changes - * rapidly. - * - * (iv) - * - * The power series is evaluated in double-double precision. This fixes accuracy - * issues in Struve H for |v| << |z| before the asymptotic expansion kicks in. - * Moreover, it improves the Struve L behavior for negative v. - * - * - * References - * ---------- - * [1] NIST Digital Library of Mathematical Functions - * https://dlmf.nist.gov/11 - */ - -/* - * Copyright (C) 2013 Pauli Virtanen - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * a. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * b. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * c. Neither the name of Enthought nor the names of the SciPy Developers - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ -#pragma once - -#include "../bessel.h" -#include "../config.h" -#include "../error.h" - -#include "dd_real.h" -#include "gamma.h" -#include "rgamma.h" -#include "scipy_iv.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr int STRUVE_MAXITER = 10000; - constexpr double STRUVE_SUM_EPS = 1e-16; /* be sure we are in the tail of the sum */ - constexpr double STRUVE_SUM_TINY = 1e-100; - constexpr double STRUVE_GOOD_EPS = 1e-12; - constexpr double STRUVE_ACCEPTABLE_EPS = 1e-7; - constexpr double STRUVE_ACCEPTABLE_ATOL = 1e-300; - - /* - * Large-z expansion for Struve H and L - * https://dlmf.nist.gov/11.6.1 - */ - XSF_HOST_DEVICE inline double struve_asymp_large_z(double v, double z, int is_h, double *err) { - int n, sgn, maxiter; - double term, sum, maxterm; - double m; - - if (is_h) { - sgn = -1; - } else { - sgn = 1; - } - - /* Asymptotic expansion divergenge point */ - m = z / 2; - if (m <= 0) { - maxiter = 0; - } else if (m > STRUVE_MAXITER) { - maxiter = STRUVE_MAXITER; - } else { - maxiter = (int) m; - } - if (maxiter == 0) { - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - if (z < v) { - /* Exclude regions where our error estimation fails */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - /* Evaluate sum */ - term = -sgn / std::sqrt(M_PI) * std::exp(-xsf::cephes::lgam(v + 0.5) + (v - 1) * std::log(z / 2)) * - xsf::cephes::gammasgn(v + 0.5); - sum = term; - maxterm = 0; - - for (n = 0; n < maxiter; ++n) { - term *= sgn * (1 + 2 * n) * (1 + 2 * n - 2 * v) / (z * z); - sum += term; - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_EPS * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - if (is_h) { - sum += xsf::cyl_bessel_y(v, z); - } else { - sum += xsf::cephes::iv(v, z); - } - - /* - * This error estimate is strictly speaking valid only for - * n > v - 0.5, but numerical results indicate that it works - * reasonably. - */ - *err = std::abs(term) + std::abs(maxterm) * STRUVE_SUM_EPS; - - return sum; - } - - /* - * Power series for Struve H and L - * https://dlmf.nist.gov/11.2.1 - * - * Starts to converge roughly at |n| > |z| - */ - XSF_HOST_DEVICE inline double struve_power_series(double v, double z, int is_h, double *err) { - int n, sgn; - double term, sum, maxterm, scaleexp, tmp; - double_double cterm, csum, cdiv, z2, c2v, ctmp; - - if (is_h) { - sgn = -1; - } else { - sgn = 1; - } - - tmp = -xsf::cephes::lgam(v + 1.5) + (v + 1) * std::log(z / 2); - if (tmp < -600 || tmp > 600) { - /* Scale exponent to postpone underflow/overflow */ - scaleexp = tmp / 2; - tmp -= scaleexp; - } else { - scaleexp = 0; - } - - term = 2 / std::sqrt(M_PI) * std::exp(tmp) * xsf::cephes::gammasgn(v + 1.5); - sum = term; - maxterm = 0; - - cterm = double_double(term); - csum = double_double(sum); - z2 = double_double(sgn * z * z); - c2v = double_double(2 * v); - - for (n = 0; n < STRUVE_MAXITER; ++n) { - /* cdiv = (3 + 2*n) * (3 + 2*n + 2*v)) */ - cdiv = double_double(3 + 2 * n); - ctmp = double_double(3 + 2 * n); - ctmp = ctmp + c2v; - cdiv = cdiv * ctmp; - - /* cterm *= z2 / cdiv */ - cterm = cterm * z2; - cterm = cterm / cdiv; - - csum = csum + cterm; - - term = static_cast(cterm); - sum = static_cast(csum); - - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_TINY * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - *err = std::abs(term) + std::abs(maxterm) * 1e-22; - - if (scaleexp != 0) { - sum *= std::exp(scaleexp); - *err *= std::exp(scaleexp); - } - - if (sum == 0 && term == 0 && v < 0 && !is_h) { - /* Spurious underflow */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - ; - } - - return sum; - } - - /* - * Bessel series - * https://dlmf.nist.gov/11.4.19 - */ - XSF_HOST_DEVICE inline double struve_bessel_series(double v, double z, int is_h, double *err) { - int n; - double term, cterm, sum, maxterm; - - if (is_h && v < 0) { - /* Works less reliably in this region */ - *err = std::numeric_limits::infinity(); - return std::numeric_limits::quiet_NaN(); - } - - sum = 0; - maxterm = 0; - - cterm = std::sqrt(z / (2 * M_PI)); - - for (n = 0; n < STRUVE_MAXITER; ++n) { - if (is_h) { - term = cterm * xsf::cyl_bessel_j(n + v + 0.5, z) / (n + 0.5); - cterm *= z / 2 / (n + 1); - } else { - term = cterm * xsf::cephes::iv(n + v + 0.5, z) / (n + 0.5); - cterm *= -z / 2 / (n + 1); - } - sum += term; - if (std::abs(term) > maxterm) { - maxterm = std::abs(term); - } - if (std::abs(term) < STRUVE_SUM_EPS * std::abs(sum) || term == 0 || !std::isfinite(sum)) { - break; - } - } - - *err = std::abs(term) + std::abs(maxterm) * 1e-16; - - /* Account for potential underflow of the Bessel functions */ - *err += 1e-300 * std::abs(cterm); - - return sum; - } - - XSF_HOST_DEVICE inline double struve_hl(double v, double z, int is_h) { - double value[4], err[4], tmp; - int n; - - if (z < 0) { - n = v; - if (v == n) { - tmp = (n % 2 == 0) ? -1 : 1; - return tmp * struve_hl(v, -z, is_h); - } else { - return std::numeric_limits::quiet_NaN(); - } - } else if (z == 0) { - if (v < -1) { - return xsf::cephes::gammasgn(v + 1.5) * std::numeric_limits::infinity(); - } else if (v == -1) { - return 2 / std::sqrt(M_PI) * xsf::cephes::rgamma(0.5); - } else { - return 0; - } - } - - n = -v - 0.5; - if (n == -v - 0.5 && n > 0) { - if (is_h) { - return (n % 2 == 0 ? 1 : -1) * xsf::cyl_bessel_j(n + 0.5, z); - } else { - return xsf::cephes::iv(n + 0.5, z); - } - } - - /* Try the asymptotic expansion */ - if (z >= 0.7 * v + 12) { - value[0] = struve_asymp_large_z(v, z, is_h, &err[0]); - if (err[0] < STRUVE_GOOD_EPS * std::abs(value[0])) { - return value[0]; - } - } else { - err[0] = std::numeric_limits::infinity(); - } - - /* Try power series */ - value[1] = struve_power_series(v, z, is_h, &err[1]); - if (err[1] < STRUVE_GOOD_EPS * std::abs(value[1])) { - return value[1]; - } - - /* Try bessel series */ - if (std::abs(z) < std::abs(v) + 20) { - value[2] = struve_bessel_series(v, z, is_h, &err[2]); - if (err[2] < STRUVE_GOOD_EPS * std::abs(value[2])) { - return value[2]; - } - } else { - err[2] = std::numeric_limits::infinity(); - } - - /* Return the best of the three, if it is acceptable */ - n = 0; - if (err[1] < err[n]) - n = 1; - if (err[2] < err[n]) - n = 2; - if (err[n] < STRUVE_ACCEPTABLE_EPS * std::abs(value[n]) || err[n] < STRUVE_ACCEPTABLE_ATOL) { - return value[n]; - } - - /* Maybe it really is an overflow? */ - tmp = -xsf::cephes::lgam(v + 1.5) + (v + 1) * std::log(z / 2); - if (!is_h) { - tmp = std::abs(tmp); - } - if (tmp > 700) { - set_error("struve", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity() * xsf::cephes::gammasgn(v + 1.5); - } - - /* Failure */ - set_error("struve", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - } // namespace detail - - XSF_HOST_DEVICE inline double struve_h(double v, double z) { return detail::struve_hl(v, z, 1); } - - XSF_HOST_DEVICE inline double struve_l(double v, double z) { return detail::struve_hl(v, z, 0); } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/tandg.h b/scipy/special/xsf/cephes/tandg.h deleted file mode 100644 index 071b1a81d8bd..000000000000 --- a/scipy/special/xsf/cephes/tandg.h +++ /dev/null @@ -1,139 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* tandg.c - * - * Circular tangent of argument in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, tandg(); - * - * y = tandg( x ); - * - * - * - * DESCRIPTION: - * - * Returns the circular tangent of the argument x in degrees. - * - * Range reduction is modulo pi/4. A rational function - * x + x**3 P(x**2)/Q(x**2) - * is employed in the basic interval [0, pi/4]. - * - * - * - * ACCURACY: - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 0,10 30000 3.2e-16 8.4e-17 - * - * ERROR MESSAGES: - * - * message condition value returned - * tandg total loss x > 1.0e14 (IEEE) 0.0 - * tandg singularity x = 180 k + 90 INFINITY - */ -/* cotdg.c - * - * Circular cotangent of argument in degrees - * - * - * - * SYNOPSIS: - * - * double x, y, cotdg(); - * - * y = cotdg( x ); - * - * - * - * DESCRIPTION: - * - * Returns the circular cotangent of the argument x in degrees. - * - * Range reduction is modulo pi/4. A rational function - * x + x**3 P(x**2)/Q(x**2) - * is employed in the basic interval [0, pi/4]. - * - * - * ERROR MESSAGES: - * - * message condition value returned - * cotdg total loss x > 1.0e14 (IEEE) 0.0 - * cotdg singularity x = 180 k INFINITY - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -namespace xsf { -namespace cephes { - - namespace detail { - constexpr double tandg_lossth = 1.0e14; - - XSF_HOST_DEVICE inline double tancot(double xx, int cotflg) { - double x; - int sign; - - /* make argument positive but save the sign */ - if (xx < 0) { - x = -xx; - sign = -1; - } else { - x = xx; - sign = 1; - } - - if (x > detail::tandg_lossth) { - set_error("tandg", SF_ERROR_NO_RESULT, NULL); - return 0.0; - } - - /* modulo 180 */ - x = x - 180.0 * std::floor(x / 180.0); - if (cotflg) { - if (x <= 90.0) { - x = 90.0 - x; - } else { - x = x - 90.0; - sign *= -1; - } - } else { - if (x > 90.0) { - x = 180.0 - x; - sign *= -1; - } - } - if (x == 0.0) { - return 0.0; - } else if (x == 45.0) { - return sign * 1.0; - } else if (x == 90.0) { - set_error((cotflg ? "cotdg" : "tandg"), SF_ERROR_SINGULAR, NULL); - return std::numeric_limits::infinity(); - } - /* x is now transformed into [0, 90) */ - return sign * std::tan(x * detail::PI180); - } - - } // namespace detail - - XSF_HOST_DEVICE inline double tandg(double x) { return (detail::tancot(x, 0)); } - - XSF_HOST_DEVICE inline double cotdg(double x) { return (detail::tancot(x, 1)); } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/trig.h b/scipy/special/xsf/cephes/trig.h deleted file mode 100644 index 47dcdbe6ab3c..000000000000 --- a/scipy/special/xsf/cephes/trig.h +++ /dev/null @@ -1,58 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * - * Original author: Josh Wilson, 2020. - */ - -/* - * Implement sin(pi * x) and cos(pi * x) for real x. Since the periods - * of these functions are integral (and thus representable in double - * precision), it's possible to compute them with greater accuracy - * than sin(x) and cos(x). - */ -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - /* Compute sin(pi * x). */ - template - XSF_HOST_DEVICE T sinpi(T x) { - T s = 1.0; - - if (x < 0.0) { - x = -x; - s = -1.0; - } - - T r = std::fmod(x, 2.0); - if (r < 0.5) { - return s * std::sin(M_PI * r); - } else if (r > 1.5) { - return s * std::sin(M_PI * (r - 2.0)); - } else { - return -s * std::sin(M_PI * (r - 1.0)); - } - } - - /* Compute cos(pi * x) */ - template - XSF_HOST_DEVICE T cospi(T x) { - if (x < 0.0) { - x = -x; - } - - T r = std::fmod(x, 2.0); - if (r == 0.5) { - // We don't want to return -0.0 - return 0.0; - } - if (r < 1.0) { - return -std::sin(M_PI * (r - 0.5)); - } else { - return std::sin(M_PI * (r - 1.5)); - } - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/tukey.h b/scipy/special/xsf/cephes/tukey.h deleted file mode 100644 index 062966df316e..000000000000 --- a/scipy/special/xsf/cephes/tukey.h +++ /dev/null @@ -1,80 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* Compute the CDF of the Tukey-Lambda distribution - * using a bracketing search with special checks - * - * The PPF of the Tukey-lambda distribution is - * G(p) = (p**lam + (1-p)**lam) / lam - * - * Author: Travis Oliphant - */ - -#pragma once - -#include "../config.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - constexpr double tukey_SMALLVAL = 1e-4; - constexpr double tukey_EPS = 1.0e-14; - constexpr int tukey_MAXCOUNT = 60; - - } // namespace detail - - XSF_HOST_DEVICE inline double tukeylambdacdf(double x, double lmbda) { - double pmin, pmid, pmax, plow, phigh, xeval; - int count; - - if (std::isnan(x) || std::isnan(lmbda)) { - return std::numeric_limits::quiet_NaN(); - } - - xeval = 1.0 / lmbda; - if (lmbda > 0.0) { - if (x <= (-xeval)) { - return 0.0; - } - if (x >= xeval) { - return 1.0; - } - } - - if ((-detail::tukey_SMALLVAL < lmbda) && (lmbda < detail::tukey_SMALLVAL)) { - if (x >= 0) { - return 1.0 / (1.0 + std::exp(-x)); - } else { - return exp(x) / (1.0 + exp(x)); - } - } - - pmin = 0.0; - pmid = 0.5; - pmax = 1.0; - plow = pmin; - phigh = pmax; - count = 0; - - while ((count < detail::tukey_MAXCOUNT) && (std::abs(pmid - plow) > detail::tukey_EPS)) { - xeval = (std::pow(pmid, lmbda) - std::pow(1.0 - pmid, lmbda)) / lmbda; - if (xeval == x) { - return pmid; - } - if (xeval > x) { - phigh = pmid; - pmid = (pmid + plow) / 2.0; - } else { - plow = pmid; - pmid = (pmid + phigh) / 2.0; - } - count++; - } - return pmid; - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/unity.h b/scipy/special/xsf/cephes/unity.h deleted file mode 100644 index eb045edda212..000000000000 --- a/scipy/special/xsf/cephes/unity.h +++ /dev/null @@ -1,186 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. */ - -/* unity.c - * - * Relative error approximations for function arguments near - * unity. - * - * log1p(x) = log(1+x) - * expm1(x) = exp(x) - 1 - * cosm1(x) = cos(x) - 1 - * lgam1p(x) = lgam(1+x) - * - */ - -/* Scipy changes: - * - 06-10-2016: added lgam1p - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "gamma.h" -#include "polevl.h" -#include "zeta.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* log1p(x) = log(1 + x) */ - - /* Coefficients for log(1+x) = x - x**2/2 + x**3 P(x)/Q(x) - * 1/sqrt(2) <= x < sqrt(2) - * Theoretical peak relative error = 2.32e-20 - */ - - constexpr double unity_LP[] = { - 4.5270000862445199635215E-5, 4.9854102823193375972212E-1, 6.5787325942061044846969E0, - 2.9911919328553073277375E1, 6.0949667980987787057556E1, 5.7112963590585538103336E1, - 2.0039553499201281259648E1, - }; - - constexpr double unity_LQ[] = { - /* 1.0000000000000000000000E0, */ - 1.5062909083469192043167E1, 8.3047565967967209469434E1, 2.2176239823732856465394E2, - 3.0909872225312059774938E2, 2.1642788614495947685003E2, 6.0118660497603843919306E1, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double log1p(double x) { - double z; - - z = 1.0 + x; - if ((z < M_SQRT1_2) || (z > M_SQRT2)) - return (std::log(z)); - z = x * x; - z = -0.5 * z + x * (z * polevl(x, detail::unity_LP, 6) / p1evl(x, detail::unity_LQ, 6)); - return (x + z); - } - - /* log(1 + x) - x */ - XSF_HOST_DEVICE inline double log1pmx(double x) { - if (std::abs(x) < 0.5) { - uint64_t n; - double xfac = x; - double term; - double res = 0; - - for (n = 2; n < detail::MAXITER; n++) { - xfac *= -x; - term = xfac / n; - res += term; - if (std::abs(term) < detail::MACHEP * std::abs(res)) { - break; - } - } - return res; - } else { - return log1p(x) - x; - } - } - - /* expm1(x) = exp(x) - 1 */ - - /* e^x = 1 + 2x P(x^2)/( Q(x^2) - P(x^2) ) - * -0.5 <= x <= 0.5 - */ - - namespace detail { - - constexpr double unity_EP[3] = { - 1.2617719307481059087798E-4, - 3.0299440770744196129956E-2, - 9.9999999999999999991025E-1, - }; - - constexpr double unity_EQ[4] = { - 3.0019850513866445504159E-6, - 2.5244834034968410419224E-3, - 2.2726554820815502876593E-1, - 2.0000000000000000000897E0, - }; - - } // namespace detail - - XSF_HOST_DEVICE inline double expm1(double x) { - double r, xx; - - if (!std::isfinite(x)) { - if (std::isnan(x)) { - return x; - } else if (x > 0) { - return x; - } else { - return -1.0; - } - } - if ((x < -0.5) || (x > 0.5)) - return (std::exp(x) - 1.0); - xx = x * x; - r = x * polevl(xx, detail::unity_EP, 2); - r = r / (polevl(xx, detail::unity_EQ, 3) - r); - return (r + r); - } - - /* cosm1(x) = cos(x) - 1 */ - - namespace detail { - constexpr double unity_coscof[7] = { - 4.7377507964246204691685E-14, -1.1470284843425359765671E-11, 2.0876754287081521758361E-9, - -2.7557319214999787979814E-7, 2.4801587301570552304991E-5, -1.3888888888888872993737E-3, - 4.1666666666666666609054E-2, - }; - - } - - XSF_HOST_DEVICE inline double cosm1(double x) { - double xx; - - if ((x < -M_PI_4) || (x > M_PI_4)) - return (std::cos(x) - 1.0); - xx = x * x; - xx = -0.5 * xx + xx * xx * polevl(xx, detail::unity_coscof, 6); - return xx; - } - - namespace detail { - /* Compute lgam(x + 1) around x = 0 using its Taylor series. */ - XSF_HOST_DEVICE inline double lgam1p_taylor(double x) { - int n; - double xfac, coeff, res; - - if (x == 0) { - return 0; - } - res = -SCIPY_EULER * x; - xfac = -x; - for (n = 2; n < 42; n++) { - xfac *= -x; - coeff = xsf::cephes::zeta(n, 1) * xfac / n; - res += coeff; - if (std::abs(coeff) < detail::MACHEP * std::abs(res)) { - break; - } - } - - return res; - } - } // namespace detail - - /* Compute lgam(x + 1). */ - XSF_HOST_DEVICE inline double lgam1p(double x) { - if (std::abs(x) <= 0.5) { - return detail::lgam1p_taylor(x); - } else if (std::abs(x - 1) < 0.5) { - return std::log(x) + detail::lgam1p_taylor(x - 1); - } else { - return lgam(x + 1); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/yn.h b/scipy/special/xsf/cephes/yn.h deleted file mode 100644 index da942305266b..000000000000 --- a/scipy/special/xsf/cephes/yn.h +++ /dev/null @@ -1,118 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* yn.c - * - * Bessel function of second kind of integer order - * - * - * - * SYNOPSIS: - * - * double x, y, yn(); - * int n; - * - * y = yn( n, x ); - * - * - * - * DESCRIPTION: - * - * Returns Bessel function of order n, where n is a - * (possibly negative) integer. - * - * The function is evaluated by forward recurrence on - * n, starting with values computed by the routines - * y0() and y1(). - * - * If n = 0 or 1 the routine for y0 or y1 is called - * directly. - * - * - * - * ACCURACY: - * - * - * Absolute error, except relative - * when y > 1: - * arithmetic domain # trials peak rms - * IEEE 0, 30 30000 3.4e-15 4.3e-16 - * - * - * ERROR MESSAGES: - * - * message condition value returned - * yn singularity x = 0 INFINITY - * yn overflow INFINITY - * - * Spot checked against tables for x, n between 0 and 100. - * - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "j0.h" -#include "j1.h" - -namespace xsf { -namespace cephes { - - XSF_HOST_DEVICE inline double yn(int n, double x) { - double an, anm1, anm2, r; - int k, sign; - - if (n < 0) { - n = -n; - if ((n & 1) == 0) { /* -1**n */ - sign = 1; - } else { - sign = -1; - } - } else { - sign = 1; - } - - if (n == 0) { - return (sign * y0(x)); - } - if (n == 1) { - return (sign * y1(x)); - } - - /* test for overflow */ - if (x == 0.0) { - set_error("yn", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity() * sign; - } else if (x < 0.0) { - set_error("yn", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - /* forward recurrence on n */ - - anm2 = y0(x); - anm1 = y1(x); - k = 1; - r = 2 * k; - do { - an = r * anm1 / x - anm2; - anm2 = anm1; - anm1 = an; - r += 2.0; - ++k; - } while (k < n && std::isfinite(an)); - - return (sign * an); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/yv.h b/scipy/special/xsf/cephes/yv.h deleted file mode 100644 index e97564506b22..000000000000 --- a/scipy/special/xsf/cephes/yv.h +++ /dev/null @@ -1,55 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* - * Cephes Math Library Release 2.8: June, 2000 - * Copyright 1984, 1987, 2000 by Stephen L. Moshier - */ -#pragma once - -#include "../config.h" -#include "../error.h" - -#include "const.h" -#include "jv.h" -#include "yn.h" - -namespace xsf { -namespace cephes { - - /* - * Bessel function of noninteger order - */ - XSF_HOST_DEVICE inline double yv(double v, double x) { - double y, t; - int n; - - n = v; - if (n == v) { - y = yn(n, x); - return (y); - } else if (v == std::floor(v)) { - /* Zero in denominator. */ - set_error("yv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - - t = M_PI * v; - y = (std::cos(t) * jv(v, x) - jv(-v, x)) / std::sin(t); - - if (std::isinf(y)) { - if (v > 0) { - set_error("yv", SF_ERROR_OVERFLOW, NULL); - return -std::numeric_limits::infinity(); - } else if (v < -1e10) { - /* Whether it's +inf or -inf is numerically ill-defined. */ - set_error("yv", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - } - - return (y); - } -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/zeta.h b/scipy/special/xsf/cephes/zeta.h deleted file mode 100644 index 6f9d68e0bdce..000000000000 --- a/scipy/special/xsf/cephes/zeta.h +++ /dev/null @@ -1,172 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* zeta.c - * - * Riemann zeta function of two arguments - * - * - * - * SYNOPSIS: - * - * double x, q, y, zeta(); - * - * y = zeta( x, q ); - * - * - * - * DESCRIPTION: - * - * - * - * inf. - * - -x - * zeta(x,q) = > (k+q) - * - - * k=0 - * - * where x > 1 and q is not a negative integer or zero. - * The Euler-Maclaurin summation formula is used to obtain - * the expansion - * - * n - * - -x - * zeta(x,q) = > (k+q) - * - - * k=1 - * - * 1-x inf. B x(x+1)...(x+2j) - * (n+q) 1 - 2j - * + --------- - ------- + > -------------------- - * x-1 x - x+2j+1 - * 2(n+q) j=1 (2j)! (n+q) - * - * where the B2j are Bernoulli numbers. Note that (see zetac.c) - * zeta(x,1) = zetac(x) + 1. - * - * - * - * ACCURACY: - * - * - * - * REFERENCE: - * - * Gradshteyn, I. S., and I. M. Ryzhik, Tables of Integrals, - * Series, and Products, p. 1073; Academic Press, 1980. - * - */ - -/* - * Cephes Math Library Release 2.0: April, 1987 - * Copyright 1984, 1987 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" -#include "../error.h" -#include "const.h" - -namespace xsf { -namespace cephes { - - namespace detail { - /* Expansion coefficients - * for Euler-Maclaurin summation formula - * (2k)! / B2k - * where B2k are Bernoulli numbers - */ - constexpr double zeta_A[] = { - 12.0, - -720.0, - 30240.0, - -1209600.0, - 47900160.0, - -1.8924375803183791606e9, /*1.307674368e12/691 */ - 7.47242496e10, - -2.950130727918164224e12, /*1.067062284288e16/3617 */ - 1.1646782814350067249e14, /*5.109094217170944e18/43867 */ - -4.5979787224074726105e15, /*8.028576626982912e20/174611 */ - 1.8152105401943546773e17, /*1.5511210043330985984e23/854513 */ - -7.1661652561756670113e18 /*1.6938241367317436694528e27/236364091 */ - }; - - /* 30 Nov 86 -- error in third coefficient fixed */ - } // namespace detail - - XSF_HOST_DEVICE double inline zeta(double x, double q) { - int i; - double a, b, k, s, t, w; - - if (x == 1.0) - goto retinf; - - if (x < 1.0) { - domerr: - set_error("zeta", SF_ERROR_DOMAIN, NULL); - return (std::numeric_limits::quiet_NaN()); - } - - if (q <= 0.0) { - if (q == floor(q)) { - set_error("zeta", SF_ERROR_SINGULAR, NULL); - retinf: - return (std::numeric_limits::infinity()); - } - if (x != std::floor(x)) - goto domerr; /* because q^-x not defined */ - } - - /* Asymptotic expansion - * https://dlmf.nist.gov/25.11#E43 - */ - if (q > 1e8) { - return (1 / (x - 1) + 1 / (2 * q)) * std::pow(q, 1 - x); - } - - /* Euler-Maclaurin summation formula */ - - /* Permit negative q but continue sum until n+q > +9 . - * This case should be handled by a reflection formula. - * If q<0 and x is an integer, there is a relation to - * the polyGamma function. - */ - s = std::pow(q, -x); - a = q; - i = 0; - b = 0.0; - while ((i < 9) || (a <= 9.0)) { - i += 1; - a += 1.0; - b = std::pow(a, -x); - s += b; - if (std::abs(b / s) < detail::MACHEP) - goto done; - } - - w = a; - s += b * w / (x - 1.0); - s -= 0.5 * b; - a = 1.0; - k = 0.0; - for (i = 0; i < 12; i++) { - a *= x + k; - b /= w; - t = a * b / detail::zeta_A[i]; - s = s + t; - t = std::abs(t / s); - if (t < detail::MACHEP) - goto done; - k += 1.0; - a *= x + k; - b /= w; - k += 1.0; - } - done: - return (s); - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/cephes/zetac.h b/scipy/special/xsf/cephes/zetac.h deleted file mode 100644 index c006681e7cc6..000000000000 --- a/scipy/special/xsf/cephes/zetac.h +++ /dev/null @@ -1,280 +0,0 @@ -/* Translated into C++ by SciPy developers in 2024. - * Original header with Copyright information appears below. - */ - -/* zetac.c - * - * Riemann zeta function - * - * - * - * SYNOPSIS: - * - * double x, y, zetac(); - * - * y = zetac( x ); - * - * - * - * DESCRIPTION: - * - * - * - * inf. - * - -x - * zetac(x) = > k , x > 1, - * - - * k=2 - * - * is related to the Riemann zeta function by - * - * Riemann zeta(x) = zetac(x) + 1. - * - * Extension of the function definition for x < 1 is implemented. - * Zero is returned for x > log2(INFINITY). - * - * ACCURACY: - * - * Tabulated values have full machine accuracy. - * - * Relative error: - * arithmetic domain # trials peak rms - * IEEE 1,50 10000 9.8e-16 1.3e-16 - * - * - */ - -/* - * Cephes Math Library Release 2.1: January, 1989 - * Copyright 1984, 1987, 1989 by Stephen L. Moshier - * Direct inquiries to 30 Frost Street, Cambridge, MA 02140 - */ -#pragma once - -#include "../config.h" - -#include "const.h" -#include "lanczos.h" -#include "polevl.h" -#include "zeta.h" - -namespace xsf { -namespace cephes { - - namespace detail { - - /* Riemann zeta(x) - 1 - * for integer arguments between 0 and 30. - */ - constexpr double azetac[] = {-1.50000000000000000000E0, 0.0, /* Not used; zetac(1.0) is infinity. */ - 6.44934066848226436472E-1, 2.02056903159594285400E-1, - 8.23232337111381915160E-2, 3.69277551433699263314E-2, - 1.73430619844491397145E-2, 8.34927738192282683980E-3, - 4.07735619794433937869E-3, 2.00839282608221441785E-3, - 9.94575127818085337146E-4, 4.94188604119464558702E-4, - 2.46086553308048298638E-4, 1.22713347578489146752E-4, - 6.12481350587048292585E-5, 3.05882363070204935517E-5, - 1.52822594086518717326E-5, 7.63719763789976227360E-6, - 3.81729326499983985646E-6, 1.90821271655393892566E-6, - 9.53962033872796113152E-7, 4.76932986787806463117E-7, - 2.38450502727732990004E-7, 1.19219925965311073068E-7, - 5.96081890512594796124E-8, 2.98035035146522801861E-8, - 1.49015548283650412347E-8, 7.45071178983542949198E-9, - 3.72533402478845705482E-9, 1.86265972351304900640E-9, - 9.31327432419668182872E-10}; - - /* 2**x (1 - 1/x) (zeta(x) - 1) = P(1/x)/Q(1/x), 1 <= x <= 10 */ - constexpr double zetac_P[9] = { - 5.85746514569725319540E11, 2.57534127756102572888E11, 4.87781159567948256438E10, - 5.15399538023885770696E9, 3.41646073514754094281E8, 1.60837006880656492731E7, - 5.92785467342109522998E5, 1.51129169964938823117E4, 2.01822444485997955865E2, - }; - - constexpr double zetac_Q[8] = { - /* 1.00000000000000000000E0, */ - 3.90497676373371157516E11, 5.22858235368272161797E10, 5.64451517271280543351E9, 3.39006746015350418834E8, - 1.79410371500126453702E7, 5.66666825131384797029E5, 1.60382976810944131506E4, 1.96436237223387314144E2, - }; - - /* log(zeta(x) - 1 - 2**-x), 10 <= x <= 50 */ - constexpr double zetac_A[11] = { - 8.70728567484590192539E6, 1.76506865670346462757E8, 2.60889506707483264896E10, - 5.29806374009894791647E11, 2.26888156119238241487E13, 3.31884402932705083599E14, - 5.13778997975868230192E15, -1.98123688133907171455E15, -9.92763810039983572356E16, - 7.82905376180870586444E16, 9.26786275768927717187E16, - }; - - constexpr double zetac_B[10] = { - /* 1.00000000000000000000E0, */ - -7.92625410563741062861E6, -1.60529969932920229676E8, -2.37669260975543221788E10, - -4.80319584350455169857E11, -2.07820961754173320170E13, -2.96075404507272223680E14, - -4.86299103694609136686E15, 5.34589509675789930199E15, 5.71464111092297631292E16, - -1.79915597658676556828E16, - }; - - /* (1-x) (zeta(x) - 1), 0 <= x <= 1 */ - constexpr double zetac_R[6] = { - -3.28717474506562731748E-1, 1.55162528742623950834E1, -2.48762831680821954401E2, - 1.01050368053237678329E3, 1.26726061410235149405E4, -1.11578094770515181334E5, - }; - - constexpr double zetac_S[5] = { - /* 1.00000000000000000000E0, */ - 1.95107674914060531512E1, 3.17710311750646984099E2, 3.03835500874445748734E3, - 2.03665876435770579345E4, 7.43853965136767874343E4, - }; - - constexpr double zetac_TAYLOR0[10] = { - -1.0000000009110164892, -1.0000000057646759799, - -9.9999983138417361078e-1, -1.0000013011460139596, - -1.000001940896320456, -9.9987929950057116496e-1, - -1.000785194477042408, -1.0031782279542924256, - -9.1893853320467274178e-1, -1.5, - }; - - constexpr int zetac_MAXL2 = 127; - - /* - * Compute zetac for positive arguments - */ - XSF_HOST_DEVICE inline double zetac_positive(double x) { - int i; - double a, b, s, w; - - if (x == 1.0) { - return std::numeric_limits::infinity(); - } - - if (x >= detail::zetac_MAXL2) { - /* because first term is 2**-x */ - return 0.0; - } - - /* Tabulated values for integer argument */ - w = std::floor(x); - if (w == x) { - i = x; - if (i < 31) { - return (azetac[i]); - } - } - - if (x < 1.0) { - w = 1.0 - x; - a = xsf::cephes::polevl(x, zetac_R, 5) / (w * xsf::cephes::p1evl(x, zetac_S, 5)); - return a; - } - - if (x <= 10.0) { - b = std::pow(2.0, x) * (x - 1.0); - w = 1.0 / x; - s = (x * xsf::cephes::polevl(w, zetac_P, 8)) / (b * xsf::cephes::p1evl(w, zetac_Q, 8)); - return s; - } - - if (x <= 50.0) { - b = std::pow(2.0, -x); - w = xsf::cephes::polevl(x, zetac_A, 10) / xsf::cephes::p1evl(x, zetac_B, 10); - w = std::exp(w) + b; - return w; - } - - /* Basic sum of inverse powers */ - s = 0.0; - a = 1.0; - do { - a += 2.0; - b = std::pow(a, -x); - s += b; - } while (b / s > MACHEP); - - b = std::pow(2.0, -x); - s = (s + b) / (1.0 - b); - return s; - } - - /* - * Compute zetac for small negative x. We can't use the reflection - * formula because to double precision 1 - x = 1 and zetac(1) = inf. - */ - XSF_HOST_DEVICE inline double zetac_smallneg(double x) { - return xsf::cephes::polevl(x, zetac_TAYLOR0, 9); - } - - /* - * Compute zetac using the reflection formula (see DLMF 25.4.2) plus - * the Lanczos approximation for Gamma to avoid overflow. - */ - XSF_HOST_DEVICE inline double zeta_reflection(double x) { - double base, large_term, small_term, hx, x_shift; - - hx = x / 2; - if (hx == std::floor(hx)) { - /* Hit a zero of the sine factor */ - return 0; - } - - /* Reduce the argument to sine */ - x_shift = std::fmod(x, 4); - small_term = -SQRT2PI * sin(0.5 * M_PI * x_shift); - small_term *= xsf::cephes::lanczos_sum_expg_scaled(x + 1) * xsf::cephes::zeta(x + 1, 1); - - /* Group large terms together to prevent overflow */ - base = (x + xsf::cephes::lanczos_g + 0.5) / (2 * M_PI * M_E); - large_term = std::pow(base, x + 0.5); - if (std::isfinite(large_term)) { - return large_term * small_term; - } - /* - * We overflowed, but we might be able to stave off overflow by - * factoring in the small term earlier. To do this we compute - * - * (sqrt(large_term) * small_term) * sqrt(large_term) - * - * Since we only call this method for negative x bounded away from - * zero, the small term can only be as small sine on that region; - * i.e. about machine epsilon. This means that if the above still - * overflows, then there was truly no avoiding it. - */ - large_term = std::pow(base, 0.5 * x + 0.25); - return (large_term * small_term) * large_term; - } - - } // namespace detail - - /* - * Riemann zeta function, minus one - */ - XSF_HOST_DEVICE inline double zetac(double x) { - if (std::isnan(x)) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x < 0.0 && x > -0.01) { - return detail::zetac_smallneg(x); - } else if (x < 0.0) { - return detail::zeta_reflection(-x) - 1; - } else { - return detail::zetac_positive(x); - } - } - - /* - * Riemann zeta function - */ - XSF_HOST_DEVICE inline double riemann_zeta(double x) { - if (std::isnan(x)) { - return x; - } else if (x == -std::numeric_limits::infinity()) { - return std::numeric_limits::quiet_NaN(); - } else if (x < 0.0 && x > -0.01) { - return 1 + detail::zetac_smallneg(x); - } else if (x < 0.0) { - return detail::zeta_reflection(-x); - } else { - return 1 + detail::zetac_positive(x); - } - } - -} // namespace cephes -} // namespace xsf diff --git a/scipy/special/xsf/config.h b/scipy/special/xsf/config.h deleted file mode 100644 index 5cb40ed1e1e0..000000000000 --- a/scipy/special/xsf/config.h +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -// Define math constants if they are not available -#ifndef M_E -#define M_E 2.71828182845904523536 -#endif - -#ifndef M_LOG2E -#define M_LOG2E 1.44269504088896340736 -#endif - -#ifndef M_LOG10E -#define M_LOG10E 0.434294481903251827651 -#endif - -#ifndef M_LN2 -#define M_LN2 0.693147180559945309417 -#endif - -#ifndef M_LN10 -#define M_LN10 2.30258509299404568402 -#endif - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -#ifndef M_PI_2 -#define M_PI_2 1.57079632679489661923 -#endif - -#ifndef M_PI_4 -#define M_PI_4 0.785398163397448309616 -#endif - -#ifndef M_1_PI -#define M_1_PI 0.318309886183790671538 -#endif - -#ifndef M_2_PI -#define M_2_PI 0.636619772367581343076 -#endif - -#ifndef M_2_SQRTPI -#define M_2_SQRTPI 1.12837916709551257390 -#endif - -#ifndef M_SQRT2 -#define M_SQRT2 1.41421356237309504880 -#endif - -#ifndef M_SQRT1_2 -#define M_SQRT1_2 0.707106781186547524401 -#endif - -#ifdef __CUDACC__ -#define XSF_HOST_DEVICE __host__ __device__ - -#include -#include -#include -#include -#include -#include -#include - -// Fallback to global namespace for functions unsupported on NVRTC Jit -#ifdef _LIBCUDACXX_COMPILER_NVRTC -#include -#endif - -namespace std { - -XSF_HOST_DEVICE inline double abs(double num) { return cuda::std::abs(num); } - -XSF_HOST_DEVICE inline double exp(double num) { return cuda::std::exp(num); } - -XSF_HOST_DEVICE inline double log(double num) { return cuda::std::log(num); } - -XSF_HOST_DEVICE inline double sqrt(double num) { return cuda::std::sqrt(num); } - -XSF_HOST_DEVICE inline bool isinf(double num) { return cuda::std::isinf(num); } - -XSF_HOST_DEVICE inline bool isnan(double num) { return cuda::std::isnan(num); } - -XSF_HOST_DEVICE inline bool isfinite(double num) { return cuda::std::isfinite(num); } - -XSF_HOST_DEVICE inline double pow(double x, double y) { return cuda::std::pow(x, y); } - -XSF_HOST_DEVICE inline double sin(double x) { return cuda::std::sin(x); } - -XSF_HOST_DEVICE inline double cos(double x) { return cuda::std::cos(x); } - -XSF_HOST_DEVICE inline double tan(double x) { return cuda::std::tan(x); } - -XSF_HOST_DEVICE inline double atan(double x) { return cuda::std::atan(x); } - -XSF_HOST_DEVICE inline double acos(double x) { return cuda::std::acos(x); } - -XSF_HOST_DEVICE inline double sinh(double x) { return cuda::std::sinh(x); } - -XSF_HOST_DEVICE inline double cosh(double x) { return cuda::std::cosh(x); } - -XSF_HOST_DEVICE inline double asinh(double x) { return cuda::std::asinh(x); } - -XSF_HOST_DEVICE inline bool signbit(double x) { return cuda::std::signbit(x); } - -// Fallback to global namespace for functions unsupported on NVRTC -#ifndef _LIBCUDACXX_COMPILER_NVRTC -XSF_HOST_DEVICE inline double ceil(double x) { return cuda::std::ceil(x); } -XSF_HOST_DEVICE inline double floor(double x) { return cuda::std::floor(x); } -XSF_HOST_DEVICE inline double round(double x) { return cuda::std::round(x); } -XSF_HOST_DEVICE inline double trunc(double x) { return cuda::std::trunc(x); } -XSF_HOST_DEVICE inline double fma(double x, double y, double z) { return cuda::std::fma(x, y, z); } -XSF_HOST_DEVICE inline double copysign(double x, double y) { return cuda::std::copysign(x, y); } -XSF_HOST_DEVICE inline double modf(double value, double *iptr) { return cuda::std::modf(value, iptr); } -XSF_HOST_DEVICE inline double fmax(double x, double y) { return cuda::std::fmax(x, y); } -XSF_HOST_DEVICE inline double fmin(double x, double y) { return cuda::std::fmin(x, y); } -XSF_HOST_DEVICE inline double log10(double num) { return cuda::std::log10(num); } -XSF_HOST_DEVICE inline double log1p(double num) { return cuda::std::log1p(num); } -XSF_HOST_DEVICE inline double frexp(double num, int *exp) { return cuda::std::frexp(num, exp); } -XSF_HOST_DEVICE inline double ldexp(double num, int exp) { return cuda::std::ldexp(num, exp); } -XSF_HOST_DEVICE inline double fmod(double x, double y) { return cuda::std::fmod(x, y); } -XSF_HOST_DEVICE inline double nextafter(double from, double to) { return cuda::std::nextafter(from, to); } -#else -XSF_HOST_DEVICE inline double ceil(double x) { return ::ceil(x); } -XSF_HOST_DEVICE inline double floor(double x) { return ::floor(x); } -XSF_HOST_DEVICE inline double round(double x) { return ::round(x); } -XSF_HOST_DEVICE inline double trunc(double x) { return ::trunc(x); } -XSF_HOST_DEVICE inline double fma(double x, double y, double z) { return ::fma(x, y, z); } -XSF_HOST_DEVICE inline double copysign(double x, double y) { return ::copysign(x, y); } -XSF_HOST_DEVICE inline double modf(double value, double *iptr) { return ::modf(value, iptr); } -XSF_HOST_DEVICE inline double fmax(double x, double y) { return ::fmax(x, y); } -XSF_HOST_DEVICE inline double fmin(double x, double y) { return ::fmin(x, y); } -XSF_HOST_DEVICE inline double log10(double num) { return ::log10(num); } -XSF_HOST_DEVICE inline double log1p(double num) { return ::log1p(num); } -XSF_HOST_DEVICE inline double frexp(double num, int *exp) { return ::frexp(num, exp); } -XSF_HOST_DEVICE inline double ldexp(double num, int exp) { return ::ldexp(num, exp); } -XSF_HOST_DEVICE inline double fmod(double x, double y) { return ::fmod(x, y); } -XSF_HOST_DEVICE inline double nextafter(double from, double to) { return ::nextafter(from, to); } -#endif - -template -XSF_HOST_DEVICE void swap(T &a, T &b) { - cuda::std::swap(a, b); -} - -// Reimplement std::clamp until it's available in CuPy -template -XSF_HOST_DEVICE constexpr T clamp(T &v, T &lo, T &hi) { - return v < lo ? lo : (v > hi ? lo : v); -} - -template -using numeric_limits = cuda::std::numeric_limits; - -// Must use thrust for complex types in order to support CuPy -template -using complex = thrust::complex; - -template -XSF_HOST_DEVICE T abs(const complex &z) { - return thrust::abs(z); -} - -template -XSF_HOST_DEVICE complex exp(const complex &z) { - return thrust::exp(z); -} - -template -XSF_HOST_DEVICE complex log(const complex &z) { - return thrust::log(z); -} - -template -XSF_HOST_DEVICE T norm(const complex &z) { - return thrust::norm(z); -} - -template -XSF_HOST_DEVICE complex sqrt(const complex &z) { - return thrust::sqrt(z); -} - -template -XSF_HOST_DEVICE complex conj(const complex &z) { - return thrust::conj(z); -} - -template -XSF_HOST_DEVICE complex pow(const complex &x, const complex &y) { - return thrust::pow(x, y); -} - -template -XSF_HOST_DEVICE complex pow(const complex &x, const T &y) { - return thrust::pow(x, y); -} - -// Other types and utilities -template -using is_floating_point = cuda::std::is_floating_point; - -template -using enable_if = cuda::std::enable_if; - -template -using decay = cuda::std::decay; - -template -using invoke_result = cuda::std::invoke_result; - -template -using pair = cuda::std::pair; - -template -using tuple = cuda::std::tuple; - -using cuda::std::ptrdiff_t; -using cuda::std::size_t; -using cuda::std::uint64_t; - -#define XSF_ASSERT(a) - -} // namespace std - -#else -#define XSF_HOST_DEVICE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef DEBUG -#define XSF_ASSERT(a) assert(a) -#else -#define XSF_ASSERT(a) -#endif - -namespace xsf { - -// basic -using std::abs; - -// exponential -using std::exp; - -// power -using std::sqrt; - -// trigonometric -using std::cos; -using std::sin; - -// floating-point manipulation -using std::copysign; - -// classification and comparison -using std::isfinite; -using std::isinf; -using std::isnan; -using std::signbit; - -// complex -using std::imag; -using std::real; - -template -struct remove_complex { - using type = T; -}; - -template -struct remove_complex> { - using type = T; -}; - -template -using remove_complex_t = typename remove_complex::type; - -template -struct complex_type { - using type = std::complex; -}; - -template -using complex_type_t = typename complex_type::type; - -template -using complex = complex_type_t; - -} // namespace xsf - -#endif diff --git a/scipy/special/xsf/digamma.h b/scipy/special/xsf/digamma.h deleted file mode 100644 index db51362ce039..000000000000 --- a/scipy/special/xsf/digamma.h +++ /dev/null @@ -1,205 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * Original header comment appears below. - */ - -/* An implementation of the digamma function for complex arguments. - * - * Author: Josh Wilson - * - * Distributed under the same license as Scipy. - * - * Sources: - * [1] "The Digital Library of Mathematical Functions", dlmf.nist.gov - * - * [2] mpmath (version 0.19), http://mpmath.org - */ - -#pragma once - -#include "cephes/psi.h" -#include "cephes/zeta.h" -#include "config.h" -#include "error.h" -#include "trig.h" - -namespace xsf { -namespace detail { - // All of the following were computed with mpmath - // Location of the positive root - constexpr double digamma_posroot = 1.4616321449683623; - // Value of the positive root - constexpr double digamma_posrootval = -9.2412655217294275e-17; - // Location of the negative root - constexpr double digamma_negroot = -0.504083008264455409; - // Value of the negative root - constexpr double digamma_negrootval = 7.2897639029768949e-17; - - template - XSF_HOST_DEVICE T digamma_zeta_series(T z, double root, double rootval) { - T res = rootval; - T coeff = -1.0; - - z = z - root; - T term; - for (int n = 1; n < 100; n++) { - coeff *= -z; - term = coeff * cephes::zeta(n + 1, root); - res += term; - if (std::abs(term) < std::numeric_limits::epsilon() * std::abs(res)) { - break; - } - } - return res; - } - - XSF_HOST_DEVICE inline std::complex - digamma_forward_recurrence(std::complex z, std::complex psiz, int n) { - /* Compute digamma(z + n) using digamma(z) using the recurrence - * relation - * - * digamma(z + 1) = digamma(z) + 1/z. - * - * See https://dlmf.nist.gov/5.5#E2 */ - std::complex res = psiz; - - for (int k = 0; k < n; k++) { - res += 1.0 / (z + static_cast(k)); - } - return res; - } - - XSF_HOST_DEVICE inline std::complex - digamma_backward_recurrence(std::complex z, std::complex psiz, int n) { - /* Compute digamma(z - n) using digamma(z) and a recurrence relation. */ - std::complex res = psiz; - - for (int k = 1; k < n + 1; k++) { - res -= 1.0 / (z - static_cast(k)); - } - return res; - } - - XSF_HOST_DEVICE inline std::complex digamma_asymptotic_series(std::complex z) { - /* Evaluate digamma using an asymptotic series. See - * - * https://dlmf.nist.gov/5.11#E2 */ - double bernoulli2k[] = {0.166666666666666667, -0.0333333333333333333, 0.0238095238095238095, - -0.0333333333333333333, 0.0757575757575757576, -0.253113553113553114, - 1.16666666666666667, -7.09215686274509804, 54.9711779448621554, - -529.124242424242424, 6192.12318840579710, -86580.2531135531136, - 1425517.16666666667, -27298231.0678160920, 601580873.900642368, - -15116315767.0921569}; - std::complex rzz = 1.0 / z / z; - std::complex zfac = 1.0; - std::complex term; - std::complex res; - - if (!(std::isfinite(z.real()) && std::isfinite(z.imag()))) { - /* Check for infinity (or nan) and return early. - * Result of division by complex infinity is implementation dependent. - * and has been observed to vary between C++ stdlib and CUDA stdlib. - */ - return std::log(z); - } - - res = std::log(z) - 0.5 / z; - - for (int k = 1; k < 17; k++) { - zfac *= rzz; - term = -bernoulli2k[k - 1] * zfac / (2 * static_cast(k)); - res += term; - if (std::abs(term) < std::numeric_limits::epsilon() * std::abs(res)) { - break; - } - } - return res; - } - -} // namespace detail - -XSF_HOST_DEVICE inline double digamma(double z) { - /* Wrap Cephes' psi to take advantage of the series expansion around - * the smallest negative zero. - */ - if (std::abs(z - detail::digamma_negroot) < 0.3) { - return detail::digamma_zeta_series(z, detail::digamma_negroot, detail::digamma_negrootval); - } - return cephes::psi(z); -} - -XSF_HOST_DEVICE inline float digamma(float z) { return static_cast(digamma(static_cast(z))); } - -XSF_HOST_DEVICE inline std::complex digamma(std::complex z) { - /* - * Compute the digamma function for complex arguments. The strategy - * is: - * - * - Around the two zeros closest to the origin (posroot and negroot) - * use a Taylor series with precomputed zero order coefficient. - * - If close to the origin, use a recurrence relation to step away - * from the origin. - * - If close to the negative real axis, use the reflection formula - * to move to the right halfplane. - * - If |z| is large (> 16), use the asymptotic series. - * - If |z| is small, use a recurrence relation to make |z| large - * enough to use the asymptotic series. - */ - double absz = std::abs(z); - std::complex res = 0; - /* Use the asymptotic series for z away from the negative real axis - * with abs(z) > smallabsz. */ - int smallabsz = 16; - /* Use the reflection principle for z with z.real < 0 that are within - * smallimag of the negative real axis. - * int smallimag = 6 # unused below except in a comment */ - - if (z.real() <= 0.0 && std::ceil(z.real()) == z) { - // Poles - set_error("digamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (std::abs(z - detail::digamma_negroot) < 0.3) { - // First negative root. - return detail::digamma_zeta_series(z, detail::digamma_negroot, detail::digamma_negrootval); - } - - if (z.real() < 0 and std::abs(z.imag()) < smallabsz) { - /* Reflection formula for digamma. See - * - *https://dlmf.nist.gov/5.5#E4 - */ - res = -M_PI * cospi(z) / sinpi(z); - z = 1.0 - z; - absz = std::abs(z); - } - - if (absz < 0.5) { - /* Use one step of the recurrence relation to step away from - * the pole. */ - res = -1.0 / z; - z += 1.0; - absz = std::abs(z); - } - - if (std::abs(z - detail::digamma_posroot) < 0.5) { - res += detail::digamma_zeta_series(z, detail::digamma_posroot, detail::digamma_posrootval); - } else if (absz > smallabsz) { - res += detail::digamma_asymptotic_series(z); - } else if (z.real() >= 0.0) { - double n = std::trunc(smallabsz - absz) + 1; - std::complex init = detail::digamma_asymptotic_series(z + n); - res += detail::digamma_backward_recurrence(z + n, init, n); - } else { - // z.real() < 0, absz < smallabsz, and z.imag() > smallimag - double n = std::trunc(smallabsz - absz) - 1; - std::complex init = detail::digamma_asymptotic_series(z - n); - res += detail::digamma_forward_recurrence(z - n, init, n); - } - return res; -} - -XSF_HOST_DEVICE inline std::complex digamma(std::complex z) { - return static_cast>(digamma(static_cast>(z))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/dual.h b/scipy/special/xsf/dual.h deleted file mode 100644 index f0f79de28902..000000000000 --- a/scipy/special/xsf/dual.h +++ /dev/null @@ -1,670 +0,0 @@ -#pragma once - -#include "binom.h" -#include "config.h" -#include "numbers.h" - -namespace xsf { - -namespace detail { - template - constexpr T small_binom_coefs[3][3] = { - {T(1.0), T(0.0), T(0.0)}, {T(1.0), T(1.0), T(0.0)}, {T(1.0), T(2.0), T(1.0)} - }; - - /* Since we only compute derivatives up to order 2, we only need - * Binomial coefficients with n <= 2 for use in the General - * Leibniz rule. Get these from a lookup table. */ - template - T fast_binom(size_t n, size_t k) { - if ((n <= 2) && (k <= 2)) { - return small_binom_coefs[n][k]; - } - return T(xsf::binom(static_cast(n), static_cast(k))); - } -} // namespace detail - -template -class dual; - -template -class dual { - public: - using value_type = T; - - private: - value_type data[Order + 1]; - - public: - dual() = default; - - dual(value_type value) { - data[0] = value; - for (size_t i = 1; i <= Order; ++i) { - data[i] = 0; - } - } - - dual(std::initializer_list data) { - for (auto it = data.begin(); it != data.end(); ++it) { - this->data[it - data.begin()] = *it; - } - for (size_t i = data.size(); i <= Order; ++i) { - this->data[i] = 0; - } - } - - template - dual(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] = other[i]; - } - } - - dual &operator=(const value_type &other) { - data[0] = other; - for (size_t i = 1; i <= Order; ++i) { - data[i] = 0; - } - - return *this; - } - - dual &operator+=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] += other.data[i]; - } - - return *this; - } - - dual &operator+=(const value_type &other) { - data[0] += other; - - return *this; - } - - dual &operator-=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] -= other.data[i]; - } - - return *this; - } - - dual &operator-=(const value_type &other) { - data[0] -= other; - - return *this; - } - - dual &operator*=(const dual &other) { - for (size_t i = Order + 1; i-- > 0;) { - data[i] *= other.data[0]; - // General Leibniz Rule - for (size_t j = 0; j < i; ++j) { - data[i] += detail::fast_binom(i, j) * data[j] * other.data[i - j]; - } - } - - return *this; - } - - dual &operator*=(const value_type &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] *= other; - } - - return *this; - } - - dual &operator/=(const dual &other) { - for (size_t i = 0; i <= Order; ++i) { - for (size_t j = 1; j <= i; ++j) { - data[i] -= detail::fast_binom(i - 1, j) * other.data[j] * data[i - j]; - } - - data[i] /= other.data[0]; - } - - return *this; - } - - dual &operator/=(const value_type &other) { - for (size_t i = 0; i <= Order; ++i) { - data[i] /= other; - } - - return *this; - } - - value_type &value() { return data[0]; } - - const value_type &value() const { return data[0]; } - - value_type &operator[](size_t i) { return data[i]; } - - const value_type &operator[](size_t i) const { return data[i]; } - - static constexpr size_t max_order() { return Order; } -}; - -template -class dual { - public: - using value_type = T; - - private: - dual data[Order0 + 1]; - - public: - dual() = default; - - dual(value_type value) { - data[0] = value; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - } - - dual(dual value) { - data[0] = value; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - } - - dual(std::initializer_list> data) { - for (auto it = data.begin(); it != data.end(); ++it) { - this->data[it - data.begin()] = *it; - } - for (size_t i = data.size(); i <= Order0; ++i) { - this->data[i] = 0; - } - } - - template - dual(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] = other[i]; - } - } - - dual &operator=(const value_type &other) { - data[0] = other; - for (size_t i = 1; i <= Order0; ++i) { - data[i] = 0; - } - - return *this; - } - - dual &operator+=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] += other.data[i]; - } - - return *this; - } - - dual &operator+=(const value_type &other) { - data[0] += other; - - return *this; - } - - dual &operator-=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] -= other.data[i]; - } - - return *this; - } - - dual &operator-=(const value_type &other) { - data[0] -= other; - - return *this; - } - - dual &operator*=(const dual &other) { - for (size_t i = Order0 + 1; i-- > 0;) { - data[i] *= other.data[0]; - // General Leibniz Rule - for (size_t j = 0; j < i; ++j) { - data[i] += detail::fast_binom(i, j) * data[j] * other.data[i - j]; - } - } - - return *this; - } - - dual &operator*=(const value_type &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] *= other; - } - - return *this; - } - - dual &operator/=(const dual &other) { - for (size_t i = 0; i <= Order0; ++i) { - for (size_t j = 1; j <= i; ++j) { - data[i] -= detail::fast_binom(i - 1, j) * other.data[j] * data[i - j]; - } - - data[i] /= other.data[0]; - } - - return *this; - } - - dual &operator/=(const value_type &other) { - for (size_t i = 0; i <= Order0; ++i) { - data[i] /= other; - } - - return *this; - } - - value_type &value() { - dual &data0 = data[0]; - - return data0.value(); - } - - const value_type &value() const { - const dual &data0 = data[0]; - - return data0.value(); - } - - dual &operator[](size_t i) { return data[i]; } - - const dual &operator[](size_t i) const { return data[i]; } - - static constexpr size_t max_order() { return std::max({Order0, Order1, Orders...}); } -}; - -template -dual operator+(const dual &x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = +x[i]; - } - - return res; -} - -template -dual operator-(const dual &x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = -x[i]; - } - - return res; -} - -template -dual operator+(const dual &lhs, const dual &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator+(const dual &lhs, const T &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator+(const T &lhs, const dual &rhs) { - dual res = lhs; - res += rhs; - - return res; -} - -template -dual operator-(const dual &lhs, const dual &rhs) { - dual res = lhs; - res -= rhs; - - return res; -} - -template -dual operator-(const dual &lhs, const T &rhs) { - dual res = lhs; - res -= rhs; - - return res; -} - -template -dual operator*(const dual &lhs, const dual &rhs) { - dual res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual, Orders...> &lhs, const dual &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual &lhs, const dual, Orders...> &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual operator*(const dual &lhs, const T &rhs) { - dual res = lhs; - res *= rhs; - - return res; -} - -template -dual, Orders...> operator*(const dual &lhs, const complex &rhs) { - dual, Orders...> res = lhs; - res *= rhs; - - return res; -} - -template -dual operator*(const T &lhs, const dual &rhs) { - dual res = rhs; - res *= lhs; - - return res; -} - -template -dual, Orders...> operator*(const complex &lhs, const dual &rhs) { - dual, Orders...> res = rhs; - res *= lhs; - - return res; -} - -template -dual operator/(const dual &lhs, const dual &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -dual operator/(const dual &lhs, const T &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -dual operator/(const T &lhs, const dual &rhs) { - dual res = lhs; - res /= rhs; - - return res; -} - -template -bool operator==(const dual &lhs, const dual &rhs) { - return lhs.value() == rhs.value(); -} - -template -bool operator==(const dual &lhs, const U &rhs) { - return lhs.value() == rhs; -} - -template -bool operator!=(const dual &lhs, const dual &rhs) { - return lhs.value() != rhs.value(); -} - -template -bool operator<(const dual &lhs, const dual &rhs) { - return lhs.value() < rhs.value(); -} - -template -bool operator>(const dual &lhs, const dual &rhs) { - return lhs.value() > rhs.value(); -} - -template -bool operator<=(const dual &lhs, const dual &rhs) { - return lhs.value() <= rhs.value(); -} - -template -bool operator>=(const dual &lhs, const dual &rhs) { - return lhs.value() >= rhs.value(); -} - -template -dual dual_var(T value, size_t dim = 0) { - // dim must be zero - - if constexpr (Order >= 1) { - return {value, 1}; - } - - return value; -} - -template -dual dual_var(T value, size_t dim = 0) { - if (dim == 0) { - if constexpr (Orders0 >= 1) { - return {value, 1}; - } - - return value; - } - - return dual_var(value, dim - 1); -} - -template -dual dual_taylor_series(const T (&coef)[N], const dual &x, T a) { - dual res = coef[0]; - if constexpr (N >= 2) { - dual y = x - a; - T denom = 1; // factorial - - res += y * coef[1]; - - for (size_t i = 2; i < N; ++i) { - y *= x - a; - denom *= T(i); - - res += y * coef[i] / denom; - } - } - - return res; -} - -template -dual abs(dual z) { - if (z.value() < 0) { - return dual_taylor_series({abs(z.value()), T(-1)}, z, z.value()); - } - - return dual_taylor_series({abs(z.value()), T(1)}, z, z.value()); -} - -template -dual abs(dual, Orders...> z) { - return dual_taylor_series({abs(z.value()), real(z.value()) / abs(z.value())}, z, z.value()); -} - -template -dual exp(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {exp(x.value())}; - for (size_t i = 1; i <= MaxOrder; ++i) { - coef[i] = coef[0]; - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual sqrt(const dual &z) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {sqrt(z.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = T(1) / (T(2) * coef[0]); - - if constexpr (MaxOrder >= 2) { - coef[2] = -T(1) / (T(4) * coef[0] * z.value()); - } - } - - return dual_taylor_series(coef, z, z.value()); -} - -template -dual sin(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {sin(x.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = cos(x.value()); - - for (size_t i = 2; i <= MaxOrder; ++i) { - coef[i] = -coef[i - 2]; - } - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual cos(const dual &x) { - static constexpr size_t MaxOrder = dual::max_order(); - - T coef[MaxOrder + 1] = {cos(x.value())}; - if constexpr (MaxOrder >= 1) { - coef[1] = -sin(x.value()); - - for (size_t i = 2; i <= MaxOrder; ++i) { - coef[i] = -coef[i - 2]; - } - } - - return dual_taylor_series(coef, x, x.value()); -} - -template -dual copysign(const dual &x, const dual &y) { - if (signbit(x.value()) == signbit(y.value())) { - return x; - } - - return -x; -} - -template -bool isfinite(const dual &x) { - return isfinite(x.value()); -} - -template -bool isinf(const dual &x) { - return isinf(x.value()); -} - -template -bool isnan(const dual &x) { - return isnan(x.value()); -} - -template -dual real(dual, Order0, Orders...> z) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = real(z[i]); - } - - return res; -} - -template -dual real(dual x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = real(x[i]); - } - - return res; -} - -template -dual imag(dual, Order0, Orders...> z) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = imag(z[i]); - } - - return res; -} - -template -dual imag(dual x) { - dual res; - for (size_t i = 0; i <= Order0; ++i) { - res[i] = imag(x[i]); - } - - return res; -} - -template -struct complex_type> { - using type = dual::type, Orders...>; -}; - -template -struct remove_dual { - using type = T; -}; - -template -struct remove_dual> { - using type = T; -}; - -template -using remove_dual_t = typename remove_dual::type; - -namespace numbers { - - template - dual, Orders...> i_v> = i_v; - -} // namespace numbers -} // namespace xsf diff --git a/scipy/special/xsf/ellip.h b/scipy/special/xsf/ellip.h deleted file mode 100644 index 814f95d8b12e..000000000000 --- a/scipy/special/xsf/ellip.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include "cephes/ellie.h" -#include "cephes/ellik.h" -#include "cephes/ellpe.h" -#include "cephes/ellpj.h" -#include "cephes/ellpk.h" -#include "config.h" - -namespace xsf { - -inline double ellipe(double m) { return cephes::ellpe(m); } - -inline float ellipe(float m) { return ellipe(static_cast(m)); } - -inline double ellipeinc(double phi, double m) { return cephes::ellie(phi, m); } - -inline float ellipeinc(float phi, float m) { return ellipeinc(static_cast(phi), static_cast(m)); } - -inline void ellipj(double u, double m, double &sn, double &cn, double &dn, double &ph) { - cephes::ellpj(u, m, &sn, &cn, &dn, &ph); -} - -inline void ellipj(float u, float m, float &sn, float &cn, float &dn, float &ph) { - double sn_double; - double cn_double; - double dn_double; - double ph_double; - ellipj(static_cast(u), static_cast(m), sn_double, cn_double, dn_double, ph_double); - - sn = sn_double; - cn = cn_double; - dn = dn_double; - ph = ph_double; -} - -inline double ellipkinc(double phi, double m) { return cephes::ellik(phi, m); } - -inline float ellipkinc(float phi, float m) { return ellipkinc(static_cast(phi), static_cast(m)); } - -XSF_HOST_DEVICE inline double ellipk(double m) { return cephes::ellpk(1.0 - m); } - -XSF_HOST_DEVICE inline float ellipk(float m) { return ellipk(static_cast(m)); } - -inline double ellipkm1(double p) { return cephes::ellpk(p); } - -inline float ellipkm1(float p) { return ellipkm1(static_cast(p)); } - -} // namespace xsf diff --git a/scipy/special/xsf/erf.h b/scipy/special/xsf/erf.h deleted file mode 100644 index 0ec390dde3d6..000000000000 --- a/scipy/special/xsf/erf.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include "faddeeva.h" -#include "cephes/ndtr.h" -#include "config.h" - -namespace xsf { - -inline double erf(double x) { return cephes::erf(x); } - -inline float erf(float x) { return erf(static_cast(x)); } - -inline std::complex erf(std::complex z) { return Faddeeva::erf(z); } - -inline std::complex erf(std::complex x) { - return static_cast>(erf(static_cast>(x))); -} - -inline double erfc(double x) { return cephes::erfc(x); } - -inline float erfc(float x) { return erfc(static_cast(x)); } - -inline std::complex erfc(std::complex z) { return Faddeeva::erfc(z); } - -inline std::complex erfc(std::complex x) { - return static_cast>(erfc(static_cast>(x))); -} - -inline double erfcx(double x) { return Faddeeva::erfcx(x); } - -inline float erfcx(float x) { return erfcx(static_cast(x)); } - -inline std::complex erfcx(std::complex z) { return Faddeeva::erfcx(z); } - -inline std::complex erfcx(std::complex x) { - return static_cast>(erfcx(static_cast>(x))); -} - -inline double erfi(double x) { return Faddeeva::erfi(x); } - -inline float erfi(float x) { return erfi(static_cast(x)); } - -inline std::complex erfi(std::complex z) { return Faddeeva::erfi(z); } - -inline std::complex erfi(std::complex z) { - return static_cast>(erfi(static_cast>(z))); -} - -inline double voigt_profile(double x, double sigma, double gamma) { - const double INV_SQRT_2 = 0.707106781186547524401; - const double SQRT_2PI = 2.5066282746310002416123552393401042; - - if (sigma == 0) { - if (gamma == 0) { - if (std::isnan(x)) - return x; - if (x == 0) - return INFINITY; - return 0; - } - return gamma / M_PI / (x * x + gamma * gamma); - } - if (gamma == 0) { - return 1 / SQRT_2PI / sigma * exp(-(x / sigma) * (x / sigma) / 2); - } - - double zreal = x / sigma * INV_SQRT_2; - double zimag = gamma / sigma * INV_SQRT_2; - std::complex z(zreal, zimag); - std::complex w = Faddeeva::w(z); - return w.real() / sigma / SQRT_2PI; -} - -inline float voigt_profile(float x, float sigma, float gamma) { - return voigt_profile(static_cast(x), static_cast(sigma), static_cast(gamma)); -} - -inline std::complex wofz(std::complex z) { return Faddeeva::w(z); } - -inline std::complex wofz(std::complex x) { - return static_cast>(wofz(static_cast>(x))); -} - -inline double dawsn(double x) { return Faddeeva::Dawson(x); } - -inline float dawsn(float x) { return dawsn(static_cast(x)); } - -inline std::complex dawsn(std::complex z) { return Faddeeva::Dawson(z); } - -inline std::complex dawsn(std::complex x) { - return static_cast>(dawsn(static_cast>(x))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/error.h b/scipy/special/xsf/error.h deleted file mode 100644 index 7221b5e6c405..000000000000 --- a/scipy/special/xsf/error.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -typedef enum { - SF_ERROR_OK = 0, /* no error */ - SF_ERROR_SINGULAR, /* singularity encountered */ - SF_ERROR_UNDERFLOW, /* floating point underflow */ - SF_ERROR_OVERFLOW, /* floating point overflow */ - SF_ERROR_SLOW, /* too many iterations required */ - SF_ERROR_LOSS, /* loss of precision */ - SF_ERROR_NO_RESULT, /* no result obtained */ - SF_ERROR_DOMAIN, /* out of domain */ - SF_ERROR_ARG, /* invalid input parameter */ - SF_ERROR_OTHER, /* unclassified error */ - SF_ERROR_MEMORY, /* memory allocation failed */ - SF_ERROR__LAST -} sf_error_t; - -#ifdef __cplusplus - -#include "config.h" - -namespace xsf { - -#ifndef SP_SPECFUN_ERROR -XSF_HOST_DEVICE inline void set_error(const char *func_name, sf_error_t code, const char *fmt, ...) { - // nothing -} -#else -void set_error(const char *func_name, sf_error_t code, const char *fmt, ...); -#endif - -template -XSF_HOST_DEVICE void set_error_and_nan(const char *name, sf_error_t code, T &value) { - if (code != SF_ERROR_OK) { - set_error(name, code, nullptr); - - if (code == SF_ERROR_DOMAIN || code == SF_ERROR_OVERFLOW || code == SF_ERROR_NO_RESULT) { - value = std::numeric_limits::quiet_NaN(); - } - } -} - -template -XSF_HOST_DEVICE void set_error_and_nan(const char *name, sf_error_t code, std::complex &value) { - if (code != SF_ERROR_OK) { - set_error(name, code, nullptr); - - if (code == SF_ERROR_DOMAIN || code == SF_ERROR_OVERFLOW || code == SF_ERROR_NO_RESULT) { - value.real(std::numeric_limits::quiet_NaN()); - value.imag(std::numeric_limits::quiet_NaN()); - } - } -} - -} // namespace xsf - -#endif diff --git a/scipy/special/xsf/evalpoly.h b/scipy/special/xsf/evalpoly.h deleted file mode 100644 index b126fb608fae..000000000000 --- a/scipy/special/xsf/evalpoly.h +++ /dev/null @@ -1,47 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * - * Original author: Josh Wilson, 2016. - */ - -/* Evaluate polynomials. - * - * All of the coefficients are stored in reverse order, i.e. if the - * polynomial is - * - * u_n x^n + u_{n - 1} x^{n - 1} + ... + u_0, - * - * then coeffs[0] = u_n, coeffs[1] = u_{n - 1}, ..., coeffs[n] = u_0. - * - * References - * ---------- - * [1] Knuth, "The Art of Computer Programming, Volume II" - */ - -#pragma once - -#include "config.h" - -namespace xsf { - -XSF_HOST_DEVICE inline std::complex cevalpoly(const double *coeffs, int degree, std::complex z) { - /* Evaluate a polynomial with real coefficients at a complex point. - * - * Uses equation (3) in section 4.6.4 of [1]. Note that it is more - * efficient than Horner's method. - */ - double a = coeffs[0]; - double b = coeffs[1]; - double r = 2 * z.real(); - double s = std::norm(z); - double tmp; - - for (int j = 2; j < degree + 1; j++) { - tmp = b; - b = std::fma(-s, a, coeffs[j]); - a = std::fma(r, a, tmp); - } - - return z * a + b; -} - -} // namespace xsf diff --git a/scipy/special/xsf/exp.h b/scipy/special/xsf/exp.h deleted file mode 100644 index 0d8551109084..000000000000 --- a/scipy/special/xsf/exp.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include "xsf/cephes/exp10.h" -#include "xsf/cephes/exp2.h" - -namespace xsf { - -inline double expm1(double x) { return cephes::expm1(x); } - -inline float expm1(float x) { return expm1(static_cast(x)); } - -// cexpm1(z) = cexp(z) - 1 -// -// The imaginary part of this is easily computed via exp(z.real)*sin(z.imag) -// The real part is difficult to compute when there is cancellation e.g. when -// z.real = -log(cos(z.imag)). There isn't a way around this problem that -// doesn't involve computing exp(z.real) and/or cos(z.imag) to higher -// precision. -inline std::complex expm1(std::complex z) { - if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) { - return std::exp(z) - 1.0; - } - - double x; - double ezr = 0; - if (std::real(z) <= -40) { - x = -1.0; - } else { - ezr = expm1(std::real(z)); - x = ezr * std::cos(std::imag(z)) + cosm1(std::imag(z)); - } - - // don't compute exp(zr) too, unless necessary - double y; - if (std::real(z) > -1.0) { - y = (ezr + 1.0) * sin(std::imag(z)); - } else { - y = exp(std::real(z)) * sin(std::imag(z)); - } - - return std::complex{x, y}; -} - -inline std::complex expm1(std::complex z) { - return static_cast>(expm1(static_cast>(z))); -} - -double exp2(double x) { return cephes::exp2(x); } - -float exp2(float x) { return exp2(static_cast(x)); } - -double exp10(double x) { return cephes::exp10(x); } - -float exp10(float x) { return exp10(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/expint.h b/scipy/special/xsf/expint.h deleted file mode 100644 index 600448aeb7f7..000000000000 --- a/scipy/special/xsf/expint.h +++ /dev/null @@ -1,266 +0,0 @@ -/* The functions exp1, expi below are based on translations of the Fortran code - * by Shanjie Zhang and Jianming Jin from the book - * - * Shanjie Zhang, Jianming Jin, - * Computation of Special Functions, - * Wiley, 1996, - * ISBN: 0-471-11963-6, - * LC: QA351.C45. - */ - -#pragma once - -#include "config.h" -#include "error.h" - -#include "cephes/const.h" - - -namespace xsf { - - -XSF_HOST_DEVICE inline double exp1(double x) { - // ============================================ - // Purpose: Compute exponential integral E1(x) - // Input : x --- Argument of E1(x) - // Output: E1 --- E1(x) ( x > 0 ) - // ============================================ - int k, m; - double e1, r, t, t0; - constexpr double ga = cephes::detail::SCIPY_EULER; - - if (x == 0.0) { - return std::numeric_limits::infinity(); - } - if (x <= 1.0) { - e1 = 1.0; - r = 1.0; - for (k = 1; k < 26; k++) { - r = -r*k*x/std::pow(k+1.0, 2); - e1 += r; - if (std::abs(r) <= std::abs(e1)*1e-15) { break; } - } - return -ga - std::log(x) + x*e1; - } - m = 20 + (int)(80.0/x); - t0 = 0.0; - for (k = m; k > 0; k--) { - t0 = k / (1.0 + k / (x+t0)); - } - t = 1.0 / (x + t0); - return std::exp(-x)*t; -} - -XSF_HOST_DEVICE inline float exp1(float x) { return exp1(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { - // ==================================================== - // Purpose: Compute complex exponential integral E1(z) - // Input : z --- Argument of E1(z) - // Output: CE1 --- E1(z) - // ==================================================== - constexpr double el = cephes::detail::SCIPY_EULER; - int k; - std::complex ce1, cr, zc, zd, zdc; - double x = z.real(); - double a0 = std::abs(z); - // Continued fraction converges slowly near negative real axis, - // so use power series in a wedge around it until radius 40.0 - double xt = -2.0*std::abs(z.imag()); - - if (a0 == 0.0) { return std::numeric_limits::infinity(); } - if ((a0 < 5.0) || ((x < xt) && (a0 < 40.0))) { - // Power series - ce1 = 1.0; - cr = 1.0; - for (k = 1; k < 501; k++) { - cr = -cr*z*static_cast(k / std::pow(k + 1, 2)); - ce1 += cr; - if (std::abs(cr) < std::abs(ce1)*1e-15) { break; } - } - if ((x <= 0.0) && (z.imag() == 0.0)) { - //Careful on the branch cut -- use the sign of the imaginary part - // to get the right sign on the factor if pi. - ce1 = -el - std::log(-z) + z*ce1 - std::copysign(M_PI, z.imag())*std::complex(0.0, 1.0); - } else { - ce1 = -el - std::log(z) + z*ce1; - } - } else { - // Continued fraction https://dlmf.nist.gov/6.9 - // 1 1 1 2 2 3 3 - // E1 = exp(-z) * ----- ----- ----- ----- ----- ----- ----- ... - // Z + 1 + Z + 1 + Z + 1 + Z + - zc = 0.0; - zd = static_cast(1) / z; - zdc = zd; - zc += zdc; - for (k = 1; k < 501; k++) { - zd = static_cast(1) / (zd*static_cast(k) + static_cast(1)); - zdc *= (zd - static_cast(1)); - zc += zdc; - - zd = static_cast(1) / (zd*static_cast(k) + z); - zdc *= (z*zd - static_cast(1)); - zc += zdc; - if ((std::abs(zdc) <= std::abs(zc)*1e-15) && (k > 20)) { break; } - } - ce1 = std::exp(-z)*zc; - if ((x <= 0.0) && (z.imag() == 0.0)) { - ce1 -= M_PI*std::complex(0.0, 1.0); - } - } - return ce1; -} - -XSF_HOST_DEVICE inline std::complex exp1(std::complex z) { - return static_cast>(exp1(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double expi(double x) { - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - constexpr double ga = cephes::detail::SCIPY_EULER; - double ei, r; - - if (x == 0.0) { - ei = -std::numeric_limits::infinity(); - } else if (x < 0) { - ei = -exp1(-x); - } else if (std::abs(x) <= 40.0) { - // Power series around x=0 - ei = 1.0; - r = 1.0; - - for (int k = 1; k <= 100; k++) { - r = r * k * x / ((k + 1.0) * (k + 1.0)); - ei += r; - if (std::abs(r / ei) <= 1.0e-15) { break; } - } - ei = ga + std::log(x) + x * ei; - } else { - // Asymptotic expansion (the series is not convergent) - ei = 1.0; - r = 1.0; - for (int k = 1; k <= 20; k++) { - r = r * k / x; - ei += r; - } - ei = std::exp(x) / x * ei; - } - return ei; -} - -XSF_HOST_DEVICE inline float expi(float x) { return expi(static_cast(x)); } - -std::complex expi(std::complex z) { - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Complex argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - std::complex cei; - cei = - exp1(-z); - if (z.imag() > 0.0) { - cei += std::complex(0.0, M_PI); - } else if (z.imag() < 0.0 ) { - cei -= std::complex(0.0, M_PI); - } else { - if (z.real() > 0.0) { - cei += std::complex(0.0, copysign(M_PI, z.imag())); - } - } - return cei; -} - - -XSF_HOST_DEVICE inline std::complex expi(std::complex z) { - return static_cast>(expi(static_cast>(z))); -} - -namespace detail { - - // - // Compute a factor of the exponential integral E1. - // This is used in scaled_exp1(x) for moderate values of x. - // - // The function uses the continued fraction expansion given in equation 5.1.22 - // of Abramowitz & Stegun, "Handbook of Mathematical Functions". - // For n=1, this is - // - // E1(x) = exp(-x)*C(x) - // - // where C(x), expressed in the notation used in A&S, is the continued fraction - // - // 1 1 1 2 2 3 3 - // C(x) = --- --- --- --- --- --- --- ... - // x + 1 + x + 1 + x + 1 + x + - // - // Here, we pull a factor of 1/z out of C(x), so - // - // E1(x) = (exp(-x)/x)*F(x) - // - // and a bit of algebra gives the continued fraction expansion of F(x) to be - // - // 1 1 1 2 2 3 3 - // F(x) = --- --- --- --- --- --- --- ... - // 1 + x + 1 + x + 1 + x + 1 + - // - XSF_HOST_DEVICE inline double expint1_factor_cont_frac(double x) { - // The number of terms to use in the truncated continued fraction - // depends on x. Larger values of x require fewer terms. - int m = 20 + (int) (80.0 / x); - double t0 = 0.0; - for (int k = m; k > 0; --k) { - t0 = k / (x + k / (1 + t0)); - } - return 1 / (1 + t0); - } - -} // namespace detail - -// -// Scaled version of the exponential integral E_1(x). -// -// Factor E_1(x) as -// -// E_1(x) = exp(-x)/x * F(x) -// -// This function computes F(x). -// -// F(x) has the properties: -// * F(0) = 0 -// * F is increasing on [0, inf) -// * lim_{x->inf} F(x) = 1. -// -XSF_HOST_DEVICE inline double scaled_exp1(double x) { - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - return 0.0; - } - - if (x <= 1) { - // For small x, the naive implementation is sufficiently accurate. - return x * std::exp(x) * exp1(x); - } - - if (x <= 1250) { - // For moderate x, use the continued fraction expansion. - return detail::expint1_factor_cont_frac(x); - } - - // For large x, use the asymptotic expansion. This is equation 5.1.51 - // from Abramowitz & Stegun, "Handbook of Mathematical Functions". - return 1 + (-1 + (2 + (-6 + (24 - 120 / x) / x) / x) / x) / x; -} - -XSF_HOST_DEVICE inline float scaled_exp1(float x) { return scaled_exp1(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/faddeeva.h b/scipy/special/xsf/faddeeva.h deleted file mode 100644 index 78b51dc84de0..000000000000 --- a/scipy/special/xsf/faddeeva.h +++ /dev/null @@ -1,1758 +0,0 @@ -/* Copyright (c) 2012 Massachusetts Institute of Technology - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * 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 AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* Available at: http://ab-initio.mit.edu/Faddeeva - - Computes various error functions (erf, erfc, erfi, erfcx), - including the Dawson integral, in the complex plane, based - on algorithms for the computation of the Faddeeva function - w(z) = exp(-z^2) * erfc(-i*z). - Given w(z), the error functions are mostly straightforward - to compute, except for certain regions where we have to - switch to Taylor expansions to avoid cancellation errors - [e.g. near the origin for erf(z)]. - - To compute the Faddeeva function, we use a combination of two - algorithms: - - For sufficiently large |z|, we use a continued-fraction expansion - for w(z) similar to those described in: - - Walter Gautschi, "Efficient computation of the complex error - function," SIAM J. Numer. Anal. 7(1), pp. 187-198 (1970) - - G. P. M. Poppe and C. M. J. Wijers, "More efficient computation - of the complex error function," ACM Trans. Math. Soft. 16(1), - pp. 38-46 (1990). - - Unlike those papers, however, we switch to a completely different - algorithm for smaller |z|: - - Mofreh R. Zaghloul and Ahmed N. Ali, "Algorithm 916: Computing the - Faddeyeva and Voigt Functions," ACM Trans. Math. Soft. 38(2), 15 - (2011). - - (I initially used this algorithm for all z, but it turned out to be - significantly slower than the continued-fraction expansion for - larger |z|. On the other hand, it is competitive for smaller |z|, - and is significantly more accurate than the Poppe & Wijers code - in some regions, e.g. in the vicinity of z=1+1i.) - - Note that this is an INDEPENDENT RE-IMPLEMENTATION of these algorithms, - based on the description in the papers ONLY. In particular, I did - not refer to the authors' Fortran or Matlab implementations, respectively, - (which are under restrictive ACM copyright terms and therefore unusable - in free/open-source software). - - Steven G. Johnson, Massachusetts Institute of Technology - http://math.mit.edu/~stevenj - October 2012. - - -- Note that Algorithm 916 assumes that the erfc(x) function, - or rather the scaled function erfcx(x) = exp(x*x)*erfc(x), - is supplied for REAL arguments x. I originally used an - erfcx routine derived from DERFC in SLATEC, but I have - since replaced it with a much faster routine written by - me which uses a combination of continued-fraction expansions - and a lookup table of Chebyshev polynomials. For speed, - I implemented a similar algorithm for Im[w(x)] of real x, - since this comes up frequently in the other error functions. - - A small test program is included the end, which checks - the w(z) etc. results against several known values. To compile - the test function, compile with -DTEST_FADDEEVA (that is, - #define TEST_FADDEEVA). - - REVISION HISTORY: - 4 October 2012: Initial public release (SGJ) - 5 October 2012: Revised (SGJ) to fix spelling error, - start summation for large x at round(x/a) (> 1) - rather than ceil(x/a) as in the original - paper, which should slightly improve performance - (and, apparently, slightly improves accuracy) - 19 October 2012: Revised (SGJ) to fix bugs for large x, large -y, - and 15 1e154. - Set relerr argument to min(relerr,0.1). - 27 October 2012: Enhance accuracy in Re[w(z)] taken by itself, - by switching to Alg. 916 in a region near - the real-z axis where continued fractions - have poor relative accuracy in Re[w(z)]. Thanks - to M. Zaghloul for the tip. - 29 October 2012: Replace SLATEC-derived erfcx routine with - completely rewritten code by me, using a very - different algorithm which is much faster. - 30 October 2012: Implemented special-case code for real z - (where real part is exp(-x^2) and imag part is - Dawson integral), using algorithm similar to erfx. - Export ImFaddeeva_w function to make Dawson's - integral directly accessible. - 3 November 2012: Provide implementations of erf, erfc, erfcx, - and Dawson functions in Faddeeva:: namespace, - in addition to Faddeeva::w. Provide header - file Faddeeva.hh. -*/ - -#pragma once - -#include -#include - -namespace Faddeeva { - -// compute w(z) = exp(-z^2) erfc(-iz) [ Faddeeva / scaled complex error func ] -std::complex w(std::complex z,double relerr=0); -double w_im(double x); // special-case code for Im[w(x)] of real x - -// Various functions that we can compute with the help of w(z) - -// compute erfcx(z) = exp(z^2) erfz(z) -std::complex erfcx(std::complex z, double relerr=0); -double erfcx(double x); // special case for real x - -// compute erf(z), the error function of complex arguments -std::complex erf(std::complex z, double relerr=0); -double erf(double x); // special case for real x - -// compute erfi(z) = -i erf(iz), the imaginary error function -std::complex erfi(std::complex z, double relerr=0); -double erfi(double x); // special case for real x - -// compute erfc(z) = 1 - erf(z), the complementary error function -std::complex erfc(std::complex z, double relerr=0); -double erfc(double x); // special case for real x - -// compute Dawson(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z) -std::complex Dawson(std::complex z, double relerr=0); -double Dawson(double x); // special case for real x - -// compute erfcx(z) = exp(z^2) erfz(z) -std::complex erfcx(std::complex z, double relerr) -{ - return w(std::complex(-imag(z), real(z))); -} - -// compute the error function erf(x) -double erf(double x) -{ - double mx2 = -x*x; - if (mx2 < -750) // underflow - return (x >= 0 ? 1.0 : -1.0); - - if (x >= 0) { - if (x < 5e-3) goto taylor; - return 1.0 - exp(mx2) * erfcx(x); - } - else { // x < 0 - if (x > -5e-3) goto taylor; - return exp(mx2) * erfcx(-x) - 1.0; - } - - // Use Taylor series for small |x|, to avoid cancellation inaccuracy - // erf(x) = 2/sqrt(pi) * x * (1 - x^2/3 + x^4/10 - ...) - taylor: - return x * (1.1283791670955125739 - + mx2 * (0.37612638903183752464 - + mx2 * 0.11283791670955125739)); -} - - -// compute the error function erf(z) -std::complex erf(std::complex z, double relerr) -{ - double x = real(z), y = imag(z); - - if (x == 0) // handle separately for speed & handling of y = Inf or NaN - return std::complex(x, // preserve sign of 0 - /* handle y -> Inf limit manually, since - exp(y^2) -> Inf but Im[w(y)] -> 0, so - IEEE will give us a NaN when it should be Inf */ - y*y > 720 ? (y > 0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()) - : exp(y*y) * w_im(y)); - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - if (mRe_z2 < -750) // underflow - return (x >= 0 ? 1.0 : -1.0); - - /* Handle positive and negative x via different formulas, - using the mirror symmetries of w, to avoid overflow/underflow - problems from multiplying exponentially large and small quantities. */ - if (x >= 0) { - if (x < 5e-3) { - if (fabs(y) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_erfi; - } - /* don't use complex exp function, since that will produce spurious NaN - values when multiplying w in an overflow situation. */ - return 1.0 - exp(mRe_z2) * - (std::complex(cos(mIm_z2), sin(mIm_z2)) - * w(std::complex(-y,x))); - } - else { // x < 0 - if (x > -5e-3) { // duplicate from above to avoid fabs(x) call - if (fabs(y) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_erfi; - } - else if (std::isnan(x)) - return std::complex(std::numeric_limits::quiet_NaN(), y == 0 ? 0 : std::numeric_limits::quiet_NaN()); - /* don't use complex exp function, since that will produce spurious NaN - values when multiplying w in an overflow situation. */ - return exp(mRe_z2) * - (std::complex(cos(mIm_z2), sin(mIm_z2)) - * w(std::complex(y,-x))) - 1.0; - } - - // Use Taylor series for small |z|, to avoid cancellation inaccuracy - // erf(z) = 2/sqrt(pi) * z * (1 - z^2/3 + z^4/10 - ...) - taylor: - { - std::complex mz2(mRe_z2, mIm_z2); // -z^2 - return z * (1.1283791670955125739 - + mz2 * (0.37612638903183752464 - + mz2 * 0.11283791670955125739)); - } - - /* for small |x| and small |xy|, - use Taylor series to avoid cancellation inaccuracy: - erf(x+iy) = erf(iy) - + 2*exp(y^2)/sqrt(pi) * - [ x * (1 - x^2 * (1+2y^2)/3 + x^4 * (3+12y^2+4y^4)/30 + ... - - i * x^2 * y * (1 - x^2 * (3+2y^2)/6 + ...) ] - where: - erf(iy) = exp(y^2) * Im[w(y)] - */ - taylor_erfi: - { - double x2 = x*x, y2 = y*y; - double expy2 = exp(y2); - return std::complex - (expy2 * x * (1.1283791670955125739 - - x2 * (0.37612638903183752464 - + 0.75225277806367504925*y2) - + x2*x2 * (0.11283791670955125739 - + y2 * (0.45135166683820502956 - + 0.15045055561273500986*y2))), - expy2 * (w_im(y) - - x2*y * (1.1283791670955125739 - - x2 * (0.56418958354775628695 - + 0.37612638903183752464*y2)))); - } -} - - -// erfi(z) = -i erf(iz) -std::complex erfi(std::complex z, double relerr) -{ - std::complex e = erf(std::complex(-imag(z),real(z)), relerr); - return std::complex(imag(e), -real(e)); -} - -// erfi(x) = -i erf(ix) -double erfi(double x) -{ - return x*x > 720 ? (x > 0 ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()) - : exp(x*x) * w_im(x); -} - -// erfc(x) = 1 - erf(x) -double erfc(double x) -{ - if (x*x > 750) // underflow - return (x >= 0 ? 0.0 : 2.0); - return x >= 0 ? exp(-x*x) * erfcx(x) - : 2. - exp(-x*x) * erfcx(-x); -} - -// erfc(z) = 1 - erf(z) -std::complex erfc(std::complex z, double relerr) -{ - double x = real(z), y = imag(z); - - if (x == 0.) - return std::complex(1, - /* handle y -> Inf limit manually, since - exp(y^2) -> Inf but Im[w(y)] -> 0, so - IEEE will give us a NaN when it should be Inf */ - y*y > 720 ? (y > 0 ? -std::numeric_limits::infinity() : std::numeric_limits::infinity()) - : -exp(y*y) * w_im(y)); - if (y == 0.) { - if (x*x > 750) // underflow - return (x >= 0 ? 0.0 : 2.0); - return x >= 0 ? exp(-x*x) * erfcx(x) - : 2. - exp(-x*x) * erfcx(-x); - } - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - if (mRe_z2 < -750) // underflow - return (x >= 0 ? 0.0 : 2.0); - - if (x >= 0) - return exp(std::complex(mRe_z2, mIm_z2)) - * w(std::complex(-y,x), relerr); - else - return 2.0 - exp(std::complex(mRe_z2, mIm_z2)) - * w(std::complex(y,-x), relerr); -} - -// compute Dawson(x) = sqrt(pi)/2 * exp(-x^2) * erfi(x) -double Dawson(double x) -{ - const double spi2 = 0.8862269254527580136490837416705725913990; // sqrt(pi)/2 - return spi2 * w_im(x); -} - -// compute Dawson(z) = sqrt(pi)/2 * exp(-z^2) * erfi(z) -std::complex Dawson(std::complex z, double relerr) -{ - const double spi2 = 0.8862269254527580136490837416705725913990; // sqrt(pi)/2 - double x = real(z), y = imag(z); - - // handle axes separately for speed & proper handling of x or y = Inf or NaN - if (y == 0) - return std::complex(spi2 * w_im(x), - -y); // preserve sign of 0 - if (x == 0) { - double y2 = y*y; - if (y2 < 2.5e-5) { // Taylor expansion - return std::complex(x, // preserve sign of 0 - y * (1. - + y2 * (0.6666666666666666666666666666666666666667 - + y2 * 0.2666666666666666666666666666666666666667))); - } - return std::complex(x, // preserve sign of 0 - spi2 * (y >= 0 - ? exp(y2) - erfcx(y) - : erfcx(-y) - exp(y2))); - } - - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2*x*y; // Im(-z^2) - std::complex mz2(mRe_z2, mIm_z2); // -z^2 - - /* Handle positive and negative x via different formulas, - using the mirror symmetries of w, to avoid overflow/underflow - problems from multiplying exponentially large and small quantities. */ - if (y >= 0) { - if (y < 5e-3) { - if (fabs(x) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_realaxis; - } - std::complex res = exp(mz2) - w(z); - return spi2 * std::complex(-imag(res), real(res)); - } - else { // y < 0 - if (y > -5e-3) { // duplicate from above to avoid fabs(x) call - if (fabs(x) < 5e-3) - goto taylor; - else if (fabs(mIm_z2) < 5e-3) - goto taylor_realaxis; - } - else if (std::isnan(y)) - return std::complex(x == 0 ? 0 : std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()); - std::complex res = w(-z) - exp(mz2); - return spi2 * std::complex(-imag(res), real(res)); - } - - // Use Taylor series for small |z|, to avoid cancellation inaccuracy - // dawson(z) = z - 2/3 z^3 + 4/15 z^5 + ... - taylor: - return z * (1. - + mz2 * (0.6666666666666666666666666666666666666667 - + mz2 * 0.2666666666666666666666666666666666666667)); - - /* for small |y| and small |xy|, - use Taylor series to avoid cancellation inaccuracy: - dawson(x + iy) - = D + y^2 (D + x - 2Dx^2) - + y^4 (D/2 + 5x/6 - 2Dx^2 - x^3/3 + 2Dx^4/3) - + iy [ (1-2Dx) + 2/3 y^2 (1 - 3Dx - x^2 + 2Dx^3) - + y^4/15 (4 - 15Dx - 9x^2 + 20Dx^3 + 2x^4 - 4Dx^5) ] + ... - where D = dawson(x) - - However, for large |x|, 2Dx -> 1 which gives cancellation problems in - this series (many of the leading terms cancel). So, for large |x|, - we need to substitute a continued-fraction expansion for D. - - dawson(x) = 0.5 / (x-0.5/(x-1/(x-1.5/(x-2/(x-2.5/(x...)))))) - - The 6 terms shown here seems to be the minimum needed to be - accurate as soon as the simpler Taylor expansion above starts - breaking down. Using this 6-term expansion, factoring out the - denominator, and simplifying with Maple, we obtain: - - Re dawson(x + iy) * (-15 + 90x^2 - 60x^4 + 8x^6) / x - = 33 - 28x^2 + 4x^4 + y^2 (18 - 4x^2) + 4 y^4 - Im dawson(x + iy) * (-15 + 90x^2 - 60x^4 + 8x^6) / y - = -15 + 24x^2 - 4x^4 + 2/3 y^2 (6x^2 - 15) - 4 y^4 - - Finally, for |x| > 5e7, we can use a simpler 1-term continued-fraction - expansion for the real part, and a 2-term expansion for the imaginary - part. (This avoids overflow problems for huge |x|.) This yields: - - Re dawson(x + iy) = [1 + y^2 (1 + y^2/2 - (xy)^2/3)] / (2x) - Im dawson(x + iy) = y [ -1 - 2/3 y^2 + y^4/15 (2x^2 - 4) ] / (2x^2 - 1) - - */ - taylor_realaxis: - { - double x2 = x*x; - if (x2 > 1600) { // |x| > 40 - double y2 = y*y; - if (x2 > 25e14) {// |x| > 5e7 - double xy2 = (x*y)*(x*y); - return std::complex((0.5 + y2 * (0.5 + 0.25*y2 - - 0.16666666666666666667*xy2)) / x, - y * (-1 + y2 * (-0.66666666666666666667 - + 0.13333333333333333333*xy2 - - 0.26666666666666666667*y2)) - / (2*x2 - 1)); - } - return (1. / (-15 + x2*(90 + x2*(-60 + 8*x2)))) * - std::complex(x * (33 + x2 * (-28 + 4*x2) - + y2 * (18 - 4*x2 + 4*y2)), - y * (-15 + x2 * (24 - 4*x2) - + y2 * (4*x2 - 10 - 4*y2))); - } - else { - double D = spi2 * w_im(x); - double x2 = x*x, y2 = y*y; - return std::complex - (D + y2 * (D + x - 2*D*x2) - + y2*y2 * (D * (0.5 - x2 * (2 - 0.66666666666666666667*x2)) - + x * (0.83333333333333333333 - - 0.33333333333333333333 * x2)), - y * (1 - 2*D*x - + y2 * 0.66666666666666666667 * (1 - x2 - D*x * (3 - 2*x2)) - + y2*y2 * (0.26666666666666666667 - - x2 * (0.6 - 0.13333333333333333333 * x2) - - D*x * (1 - x2 * (1.3333333333333333333 - - 0.26666666666666666667 * x2))))); - } - } -} - -// return sinc(x) = sin(x)/x, given both x and sin(x) -// [since we only use this in cases where sin(x) has already been computed] -inline double sinc(double x, double sinx) { - return fabs(x) < 1e-4 ? 1 - (0.1666666666666666666667)*x*x : sinx / x; -} - -// sinh(x) via Taylor series, accurate to machine precision for |x| < 1e-2 -inline double sinh_taylor(double x) { - return x * (1 + (x*x) * (0.1666666666666666666667 - + 0.00833333333333333333333 * (x*x))); -} - -inline double sqr(double x) { return x*x; } - -///////////////////////////////////////////////////////////////////////// - -// precomputed table of expa2n2[n-1] = exp(-a2*n*n) -// for double-precision a2 = 0.26865... in Faddeeva::w, below. -static const double expa2n2[] = { - 7.64405281671221563e-01, - 3.41424527166548425e-01, - 8.91072646929412548e-02, - 1.35887299055460086e-02, - 1.21085455253437481e-03, - 6.30452613933449404e-05, - 1.91805156577114683e-06, - 3.40969447714832381e-08, - 3.54175089099469393e-10, - 2.14965079583260682e-12, - 7.62368911833724354e-15, - 1.57982797110681093e-17, - 1.91294189103582677e-20, - 1.35344656764205340e-23, - 5.59535712428588720e-27, - 1.35164257972401769e-30, - 1.90784582843501167e-34, - 1.57351920291442930e-38, - 7.58312432328032845e-43, - 2.13536275438697082e-47, - 3.51352063787195769e-52, - 3.37800830266396920e-57, - 1.89769439468301000e-62, - 6.22929926072668851e-68, - 1.19481172006938722e-73, - 1.33908181133005953e-79, - 8.76924303483223939e-86, - 3.35555576166254986e-92, - 7.50264110688173024e-99, - 9.80192200745410268e-106, - 7.48265412822268959e-113, - 3.33770122566809425e-120, - 8.69934598159861140e-128, - 1.32486951484088852e-135, - 1.17898144201315253e-143, - 6.13039120236180012e-152, - 1.86258785950822098e-160, - 3.30668408201432783e-169, - 3.43017280887946235e-178, - 2.07915397775808219e-187, - 7.36384545323984966e-197, - 1.52394760394085741e-206, - 1.84281935046532100e-216, - 1.30209553802992923e-226, - 5.37588903521080531e-237, - 1.29689584599763145e-247, - 1.82813078022866562e-258, - 1.50576355348684241e-269, - 7.24692320799294194e-281, - 2.03797051314726829e-292, - 3.34880215927873807e-304, - 0.0 // underflow (also prevents reads past array end, below) -}; - - - -///////////////////////////////////////////////////////////////////////// - -std::complex w(std::complex z, double relerr) -{ - if (real(z) == 0.0) - return std::complex(erfcx(imag(z)), - real(z)); // give correct sign of 0 in imag(w) - else if (imag(z) == 0) - return std::complex(exp(-sqr(real(z))), - w_im(real(z))); - - double a, a2, c; - if (relerr <= DBL_EPSILON) { - relerr = DBL_EPSILON; - a = 0.518321480430085929872; // pi / sqrt(-log(eps*0.5)) - c = 0.329973702884629072537; // (2/pi) * a; - a2 = 0.268657157075235951582; // a^2 - } - else { - const double pi = 3.14159265358979323846264338327950288419716939937510582; - if (relerr > 0.1) relerr = 0.1; // not sensible to compute < 1 digit - a = pi / sqrt(-log(relerr*0.5)); - c = (2/pi)*a; - a2 = a*a; - } - const double x = fabs(real(z)); - const double y = imag(z), ya = fabs(y); - - std::complex ret(0.,0.); // return value - - double sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0, sum5 = 0; - -#define USE_CONTINUED_FRACTION 1 // 1 to use continued fraction for large |z| - -#if USE_CONTINUED_FRACTION - if (ya > 7 || (x > 6 // continued fraction is faster - /* As pointed out by M. Zaghloul, the continued - fraction seems to give a large relative error in - Re w(z) for |x| ~ 6 and small |y|, so use - algorithm 816 in this region: */ - && (ya > 0.1 || (x > 8 && ya > 1e-10) || x > 28))) { - - /* Poppe & Wijers suggest using a number of terms - nu = 3 + 1442 / (26*rho + 77) - where rho = sqrt((x/x0)^2 + (y/y0)^2) where x0=6.3, y0=4.4. - (They only use this expansion for rho >= 1, but rho a little less - than 1 seems okay too.) - Instead, I did my own fit to a slightly different function - that avoids the hypotenuse calculation, using NLopt to minimize - the sum of the squares of the errors in nu with the constraint - that the estimated nu be >= minimum nu to attain machine precision. - I also separate the regions where nu == 2 and nu == 1. */ - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - double xs = y < 0 ? -real(z) : real(z); // compute for -z if y < 0 - if (x + ya > 4000) { // nu <= 2 - if (x + ya > 1e7) { // nu == 1, w(z) = i/sqrt(pi) / z - // scale to avoid overflow - if (x > ya) { - double yax = ya / xs; - double denom = ispi / (xs + yax*ya); - ret = std::complex(denom*yax, denom); - } - else if (std::isinf(ya)) - return ((std::isnan(x) || y < 0) - ? std::complex(std::numeric_limits::quiet_NaN(),std::numeric_limits::quiet_NaN()) : std::complex(0,0)); - else { - double xya = xs / ya; - double denom = ispi / (xya*xs + ya); - ret = std::complex(denom, denom*xya); - } - } - else { // nu == 2, w(z) = i/sqrt(pi) * z / (z*z - 0.5) - double dr = xs*xs - ya*ya - 0.5, di = 2*xs*ya; - double denom = ispi / (dr*dr + di*di); - ret = std::complex(denom * (xs*di-ya*dr), denom * (xs*dr+ya*di)); - } - } - else { // compute nu(z) estimate and do general continued fraction - const double c0=3.9, c1=11.398, c2=0.08254, c3=0.1421, c4=0.2023; // fit - double nu = floor(c0 + c1 / (c2*x + c3*ya + c4)); - double wr = xs, wi = ya; - for (nu = 0.5 * (nu - 1); nu > 0.4; nu -= 0.5) { - // w <- z - nu/w: - double denom = nu / (wr*wr + wi*wi); - wr = xs - wr * denom; - wi = ya + wi * denom; - } - { // w(z) = i/sqrt(pi) / w: - double denom = ispi / (wr*wr + wi*wi); - ret = std::complex(denom*wi, denom*wr); - } - } - if (y < 0) { - // use w(z) = 2.0*exp(-z*z) - w(-z), - // but be careful of overflow in exp(-z*z) - // = exp(-(xs*xs-ya*ya) -2*i*xs*ya) - return 2.0*exp(std::complex((ya-xs)*(xs+ya), 2*xs*y)) - ret; - } - else - return ret; - } -#else // !USE_CONTINUED_FRACTION - if (x + ya > 1e7) { // w(z) = i/sqrt(pi) / z, to machine precision - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - double xs = y < 0 ? -real(z) : real(z); // compute for -z if y < 0 - // scale to avoid overflow - if (x > ya) { - double yax = ya / xs; - double denom = ispi / (xs + yax*ya); - ret = std::complex(denom*yax, denom); - } - else { - double xya = xs / ya; - double denom = ispi / (xya*xs + ya); - ret = std::complex(denom, denom*xya); - } - if (y < 0) { - // use w(z) = 2.0*exp(-z*z) - w(-z), - // but be careful of overflow in exp(-z*z) - // = exp(-(xs*xs-ya*ya) -2*i*xs*ya) - return 2.0*exp(std::complex((ya-xs)*(xs+ya), 2*xs*y)) - ret; - } - else - return ret; - } -#endif // !USE_CONTINUED_FRACTION - - /* Note: The test that seems to be suggested in the paper is x < - sqrt(-log(DBL_MIN)), about 26.6, since otherwise exp(-x^2) - underflows to zero and sum1,sum2,sum4 are zero. However, long - before this occurs, the sum1,sum2,sum4 contributions are - negligible in double precision; I find that this happens for x > - about 6, for all y. On the other hand, I find that the case - where we compute all of the sums is faster (at least with the - precomputed expa2n2 table) until about x=10. Furthermore, if we - try to compute all of the sums for x > 20, I find that we - sometimes run into numerical problems because underflow/overflow - problems start to appear in the various coefficients of the sums, - below. Therefore, we use x < 10 here. */ - else if (x < 10) { - double prod2ax = 1, prodm2ax = 1; - double expx2; - - if (std::isnan(y)) - return std::complex(y,y); - - /* Somewhat ugly copy-and-paste duplication here, but I see significant - speedups from using the special-case code with the precomputed - exponential, and the x < 5e-4 special case is needed for accuracy. */ - - if (relerr == DBL_EPSILON) { // use precomputed exp(-a2*(n*n)) table - if (x < 5e-4) { // compute sum4 and sum5 together as sum5-sum4 - const double x2 = x*x; - expx2 = 1 - x2 * (1 - 0.5*x2); // exp(-x*x) via Taylor - // compute exp(2*a*x) and exp(-2*a*x) via Taylor, to double precision - const double ax2 = 1.036642960860171859744*x; // 2*a*x - const double exp2ax = - 1 + ax2 * (1 + ax2 * (0.5 + 0.166666666666666666667*ax2)); - const double expm2ax = - 1 - ax2 * (1 - ax2 * (0.5 - 0.166666666666666666667*ax2)); - for (int n = 1; 1; ++n) { - const double coef = expa2n2[n-1] * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum3 += coef * prod2ax; - - // really = sum5 - sum4 - sum5 += coef * (2*a) * n * sinh_taylor((2*a)*n*x); - - // test convergence via sum3 - if (coef * prod2ax < relerr * sum3) break; - } - } - else { // x > 5e-4, compute sum4 and sum5 separately - expx2 = exp(-x*x); - const double exp2ax = exp((2*a)*x), expm2ax = 1 / exp2ax; - for (int n = 1; 1; ++n) { - const double coef = expa2n2[n-1] * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum4 += (coef * prodm2ax) * (a*n); - sum3 += coef * prod2ax; - sum5 += (coef * prod2ax) * (a*n); - // test convergence via sum5, since this sum has the slowest decay - if ((coef * prod2ax) * (a*n) < relerr * sum5) break; - } - } - } - else { // relerr != DBL_EPSILON, compute exp(-a2*(n*n)) on the fly - const double exp2ax = exp((2*a)*x), expm2ax = 1 / exp2ax; - if (x < 5e-4) { // compute sum4 and sum5 together as sum5-sum4 - const double x2 = x*x; - expx2 = 1 - x2 * (1 - 0.5*x2); // exp(-x*x) via Taylor - for (int n = 1; 1; ++n) { - const double coef = exp(-a2*(n*n)) * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum3 += coef * prod2ax; - - // really = sum5 - sum4 - sum5 += coef * (2*a) * n * sinh_taylor((2*a)*n*x); - - // test convergence via sum3 - if (coef * prod2ax < relerr * sum3) break; - } - } - else { // x > 5e-4, compute sum4 and sum5 separately - expx2 = exp(-x*x); - for (int n = 1; 1; ++n) { - const double coef = exp(-a2*(n*n)) * expx2 / (a2*(n*n) + y*y); - prod2ax *= exp2ax; - prodm2ax *= expm2ax; - sum1 += coef; - sum2 += coef * prodm2ax; - sum4 += (coef * prodm2ax) * (a*n); - sum3 += coef * prod2ax; - sum5 += (coef * prod2ax) * (a*n); - // test convergence via sum5, since this sum has the slowest decay - if ((coef * prod2ax) * (a*n) < relerr * sum5) break; - } - } - } - const double expx2erfcxy = // avoid spurious overflow for large negative y - y > -6 // for y < -6, erfcx(y) = 2*exp(y*y) to double precision - ? expx2*erfcx(y) : 2*exp(y*y-x*x); - if (y > 5) { // imaginary terms cancel - const double sinxy = sin(x*y); - ret = (expx2erfcxy - c*y*sum1) * cos(2*x*y) - + (c*x*expx2) * sinxy * sinc(x*y, sinxy); - } - else { - double xs = real(z); - const double sinxy = sin(xs*y); - const double sin2xy = sin(2*xs*y), cos2xy = cos(2*xs*y); - const double coef1 = expx2erfcxy - c*y*sum1; - const double coef2 = c*xs*expx2; - ret = std::complex(coef1 * cos2xy + coef2 * sinxy * sinc(xs*y, sinxy), - coef2 * sinc(2*xs*y, sin2xy) - coef1 * sin2xy); - } - } - else { // x large: only sum3 & sum5 contribute (see above note) - if (std::isnan(x)) - return std::complex(x,x); - if (std::isnan(y)) - return std::complex(y,y); - -#if USE_CONTINUED_FRACTION - ret = exp(-x*x); // |y| < 1e-10, so we only need exp(-x*x) term -#else - if (y < 0) { - /* erfcx(y) ~ 2*exp(y*y) + (< 1) if y < 0, so - erfcx(y)*exp(-x*x) ~ 2*exp(y*y-x*x) term may not be negligible - if y*y - x*x > -36 or so. So, compute this term just in case. - We also need the -exp(-x*x) term to compute Re[w] accurately - in the case where y is very small. */ - ret = polar(2*exp(y*y-x*x) - exp(-x*x), -2*real(z)*y); - } - else - ret = exp(-x*x); // not negligible in real part if y very small -#endif - // (round instead of ceil as in original paper; note that x/a > 1 here) - double n0 = floor(x/a + 0.5); // sum in both directions, starting at n0 - double dx = a*n0 - x; - sum3 = exp(-dx*dx) / (a2*(n0*n0) + y*y); - sum5 = a*n0 * sum3; - double exp1 = exp(4*a*dx), exp1dn = 1; - int dn; - for (dn = 1; n0 - dn > 0; ++dn) { // loop over n0-dn and n0+dn terms - double np = n0 + dn, nm = n0 - dn; - double tp = exp(-sqr(a*dn+dx)); - double tm = tp * (exp1dn *= exp1); // trick to get tm from tp - tp /= (a2*(np*np) + y*y); - tm /= (a2*(nm*nm) + y*y); - sum3 += tp + tm; - sum5 += a * (np * tp + nm * tm); - if (a * (np * tp + nm * tm) < relerr * sum5) goto finish; - } - while (1) { // loop over n0+dn terms only (since n0-dn <= 0) - double np = n0 + dn++; - double tp = exp(-sqr(a*dn+dx)) / (a2*(np*np) + y*y); - sum3 += tp; - sum5 += a * np * tp; - if (a * np * tp < relerr * sum5) goto finish; - } - } - finish: - return ret + std::complex((0.5*c)*y*(sum2+sum3), - (0.5*c)*std::copysign(sum5-sum4, real(z))); -} - - -///////////////////////////////////////////////////////////////////////// - -/* erfcx(x) = exp(x^2) erfc(x) function, for real x, written by - Steven G. Johnson, October 2012. - - This function combines a few different ideas. - - First, for x > 50, it uses a continued-fraction expansion (same as - for the Faddeeva function, but with algebraic simplifications for z=i*x). - - Second, for 0 <= x <= 50, it uses Chebyshev polynomial approximations, - but with two twists: - - a) It maps x to y = 4 / (4+x) in [0,1]. This simple transformation, - inspired by a similar transformation in the octave-forge/specfun - erfcx by Soren Hauberg, results in much faster Chebyshev convergence - than other simple transformations I have examined. - - b) Instead of using a single Chebyshev polynomial for the entire - [0,1] y interval, we break the interval up into 100 equal - subintervals, with a switch/lookup table, and use much lower - degree Chebyshev polynomials in each subinterval. This greatly - improves performance in my tests. - - For x < 0, we use the relationship erfcx(-x) = 2 exp(x^2) - erfc(x), - with the usual checks for overflow etcetera. - - Performance-wise, it seems to be substantially faster than either - the SLATEC DERFC function [or an erfcx function derived therefrom] - or Cody's CALERF function (from netlib.org/specfun), while - retaining near machine precision in accuracy. */ - -/* Given y100=100*y, where y = 4/(4+x) for x >= 0, compute erfc(x). - - Uses a look-up table of 100 different Chebyshev polynomials - for y intervals [0,0.01], [0.01,0.02], ...., [0.99,1], generated - with the help of Maple and a little shell script. This allows - the Chebyshev polynomials to be of significantly lower degree (about 1/4) - compared to fitting the whole [0,1] interval with a single polynomial. */ -double erfcx_y100(double y100) -{ - switch ((int) y100) { -case 0: { -double t = 2*y100 - 1; -return 0.70878032454106438663e-3 + (0.71234091047026302958e-3 + (0.35779077297597742384e-5 + (0.17403143962587937815e-7 + (0.81710660047307788845e-10 + (0.36885022360434957634e-12 + 0.15917038551111111111e-14 * t) * t) * t) * t) * t) * t; -} -case 1: { -double t = 2*y100 - 3; -return 0.21479143208285144230e-2 + (0.72686402367379996033e-3 + (0.36843175430938995552e-5 + (0.18071841272149201685e-7 + (0.85496449296040325555e-10 + (0.38852037518534291510e-12 + 0.16868473576888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 2: { -double t = 2*y100 - 5; -return 0.36165255935630175090e-2 + (0.74182092323555510862e-3 + (0.37948319957528242260e-5 + (0.18771627021793087350e-7 + (0.89484715122415089123e-10 + (0.40935858517772440862e-12 + 0.17872061464888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 3: { -double t = 2*y100 - 7; -return 0.51154983860031979264e-2 + (0.75722840734791660540e-3 + (0.39096425726735703941e-5 + (0.19504168704300468210e-7 + (0.93687503063178993915e-10 + (0.43143925959079664747e-12 + 0.18939926435555555556e-14 * t) * t) * t) * t) * t) * t; -} -case 4: { -double t = 2*y100 - 9; -return 0.66457513172673049824e-2 + (0.77310406054447454920e-3 + (0.40289510589399439385e-5 + (0.20271233238288381092e-7 + (0.98117631321709100264e-10 + (0.45484207406017752971e-12 + 0.20076352213333333333e-14 * t) * t) * t) * t) * t) * t; -} -case 5: { -double t = 2*y100 - 11; -return 0.82082389970241207883e-2 + (0.78946629611881710721e-3 + (0.41529701552622656574e-5 + (0.21074693344544655714e-7 + (0.10278874108587317989e-9 + (0.47965201390613339638e-12 + 0.21285907413333333333e-14 * t) * t) * t) * t) * t) * t; -} -case 6: { -double t = 2*y100 - 13; -return 0.98039537275352193165e-2 + (0.80633440108342840956e-3 + (0.42819241329736982942e-5 + (0.21916534346907168612e-7 + (0.10771535136565470914e-9 + (0.50595972623692822410e-12 + 0.22573462684444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 7: { -double t = 2*y100 - 15; -return 0.11433927298290302370e-1 + (0.82372858383196561209e-3 + (0.44160495311765438816e-5 + (0.22798861426211986056e-7 + (0.11291291745879239736e-9 + (0.53386189365816880454e-12 + 0.23944209546666666667e-14 * t) * t) * t) * t) * t) * t; -} -case 8: { -double t = 2*y100 - 17; -return 0.13099232878814653979e-1 + (0.84167002467906968214e-3 + (0.45555958988457506002e-5 + (0.23723907357214175198e-7 + (0.11839789326602695603e-9 + (0.56346163067550237877e-12 + 0.25403679644444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 9: { -double t = 2*y100 - 19; -return 0.14800987015587535621e-1 + (0.86018092946345943214e-3 + (0.47008265848816866105e-5 + (0.24694040760197315333e-7 + (0.12418779768752299093e-9 + (0.59486890370320261949e-12 + 0.26957764568888888889e-14 * t) * t) * t) * t) * t) * t; -} -case 10: { -double t = 2*y100 - 21; -return 0.16540351739394069380e-1 + (0.87928458641241463952e-3 + (0.48520195793001753903e-5 + (0.25711774900881709176e-7 + (0.13030128534230822419e-9 + (0.62820097586874779402e-12 + 0.28612737351111111111e-14 * t) * t) * t) * t) * t) * t; -} -case 11: { -double t = 2*y100 - 23; -return 0.18318536789842392647e-1 + (0.89900542647891721692e-3 + (0.50094684089553365810e-5 + (0.26779777074218070482e-7 + (0.13675822186304615566e-9 + (0.66358287745352705725e-12 + 0.30375273884444444444e-14 * t) * t) * t) * t) * t) * t; -} -case 12: { -double t = 2*y100 - 25; -return 0.20136801964214276775e-1 + (0.91936908737673676012e-3 + (0.51734830914104276820e-5 + (0.27900878609710432673e-7 + (0.14357976402809042257e-9 + (0.70114790311043728387e-12 + 0.32252476000000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 13: { -double t = 2*y100 - 27; -return 0.21996459598282740954e-1 + (0.94040248155366777784e-3 + (0.53443911508041164739e-5 + (0.29078085538049374673e-7 + (0.15078844500329731137e-9 + (0.74103813647499204269e-12 + 0.34251892320000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 14: { -double t = 2*y100 - 29; -return 0.23898877187226319502e-1 + (0.96213386835900177540e-3 + (0.55225386998049012752e-5 + (0.30314589961047687059e-7 + (0.15840826497296335264e-9 + (0.78340500472414454395e-12 + 0.36381553564444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 15: { -double t = 2*y100 - 31; -return 0.25845480155298518485e-1 + (0.98459293067820123389e-3 + (0.57082915920051843672e-5 + (0.31613782169164830118e-7 + (0.16646478745529630813e-9 + (0.82840985928785407942e-12 + 0.38649975768888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 16: { -double t = 2*y100 - 33; -return 0.27837754783474696598e-1 + (0.10078108563256892757e-2 + (0.59020366493792212221e-5 + (0.32979263553246520417e-7 + (0.17498524159268458073e-9 + (0.87622459124842525110e-12 + 0.41066206488888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 17: { -double t = 2*y100 - 35; -return 0.29877251304899307550e-1 + (0.10318204245057349310e-2 + (0.61041829697162055093e-5 + (0.34414860359542720579e-7 + (0.18399863072934089607e-9 + (0.92703227366365046533e-12 + 0.43639844053333333334e-14 * t) * t) * t) * t) * t) * t; -} -case 18: { -double t = 2*y100 - 37; -return 0.31965587178596443475e-1 + (0.10566560976716574401e-2 + (0.63151633192414586770e-5 + (0.35924638339521924242e-7 + (0.19353584758781174038e-9 + (0.98102783859889264382e-12 + 0.46381060817777777779e-14 * t) * t) * t) * t) * t) * t; -} -case 19: { -double t = 2*y100 - 39; -return 0.34104450552588334840e-1 + (0.10823541191350532574e-2 + (0.65354356159553934436e-5 + (0.37512918348533521149e-7 + (0.20362979635817883229e-9 + (0.10384187833037282363e-11 + 0.49300625262222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 20: { -double t = 2*y100 - 41; -return 0.36295603928292425716e-1 + (0.11089526167995268200e-2 + (0.67654845095518363577e-5 + (0.39184292949913591646e-7 + (0.21431552202133775150e-9 + (0.10994259106646731797e-11 + 0.52409949102222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 21: { -double t = 2*y100 - 43; -return 0.38540888038840509795e-1 + (0.11364917134175420009e-2 + (0.70058230641246312003e-5 + (0.40943644083718586939e-7 + (0.22563034723692881631e-9 + (0.11642841011361992885e-11 + 0.55721092871111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 22: { -double t = 2*y100 - 45; -return 0.40842225954785960651e-1 + (0.11650136437945673891e-2 + (0.72569945502343006619e-5 + (0.42796161861855042273e-7 + (0.23761401711005024162e-9 + (0.12332431172381557035e-11 + 0.59246802364444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 23: { -double t = 2*y100 - 47; -return 0.43201627431540222422e-1 + (0.11945628793917272199e-2 + (0.75195743532849206263e-5 + (0.44747364553960993492e-7 + (0.25030885216472953674e-9 + (0.13065684400300476484e-11 + 0.63000532853333333334e-14 * t) * t) * t) * t) * t) * t; -} -case 24: { -double t = 2*y100 - 49; -return 0.45621193513810471438e-1 + (0.12251862608067529503e-2 + (0.77941720055551920319e-5 + (0.46803119830954460212e-7 + (0.26375990983978426273e-9 + (0.13845421370977119765e-11 + 0.66996477404444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 25: { -double t = 2*y100 - 51; -return 0.48103121413299865517e-1 + (0.12569331386432195113e-2 + (0.80814333496367673980e-5 + (0.48969667335682018324e-7 + (0.27801515481905748484e-9 + (0.14674637611609884208e-11 + 0.71249589351111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 26: { -double t = 2*y100 - 53; -return 0.50649709676983338501e-1 + (0.12898555233099055810e-2 + (0.83820428414568799654e-5 + (0.51253642652551838659e-7 + (0.29312563849675507232e-9 + (0.15556512782814827846e-11 + 0.75775607822222222221e-14 * t) * t) * t) * t) * t) * t; -} -case 27: { -double t = 2*y100 - 55; -return 0.53263363664388864181e-1 + (0.13240082443256975769e-2 + (0.86967260015007658418e-5 + (0.53662102750396795566e-7 + (0.30914568786634796807e-9 + (0.16494420240828493176e-11 + 0.80591079644444444445e-14 * t) * t) * t) * t) * t) * t; -} -case 28: { -double t = 2*y100 - 57; -return 0.55946601353500013794e-1 + (0.13594491197408190706e-2 + (0.90262520233016380987e-5 + (0.56202552975056695376e-7 + (0.32613310410503135996e-9 + (0.17491936862246367398e-11 + 0.85713381688888888890e-14 * t) * t) * t) * t) * t) * t; -} -case 29: { -double t = 2*y100 - 59; -return 0.58702059496154081813e-1 + (0.13962391363223647892e-2 + (0.93714365487312784270e-5 + (0.58882975670265286526e-7 + (0.34414937110591753387e-9 + (0.18552853109751857859e-11 + 0.91160736711111111110e-14 * t) * t) * t) * t) * t) * t; -} -case 30: { -double t = 2*y100 - 61; -return 0.61532500145144778048e-1 + (0.14344426411912015247e-2 + (0.97331446201016809696e-5 + (0.61711860507347175097e-7 + (0.36325987418295300221e-9 + (0.19681183310134518232e-11 + 0.96952238400000000000e-14 * t) * t) * t) * t) * t) * t; -} -case 31: { -double t = 2*y100 - 63; -return 0.64440817576653297993e-1 + (0.14741275456383131151e-2 + (0.10112293819576437838e-4 + (0.64698236605933246196e-7 + (0.38353412915303665586e-9 + (0.20881176114385120186e-11 + 0.10310784480000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 32: { -double t = 2*y100 - 65; -return 0.67430045633130393282e-1 + (0.15153655418916540370e-2 + (0.10509857606888328667e-4 + (0.67851706529363332855e-7 + (0.40504602194811140006e-9 + (0.22157325110542534469e-11 + 0.10964842115555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 33: { -double t = 2*y100 - 67; -return 0.70503365513338850709e-1 + (0.15582323336495709827e-2 + (0.10926868866865231089e-4 + (0.71182482239613507542e-7 + (0.42787405890153386710e-9 + (0.23514379522274416437e-11 + 0.11659571751111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 34: { -double t = 2*y100 - 69; -return 0.73664114037944596353e-1 + (0.16028078812438820413e-2 + (0.11364423678778207991e-4 + (0.74701423097423182009e-7 + (0.45210162777476488324e-9 + (0.24957355004088569134e-11 + 0.12397238257777777778e-13 * t) * t) * t) * t) * t) * t; -} -case 35: { -double t = 2*y100 - 71; -return 0.76915792420819562379e-1 + (0.16491766623447889354e-2 + (0.11823685320041302169e-4 + (0.78420075993781544386e-7 + (0.47781726956916478925e-9 + (0.26491544403815724749e-11 + 0.13180196462222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 36: { -double t = 2*y100 - 73; -return 0.80262075578094612819e-1 + (0.16974279491709504117e-2 + (0.12305888517309891674e-4 + (0.82350717698979042290e-7 + (0.50511496109857113929e-9 + (0.28122528497626897696e-11 + 0.14010889635555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 37: { -double t = 2*y100 - 75; -return 0.83706822008980357446e-1 + (0.17476561032212656962e-2 + (0.12812343958540763368e-4 + (0.86506399515036435592e-7 + (0.53409440823869467453e-9 + (0.29856186620887555043e-11 + 0.14891851591111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 38: { -double t = 2*y100 - 77; -return 0.87254084284461718231e-1 + (0.17999608886001962327e-2 + (0.13344443080089492218e-4 + (0.90900994316429008631e-7 + (0.56486134972616465316e-9 + (0.31698707080033956934e-11 + 0.15825697795555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 39: { -double t = 2*y100 - 79; -return 0.90908120182172748487e-1 + (0.18544478050657699758e-2 + (0.13903663143426120077e-4 + (0.95549246062549906177e-7 + (0.59752787125242054315e-9 + (0.33656597366099099413e-11 + 0.16815130613333333333e-13 * t) * t) * t) * t) * t) * t; -} -case 40: { -double t = 2*y100 - 81; -return 0.94673404508075481121e-1 + (0.19112284419887303347e-2 + (0.14491572616545004930e-4 + (0.10046682186333613697e-6 + (0.63221272959791000515e-9 + (0.35736693975589130818e-11 + 0.17862931591111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 41: { -double t = 2*y100 - 83; -return 0.98554641648004456555e-1 + (0.19704208544725622126e-2 + (0.15109836875625443935e-4 + (0.10567036667675984067e-6 + (0.66904168640019354565e-9 + (0.37946171850824333014e-11 + 0.18971959040000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 42: { -double t = 2*y100 - 85; -return 0.10255677889470089531e0 + (0.20321499629472857418e-2 + (0.15760224242962179564e-4 + (0.11117756071353507391e-6 + (0.70814785110097658502e-9 + (0.40292553276632563925e-11 + 0.20145143075555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 43: { -double t = 2*y100 - 87; -return 0.10668502059865093318e0 + (0.20965479776148731610e-2 + (0.16444612377624983565e-4 + (0.11700717962026152749e-6 + (0.74967203250938418991e-9 + (0.42783716186085922176e-11 + 0.21385479360000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 44: { -double t = 2*y100 - 89; -return 0.11094484319386444474e0 + (0.21637548491908170841e-2 + (0.17164995035719657111e-4 + (0.12317915750735938089e-6 + (0.79376309831499633734e-9 + (0.45427901763106353914e-11 + 0.22696025653333333333e-13 * t) * t) * t) * t) * t) * t; -} -case 45: { -double t = 2*y100 - 91; -return 0.11534201115268804714e0 + (0.22339187474546420375e-2 + (0.17923489217504226813e-4 + (0.12971465288245997681e-6 + (0.84057834180389073587e-9 + (0.48233721206418027227e-11 + 0.24079890062222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 46: { -double t = 2*y100 - 93; -return 0.11988259392684094740e0 + (0.23071965691918689601e-2 + (0.18722342718958935446e-4 + (0.13663611754337957520e-6 + (0.89028385488493287005e-9 + (0.51210161569225846701e-11 + 0.25540227111111111111e-13 * t) * t) * t) * t) * t) * t; -} -case 47: { -double t = 2*y100 - 95; -return 0.12457298393509812907e0 + (0.23837544771809575380e-2 + (0.19563942105711612475e-4 + (0.14396736847739470782e-6 + (0.94305490646459247016e-9 + (0.54366590583134218096e-11 + 0.27080225920000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 48: { -double t = 2*y100 - 97; -return 0.12941991566142438816e0 + (0.24637684719508859484e-2 + (0.20450821127475879816e-4 + (0.15173366280523906622e-6 + (0.99907632506389027739e-9 + (0.57712760311351625221e-11 + 0.28703099555555555556e-13 * t) * t) * t) * t) * t) * t; -} -case 49: { -double t = 2*y100 - 99; -return 0.13443048593088696613e0 + (0.25474249981080823877e-2 + (0.21385669591362915223e-4 + (0.15996177579900443030e-6 + (0.10585428844575134013e-8 + (0.61258809536787882989e-11 + 0.30412080142222222222e-13 * t) * t) * t) * t) * t) * t; -} -case 50: { -double t = 2*y100 - 101; -return 0.13961217543434561353e0 + (0.26349215871051761416e-2 + (0.22371342712572567744e-4 + (0.16868008199296822247e-6 + (0.11216596910444996246e-8 + (0.65015264753090890662e-11 + 0.32210394506666666666e-13 * t) * t) * t) * t) * t) * t; -} -case 51: { -double t = 2*y100 - 103; -return 0.14497287157673800690e0 + (0.27264675383982439814e-2 + (0.23410870961050950197e-4 + (0.17791863939526376477e-6 + (0.11886425714330958106e-8 + (0.68993039665054288034e-11 + 0.34101266222222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 52: { -double t = 2*y100 - 105; -return 0.15052089272774618151e0 + (0.28222846410136238008e-2 + (0.24507470422713397006e-4 + (0.18770927679626136909e-6 + (0.12597184587583370712e-8 + (0.73203433049229821618e-11 + 0.36087889048888888890e-13 * t) * t) * t) * t) * t) * t; -} -case 53: { -double t = 2*y100 - 107; -return 0.15626501395774612325e0 + (0.29226079376196624949e-2 + (0.25664553693768450545e-4 + (0.19808568415654461964e-6 + (0.13351257759815557897e-8 + (0.77658124891046760667e-11 + 0.38173420035555555555e-13 * t) * t) * t) * t) * t) * t; -} -case 54: { -double t = 2*y100 - 109; -return 0.16221449434620737567e0 + (0.30276865332726475672e-2 + (0.26885741326534564336e-4 + (0.20908350604346384143e-6 + (0.14151148144240728728e-8 + (0.82369170665974313027e-11 + 0.40360957457777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 55: { -double t = 2*y100 - 111; -return 0.16837910595412130659e0 + (0.31377844510793082301e-2 + (0.28174873844911175026e-4 + (0.22074043807045782387e-6 + (0.14999481055996090039e-8 + (0.87348993661930809254e-11 + 0.42653528977777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 56: { -double t = 2*y100 - 113; -return 0.17476916455659369953e0 + (0.32531815370903068316e-2 + (0.29536024347344364074e-4 + (0.23309632627767074202e-6 + (0.15899007843582444846e-8 + (0.92610375235427359475e-11 + 0.45054073102222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 57: { -double t = 2*y100 - 115; -return 0.18139556223643701364e0 + (0.33741744168096996041e-2 + (0.30973511714709500836e-4 + (0.24619326937592290996e-6 + (0.16852609412267750744e-8 + (0.98166442942854895573e-11 + 0.47565418097777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 58: { -double t = 2*y100 - 117; -return 0.18826980194443664549e0 + (0.35010775057740317997e-2 + (0.32491914440014267480e-4 + (0.26007572375886319028e-6 + (0.17863299617388376116e-8 + (0.10403065638343878679e-10 + 0.50190265831111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 59: { -double t = 2*y100 - 119; -return 0.19540403413693967350e0 + (0.36342240767211326315e-2 + (0.34096085096200907289e-4 + (0.27479061117017637474e-6 + (0.18934228504790032826e-8 + (0.11021679075323598664e-10 + 0.52931171733333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 60: { -double t = 2*y100 - 121; -return 0.20281109560651886959e0 + (0.37739673859323597060e-2 + (0.35791165457592409054e-4 + (0.29038742889416172404e-6 + (0.20068685374849001770e-8 + (0.11673891799578381999e-10 + 0.55790523093333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 61: { -double t = 2*y100 - 123; -return 0.21050455062669334978e0 + (0.39206818613925652425e-2 + (0.37582602289680101704e-4 + (0.30691836231886877385e-6 + (0.21270101645763677824e-8 + (0.12361138551062899455e-10 + 0.58770520160000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 62: { -double t = 2*y100 - 125; -return 0.21849873453703332479e0 + (0.40747643554689586041e-2 + (0.39476163820986711501e-4 + (0.32443839970139918836e-6 + (0.22542053491518680200e-8 + (0.13084879235290858490e-10 + 0.61873153262222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 63: { -double t = 2*y100 - 127; -return 0.22680879990043229327e0 + (0.42366354648628516935e-2 + (0.41477956909656896779e-4 + (0.34300544894502810002e-6 + (0.23888264229264067658e-8 + (0.13846596292818514601e-10 + 0.65100183751111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 64: { -double t = 2*y100 - 129; -return 0.23545076536988703937e0 + (0.44067409206365170888e-2 + (0.43594444916224700881e-4 + (0.36268045617760415178e-6 + (0.25312606430853202748e-8 + (0.14647791812837903061e-10 + 0.68453122631111111110e-13 * t) * t) * t) * t) * t) * t; -} -case 65: { -double t = 2*y100 - 131; -return 0.24444156740777432838e0 + (0.45855530511605787178e-2 + (0.45832466292683085475e-4 + (0.38352752590033030472e-6 + (0.26819103733055603460e-8 + (0.15489984390884756993e-10 + 0.71933206364444444445e-13 * t) * t) * t) * t) * t) * t; -} -case 66: { -double t = 2*y100 - 133; -return 0.25379911500634264643e0 + (0.47735723208650032167e-2 + (0.48199253896534185372e-4 + (0.40561404245564732314e-6 + (0.28411932320871165585e-8 + (0.16374705736458320149e-10 + 0.75541379822222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 67: { -double t = 2*y100 - 135; -return 0.26354234756393613032e0 + (0.49713289477083781266e-2 + (0.50702455036930367504e-4 + (0.42901079254268185722e-6 + (0.30095422058900481753e-8 + (0.17303497025347342498e-10 + 0.79278273368888888890e-13 * t) * t) * t) * t) * t) * t; -} -case 68: { -double t = 2*y100 - 137; -return 0.27369129607732343398e0 + (0.51793846023052643767e-2 + (0.53350152258326602629e-4 + (0.45379208848865015485e-6 + (0.31874057245814381257e-8 + (0.18277905010245111046e-10 + 0.83144182364444444445e-13 * t) * t) * t) * t) * t) * t; -} -case 69: { -double t = 2*y100 - 139; -return 0.28426714781640316172e0 + (0.53983341916695141966e-2 + (0.56150884865255810638e-4 + (0.48003589196494734238e-6 + (0.33752476967570796349e-8 + (0.19299477888083469086e-10 + 0.87139049137777777779e-13 * t) * t) * t) * t) * t) * t; -} -case 70: { -double t = 2*y100 - 141; -return 0.29529231465348519920e0 + (0.56288077305420795663e-2 + (0.59113671189913307427e-4 + (0.50782393781744840482e-6 + (0.35735475025851713168e-8 + (0.20369760937017070382e-10 + 0.91262442613333333334e-13 * t) * t) * t) * t) * t) * t; -} -case 71: { -double t = 2*y100 - 143; -return 0.30679050522528838613e0 + (0.58714723032745403331e-2 + (0.62248031602197686791e-4 + (0.53724185766200945789e-6 + (0.37827999418960232678e-8 + (0.21490291930444538307e-10 + 0.95513539182222222221e-13 * t) * t) * t) * t) * t) * t; -} -case 72: { -double t = 2*y100 - 145; -return 0.31878680111173319425e0 + (0.61270341192339103514e-2 + (0.65564012259707640976e-4 + (0.56837930287837738996e-6 + (0.40035151353392378882e-8 + (0.22662596341239294792e-10 + 0.99891109760000000000e-13 * t) * t) * t) * t) * t) * t; -} -case 73: { -double t = 2*y100 - 147; -return 0.33130773722152622027e0 + (0.63962406646798080903e-2 + (0.69072209592942396666e-4 + (0.60133006661885941812e-6 + (0.42362183765883466691e-8 + (0.23888182347073698382e-10 + 0.10439349811555555556e-12 * t) * t) * t) * t) * t) * t; -} -case 74: { -double t = 2*y100 - 149; -return 0.34438138658041336523e0 + (0.66798829540414007258e-2 + (0.72783795518603561144e-4 + (0.63619220443228800680e-6 + (0.44814499336514453364e-8 + (0.25168535651285475274e-10 + 0.10901861383111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 75: { -double t = 2*y100 - 151; -return 0.35803744972380175583e0 + (0.69787978834882685031e-2 + (0.76710543371454822497e-4 + (0.67306815308917386747e-6 + (0.47397647975845228205e-8 + (0.26505114141143050509e-10 + 0.11376390933333333333e-12 * t) * t) * t) * t) * t) * t; -} -case 76: { -double t = 2*y100 - 153; -return 0.37230734890119724188e0 + (0.72938706896461381003e-2 + (0.80864854542670714092e-4 + (0.71206484718062688779e-6 + (0.50117323769745883805e-8 + (0.27899342394100074165e-10 + 0.11862637614222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 77: { -double t = 2*y100 - 155; -return 0.38722432730555448223e0 + (0.76260375162549802745e-2 + (0.85259785810004603848e-4 + (0.75329383305171327677e-6 + (0.52979361368388119355e-8 + (0.29352606054164086709e-10 + 0.12360253370666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 78: { -double t = 2*y100 - 157; -return 0.40282355354616940667e0 + (0.79762880915029728079e-2 + (0.89909077342438246452e-4 + (0.79687137961956194579e-6 + (0.55989731807360403195e-8 + (0.30866246101464869050e-10 + 0.12868841946666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 79: { -double t = 2*y100 - 159; -return 0.41914223158913787649e0 + (0.83456685186950463538e-2 + (0.94827181359250161335e-4 + (0.84291858561783141014e-6 + (0.59154537751083485684e-8 + (0.32441553034347469291e-10 + 0.13387957943111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 80: { -double t = 2*y100 - 161; -return 0.43621971639463786896e0 + (0.87352841828289495773e-2 + (0.10002929142066799966e-3 + (0.89156148280219880024e-6 + (0.62480008150788597147e-8 + (0.34079760983458878910e-10 + 0.13917107176888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 81: { -double t = 2*y100 - 163; -return 0.45409763548534330981e0 + (0.91463027755548240654e-2 + (0.10553137232446167258e-3 + (0.94293113464638623798e-6 + (0.65972492312219959885e-8 + (0.35782041795476563662e-10 + 0.14455745872000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 82: { -double t = 2*y100 - 165; -return 0.47282001668512331468e0 + (0.95799574408860463394e-2 + (0.11135019058000067469e-3 + (0.99716373005509038080e-6 + (0.69638453369956970347e-8 + (0.37549499088161345850e-10 + 0.15003280712888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 83: { -double t = 2*y100 - 167; -return 0.49243342227179841649e0 + (0.10037550043909497071e-1 + (0.11750334542845234952e-3 + (0.10544006716188967172e-5 + (0.73484461168242224872e-8 + (0.39383162326435752965e-10 + 0.15559069118222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 84: { -double t = 2*y100 - 169; -return 0.51298708979209258326e0 + (0.10520454564612427224e-1 + (0.12400930037494996655e-3 + (0.11147886579371265246e-5 + (0.77517184550568711454e-8 + (0.41283980931872622611e-10 + 0.16122419680000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 85: { -double t = 2*y100 - 171; -return 0.53453307979101369843e0 + (0.11030120618800726938e-1 + (0.13088741519572269581e-3 + (0.11784797595374515432e-5 + (0.81743383063044825400e-8 + (0.43252818449517081051e-10 + 0.16692592640000000000e-12 * t) * t) * t) * t) * t) * t; -} -case 86: { -double t = 2*y100 - 173; -return 0.55712643071169299478e0 + (0.11568077107929735233e-1 + (0.13815797838036651289e-3 + (0.12456314879260904558e-5 + (0.86169898078969313597e-8 + (0.45290446811539652525e-10 + 0.17268801084444444444e-12 * t) * t) * t) * t) * t) * t; -} -case 87: { -double t = 2*y100 - 175; -return 0.58082532122519320968e0 + (0.12135935999503877077e-1 + (0.14584223996665838559e-3 + (0.13164068573095710742e-5 + (0.90803643355106020163e-8 + (0.47397540713124619155e-10 + 0.17850211608888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 88: { -double t = 2*y100 - 177; -return 0.60569124025293375554e0 + (0.12735396239525550361e-1 + (0.15396244472258863344e-3 + (0.13909744385382818253e-5 + (0.95651595032306228245e-8 + (0.49574672127669041550e-10 + 0.18435945564444444444e-12 * t) * t) * t) * t) * t) * t; -} -case 89: { -double t = 2*y100 - 179; -return 0.63178916494715716894e0 + (0.13368247798287030927e-1 + (0.16254186562762076141e-3 + (0.14695084048334056083e-5 + (0.10072078109604152350e-7 + (0.51822304995680707483e-10 + 0.19025081422222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 90: { -double t = 2*y100 - 181; -return 0.65918774689725319200e0 + (0.14036375850601992063e-1 + (0.17160483760259706354e-3 + (0.15521885688723188371e-5 + (0.10601827031535280590e-7 + (0.54140790105837520499e-10 + 0.19616655146666666667e-12 * t) * t) * t) * t) * t) * t; -} -case 91: { -double t = 2*y100 - 183; -return 0.68795950683174433822e0 + (0.14741765091365869084e-1 + (0.18117679143520433835e-3 + (0.16392004108230585213e-5 + (0.11155116068018043001e-7 + (0.56530360194925690374e-10 + 0.20209663662222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 92: { -double t = 2*y100 - 185; -return 0.71818103808729967036e0 + (0.15486504187117112279e-1 + (0.19128428784550923217e-3 + (0.17307350969359975848e-5 + (0.11732656736113607751e-7 + (0.58991125287563833603e-10 + 0.20803065333333333333e-12 * t) * t) * t) * t) * t) * t; -} -case 93: { -double t = 2*y100 - 187; -return 0.74993321911726254661e0 + (0.16272790364044783382e-1 + (0.20195505163377912645e-3 + (0.18269894883203346953e-5 + (0.12335161021630225535e-7 + (0.61523068312169087227e-10 + 0.21395783431111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 94: { -double t = 2*y100 - 189; -return 0.78330143531283492729e0 + (0.17102934132652429240e-1 + (0.21321800585063327041e-3 + (0.19281661395543913713e-5 + (0.12963340087354341574e-7 + (0.64126040998066348872e-10 + 0.21986708942222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 95: { -double t = 2*y100 - 191; -return 0.81837581041023811832e0 + (0.17979364149044223802e-1 + (0.22510330592753129006e-3 + (0.20344732868018175389e-5 + (0.13617902941839949718e-7 + (0.66799760083972474642e-10 + 0.22574701262222222222e-12 * t) * t) * t) * t) * t) * t; -} -case 96: { -double t = 2*y100 - 193; -return 0.85525144775685126237e0 + (0.18904632212547561026e-1 + (0.23764237370371255638e-3 + (0.21461248251306387979e-5 + (0.14299555071870523786e-7 + (0.69543803864694171934e-10 + 0.23158593688888888889e-12 * t) * t) * t) * t) * t) * t; -} -case 97: { -double t = 2*y100 - 195; -return 0.89402868170849933734e0 + (0.19881418399127202569e-1 + (0.25086793128395995798e-3 + (0.22633402747585233180e-5 + (0.15008997042116532283e-7 + (0.72357609075043941261e-10 + 0.23737194737777777778e-12 * t) * t) * t) * t) * t) * t; -} -case 98: { -double t = 2*y100 - 197; -return 0.93481333942870796363e0 + (0.20912536329780368893e-1 + (0.26481403465998477969e-3 + (0.23863447359754921676e-5 + (0.15746923065472184451e-7 + (0.75240468141720143653e-10 + 0.24309291271111111111e-12 * t) * t) * t) * t) * t) * t; -} -case 99: { -double t = 2*y100 - 199; -return 0.97771701335885035464e0 + (0.22000938572830479551e-1 + (0.27951610702682383001e-3 + (0.25153688325245314530e-5 + (0.16514019547822821453e-7 + (0.78191526829368231251e-10 + 0.24873652355555555556e-12 * t) * t) * t) * t) * t) * t; -} - } - // we only get here if y = 1, i.e. |x| < 4*eps, in which case - // erfcx is within 1e-15 of 1.. - return 1.0; -} - -double erfcx(double x) -{ - if (x >= 0) { - if (x > 50) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x > 5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x+0.5/(x+1/(x+1.5/(x+2/x)))) */ - return ispi*((x*x) * (x*x+4.5) + 2) / (x * ((x*x) * (x*x+5) + 3.75)); - } - return erfcx_y100(400/(4+x)); - } - else - return x < -26.7 ? HUGE_VAL : (x < -6.1 ? 2*exp(x*x) - : 2*exp(x*x) - erfcx_y100(400/(4-x))); -} - -///////////////////////////////////////////////////////////////////////// -/* Compute a scaled Dawson integral - Faddeeva::w_im(x) = 2*Dawson(x)/sqrt(pi) - equivalent to the imaginary part w(x) for real x. - - Uses methods similar to the erfcx calculation above: continued fractions - for large |x|, a lookup table of Chebyshev polynomials for smaller |x|, - and finally a Taylor expansion for |x|<0.01. - - Steven G. Johnson, October 2012. */ - -/* Given y100=100*y, where y = 1/(1+x) for x >= 0, compute w_im(x). - - Uses a look-up table of 100 different Chebyshev polynomials - for y intervals [0,0.01], [0.01,0.02], ...., [0.99,1], generated - with the help of Maple and a little shell script. This allows - the Chebyshev polynomials to be of significantly lower degree (about 1/30) - compared to fitting the whole [0,1] interval with a single polynomial. */ -double w_im_y100(double y100, double x) { - switch ((int) y100) { - case 0: { - double t = 2*y100 - 1; - return 0.28351593328822191546e-2 + (0.28494783221378400759e-2 + (0.14427470563276734183e-4 + (0.10939723080231588129e-6 + (0.92474307943275042045e-9 + (0.89128907666450075245e-11 + 0.92974121935111111110e-13 * t) * t) * t) * t) * t) * t; - } - case 1: { - double t = 2*y100 - 3; - return 0.85927161243940350562e-2 + (0.29085312941641339862e-2 + (0.15106783707725582090e-4 + (0.11716709978531327367e-6 + (0.10197387816021040024e-8 + (0.10122678863073360769e-10 + 0.10917479678400000000e-12 * t) * t) * t) * t) * t) * t; - } - case 2: { - double t = 2*y100 - 5; - return 0.14471159831187703054e-1 + (0.29703978970263836210e-2 + (0.15835096760173030976e-4 + (0.12574803383199211596e-6 + (0.11278672159518415848e-8 + (0.11547462300333495797e-10 + 0.12894535335111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 3: { - double t = 2*y100 - 7; - return 0.20476320420324610618e-1 + (0.30352843012898665856e-2 + (0.16617609387003727409e-4 + (0.13525429711163116103e-6 + (0.12515095552507169013e-8 + (0.13235687543603382345e-10 + 0.15326595042666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 4: { - double t = 2*y100 - 9; - return 0.26614461952489004566e-1 + (0.31034189276234947088e-2 + (0.17460268109986214274e-4 + (0.14582130824485709573e-6 + (0.13935959083809746345e-8 + (0.15249438072998932900e-10 + 0.18344741882133333333e-12 * t) * t) * t) * t) * t) * t; - } - case 5: { - double t = 2*y100 - 11; - return 0.32892330248093586215e-1 + (0.31750557067975068584e-2 + (0.18369907582308672632e-4 + (0.15761063702089457882e-6 + (0.15577638230480894382e-8 + (0.17663868462699097951e-10 + (0.22126732680711111111e-12 + 0.30273474177737853668e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 6: { - double t = 2*y100 - 13; - return 0.39317207681134336024e-1 + (0.32504779701937539333e-2 + (0.19354426046513400534e-4 + (0.17081646971321290539e-6 + (0.17485733959327106250e-8 + (0.20593687304921961410e-10 + (0.26917401949155555556e-12 + 0.38562123837725712270e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 7: { - double t = 2*y100 - 15; - return 0.45896976511367738235e-1 + (0.33300031273110976165e-2 + (0.20423005398039037313e-4 + (0.18567412470376467303e-6 + (0.19718038363586588213e-8 + (0.24175006536781219807e-10 + (0.33059982791466666666e-12 + 0.49756574284439426165e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 8: { - double t = 2*y100 - 17; - return 0.52640192524848962855e-1 + (0.34139883358846720806e-2 + (0.21586390240603337337e-4 + (0.20247136501568904646e-6 + (0.22348696948197102935e-8 + (0.28597516301950162548e-10 + (0.41045502119111111110e-12 + 0.65151614515238361946e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 9: { - double t = 2*y100 - 19; - return 0.59556171228656770456e-1 + (0.35028374386648914444e-2 + (0.22857246150998562824e-4 + (0.22156372146525190679e-6 + (0.25474171590893813583e-8 + (0.34122390890697400584e-10 + (0.51593189879111111110e-12 + 0.86775076853908006938e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 10: { - double t = 2*y100 - 21; - return 0.66655089485108212551e-1 + (0.35970095381271285568e-2 + (0.24250626164318672928e-4 + (0.24339561521785040536e-6 + (0.29221990406518411415e-8 + (0.41117013527967776467e-10 + (0.65786450716444444445e-12 + 0.11791885745450623331e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 11: { - double t = 2*y100 - 23; - return 0.73948106345519174661e-1 + (0.36970297216569341748e-2 + (0.25784588137312868792e-4 + (0.26853012002366752770e-6 + (0.33763958861206729592e-8 + (0.50111549981376976397e-10 + (0.85313857496888888890e-12 + 0.16417079927706899860e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 12: { - double t = 2*y100 - 25; - return 0.81447508065002963203e-1 + (0.38035026606492705117e-2 + (0.27481027572231851896e-4 + (0.29769200731832331364e-6 + (0.39336816287457655076e-8 + (0.61895471132038157624e-10 + (0.11292303213511111111e-11 + 0.23558532213703884304e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 13: { - double t = 2*y100 - 27; - return 0.89166884027582716628e-1 + (0.39171301322438946014e-2 + (0.29366827260422311668e-4 + (0.33183204390350724895e-6 + (0.46276006281647330524e-8 + (0.77692631378169813324e-10 + (0.15335153258844444444e-11 + 0.35183103415916026911e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 14: { - double t = 2*y100 - 29; - return 0.97121342888032322019e-1 + (0.40387340353207909514e-2 + (0.31475490395950776930e-4 + (0.37222714227125135042e-6 + (0.55074373178613809996e-8 + (0.99509175283990337944e-10 + (0.21552645758222222222e-11 + 0.55728651431872687605e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 15: { - double t = 2*y100 - 31; - return 0.10532778218603311137e0 + (0.41692873614065380607e-2 + (0.33849549774889456984e-4 + (0.42064596193692630143e-6 + (0.66494579697622432987e-8 + (0.13094103581931802337e-9 + (0.31896187409777777778e-11 + 0.97271974184476560742e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 16: { - double t = 2*y100 - 33; - return 0.11380523107427108222e0 + (0.43099572287871821013e-2 + (0.36544324341565929930e-4 + (0.47965044028581857764e-6 + (0.81819034238463698796e-8 + (0.17934133239549647357e-9 + (0.50956666166186293627e-11 + (0.18850487318190638010e-12 + 0.79697813173519853340e-14 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 17: { - double t = 2*y100 - 35; - return 0.12257529703447467345e0 + (0.44621675710026986366e-2 + (0.39634304721292440285e-4 + (0.55321553769873381819e-6 + (0.10343619428848520870e-7 + (0.26033830170470368088e-9 + (0.87743837749108025357e-11 + (0.34427092430230063401e-12 + 0.10205506615709843189e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 18: { - double t = 2*y100 - 37; - return 0.13166276955656699478e0 + (0.46276970481783001803e-2 + (0.43225026380496399310e-4 + (0.64799164020016902656e-6 + (0.13580082794704641782e-7 + (0.39839800853954313927e-9 + (0.14431142411840000000e-10 + 0.42193457308830027541e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 19: { - double t = 2*y100 - 39; - return 0.14109647869803356475e0 + (0.48088424418545347758e-2 + (0.47474504753352150205e-4 + (0.77509866468724360352e-6 + (0.18536851570794291724e-7 + (0.60146623257887570439e-9 + (0.18533978397305276318e-10 + (0.41033845938901048380e-13 - 0.46160680279304825485e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 20: { - double t = 2*y100 - 41; - return 0.15091057940548936603e0 + (0.50086864672004685703e-2 + (0.52622482832192230762e-4 + (0.95034664722040355212e-6 + (0.25614261331144718769e-7 + (0.80183196716888606252e-9 + (0.12282524750534352272e-10 + (-0.10531774117332273617e-11 - 0.86157181395039646412e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 21: { - double t = 2*y100 - 43; - return 0.16114648116017010770e0 + (0.52314661581655369795e-2 + (0.59005534545908331315e-4 + (0.11885518333915387760e-5 + (0.33975801443239949256e-7 + (0.82111547144080388610e-9 + (-0.12357674017312854138e-10 + (-0.24355112256914479176e-11 - 0.75155506863572930844e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 22: { - double t = 2*y100 - 45; - return 0.17185551279680451144e0 + (0.54829002967599420860e-2 + (0.67013226658738082118e-4 + (0.14897400671425088807e-5 + (0.40690283917126153701e-7 + (0.44060872913473778318e-9 + (-0.52641873433280000000e-10 - 0.30940587864543343124e-11 * t) * t) * t) * t) * t) * t) * t; - } - case 23: { - double t = 2*y100 - 47; - return 0.18310194559815257381e0 + (0.57701559375966953174e-2 + (0.76948789401735193483e-4 + (0.18227569842290822512e-5 + (0.41092208344387212276e-7 + (-0.44009499965694442143e-9 + (-0.92195414685628803451e-10 + (-0.22657389705721753299e-11 + 0.10004784908106839254e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 24: { - double t = 2*y100 - 49; - return 0.19496527191546630345e0 + (0.61010853144364724856e-2 + (0.88812881056342004864e-4 + (0.21180686746360261031e-5 + (0.30652145555130049203e-7 + (-0.16841328574105890409e-8 + (-0.11008129460612823934e-9 + (-0.12180794204544515779e-12 + 0.15703325634590334097e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 25: { - double t = 2*y100 - 51; - return 0.20754006813966575720e0 + (0.64825787724922073908e-2 + (0.10209599627522311893e-3 + (0.22785233392557600468e-5 + (0.73495224449907568402e-8 + (-0.29442705974150112783e-8 + (-0.94082603434315016546e-10 + (0.23609990400179321267e-11 + 0.14141908654269023788e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 26: { - double t = 2*y100 - 53; - return 0.22093185554845172146e0 + (0.69182878150187964499e-2 + (0.11568723331156335712e-3 + (0.22060577946323627739e-5 + (-0.26929730679360840096e-7 + (-0.38176506152362058013e-8 + (-0.47399503861054459243e-10 + (0.40953700187172127264e-11 + 0.69157730376118511127e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 27: { - double t = 2*y100 - 55; - return 0.23524827304057813918e0 + (0.74063350762008734520e-2 + (0.12796333874615790348e-3 + (0.18327267316171054273e-5 + (-0.66742910737957100098e-7 + (-0.40204740975496797870e-8 + (0.14515984139495745330e-10 + (0.44921608954536047975e-11 - 0.18583341338983776219e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 28: { - double t = 2*y100 - 57; - return 0.25058626331812744775e0 + (0.79377285151602061328e-2 + (0.13704268650417478346e-3 + (0.11427511739544695861e-5 + (-0.10485442447768377485e-6 + (-0.34850364756499369763e-8 + (0.72656453829502179208e-10 + (0.36195460197779299406e-11 - 0.84882136022200714710e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 29: { - double t = 2*y100 - 59; - return 0.26701724900280689785e0 + (0.84959936119625864274e-2 + (0.14112359443938883232e-3 + (0.17800427288596909634e-6 + (-0.13443492107643109071e-6 + (-0.23512456315677680293e-8 + (0.11245846264695936769e-9 + (0.19850501334649565404e-11 - 0.11284666134635050832e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 30: { - double t = 2*y100 - 61; - return 0.28457293586253654144e0 + (0.90581563892650431899e-2 + (0.13880520331140646738e-3 + (-0.97262302362522896157e-6 + (-0.15077100040254187366e-6 + (-0.88574317464577116689e-9 + (0.12760311125637474581e-9 + (0.20155151018282695055e-12 - 0.10514169375181734921e-12 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 31: { - double t = 2*y100 - 63; - return 0.30323425595617385705e0 + (0.95968346790597422934e-2 + (0.12931067776725883939e-3 + (-0.21938741702795543986e-5 + (-0.15202888584907373963e-6 + (0.61788350541116331411e-9 + (0.11957835742791248256e-9 + (-0.12598179834007710908e-11 - 0.75151817129574614194e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 32: { - double t = 2*y100 - 65; - return 0.32292521181517384379e0 + (0.10082957727001199408e-1 + (0.11257589426154962226e-3 + (-0.33670890319327881129e-5 + (-0.13910529040004008158e-6 + (0.19170714373047512945e-8 + (0.94840222377720494290e-10 + (-0.21650018351795353201e-11 - 0.37875211678024922689e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 33: { - double t = 2*y100 - 67; - return 0.34351233557911753862e0 + (0.10488575435572745309e-1 + (0.89209444197248726614e-4 + (-0.43893459576483345364e-5 + (-0.11488595830450424419e-6 + (0.28599494117122464806e-8 + (0.61537542799857777779e-10 - 0.24935749227658002212e-11 * t) * t) * t) * t) * t) * t) * t; - } - case 34: { - double t = 2*y100 - 69; - return 0.36480946642143669093e0 + (0.10789304203431861366e-1 + (0.60357993745283076834e-4 + (-0.51855862174130669389e-5 + (-0.83291664087289801313e-7 + (0.33898011178582671546e-8 + (0.27082948188277716482e-10 + (-0.23603379397408694974e-11 + 0.19328087692252869842e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 35: { - double t = 2*y100 - 71; - return 0.38658679935694939199e0 + (0.10966119158288804999e-1 + (0.27521612041849561426e-4 + (-0.57132774537670953638e-5 + (-0.48404772799207914899e-7 + (0.35268354132474570493e-8 + (-0.32383477652514618094e-11 + (-0.19334202915190442501e-11 + 0.32333189861286460270e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 36: { - double t = 2*y100 - 73; - return 0.40858275583808707870e0 + (0.11006378016848466550e-1 + (-0.76396376685213286033e-5 + (-0.59609835484245791439e-5 + (-0.13834610033859313213e-7 + (0.33406952974861448790e-8 + (-0.26474915974296612559e-10 + (-0.13750229270354351983e-11 + 0.36169366979417390637e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 37: { - double t = 2*y100 - 75; - return 0.43051714914006682977e0 + (0.10904106549500816155e-1 + (-0.43477527256787216909e-4 + (-0.59429739547798343948e-5 + (0.17639200194091885949e-7 + (0.29235991689639918688e-8 + (-0.41718791216277812879e-10 + (-0.81023337739508049606e-12 + 0.33618915934461994428e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 38: { - double t = 2*y100 - 77; - return 0.45210428135559607406e0 + (0.10659670756384400554e-1 + (-0.78488639913256978087e-4 + (-0.56919860886214735936e-5 + (0.44181850467477733407e-7 + (0.23694306174312688151e-8 + (-0.49492621596685443247e-10 + (-0.31827275712126287222e-12 + 0.27494438742721623654e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 39: { - double t = 2*y100 - 79; - return 0.47306491195005224077e0 + (0.10279006119745977570e-1 + (-0.11140268171830478306e-3 + (-0.52518035247451432069e-5 + (0.64846898158889479518e-7 + (0.17603624837787337662e-8 + (-0.51129481592926104316e-10 + (0.62674584974141049511e-13 + 0.20055478560829935356e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 40: { - double t = 2*y100 - 81; - return 0.49313638965719857647e0 + (0.97725799114772017662e-2 + (-0.14122854267291533334e-3 + (-0.46707252568834951907e-5 + (0.79421347979319449524e-7 + (0.11603027184324708643e-8 + (-0.48269605844397175946e-10 + (0.32477251431748571219e-12 + 0.12831052634143527985e-13 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 41: { - double t = 2*y100 - 83; - return 0.51208057433416004042e0 + (0.91542422354009224951e-2 + (-0.16726530230228647275e-3 + (-0.39964621752527649409e-5 + (0.88232252903213171454e-7 + (0.61343113364949928501e-9 + (-0.42516755603130443051e-10 + (0.47910437172240209262e-12 + 0.66784341874437478953e-14 * t) * t) * t) * t) * t) * t) * t) * t; - } - case 42: { - double t = 2*y100 - 85; - return 0.52968945458607484524e0 + (0.84400880445116786088e-2 + (-0.18908729783854258774e-3 + (-0.32725905467782951931e-5 + (0.91956190588652090659e-7 + (0.14593989152420122909e-9 + (-0.35239490687644444445e-10 + 0.54613829888448694898e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 43: { - double t = 2*y100 - 87; - return 0.54578857454330070965e0 + (0.76474155195880295311e-2 + (-0.20651230590808213884e-3 + (-0.25364339140543131706e-5 + (0.91455367999510681979e-7 + (-0.23061359005297528898e-9 + (-0.27512928625244444444e-10 + 0.54895806008493285579e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 44: { - double t = 2*y100 - 89; - return 0.56023851910298493910e0 + (0.67938321739997196804e-2 + (-0.21956066613331411760e-3 + (-0.18181127670443266395e-5 + (0.87650335075416845987e-7 + (-0.51548062050366615977e-9 + (-0.20068462174044444444e-10 + 0.50912654909758187264e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 45: { - double t = 2*y100 - 91; - return 0.57293478057455721150e0 + (0.58965321010394044087e-2 + (-0.22841145229276575597e-3 + (-0.11404605562013443659e-5 + (0.81430290992322326296e-7 + (-0.71512447242755357629e-9 + (-0.13372664928000000000e-10 + 0.44461498336689298148e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 46: { - double t = 2*y100 - 93; - return 0.58380635448407827360e0 + (0.49717469530842831182e-2 + (-0.23336001540009645365e-3 + (-0.51952064448608850822e-6 + (0.73596577815411080511e-7 + (-0.84020916763091566035e-9 + (-0.76700972702222222221e-11 + 0.36914462807972467044e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 47: { - double t = 2*y100 - 95; - return 0.59281340237769489597e0 + (0.40343592069379730568e-2 + (-0.23477963738658326185e-3 + (0.34615944987790224234e-7 + (0.64832803248395814574e-7 + (-0.90329163587627007971e-9 + (-0.30421940400000000000e-11 + 0.29237386653743536669e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 48: { - double t = 2*y100 - 97; - return 0.59994428743114271918e0 + (0.30976579788271744329e-2 + (-0.23308875765700082835e-3 + (0.51681681023846925160e-6 + (0.55694594264948268169e-7 + (-0.91719117313243464652e-9 + (0.53982743680000000000e-12 + 0.22050829296187771142e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 49: { - double t = 2*y100 - 99; - return 0.60521224471819875444e0 + (0.21732138012345456060e-2 + (-0.22872428969625997456e-3 + (0.92588959922653404233e-6 + (0.46612665806531930684e-7 + (-0.89393722514414153351e-9 + (0.31718550353777777778e-11 + 0.15705458816080549117e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 50: { - double t = 2*y100 - 101; - return 0.60865189969791123620e0 + (0.12708480848877451719e-2 + (-0.22212090111534847166e-3 + (0.12636236031532793467e-5 + (0.37904037100232937574e-7 + (-0.84417089968101223519e-9 + (0.49843180828444444445e-11 + 0.10355439441049048273e-12 * t) * t) * t) * t) * t) * t) * t; - } - case 51: { - double t = 2*y100 - 103; - return 0.61031580103499200191e0 + (0.39867436055861038223e-3 + (-0.21369573439579869291e-3 + (0.15339402129026183670e-5 + (0.29787479206646594442e-7 + (-0.77687792914228632974e-9 + (0.61192452741333333334e-11 + 0.60216691829459295780e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 52: { - double t = 2*y100 - 105; - return 0.61027109047879835868e0 + (-0.43680904508059878254e-3 + (-0.20383783788303894442e-3 + (0.17421743090883439959e-5 + (0.22400425572175715576e-7 + (-0.69934719320045128997e-9 + (0.67152759655111111110e-11 + 0.26419960042578359995e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 53: { - double t = 2*y100 - 107; - return 0.60859639489217430521e0 + (-0.12305921390962936873e-2 + (-0.19290150253894682629e-3 + (0.18944904654478310128e-5 + (0.15815530398618149110e-7 + (-0.61726850580964876070e-9 + 0.68987888999111111110e-11 * t) * t) * t) * t) * t) * t; - } - case 54: { - double t = 2*y100 - 109; - return 0.60537899426486075181e0 + (-0.19790062241395705751e-2 + (-0.18120271393047062253e-3 + (0.19974264162313241405e-5 + (0.10055795094298172492e-7 + (-0.53491997919318263593e-9 + (0.67794550295111111110e-11 - 0.17059208095741511603e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 55: { - double t = 2*y100 - 111; - return 0.60071229457904110537e0 + (-0.26795676776166354354e-2 + (-0.16901799553627508781e-3 + (0.20575498324332621581e-5 + (0.51077165074461745053e-8 + (-0.45536079828057221858e-9 + (0.64488005516444444445e-11 - 0.29311677573152766338e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 56: { - double t = 2*y100 - 113; - return 0.59469361520112714738e0 + (-0.33308208190600993470e-2 + (-0.15658501295912405679e-3 + (0.20812116912895417272e-5 + (0.93227468760614182021e-9 + (-0.38066673740116080415e-9 + (0.59806790359111111110e-11 - 0.36887077278950440597e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 57: { - double t = 2*y100 - 115; - return 0.58742228631775388268e0 + (-0.39321858196059227251e-2 + (-0.14410441141450122535e-3 + (0.20743790018404020716e-5 + (-0.25261903811221913762e-8 + (-0.31212416519526924318e-9 + (0.54328422462222222221e-11 - 0.40864152484979815972e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 58: { - double t = 2*y100 - 117; - return 0.57899804200033018447e0 + (-0.44838157005618913447e-2 + (-0.13174245966501437965e-3 + (0.20425306888294362674e-5 + (-0.53330296023875447782e-8 + (-0.25041289435539821014e-9 + (0.48490437205333333334e-11 - 0.42162206939169045177e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 59: { - double t = 2*y100 - 119; - return 0.56951968796931245974e0 + (-0.49864649488074868952e-2 + (-0.11963416583477567125e-3 + (0.19906021780991036425e-5 + (-0.75580140299436494248e-8 + (-0.19576060961919820491e-9 + (0.42613011928888888890e-11 - 0.41539443304115604377e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 60: { - double t = 2*y100 - 121; - return 0.55908401930063918964e0 + (-0.54413711036826877753e-2 + (-0.10788661102511914628e-3 + (0.19229663322982839331e-5 + (-0.92714731195118129616e-8 + (-0.14807038677197394186e-9 + (0.36920870298666666666e-11 - 0.39603726688419162617e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 61: { - double t = 2*y100 - 123; - return 0.54778496152925675315e0 + (-0.58501497933213396670e-2 + (-0.96582314317855227421e-4 + (0.18434405235069270228e-5 + (-0.10541580254317078711e-7 + (-0.10702303407788943498e-9 + (0.31563175582222222222e-11 - 0.36829748079110481422e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 62: { - double t = 2*y100 - 125; - return 0.53571290831682823999e0 + (-0.62147030670760791791e-2 + (-0.85782497917111760790e-4 + (0.17553116363443470478e-5 + (-0.11432547349815541084e-7 + (-0.72157091369041330520e-10 + (0.26630811607111111111e-11 - 0.33578660425893164084e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 63: { - double t = 2*y100 - 127; - return 0.52295422962048434978e0 + (-0.65371404367776320720e-2 + (-0.75530164941473343780e-4 + (0.16613725797181276790e-5 + (-0.12003521296598910761e-7 + (-0.42929753689181106171e-10 + (0.22170894940444444444e-11 - 0.30117697501065110505e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 64: { - double t = 2*y100 - 129; - return 0.50959092577577886140e0 + (-0.68197117603118591766e-2 + (-0.65852936198953623307e-4 + (0.15639654113906716939e-5 + (-0.12308007991056524902e-7 + (-0.18761997536910939570e-10 + (0.18198628922666666667e-11 - 0.26638355362285200932e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 65: { - double t = 2*y100 - 131; - return 0.49570040481823167970e0 + (-0.70647509397614398066e-2 + (-0.56765617728962588218e-4 + (0.14650274449141448497e-5 + (-0.12393681471984051132e-7 + (0.92904351801168955424e-12 + (0.14706755960177777778e-11 - 0.23272455351266325318e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 66: { - double t = 2*y100 - 133; - return 0.48135536250935238066e0 + (-0.72746293327402359783e-2 + (-0.48272489495730030780e-4 + (0.13661377309113939689e-5 + (-0.12302464447599382189e-7 + (0.16707760028737074907e-10 + (0.11672928324444444444e-11 - 0.20105801424709924499e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 67: { - double t = 2*y100 - 135; - return 0.46662374675511439448e0 + (-0.74517177649528487002e-2 + (-0.40369318744279128718e-4 + (0.12685621118898535407e-5 + (-0.12070791463315156250e-7 + (0.29105507892605823871e-10 + (0.90653314645333333334e-12 - 0.17189503312102982646e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 68: { - double t = 2*y100 - 137; - return 0.45156879030168268778e0 + (-0.75983560650033817497e-2 + (-0.33045110380705139759e-4 + (0.11732956732035040896e-5 + (-0.11729986947158201869e-7 + (0.38611905704166441308e-10 + (0.68468768305777777779e-12 - 0.14549134330396754575e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 69: { - double t = 2*y100 - 139; - return 0.43624909769330896904e0 + (-0.77168291040309554679e-2 + (-0.26283612321339907756e-4 + (0.10811018836893550820e-5 + (-0.11306707563739851552e-7 + (0.45670446788529607380e-10 + (0.49782492549333333334e-12 - 0.12191983967561779442e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 70: { - double t = 2*y100 - 141; - return 0.42071877443548481181e0 + (-0.78093484015052730097e-2 + (-0.20064596897224934705e-4 + (0.99254806680671890766e-6 + (-0.10823412088884741451e-7 + (0.50677203326904716247e-10 + (0.34200547594666666666e-12 - 0.10112698698356194618e-13 * t) * t) * t) * t) * t) * t) * t; - } - case 71: { - double t = 2*y100 - 143; - return 0.40502758809710844280e0 + (-0.78780384460872937555e-2 + (-0.14364940764532853112e-4 + (0.90803709228265217384e-6 + (-0.10298832847014466907e-7 + (0.53981671221969478551e-10 + (0.21342751381333333333e-12 - 0.82975901848387729274e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 72: { - double t = 2*y100 - 145; - return 0.38922115269731446690e0 + (-0.79249269708242064120e-2 + (-0.91595258799106970453e-5 + (0.82783535102217576495e-6 + (-0.97484311059617744437e-8 + (0.55889029041660225629e-10 + (0.10851981336888888889e-12 - 0.67278553237853459757e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 73: { - double t = 2*y100 - 147; - return 0.37334112915460307335e0 + (-0.79519385109223148791e-2 + (-0.44219833548840469752e-5 + (0.75209719038240314732e-6 + (-0.91848251458553190451e-8 + (0.56663266668051433844e-10 + (0.23995894257777777778e-13 - 0.53819475285389344313e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 74: { - double t = 2*y100 - 149; - return 0.35742543583374223085e0 + (-0.79608906571527956177e-2 + (-0.12530071050975781198e-6 + (0.68088605744900552505e-6 + (-0.86181844090844164075e-8 + (0.56530784203816176153e-10 + (-0.43120012248888888890e-13 - 0.42372603392496813810e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 75: { - double t = 2*y100 - 151; - return 0.34150846431979618536e0 + (-0.79534924968773806029e-2 + (0.37576885610891515813e-5 + (0.61419263633090524326e-6 + (-0.80565865409945960125e-8 + (0.55684175248749269411e-10 + (-0.95486860764444444445e-13 - 0.32712946432984510595e-14 * t) * t) * t) * t) * t) * t) * t; - } - case 76: { - double t = 2*y100 - 153; - return 0.32562129649136346824e0 + (-0.79313448067948884309e-2 + (0.72539159933545300034e-5 + (0.55195028297415503083e-6 + (-0.75063365335570475258e-8 + (0.54281686749699595941e-10 - 0.13545424295111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 77: { - double t = 2*y100 - 155; - return 0.30979191977078391864e0 + (-0.78959416264207333695e-2 + (0.10389774377677210794e-4 + (0.49404804463196316464e-6 + (-0.69722488229411164685e-8 + (0.52469254655951393842e-10 - 0.16507860650666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 78: { - double t = 2*y100 - 157; - return 0.29404543811214459904e0 + (-0.78486728990364155356e-2 + (0.13190885683106990459e-4 + (0.44034158861387909694e-6 + (-0.64578942561562616481e-8 + (0.50354306498006928984e-10 - 0.18614473550222222222e-12 * t) * t) * t) * t) * t) * t; - } - case 79: { - double t = 2*y100 - 159; - return 0.27840427686253660515e0 + (-0.77908279176252742013e-2 + (0.15681928798708548349e-4 + (0.39066226205099807573e-6 + (-0.59658144820660420814e-8 + (0.48030086420373141763e-10 - 0.20018995173333333333e-12 * t) * t) * t) * t) * t) * t; - } - case 80: { - double t = 2*y100 - 161; - return 0.26288838011163800908e0 + (-0.77235993576119469018e-2 + (0.17886516796198660969e-4 + (0.34482457073472497720e-6 + (-0.54977066551955420066e-8 + (0.45572749379147269213e-10 - 0.20852924954666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 81: { - double t = 2*y100 - 163; - return 0.24751539954181029717e0 + (-0.76480877165290370975e-2 + (0.19827114835033977049e-4 + (0.30263228619976332110e-6 + (-0.50545814570120129947e-8 + (0.43043879374212005966e-10 - 0.21228012028444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 82: { - double t = 2*y100 - 165; - return 0.23230087411688914593e0 + (-0.75653060136384041587e-2 + (0.21524991113020016415e-4 + (0.26388338542539382413e-6 + (-0.46368974069671446622e-8 + (0.40492715758206515307e-10 - 0.21238627815111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 83: { - double t = 2*y100 - 167; - return 0.21725840021297341931e0 + (-0.74761846305979730439e-2 + (0.23000194404129495243e-4 + (0.22837400135642906796e-6 + (-0.42446743058417541277e-8 + (0.37958104071765923728e-10 - 0.20963978568888888889e-12 * t) * t) * t) * t) * t) * t; - } - case 84: { - double t = 2*y100 - 169; - return 0.20239979200788191491e0 + (-0.73815761980493466516e-2 + (0.24271552727631854013e-4 + (0.19590154043390012843e-6 + (-0.38775884642456551753e-8 + (0.35470192372162901168e-10 - 0.20470131678222222222e-12 * t) * t) * t) * t) * t) * t; - } - case 85: { - double t = 2*y100 - 171; - return 0.18773523211558098962e0 + (-0.72822604530339834448e-2 + (0.25356688567841293697e-4 + (0.16626710297744290016e-6 + (-0.35350521468015310830e-8 + (0.33051896213898864306e-10 - 0.19811844544000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 86: { - double t = 2*y100 - 173; - return 0.17327341258479649442e0 + (-0.71789490089142761950e-2 + (0.26272046822383820476e-4 + (0.13927732375657362345e-6 + (-0.32162794266956859603e-8 + (0.30720156036105652035e-10 - 0.19034196304000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 87: { - double t = 2*y100 - 175; - return 0.15902166648328672043e0 + (-0.70722899934245504034e-2 + (0.27032932310132226025e-4 + (0.11474573347816568279e-6 + (-0.29203404091754665063e-8 + (0.28487010262547971859e-10 - 0.18174029063111111111e-12 * t) * t) * t) * t) * t) * t; - } - case 88: { - double t = 2*y100 - 177; - return 0.14498609036610283865e0 + (-0.69628725220045029273e-2 + (0.27653554229160596221e-4 + (0.92493727167393036470e-7 + (-0.26462055548683583849e-8 + (0.26360506250989943739e-10 - 0.17261211260444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 89: { - double t = 2*y100 - 179; - return 0.13117165798208050667e0 + (-0.68512309830281084723e-2 + (0.28147075431133863774e-4 + (0.72351212437979583441e-7 + (-0.23927816200314358570e-8 + (0.24345469651209833155e-10 - 0.16319736960000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 90: { - double t = 2*y100 - 181; - return 0.11758232561160626306e0 + (-0.67378491192463392927e-2 + (0.28525664781722907847e-4 + (0.54156999310046790024e-7 + (-0.21589405340123827823e-8 + (0.22444150951727334619e-10 - 0.15368675584000000000e-12 * t) * t) * t) * t) * t) * t; - } - case 91: { - double t = 2*y100 - 183; - return 0.10422112945361673560e0 + (-0.66231638959845581564e-2 + (0.28800551216363918088e-4 + (0.37758983397952149613e-7 + (-0.19435423557038933431e-8 + (0.20656766125421362458e-10 - 0.14422990012444444444e-12 * t) * t) * t) * t) * t) * t; - } - case 92: { - double t = 2*y100 - 185; - return 0.91090275493541084785e-1 + (-0.65075691516115160062e-2 + (0.28982078385527224867e-4 + (0.23014165807643012781e-7 + (-0.17454532910249875958e-8 + (0.18981946442680092373e-10 - 0.13494234691555555556e-12 * t) * t) * t) * t) * t) * t; - } - case 93: { - double t = 2*y100 - 187; - return 0.78191222288771379358e-1 + (-0.63914190297303976434e-2 + (0.29079759021299682675e-4 + (0.97885458059415717014e-8 + (-0.15635596116134296819e-8 + (0.17417110744051331974e-10 - 0.12591151763555555556e-12 * t) * t) * t) * t) * t) * t; - } - case 94: { - double t = 2*y100 - 189; - return 0.65524757106147402224e-1 + (-0.62750311956082444159e-2 + (0.29102328354323449795e-4 + (-0.20430838882727954582e-8 + (-0.13967781903855367270e-8 + (0.15958771833747057569e-10 - 0.11720175765333333333e-12 * t) * t) * t) * t) * t) * t; - } - case 95: { - double t = 2*y100 - 191; - return 0.53091065838453612773e-1 + (-0.61586898417077043662e-2 + (0.29057796072960100710e-4 + (-0.12597414620517987536e-7 + (-0.12440642607426861943e-8 + (0.14602787128447932137e-10 - 0.10885859114666666667e-12 * t) * t) * t) * t) * t) * t; - } - case 96: { - double t = 2*y100 - 193; - return 0.40889797115352738582e-1 + (-0.60426484889413678200e-2 + (0.28953496450191694606e-4 + (-0.21982952021823718400e-7 + (-0.11044169117553026211e-8 + (0.13344562332430552171e-10 - 0.10091231402844444444e-12 * t) * t) * t) * t) * t) * t; - } - case 97: { - double t = 2*y100 - 195; - return 0.28920121009594899986e-1 + (-0.59271325915413781788e-2 + (0.28796136372768177423e-4 + (-0.30300382596279568642e-7 + (-0.97688275022802329749e-9 + (0.12179215701512592356e-10 - 0.93380988481777777779e-13 * t) * t) * t) * t) * t) * t; - } - case 98: { - double t = 2*y100 - 197; - return 0.17180782722617876655e-1 + (-0.58123419543161127769e-2 + (0.28591841095380959666e-4 + (-0.37642963496443667043e-7 + (-0.86055809047367300024e-9 + (0.11101709356762665578e-10 - 0.86272947493333333334e-13 * t) * t) * t) * t) * t) * t; - } - case 99: case 100: { // use Taylor expansion for small x (|x| <= 0.010101...) - // (2/sqrt(pi)) * (x - 2/3 x^3 + 4/15 x^5 - 8/105 x^7) - double x2 = x*x; - return x * (1.1283791670955125739 - - x2 * (0.75225277806367504925 - - x2 * (0.30090111122547001970 - - x2 * 0.085971746064420005629))); - } - } - /* Since 0 <= y100 < 101, this is only reached if x is NaN, - in which case we should return NaN. */ - return std::numeric_limits::quiet_NaN(); -} - -double w_im(double x) -{ - if (x >= 0) { - if (x > 45) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x > 5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x-0.5/(x-1/(x-1.5/(x-2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); - } - return w_im_y100(100/(1+x), x); - } - else { // = -Faddeeva::w_im(-x) - if (x < -45) { // continued-fraction expansion is faster - const double ispi = 0.56418958354775628694807945156; // 1 / sqrt(pi) - if (x < -5e7) // 1-term expansion, important to avoid overflow - return ispi / x; - /* 5-term expansion (rely on compiler for CSE), simplified from: - ispi / (x+0.5/(x+1/(x+1.5/(x+2/x)))) */ - return ispi*((x*x) * (x*x-4.5) + 2) / (x * ((x*x) * (x*x-5) + 3.75)); - } - return -w_im_y100(100/(1-x), -x); - } -} - -} \ No newline at end of file diff --git a/scipy/special/xsf/fresnel.h b/scipy/special/xsf/fresnel.h deleted file mode 100644 index 094ba031710c..000000000000 --- a/scipy/special/xsf/fresnel.h +++ /dev/null @@ -1,424 +0,0 @@ -#pragma once - -#include "cephes/fresnl.h" -#include "config.h" - -namespace xsf { -namespace detail { - - inline void cfc(std::complex z, std::complex *zf, std::complex *zd) { - - // ========================================================= - // Purpose: Compute complex Fresnel integral C(z) and C'(z) - // Input : z --- Argument of C(z) - // Output: ZF --- C(z) - // ZD --- C'(z) - // ========================================================= - - int k, m; - double wa0, wa; - std::complex c, cr, cf, cf0, cf1, cg, d; - const double eps = 1.0e-14; - const double pi = 3.141592653589793; - - double w0 = std::abs(z); - std::complex zp = 0.5 * pi * z * z; - std::complex zp2 = zp * zp; - std::complex z0 = 0.0; - - if (z == z0) { - c = z0; - } else if (w0 <= 2.5) { - cr = z; - c = cr; - wa0 = 0.0; - for (k = 1; k <= 80; k++) { - cr = -0.5 * cr * (4.0 * k - 3.0) / static_cast(k) / (2.0 * k - 1.0) / (4.0 * k + 1.0) * zp2; - c += cr; - wa = std::abs(c); - if ((fabs((wa - wa0) / wa) < eps) && (k > 10)) { - *zf = c; - *zd = std::cos(0.5 * pi * z * z); - return; - } - wa0 = wa; - } - } else if ((w0 > 2.5) && (w0 < 4.5)) { - m = 85; - c = z0; - cf1 = z0; - cf0 = 1.0e-100; - for (k = m; k >= 0; k--) { - cf = (2.0 * k + 3.0) * cf0 / zp - cf1; - if (k % 2 == 0) { - c += cf; - } - cf1 = cf0; - cf0 = cf; - } - c *= 2.0 / (pi * z) * std::sin(zp) / cf; - } else { - // See comment at CFS(), use C(z) = iC(-iz) - if ((z.imag() > -z.real()) && (z.imag() <= z.real())) { - // right quadrant - d = 0.5; - } else if ((z.imag() > z.real()) && (z.imag() >= -z.real())) { - // upper quadrant - d = std::complex(0, 0.5); - } else if ((z.imag() < -z.real()) && (z.imag() >= z.real())) { - // left quadrant - d = -0.5; - } else { - d = std::complex(0, -0.5); - } - cr = 1.0; - cf = 1.0; - for (k = 1; k <= 20; k++) { - cr = -0.25 * cr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / zp2; - cf += cr; - } - cr = 1.0 / (pi * z * z); - cg = cr; - for (k = 1; k <= 12; k++) { - cr = -0.25 * cr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / zp2; - cg += cr; - } - c = d + (cf * std::sin(zp) - cg * std::cos(zp)) / (pi * z); - } - *zf = c; - *zd = std::cos(0.5 * pi * z * z); - return; - } - - inline void cfs(std::complex z, std::complex *zf, std::complex *zd) { - - // ========================================================= - // Purpose: Compute complex Fresnel Integral S(z) and S'(z) - // Input : z --- Argument of S(z) - // Output: ZF --- S(z) - // ZD --- S'(z) - // ========================================================= - - int k, m; - double wb0, wb; - std::complex s, cr, cf, cf0, cf1, cg, d; - const double eps = 1.0e-14; - const double pi = 3.141592653589793; - - double w0 = std::abs(z); - std::complex zp = 0.5 * pi * z * z; - std::complex zp2 = zp * zp; - std::complex z0 = 0.0; - - if (z == z0) { - s = z0; - } else if (w0 <= 2.5) { - s = z * zp / 3.0; - cr = s; - wb0 = 0.0; - for (k = 1; k <= 80; k++) { - cr = -0.5 * cr * (4.0 * k - 1.0) / static_cast(k) / (2.0 * k + 1.0) / (4.0 * k + 3.0) * zp2; - s += cr; - wb = std::abs(s); - if ((fabs(wb - wb0) < eps) && (k > 10)) { - *zf = s; - *zd = std::sin(0.5 * pi * z * z); - return; - } - wb0 = wb; - } - } else if ((w0 > 2.5) && (w0 < 4.5)) { - m = 85; - s = z0; - cf1 = z0; - cf0 = 1.0e-100; - for (k = m; k >= 0; k--) { - cf = (2.0 * k + 3.0) * cf0 / zp - cf1; - if (k % 2 == 1) { - s += cf; - } - cf1 = cf0; - cf0 = cf; - } - s = 2.0 / (pi * z) * std::sin(zp) / cf * s; - } else { - // Auxiliary functions f(z) and g(z) can be computed using an - // asymptotic expansion in the right quadrant |arg(z)| <= pi/4, not pi/2 - // as sometimes suggested. Use the symmetry S(z) = -iS(-iz). - // Interestingly, most of the expansion code is the same across - // the quadrants. (The forth power in Z is the equalizer here.) - // Only one constant has to be adapted. - if ((z.imag() > -z.real()) && (z.imag() <= z.real())) { - // right quadrant - d = 0.5; - } else if ((z.imag() > z.real()) && (z.imag() >= -z.real())) { - // upper quadrant - d = std::complex(0, -0.5); - } else if ((z.imag() < -z.real()) && (z.imag() >= z.real())) { - // left quadrant - d = -0.5; - } else { - d = std::complex(0, 0.5); - } - cr = 1.0; - cf = 1.0; - for (k = 1; k <= 20; k++) { - cr = -0.25 * cr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / zp2; - cf += cr; - } - cr = 1.0; - cg = 1.0; - for (k = 1; k <= 12; k++) { - cr = -0.25 * cr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / zp2; - cg += cr; - } - cg = cg / (pi * z * z); - s = d - (cf * std::cos(zp) + cg * std::sin(zp)) / (pi * z); - } - *zf = s; - *zd = std::sin(0.5 * pi * z * z); - return; - } - - template - void ffk(int ks, T x, std::complex &f, std::complex &g) { - - // ======================================================= - // Purpose: Compute modified Fresnel integrals F±(x) - // and K±(x) - // Input : x --- Argument of F±(x) and K±(x) - // KS --- Sign code - // KS=0 for calculating F+(x) and K+(x) - // KS=1 for calculating F_(x) and K_(x) - // Output: FR --- Re[F±(x)] - // FI --- Im[F±(x)] - // GR --- Re[K±(x)] - // GI --- Im[K±(x)] - // ====================================================== - - const T eps = 1.0e-15; - const T pi = 3.141592653589793; - const T pp2 = 1.2533141373155; - const T p2p = 0.7978845608028654; - - T fi0, c1, s1, cs, ss, xa, x2, x4, xc, xf, xf0, xf1, xg, xp, xq, xq2, xr, xs, xsu, xw; - - xa = fabs(x); - x2 = x * x; - x4 = x2 * x2; - - if (x == 0.0) { - f.real(0.5 * sqrt(0.5 * pi)); - f.imag(pow(-1, ks) * f.real()); - g = 0.5; - } else { - if (xa <= 2.5) { - xr = p2p * xa; - c1 = xr; - - for (int k = 1; k <= 50; ++k) { - xr = -0.5 * xr * (4.0 * k - 3.0) / k / (2.0 * k - 1.0) / (4.0 * k + 1.0) * x4; - c1 += xr; - if (fabs(xr / c1) < eps) - break; - } - - s1 = p2p * xa * xa * xa / 3.0; - xr = s1; - - for (int k = 1; k <= 50; ++k) { - xr = -0.5 * xr * (4.0 * k - 1.0) / k / (2.0 * k + 1.0) / (4.0 * k + 3.0) * x4; - s1 += xr; - if (fabs(xr / s1) < eps) - break; - } - - f.real(pp2 * (0.5 - c1)); - fi0 = pp2 * (0.5 - s1); - f.imag(pow(-1, ks) * fi0); - } else if (xa < 5.5) { - int m = (int) (42 + 1.75 * x2); - xsu = 0.0; - xc = 0.0; - xs = 0.0; - xf1 = 0.0; - xf0 = 1.0e-100; - - for (int k = m; k >= 0; --k) { - xf = (2.0 * k + 3.0) * xf0 / x2 - xf1; - if (k % 2 == 0) { - xc += xf; - } else { - xs += xf; - } - xsu += (2.0 * k + 1.0) * xf * xf; - xf1 = xf0; - xf0 = xf; - } - - xq = sqrt(xsu); - xw = p2p * xa / xq; - c1 = xc * xw; - s1 = xs * xw; - } else { - xr = 1.0; - xf = 1.0; - - for (int k = 1; k <= 12; ++k) { - xr = -0.25 * xr * (4.0 * k - 1.0) * (4.0 * k - 3.0) / x4; - xf += xr; - } - - xr = 1.0 / (2.0 * xa * xa); - xg = xr; - - for (int k = 1; k <= 12; ++k) { - xr = -0.25 * xr * (4.0 * k + 1.0) * (4.0 * k - 1.0) / x4; - xg += xr; - } - - c1 = 0.5 + (xf * sin(x2) - xg * cos(x2)) / sqrt(2.0 * pi) / xa; - s1 = 0.5 - (xf * cos(x2) + xg * sin(x2)) / sqrt(2.0 * pi) / xa; - } - - f.real(pp2 * (0.5 - c1)); - fi0 = pp2 * (0.5 - s1); - f.imag(pow(-1, ks) * fi0); - - xp = x2 + pi / 4.0; - cs = cos(xp); - ss = sin(xp); - xq2 = 1.0 / sqrt(pi); - - g.real(xq2 * (f.real() * cs + fi0 * ss)); - g.imag(pow(-1, ks) * xq2 * (fi0 * cs - f.real() * ss)); - - if (x < 0.0) { - f.real(pp2 - f.real()); - f.imag(pow(-1, ks) * pp2 - f.real()); - g.real(cos(x2) - g.real()); - g.imag(-pow(-1, ks) * sin(x2) - g.imag()); - } - } - } - -} // namespace detail - -/* Fresnel integrals of complex numbers */ - -inline void fresnel(double z, double &fs, double &fc) { cephes::fresnl(z, &fs, &fc); } - -inline void fresnel(float z, float &fs, float &fc) { - double fs_double; - double fc_double; - fresnel(static_cast(z), fs_double, fc_double); - - fs = fs_double; - fc = fc_double; -} - -inline void fresnel(std::complex z, std::complex &fs, std::complex &fc) { - std::complex fd; - detail::cfs(z, &fs, &fd); - detail::cfc(z, &fc, &fd); -} - -inline void fresnel(std::complex z, std::complex &fs, std::complex &fc) { - std::complex fs_cdouble; - std::complex fc_cdouble; - fresnel(static_cast>(z), fs_cdouble, fc_cdouble); - - fs = fs_cdouble; - fc = fc_cdouble; -} - -template -void modified_fresnel_plus(T x, std::complex &Fplus, std::complex &Kplus) { - detail::ffk(0, x, Fplus, Kplus); -} - -template -void modified_fresnel_minus(T x, std::complex &Fminus, std::complex &Kminus) { - detail::ffk(1, x, Fminus, Kminus); -} - -inline void fcszo(int kf, int nt, std::complex *zo) { - - // =============================================================== - // Purpose: Compute the complex zeros of Fresnel integral C(z) - // or S(z) using modified Newton's iteration method - // Input : KF --- Function code - // KF=1 for C(z) or KF=2 for S(z) - // NT --- Total number of zeros - // Output: ZO(L) --- L-th zero of C(z) or S(z) - // Routines called: - // (1) CFC for computing Fresnel integral C(z) - // (2) CFS for computing Fresnel integral S(z) - // ============================================================== - - int it; - double psq, px, py, w, w0; - std::complex z, zp, zf, zd, zfd, zgd, zq, zw; - const double pi = 3.141592653589793; - psq = 0.0; - w = 0.0; - - for (int nr = 1; nr <= nt; ++nr) { - if (kf == 1) - psq = sqrt(4.0 * nr - 1.0); - if (kf == 2) - psq = 2.0 * sqrt(nr); - - px = psq - log(pi * psq) / (pi * pi * psq * psq * psq); - py = log(pi * psq) / (pi * psq); - z = std::complex(px, py); - - if (kf == 2) { - if (nr == 2) { - z = std::complex(2.8334, 0.2443); - } - if (nr == 3) { - z = std::complex(3.4674, 0.2185); - } - if (nr == 4) { - z = std::complex(4.0025, 0.2008); - } - } - - it = 0; - do { - it++; - if (kf == 1) { - detail::cfc(z, &zf, &zd); - } - if (kf == 2) { - detail::cfs(z, &zf, &zd); - } - - zp = 1.0; - for (int i = 1; i < nr; i++) - zp *= (z - zo[i - 1]); - - zfd = zf / zp; - zq = 0.0; - for (int i = 1; i < nr; i++) { - zw = 1.0; - for (int j = 1; j < nr; j++) { - if (j == i) { - continue; - } - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-12)); - zo[nr - 1] = z; - } - return; -} - -} // namespace xsf diff --git a/scipy/special/xsf/gamma.h b/scipy/special/xsf/gamma.h deleted file mode 100644 index 5d94a6111084..000000000000 --- a/scipy/special/xsf/gamma.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "cephes/gamma.h" -#include "cephes/igam.h" -#include "cephes/igami.h" -#include "loggamma.h" - -namespace xsf { - -template -XSF_HOST_DEVICE T gamma(T x) { - return cephes::Gamma(x); -} - -inline double gammainc(double a, double x) { return cephes::igam(a, x); } - -inline float gammainc(float a, float x) { return gammainc(static_cast(a), static_cast(x)); } - -inline double gammaincinv(double a, double p) { return cephes::igami(a, p); } - -inline float gammaincinv(float a, float p) { return gammaincinv(static_cast(a), static_cast(p)); } - -inline double gammaincc(double a, double x) { return cephes::igamc(a, x); } - -inline float gammaincc(float a, float x) { return gammaincc(static_cast(a), static_cast(x)); } - -inline double gammainccinv(double a, double p) { return cephes::igamci(a, p); } - -inline float gammainccinv(float a, float p) { return gammainccinv(static_cast(a), static_cast(p)); } - -XSF_HOST_DEVICE inline double gammaln(double x) { return cephes::lgam(x); } - -XSF_HOST_DEVICE inline float gammaln(float x) { return gammaln(static_cast(x)); } - -XSF_HOST_DEVICE inline double gammasgn(double x) { return cephes::gammasgn(x); } - -XSF_HOST_DEVICE inline float gammasgn(float x) { return gammasgn(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex gamma(std::complex z) { - // Compute Gamma(z) using loggamma. - if (z.real() <= 0 && z == std::floor(z.real())) { - // Poles - set_error("gamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - return std::exp(loggamma(z)); -} - -XSF_HOST_DEVICE inline std::complex gamma(std::complex z) { - return static_cast>(gamma(static_cast>(z))); -} - -template -T gamma_ratio(T a, T b) { - return std::tgamma(a) / std::tgamma(b); -} - -} // namespace xsf diff --git a/scipy/special/xsf/hyp2f1.h b/scipy/special/xsf/hyp2f1.h deleted file mode 100644 index 9d4eff753220..000000000000 --- a/scipy/special/xsf/hyp2f1.h +++ /dev/null @@ -1,694 +0,0 @@ -/* Implementation of Gauss's hypergeometric function for complex values. - * - * This implementation is based on the Fortran implementation by Shanjie Zhang and - * Jianming Jin included in specfun.f [1]_. Computation of Gauss's hypergeometric - * function involves handling a patchwork of special cases. By default the Zhang and - * Jin implementation has been followed as closely as possible except for situations where - * an improvement was obvious. We've attempted to document the reasons behind decisions - * made by Zhang and Jin and to document the reasons for deviating from their implementation - * when this has been done. References to the NIST Digital Library of Mathematical - * Functions [2]_ have been added where they are appropriate. The review paper by - * Pearson et al [3]_ is an excellent resource for best practices for numerical - * computation of hypergeometric functions. We have followed this review paper - * when making improvements to and correcting defects in Zhang and Jin's - * implementation. When Pearson et al propose several competing alternatives for a - * given case, we've used our best judgment to decide on the method to use. - * - * Author: Albert Steppi - * - * Distributed under the same license as Scipy. - * - * References - * ---------- - * .. [1] S. Zhang and J.M. Jin, "Computation of Special Functions", Wiley 1996 - * .. [2] NIST Digital Library of Mathematical Functions. http://dlmf.nist.gov/, - * Release 1.1.1 of 2021-03-15. F. W. J. Olver, A. B. Olde Daalhuis, - * D. W. Lozier, B. I. Schneider, R. F. Boisvert, C. W. Clark, B. R. Miller, - * B. V. Saunders, H. S. Cohl, and M. A. McClain, eds. - * .. [3] Pearson, J.W., Olver, S. & Porter, M.A. - * "Numerical methods for the computation of the confluent and Gauss - * hypergeometric functions." - * Numer Algor 74, 821-866 (2017). https://doi.org/10.1007/s11075-016-0173-0 - * .. [4] Raimundas Vidunas, "Degenerate Gauss Hypergeometric Functions", - * Kyushu Journal of Mathematics, 2007, Volume 61, Issue 1, Pages 109-135, - * .. [5] López, J.L., Temme, N.M. New series expansions of the Gauss hypergeometric - * function. Adv Comput Math 39, 349-365 (2013). - * https://doi.org/10.1007/s10444-012-9283-y - * """ - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "tools.h" - -#include "binom.h" -#include "cephes/gamma.h" -#include "cephes/lanczos.h" -#include "cephes/poch.h" -#include "cephes/hyp2f1.h" -#include "digamma.h" - -namespace xsf { -namespace detail { - constexpr double hyp2f1_EPS = 1e-15; - /* The original implementation in SciPy from Zhang and Jin used 1500 for the - * maximum number of series iterations in some cases and 500 in others. - * Through the empirical results on the test cases in - * scipy/special/_precompute/hyp2f1_data.py, it was determined that these values - * can lead to early termination of series which would have eventually converged - * at a reasonable level of accuracy. We've bumped the iteration limit to 3000, - * and may adjust it again based on further analysis. */ - constexpr std::uint64_t hyp2f1_MAXITER = 3000; - - XSF_HOST_DEVICE inline double four_gammas_lanczos(double u, double v, double w, double x) { - /* Compute ratio of gamma functions using lanczos approximation. - * - * Computes gamma(u)*gamma(v)/(gamma(w)*gamma(x)) - * - * It is assumed that x = u + v - w, but it is left to the user to - * ensure this. - * - * The lanczos approximation takes the form - * - * gamma(x) = factor(x) * lanczos_sum_expg_scaled(x) - * - * where factor(x) = ((x + lanczos_g - 0.5)/e)**(x - 0.5). - * - * The formula above is only valid for x >= 0.5, but can be extended to - * x < 0.5 with the reflection principle. - * - * Using the lanczos approximation when computing this ratio of gamma functions - * allows factors to be combined analytically to avoid underflow and overflow - * and produce a more accurate result. The condition x = u + v - w makes it - * possible to cancel the factors in the expression - * - * factor(u) * factor(v) / (factor(w) * factor(x)) - * - * by taking one factor and absorbing it into the others. Currently, this - * implementation takes the factor corresponding to the argument with largest - * absolute value and absorbs it into the others. - * - * Since this is only called internally by four_gammas. It is assumed that - * |u| >= |v| and |w| >= |x|. - */ - - /* The below implementation may incorrectly return finite results - * at poles of the gamma function. Handle these cases explicitly. */ - if ((u == std::trunc(u) && u <= 0) || (v == std::trunc(v) && v <= 0)) { - /* Return nan if numerator has pole. Diverges to +- infinity - * depending on direction so value is undefined. */ - return std::numeric_limits::quiet_NaN(); - } - if ((w == std::trunc(w) && w <= 0) || (x == std::trunc(x) && x <= 0)) { - // Return 0 if denominator has pole but not numerator. - return 0.0; - } - - double result = 1.0; - double ugh, vgh, wgh, xgh, u_prime, v_prime, w_prime, x_prime; - - if (u >= 0.5) { - result *= cephes::lanczos_sum_expg_scaled(u); - ugh = u + cephes::lanczos_g - 0.5; - u_prime = u; - } else { - result /= cephes::lanczos_sum_expg_scaled(1 - u) * std::sin(M_PI * u) * M_1_PI; - ugh = 0.5 - u + cephes::lanczos_g; - u_prime = 1 - u; - } - - if (v >= 0.5) { - result *= cephes::lanczos_sum_expg_scaled(v); - vgh = v + cephes::lanczos_g - 0.5; - v_prime = v; - } else { - result /= cephes::lanczos_sum_expg_scaled(1 - v) * std::sin(M_PI * v) * M_1_PI; - vgh = 0.5 - v + cephes::lanczos_g; - v_prime = 1 - v; - } - - if (w >= 0.5) { - result /= cephes::lanczos_sum_expg_scaled(w); - wgh = w + cephes::lanczos_g - 0.5; - w_prime = w; - } else { - result *= cephes::lanczos_sum_expg_scaled(1 - w) * std::sin(M_PI * w) * M_1_PI; - wgh = 0.5 - w + cephes::lanczos_g; - w_prime = 1 - w; - } - - if (x >= 0.5) { - result /= cephes::lanczos_sum_expg_scaled(x); - xgh = x + cephes::lanczos_g - 0.5; - x_prime = x; - } else { - result *= cephes::lanczos_sum_expg_scaled(1 - x) * std::sin(M_PI * x) * M_1_PI; - xgh = 0.5 - x + cephes::lanczos_g; - x_prime = 1 - x; - } - - if (std::abs(u) >= std::abs(w)) { - // u has greatest absolute value. Absorb ugh into the others. - if (std::abs((v_prime - u_prime) * (v - 0.5)) < 100 * ugh and v > 100) { - /* Special case where base is close to 1. Condition taken from - * Boost's beta function implementation. */ - result *= std::exp((v - 0.5) * std::log1p((v_prime - u_prime) / ugh)); - } else { - result *= std::pow(vgh / ugh, v - 0.5); - } - - if (std::abs((u_prime - w_prime) * (w - 0.5)) < 100 * wgh and u > 100) { - result *= std::exp((w - 0.5) * std::log1p((u_prime - w_prime) / wgh)); - } else { - result *= std::pow(ugh / wgh, w - 0.5); - } - - if (std::abs((u_prime - x_prime) * (x - 0.5)) < 100 * xgh and u > 100) { - result *= std::exp((x - 0.5) * std::log1p((u_prime - x_prime) / xgh)); - } else { - result *= std::pow(ugh / xgh, x - 0.5); - } - } else { - // w has greatest absolute value. Absorb wgh into the others. - if (std::abs((u_prime - w_prime) * (u - 0.5)) < 100 * wgh and u > 100) { - result *= std::exp((u - 0.5) * std::log1p((u_prime - w_prime) / wgh)); - } else { - result *= pow(ugh / wgh, u - 0.5); - } - if (std::abs((v_prime - w_prime) * (v - 0.5)) < 100 * wgh and v > 100) { - result *= std::exp((v - 0.5) * std::log1p((v_prime - w_prime) / wgh)); - } else { - result *= std::pow(vgh / wgh, v - 0.5); - } - if (std::abs((w_prime - x_prime) * (x - 0.5)) < 100 * xgh and x > 100) { - result *= std::exp((x - 0.5) * std::log1p((w_prime - x_prime) / xgh)); - } else { - result *= std::pow(wgh / xgh, x - 0.5); - } - } - // This exhausts all cases because we assume |u| >= |v| and |w| >= |x|. - - return result; - } - - XSF_HOST_DEVICE inline double four_gammas(double u, double v, double w, double x) { - double result; - - // Without loss of generality, ensure |u| >= |v| and |w| >= |x|. - if (std::abs(v) > std::abs(u)) { - std::swap(u, v); - } - if (std::abs(x) > std::abs(w)) { - std::swap(x, w); - } - /* Direct ratio tends to be more accurate for arguments in this range. Range - * chosen empirically based on the relevant benchmarks in - * scipy/special/_precompute/hyp2f1_data.py */ - if (std::abs(u) <= 100 && std::abs(v) <= 100 && std::abs(w) <= 100 && std::abs(x) <= 100) { - result = cephes::Gamma(u) * cephes::Gamma(v) * (cephes::rgamma(w) * cephes::rgamma(x)); - if (std::isfinite(result) && result != 0.0) { - return result; - } - } - result = four_gammas_lanczos(u, v, w, x); - if (std::isfinite(result) && result != 0.0) { - return result; - } - // If overflow or underflow, try again with logs. - result = std::exp(cephes::lgam(v) - cephes::lgam(x) + cephes::lgam(u) - cephes::lgam(w)); - result *= cephes::gammasgn(u) * cephes::gammasgn(w) * cephes::gammasgn(v) * cephes::gammasgn(x); - return result; - } - - class HypergeometricSeriesGenerator { - /* Maclaurin series for hyp2f1. - * - * Series is convergent for |z| < 1 but is only practical for numerical - * computation when |z| < 0.9. - */ - public: - XSF_HOST_DEVICE HypergeometricSeriesGenerator(double a, double b, double c, std::complex z) - : a_(a), b_(b), c_(c), z_(z), term_(1.0), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex output = term_; - term_ = term_ * (a_ + k_) * (b_ + k_) / ((k_ + 1) * (c_ + k_)) * z_; - ++k_; - return output; - } - - private: - double a_, b_, c_; - std::complex z_, term_; - std::uint64_t k_; - }; - - class Hyp2f1Transform1Generator { - /* 1 -z transformation of standard series.*/ - public: - XSF_HOST_DEVICE Hyp2f1Transform1Generator(double a, double b, double c, std::complex z) - : factor1_(four_gammas(c, c - a - b, c - a, c - b)), - factor2_(four_gammas(c, a + b - c, a, b) * std::pow(1.0 - z, c - a - b)), - generator1_(HypergeometricSeriesGenerator(a, b, a + b - c + 1, 1.0 - z)), - generator2_(HypergeometricSeriesGenerator(c - a, c - b, c - a - b + 1, 1.0 - z)) {} - - XSF_HOST_DEVICE std::complex operator()() { - return factor1_ * generator1_() + factor2_ * generator2_(); - } - - private: - std::complex factor1_, factor2_; - HypergeometricSeriesGenerator generator1_, generator2_; - }; - - class Hyp2f1Transform1LimitSeriesGenerator { - /* 1 - z transform in limit as c - a - b approaches an integer m. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform1LimitSeriesGenerator(double a, double b, double m, std::complex z) - : d1_(xsf::digamma(a)), d2_(xsf::digamma(b)), d3_(xsf::digamma(1 + m)), - d4_(xsf::digamma(1.0)), a_(a), b_(b), m_(m), z_(z), log_1_z_(std::log(1.0 - z)), - factor_(cephes::rgamma(m + 1)), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term_ = (d1_ + d2_ - d3_ - d4_ + log_1_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (a_ + k_); // d1 = digamma(a + k) - d2_ += 1 / (b_ + k_); // d2 = digamma(b + k) - d3_ += 1 / (1.0 + m_ + k_); // d3 = digamma(1 + m + k) - d4_ += 1 / (1.0 + k_); // d4 = digamma(1 + k) - factor_ *= (a_ + k_) * (b_ + k_) / ((k_ + 1.0) * (m_ + k_ + 1)) * (1.0 - z_); - ++k_; - return term_; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, m_; - std::complex z_, log_1_z_, factor_; - int k_; - }; - - class Hyp2f1Transform2Generator { - /* 1/z transformation of standard series.*/ - public: - XSF_HOST_DEVICE Hyp2f1Transform2Generator(double a, double b, double c, std::complex z) - : factor1_(four_gammas(c, b - a, b, c - a) * std::pow(-z, -a)), - factor2_(four_gammas(c, a - b, a, c - b) * std::pow(-z, -b)), - generator1_(HypergeometricSeriesGenerator(a, a - c + 1, a - b + 1, 1.0 / z)), - generator2_(HypergeometricSeriesGenerator(b, b - c + 1, b - a + 1, 1.0 / z)) {} - - XSF_HOST_DEVICE std::complex operator()() { - return factor1_ * generator1_() + factor2_ * generator2_(); - } - - private: - std::complex factor1_, factor2_; - HypergeometricSeriesGenerator generator1_, generator2_; - }; - - class Hyp2f1Transform2LimitSeriesGenerator { - /* 1/z transform in limit as a - b approaches a non-negative integer m. (Can swap a and b to - * handle the m a negative integer case. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitSeriesGenerator(double a, double b, double c, double m, - std::complex z) - : d1_(xsf::digamma(1.0)), d2_(xsf::digamma(1 + m)), d3_(xsf::digamma(a)), - d4_(xsf::digamma(c - a)), a_(a), b_(b), c_(c), m_(m), z_(z), log_neg_z_(std::log(-z)), - factor_(xsf::cephes::poch(b, m) * xsf::cephes::poch(1 - c + b, m) * - xsf::cephes::rgamma(m + 1)), - k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term = (d1_ + d2_ - d3_ - d4_ + log_neg_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (1.0 + k_); // d1 = digamma(1 + k) - d2_ += 1 / (1.0 + m_ + k_); // d2 = digamma(1 + m + k) - d3_ += 1 / (a_ + k_); // d3 = digamma(a + k) - d4_ -= 1 / (c_ - a_ - k_ - 1); // d4 = digamma(c - a - k) - factor_ *= (b_ + m_ + k_) * (1 - c_ + b_ + m_ + k_) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, c_, m_; - std::complex z_, log_neg_z_, factor_; - std::uint64_t k_; - }; - - class Hyp2f1Transform2LimitSeriesCminusAIntGenerator { - /* 1/z transform in limit as a - b approaches a non-negative integer m, and c - a approaches - * a positive integer n. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitSeriesCminusAIntGenerator(double a, double b, double c, double m, - double n, std::complex z) - : d1_(xsf::digamma(1.0)), d2_(xsf::digamma(1 + m)), d3_(xsf::digamma(a)), - d4_(xsf::digamma(n)), a_(a), b_(b), c_(c), m_(m), n_(n), z_(z), log_neg_z_(std::log(-z)), - factor_(xsf::cephes::poch(b, m) * xsf::cephes::poch(1 - c + b, m) * - xsf::cephes::rgamma(m + 1)), - k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex term; - if (k_ < n_) { - term = (d1_ + d2_ - d3_ - d4_ + log_neg_z_) * factor_; - // Use digamma(x + 1) = digamma(x) + 1/x - d1_ += 1 / (1.0 + k_); // d1 = digamma(1 + k) - d2_ += 1 / (1 + m_ + k_); // d2 = digamma(1 + m + k) - d3_ += 1 / (a_ + k_); // d3 = digamma(a + k) - d4_ -= 1 / (n_ - k_ - 1); // d4 = digamma(c - a - k) - factor_ *= (b_ + m_ + k_) * (1 - c_ + b_ + m_ + k_) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - if (k_ == n_) { - /* When c - a approaches a positive integer and k_ >= c - a = n then - * poch(1 - c + b + m + k) = poch(1 - c + a + k) = approaches zero and - * digamma(c - a - k) approaches a pole. However we can use the limit - * digamma(-n + epsilon) / gamma(-n + epsilon) -> (-1)**(n + 1) * (n+1)! as epsilon -> 0 - * to continue the series. - * - * poch(1 - c + b, m + k) = gamma(1 - c + b + m + k)/gamma(1 - c + b) - * - * If a - b is an integer and c - a is an integer, then a and b must both be integers, so assume - * a and b are integers and take the limit as c approaches an integer. - * - * gamma(1 - c + epsilon + a + k)/gamma(1 - c - epsilon + b) = - * (gamma(c + epsilon - b) / gamma(c + epsilon - a - k)) * - * (sin(pi * (c + epsilon - b)) / sin(pi * (c + epsilon - a - k))) (reflection principle) - * - * In the limit as epsilon goes to zero, the ratio of sines will approach - * (-1)**(a - b + k) = (-1)**(m + k) - * - * We may then replace - * - * poch(1 - c - epsilon + b, m + k)*digamma(c + epsilon - a - k) - * - * with - * - * (-1)**(a - b + k)*gamma(c + epsilon - b) * digamma(c + epsilon - a - k) / gamma(c + epsilon - a - k) - * - * and taking the limit epsilon -> 0 gives - * - * (-1)**(a - b + k) * gamma(c - b) * (-1)**(k + a - c + 1)(k + a - c)! - * = (-1)**(c - b - 1)*Gamma(k + a - c + 1) - */ - factor_ = std::pow(-1, m_ + n_) * xsf::binom(c_ - 1, b_ - 1) * - xsf::cephes::poch(c_ - a_ + 1, m_ - 1) / std::pow(z_, static_cast(k_)); - } - term = factor_; - factor_ *= (b_ + m_ + k_) * (k_ + a_ - c_ + 1) / ((k_ + 1) * (m_ + k_ + 1)) / z_; - ++k_; - return term; - } - - private: - double d1_, d2_, d3_, d4_, a_, b_, c_, m_, n_; - std::complex z_, log_neg_z_, factor_; - std::uint64_t k_; - }; - - class Hyp2f1Transform2LimitFinitePartGenerator { - /* Initial finite sum in limit as a - b approaches a non-negative integer m. The limiting series - * for the 1 - z transform also has an initial finite sum, but it is a standard hypergeometric - * series. */ - public: - XSF_HOST_DEVICE Hyp2f1Transform2LimitFinitePartGenerator(double b, double c, double m, - std::complex z) - : b_(b), c_(c), m_(m), z_(z), term_(cephes::Gamma(m) * cephes::rgamma(c - b)), k_(0) {} - - XSF_HOST_DEVICE std::complex operator()() { - std::complex output = term_; - term_ = term_ * (b_ + k_) * (c_ - b_ - k_ - 1) / ((k_ + 1) * (m_ - k_ - 1)) / z_; - ++k_; - return output; - } - - private: - double b_, c_, m_; - std::complex z_, term_; - std::uint64_t k_; - }; - - class LopezTemmeSeriesGenerator { - /* Lopez-Temme Series for Gaussian hypergeometric function [4]. - * - * Converges for all z with real(z) < 1, including in the regions surrounding - * the points exp(+- i*pi/3) that are not covered by any of the standard - * transformations. - */ - public: - XSF_HOST_DEVICE LopezTemmeSeriesGenerator(double a, double b, double c, std::complex z) - : n_(0), a_(a), b_(b), c_(c), phi_previous_(1.0), phi_(1 - 2 * b / c), z_(z), Z_(a * z / (z - 2.0)) {} - - XSF_HOST_DEVICE std::complex operator()() { - if (n_ == 0) { - ++n_; - return 1.0; - } - if (n_ > 1) { // Update phi and Z for n>=2 - double new_phi = ((n_ - 1) * phi_previous_ - (2.0 * b_ - c_) * phi_) / (c_ + (n_ - 1)); - phi_previous_ = phi_; - phi_ = new_phi; - Z_ = Z_ * z_ / (z_ - 2.0) * ((a_ + (n_ - 1)) / n_); - } - ++n_; - return Z_ * phi_; - } - - private: - std::uint64_t n_; - double a_, b_, c_, phi_previous_, phi_; - std::complex z_, Z_; - }; - - XSF_HOST_DEVICE std::complex hyp2f1_transform1_limiting_case(double a, double b, double c, double m, - std::complex z) { - /* 1 - z transform in limiting case where c - a - b approaches an integer m. */ - std::complex result = 0.0; - if (m >= 0) { - if (m != 0) { - auto series_generator = HypergeometricSeriesGenerator(a, b, 1 - m, 1.0 - z); - result += four_gammas(m, c, a + m, b + m) * series_eval_fixed_length(series_generator, - std::complex{0.0, 0.0}, - static_cast(m)); - } - std::complex prefactor = std::pow(-1.0, m + 1) * xsf::cephes::Gamma(c) / - (xsf::cephes::Gamma(a) * xsf::cephes::Gamma(b)) * - std::pow(1.0 - z, m); - auto series_generator = Hyp2f1Transform1LimitSeriesGenerator(a + m, b + m, m, z); - result += prefactor * series_eval(series_generator, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } else { - result = four_gammas(-m, c, a, b) * std::pow(1.0 - z, m); - auto series_generator1 = HypergeometricSeriesGenerator(a + m, b + m, 1 + m, 1.0 - z); - result *= series_eval_fixed_length(series_generator1, std::complex{0.0, 0.0}, - static_cast(-m)); - double prefactor = std::pow(-1.0, m + 1) * xsf::cephes::Gamma(c) * - (xsf::cephes::rgamma(a + m) * xsf::cephes::rgamma(b + m)); - auto series_generator2 = Hyp2f1Transform1LimitSeriesGenerator(a, b, -m, z); - result += prefactor * series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } - } - - XSF_HOST_DEVICE std::complex hyp2f1_transform2_limiting_case(double a, double b, double c, double m, - std::complex z) { - /* 1 / z transform in limiting case where a - b approaches a non-negative integer m. Negative integer case - * can be handled by swapping a and b. */ - auto series_generator1 = Hyp2f1Transform2LimitFinitePartGenerator(b, c, m, z); - std::complex result = cephes::Gamma(c) * cephes::rgamma(a) * std::pow(-z, -b); - result *= - series_eval_fixed_length(series_generator1, std::complex{0.0, 0.0}, static_cast(m)); - std::complex prefactor = cephes::Gamma(c) * (cephes::rgamma(a) * cephes::rgamma(c - b) * std::pow(-z, -a)); - double n = c - a; - if (abs(n - std::round(n)) < hyp2f1_EPS) { - auto series_generator2 = Hyp2f1Transform2LimitSeriesCminusAIntGenerator(a, b, c, m, n, z); - result += prefactor * series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, - hyp2f1_MAXITER, "hyp2f1"); - return result; - } - auto series_generator2 = Hyp2f1Transform2LimitSeriesGenerator(a, b, c, m, z); - result += prefactor * - series_eval(series_generator2, std::complex{0.0, 0.0}, hyp2f1_EPS, hyp2f1_MAXITER, "hyp2f1"); - return result; - } - -} // namespace detail - -XSF_HOST_DEVICE inline std::complex hyp2f1(double a, double b, double c, std::complex z) { - /* Special Cases - * ----------------------------------------------------------------------- - * Takes constant value 1 when a = 0 or b = 0, even if c is a non-positive - * integer. This follows mpmath. */ - if (a == 0 || b == 0) { - return 1.0; - } - double z_abs = std::abs(z); - // Equals 1 when z i 0, unless c is 0. - if (z_abs == 0) { - if (c != 0) { - return 1.0; - } else { - // Returning real part NAN and imaginary part 0 follows mpmath. - return std::complex{std::numeric_limits::quiet_NaN(), 0}; - } - } - bool a_neg_int = a == std::trunc(a) && a < 0; - bool b_neg_int = b == std::trunc(b) && b < 0; - bool c_non_pos_int = c == std::trunc(c) and c <= 0; - /* Diverges when c is a non-positive integer unless a is an integer with - * c <= a <= 0 or b is an integer with c <= b <= 0, (or z equals 0 with - * c != 0) Cases z = 0, a = 0, or b = 0 have already been handled. We follow - * mpmath in handling the degenerate cases where any of a, b, c are - * non-positive integers. See [3] for a treatment of degenerate cases. */ - if (c_non_pos_int && !((a_neg_int && c <= a && a < 0) || (b_neg_int && c <= b && b < 0))) { - return std::complex{std::numeric_limits::infinity(), 0}; - } - /* Reduces to a polynomial when a or b is a negative integer. - * If a and b are both negative integers, we take care to terminate - * the series at a or b of smaller magnitude. This is to ensure proper - * handling of situations like a < c < b <= 0, a, b, c all non-positive - * integers, where terminating at a would lead to a term of the form 0 / 0. */ - double max_degree; - if (a_neg_int || b_neg_int) { - if (a_neg_int && b_neg_int) { - max_degree = a > b ? std::abs(a) : std::abs(b); - } else if (a_neg_int) { - max_degree = std::abs(a); - } else { - max_degree = std::abs(b); - } - if (max_degree <= (double) UINT64_MAX) { - auto series_generator = detail::HypergeometricSeriesGenerator(a, b, c, z); - return detail::series_eval_fixed_length(series_generator, std::complex{0.0, 0.0}, max_degree + 1); - } else { - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - return std::complex{std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - } - // Kummer's Theorem for z = -1; c = 1 + a - b (DLMF 15.4.26) - if (std::abs(z + 1.0) < detail::hyp2f1_EPS && std::abs(1 + a - b - c) < detail::hyp2f1_EPS && !c_non_pos_int) { - return detail::four_gammas(a - b + 1, 0.5 * a + 1, a + 1, 0.5 * a - b + 1); - } - std::complex result; - bool c_minus_a_neg_int = c - a == std::trunc(c - a) && c - a < 0; - bool c_minus_b_neg_int = c - b == std::trunc(c - b) && c - b < 0; - /* If one of c - a or c - b is a negative integer, reduces to evaluating - * a polynomial through an Euler hypergeometric transformation. - * (DLMF 15.8.1) */ - if (c_minus_a_neg_int || c_minus_b_neg_int) { - max_degree = c_minus_b_neg_int ? std::abs(c - b) : std::abs(c - a); - if (max_degree <= (double) UINT64_MAX) { - result = std::pow(1.0 - z, c - a - b); - auto series_generator = detail::HypergeometricSeriesGenerator(c - a, c - b, c, z); - result *= - detail::series_eval_fixed_length(series_generator, std::complex{0.0, 0.0}, max_degree + 2); - return result; - } else { - set_error("hyp2f1", SF_ERROR_NO_RESULT, NULL); - return std::complex{std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN()}; - } - } - /* Diverges as real(z) -> 1 when c <= a + b. - * Todo: Actually check for overflow instead of using a fixed tolerance for - * all parameter combinations like in the Fortran original. */ - if (std::abs(1 - z.real()) < detail::hyp2f1_EPS && z.imag() == 0 && c - a - b <= 0 && !c_non_pos_int) { - return std::complex{std::numeric_limits::infinity(), 0}; - } - // Gauss's Summation Theorem for z = 1; c - a - b > 0 (DLMF 15.4.20). - if (z == 1.0 && c - a - b > 0 && !c_non_pos_int) { - return detail::four_gammas(c, c - a - b, c - a, c - b); - } - /* |z| < 0, z.real() >= 0. Use the Maclaurin Series. - * ----------------------------------------------------------------------- - * Apply Euler Hypergeometric Transformation (DLMF 15.8.1) to reduce - * size of a and b if possible. We follow Zhang and Jin's - * implementation [1] although there is very likely a better heuristic - * to determine when this transformation should be applied. As it - * stands, this hurts precision in some cases. */ - if (z_abs < 0.9 && z.real() >= 0) { - if (c - a < a && c - b < b) { - result = std::pow(1.0 - z, c - a - b); - auto series_generator = detail::HypergeometricSeriesGenerator(c - a, c - b, c, z); - result *= detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return result; - } - auto series_generator = detail::HypergeometricSeriesGenerator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* Points near exp(iπ/3), exp(-iπ/3) not handled by any of the standard - * transformations. Use series of López and Temme [5]. These regions - * were not correctly handled by Zhang and Jin's implementation. - * -------------------------------------------------------------------------*/ - if (0.9 <= z_abs && z_abs < 1.1 && std::abs(1.0 - z) >= 0.9 && z.real() >= 0) { - /* This condition for applying Euler Transformation (DLMF 15.8.1) - * was determined empirically to work better for this case than that - * used in Zhang and Jin's implementation for |z| < 0.9, - * real(z) >= 0. */ - if ((c - a <= a && c - b < b) || (c - a < a && c - b <= b)) { - auto series_generator = detail::LopezTemmeSeriesGenerator(c - a, c - b, c, z); - result = std::pow(1.0 - 0.5 * z, a - c); // Lopez-Temme prefactor - result *= detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return std::pow(1.0 - z, c - a - b) * result; // Euler transform prefactor. - } - auto series_generator = detail::LopezTemmeSeriesGenerator(a, b, c, z); - result = detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - return std::pow(1.0 - 0.5 * z, -a) * result; // Lopez-Temme prefactor. - } - /* z/(z - 1) transformation (DLMF 15.8.1). Avoids cancellation issues that - * occur with Maclaurin series for real(z) < 0. - * -------------------------------------------------------------------------*/ - if (z_abs < 1.1 && z.real() < 0) { - if (0 < b && b < a && a < c) { - std::swap(a, b); - } - auto series_generator = detail::HypergeometricSeriesGenerator(a, c - b, c, z / (z - 1.0)); - return std::pow(1.0 - z, -a) * detail::series_eval(series_generator, std::complex{0.0, 0.0}, - detail::hyp2f1_EPS, detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* 1 - z transformation (DLMF 15.8.4). */ - if (0.9 <= z_abs && z_abs < 1.1) { - if (std::abs(c - a - b - std::round(c - a - b)) < detail::hyp2f1_EPS) { - // Removable singularity when c - a - b is an integer. Need to use limiting formula. - double m = std::round(c - a - b); - return detail::hyp2f1_transform1_limiting_case(a, b, c, m, z); - } - auto series_generator = detail::Hyp2f1Transform1Generator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); - } - /* 1/z transformation (DLMF 15.8.2). */ - if (std::abs(a - b - std::round(a - b)) < detail::hyp2f1_EPS) { - if (b > a) { - std::swap(a, b); - } - double m = std::round(a - b); - return detail::hyp2f1_transform2_limiting_case(a, b, c, m, z); - } - auto series_generator = detail::Hyp2f1Transform2Generator(a, b, c, z); - return detail::series_eval(series_generator, std::complex{0.0, 0.0}, detail::hyp2f1_EPS, - detail::hyp2f1_MAXITER, "hyp2f1"); -} - -XSF_HOST_DEVICE inline std::complex hyp2f1(float a, float b, float c, std::complex x) { - return static_cast>(hyp2f1(static_cast(a), static_cast(b), - static_cast(c), static_cast>(x))); -} - -XSF_HOST_DEVICE inline double hyp2f1(double a, double b, double c, double x) { return cephes::hyp2f1(a, b, c, x); } - -XSF_HOST_DEVICE inline float hyp2f1(float a, float b, float c, float x) { - return hyp2f1(static_cast(a), static_cast(b), static_cast(c), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/iv_ratio.h b/scipy/special/xsf/iv_ratio.h deleted file mode 100644 index e5dc871bd003..000000000000 --- a/scipy/special/xsf/iv_ratio.h +++ /dev/null @@ -1,173 +0,0 @@ -// Numerically stable computation of iv(v+1, x) / iv(v, x) - -#pragma once - -#include "config.h" -#include "tools.h" -#include "error.h" -#include "cephes/dd_real.h" - -namespace xsf { - -/* Generates the "tail" of Perron's continued fraction for iv(v,x)/iv(v-1,x). - * - * The Perron continued fraction is studied in [1]. It is given by - * - * iv(v, x) x -(2v+1)x -(2v+3)x -(2v+5)x - * R := --------- = ------ ---------- ---------- ---------- ... - * iv(v-1,x) x+2v + 2(v+x)+1 + 2(v+x)+2 + 2(v+x)+3 + - * - * Given a suitable constant c, the continued fraction may be rearranged - * into the following form to avoid premature floating point overflow: - * - * xc -(2vc+c)(xc) -(2vc+3c)(xc) -(2vc+5c)(xc) - * R = -----, fc = 2vc + ------------ ------------- ------------- ... - * xc+fc 2(vc+xc)+c + 2(vc+xc)+2c + 2(vc+xc)+3c + - * - * This class generates the fractions of fc after 2vc. - * - * [1] Gautschi, W. and Slavik, J. (1978). "On the computation of modified - * Bessel function ratios." Mathematics of Computation, 32(143):865-875. - */ -template -struct IvRatioCFTailGenerator { - - XSF_HOST_DEVICE IvRatioCFTailGenerator(T vc, T xc, T c) noexcept { - a0_ = -(2*vc-c)*xc; - as_ = -2*c*xc; - b0_ = 2*(vc+xc); - bs_ = c; - k_ = 0; - } - - XSF_HOST_DEVICE std::pair operator()() noexcept { - using std::fma; - ++k_; - return {fma(static_cast(k_), as_, a0_), - fma(static_cast(k_), bs_, b0_)}; - } - -private: - T a0_, as_; // a[k] == a0 + as*k, k >= 1 - T b0_, bs_; // b[k] == b0 + bs*k, k >= 1 - std::uint64_t k_; // current index -}; - -// Computes f(v, x) using Perron's continued fraction. -// -// T specifies the working type. This allows the function to perform -// calculations in a higher precision, such as double-double, even if -// the return type is hardcoded to be double. -template -XSF_HOST_DEVICE inline std::pair -_iv_ratio_cf(double v, double x, bool complement) { - - int e; - std::frexp(std::fmax(v, x), &e); - T c = T(std::ldexp(1, 2-e)); // rescaling multiplier - T vc = v * c; - T xc = x * c; - - IvRatioCFTailGenerator cf(vc, xc, c); - auto [fc, terms] = detail::series_eval_kahan( - detail::continued_fraction_series(cf), - T(std::numeric_limits::epsilon()), - 1000, - 2*vc); - - T ret = (complement ? fc : xc) / (xc + fc); - return {static_cast(ret), terms}; -} - -XSF_HOST_DEVICE inline double iv_ratio(double v, double x) { - - if (std::isnan(v) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (v < 0.5 || x < 0) { - set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(v) && std::isinf(x)) { - // There is not a unique limit as both v and x tends to infinity. - set_error("iv_ratio", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - return x; // keep sign of x because iv_ratio is an odd function - } - if (std::isinf(v)) { - return 0.0; - } - if (std::isinf(x)) { - return 1.0; - } - - auto [ret, terms] = _iv_ratio_cf(v, x, false); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; -} - -XSF_HOST_DEVICE inline float iv_ratio(float v, float x) { - return iv_ratio(static_cast(v), static_cast(x)); -} - -XSF_HOST_DEVICE inline double iv_ratio_c(double v, double x) { - - if (std::isnan(v) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (v < 0.5 || x < 0) { - set_error("iv_ratio_c", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(v) && std::isinf(x)) { - // There is not a unique limit as both v and x tends to infinity. - set_error("iv_ratio_c", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0.0) { - return 1.0; - } - if (std::isinf(v)) { - return 1.0; - } - if (std::isinf(x)) { - return 0.0; - } - - if (v >= 1) { - // Numerical experiments show that evaluating the Perron c.f. - // in double precision is sufficiently accurate if v >= 1. - auto [ret, terms] = _iv_ratio_cf(v, x, true); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio_c", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; - } else if (v > 0.5) { - // double-double arithmetic is needed for 0.5 < v < 1 to - // achieve relative error on the scale of machine precision. - using cephes::detail::double_double; - auto [ret, terms] = _iv_ratio_cf(v, x, true); - if (terms == 0) { // failed to converge; should not happen - set_error("iv_ratio_c", SF_ERROR_NO_RESULT, NULL); - return std::numeric_limits::quiet_NaN(); - } - return ret; - } else { - // The previous branch (v > 0.5) also works for v == 0.5, but - // the closed-form formula "1 - tanh(x)" is more efficient. - double t = std::exp(-2*x); - return (2 * t) / (1 + t); - } -} - -XSF_HOST_DEVICE inline float iv_ratio_c(float v, float x) { - return iv_ratio_c(static_cast(v), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/kelvin.h b/scipy/special/xsf/kelvin.h deleted file mode 100644 index 7da98d0f3624..000000000000 --- a/scipy/special/xsf/kelvin.h +++ /dev/null @@ -1,424 +0,0 @@ -#pragma once - -#include "specfun.h" - -namespace xsf { - -namespace detail { - - template - void klvna(T x, T *ber, T *bei, T *ger, T *gei, T *der, T *dei, T *her, T *hei) { - - // ====================================================== - // Purpose: Compute Kelvin functions ber x, bei x, ker x - // and kei x, and their derivatives ( x > 0 ) - // Input : x --- Argument of Kelvin functions - // Output: BER --- ber x - // BEI --- bei x - // GER --- ker x - // GEI --- kei x - // DER --- ber'x - // DEI --- bei'x - // HER --- ker'x - // HEI --- kei'x - // ================================================ - - int k, km, m; - T gs, r, x2, x4, pp1, pn1, qp1, qn1, r1, pp0, pn0, qp0, qn0, r0, fac, xt, cs, ss, xd, xe1, xe2, xc1, xc2, cp0, - cn0, sp0, sn0, rc, rs; - const T pi = 3.141592653589793; - const T el = 0.5772156649015329; - const T eps = 1.0e-15; - - if (x == 0.0) { - *ber = 1.0; - *bei = 0.0; - *ger = 1.0e+300; - *gei = -0.25 * pi; - *der = 0.0; - *dei = 0.0; - *her = -1.0e+300; - *hei = 0.0; - return; - } - - x2 = 0.25 * x * x; - x4 = x2 * x2; - - if (fabs(x) < 10.0) { - *ber = 1.0; - r = 1.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m - 1.0), 2) * x4; - *ber += r; - if (fabs(r) < fabs(*ber) * eps) { - break; - } - } - - *bei = x2; - r = x2; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m + 1.0), 2) * x4; - *bei += r; - if (fabs(r) < fabs(*bei) * eps) { - break; - } - } - - *ger = -(log(x / 2.0) + el) * (*ber) + 0.25 * pi * (*bei); - - r = 1.0; - gs = 0.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m - 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m - 1.0) + 1.0 / (2.0 * m); - *ger += r * gs; - if (fabs(r * gs) < fabs(*ger) * eps) { - break; - } - } - - *gei = x2 - (log(x / 2.0) + el) * (*bei) - 0.25 * pi * (*ber); - - r = x2; - gs = 1.0; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / pow((2.0 * m + 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m) + 1.0 / (2.0 * m + 1.0); - *gei += r * gs; - if (fabs(r * gs) < fabs(*gei) * eps) { - break; - } - } - - *der = -0.25 * x * x2; - r = *der; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / m / (m + 1.0) / pow((2.0 * m + 1.0), 2) * x4; - *der += r; - if (fabs(r) < fabs(*der) * eps) { - break; - } - } - - *dei = 0.5 * x; - r = *dei; - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / (2.0 * m - 1.0) / (2.0 * m + 1.0) * x4; - *dei += r; - if (fabs(r) < fabs(*dei) * eps) { - break; - } - } - - r = -0.25 * x * x2; - gs = 1.5; - *her = 1.5 * r - (*ber) / x - (log(x / 2.0) + el) * (*der) + 0.25 * pi * (*dei); - for (m = 1; m <= 60; m++) { - r = -0.25 * r / m / (m + 1.0) / pow((2.0 * m + 1.0), 2) * x4; - gs = gs + 1.0 / (2.0 * m + 1.0) + 1.0 / (2 * m + 2.0); - *her += r * gs; - if (fabs(r * gs) < fabs(*her) * eps) { - break; - } - } - - r = 0.5 * x; - gs = 1.0; - *hei = 0.5 * x - (*bei) / x - (log(x / 2.0) + el) * (*dei) - 0.25 * pi * (*der); - for (m = 1; m <= 60; m++) { - r = -0.25 * r / (m * m) / (2 * m - 1.0) / (2 * m + 1.0) * x4; - gs = gs + 1.0 / (2.0 * m) + 1.0 / (2 * m + 1.0); - *hei += r * gs; - if (fabs(r * gs) < fabs(*hei) * eps) { - return; - } - } - } else { - pp0 = 1.0; - pn0 = 1.0; - qp0 = 0.0; - qn0 = 0.0; - r0 = 1.0; - km = 18; - if (fabs(x) >= 40.0) - km = 10; - fac = 1.0; - for (k = 1; k <= km; k++) { - fac = -fac; - xt = 0.25 * k * pi - trunc(0.125 * k) * 2.0 * pi; - cs = cos(xt); - ss = sin(xt); - r0 = 0.125 * r0 * pow((2.0 * k - 1.0), 2) / k / x; - rc = r0 * cs; - rs = r0 * ss; - pp0 += rc; - pn0 += fac * rc; - qp0 += rs; - qn0 += fac * rs; - } - - xd = x / sqrt(2.0); - xe1 = exp(xd); - xe2 = exp(-xd); - xc1 = 1.0 / sqrt(2.0 * pi * x); - xc2 = sqrt(0.5 * pi / x); - cp0 = cos(xd + 0.125 * pi); - cn0 = cos(xd - 0.125 * pi); - sp0 = sin(xd + 0.125 * pi); - sn0 = sin(xd - 0.125 * pi); - - *ger = xc2 * xe2 * (pn0 * cp0 - qn0 * sp0); - *gei = xc2 * xe2 * (-pn0 * sp0 - qn0 * cp0); - *ber = xc1 * xe1 * (pp0 * cn0 + qp0 * sn0) - (*gei) / pi; - *bei = xc1 * xe1 * (pp0 * sn0 - qp0 * cn0) + (*ger) / pi; - - pp1 = 1.0; - pn1 = 1.0; - qp1 = 0.0; - qn1 = 0.0; - r1 = 1.0; - fac = 1.0; - for (int k = 1; k <= km; k++) { - fac = -fac; - xt = 0.25 * k * pi - (int) (0.125 * k) * 2.0 * pi; - cs = cos(xt); - ss = sin(xt); - r1 = 0.125 * r1 * (4.0 - pow(2.0 * k - 1.0, 2)) / (k * x); - rc = r1 * cs; - rs = r1 * ss; - pp1 += fac * rc; - pn1 += rc; - qp1 += fac * rs; - qn1 += rs; - } - *her = xc2 * xe2 * (-pn1 * cn0 + qn1 * sn0); - *hei = xc2 * xe2 * (pn1 * sn0 + qn1 * cn0); - *der = xc1 * xe1 * (pp1 * cp0 + qp1 * sp0) - (*hei) / pi; - *dei = xc1 * xe1 * (pp1 * sp0 - qp1 * cp0) + (*her) / pi; - } - return; - } - -} // namespace detail - -template -T ber(T x) { - std::complex Be; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - x = -x; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - SPECFUN_ZCONVINF("ber", Be); - return Be.real(); -} - -template -T bei(T x) { - std::complex Be; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - x = -x; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - SPECFUN_ZCONVINF("bei", Be); - return Be.imag(); -} - -template -T ker(T x) { - std::complex Ke; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Ke.real(ger); - Ke.imag(gei); - SPECFUN_ZCONVINF("ker", Ke); - return Ke.real(); -} - -template -T kei(T x) { - std::complex Ke; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Ke.real(ger); - Ke.imag(gei); - SPECFUN_ZCONVINF("kei", Ke); - return Ke.imag(); -} - -template -T berp(T x) { - std::complex Bep; - T ber, bei, ger, gei, der, dei, her, hei; - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Bep.real(der); - Bep.imag(dei); - SPECFUN_ZCONVINF("berp", Bep); - if (flag) { - return -Bep.real(); - } - return Bep.real(); -} - -template -T beip(T x) { - std::complex Bep; - T ber, bei, ger, gei, der, dei, her, hei; - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Bep.real(der); - Bep.imag(dei); - SPECFUN_ZCONVINF("beip", Bep); - if (flag) { - return -Bep.imag(); - } - return Bep.imag(); -} - -template -T kerp(T x) { - std::complex Kep; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Kep.real(her); - Kep.imag(hei); - SPECFUN_ZCONVINF("kerp", Kep); - return Kep.real(); -} - -template -T keip(T x) { - std::complex Kep; - T ber, bei, ger, gei, der, dei, her, hei; - - if (x < 0) { - return std::numeric_limits::quiet_NaN(); - } - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Kep.real(her); - Kep.imag(hei); - SPECFUN_ZCONVINF("keip", Kep); - return Kep.imag(); -} - -template -void kelvin(T x, std::complex &Be, std::complex &Ke, std::complex &Bep, std::complex &Kep) { - int flag = 0; - T ber, bei, ger, gei, der, dei, her, hei; - if (x < 0) { - x = -x; - flag = 1; - } - - detail::klvna(x, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - Be.real(ber); - Be.imag(bei); - Ke.real(ger); - Ke.imag(gei); - Bep.real(der); - Bep.imag(dei); - Kep.real(her); - Kep.imag(hei); - - SPECFUN_ZCONVINF("klvna", Be); - SPECFUN_ZCONVINF("klvna", Ke); - SPECFUN_ZCONVINF("klvna", Bep); - SPECFUN_ZCONVINF("klvna", Kep); - if (flag) { - Bep.real(-Bep.real()); - Bep.imag(-Bep.imag()); - Ke.real(std::numeric_limits::quiet_NaN()); - Ke.imag(std::numeric_limits::quiet_NaN()); - Kep.real(std::numeric_limits::quiet_NaN()); - Kep.imag(std::numeric_limits::quiet_NaN()); - } -} - -inline void klvnzo(int nt, int kd, double *zo) { - - // ==================================================== - // Purpose: Compute the zeros of Kelvin functions - // Input : NT --- Total number of zeros - // KD --- Function code - // KD=1 to 8 for ber x, bei x, ker x, kei x, - // ber'x, bei'x, ker'x and kei'x, - // respectively. - // Output: ZO(M) --- the M-th zero of Kelvin function - // for code KD - // Routine called: - // KLVNA for computing Kelvin functions and - // their derivatives - // ==================================================== - - double ber, bei, ger, gei, der, dei, her, hei; - double rt0[9] = {0.0, 2.84891, 5.02622, 1.71854, 3.91467, 6.03871, 3.77268, 2.66584, 4.93181}; - double rt = rt0[kd]; - - for (int m = 1; m <= nt; m++) { - while (1) { - detail::klvna(rt, &ber, &bei, &ger, &gei, &der, &dei, &her, &hei); - if (kd == 1) { - rt -= ber / der; - } else if (kd == 2) { - rt -= bei / dei; - } else if (kd == 3) { - rt -= ger / her; - } else if (kd == 4) { - rt -= gei / hei; - } else if (kd == 5) { - rt -= der / (-bei - der / rt); - } else if (kd == 6) { - rt -= dei / (ber - dei / rt); - } else if (kd == 7) { - rt -= her / (-gei - her / rt); - } else { - rt -= hei / (ger - hei / rt); - } - - if (fabs(rt - rt0[kd]) <= 5e-10) { - break; - } else { - rt0[kd] = rt; - } - } - zo[m - 1] = rt; - rt += 4.44; - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/lambertw.h b/scipy/special/xsf/lambertw.h deleted file mode 100644 index 9eb1882eaec4..000000000000 --- a/scipy/special/xsf/lambertw.h +++ /dev/null @@ -1,150 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * Original header with Copyright information appears below. - */ - -/* Implementation of the Lambert W function [1]. Based on MPMath - * Implementation [2], and documentation [3]. - * - * Copyright: Yosef Meller, 2009 - * Author email: mellerf@netvision.net.il - * - * Distributed under the same license as SciPy - * - * - * References: - * [1] On the Lambert W function, Adv. Comp. Math. 5 (1996) 329-359, - * available online: https://web.archive.org/web/20230123211413/https://cs.uwaterloo.ca/research/tr/1993/03/W.pdf - * [2] mpmath source code, - https://github.com/mpmath/mpmath/blob/c5939823669e1bcce151d89261b802fe0d8978b4/mpmath/functions/functions.py#L435-L461 - * [3] - https://web.archive.org/web/20230504171447/https://mpmath.org/doc/current/functions/powers.html#lambert-w-function - * - - * TODO: use a series expansion when extremely close to the branch point - * at `-1/e` and make sure that the proper branch is chosen there. - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "evalpoly.h" - -namespace xsf { -constexpr double EXPN1 = 0.36787944117144232159553; // exp(-1) -constexpr double OMEGA = 0.56714329040978387299997; // W(1, 0) - -namespace detail { - XSF_HOST_DEVICE inline std::complex lambertw_branchpt(std::complex z) { - // Series for W(z, 0) around the branch point; see 4.22 in [1]. - double coeffs[] = {-1.0 / 3.0, 1.0, -1.0}; - std::complex p = std::sqrt(2.0 * (M_E * z + 1.0)); - - return cevalpoly(coeffs, 2, p); - } - - XSF_HOST_DEVICE inline std::complex lambertw_pade0(std::complex z) { - // (3, 2) Pade approximation for W(z, 0) around 0. - double num[] = {12.85106382978723404255, 12.34042553191489361902, 1.0}; - double denom[] = {32.53191489361702127660, 14.34042553191489361702, 1.0}; - - /* This only gets evaluated close to 0, so we don't need a more - * careful algorithm that avoids overflow in the numerator for - * large z. */ - return z * cevalpoly(num, 2, z) / cevalpoly(denom, 2, z); - } - - XSF_HOST_DEVICE inline std::complex lambertw_asy(std::complex z, long k) { - /* Compute the W function using the first two terms of the - * asymptotic series. See 4.20 in [1]. - */ - std::complex w = std::log(z) + 2.0 * M_PI * k * std::complex(0, 1); - return w - std::log(w); - } - -} // namespace detail - -XSF_HOST_DEVICE inline std::complex lambertw(std::complex z, long k, double tol) { - double absz; - std::complex w; - std::complex ew, wew, wewz, wn; - - if (std::isnan(z.real()) || std::isnan(z.imag())) { - return z; - } - if (z.real() == std::numeric_limits::infinity()) { - return z + 2.0 * M_PI * k * std::complex(0, 1); - } - if (z.real() == -std::numeric_limits::infinity()) { - return -z + (2.0 * M_PI * k + M_PI) * std::complex(0, 1); - } - if (z == 0.0) { - if (k == 0) { - return z; - } - set_error("lambertw", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } - if (z == 1.0 && k == 0) { - // Split out this case because the asymptotic series blows up - return OMEGA; - } - - absz = std::abs(z); - // Get an initial guess for Halley's method - if (k == 0) { - if (std::abs(z + EXPN1) < 0.3) { - w = detail::lambertw_branchpt(z); - } else if (-1.0 < z.real() && z.real() < 1.5 && std::abs(z.imag()) < 1.0 && - -2.5 * std::abs(z.imag()) - 0.2 < z.real()) { - /* Empirically determined decision boundary where the Pade - * approximation is more accurate. */ - w = detail::lambertw_pade0(z); - } else { - w = detail::lambertw_asy(z, k); - } - } else if (k == -1) { - if (absz <= EXPN1 && z.imag() == 0.0 && z.real() < 0.0) { - w = std::log(-z.real()); - } else { - w = detail::lambertw_asy(z, k); - } - } else { - w = detail::lambertw_asy(z, k); - } - - // Halley's method; see 5.9 in [1] - if (w.real() >= 0) { - // Rearrange the formula to avoid overflow in exp - for (int i = 0; i < 100; i++) { - ew = std::exp(-w); - wewz = w - z * ew; - wn = w - wewz / (w + 1.0 - (w + 2.0) * wewz / (2.0 * w + 2.0)); - if (std::abs(wn - w) <= tol * std::abs(wn)) { - return wn; - } - w = wn; - } - } else { - for (int i = 0; i < 100; i++) { - ew = std::exp(w); - wew = w * ew; - wewz = wew - z; - wn = w - wewz / (wew + ew - (w + 2.0) * wewz / (2.0 * w + 2.0)); - if (std::abs(wn - w) <= tol * std::abs(wn)) { - return wn; - } - w = wn; - } - } - - set_error("lambertw", SF_ERROR_SLOW, "iteration failed to converge: %g + %gj", z.real(), z.imag()); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; -} - -XSF_HOST_DEVICE inline std::complex lambertw(std::complex z, long k, float tol) { - return static_cast>( - lambertw(static_cast>(z), k, static_cast(tol))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/legendre.h b/scipy/special/xsf/legendre.h deleted file mode 100644 index 6565ed52c368..000000000000 --- a/scipy/special/xsf/legendre.h +++ /dev/null @@ -1,1064 +0,0 @@ -#pragma once - -#include "dual.h" -#include "error.h" -#include "recur.h" - -namespace xsf { - -template -struct legendre_p_initializer_n { - T z; - - void operator()(T (&res)[2]) const { - res[0] = T(1); - res[1] = z; - } -}; - -template -struct legendre_p_recurrence_n { - T z; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = -value_type(n - 1) / value_type(n); - value_type fac1 = value_type(2 * n - 1) / value_type(n); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -/** - * Compute the Legendre polynomial of degree n. - * - * @param n degree of the polynomial - * @param z argument of the polynomial, either real or complex - * @param f a function to be called as callback(j, res) for 0 <= j <= n - * @param res value and derivatives of the polynomial - */ -template -void legendre_p_for_each_n(int n, T z, T (&res)[2], Func f) { - legendre_p_initializer_n init_n{z}; - init_n(res); - - legendre_p_recurrence_n re_n{z}; - forward_recur(0, n + 1, re_n, res, f); -} - -/* - * Compute the Legendre polynomial of degree n. - * - * @param n degree of the polynomial - * @param z argument of the polynomial, either real or complex - * - * @return value of the polynomial - */ -template -T legendre_p(int n, T z) { - T res_n[2]; - legendre_p_for_each_n(n, z, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -/** - * Compute all Legendre polynomials of degree j, where 0 <= j <= n. - * - * @param z argument of the polynomials, either real or complex - * @param res a view into a multidimensional array with element type T and size n + 1 to store the value of each - * polynomial - */ -template -void legendre_p_all(T z, OutputVec res) { - int n = res.extent(0) - 1; - - T res_n[2]; - legendre_p_for_each_n(n, z, res_n, [&res](int n, const T(&res_n)[2]) { res(n) = res_n[1]; }); -} - -struct assoc_legendre_unnorm_policy {}; - -struct assoc_legendre_norm_policy {}; - -constexpr assoc_legendre_unnorm_policy assoc_legendre_unnorm; - -constexpr assoc_legendre_norm_policy assoc_legendre_norm; - -template -struct assoc_legendre_p_initializer_m_abs_m; - -template -struct assoc_legendre_p_initializer_m_abs_m { - bool m_signbit; - T z; - int branch_cut; - T w; - - assoc_legendre_p_initializer_m_abs_m(bool m_signbit, T z, int branch_cut) - : m_signbit(m_signbit), z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - w = sqrt(z - T(1)) * sqrt(z + T(1)); // form for this branch cut - } else { - w = -sqrt(T(1) - z * z); // form for this branch cut - if (m_signbit) { - w = -w; - } - } - } - - void operator()(T (&res)[2]) const { - res[0] = T(1); - res[1] = w; - - if (m_signbit) { - res[1] /= 2; - } - } -}; - -template -struct assoc_legendre_p_initializer_m_abs_m { - bool m_signbit; - T z; - int branch_cut; - T w; - - assoc_legendre_p_initializer_m_abs_m(bool m_signbit, T z, int branch_cut) - : m_signbit(m_signbit), z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - - w = sqrt(z - T(1)) * sqrt(z + T(1)); // form for this branch cut - } else { - w = -sqrt(T(1) - z * z); // form for this branch cut - if (m_signbit) { - w = -w; - } - } - } - - void operator()(T (&res)[2]) const { - res[0] = T(1) / sqrt(T(2)); - res[1] = sqrt(T(3)) * w / T(2); - } -}; - -template -struct assoc_legendre_p_recurrence_m_abs_m; - -template -struct assoc_legendre_p_recurrence_m_abs_m { - T z; - int branch_cut; - T branch_cut_sign; - - assoc_legendre_p_recurrence_m_abs_m(T z, int branch_cut) : z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - branch_cut_sign = T(-1); - } else { - branch_cut_sign = T(1); - } - } - - // other square roots can be avoided if each iteration increments by 2 - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac; - if (m < 0) { - fac = branch_cut_sign / T((2 * m_abs) * (2 * m_abs - 2)); - } else { - fac = branch_cut_sign * T((2 * m_abs - 1) * (2 * m_abs - 3)); - } - - res[0] = fac * (T(1) - z * z); - res[1] = T(0); - } -}; - -template -struct assoc_legendre_p_recurrence_m_abs_m { - T z; - int branch_cut; - T branch_cut_sign; - - assoc_legendre_p_recurrence_m_abs_m(T z, int branch_cut) : z(z), branch_cut(branch_cut) { - if (branch_cut == 3) { - branch_cut_sign = T(-1); - } else { - branch_cut_sign = T(1); - } - } - - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac = branch_cut_sign * sqrt(T((2 * m_abs + 1) * (2 * m_abs - 1)) / T(4 * m_abs * (m_abs - 1))); - - res[0] = fac * (T(1) - z * z); - res[1] = T(0); - } -}; - -template -void assoc_legendre_p_for_each_m_abs_m(NormPolicy norm, int m, T z, int branch_cut, T (&res)[2], Func f) { - bool m_signbit; - if (m < 0) { - m_signbit = true; - } else { - m_signbit = false; - } - - assoc_legendre_p_initializer_m_abs_m init_m_abs_m{m_signbit, z, branch_cut}; - init_m_abs_m(res); - - assoc_legendre_p_recurrence_m_abs_m re_m_abs_m{z, branch_cut}; - if (m >= 0) { - forward_recur(0, m + 1, re_m_abs_m, res, f); - } else { - backward_recur(0, m - 1, re_m_abs_m, res, f); - } -} - -/** - * Compute the associated Legendre polynomial of degree n and order n. - * - * We need to be careful with complex arithmetic, in particular the square roots - * should not be modified. This is because the sign bit of a real or imaginary part, - * even if it is equal to zero, can affect the branch cut. - */ - -template -struct assoc_legendre_p_initializer_n; - -template -struct assoc_legendre_p_initializer_n { - int m; - T z; - int type; - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - int m_abs = abs(m); - T fac = T(2 * (m_abs + 1) - 1) / T(m_abs + 1 - m); - - res[0] = res_m_abs_m; - res[1] = fac * z * res_m_abs_m; - } -}; - -template -struct assoc_legendre_p_initializer_n { - int m; - T z; - int type; - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - T fac = sqrt(T(2 * abs(m) + 3)); - - res[0] = res_m_abs_m; - res[1] = fac * z * res_m_abs_m; - } -}; - -template -struct assoc_legendre_p_recurrence_n; - -template -struct assoc_legendre_p_recurrence_n { - int m; - T z; - int type; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = -value_type(n + m - 1) / value_type(n - m); - value_type fac1 = value_type(2 * n - 1) / value_type(n - m); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -template -struct assoc_legendre_p_recurrence_n { - int m; - T z; - int type; - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = - -sqrt(value_type((2 * n + 1) * ((n - 1) * (n - 1) - m * m)) / value_type((2 * n - 3) * (n * n - m * m))); - value_type fac1 = - sqrt(value_type((2 * n + 1) * (4 * (n - 1) * (n - 1) - 1)) / value_type((2 * n - 3) * (n * n - m * m))); - - res[0] = fac0; - res[1] = fac1 * z; - } -}; - -template -void assoc_legendre_p_pm1(NormPolicy norm, int n, int m, T z, int branch_cut, T &res) { - if (m == 0) { - res = T(1); - } else { - res = T(0); - } -} - -template -void assoc_legendre_p_pm1(NormPolicy norm, int n, int m, dual z, int branch_cut, dual &res) { - if (m == 0) { - res[0] = T(1); - } else { - res[0] = T(0); - } - - T branch_cut_sign; - if (branch_cut == 3) { - branch_cut_sign = -1; - } else { - branch_cut_sign = 1; - } - - if (Order >= 1) { - if (abs(m) > n) { - res[1] = 0; - } else if (m == 0) { - res[1] = T(n) * T(n + 1) * std::pow(z[0], T(n + 1)) / T(2); - } else if (m == 1) { - res[1] = std::pow(z[0], T(n)) * std::numeric_limits>::infinity(); - } else if (m == 2) { - res[1] = -branch_cut_sign * T(n + 2) * T(n + 1) * T(n) * T(n - 1) * std::pow(z[0], T(n + 1)) / T(4); - } else if (m == -2) { - res[1] = -branch_cut_sign * std::pow(z[0], T(n + 1)) / T(4); - } else if (m == -1) { - res[1] = -std::pow(z[0], T(n)) * std::numeric_limits>::infinity(); - } else { - res[1] = 0; - } - - if (Order >= 2) { - if (abs(m) > n) { - res[2] = 0; - } else if (m == 0) { - res[2] = T(n + 2) * T(n + 1) * T(n) * T(n - 1) / T(8); - } else if (m == 1) { - res[2] = std::numeric_limits>::infinity(); - } else if (m == 2) { - res[2] = -T((n + 1) * n - 3) * T(n + 2) * T(n + 1) * T(n) * T(n - 1) / T(12); - } else if (m == 3) { - res[2] = std::numeric_limits>::infinity(); - } else if (m == 4) { - res[2] = T(n + 4) * T(n + 3) * T(n + 2) * T(n + 1) * T(n) * T(n - 1) * T(n - 2) * T(n - 3) / T(48); - } else if (m == -4) { - res[2] = 0; - } else if (m == -3) { - res[2] = -std::numeric_limits>::infinity(); - } else if (m == -2) { - res[2] = -T(1) / T(4); - } else if (m == -1) { - res[2] = -std::numeric_limits>::infinity(); - } else { - res[2] = 0; - } - } - } -} - -/** - * Compute the associated Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * @param callback a function to be called as callback(j, m, type, z, p, p_prev, args...) for 0 <= j <= n - * @param args arguments to forward to the callback - * - * @return value of the polynomial - */ -template -void assoc_legendre_p_for_each_n( - NormPolicy norm, int n, int m, T z, int branch_cut, const T &res_m_abs_m, T (&res)[2], Func f -) { - res[0] = 0; - res[1] = 0; - - int m_abs = abs(m); - if (m_abs > n) { - for (int j = 0; j <= n; ++j) { - f(j, res); - } - } else { - for (int j = 0; j < m_abs; ++j) { - f(j, res); - } - - if (abs(real(z)) == 1 && imag(z) == 0) { - for (int j = m_abs; j <= n; ++j) { - forward_recur_shift_left(res); - assoc_legendre_p_pm1(norm, j, m, z, branch_cut, res[1]); - - f(j, res); - } - } else { - assoc_legendre_p_initializer_n init_n{m, z, branch_cut}; - init_n(res_m_abs_m, res); - - assoc_legendre_p_recurrence_n re_n{m, z, branch_cut}; - forward_recur(m_abs, n + 1, re_n, res, f); - } - } -} - -template -void assoc_legendre_p_for_each_n(NormPolicy norm, int n, int m, T z, int branch_cut, T (&res)[2], Func f) { - assoc_legendre_p_for_each_m_abs_m(norm, m, z, branch_cut, res, [](int m, const T(&res)[2]) {}); - - T res_m_abs_m = res[1]; - assoc_legendre_p_for_each_n(norm, n, m, z, branch_cut, res_m_abs_m, res, f); -} - -template -void assoc_legendre_p_for_each_n_m(NormPolicy norm, int n, int m, T z, int branch_cut, T (&res)[2], Func f) { - T res_m_abs_m[2]; - assoc_legendre_p_for_each_m_abs_m( - norm, m, z, branch_cut, res_m_abs_m, - [norm, n, z, branch_cut, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - assoc_legendre_p_for_each_n( - norm, n, m, z, branch_cut, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { f(n, m, res_n); } - ); - } - ); - assoc_legendre_p_for_each_m_abs_m( - norm, -m, z, branch_cut, res_m_abs_m, - [norm, n, z, branch_cut, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - assoc_legendre_p_for_each_n( - norm, n, m, z, branch_cut, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { f(n, m, res_n); } - ); - } - ); -} - -/** - * Compute the associated Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * - * @return value of the polynomial - */ -template -T assoc_legendre_p(NormPolicy norm, int n, int m, T z, int branch_cut) { - T res_n[2]; - assoc_legendre_p_for_each_n(norm, n, m, z, branch_cut, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -/** - * Compute all associated Legendre polynomials of degree j and order i, where 0 <= j <= n and -m <= i <= m. - * - * @param type specifies the branch cut of the polynomial, either 1, 2, or 3 - * @param z argument of the polynomial, either real or complex - * @param res a view into the output with element type T and extents (2 * m + 1, n + 1) - * - * @return value of the polynomial - */ -template -void assoc_legendre_p_all(NormPolicy norm, T z, int branch_cut, OutputMat res) { - int n = res.extent(0) - 1; - int m = (res.extent(1) - 1) / 2; - - T p[2]; - assoc_legendre_p_for_each_n_m(norm, n, m, z, branch_cut, p, [&res](int n, int m, const T(&res_n_m)[2]) { - if (m >= 0) { - res(n, m) = res_n_m[1]; - } else { - res(n, m + res.extent(1)) = res_n_m[1]; - } - }); -} - -template -struct sph_legendre_p_initializer_m_abs_m { - bool m_signbit; - T theta; - T theta_sin; - - sph_legendre_p_initializer_m_abs_m(bool m_signbit, T theta) - : m_signbit(m_signbit), theta(theta), theta_sin(sin(theta)) {} - - void operator()(T (&res)[2]) const { - T fac0 = T(1) / (T(2) * sqrt(T(M_PI))); - T fac1 = -sqrt(T(3)) / (T(2) * sqrt(T(2) * T(M_PI))); - if (m_signbit) { - fac1 = -fac1; - } - - res[0] = fac0; - res[1] = fac1 * abs(theta_sin); - } -}; - -template -struct sph_legendre_p_recurrence_m_abs_m { - T theta; - T theta_sin; - - sph_legendre_p_recurrence_m_abs_m(T theta) : theta(theta), theta_sin(sin(theta)) {} - - void operator()(int m, T (&res)[2]) const { - int m_abs = abs(m); - - T fac = sqrt(T((2 * m_abs + 1) * (2 * m_abs - 1)) / T(4 * m_abs * (m_abs - 1))); - - res[0] = fac * theta_sin * theta_sin; - res[1] = 0; - } -}; - -template -void sph_legendre_p_for_each_m_abs_m(int m, T theta, T (&res)[2], Func f) { - bool m_signbit; - if (m < 0) { - m_signbit = true; - } else { - m_signbit = false; - } - - sph_legendre_p_initializer_m_abs_m init_m_abs_m{m_signbit, theta}; - init_m_abs_m(res); - - sph_legendre_p_recurrence_m_abs_m re_m_abs_m{theta}; - if (m >= 0) { - forward_recur(0, m + 1, re_m_abs_m, res, f); - } else { - backward_recur(0, m - 1, re_m_abs_m, res, f); - } -} - -template -struct sph_legendre_p_initializer_n { - int m; - T theta; - T theta_cos; - - sph_legendre_p_initializer_n(int m, T theta) : m(m), theta(theta), theta_cos(cos(theta)) {} - - void operator()(const T &res_m_abs_m, T (&res)[2]) const { - T fac = sqrt(T(2 * abs(m) + 3)); - - res[0] = res_m_abs_m; - res[1] = fac * theta_cos * res_m_abs_m; - } -}; - -template -struct sph_legendre_p_recurrence_n { - int m; - T theta; - T theta_cos; - - sph_legendre_p_recurrence_n(int m, T theta) : m(m), theta(theta), theta_cos(cos(theta)) {} - - void operator()(int n, T (&res)[2]) const { - using value_type = remove_dual_t; - value_type fac0 = - -sqrt(value_type((2 * n + 1) * ((n - 1) * (n - 1) - m * m)) / value_type((2 * n - 3) * (n * n - m * m))); - value_type fac1 = - sqrt(value_type((2 * n + 1) * (4 * (n - 1) * (n - 1) - 1)) / value_type((2 * n - 3) * (n * n - m * m))); - - res[0] = fac0; - res[1] = fac1 * theta_cos; - } -}; - -/** - * Compute the spherical Legendre polynomial of degree n and order m. - * - * @param n degree of the polynomial - * @param m order of the polynomial - * @param theta z = cos(theta) argument of the polynomial, either real or complex - * @param callback a function to be called as callback(j, m, type, z, p, p_prev, args...) for 0 <= j <= n - * @param args arguments to forward to the callback - * - * @return value of the polynomial - */ -template -void sph_legendre_p_for_each_n(int n, int m, T theta, const T &res_m_abs_m, T (&res)[2], Func f) { - res[0] = 0; - res[1] = 0; - - int m_abs = abs(m); - if (m_abs > n) { - for (int j = 0; j <= n; ++j) { - f(j, res); - } - } else { - for (int j = 0; j < m_abs; ++j) { - f(j, res); - } - - sph_legendre_p_initializer_n init_n{m, theta}; - init_n(res_m_abs_m, res); - - sph_legendre_p_recurrence_n re_n{m, theta}; - forward_recur(m_abs, n + 1, re_n, res, f); - } -} - -template -void sph_legendre_p_for_each_n(int n, int m, T theta, T (&res)[2], Func f) { - sph_legendre_p_for_each_m_abs_m(m, theta, res, [](int m, auto) {}); - - T res_m_abs_m = res[1]; - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m, res, f); -} - -template -void sph_legendre_p_for_each_n_m(int n, int m, T theta, T (&res)[2], Func f) { - T res_m_abs_m[2]; - sph_legendre_p_for_each_m_abs_m(m, theta, res_m_abs_m, [n, theta, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { - f(n, m, res_n); - }); - }); - sph_legendre_p_for_each_m_abs_m(-m, theta, res_m_abs_m, [n, theta, &res, f](int m, const T(&res_m_abs_m)[2]) { - res[0] = res_m_abs_m[1]; - - sph_legendre_p_for_each_n(n, m, theta, res_m_abs_m[1], res, [f, m](int n, const T(&res_n)[2]) { - f(n, m, res_n); - }); - }); -} - -template -T sph_legendre_p(int n, int m, T theta) { - T res_n[2]; - sph_legendre_p_for_each_n(n, m, theta, res_n, [](int n, const T(&res_n)[2]) {}); - - return res_n[1]; -} - -template -void sph_legendre_p_all(T theta, OutputMat res) { - int n_max = res.extent(0) - 1; - int m_max = (res.extent(1) - 1) / 2; - - T res_n_m[2]; - sph_legendre_p_for_each_n_m(n_max, m_max, theta, res_n_m, [m_max, &res](int n, int m, const T(&res_n_m)[2]) { - if (m >= 0) { - res(n, m) = res_n_m[1]; - } else { - res(n, m + 2 * m_max + 1) = res_n_m[1]; - } - }); -} - -// ==================================================== -// Purpose: Compute Legendre functions Qn(x) & Qn'(x) -// Input : x --- Argument of Qn(x) -// n --- Degree of Qn(x) ( n = 0,1,2,…) -// Output: QN(n) --- Qn(x) -// QD(n) --- Qn'(x) -// ==================================================== - -template -void lqn(T x, OutputVec1 qn, OutputVec2 qd) { - int n = qn.size() - 1; - - T x2, q0, q1, qf, qc1, qc2, qr, qf0, qf1, qf2; - const T eps = 1.0e-14; - - if (fabs(x) == 1.0) { - for (int k = 0; k <= n; k++) { - qn[k] = 1.0e300; - qd[k] = 1.0e300; - } - return; - } - - if (x <= 1.021) { - x2 = fabs((1.0 + x) / (1.0 - x)); - q0 = 0.5 * log(x2); - q1 = x * q0 - 1.0; - qn[0] = q0; - qn[1] = q1; - qd[0] = 1.0 / (1.0 - x * x); - qd[1] = qn[0] + x * qd[0]; - - for (int k = 2; k <= n; k++) { - qf = ((2.0 * k - 1.0) * x * q1 - (k - 1.0) * q0) / k; - qn[k] = qf; - qd[k] = (qn[k - 1] - x * qf) * k / (1.0 - x * x); - q0 = q1; - q1 = qf; - } - } else { - qc1 = 0.0; - qc2 = 1.0 / x; - - for (int j = 1; j <= n; j++) { - qc2 *= j / ((2.0 * j + 1.0) * x); - if (j == n - 1) - qc1 = qc2; - } - - for (int l = 0; l <= 1; l++) { - int nl = n + l; - qf = 1.0; - qr = 1.0; - - for (int k = 1; k <= 500; k++) { - qr = qr * (0.5 * nl + k - 1.0) * (0.5 * (nl - 1) + k) / ((nl + k - 0.5) * k * x * x); - qf += qr; - if (fabs(qr / qf) < eps) - break; - } - - if (l == 0) { - qn[n - 1] = qf * qc1; - } else { - qn[n] = qf * qc2; - } - } - - qf2 = qn[n]; - qf1 = qn[n - 1]; - - for (int k = n; k >= 2; k--) { - qf0 = ((2 * k - 1.0) * x * qf1 - k * qf2) / (k - 1.0); - qn[k - 2] = qf0; - qf2 = qf1; - qf1 = qf0; - } - - qd[0] = 1.0 / (1.0 - x * x); - - for (int k = 1; k <= n; k++) { - qd[k] = k * (qn[k - 1] - x * qn[k]) / (1.0 - x * x); - } - } -} - -// ================================================== -// Purpose: Compute the Legendre functions Qn(z) and -// their derivatives Qn'(z) for a complex -// argument -// Input : x --- Real part of z -// y --- Imaginary part of z -// n --- Degree of Qn(z), n = 0,1,2,... -// Output: CQN(n) --- Qn(z) -// CQD(n) --- Qn'(z) -// ================================================== - -template -void lqn(std::complex z, OutputVec1 cqn, OutputVec2 cqd) { - int n = cqn.size() - 1; - - std::complex cq0, cq1, cqf0 = 0.0, cqf1, cqf2; - - if (real(z) == 1) { - for (int k = 0; k <= n; ++k) { - cqn(k) = 1e300; - cqd(k) = 1e300; - } - return; - } - int ls = ((abs(z) > 1.0) ? -1 : 1); - - cq0 = std::log(static_cast(ls) * (static_cast(1) + z) / (static_cast(1) - z)) / static_cast(2); - cq1 = z * cq0 - static_cast(1); - - cqn(0) = cq0; - cqn(1) = cq1; - - if (abs(z) < 1.0001) { - cqf0 = cq0; - cqf1 = cq1; - for (int k = 2; k <= n; k++) { - cqf2 = (static_cast(2 * k - 1) * z * cqf1 - static_cast(k - 1) * cqf0) / static_cast(k); - cqn(k) = cqf2; - cqf0 = cqf1; - cqf1 = cqf2; - } - } else { - int km; - if (abs(z) > 1.1) { - km = 40 + n; - } else { - km = (int) ((40 + n) * floor(-1.0 - 1.8 * log(abs(z - static_cast(1))))); - } - - cqf2 = 0.0; - cqf1 = 1.0; - for (int k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 2) * cqf2) / static_cast(k + 1); - if (k <= n) { - cqn[k] = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - for (int k = 0; k <= n; ++k) { - cqn[k] *= cq0 / cqf0; - } - } - cqd(0) = (cqn(1) - z * cqn(0)) / (z * z - static_cast(1)); - - for (int k = 1; k <= n; ++k) { - cqd(k) = (static_cast(k) * z * cqn(k) - static_cast(k) * cqn(k - 1)) / (z * z - static_cast(1)); - } -} - -// ========================================================== -// Purpose: Compute the associated Legendre functions of the -// second kind, Qmn(x) and Qmn'(x) -// Input : x --- Argument of Qmn(x) -// m --- Order of Qmn(x) ( m = 0,1,2,… ) -// n --- Degree of Qmn(x) ( n = 0,1,2,… ) -// mm --- Physical dimension of QM and QD -// Output: QM(m,n) --- Qmn(x) -// QD(m,n) --- Qmn'(x) -// ========================================================== - -template -void lqmn(T x, OutputMat1 qm, OutputMat2 qd) { - int m = qm.extent(0) - 1; - int n = qm.extent(1) - 1; - - double q0, q1, q10, qf, qf0, qf1, qf2, xs, xq; - int i, j, k, km, ls; - - if (fabs(x) == 1.0) { - for (i = 0; i < (m + 1); i++) { - for (j = 0; j < (n + 1); j++) { - qm(i, j) = 1e300; - qd(i, j) = 1e300; - } - } - return; - } - ls = 1; - if (fabs(x) > 1.0) { - ls = -1; - } - xs = ls * (1.0 - x * x); - xq = sqrt(xs); - q0 = 0.5 * log(fabs((x + 1.0) / (x - 1.0))); - if (fabs(x) < 1.0001) { - qm(0, 0) = q0; - qm(0, 1) = x * q0 - 1.0; - qm(1, 0) = -1.0 / xq; - qm(1, 1) = -ls * xq * (q0 + x / (1. - x * x)); - for (i = 0; i <= 1; i++) { - for (j = 2; j <= n; j++) { - qm(i, j) = ((2.0 * j - 1.) * x * qm(i, j - 1) - (j + i - 1) * qm(i, j - 2)) / (j - i); - } - } - /* 15 */ - for (i = 2; i <= m; i++) { - for (j = 0; j <= n; j++) { - qm(i, j) = -2.0 * (i - 1.0) * x / xq * qm(i - 1, j) - ls * (j + i - 1.0) * (j - i + 2.0) * qm(i - 2, j); - } - } - } else { - if (fabs(x) > 1.1) { - km = 40 + m + n; - } else { - km = (40 + m + n) * ((int) (-1. - 1.8 * log(x - 1.))); - } - qf2 = 0.0; - qf1 = 1.0; - qf0 = 0.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 2.0) * qf2) / (k + 1.0); - if (k <= n) { - qm(0, k) = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - - for (k = 0; k <= n; k++) { - qm(0, k) *= q0 / qf0; - } - - qf2 = 0.0; - qf1 = 1.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 1.0) * qf2) / (k + 2.0); - if (k <= n) { - qm(1, k) = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - - q10 = -1.0 / xq; - for (k = 0; k <= n; k++) { - qm(1, k) *= q10 / qf0; - } - - for (j = 0; j <= n; j++) { - q0 = qm(0, j); - q1 = qm(1, j); - for (i = 0; i <= (m - 2); i++) { - qf = -2. * (i + 1.) * x / xq * q1 + (j - i) * (j + i + 1.) * q0; - qm(i + 2, j) = qf; - q0 = q1; - q1 = qf; - } - } - } - - qd(0, 0) = ls / xs; - for (j = 1; j <= n; j++) { - qd(0, j) = ls * j * (qm(0, j - 1) - x * qm(0, j)) / xs; - } - - for (i = 1; i <= m; i++) { - for (j = 0; j <= n; j++) { - qd(i, j) = ls * i * x / xs * qm(i, j) + (i + j) * (j - i + 1.) / xq * qm(i - 1, j); - } - } -} - -// ======================================================= -// Purpose: Compute the associated Legendre functions of -// the second kind, Qmn(z) and Qmn'(z), for a -// complex argument -// Input : x --- Real part of z -// y --- Imaginary part of z -// m --- Order of Qmn(z) ( m = 0,1,2,… ) -// n --- Degree of Qmn(z) ( n = 0,1,2,… ) -// mm --- Physical dimension of CQM and CQD -// Output: CQM(m,n) --- Qmn(z) -// CQD(m,n) --- Qmn'(z) -// ======================================================= - -template -void lqmn(std::complex z, OutputMat1 cqm, OutputMat2 cqd) { - int m = cqm.extent(0) - 1; - int n = cqm.extent(1) - 1; - - int i, j, k, km, ls; - std::complex cq0, cq1, cq10, cqf0 = 0, cqf, cqf1, cqf2, zq, zs; - - if ((abs(real(z)) == 1) && (imag(z) == 0)) { - for (i = 0; i < (m + 1); i++) { - for (j = 0; j < (n + 1); j++) { - cqm(i, j) = 1e300; - cqd(i, j) = 1e300; - } - } - - return; - } - - T xc = abs(z); - ls = 0; - if ((imag(z) == 0) || (xc < 1)) { - ls = 1; - } - if (xc > 1) { - ls = -1; - } - zs = static_cast(ls) * (static_cast(1) - z * z); - zq = sqrt(zs); - - cq0 = std::log(static_cast(ls) * (static_cast(1) + z) / (static_cast(1) - z)) / static_cast(2); - if (xc < 1.0001) { - cqm(0, 0) = cq0; - cqm(1, 0) = -static_cast(1) / zq; - cqm(0, 1) = z * cq0 - static_cast(1); - cqm(1, 1) = -zq * (cq0 + z / (static_cast(1) - z * z)); - - for (i = 0; i <= 1; i++) { - for (j = 2; j <= n; j++) { - cqm(i, j) = - (static_cast(2 * j - 1) * z * cqm(i, j - 1) - static_cast(j + i - 1) * cqm(i, j - 2)) / - static_cast(j - i); - } - } - - for (i = 2; i <= m; i++) { - for (j = 0; j <= n; j++) { - cqm(i, j) = -2 * static_cast(i - 1) * z / zq * cqm(i - 1, j) - - static_cast(ls * (j + i - 1) * (j - i + 2)) * cqm(i - 2, j); - } - } - } else { - if (xc > 1.1) { - km = 40 + m + n; - } else { - km = (40 + m + n) * ((int) (-1.0 - 1.8 * log(xc - 1.))); - } - cqf2 = 0.0; - cqf1 = 1.0; - for (k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 2) * cqf2) / static_cast(k + 1); - if (k <= n) { - cqm(0, k) = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - - for (k = 0; k <= n; k++) { - cqm(0, k) *= cq0 / cqf0; - } - - cqf2 = 0.0; - cqf1 = 1.0; - for (k = km; k >= 0; k--) { - cqf0 = (static_cast(2 * k + 3) * z * cqf1 - static_cast(k + 1) * cqf2) / static_cast(k + 2); - if (k <= n) { - cqm(1, k) = cqf0; - } - cqf2 = cqf1; - cqf1 = cqf0; - } - - cq10 = -static_cast(1) / zq; - for (k = 0; k <= n; k++) { - cqm(1, k) *= cq10 / cqf0; - } - - for (j = 0; j <= n; j++) { - cq0 = cqm(0, j); - cq1 = cqm(1, j); - for (i = 0; i <= (m - 2); i++) { - cqf = -static_cast(2 * (i + 1)) * z / zq * cq1 + static_cast((j - i) * (j + i + 1)) * cq0; - cqm(i + 2, j) = cqf; - cq0 = cq1; - cq1 = cqf; - } - } - - cqd(0, 0) = static_cast(ls) / zs; - for (j = 1; j <= n; j++) { - cqd(0, j) = ls * static_cast(j) * (cqm(0, j - 1) - z * cqm(0, j)) / zs; - } - - for (i = 1; i <= m; i++) { - for (j = 0; j <= n; j++) { - cqd(i, j) = static_cast(ls * i) * z / zs * cqm(i, j) + - static_cast((i + j) * (j - i + 1)) / zq * cqm(i - 1, j); - } - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/log.h b/scipy/special/xsf/log.h deleted file mode 100644 index 23681cb2ccb0..000000000000 --- a/scipy/special/xsf/log.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include "cephes/dd_real.h" -#include "trig.h" - -namespace xsf { - -inline double log1p(double x) { return cephes::log1p(x); } - -inline float log1p(float x) { return log1p(static_cast(x)); } - -inline std::complex clog1p_ddouble(double zr, double zi) { - double x, y; - - cephes::detail::double_double r(zr); - cephes::detail::double_double i(zi); - cephes::detail::double_double two(2.0); - - cephes::detail::double_double rsqr = r * r; - cephes::detail::double_double isqr = i * i; - cephes::detail::double_double rtwo = two * r; - cephes::detail::double_double absm1 = rsqr + isqr; - absm1 = absm1 + rtwo; - - x = 0.5 * log1p(static_cast(absm1)); - y = atan2(zi, zr + 1.0); - return std::complex{x, y}; -} - -// log(z + 1) = log(x + 1 + 1j*y) -// = log(sqrt((x+1)**2 + y**2)) + 1j*atan2(y, x+1) -// -// Using atan2(y, x+1) for the imaginary part is always okay. The real part -// needs to be calculated more carefully. For |z| large, the naive formula -// log(z + 1) can be used. When |z| is small, rewrite as -// -// log(sqrt((x+1)**2 + y**2)) = 0.5*log(x**2 + 2*x +1 + y**2) -// = 0.5 * log1p(x**2 + y**2 + 2*x) -// = 0.5 * log1p(hypot(x,y) * (hypot(x, y) + 2*x/hypot(x,y))) -// -// This expression suffers from cancellation when x < 0 and -// y = +/-sqrt(2*fabs(x)). To get around this cancellation problem, we use -// double-double precision when necessary. -inline std::complex log1p(std::complex z) { - double x, y, az, azi; - - if (!std::isfinite(std::real(z)) || !std::isfinite(std::imag(z))) { - z = z + 1.0; - return std::log(z); - } - - double zr = z.real(); - double zi = z.imag(); - - if (zi == 0.0 && zr >= -1.0) { - return log1p(zr); - } - - az = std::abs(z); - if (az < 0.707) { - azi = std::fabs(zi); - if (zr < 0 && std::abs(-zr - azi * azi / 2) / (-zr) < 0.5) { - return clog1p_ddouble(zr, zi); - } else { - x = 0.5 * log1p(az * (az + 2 * zr / az)); - y = atan2(zi, zr + 1.0); - return std::complex(x, y); - } - } - - z = z + 1.0; - return std::log(z); -} - -inline std::complex log1p(std::complex z) { - return static_cast>(log1p(static_cast>(z))); -} - -inline double log1pmx(double x) { return cephes::log1pmx(x); } - -inline float log1pmx(float x) { return log1pmx(static_cast(x)); } - -template -T xlogy(T x, T y) { - if (x == 0 && !std::isnan(y)) { - return 0; - } - - return x * std::log(y); -} - -template -std::complex xlogy(std::complex x, std::complex y) { - if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) { - return 0; - } - - return x * std::log(y); -} - -template -T xlog1py(T x, T y) { - if (x == 0 && !std::isnan(y)) { - return 0; - } - - return x * log1p(y); -} - -template -std::complex xlog1py(std::complex x, std::complex y) { - if (x == T(0) && !std::isnan(std::real(y)) && !std::isnan(std::imag(y))) { - return 0; - } - - return x * log1p(y); -} - -} // namespace xsf diff --git a/scipy/special/xsf/log_exp.h b/scipy/special/xsf/log_exp.h deleted file mode 100644 index 2f9e901e9157..000000000000 --- a/scipy/special/xsf/log_exp.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -#include "config.h" -#include "error.h" - -namespace xsf { - -template -T expit(T x) { - return 1 / (1 + std::exp(-x)); -}; - -inline double exprel(double x) { - if (std::abs(x) < std::numeric_limits::epsilon()) { - return 1; - } - - if (x > 717) { // near log(DBL_MAX) - return std::numeric_limits::infinity(); - } - - return std::expm1(x) / x; -} - -inline float exprel(float x) { return exprel(static_cast(x)); } - -template -T logit(T x) { - // The standard formula is log(x/(1 - x)), but this expression - // loses precision near x=0.5, as does log(x) - log1p(-x). - // We use the standard formula away from p=0.5, and use - // log1p(2*(x - 0.5)) - log1p(-2*(x - 0.5)) around p=0.5, which - // provides very good precision in this interval. - if (x < 0.3 || x > 0.65) { - return std::log(x/(1 - x)); - } - else { - T s = 2*(x - 0.5); - return std::log1p(s) - std::log1p(-s); - } -}; - -// -// The logistic sigmoid function 'expit' is -// -// S(x) = 1/(1 + exp(-x)) = exp(x)/(exp(x) + 1) -// -// so -// -// log S(x) = -log(1 + exp(-x)) = x - log(exp(x) + 1) -// = -log1p(exp(-x)) = x - log1p(exp(x)) -// -// By using -log1p(exp(-x)) for x >= 0 and x - log1p(exp(x)) -// for x < 0, we extend the range of x values for which we -// obtain accurate results (compared to the naive implementation -// log(expit(x))). -// -template -T log_expit(T x) { - if (x < 0) { - return x - std::log1p(std::exp(x)); - } - - return -std::log1p(std::exp(-x)); -}; - - -/* Compute log(1 - exp(x)). */ -template -T log1mexp(T x) { - if (x > 0) { - set_error("_log1mexp", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0) { - set_error("_log1mexp", SF_ERROR_SINGULAR, NULL); - return -std::numeric_limits::infinity(); - } - if (x < -1) { - return std::log1p(-std::exp(x)); - } - return std::log(-std::expm1(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/loggamma.h b/scipy/special/xsf/loggamma.h deleted file mode 100644 index eaae479b2054..000000000000 --- a/scipy/special/xsf/loggamma.h +++ /dev/null @@ -1,163 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2024. - * Original header comment appears below. - */ - -/* An implementation of the principal branch of the logarithm of - * Gamma. Also contains implementations of Gamma and 1/Gamma which are - * easily computed from log-Gamma. - * - * Author: Josh Wilson - * - * Distributed under the same license as Scipy. - * - * References - * ---------- - * [1] Hare, "Computing the Principal Branch of log-Gamma", - * Journal of Algorithms, 1997. - * - * [2] Julia, - * https://github.com/JuliaLang/julia/blob/master/base/special/gamma.jl - */ - -#pragma once - -#include "cephes/gamma.h" -#include "cephes/rgamma.h" -#include "config.h" -#include "error.h" -#include "evalpoly.h" -#include "trig.h" -#include "zlog1.h" - -namespace xsf { - -namespace detail { - constexpr double loggamma_SMALLX = 7; - constexpr double loggamma_SMALLY = 7; - constexpr double loggamma_HLOG2PI = 0.918938533204672742; // log(2*pi)/2 - constexpr double loggamma_LOGPI = 1.1447298858494001741434262; // log(pi) - constexpr double loggamma_TAYLOR_RADIUS = 0.2; - - XSF_HOST_DEVICE std::complex loggamma_stirling(std::complex z) { - /* Stirling series for log-Gamma - * - * The coefficients are B[2*n]/(2*n*(2*n - 1)) where B[2*n] is the - * (2*n)th Bernoulli number. See (1.1) in [1]. - */ - double coeffs[] = {-2.955065359477124183E-2, 6.4102564102564102564E-3, -1.9175269175269175269E-3, - 8.4175084175084175084E-4, -5.952380952380952381E-4, 7.9365079365079365079E-4, - -2.7777777777777777778E-3, 8.3333333333333333333E-2}; - std::complex rz = 1.0 / z; - std::complex rzz = rz / z; - - return (z - 0.5) * std::log(z) - z + loggamma_HLOG2PI + rz * cevalpoly(coeffs, 7, rzz); - } - - XSF_HOST_DEVICE std::complex loggamma_recurrence(std::complex z) { - /* Backward recurrence relation. - * - * See Proposition 2.2 in [1] and the Julia implementation [2]. - * - */ - int signflips = 0; - int sb = 0; - std::complex shiftprod = z; - - z += 1.0; - int nsb; - while (z.real() <= loggamma_SMALLX) { - shiftprod *= z; - nsb = std::signbit(shiftprod.imag()); - signflips += nsb != 0 && sb == 0 ? 1 : 0; - sb = nsb; - z += 1.0; - } - return loggamma_stirling(z) - std::log(shiftprod) - signflips * 2 * M_PI * std::complex(0, 1); - } - - XSF_HOST_DEVICE std::complex loggamma_taylor(std::complex z) { - /* Taylor series for log-Gamma around z = 1. - * - * It is - * - * loggamma(z + 1) = -gamma*z + zeta(2)*z**2/2 - zeta(3)*z**3/3 ... - * - * where gamma is the Euler-Mascheroni constant. - */ - - double coeffs[] = { - -4.3478266053040259361E-2, 4.5454556293204669442E-2, -4.7619070330142227991E-2, 5.000004769810169364E-2, - -5.2631679379616660734E-2, 5.5555767627403611102E-2, -5.8823978658684582339E-2, 6.2500955141213040742E-2, - -6.6668705882420468033E-2, 7.1432946295361336059E-2, -7.6932516411352191473E-2, 8.3353840546109004025E-2, - -9.0954017145829042233E-2, 1.0009945751278180853E-1, -1.1133426586956469049E-1, 1.2550966952474304242E-1, - -1.4404989676884611812E-1, 1.6955717699740818995E-1, -2.0738555102867398527E-1, 2.7058080842778454788E-1, - -4.0068563438653142847E-1, 8.2246703342411321824E-1, -5.7721566490153286061E-1}; - - z -= 1.0; - return z * cevalpoly(coeffs, 22, z); - } -} // namespace detail - -XSF_HOST_DEVICE inline double loggamma(double x) { - if (x < 0.0) { - return std::numeric_limits::quiet_NaN(); - } - return cephes::lgam(x); -} - -XSF_HOST_DEVICE inline float loggamma(float x) { return loggamma(static_cast(x)); } - -XSF_HOST_DEVICE inline std::complex loggamma(std::complex z) { - // Compute the principal branch of log-Gamma - - if (std::isnan(z.real()) || std::isnan(z.imag())) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (z.real() <= 0 and z == std::floor(z.real())) { - set_error("loggamma", SF_ERROR_SINGULAR, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - if (z.real() > detail::loggamma_SMALLX || std::abs(z.imag()) > detail::loggamma_SMALLY) { - return detail::loggamma_stirling(z); - } - if (std::abs(z - 1.0) < detail::loggamma_TAYLOR_RADIUS) { - return detail::loggamma_taylor(z); - } - if (std::abs(z - 2.0) < detail::loggamma_TAYLOR_RADIUS) { - // Recurrence relation and the Taylor series around 1. - return detail::zlog1(z - 1.0) + detail::loggamma_taylor(z - 1.0); - } - if (z.real() < 0.1) { - // Reflection formula; see Proposition 3.1 in [1] - double tmp = std::copysign(2 * M_PI, z.imag()) * std::floor(0.5 * z.real() + 0.25); - return std::complex(detail::loggamma_LOGPI, tmp) - std::log(sinpi(z)) - loggamma(1.0 - z); - } - if (std::signbit(z.imag()) == 0) { - // z.imag() >= 0 but is not -0.0 - return detail::loggamma_recurrence(z); - } - return std::conj(detail::loggamma_recurrence(std::conj(z))); -} - -XSF_HOST_DEVICE inline std::complex loggamma(std::complex z) { - return static_cast>(loggamma(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double rgamma(double z) { return cephes::rgamma(z); } - -XSF_HOST_DEVICE inline float rgamma(float z) { return rgamma(static_cast(z)); } - -XSF_HOST_DEVICE inline std::complex rgamma(std::complex z) { - // Compute 1/Gamma(z) using loggamma. - if (z.real() <= 0 && z == std::floor(z.real())) { - // Zeros at 0, -1, -2, ... - return 0.0; - } - return std::exp(-loggamma(z)); -} - -XSF_HOST_DEVICE inline std::complex rgamma(std::complex z) { - return static_cast>(rgamma(static_cast>(z))); -} - -} // namespace xsf diff --git a/scipy/special/xsf/mathieu.h b/scipy/special/xsf/mathieu.h deleted file mode 100644 index 7fe82a5ec2d2..000000000000 --- a/scipy/special/xsf/mathieu.h +++ /dev/null @@ -1,233 +0,0 @@ -#pragma once - -#include "specfun/specfun.h" - -namespace xsf { - -template -T sem_cva(T m, T q); - -template -void sem(T m, T q, T x, T &csf, T &csd); - -/* Mathieu functions */ -/* Characteristic values */ -template -T cem_cva(T m, T q) { - int int_m, kd = 1; - - if ((m < 0) || (m != floor(m))) { - set_error("mathieu_a", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E26 */ - if (int_m % 2 == 0) { - return cem_cva(m, -q); - } else { - return sem_cva(m, -q); - } - } - - if (int_m % 2) { - kd = 2; - } - return specfun::cva2(kd, int_m, q); -} - -template -T sem_cva(T m, T q) { - int int_m, kd = 4; - - if ((m <= 0) || (m != floor(m))) { - set_error("mathieu_b", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E26 */ - if (int_m % 2 == 0) { - return sem_cva(m, -q); - } else { - return cem_cva(m, -q); - } - } - if (int_m % 2) { - kd = 3; - } - return specfun::cva2(kd, int_m, q); -} - -/* Mathieu functions */ -template -void cem(T m, T q, T x, T &csf, T &csd) { - int int_m, kf = 1, sgn; - T f = 0.0, d = 0.0; - if ((m < 0) || (m != floor(m))) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - set_error("mathieu_cem", SF_ERROR_DOMAIN, NULL); - } else { - int_m = (int) m; - if (q < 0) { - /* https://dlmf.nist.gov/28.2#E34 */ - if (int_m % 2 == 0) { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - cem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - - } else { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - sem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } - } else { - using specfun::Status; - Status status = specfun::mtu0(kf, int_m, q, x, &csf, &csd); - if (status != Status::OK) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_cem", sf_error, NULL); - } - } - } -} - -template -void sem(T m, T q, T x, T &csf, T &csd) { - int int_m, kf = 2, sgn; - T f = 0.0, d = 0.0; - if ((m < 0) || (m != floor(m))) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - set_error("mathieu_sem", SF_ERROR_DOMAIN, NULL); - } else { - int_m = (int) m; - if (int_m == 0) { - csf = 0; - csd = 0; - } else if (q < 0) { - /* https://dlmf.nist.gov/28.2#E34 */ - if (int_m % 2 == 0) { - sgn = ((int_m / 2) % 2 == 0) ? -1 : 1; - sem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } else { - sgn = ((int_m / 2) % 2 == 0) ? 1 : -1; - cem(m, -q, 90 - x, f, d); - csf = sgn * f; - csd = -sgn * d; - } - } else { - using specfun::Status; - Status status = specfun::mtu0(kf, int_m, q, x, &csf, &csd); - if (status != Status::OK) { - csf = std::numeric_limits::quiet_NaN(); - csd = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_sem", sf_error, NULL); - } - } - } -} - -template -void mcm1(T m, T q, T x, T &f1r, T &d1r) { - int int_m, kf = 1, kc = 1; - T f2r = 0.0, d2r = 0.0; - - if ((m < 0) || (m != floor(m)) || (q < 0)) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modcem1", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modcem1", sf_error, NULL); - } - } -} - -template -void msm1(T m, T q, T x, T &f1r, T &d1r) { - int int_m, kf = 2, kc = 1; - T f2r = 0.0, d2r = 0.0; - - if ((m < 1) || (m != floor(m)) || (q < 0)) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modsem1", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f1r = std::numeric_limits::quiet_NaN(); - d1r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modsem1", sf_error, NULL); - } - } -} - -template -void mcm2(T m, T q, T x, T &f2r, T &d2r) { - int int_m, kf = 1, kc = 2; - T f1r = 0.0, d1r = 0.0; - - if ((m < 0) || (m != floor(m)) || (q < 0)) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modcem2", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modcem2", sf_error, NULL); - } - } -} - -template -void msm2(T m, T q, T x, T &f2r, T &d2r) { - int int_m, kf = 2, kc = 2; - T f1r = 0.0, d1r = 0.0; - - if ((m < 1) || (m != floor(m)) || (q < 0)) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - set_error("mathieu_modsem2", SF_ERROR_DOMAIN, NULL); - } else { - using specfun::Status; - int_m = (int) m; - Status status = specfun::mtu12(kf, kc, int_m, q, x, &f1r, &d1r, &f2r, &d2r); - if (status != Status::OK) { - f2r = std::numeric_limits::quiet_NaN(); - d2r = std::numeric_limits::quiet_NaN(); - sf_error_t sf_error = status == Status::NoMemory ? SF_ERROR_MEMORY - : SF_ERROR_OTHER; - set_error("mathieu_modsem2", sf_error, NULL); - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/numbers.h b/scipy/special/xsf/numbers.h deleted file mode 100644 index da4e241ad542..000000000000 --- a/scipy/special/xsf/numbers.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "config.h" - -namespace xsf { -namespace numbers { - - template - std::complex i_v; - - template <> - std::complex i_v = std::literals::complex_literals::operator""if(1.0L); - - template <> - std::complex i_v = std::literals::complex_literals::operator""i(1.0L); - -} // namespace numbers -} // namespace xsf diff --git a/scipy/special/xsf/numpy.h b/scipy/special/xsf/numpy.h deleted file mode 100644 index 1e37caf81418..000000000000 --- a/scipy/special/xsf/numpy.h +++ /dev/null @@ -1,1081 +0,0 @@ -#pragma once - -#define PY_SSIZE_T_CLEAN -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "dual.h" -#include "error.h" -// Force defining the parenthesis operator even when compiling with a compiler -// defaulting to C++ >= 23. -#define MDSPAN_USE_PAREN_OPERATOR 1 -#include "third_party/kokkos/mdspan.hpp" - -/* PyUFunc_getfperr gets bits for current floating point error (fpe) status codes so we - * can check for floating point errors and make proper calls to set_error in ufunc loops. - * Define a wrapper so it can be given C linkage within this C++ header. */ -extern "C" int wrap_PyUFunc_getfperr() { return PyUFunc_getfperr(); } - -namespace xsf { -namespace numpy { - - void set_error_check_fpe(const char *func_name) { - int status = wrap_PyUFunc_getfperr(); - if (status & NPY_FPE_DIVIDEBYZERO) { - xsf::set_error(func_name, SF_ERROR_SINGULAR, "floating point division by zero"); - } - if (status & NPY_FPE_OVERFLOW) { - xsf::set_error(func_name, SF_ERROR_UNDERFLOW, "floating point underflow"); - } - if (status & NPY_FPE_UNDERFLOW) { - xsf::set_error(func_name, SF_ERROR_OVERFLOW, "floating point overflow"); - } - if (status & NPY_FPE_INVALID) { - xsf::set_error(func_name, SF_ERROR_DOMAIN, "floating point invalid value"); - } - } - - namespace detail { - - // This is std::accumulate, but that is not constexpr until C++20 - template - constexpr T initializer_accumulate(InputIt first, InputIt last, T init) { - for (InputIt it = first; it != last; ++it) { - init = std::move(init) + *it; - } - - return init; - } - - } // namespace detail - - using cfloat = std::complex; - using cdouble = std::complex; - - using float_1d = std::mdspan, std::layout_stride>; - using float_2d = std::mdspan, std::layout_stride>; - using float_3d = std::mdspan, std::layout_stride>; - using float_4d = std::mdspan, std::layout_stride>; - using double_1d = std::mdspan, std::layout_stride>; - using double_2d = std::mdspan, std::layout_stride>; - using double_3d = std::mdspan, std::layout_stride>; - using double_4d = std::mdspan, std::layout_stride>; - using cfloat_1d = std::mdspan, std::layout_stride>; - using cfloat_2d = std::mdspan, std::layout_stride>; - using cfloat_3d = std::mdspan, std::layout_stride>; - using cfloat_4d = std::mdspan, std::layout_stride>; - using cdouble_1d = std::mdspan, std::layout_stride>; - using cdouble_2d = std::mdspan, std::layout_stride>; - using cdouble_3d = std::mdspan, std::layout_stride>; - using cdouble_4d = std::mdspan, std::layout_stride>; - - using autodiff0_float = dual; - using autodiff0_double = dual; - using autodiff0_cfloat = dual; - using autodiff0_cdouble = dual; - using autodiff0_float_1d = std::mdspan, std::layout_stride>; - using autodiff0_double_1d = std::mdspan, std::layout_stride>; - using autodiff0_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff0_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff0_float_2d = std::mdspan, std::layout_stride>; - using autodiff0_double_2d = std::mdspan, std::layout_stride>; - using autodiff0_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff0_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff1_float = dual; - using autodiff1_double = dual; - using autodiff1_cfloat = dual; - using autodiff1_cdouble = dual; - using autodiff1_float_1d = std::mdspan, std::layout_stride>; - using autodiff1_double_1d = std::mdspan, std::layout_stride>; - using autodiff1_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff1_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff1_float_2d = std::mdspan, std::layout_stride>; - using autodiff1_double_2d = std::mdspan, std::layout_stride>; - using autodiff1_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff1_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff2_float = dual; - using autodiff2_double = dual; - using autodiff2_cfloat = dual; - using autodiff2_cdouble = dual; - using autodiff2_float_1d = std::mdspan, std::layout_stride>; - using autodiff2_double_1d = std::mdspan, std::layout_stride>; - using autodiff2_cfloat_1d = std::mdspan, std::layout_stride>; - using autodiff2_cdouble_1d = std::mdspan, std::layout_stride>; - using autodiff2_float_2d = std::mdspan, std::layout_stride>; - using autodiff2_double_2d = std::mdspan, std::layout_stride>; - using autodiff2_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff2_cdouble_2d = std::mdspan, std::layout_stride>; - - using autodiff00_float = dual; - using autodiff00_double = dual; - using autodiff00_cfloat = dual; - using autodiff00_cdouble = dual; - using autodiff00_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff00_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff11_float = dual; - using autodiff11_double = dual; - using autodiff11_cfloat = dual; - using autodiff11_cdouble = dual; - using autodiff11_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff11_cdouble_2d = std::mdspan, std::layout_stride>; - using autodiff22_float = dual; - using autodiff22_double = dual; - using autodiff22_cfloat = dual; - using autodiff22_cdouble = dual; - using autodiff22_cfloat_2d = std::mdspan, std::layout_stride>; - using autodiff22_cdouble_2d = std::mdspan, std::layout_stride>; - - // The following are based off NumPy's dtype type codes and functions like PyUFunc_dd_d - - // 1 input, 1 output - using f_f = float (*)(float); - using d_d = double (*)(double); - using F_F = cfloat (*)(cfloat); - using D_D = cdouble (*)(cdouble); - - // autodiff, 1 input, 1 output - using autodiff0_f_f1 = void (*)(autodiff0_float, autodiff0_float_1d); - using autodiff0_d_d1 = void (*)(autodiff0_double, autodiff0_double_1d); - using autodiff0_F_F1 = void (*)(autodiff0_cfloat, autodiff0_cfloat_1d); - using autodiff0_D_D1 = void (*)(autodiff0_cdouble, autodiff0_cdouble_1d); - using autodiff1_f_f1 = void (*)(autodiff1_float, autodiff1_float_1d); - using autodiff1_d_d1 = void (*)(autodiff1_double, autodiff1_double_1d); - using autodiff1_F_F1 = void (*)(autodiff1_cfloat, autodiff1_cfloat_1d); - using autodiff1_D_D1 = void (*)(autodiff1_cdouble, autodiff1_cdouble_1d); - using autodiff2_f_f1 = void (*)(autodiff2_float, autodiff2_float_1d); - using autodiff2_d_d1 = void (*)(autodiff2_double, autodiff2_double_1d); - using autodiff2_F_F1 = void (*)(autodiff2_cfloat, autodiff2_cfloat_1d); - using autodiff2_D_D1 = void (*)(autodiff2_cdouble, autodiff2_cdouble_1d); - - using autodiff0_f_f2 = void (*)(autodiff0_float, autodiff0_float_2d); - using autodiff0_d_d2 = void (*)(autodiff0_double, autodiff0_double_2d); - using autodiff0_F_F2 = void (*)(autodiff0_cfloat, autodiff0_cfloat_2d); - using autodiff0_D_D2 = void (*)(autodiff0_cdouble, autodiff0_cdouble_2d); - using autodiff1_f_f2 = void (*)(autodiff1_float, autodiff1_float_2d); - using autodiff1_d_d2 = void (*)(autodiff1_double, autodiff1_double_2d); - using autodiff1_F_F2 = void (*)(autodiff1_cfloat, autodiff1_cfloat_2d); - using autodiff1_D_D2 = void (*)(autodiff1_cdouble, autodiff1_cdouble_2d); - using autodiff2_f_f2 = void (*)(autodiff2_float, autodiff2_float_2d); - using autodiff2_d_d2 = void (*)(autodiff2_double, autodiff2_double_2d); - using autodiff2_F_F2 = void (*)(autodiff2_cfloat, autodiff2_cfloat_2d); - using autodiff2_D_D2 = void (*)(autodiff2_cdouble, autodiff2_cdouble_2d); - - // 1 input, 2 outputs - using f_ff = void (*)(float, float &, float &); - using d_dd = void (*)(double, double &, double &); - using f_FF = void (*)(float, cfloat &, cfloat &); - using d_DD = void (*)(double, cdouble &, cdouble &); - using F_FF = void (*)(cfloat, cfloat &, cfloat &); - using D_DD = void (*)(cdouble, cdouble &, cdouble &); - - // 1 input, 4 outputs - using f_ffff = void (*)(float, float &, float &, float &, float &); - using d_dddd = void (*)(double, double &, double &, double &, double &); - using f_FFFF = void (*)(float, cfloat &, cfloat &, cfloat &, cfloat &); - using d_DDDD = void (*)(double, cdouble &, cdouble &, cdouble &, cdouble &); - using F_FFFF = void (*)(cfloat, cfloat &, cfloat &, cfloat &, cfloat &); - using D_DDDD = void (*)(cdouble, cdouble &, cdouble &, cdouble &, cdouble &); - - // 2 inputs, 1 output - using qf_f = float (*)(long long int, float); - using qd_d = double (*)(long long int, double); - using ff_f = float (*)(float, float); - using dd_d = double (*)(double, double); - using FF_F = cfloat (*)(cfloat, cfloat); - using DD_D = cdouble (*)(cdouble, cdouble); - using fF_F = cfloat (*)(float, cfloat); - using dD_D = cdouble (*)(double, cdouble); - using lf_f = float (*)(long int, float); - using ld_d = double (*)(long int, double); - using lF_F = cfloat (*)(long int, cfloat); - using lD_D = cdouble (*)(long int, cdouble); - using Dd_D = cdouble (*) (cdouble, double); - using Ff_F = cfloat (*) (cfloat, float); - - // autodiff, 2 inputs, 1 output - using autodiff0_if_f = autodiff0_float (*)(int, autodiff0_float); - using autodiff0_id_d = autodiff0_double (*)(int, autodiff0_double); - using autodiff1_if_f = autodiff1_float (*)(int, autodiff1_float); - using autodiff1_id_d = autodiff1_double (*)(int, autodiff1_double); - using autodiff2_if_f = autodiff2_float (*)(int, autodiff2_float); - using autodiff2_id_d = autodiff2_double (*)(int, autodiff2_double); - - // 2 inputs, 2 outputs - using qf_ff = void (*)(long long int, float, float &, float &); - using qd_dd = void (*)(long long int, double, double &, double &); - - // 2 inputs, 3 outputs - using qf_fff = void (*)(long long int, float, float &, float &, float &); - using qd_ddd = void (*)(long long int, double, double &, double &, double &); - - // 2 inputs, 2 outputs - using ff_ff = void (*)(float, float, float &, float &); - using dd_dd = void (*)(double, double, double &, double &); - using lf_ff = void (*)(long int, float, float &, float &); - using ld_dd = void (*)(long int, double, double &, double &); - - // 2 inputs, 3 outputs - using lf_fff = void (*)(long int, float, float &, float &, float &); - using ld_ddd = void (*)(long int, double, double &, double &, double &); - - // 2 inputs, 4 outputs - using ff_ffff = void (*)(float, float, float &, float &, float &, float &); - using dd_dddd = void (*)(double, double, double &, double &, double &, double &); - - // 3 inputs, 1 output - using fff_f = float (*)(float, float, float); - using ddd_d = double (*)(double, double, double); - using Flf_F = cfloat (*)(cfloat, long int, float); - using Dld_D = cdouble (*)(cdouble, long int, double); - - // 3 inputs, 2 outputs - using fff_ff = void (*)(float, float, float, float &, float &); - using ddd_dd = void (*)(double, double, double, double &, double &); - - // 3 inputs, 1 output - using qqf_f = float (*)(long long int, long long int, float); - using qqd_d = double (*)(long long int, long long int, double); - - // autodiff, 3 inputs, 1 ouput - using autodiff0_iif_f = autodiff0_float (*)(int, int, autodiff0_float); - using autodiff0_iid_d = autodiff0_double (*)(int, int, autodiff0_double); - using autodiff1_iif_f = autodiff1_float (*)(int, int, autodiff1_float); - using autodiff1_iid_d = autodiff1_double (*)(int, int, autodiff1_double); - using autodiff2_iif_f = autodiff2_float (*)(int, int, autodiff2_float); - using autodiff2_iid_d = autodiff2_double (*)(int, int, autodiff2_double); - - // 3 inputs, 2 outputs - using qqf_ff = void (*)(long long int, long long int, float, float &, float &); - using qqd_dd = void (*)(long long int, long long int, double, double &, double &); - - // 3 inputs, 3 outputs - using qqf_fff = void (*)(long long int, long long int, float, float &, float &, float &); - using qqd_ddd = void (*)(long long int, long long int, double, double &, double &, double &); - - // 4 inputs, 1 output - using qqqF_F = cfloat (*)(long long int, long long int, long long int, cfloat); - using qqqD_D = cdouble (*)(long long int, long long int, long long int, cdouble); - using qqff_F = cfloat (*)(long long int, long long int, float, float); - using qqdd_D = cdouble (*)(long long int, long long int, double, double); - using ffff_f = float (*)(float, float, float, float); - using dddd_d = double (*)(double, double, double, double); - using fffF_F = cfloat (*)(float, float, float, cfloat); - using dddD_D = cdouble (*)(double, double, double, cdouble); - using ffff_F = cfloat (*)(float, float, float, float); - using dddd_D = cdouble (*)(double, double, double, double); - - // autodiff, 4 inputs, 1 output - using autodiff00_iiff_F = autodiff00_cfloat (*)(int, int, autodiff00_float, autodiff00_float); - using autodiff00_iidd_D = autodiff00_cdouble (*)(int, int, autodiff00_double, autodiff00_double); - using autodiff11_iiff_F = autodiff11_cfloat (*)(int, int, autodiff11_float, autodiff11_float); - using autodiff11_iidd_D = autodiff11_cdouble (*)(int, int, autodiff11_double, autodiff11_double); - using autodiff22_iiff_F = autodiff22_cfloat (*)(int, int, autodiff22_float, autodiff22_float); - using autodiff22_iidd_D = autodiff22_cdouble (*)(int, int, autodiff22_double, autodiff22_double); - - // 4 inputs, 2 outputs - using qqqf_ff = void (*)(long long int, long long int, long long int, float, float &, float &); - using ffff_ff = void (*)(float, float, float, float, float &, float &); - using qqqd_dd = void (*)(long long int, long long int, long long int, double, double &, double &); - using dddd_dd = void (*)(double, double, double, double, double &, double &); - using qqqF_FF = void (*)(long long int, long long int, long long int, cfloat, cfloat &, cfloat &); - using qqqD_DD = void (*)(long long int, long long int, long long int, cdouble, cdouble &, cdouble &); - using qqff_FF = void (*)(long long int, long long int, float, float, cfloat &, cfloat &); - using qqdd_DD = void (*)(long long int, long long int, double, double, cdouble &, cdouble &); - - // 4 inputs, 3 outputs - using qqqf_fff = void (*)(long long int, long long int, long long int, float, float &, float &, float &); - using qqqd_ddd = void (*)(long long int, long long int, long long int, double, double &, double &, double &); - using qqqF_FFF = void (*)(long long int, long long int, long long int, cfloat, cfloat &, cfloat &, cfloat &); - using qqqD_DDD = void (*)(long long int, long long int, long long int, cdouble, cdouble &, cdouble &, cdouble &); - using qqff_FFF = void (*)(long long int, long long int, float, float, cfloat &, cfloat &, cfloat &); - using qqdd_DDD = void (*)(long long int, long long int, double, double, cdouble &, cdouble &, cdouble &); - - // 5 inputs, 2 outputs - using fffff_ff = void (*)(float, float, float, float, float, float &, float &); - using ddddd_dd = void (*)(double, double, double, double, double, double &, double &); - -#if (NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE) - using g_g = double (*)(double); - using gg_g = double (*)(double); -#else - using g_g = long double (*)(long double); - using gg_g = long double (*)(long double); -#endif - - // 1 input, 1 output - using f_f1 = void (*)(float, float_1d); - using f_f2 = void (*)(float, float_2d); - using d_d1 = void (*)(double, double_1d); - using d_d2 = void (*)(double, double_2d); - using F_F1 = void (*)(cfloat, cfloat_1d); - using D_D1 = void (*)(cdouble, cdouble_1d); - - // 1 input, 2 outputs - using f_f1f1 = void (*)(float, float_1d, float_1d); - using f_f2f2 = void (*)(float, float_2d, float_2d); - using d_d1d1 = void (*)(double, double_1d, double_1d); - using d_d2d2 = void (*)(double, double_2d, double_2d); - using F_F1F1 = void (*)(cfloat, cfloat_1d, cfloat_1d); - using F_F2F2 = void (*)(cfloat, cfloat_2d, cfloat_2d); - using D_D1D1 = void (*)(cdouble, cdouble_1d, cdouble_1d); - using D_D2D2 = void (*)(cdouble, cdouble_2d, cdouble_2d); - - // 1 input, 3 outputs - using f_f1f1f1 = void (*)(float, float_1d, float_1d, float_1d); - using f_f2f2f2 = void (*)(float, float_2d, float_2d, float_2d); - using d_d1d1d1 = void (*)(double, double_1d, double_1d, double_1d); - using d_d2d2d2 = void (*)(double, double_2d, double_2d, double_2d); - using F_F1F1F1 = void (*)(cfloat, cfloat_1d, cfloat_1d, cfloat_1d); - using D_D1D1D1 = void (*)(cdouble, cdouble_1d, cdouble_1d, cdouble_1d); - - // 2 inputs, 1 output - using ff_F2 = void (*)(float, float, cfloat_2d); - using dd_D2 = void (*)(double, double, cdouble_2d); - using qF_F2 = void (*)(long long int, cfloat, cfloat_2d); - using qD_D2 = void (*)(long long int, cdouble, cdouble_2d); - - using autodiff00_ff_F2 = void (*)(autodiff00_float, autodiff00_float, autodiff00_cfloat_2d); - using autodiff00_dd_D2 = void (*)(autodiff00_double, autodiff00_double, autodiff00_cdouble_2d); - using autodiff11_ff_F2 = void (*)(autodiff11_float, autodiff11_float, autodiff11_cfloat_2d); - using autodiff11_dd_D2 = void (*)(autodiff11_double, autodiff11_double, autodiff11_cdouble_2d); - using autodiff22_ff_F2 = void (*)(autodiff22_float, autodiff22_float, autodiff22_cfloat_2d); - using autodiff22_dd_D2 = void (*)(autodiff22_double, autodiff22_double, autodiff22_cdouble_2d); - - // 2 inputs, 2 outputs - using qF_F2F2 = void (*)(long long int, cfloat, cfloat_2d, cfloat_2d); - using qD_D2D2 = void (*)(long long int, cdouble, cdouble_2d, cdouble_2d); - using ff_F2F3 = void (*)(float, float, cfloat_2d, cfloat_3d); - using dd_D2D3 = void (*)(double, double, cdouble_2d, cdouble_3d); - - // 2 inputs, 3 outputs - using qF_F2F2F2 = void (*)(long long int, cfloat, cfloat_2d, cfloat_2d, cfloat_2d); - using qD_D2D2D2 = void (*)(long long int, cdouble, cdouble_2d, cdouble_2d, cdouble_2d); - - // 2 inputs, 4 outputs - using ff_F2F3F4 = void (*)(float, float, cfloat_2d, cfloat_3d, cfloat_4d); - using dd_D2D3D4 = void (*)(double, double, cdouble_2d, cdouble_3d, cdouble_4d); - - template - struct signature_of { - using type = typename signature_of::type; - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - struct signature_of { - using type = Res(Args...); - }; - - template - using signature_of_t = typename signature_of::type; - - // Deduces the number of arguments of a callable F. - template - struct arity_of { - static constexpr size_t value = arity_of>::value; - }; - - template - struct arity_of { - static constexpr size_t value = sizeof...(Args); - }; - - template - constexpr size_t arity_of_v = arity_of::value; - - template - struct has_return { - static constexpr bool value = has_return>::value; - }; - - template - struct has_return { - static constexpr bool value = true; - }; - - template - struct has_return { - static constexpr bool value = false; - }; - - template - constexpr size_t has_return_v = has_return::value; - - template - struct rank_of { - static constexpr size_t value = 0; - }; - - template - struct rank_of> { - static constexpr size_t value = Extents::rank() + rank_of::value; - }; - - template - struct rank_of> { - static constexpr size_t value = sizeof...(Orders); - }; - - template - inline constexpr size_t rank_of_v = rank_of::value; - - // Maps a C++ type to a NumPy type - template - struct npy_type; - - template <> - struct npy_type { - using type = npy_bool; - }; - - template <> - struct npy_type { - using type = npy_byte; - }; - - template <> - struct npy_type { - using type = npy_short; - }; - - template <> - struct npy_type { - using type = npy_int; - }; - - template <> - struct npy_type { - using type = npy_long; - }; - - template <> - struct npy_type { - using type = npy_longlong; - }; - - template <> - struct npy_type { - using type = npy_ubyte; - }; - - template <> - struct npy_type { - using type = npy_ushort; - }; - - template <> - struct npy_type { - using type = npy_uint; - }; - - template <> - struct npy_type { - using type = npy_ulong; - }; - - template <> - struct npy_type { - using type = npy_ulonglong; - }; - - template <> - struct npy_type { - using type = npy_float; - }; - - template <> - struct npy_type { - using type = npy_double; - }; - - template <> - struct npy_type { - using type = npy_longdouble; - }; - - template <> - struct npy_type> { - using type = npy_cfloat; - }; - - template <> - struct npy_type> { - using type = npy_cdouble; - }; - - template <> - struct npy_type> { - using type = npy_clongdouble; - }; - - template - using npy_type_t = typename npy_type::type; - - // Maps a C++ type to a NumPy type number - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum>::value; - }; - - // We need to specialise for bool as npy_bool is defined as npy_ubyte - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_BOOL; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_BYTE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_SHORT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_INT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONGLONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_UBYTE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_USHORT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_UINT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_ULONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_ULONGLONG; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_FLOAT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_DOUBLE; - }; - -// When NPY_SIZEOF_LONGDOUBLE == NPY_SIZEOF_DOUBLE, npy_longdouble is defined as npy_double -// See https://github.com/numpy/numpy/blob/main/numpy/_core/include/numpy/npy_common.h -#if (NPY_SIZEOF_LONGDOUBLE != NPY_SIZEOF_DOUBLE) - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_LONGDOUBLE; - }; -#endif - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CFLOAT; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CDOUBLE; - }; - - template <> - struct npy_typenum { - static constexpr NPY_TYPES value = NPY_CLONGDOUBLE; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum> { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - struct npy_typenum> { - static constexpr NPY_TYPES value = npy_typenum::value; - }; - - template - inline constexpr NPY_TYPES npy_typenum_v = npy_typenum::value; - - template - struct npy_traits { - static T get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast(src); - } - - static void set(char *dst, const T &src) { *reinterpret_cast *>(dst) = src; } - }; - - template - struct npy_traits> { - static std::complex get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast *>(src); - } - - static void set(char *dst, const std::complex &src) { - *reinterpret_cast *>(dst) = std::real(src); - *reinterpret_cast *>(dst + sizeof(T)) = std::imag(src); - } - }; - - template - struct npy_traits { - static T *get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return reinterpret_cast(src); - } - }; - - template - struct npy_traits { - static T &get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits { - using dst_type = T (&)[N]; - - static dst_type get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits { - using dst_type = T (&)[N][N]; - - static dst_type get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument type"); - - return *reinterpret_cast(src); - } - }; - - template - struct npy_traits> { - static std::mdspan - get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - // static_assert(sizeof(T) == sizeof(npy_type_t), "NumPy type has different size than argument - // type"); - - std::array strides; - for (npy_uintp i = 0; i < strides.size(); ++i) { - strides[i] = steps[i] / sizeof(T); - } - - std::array exts; - for (npy_uintp i = 0; i < exts.size(); ++i) { - exts[i] = dimensions[i]; - } - - return {reinterpret_cast(src), {exts, strides}}; - } - }; - - template - struct npy_traits> { - static dual get(char *src, const npy_intp *dimensions, const npy_intp *steps) { - return *reinterpret_cast *>(src); - } - - static void set(char *dst, const dual &src) { - *reinterpret_cast *>(dst) = src; - } - }; - - using map_dims_type = void (*)(const npy_intp *, npy_intp *); - - struct base_ufunc_data { - const char *name; - map_dims_type map_dims; - int flags; - }; - - template - struct ufunc_data : base_ufunc_data { - Func func; - }; - - template < - typename Func, typename Signature = signature_of_t, - typename Indices = std::make_index_sequence>> - struct ufunc_traits; - - template - struct ufunc_traits> { - static constexpr char types[sizeof...(Args) + 1] = {npy_typenum_v..., npy_typenum_v}; - - static constexpr size_t ranks[sizeof...(Args) + 1] = {rank_of_v..., rank_of_v}; - - static constexpr size_t ranks_scan[sizeof...(Args) + 2] = { - detail::initializer_accumulate(ranks, ranks + I, 0)..., - detail::initializer_accumulate(ranks, ranks + sizeof...(Args), 0), - detail::initializer_accumulate(ranks, ranks + sizeof...(Args) + 1, 0) - }; - - static void loop(char **args, const npy_intp *dims, const npy_intp *steps, void *data) { - std::array new_dims; - - map_dims_type map_dims = static_cast *>(data)->map_dims; - map_dims(dims + 1, new_dims.data()); - - Func func = static_cast *>(data)->func; - for (npy_intp i = 0; i < dims[0]; ++i) { - Res res = func(npy_traits::get( - args[I], new_dims.data() + ranks_scan[I], steps + ranks_scan[I] + sizeof...(Args) + 1 - )...); - npy_traits::set(args[sizeof...(Args)], res); // assign to the output pointer - - for (npy_uintp j = 0; j <= sizeof...(Args); ++j) { - args[j] += steps[j]; - } - } - - const char *name = static_cast *>(data)->name; - set_error_check_fpe(name); - } - }; - - template - struct ufunc_traits> { - static constexpr char types[sizeof...(Args)] = {npy_typenum_v...}; - - static constexpr size_t ranks[sizeof...(Args)] = {rank_of_v...}; - - static constexpr size_t ranks_scan[sizeof...(Args) + 1] = { - detail::initializer_accumulate(ranks, ranks + I, 0)..., - detail::initializer_accumulate(ranks, ranks + sizeof...(Args), 0) - }; - - static void loop(char **args, const npy_intp *dims, const npy_intp *steps, void *data) { - std::array new_dims; - - map_dims_type map_dims = static_cast *>(data)->map_dims; - map_dims(dims + 1, new_dims.data()); - - Func func = static_cast *>(data)->func; - for (npy_intp i = 0; i < dims[0]; ++i) { - func(npy_traits::get( - args[I], new_dims.data() + ranks_scan[I], steps + ranks_scan[I] + sizeof...(Args) - )...); - - for (npy_uintp j = 0; j < sizeof...(Args); ++j) { - args[j] += steps[j]; - } - } - - const char *name = static_cast *>(data)->name; - set_error_check_fpe(name); - } - }; - - namespace detail { - - template - decltype(auto) compose(Func func, Tr tr) { - return tr(func); - } - - template - decltype(auto) compose(Func func, Tr0 tr0, Tr1 tr1, Trs... trs) { - return compose(tr0(func), tr1, trs...); - } - - } // namespace detail - - template - class compose { - std::tuple m_trs; - - public: - compose(Trs... trs) : m_trs(trs...) {} - - template - decltype(auto) operator()(Func func) const { - return std::apply([func](auto... trs) { return detail::compose(func, trs...); }, m_trs); - } - }; - - struct ufunc_wraps { - bool has_return; - int nin_and_nout; - PyUFuncGenericFunction func; - void *data; - void (*data_deleter)(void *); - const char *types; - - template - ufunc_wraps(Func func) - : has_return(has_return_v), nin_and_nout(arity_of_v + has_return), - func(ufunc_traits::loop), data(new ufunc_data{{nullptr}, func}), - data_deleter([](void *ptr) { delete static_cast *>(ptr); }), - types(ufunc_traits::types) {} - }; - - class ufunc_overloads { - public: - using data_handle_type = void *; - using data_deleter_type = void (*)(void *); - - private: - int m_ntypes; - bool m_has_return; - int m_nin_and_nout; - std::unique_ptr m_func; - std::unique_ptr m_data; - std::unique_ptr m_data_deleters; - std::unique_ptr m_types; - - public: - template - ufunc_overloads(Func0 func0, Funcs... funcs) - : m_ntypes(sizeof...(Funcs) + 1), m_has_return(has_return_v), - m_nin_and_nout(arity_of_v + m_has_return), m_func(new PyUFuncGenericFunction[m_ntypes]), - m_data(new data_handle_type[m_ntypes]), m_data_deleters(new data_deleter_type[m_ntypes]), - m_types(new char[m_ntypes * m_nin_and_nout]) { - ufunc_wraps func[sizeof...(Funcs) + 1] = {func0, funcs...}; - for (auto it = std::begin(func); it != std::end(func); ++it) { - if (it->nin_and_nout != m_nin_and_nout) { - PyErr_SetString(PyExc_RuntimeError, "all functions must have the same number of arguments"); - } - if (it->has_return != m_has_return) { - PyErr_SetString(PyExc_RuntimeError, "all functions must be void if any function is"); - } - - size_t i = it - std::begin(func); - m_func[i] = it->func; - m_data[i] = it->data; - m_data_deleters[i] = it->data_deleter; - std::memcpy(m_types.get() + i * m_nin_and_nout, it->types, m_nin_and_nout); - } - } - - template - ufunc_overloads(compose trs, Funcs... funcs) : ufunc_overloads(trs(funcs)...) {} - - ufunc_overloads(ufunc_overloads &&other) = default; - - ~ufunc_overloads() { - if (m_data) { - for (int i = 0; i < m_ntypes; ++i) { - data_deleter_type data_deleter = m_data_deleters[i]; - data_deleter(m_data[i]); - } - } - } - - int ntypes() const { return m_ntypes; } - - bool has_return() const { return m_has_return; } - - int nin_and_nout() const { return m_nin_and_nout; } - - PyUFuncGenericFunction *func() const { return m_func.get(); } - - data_handle_type *data() const { return m_data.get(); } - - char *types() const { return m_types.get(); } - - void set_name(const char *name) { - for (int i = 0; i < m_ntypes; ++i) { - static_cast(m_data[i])->name = name; - } - } - - void set_map_dims(map_dims_type map_dims) { - for (int i = 0; i < m_ntypes; ++i) { - static_cast(m_data[i])->map_dims = map_dims; - } - } - }; - - PyObject *ufunc(ufunc_overloads func, int nout, const char *name, const char *doc) { - static std::vector ufuncs; - - if (PyErr_Occurred()) { - return nullptr; - } - - ufunc_overloads &ufunc = ufuncs.emplace_back(std::move(func)); - ufunc.set_name(name); - ufunc.set_map_dims([](const npy_intp *dims, npy_intp *new_dims) {}); - - return PyUFunc_FromFuncAndData( - ufunc.func(), ufunc.data(), ufunc.types(), ufunc.ntypes(), ufunc.nin_and_nout() - nout, nout, PyUFunc_None, - name, doc, 0 - ); - } - - PyObject *ufunc(ufunc_overloads overloads, const char *name, const char *doc) { - int nout = overloads.has_return(); - - return ufunc(std::move(overloads), nout, name, doc); - } - - PyObject *gufunc( - ufunc_overloads overloads, int nout, const char *name, const char *doc, const char *signature, - map_dims_type map_dims - ) { - static std::vector ufuncs; - - if (PyErr_Occurred()) { - return nullptr; - } - - ufunc_overloads &ufunc = ufuncs.emplace_back(std::move(overloads)); - ufunc.set_name(name); - ufunc.set_map_dims(map_dims); - - return PyUFunc_FromFuncAndDataAndSignature( - ufunc.func(), ufunc.data(), ufunc.types(), ufunc.ntypes(), ufunc.nin_and_nout() - nout, nout, PyUFunc_None, - name, doc, 0, signature - ); - } - - PyObject *gufunc( - ufunc_overloads overloads, const char *name, const char *doc, const char *signature, map_dims_type map_dims - ) { - int nout = overloads.has_return(); - - return gufunc(std::move(overloads), nout, name, doc, signature, map_dims); - } - - // rename to autodiff_var? - template - struct autodiff_traits { - static T to_var(T arg, size_t i) { return arg; } - }; - - template - struct autodiff_traits> { - static dual to_var(T arg, size_t i) { return dual_var(arg, i); } - }; - - template < - typename Func, typename Signature = signature_of_t, - typename Indices = std::make_index_sequence>> - struct autodiff_wrapper; - - template - struct autodiff_wrapper> { - Func func; - - Res operator()(remove_dual_t... args) { - return func(autodiff_traits::to_var(args, I - i_scan[I])...); - } - - static constexpr size_t is_autodiff[sizeof...(Args)] = {std::is_same_v>...}; - - static constexpr size_t i_scan[sizeof...(Args)] = { - detail::initializer_accumulate(is_autodiff, is_autodiff + I, 0)..., - }; - }; - - template - autodiff_wrapper(Func func) -> autodiff_wrapper; - - struct autodiff { - template - decltype(auto) operator()(Func f) { - return autodiff_wrapper{f}; - } - }; - - template > - struct use_long_long_int_wrapper; - - template - struct use_long_long_int_wrapper { - Func func; - - Res operator()( - std::conditional_t && !std::is_same_v, long long int, Args>... args - ) { - return func(args...); - } - }; - - template - use_long_long_int_wrapper(Func func) -> use_long_long_int_wrapper; - - struct use_long_long_int { - template - decltype(auto) operator()(Func f) { - return use_long_long_int_wrapper{f}; - } - }; - -} // namespace numpy -} // namespace xsf diff --git a/scipy/special/xsf/par_cyl.h b/scipy/special/xsf/par_cyl.h deleted file mode 100644 index 6092ab2dee43..000000000000 --- a/scipy/special/xsf/par_cyl.h +++ /dev/null @@ -1,667 +0,0 @@ -#pragma once - -#include "specfun/specfun.h" - -namespace xsf { -namespace detail { - - template - T vvla(T x, T va); - - template - T dvsa(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Dv(x) - // for small argument - // Input: x --- Argument - // va --- Order - // Output: PD --- Dv(x) - // Routine called: GAMMA2 for computing Г(x) - // =================================================== - - int m; - T ep, a0, va0, pd, ga0, g1, g0, r, vm, gm, r1, vt; - - const T pi = 3.141592653589793; - const T eps = 1.0e-15; - const T sq2 = sqrt(2); - - ep = exp(-0.25 * x * x); - va0 = 0.5 * (1.0 - va); - if (va == 0.0) { - pd = ep; - } else { - if (x == 0.0) { - if ((va0 <= 0.0) && (va0 == (int) va0)) { - pd = 0.0; - } else { - ga0 = specfun::gamma2(va0); - pd = sqrt(pi) / (pow(2.0, -0.5 * va) * ga0); - } - } else { - g1 = specfun::gamma2(-va); - a0 = pow(2.0, -0.5 * va - 1.0) * ep / g1; - vt = -0.5 * va; - g0 = specfun::gamma2(vt); - pd = g0; - r = 1.0; - for (m = 1; m <= 250; m++) { - vm = 0.5 * (m - va); - gm = specfun::gamma2(vm); - r = -r * sq2 * x / m; - r1 = gm * r; - pd += r1; - if (fabs(r1) < fabs(pd) * eps) { - break; - } - } - pd *= a0; - } - } - return pd; - } - - template - T dvla(T x, T va) { - - // ==================================================== - // Purpose: Compute parabolic cylinder functions Dv(x) - // for large argument - // Input: x --- Argument - // va --- Order - // Output: PD --- Dv(x) - // Routines called: - // (1) VVLA for computing Vv(x) for large |x| - // (2) GAMMA2 for computing Г(x) - // ==================================================== - - int k; - T ep, a0, gl, pd, r, vl, x1; - - const T pi = 3.141592653589793; - const T eps = 1.0e-12; - ep = exp(-.25 * x * x); - a0 = pow(fabs(x), va) * ep; - r = 1.0; - pd = 1.0; - for (k = 1; k <= 16; k++) { - r = -0.5 * r * (2.0 * k - va - 1.0) * (2.0 * k - va - 2.0) / (k * x * x); - pd += r; - if (fabs(r / pd) < eps) { - break; - } - } - pd *= a0; - if (x < 0.0) { - x1 = -x; - vl = vvla(x1, va); - gl = specfun::gamma2(-va); - pd = pi * vl / gl + cos(pi * va) * pd; - } - return pd; - } - - template - T vvla(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Vv(x) - // for large argument - // Input: x --- Argument - // va --- Order - // Output: PV --- Vv(x) - // Routines called: - // (1) DVLA for computing Dv(x) for large |x| - // (2) GAMMA2 for computing Г(x) - // =================================================== - - int k; - T pv, qe, a0, r, x1, gl, dsl, pdl; - - const T pi = 3.141592653589793; - const T eps = 1e-12; - - qe = exp(0.25 * x * x); - a0 = pow(fabs(x), -va - 1) * sqrt(2.0 / pi) * qe; - r = 1.0; - pv = 1.0; - for (k = 1; k <= 18; k++) { - r = 0.5 * r * (2.0 * k + va - 1.0) * (2.0 * k + va) / (k * x * x); - pv += r; - if (fabs(r / pv) < eps) { - break; - } - } - pv *= a0; - if (x < 0.0) { - x1 = -x; - pdl = dvla(x1, va); - gl = specfun::gamma2(-va); - dsl = sin(pi * va) * sin(pi * va); - pv = dsl * gl / pi * pdl - cos(pi * va) * pv; - } - return pv; - } - - template - T vvsa(T x, T va) { - - // =================================================== - // Purpose: Compute parabolic cylinder function Vv(x) - // for small argument - // Input: x --- Argument - // va --- Order - // Output: PV --- Vv(x) - // Routine called : GAMMA2 for computing Г(x) - // =================================================== - - T a0, fac, g1, ga0, gm, gw, r, r1, sq2, sv, sv0, v1, vb0, vm, pv; - - const T eps = 1.0e-15; - const T pi = 3.141592653589793; - const T ep = exp(-0.25 * x * x); - T va0 = 1.0 + 0.5 * va; - - if (x == 0.0) { - if (((va0 <= 0.0) && (va0 == (int) va0)) || va == 0.0) { - pv = 0.0; - } else { - vb0 = -0.5 * va; - sv0 = sin(va0 * pi); - ga0 = specfun::gamma2(va0); - pv = pow(2.0, vb0) * sv0 / ga0; - } - } else { - sq2 = sqrt(2.0); - a0 = pow(2.0, -0.5 * va) * ep / (2.0 * pi); - sv = sin(-(va + 0.5) * pi); - v1 = -0.5 * va; - g1 = specfun::gamma2(v1); - pv = (sv + 1.0) * g1; - r = 1.0; - fac = 1.0; - - for (int m = 1; m <= 250; m++) { - vm = 0.5 * (m - va); - gm = specfun::gamma2(vm); - r = r * sq2 * x / m; - fac = -fac; - gw = fac * sv + 1.0; - r1 = gw * r * gm; - pv += r1; - if ((fabs(r1 / pv) < eps) && (gw != 0.0)) { - break; - } - } - pv *= a0; - } - return pv; - } - - template - void pbdv(T x, T v, T *dv, T *dp, T *pdf, T *pdd) { - - // ==================================================== - // Purpose: Compute parabolic cylinder functions Dv(x) - // and their derivatives - // Input: x --- Argument of Dv(x) - // v --- Order of Dv(x) - // Output: DV(na) --- Dn+v0(x) - // DP(na) --- Dn+v0'(x) - // ( na = |n|, v0 = v-n, |v0| < 1, - // n = 0,±1,±2,… ) - // PDF --- Dv(x) - // PDD --- Dv'(x) - // Routines called: - // (1) DVSA for computing Dv(x) for small |x| - // (2) DVLA for computing Dv(x) for large |x| - // ==================================================== - - int ja, k, l, m, nk, nv, na; - T xa, vh, ep, f, f0, f1, v0, v1, v2, pd, pd0, pd1, s0; - - xa = fabs(x); - vh = v; - v += copysign(1.0, v); - nv = (int) v; - v0 = v - nv; - na = abs(nv); - ep = exp(-0.25 * x * x); - ja = 0; - if (na >= 1) { - ja = 1; - } - if (v >= 0.0) { - if (v0 == 0.0) { - pd0 = ep; - pd1 = x * ep; - } else { - for (l = 0; l <= ja; l++) { - v1 = v0 + l; - if (xa <= 5.8) { - pd1 = dvsa(x, v1); - } else { - pd1 = dvla(x, v1); - } - if (l == 0) { - pd0 = pd1; - } - } - } - dv[0] = pd0; - dv[1] = pd1; - for (k = 2; k <= na; k++) { - *pdf = x * pd1 - (k + v0 - 1.0) * pd0; - dv[k] = *pdf; - pd0 = pd1; - pd1 = *pdf; - } - } else { - if (x <= 0.0) { - if (xa <= 5.8) { - pd0 = dvsa(x, v0); - v1 = v0 - 1.0; - pd1 = dvsa(x, v1); - } else { - pd0 = dvla(x, v0); - v1 = v0 - 1.0; - pd1 = dvla(x, v1); - } - dv[0] = pd0; - dv[1] = pd1; - for (k = 2; k <= na; k++) { - pd = (-x * pd1 + pd0) / (k - 1.0 - v0); - dv[k] = pd; - pd0 = pd1; - pd1 = pd; - } - } else if (x <= 2.0) { - v2 = nv + v0; - if (nv == 0) { - v2 -= 1.0; - } - nk = (int) (-v2); - f1 = dvsa(x, v2); - v1 = v2 + 1.0; - f0 = dvsa(x, v1); - dv[nk] = f1; - dv[nk - 1] = f0; - for (k = nk - 2; k >= 0; k--) { - f = x * f0 + (k - v0 + 1.0) * f1; - dv[k] = f; - f1 = f0; - f0 = f; - } - } else { - if (xa <= 5.8) { - pd0 = dvsa(x, v0); - } else { - pd0 = dvla(x, v0); - } - dv[0] = pd0; - m = 100 + na; - f1 = 0.0; - f0 = 1e-30; - f = 0.0; - for (k = m; k >= 0; k--) { - f = x * f0 + (k - v0 + 1.0) * f1; - if (k <= na) { - dv[k] = f; - } - f1 = f0; - f0 = f; - } - s0 = pd0 / f; - for (k = 0; k <= na; k++) { - dv[k] *= s0; - } - } - } - for (k = 0; k < na; k++) { - v1 = fabs(v0) + k; - if (v >= 0.0) { - dp[k] = 0.5 * x * dv[k] - dv[k + 1]; - } else { - dp[k] = -0.5 * x * dv[k] - v1 * dv[k + 1]; - } - } - *pdf = dv[na - 1]; - *pdd = dp[na - 1]; - v = vh; - return; - } - - template - void pbvv(T x, T v, T *vv, T *vp, T *pvf, T *pvd) { - - // =================================================== - // Purpose: Compute parabolic cylinder functions Vv(x) - // and their derivatives - // Input: x --- Argument of Vv(x) - // v --- Order of Vv(x) - // Output: VV(na) --- Vv(x) - // VP(na) --- Vv'(x) - // ( na = |n|, v = n+v0, |v0| < 1 - // n = 0,±1,±2,… ) - // PVF --- Vv(x) - // PVD --- Vv'(x) - // Routines called: - // (1) VVSA for computing Vv(x) for small |x| - // (2) VVLA for computing Vv(x) for large |x| - // =================================================== - - int ja, k, kv, l, m, na, nv; - T f, f0, f1, pv0, q2p, qe, s0, v0, v1, v2, vh, xa; - - const T pi = 3.141592653589793; - - xa = fabs(x); - vh = v; - v += copysign(1.0, v); - nv = (int) v; - v0 = v - nv; - na = abs(nv); - qe = exp(0.25 * x * x); - q2p = sqrt(2.0 / pi); - ja = 0; - if (na >= 1) { - ja = 1; - } - f = 0.0; - if (v <= 0.0) { - if (v0 == 0.0) { - if (xa <= 7.5) { - pv0 = vvsa(x, v0); - } else { - pv0 = vvla(x, v0); - } - f0 = q2p * qe; - f1 = x * f0; - vv[0] = pv0; - vv[1] = f0; - vv[2] = f1; - } else { - for (l = 0; l <= ja; l++) { - v1 = v0 - l; - if (xa <= 7.5) { - f1 = vvsa(x, v1); - } else { - f1 = vvla(x, v1); - } - if (l == 0) { - f0 = f1; - } - } - vv[0] = f0; - vv[1] = f1; - } - kv = 2; - if (v0 == 0.0) { - kv = 3; - } - for (k = kv; k <= na; k++) { - f = x * f1 + (k - v0 - 2.0) * f0; - vv[k] = f; - f0 = f1; - f1 = f; - } - } else { - if ((x >= 0.0) && (x <= 7.5)) { - v2 = v; - if (v2 < 1.0) { - v2 = v2 + 1.0; - } - f1 = vvsa(x, v2); - v1 = v2 - 1.0; - kv = (int) v2; - f0 = vvsa(x, v1); - vv[kv] = f1; - vv[kv - 1] = f0; - for (k = kv - 2; k >= 0; k--) { - f = x * f0 - (k + v0 + 2.0) * f1; - if (k <= na) { - vv[k] = f; - } - f1 = f0; - f0 = f; - } - } else if (x > 7.5) { - pv0 = vvla(x, v0); - m = 100 + abs(na); - vv[1] = pv0; - f1 = 0.0; - f0 = 1.0e-40; - for (k = m; k >= 0; k--) { - f = x * f0 - (k + v0 + 2.0) * f1; - if (k <= na) { - vv[k] = f; - } - f1 = f0; - f0 = f; - } - s0 = pv0 / f; - for (k = 0; k <= na; k++) { - vv[k] *= s0; - } - } else { - if (xa <= 7.5) { - f0 = vvsa(x, v0); - v1 = v0 + 1.0; - f1 = vvsa(x, v1); - } else { - f0 = vvla(x, v0); - v1 = v0 + 1.0; - f1 = vvla(x, v1); - } - vv[0] = f0; - vv[1] = f1; - for (k = 2; k <= na; k++) { - f = (x * f1 - f0) / (k + v0); - vv[k] = f; - f0 = f1; - f1 = f; - } - } - } - for (k = 0; k < na; k++) { - v1 = v0 + k; - if (v >= 0.0) { - vp[k] = 0.5 * x * vv[k] - (v1 + 1.0) * vv[k + 1]; - } else { - vp[k] = -0.5 * x * vv[k] + vv[k + 1]; - } - } - *pvf = vv[na - 1]; - *pvd = vp[na - 1]; - v = vh; - return; - } - - template - void pbwa(T a, T x, T *w1f, T *w1d, T *w2f, T *w2d) { - - // ====================================================== - // Purpose: Compute parabolic cylinder functions W(a,±x) - // and their derivatives - // Input : a --- Parameter ( 0 ≤ |a| ≤ 5 ) - // x --- Argument of W(a,±x) ( 0 ≤ |x| ≤ 5 ) - // Output : W1F --- W(a,x) - // W1D --- W'(a,x) - // W2F --- W(a,-x) - // W2D --- W'(a,-x) - // Routine called: - // CGAMA for computing complex gamma function - // ====================================================== - - int k, L1, L2; - T d[80], d1, d2, dl, f1, f2, g1, g2, h[100], h0, h1, hl, r, r1, y1d, y2d, y1f, y2f; - std::complex ug, vg; - const T eps = 1e-15; - const T p0 = 0.59460355750136; - - if (a == 0.0) { - g1 = 3.625609908222; - g2 = 1.225416702465; - } else { - ug = specfun::cgama(std::complex(0.25, 0.5 * a), 1); - g1 = std::abs(ug); - vg = specfun::cgama(std::complex(0.75, 0.5 * a), 1); - g2 = std::abs(vg); - } - f1 = sqrt(g1 / g2); - f2 = sqrt(2.0 * g2 / g1); - h0 = 1.0; - h1 = a; - h[0] = a; - for (L1 = 2; L1 <= 100; L1++) { - hl = a * h1 - 0.25 * (2 * L1 - 2.0) * (2 * L1 - 3.0) * h0; - h[L1 - 1] = hl; - h0 = h1; - h1 = hl; - } - y1f = 1.0; - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k - 1.0)); - r1 = h[k - 1] * r; - y1f += r1; - if ((fabs(r1) <= eps * fabs(y1f)) && (k > 30)) { - break; - } - } - y1d = a; - r = 1.0; - for (k = 1; k < 100; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k + 1.0)); - r1 = h[k] * r; - y1d += r1; - if ((fabs(r1) <= eps * fabs(y1d)) && (k > 30)) { - break; - } - } - y1d *= x; - d1 = 1.0; - d2 = a; - d[0] = 1.0; - d[1] = a; - for (L2 = 3; L2 <= 80; L2++) { - dl = a * d2 - 0.25 * ((2 * L2 - 1) - 2.0) * ((2 * L2 - 1) - 3.0) * d1; - d[L2 - 1] = dl; - d1 = d2; - d2 = dl; - } - y2f = 1.0; - r = 1.0; - for (k = 1; k < 80; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k + 1.0)); - r1 = d[k] * r; - y2f += r1; - if ((fabs(r1) <= eps * fabs(y2f)) && (k > 30)) { - break; - } - } - y2f *= x; - y2d = 1.0; - r = 1.0; - for (k = 1; k < 80; k++) { - r = 0.5 * r * x * x / (k * (2.0 * k - 1.0)); - r1 = d[k] * r; - y2d += r1; - if ((fabs(r1) <= eps * fabs(y2d)) && (k > 30)) { - break; - } - } - *w1f = p0 * (f1 * y1f - f2 * y2f); - *w2f = p0 * (f1 * y1f + f2 * y2f); - *w1d = p0 * (f1 * y1d - f2 * y2d); - *w2d = p0 * (f1 * y1d + f2 * y2d); - return; - } - -} // namespace detail - -/* - * If x > 0 return w1f and w1d. Otherwise set x = abs(x) and return - * w2f and -w2d. - */ -template -void pbwa(T a, T x, T &wf, T &wd) { - int flag = 0; - T w1f = 0.0, w1d = 0.0, w2f = 0.0, w2d = 0.0; - - if (x < -5 || x > 5 || a < -5 || a > 5) { - /* - * The Zhang and Jin implementation only uses Taylor series; - * return NaN outside of the range which they are accurate. - */ - wf = std::numeric_limits::quiet_NaN(); - wd = std::numeric_limits::quiet_NaN(); - set_error("pbwa", SF_ERROR_LOSS, NULL); - } else { - if (x < 0) { - x = -x; - flag = 1; - } - detail::pbwa(a, x, &w1f, &w1d, &w2f, &w2d); - if (flag) { - wf = w2f; - wd = -w2d; - } else { - wf = w1f; - wd = w1d; - } - } -} - -template -void pbdv(T v, T x, T &pdf, T &pdd) { - T *dv; - T *dp; - int num; - - if (isnan(v) || isnan(x)) { - pdf = std::numeric_limits::quiet_NaN(); - pdd = std::numeric_limits::quiet_NaN(); - } else { - /* NB. Indexing of DV/DP in specfun.f:PBDV starts from 0, hence +2 */ - num = std::abs((int) v) + 2; - dv = (T *) malloc(sizeof(T) * 2 * num); - if (dv == NULL) { - set_error("pbdv", SF_ERROR_MEMORY, "memory allocation error"); - pdf = std::numeric_limits::quiet_NaN(); - pdd = std::numeric_limits::quiet_NaN(); - } else { - dp = dv + num; - detail::pbdv(x, v, dv, dp, &pdf, &pdd); - free(dv); - } - } -} - -template -void pbvv(T v, T x, T &pvf, T &pvd) { - T *vv; - T *vp; - int num; - - if (isnan(v) || isnan(x)) { - pvf = std::numeric_limits::quiet_NaN(); - pvd = std::numeric_limits::quiet_NaN(); - } else { - /* NB. Indexing of DV/DP in specfun.f:PBVV starts from 0, hence +2 */ - num = std::abs((int) v) + 2; - vv = (T *) malloc(sizeof(T) * 2 * num); - if (vv == NULL) { - set_error("pbvv", SF_ERROR_MEMORY, "memory allocation error"); - pvf = std::numeric_limits::quiet_NaN(); - pvd = std::numeric_limits::quiet_NaN(); - } else { - vp = vv + num; - detail::pbvv(x, v, vv, vp, &pvf, &pvd); - free(vv); - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/recur.h b/scipy/special/xsf/recur.h deleted file mode 100644 index 9e9828949d7a..000000000000 --- a/scipy/special/xsf/recur.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include "config.h" - -namespace xsf { - -template -T dot(const T (&x)[N], const T (&y)[N]) { - T res = T(0); - for (size_t i = 0; i < N; ++i) { - res += x[i] * y[i]; - } - - return res; -} - -template -void forward_recur_shift_left(T (&res)[K]) { - for (size_t k = 1; k < K; ++k) { - res[k - 1] = res[k]; - } -} - -template -void forward_recur_rotate_left(T (&res)[K]) { - T tmp = res[0]; - forward_recur_shift_left(res); - res[K - 1] = tmp; -} - -/** - * Compute a forward recurrence that depends on K previous values. - * - * @param first begin iterator - * @param last end iterator - * @param r recurrence - * @param res values, initialised to the leading K values - * @param f a function to be called as f(it, res) - */ -template -void forward_recur(InputIt first, InputIt last, Recurrence r, T (&res)[K], Func f) { - InputIt it = first; - while (it - first != K && it != last) { - forward_recur_rotate_left(res); - - f(it, res); - ++it; - } - - if (last - first > K) { - while (it != last) { - T coef[K]; - r(it, coef); - - T tmp = dot(coef, res); - forward_recur_shift_left(res); - res[K - 1] = tmp; - - f(it, res); - ++it; - } - } -} - -template -void backward_recur(InputIt first, InputIt last, Recurrence r, T (&res)[K], Func f) { - InputIt it = first; - while (std::abs(it - first) != K && it != last) { - forward_recur_rotate_left(res); - - f(it, res); - --it; - } - - if (std::abs(last - first) > K) { - while (it != last) { - T coef[K]; - r(it, coef); - - T tmp = dot(coef, res); - forward_recur_shift_left(res); - res[K - 1] = tmp; - - f(it, res); - --it; - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/sici.h b/scipy/special/xsf/sici.h deleted file mode 100644 index 4d26b64e02aa..000000000000 --- a/scipy/special/xsf/sici.h +++ /dev/null @@ -1,200 +0,0 @@ -/* 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 z, - std::complex *s, std::complex *c) { - /* DLMF 6.6.5 and 6.6.6. If sgn = -1 computes si/ci, and if sgn = 1 - * computes shi/chi. - */ - std::complex fac = z; - *s = fac; - *c = 0; - std::complex term1, term2; - for (int n = 1; n < 100; n++) { - fac *= static_cast(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::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 z, - std::complex *si, std::complex *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::infinity()) { - *si = M_PI_2; - *ci = 0; - return 0; - } - if (z == -std::numeric_limits::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::infinity(), std::numeric_limits::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 jz = std::complex(0.0, 1.0) * z; - std::complex term1 = expi(jz); - std::complex term2 = expi(-jz); - *si = std::complex(0.0, -0.5)*(term1 - term2); - *ci = 0.5*(term1 + term2); - if (z.real() == 0) { - if (z.imag() > 0) { - *ci += std::complex(0.0, M_PI_2); - } else if (z.imag() < 0) { - *ci -= std::complex(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(0.0, M_PI); - } else { - *ci -= std::complex(0.0, M_PI); - } - } - return 0; -} - -XSF_HOST_DEVICE inline int sici(std::complex z, - std::complex *si_f, std::complex *ci_f) { - std::complex si; - std::complex ci; - int res = sici(z, &si, &ci); - *si_f = si; - *ci_f = ci; - return res; -} - -XSF_HOST_DEVICE inline int shichi(std::complex z, - std::complex *shi, std::complex *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::infinity()) { - *shi = std::numeric_limits::infinity(); - *chi = std::numeric_limits::infinity(); - return 0; - } - if (z == -std::numeric_limits::infinity()) { - *shi = -std::numeric_limits::infinity(); - *chi = std::numeric_limits::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::infinity(), std::numeric_limits::quiet_NaN()}; - } else { - *chi += EULER + std::log(z); - } - return 0; - } - - std::complex term1 = expi(z); - std::complex term2 = expi(-z); - *shi = 0.5*(term1 - term2); - *chi = 0.5*(term1 + term2); - if (z.imag() > 0) { - *shi -= std::complex(0.0, 0.5*M_PI); - *chi += std::complex(0.0, 0.5*M_PI); - } else if (z.imag() < 0) { - *shi += std::complex(0.0, 0.5*M_PI); - *chi -= std::complex(0.0, 0.5*M_PI); - } else if (z.real() < 0) { - *chi += std::complex(0.0, M_PI); - } - return 0; -} - -XSF_HOST_DEVICE inline int shichi(std::complex z, - std::complex *shi_f, std::complex *chi_f) { - std::complex shi; - std::complex 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; -} -} diff --git a/scipy/special/xsf/specfun.h b/scipy/special/xsf/specfun.h deleted file mode 100644 index 305416f3e9b4..000000000000 --- a/scipy/special/xsf/specfun.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -#include "error.h" -#include "specfun/specfun.h" - -#define SPECFUN_ZCONVINF(func, z) \ - do { \ - if ((double) (z).real() == (double) 1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (z).real(std::numeric_limits::infinity()); \ - } \ - if ((double) (z).real() == (double) -1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (z).real(-std::numeric_limits::infinity()); \ - } \ - } while (0) -#define SPECFUN_CONVINF(func, x) \ - do { \ - if ((double) (x) == (double) 1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (x) = std::numeric_limits::infinity(); \ - } \ - if ((double) (x) == (double) -1.0e300) { \ - set_error(func, SF_ERROR_OVERFLOW, NULL); \ - (x) = -std::numeric_limits::infinity(); \ - } \ - } while (0) - -namespace xsf { - -inline std::complex chyp2f1(double a, double b, double c, std::complex z) { - int l0 = ((c == floor(c)) && (c < 0)); - int l1 = ((fabs(1 - z.real()) < 1e-15) && (z.imag() == 0) && (c - a - b <= 0)); - if (l0 || l1) { - set_error("chyp2f1", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::infinity(); - } - - int isfer = 0; - std::complex outz = specfun::hygfz(a, b, c, z, &isfer); - if (isfer == 3) { - set_error("chyp2f1", SF_ERROR_OVERFLOW, NULL); - outz.real(std::numeric_limits::infinity()); - outz.imag(0.0); - } else if (isfer == 5) { - set_error("chyp2f1", SF_ERROR_LOSS, NULL); - } else if (isfer != 0) { - set_error("chyp2f1", static_cast(isfer), NULL); - outz.real(std::numeric_limits::quiet_NaN()); - outz.imag(std::numeric_limits::quiet_NaN()); - } - return outz; -} - -inline std::complex chyp1f1(double a, double b, std::complex z) { - std::complex outz = specfun::cchg(a, b, z); - if (outz.real() == 1e300) { - set_error("chyp1f1", SF_ERROR_OVERFLOW, NULL); - outz.real(std::numeric_limits::infinity()); - } - - return outz; -} - -inline double hypu(double a, double b, double x) { - double out; - int md; /* method code --- not returned */ - int isfer = 0; - - out = specfun::chgu(x, a, b, &md, &isfer); - if (out == 1e300) { - set_error("hypU", SF_ERROR_OVERFLOW, NULL); - out = std::numeric_limits::infinity(); - } - if (isfer == 6) { - set_error("hypU", SF_ERROR_NO_RESULT, NULL); - out = std::numeric_limits::quiet_NaN(); - } else if (isfer != 0) { - set_error("hypU", static_cast(isfer), NULL); - out = std::numeric_limits::quiet_NaN(); - } - return out; -} - -inline double hyp1f1(double a, double b, double x) { - double outy; - - outy = specfun::chgm(x, a, b); - return outy; -} - -inline std::complex cerf(std::complex z) { return specfun::cerror(z); } - -inline double pmv(double m, double v, double x) { - int int_m; - double out; - - if (m != floor(m)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - out = specfun::lpmv(x, int_m, v); - SPECFUN_CONVINF("pmv", out); - return out; -} - -} // namespace special diff --git a/scipy/special/xsf/specfun/specfun.h b/scipy/special/xsf/specfun/specfun.h deleted file mode 100644 index dd90091082a7..000000000000 --- a/scipy/special/xsf/specfun/specfun.h +++ /dev/null @@ -1,5894 +0,0 @@ -/* - * - * This file accompanied with the header file specfun.h is a partial - * C translation of the Fortran code by Zhang and Jin following - * original description: - * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * COMPUTATION OF SPECIAL FUNCTIONS - * - * Shanjie Zhang and Jianming Jin - * - * Copyrighted but permission granted to use code in programs. - * Buy their book: - * - * Shanjie Zhang, Jianming Jin, - * Computation of Special Functions, - * Wiley, 1996, - * ISBN: 0-471-11963-6, - * LC: QA351.C45. - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * - * Scipy changes: - * - Compiled into a single source file and changed REAL To DBLE throughout. - * - Changed according to ERRATA. - * - Changed GAMMA to GAMMA2 and PSI to PSI_SPEC to avoid potential conflicts. - * - Made functions return sf_error codes in ISFER variables instead - * of printing warnings. The codes are - * - SF_ERROR_OK = 0: no error - * - SF_ERROR_SINGULAR = 1: singularity encountered - * - SF_ERROR_UNDERFLOW = 2: floating point underflow - * - SF_ERROR_OVERFLOW = 3: floating point overflow - * - SF_ERROR_SLOW = 4: too many iterations required - * - SF_ERROR_LOSS = 5: loss of precision - * - SF_ERROR_NO_RESULT = 6: no result obtained - * - SF_ERROR_DOMAIN = 7: out of domain - * - SF_ERROR_ARG = 8: invalid input parameter - * - SF_ERROR_OTHER = 9: unclassified error - * - Improved initial guesses for roots in JYZO. - * - * - */ - -/* - * Copyright (C) 2024 SciPy developers - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * a. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * b. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * c. Names of the SciPy Developers may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS - * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF - * THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include -#include "../config.h" - -namespace xsf { -namespace specfun { - -// The Status enum is the return type of a few private, low-level functions -// defined here. Currently the only use is by functions that allocate -// memory internally. If the allocation fails, the function returns -// Status::NoMemory. - -enum class Status { - OK = 0, - NoMemory, - Other -}; - -void airyb(double, double*, double*, double*, double*); -void bjndd(double, int, double *, double *, double *); - -void cerzo(int, std::complex *); - -void cyzo(int, int, int, std::complex*, std::complex *); - -void cerf(std::complex, std::complex *, std::complex *); -std::complex cgama(std::complex, int); -double chgubi(double, double, double, int *); -double chguit(double, double, double, int *); -double chgul(double, double, double, int *); -double chgus(double, double, double, int *); -void cpbdn(int, std::complex, std::complex *, std::complex *); -std::complex cpdla(int, std::complex); -std::complex cpdsa(int, std::complex); -double cv0(double, double, double); -double cvf(int, int, double, double, int); -double cvql(int, int, double); -double cvqm(int, double); -double gaih(double); -double gam0(double); -double gamma2(double); - -template -void jynbh(int, int, T, int *, T *, T *); - -void jyndd(int, double, double *, double *, double *, double *, double *, double *); - -double lpmv0(double, int, double); -int msta1(double, int); -int msta2(double, int, int); -double psi_spec(double); -double refine(int, int, double, double); - -template -void sckb(int, int, T, T *, T *); - -template -Status sdmn(int, int, T, T, int, T *); - -template -void sphj(T, int, int *, T *, T *); - -template -void sphy(T, int, int *, T *, T *); - -template -Status aswfa(T x, int m, int n, T c, int kd, T cv, T *s1f, T *s1d) { - - // =========================================================== - // Purpose: Compute the prolate and oblate spheroidal angular - // functions of the first kind and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,... - // c --- Spheroidal parameter - // x --- Argument of angular function, |x| < 1.0 - // KD --- Function code - // KD=1 for prolate; KD=-1 for oblate - // cv --- Characteristic value - // Output: S1F --- Angular function of the first kind - // S1D --- Derivative of the angular function of - // the first kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routine called: - // SDMN for computing expansion coefficients df - // SCKB for computing expansion coefficients ck - // =========================================================== - - int ip, k, nm, nm2; - T a0, d0, d1, r, su1, su2, x0, x1; - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto df = std::unique_ptr{new (std::nothrow) T[200]()}; - if (ck == nullptr || df == nullptr) { - return Status::NoMemory; - } - const T eps = 1e-14; - x0 = x; - x = fabs(x); - ip = ((n-m) % 2 == 0 ? 0 : 1); - nm = 40 + (int)((n-m)/2 + c); - nm2 = nm/2 - 2; - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - sckb(m, n, c, df.get(), ck.get()); - x1 = 1.0 - x*x; - if ((m == 0) && (x1 == 0.0)) { - a0 = 1.0; - } else { - a0 = pow(x1, 0.5*m); - } - su1 = ck[0]; - for (k = 1; k <= nm2; k++) { - r = ck[k]*pow(x1, k); - su1 += r; - if ((k >= 10) && (fabs(r/su1) < eps)) { break; } - } - *s1f = a0*pow(x, ip)*su1; - if (x == 1.0) { - if (m == 0) { - *s1d = ip*ck[0] - 2.0*ck[1]; - } else if (m == 1) { - *s1d = -1e100; - } else if (m == 2) { - *s1d = -2.0*ck[0]; - } else if (m >= 3) { - *s1d = 0.0; - } - } else { - d0 = ip - m/x1*pow(x, ip+1.0); - d1 = -2.0*a0*pow(x, ip+1.0); - su2 = ck[1]; - for (k = 2; k <= nm2; k++) { - r = k*ck[k]*pow(x1, (k-1.0)); - su2 += r; - if ((k >= 10) && (fabs(r/su2) < eps)) { break; } - } - *s1d = d0*a0*su1 + d1*su2; - } - if ((x0 < 0.0) && (ip == 0)) { *s1d = -*s1d; } - if ((x0 < 0.0) && (ip == 1)) { *s1f = -*s1f; } - x = x0; - return Status::OK; -} - - -inline void bernob(int n, double *bn) { - - // ====================================== - // Purpose: Compute Bernoulli number Bn - // Input : n >= 3 --- Serial number - // Output: BN(n) --- Bn - // ====================================== - - int k, m; - double r1, r2, s; - const double tpi = 6.283185307179586; - - bn[0] = 1.0; - bn[1] = -0.5; - bn[2] = 1.0 / 6.0; - r1 = pow(2.0 / tpi, 2); - for ( m = 4; m < (n+1); m += 2) { - r1 = -r1 * (m-1)*m/(tpi*tpi); - r2 = 1.0; - for (k = 2; k < 10001; k++) { - s = pow(1.0/k, m); - r2 += s; - if (s < 1e-15) { break; } - } - bn[m] = r1*r2; - } - return; -} - - -inline void bjndd(double x, int n, double *bj, double *dj, double *fj) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x) and their - // first and second derivatives ( 0 <= n <= 100) - // Input: x --- Argument of Jn(x) ( x ≥ 0 ) - // n --- Order of Jn(x) - // Output: BJ(n+1) --- Jn(x) - // DJ(n+1) --- Jn'(x) - // FJ(n+1) --- Jn"(x) - // ===================================================== - - int k, m, mt; - double bs = 0.0, f = 0.0, f0 = 0.0, f1 = 1e-35; - - for (m = 1; m < 901; m++) { - mt = (int)(0.5*log10(6.28*m)-m*log10(1.36*fabs(x)/m)); - if (mt > 20) { break; } - } - if (m == 901) { m -= 1; } - for (k = m; k > -1; k--) { - f = 2.0*(k+1.0)*f1/x - f0; - if (k <= n) { bj[k] = f; } - if (k % 2 == 0) { bs += 2.0*f; } - f0 = f1; - f1 = f; - } - for (k = 0; k < (n+1); k++) { - bj[k] /= (bs - f); - } - dj[0] = -bj[1]; - fj[0] = -bj[0] - dj[0]/x; - for (k = 1; k < (n+1); k++) { - dj[k] = bj[k-1] - k*bj[k]/x; - fj[k] = (k*k/(x*x)-1.0)*bj[k] - dj[k]/x; - } - return; -} - - -template -Status cbk(int m, int n, T c, T cv, T qt, T *ck, T *bk) { - - // ========================================================== - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================== - - const T eps = 1.0e-14; - - int i, i1, ip, j, k, n2, nm; - T r1, s1, sw, t; - - ip = ((n - m) % 2 == 0 ? 0 : 1); - nm = 25 + (int)(0.5 * (n - m) + c); - - auto u = std::unique_ptr{new (std::nothrow) T[200]()}; - auto v = std::unique_ptr{new (std::nothrow) T[200]()}; - auto w = std::unique_ptr{new (std::nothrow) T[200]()}; - if (u.get() == nullptr || v.get() == nullptr || w.get() == nullptr) { - return Status::NoMemory; - } - - u[0] = 0.0; - n2 = nm - 2; - - for (j = 1; j < n2 + 1; j++) { - u[j] = c * c; - } - for (j = 1; j < n2 + 1; j++) { - v[j - 1] = (2.0 * j - 1.0 - ip) * (2.0 * (j - m) - ip) + m * (m - 1.0) - cv; - } - for (j = 1; j < nm; j++) - w[j - 1] = (2.0 * j - ip) * (2.0 * j + 1.0 - ip); - - if (ip == 0) { - sw = 0.0; - for (k = 0; k < n2; k++) { - s1 = 0.0; - i1 = k - m + 1; - - for (i = i1; i < nm + 1; i++) { - if (i < 0) { continue; } - r1 = 1.0; - for (j = 1; j <= k; j++) { - r1 = r1*(i + m - j) / (1.0*j); - } - s1 += ck[i] * (2.0 * i + m) * r1; - if (fabs(s1 - sw) < fabs(s1) * eps) { break; } - sw = s1; - } - bk[k] = qt * s1; - } - } else if (ip == 1) { - sw = 0.0; - for (k = 0; k < n2; k++) { - s1 = 0.0; - i1 = k - m + 1; - - for (int i = i1; i < nm + 1; i++) { - if (i < 0) { continue; } - r1 = 1.0; - for (j = 1; j <= k; j++) { - r1 = r1* (i + m - j) / (1.0*j); - } - if (i > 0) { - s1 += ck[i - 1] * (2.0 * i + m - 1) * r1; - } - s1 -= ck[i] * (2.0 * i + m) * r1; - - if (fabs(s1 - sw) < fabs(s1) * eps) { break; } - sw = s1; - } - bk[k] = qt * s1; - } - } - - w[0] /= v[0]; - bk[0] /= v[0]; - - for (k = 2; k <= n2; k++) { - t = v[k - 1] - w[k - 2] * u[k - 1]; - w[k - 1] /= t; - bk[k - 1] = (bk[k - 1] - bk[k - 2] * u[k - 1]) / t; - } - - for (k = n2 - 1; k >= 1; k--) { - bk[k - 1] -= w[k - 1] * bk[k]; - } - return Status::OK; -} - - -inline void cerf(std::complex z, std::complex *cer, std::complex *cder) { - - // ========================================================== - // Purpose: Compute complex Error function erf(z) & erf'(z) - // Input: z --- Complex argument of erf(z) - // x --- Real part of z - // y --- Imaginary part of z - // Output: CER --- erf(z) - // CDER --- erf'(z) - // ========================================================== - - int k; - double c0, cs, er0, er, er1, ei1, er2, ei2, err, eri, r, ss, w, w1, w2; - const double eps = 1.0e-12; - const double pi = 3.141592653589793; - - double x = z.real(); - double y = z.imag(); - double x2 = x * x; - - if (x <= 3.5) { - er = 1.0; - r = 1.0; - w = 0.0; - - for (k = 1; k <= 100; k++) { - r = r * x2 / (k + 0.5); - er += r; - if (fabs(er - w) <= eps * fabs(er)) - break; - w = er; - } - - c0 = 2.0 / sqrt(pi) * x * exp(-x2); - er0 = c0 * er; - *cer = er0; - } else { - er = 1.0; - r = 1.0; - - for (k = 1; k <= 12; k++) { - r = -r * (k - 0.5) / x2; - er += r; - } - - c0 = exp(-x2) / (x * sqrt(pi)); - er0 = 1.0 - c0 * er; - *cer = er0; - } - - if (y == 0.0) { - err = cer->real(); - eri = 0.0; - *cer = std::complex(err, eri); - } else { - cs = cos(2.0 * x * y); - ss = sin(2.0 * x * y); - er1 = exp(-x2) * (1.0 - cs) / (2.0 * pi * x); - ei1 = exp(-x2) * ss / (2.0 * pi * x); - er2 = 0.0; - w1 = 0.0; - - for (int n = 1; n <= 100; n++) { - er2 += exp(-0.25 * n * n) / (n * n + 4.0 * x2) * (2.0 * x - 2.0 * x * cosh(n * y) * cs + n * sinh(n * y) * ss); - if (fabs((er2 - w1) / er2) < eps) - break; - w1 = er2; - } - - c0 = 2.0 * exp(-x2) / pi; - err = cer->real() + er1 + c0 * er2; - ei2 = 0.0; - w2 = 0.0; - - for (int n = 1; n <= 100; n++) { - ei2 += exp(-0.25 * n * n) / (n * n + 4.0 * x2) * (2.0 * x * cosh(n * y) * ss + n * sinh(n * y) * cs); - if (fabs((ei2 - w2) / ei2) < eps) - break; - w2 = ei2; - } - *cer = std::complex(err, ei1 + c0 * ei2); - } - *cder = 2.0 / sqrt(pi) * std::exp(-z*z); - -} - - -inline std::complex cerror(std::complex z) { - - // ==================================================== - // Purpose: Compute error function erf(z) for a complex - // argument (z=x+iy) - // Input : z --- Complex argument - // Output: CER --- erf(z) - // ==================================================== - - int k; - std::complex cer, cl, cr, cs, z1; - std::complex c0 = std::exp(-z*z); - const double sqpi = 1.7724538509055160273; - z1 = z; - if (z.real() < 0.0) { z1 = -z; } - // Cutoff radius R = 4.36; determined by balancing rounding error - // and asymptotic expansion error, see below. - // - // The resulting maximum global accuracy expected is around 1e-8 - // - if (std::abs(z) <= 4.36) { - // Rounding error in the Taylor expansion is roughly - // ~ R*R * EPSILON * R**(2 R**2) / (2 R**2 Gamma(R**2 + 1/2)) - cs = z1; - cr = z1; - for (k = 1; k < 121; k++) { - cr = cr*(z1*z1) / (k+0.5); - cs += cr; - if (std::abs(cr/cs) < 1e-15) { break; } - } - cer = 2.0*c0*cs/sqpi; - } else { - cl = 1.0 / z1; - cr = cl; - // Asymptotic series; maximum K must be at most ~ R^2. - // - // The maximum accuracy obtainable from this expansion is roughly - // - // ~ Gamma(2R**2 + 2) / ( - // (2 R**2)**(R**2 + 1/2) Gamma(R**2 + 3/2) 2**(R**2 + 1/2)) - for (k = 1; k < 21; k++) { - cr = -cr*(k-0.5) / (z1*z1); - cl += cr; - if (std::abs(cr/cl) < 1e-15) { break; } - } - cer = 1.0 - c0*cl/sqpi; - } - if (z.real() < 0.0) { cer = -cer; } - return cer; -} - - -inline void cerzo(int nt, std::complex *zo) { - - // =============================================================== - // Purpose : Evaluate the complex zeros of error function erf(z) - // using the modified Newton's iteration method - // Input : NT --- Total number of zeros - // Output: ZO(L) --- L-th zero of erf(z), L=1,2,...,NT - // Routine called: CERF for computing erf(z) and erf'(z) - // =============================================================== - - int i, j, nr, it = 0; - double pu, pv, px, py, w0; - std::complex z, zf, zd, zp, zw, zq, zfd, zgd; - double w = 0.0; - const double pi = 3.141592653589793; - - for (nr = 1; nr <= nt; nr++) { - pu = sqrt(pi * (4.0 * nr - 0.5)); - pv = pi * sqrt(2.0 * nr - 0.25); - px = 0.5 * pu - 0.5 * log(pv) / pu; - py = 0.5 * pu + 0.5 * log(pv) / pu; - z = std::complex(px, py); - it = 0; - - do { - it++; - cerf(z, &zf, &zd); - zp = 1.0; - - for (i = 1; i < nr; i++) { - zp *= (z - zo[i - 1]); - } - zfd = zf / zp; - zq = 0.0; - for (i = 1; i < nr; i++) { - zw = 1.0; - for (j = 1; j < nr; j++) { - if (j == i) continue; - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-11)); - zo[nr - 1] = z; - } - return; -} - -inline std::complex cchg(double a, double b, std::complex z) { - - // =================================================== - // Purpose: Compute confluent hypergeometric function - // M(a,b,z) with real parameters a, b and a - // complex argument z - // Input : a --- Parameter - // b --- Parameter - // z --- Complex argument - // Output: CHG --- M(a,b,z) - // Routine called: CGAMA for computing complex ln[Г(x)] - // =================================================== - - int i, j, k, la, m, n, nl, ns; - double a0, a1, phi, x0, x, y; - std::complex cfac, cg1, cg2, cg3, chg, chg1, chg2, chw, cr, cr1, cr2, cs1, - cs2, crg, cy0, cy1, z0; - const double pi = 3.141592653589793; - const std::complex ci(0.0, 1.0); - a0 = a; - a1 = a; - z0 = z; - cy0 = 0.0; - cy1 = 0.0; - if ((b == 0.0) || (b == -(int)fabs(b))) { return 1e300; } - if ((a == 0.0) || (z == 0.0)) { return 1.0; } - if (a == -1.0) { return 1.0 - z/b; } - if (a == b) { return std::exp(z); } - if (a - b == 1.0) { return (1.0 + z/b)*std::exp(z); } - if ((a == 1.0) && (b == 2.0)) { return (std::exp(z)-1.0) / z; } - if ((a == (int)a) && (a < 0.0)) { - m = (int)(-a); - cr = 1.0; - chg = 1.0; - for (k = 1; k < (m+1); k++) { - cr = cr * (a+k-1.0)/static_cast(k)/(b+k-1.0)*z; - chg += cr; - } - } else { - x0 = z.real(); - if (x0 < 0.0) { - a = b-a; - a0 = a; - z = -z; - } - nl = 0; - la = 0; - if (a >= 2.0) { - nl = 1; - la = (int)a; - a -= la + 1; - } - ns = 0; - for (n = 0; n < (nl+1); n++) { - if (a0 >= 2.0) { a += 1.0; } - if ((std::abs(z) < 20.0+fabs(b)) || (a < 0.0)) { - chg = 1.0; - chw = 0.0; - crg = 1.0; - for (j = 1; j < 501; j++) { - crg = crg * (a+j-1.0)/(j*(b+j-1.0))*z; - chg += crg; - if (std::abs((chg-chw)/chg) < 1e-15) { break; } - chw = chg; - } - } else { - y = 0.0; - cg1 = cgama(a, 0); - cg2 = cgama(b, 0); - cg3 = cgama(b-a, 0); - cs1 = 1.0; - cs2 = 1.0; - cr1 = 1.0; - cr2 = 1.0; - for (i = 1; i <= 8; i++) { - cr1 = -cr1 * (a+i-1.0)*(a-b+i)/(z*static_cast(i)); - cr2 = cr2 * (b-a+i-1.0)*(i-a)/(z*static_cast(i)); - cs1 += cr1; - cs2 += cr2; - } - x = z.real(); - y = z.imag(); - if ((x == 0.0) && (y >= 0.0)) { - phi = 0.5*pi; - } else if ((x == 0.0) && (y <= 0.0)) { - phi = -0.5*pi; - } else { - phi = atan(y/x); - } - if ((phi > -0.5*pi) && (phi < 1.5*pi)) { ns = 1; } - if ((phi > -1.5*pi) && (phi <= -0.5*pi)) { ns = -1; } - cfac = std::exp(static_cast(ns)*ci*pi*a); - if (y == 0.0) { cfac = cos(pi*a); } - chg1 = std::exp(cg2-cg3)*std::pow(z, -a)*cfac*cs1; - chg2 = std::exp(cg2-cg1+z)*std::pow(z, a-b)*cs2; - chg = chg1 + chg2; - } - if (n == 0) { cy0 = chg; } - if (n == 1) { cy1 = chg; } - } - if (a0 >= 2.0) { - for (i = 1; i < la; i++) { - chg = ((2.0*a-b+z)*cy1 + (b-a)*cy0)/a; - cy0 = cy1; - cy1 = chg; - a += 1.0; - } - } - if (x0 < 0.0) { chg *= std::exp(-z); } - } - a = a1; - z = z0; - return chg; -} - - -inline std::complex cgama(std::complex z, int kf) { - - // ========================================================= - // Purpose: Compute the gamma function Г(z) or ln[Г(z)] - // for a complex argument - // Input : z --- Complex argument - // kf --- Function code - // kf=0 for ln[Г(z)] - // kf=1 for Г(z) - // Output: g --- ln[Г(z)] or Г(z) - // ======================================================== - - std::complex g, z1; - double az0, az1, gi, gi1, gr, gr1, t, th, th1, th2, sr, si, x0, xx, yy; - int j, k, na; - const double pi = 3.141592653589793; - static const double a[10] = { - 8.333333333333333e-02, -2.777777777777778e-03, - 7.936507936507937e-04, -5.952380952380952e-04, - 8.417508417508418e-04, -1.917526917526918e-03, - 6.410256410256410e-03, -2.955065359477124e-02, - 1.796443723688307e-01, -1.392432216905900e+00 - }; - xx = z.real(); - yy = z.imag(); - if ((yy == 0.0) && (xx <= 0.0) && (xx == (int)xx)) { - return 1e300; - } else if (xx < 0.0) { - z1 = z; - z = -z; - xx = -xx; - yy = -yy; - } else { - z1 = std::complex(xx, 0.0); - } - x0 = xx; - na = 0; - if (xx <= 7.0) { - na = (int)(7 - xx); - x0 = xx + na; - } - az0 = std::abs(std::complex(x0, yy)); - th = atan(yy / x0); - gr = (x0 - 0.5)*log(az0) - th*yy - x0 + 0.5*log(2.0*pi); - gi = th*(x0 - 0.5) + yy*log(az0) - yy; - for (k = 1; k < 11; k++) { - t = pow(az0, 1-2*k); - gr += a[k - 1]*t*cos((2.0*k - 1.0)*th); - gi += -a[k - 1]*t*sin((2.0*k - 1.0)*th); - } - if (xx <= 7.0) { - gr1 = 0.0; - gi1 = 0.0; - for (j = 0; j < na; j++) { - gr1 += 0.5*log(pow(xx + j, 2) + yy*yy); - gi1 += atan(yy/(xx + j)); - } - gr -= gr1; - gi -= gi1; - } - if (z1.real() < 0.0) { - az0 = std::abs(z); - th1 = atan(yy/xx); - sr = -sin(pi*xx)*cosh(pi*yy); - si = -cos(pi*xx)*sinh(pi*yy); - az1 = std::abs(std::complex(sr, si)); - th2 = atan(si/sr); - if (sr < 0.0) { - th2 += pi; - } - gr = log(pi/(az0*az1)) - gr; - gi = - th1 - th2 - gi; - z = z1; - } - if (kf == 1) { - g = exp(gr)*std::complex(cos(gi), sin(gi)); - } else { - g = std::complex(gr, gi); - } - return g; -} - - -inline double chgm(double x, double a, double b) { - - // =================================================== - // Purpose: Compute confluent hypergeometric function - // M(a,b,x) - // Input : a --- Parameter - // b --- Parameter ( b <> 0,-1,-2,... ) - // x --- Argument - // Output: HG --- M(a,b,x) - // Routine called: CGAMA for computing complex ln[Г(x)] - // =================================================== - - int i, j, la, n, nl; - double a0 = a, a1 = a, x0 = x, y0, y1, hg1, hg2, r1, r2, rg, xg, sum1, sum2; - std::complex cta, ctb, ctba; - const double pi = 3.141592653589793; - double hg = 0.0; - - // DLMF 13.2.39 - if (x < 0.0) { - a = b - a; - a0 = a; - x = fabs(x); - } - nl = 0; - la = 0; - if (a >= 2.0) { - // preparing terms for DLMF 13.3.1 - nl = 1; - la = (int)a; - a -= la+1; - } - y0 = 0.0; - y1 = 0.0; - for (n = 0; n < (nl + 1); n++) { - if (a0 >= 2.0) { a += 1.0; } - if ((x <= 30.0 + fabs(b)) || (a < 0.0)) { - hg = 1.0; - rg = 1.0; - for (j = 1; j < 501; j++) { - rg = rg * (a + j - 1.0) / (j*(b + j - 1.0))*x; - hg += rg; - if ((hg != 0.0) && (fabs(rg/hg) < 1e-15)) { - // DLMF 13.2.39 (cf. above) - if (x0 < 0.0) { hg *= exp(x0); } - break; - } - } - } else { - // DLMF 13.7.2 & 13.2.4, SUM2 corresponds to first sum - cta = cgama(a, 0); - ctb = cgama(b, 0); - xg = b-a; - ctba = cgama(xg, 0); - sum1 = 1.0; - sum2 = 1.0; - r1 = 1.0; - r2 = 1.0; - for (i = 1; i < 9; i++) { - r1 = -r1*(a+i-1.0)*(a-b+i)/(x*i); - r2 = -r2*(b-a+i-1.0)*(a-i)/(x*i); - sum1 += r1; - sum2 += r2; - } - if (x0 >= 0.0) { - hg1 = (std::exp(ctb-ctba)).real()*pow(x, -a)*cos(pi*a)*sum1; - hg2 = (std::exp(ctb-cta+x)).real()*pow(x, a-b)*sum2; - } else { - // DLMF 13.2.39 (cf. above) - hg1 = (std::exp(ctb-ctba+x0)).real()*pow(x, -a)*cos(pi*a)*sum1; - hg2 = (std::exp(ctb-cta)).real()*pow(x, a-b)*sum2; - } - hg = hg1 + hg2; - } - /* 25 */ - if (n == 0) { y0 = hg; } - if (n == 1) { y1 = hg; } - } - if (a0 >= 2.0) { - // DLMF 13.3.1 - for (i = 1; i < la; i++) { - hg = ((2.0*a - b + x)*y1 + (b - a)*y0) / a; - y0 = y1; - y1 = hg; - a += 1.0; - } - } - a = a1; - x = x0; - return hg; -} - - -inline double chgu(double x, double a, double b, int *md, int *isfer) { - - // ======================================================= - // Purpose: Compute the confluent hypergeometric function - // U(a,b,x) - // Input : a --- Parameter - // b --- Parameter - // x --- Argument ( x > 0 ) - // Output: HU --- U(a,b,x) - // MD --- Method code - // ISFER --- Error flag - // Routines called: - // (1) CHGUS for small x ( MD=1 ) - // (2) CHGUL for large x ( MD=2 ) - // (3) CHGUBI for integer b ( MD=3 ) - // (4) CHGUIT for numerical integration ( MD=4 ) - // ======================================================= - - int il1, il2, il3, bl1, bl2, bl3, bn, id1 = 0, id; - double aa, hu = 0.0, hu1, b00; - - aa = a - b + 1.0; - *isfer = 0; - il1 = (a == (int)a) && (a <= 0.0); - il2 = (aa == (int)aa) && (aa <= 0.0); - il3 = fabs(a*(a-b+1.0))/x <= 2.0; - bl1 = (x <= 5.0) || (x <= 10.0 && a <= 2.0); - bl2 = (x > 5.0) && (x <= 12.5) && ((a >= 1.0) && (b >= a+4.0)); - bl3 = (x > 12.5) && (a >= 5.0) && (b >= a + 5.0); - bn = (b == (int)b) && (b != 0.0); - - id = -100; - hu1 = 0.0; - if (b != (int)b) { - hu = chgus(x, a, b, &id1); - *md = 1; - if (id1 >= 9) { return hu; } - hu1 = hu; - } - if (il1 || il2 || il3) { - hu = chgul(x, a, b, &id); - *md = 2; - if (id >= 9) { return hu; } - if (id1 > id) { - *md = 1; - id = id1; - hu = hu1; - } - } - if (a >= 1.0) { - if (bn && (bl1 || bl2 || bl3)) { - hu = chgubi(x, a, b, &id); - *md = 3; - } else { - hu = chguit(x, a, b, &id); - *md = 4; - } - } else { - if (b <= a) { - b00 = b; - a -= b - 1.0; - b = 2.0 - b; - hu = chguit(x, a, b, &id); - hu *= pow(x, 1.0 - b00); - *md = 4; - } else if (bn && (~il1)) { - hu = chgubi(x, a, b, &id); - *md = 3; - } - } - if (id < 6) { *isfer = 6; } - return hu; -} - - -inline double chgubi(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute confluent hypergeometric function - // U(a,b,x) with integer b ( b = ±1,±2,... ) - // Input : a --- Parameter - // b --- Parameter - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // Routines called: - // (1) GAMMA2 for computing gamma function Г(x) - // (2) PSI_SPEC for computing psi function - // ====================================================== - - int id1, id2, j, k, m, n; - double a0, a1, a2, da1, da2, db1, db2, ga, ga1, h0, hm1, hm2, hm3,\ - hmax, hmin, hu, hu1, hu2, hw, ps, r, rn, rn1, s0, s1, s2,\ - sa, sb, ua, ub; - const double el = 0.5772156649015329; - - *id = -100; - n = (int)fabs(b-1); - rn1 = 1.0; - rn = 1.0; - for (j = 1; j <= n; j++) { - rn *= j; - if (j == n-1) { - rn1 = rn; - } - } - ps = psi_spec(a); - ga = gamma2(a); - if (b > 0.0) { - a0 = a; - a1 = a - n; - a2 = a1; - ga1 = gamma2(a1); - ua = pow(-1, n-1) / (rn * ga1); - ub = rn1 / ga * pow(x, -n); - } else { - a0 = a + n; - a1 = a0; - a2 = a; - ga1 = gamma2(a1); - ua = pow(-1, n-1) / (rn * ga) * pow(x, n); - ub = rn1 / ga1; - } - hm1 = 1.0; - r = 1.0; - hmax = 0.0; - hmin = 1e300; - h0 = 0.0; - for (k = 1; k <= 150; k++) { - r = r * (a0 + k - 1) * x / ((n + k) * k); - hm1 += r; - hu1 = fabs(hm1); - - if (hu1 > hmax) { - hmax = hu1; - } - if (hu1 < hmin) { - hmin = hu1; - } - if (fabs(hm1 - h0) < fabs(hm1) * 1.0e-15) { break; } - h0 = hm1; - } - - da1 = log10(hmax); - da2 = 0; - - if (hmin != 0) { - da2 = log10(hmin); - } - - *id = 15 - (int)fabs(da1 - da2); - hm1 *= log(x); - s0 = 0; - - for (m = 1; m <= n; m++) { - if (b >= 0) { - s0 -= 1.0 / m; - } - if (b < 0) { - s0 += (1.0 - a) / (m * (a + m - 1)); - } - } - - hm2 = ps + 2 * el + s0; - r = 1; - hmax = 0; - hmin = 1.0e+300; - - for (k = 1; k <= 150; k++) { - s1 = 0; - s2 = 0; - - if (b > 0) { - for (m = 1; m <= k; m++) { - s1 -= (m + 2 * a - 2) / (m * (m + a - 1)); - } - for (m = 1; m <= n; m++) { - s2 += 1.0 / (k + m); - } - } else { - for (m = 1; m <= k + n; m++) { - s1 += (1.0 - a) / (m * (m + a - 1)); - } - for (m = 1; m <= k; m++) { - s2 += 1.0 / m; - } - } - - hw = 2 * el + ps + s1 - s2; - r = r * (a0 + k - 1) * x / ((n + k) * k); - hm2 += r * hw; - hu2 = fabs(hm2); - - if (hu2 > hmax) { - hmax = hu2; - } - - if (hu2 < hmin) { - hmin = hu2; - } - - if (fabs((hm2 - h0) / hm2) < 1.0e-15) { - break; - } - - h0 = hm2; - } - - db1 = log10(hmax); - db2 = 0.0; - if (hmin != 0.0) { db2 = log10(hmin); } - id1 = 15 - (int)fabs(db1 - db2); - if (id1 < *id) { *id = id1; } - hm3 = 1.0; - if (n == 0) { hm3 = 0.0; } - r = 1.0; - for (k = 1; k < n; k++) { - r = r * (a2 + k - 1.0) / ((k - n)*k)*x; - hm3 += r; - } - sa = ua*(hm1 + hm2); - sb = ub*hm3; - hu = sa + sb; - id2 = 0; - if (sa != 0.0) { id1 = (int)(log10(fabs(sa))); } - if (hu != 0.0) { id2 = (int)(log10(fabs(hu))); } - if (sa*sb < 0.0) { *id -= abs(id1-id2); } - return hu; -} - - -inline double chguit(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute hypergeometric function U(a,b,x) by - // using Gaussian-Legendre integration (n=60) - // Input : a --- Parameter ( a > 0 ) - // b --- Parameter - // x --- Argument ( x > 0 ) - // Output: HU --- U(a,b,z) - // ID --- Estimated number of significant digits - // Routine called: GAMMA2 for computing Г(x) - // ====================================================== - - int k, j, m; - double a1, b1, c, d, f1, f2, g, ga, hu, hu0, hu1, hu2, s, t1, t2, t3, t4; - static const double t[30] = { - 0.259597723012478e-01, 0.778093339495366e-01, 0.129449135396945e+00, 0.180739964873425e+00, - 0.231543551376029e+00, 0.281722937423262e+00, 0.331142848268448e+00, 0.379670056576798e+00, - 0.427173741583078e+00, 0.473525841761707e+00, 0.518601400058570e+00, 0.562278900753945e+00, - 0.604440597048510e+00, 0.644972828489477e+00, 0.683766327381356e+00, 0.720716513355730e+00, - 0.755723775306586e+00, 0.788693739932264e+00, 0.819537526162146e+00, 0.848171984785930e+00, - 0.874519922646898e+00, 0.898510310810046e+00, 0.920078476177628e+00, 0.939166276116423e+00, - 0.955722255839996e+00, 0.969701788765053e+00, 0.981067201752598e+00, 0.989787895222222e+00, - 0.995840525118838e+00, 0.999210123227436e+00 - }; - static const double w[30] = { - 0.519078776312206e-01, 0.517679431749102e-01, 0.514884515009810e-01, 0.510701560698557e-01, - 0.505141845325094e-01, 0.498220356905502e-01, 0.489955754557568e-01, 0.480370318199712e-01, - 0.469489888489122e-01, 0.457343797161145e-01, 0.443964787957872e-01, 0.429388928359356e-01, - 0.413655512355848e-01, 0.396806954523808e-01, 0.378888675692434e-01, 0.359948980510845e-01, - 0.340038927249464e-01, 0.319212190192963e-01, 0.297524915007890e-01, 0.275035567499248e-01, - 0.251804776215213e-01, 0.227895169439978e-01, 0.203371207294572e-01, 0.178299010142074e-01, - 0.152746185967848e-01, 0.126781664768159e-01, 0.100475571822880e-01, 0.738993116334531e-02, - 0.471272992695363e-02, 0.202681196887362e-02 - }; - *id = 9; - // DLMF 13.4.4, integration up to C=12/X - a1 = a - 1.0; - b1 = b - a - 1.0; - c = 12.0 / x; - hu0 = 0.0; - for (m = 10; m <= 100; m += 5) { - hu1 = 0.0; - g=0.5 * c / m; - d=g; - for (j = 1; j < (m + 1); j++) { - s = 0.0; - for (k = 1; k <= 30; k++) { - t1 = d + g * t[k-1]; - t2 = d - g * t[k-1]; - f1 = exp(-x*t1) * pow(t1, a1) * pow(1.0 + t1, b1); - f2 = exp(-x*t2) * pow(t2, a1) * pow(1.0 + t2, b1); - s += w[k-1]*(f1 + f2); - } - hu1 += s * g; - d += 2.0 * g; - } - if (fabs(1.0 - hu0/hu1) < 1.0e-9) { break; } - hu0 = hu1; - } - ga = gamma2(a); - hu1 /= ga; - // DLMF 13.4.4 with substitution t=C/(1-u) - // integration u from 0 to 1, i.e. t from C=12/X to infinity - for (m = 2; m <= 10; m += 2) { - hu2 = 0.0; - g = 0.5 / m; - d = g; - for (j = 1; j <= m; j++) { - s = 0.0; - for (k = 1; k <= 30; k++) { - t1 = d + g * t[k-1]; - t2 = d - g * t[k-1]; - t3 = c / (1.0 - t1); - t4 = c / (1.0 - t2); - f1 = t3*t3 / c * exp(-x*t3)*pow(t3, a1)*pow(1.0 + t3, b1); - f2 = t4*t4 / c * exp(-x*t4)*pow(t4, a1)*pow(1.0 + t4, b1); - s += w[k-1]*(f1 + f2); - } - hu2 += s*g; - d += 2.0*g; - } - if (fabs(1.0 - hu0/hu2) < 1.0e-9) { break; } - hu0 = hu2; - } - ga = gamma2(a); - hu2 /= ga; - hu = hu1 + hu2; - return hu; -} - - -inline double chgul(double x, double a, double b, int *id) { - - // ======================================================= - // Purpose: Compute the confluent hypergeometric function - // U(a,b,x) for large argument x - // Input : a --- Parameter - // b --- Parameter - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // ======================================================= - - int il1, il2, k, nm; - double aa, hu, r, r0 = 0.0, ra = 0.0; - - *id = -100; - aa = a - b + 1.0; - il1 = (a == (int)a) && (a <= 0.0); - il2 = (aa == (int)aa) && (aa <= 0.0); - nm = 0; - if (il1) { nm = (int)fabs(a); } - if (il2) { nm = (int)fabs(aa); } - // IL1: DLMF 13.2.7 with k=-s-a - // IL2: DLMF 13.2.8 - if (il1 || il2) { - hu = 1.0; - r = 1.0; - for (k = 1; k <= nm; k++) { - r = -r*(a + k - 1.0)*(a - b + k) / (k*x); - hu += r; - } - hu *= pow(x, -a); - *id = 10; - } else { - // DLMF 13.7.3 - hu = 1.0; - r = 1.0; - for (k = 1; k <= 25; k++) { - r = -r*(a + k - 1.0)*(a - b + k) / (k*x); - ra = fabs(r); - if (((k > 5) && (ra >= r0)) || (ra < 1e-15)) { break; } - r0 = ra; - hu += r; - } - *id = (int)fabs(log10(ra)); - hu *= pow(x, -a); - } - return hu; -} - - -inline double chgus(double x, double a, double b, int *id) { - - // ====================================================== - // Purpose: Compute confluent hypergeometric function - // U(a,b,x) for small argument x - // Input : a --- Parameter - // b --- Parameter ( b <> 0,-1,-2,...) - // x --- Argument - // Output: HU --- U(a,b,x) - // ID --- Estimated number of significant digits - // Routine called: GAMMA2 for computing gamma function - // ====================================================== - - // DLMF 13.2.42 with prefactors rewritten according to - // DLMF 5.5.3, M(a, b, x) with DLMF 13.2.2 - int j; - double d1, d2, ga, gb, gab, gb2, h0, hmax, hmin, hu, hu0, hua, r1, r2; - const double pi = 3.141592653589793; - - *id = 100; - ga = gamma2(a); - gb = gamma2(b); - gab = gamma2(1.0 + a - b); - gb2 = gamma2(2.0 - b); - hu0 = pi / sin(pi*b); - r1 = hu0 / (gab*gb); - r2 = hu0*pow(x, 1.0 - b) / (ga*gb2); - hu = r1 - r2; - hmax = 0.0; - hmin = 1e300; - h0 = 0.0; - for (j = 1; j < 151; j++) { - r1 = r1*(a + j - 1.0) / (j*(b + j - 1.0))*x; - r2 = r2*(a - b + j) / (j*(1.0 - b + j))*x; - hu += r1 - r2; - hua = fabs(hu); - if (hua > hmax) { hmax = hua; } - if (hua < hmin) { hmin = hua; } - if (fabs(hu - h0) < fabs(hu)*1e-15) { break; } - h0 = hu; - } - d1 = log10(hmax); - d2 = 0.0; - if (hmin != 0.0) { d2 = log10(hmin); } - *id = 15 - (d1 - d2 < 0 ? d2 - d1 : d1 - d2); - return hu; -} - - -inline void cpbdn(int n, std::complex z, std::complex *cpb, std::complex *cpd) { - - // ================================================== - // Purpose: Compute the parabolic cylinder functions - // Dn(z) and Dn'(z) for a complex argument - // Input: z --- Complex argument of Dn(z) - // n --- Order of Dn(z) ( n=0,±1,±2,… ) - // Output: CPB(|n|) --- Dn(z) - // CPD(|n|) --- Dn'(z) - // Routines called: - // (1) CPDSA for computing Dn(z) for a small |z| - // (2) CPDLA for computing Dn(z) for a large |z| - // ================================================== - - int n0, n1, nm1; - double a0, x; - std::complex ca0, cf, cf0, cf1, cfa, cfb, cs0, z1; - const double pi = 3.141592653589793; - - x = z.real(); - a0 = std::abs(z); - ca0 = std::exp(-0.25 * z * conj(z)); - n0 = 0; - - if (n >= 0) { - cf0 = ca0; - cf1 = z * ca0; - - cpb[0] = cf0; - cpb[1] = cf1; - - for (int k = 2; k <= n; ++k) { - cf = z * cf1 - (k - 1.0) * cf0; - cpb[k] = cf; - cf0 = cf1; - cf1 = cf; - } - } else { - n0 = -n; - - if (x <= 0.0 || a0 == 0.0) { - cf0 = ca0; - cpb[0] = cf0; - - z1 = -z; - - if (a0 <= 7.0) { - cpb[1] = cpdsa(-1, z1); - } else { - cpb[1] = cpdla(-1, z1); - } - - cf1 = std::sqrt(2.0 * pi) / ca0 - cpb[1]; - cpb[1] = cf1; - - for (int k = 2; k < n0; ++k) { - cf = (-z * cf1 + cf0) / (k - 1.0); - cpb[k] = cf; - cf0 = cf1; - cf1 = cf; - } - } else if (a0 <= 3.0) { - cpb[n0] = cpdsa(-n0, z); - n1 = n0 + 1; - cpb[n1] = cpdsa(-n1, z); - - nm1 = n0 - 1; - for (int k = nm1; k >= 0; --k) { - cf = z * cpb[n0] + (k + 1.0) * cpb[n1]; - cpb[k] = cf; - cpb[n1] = cpb[n0]; - cpb[n0] = cf; - } - } else { - int m = 100 + abs(n); - cfa = 0.0; - cfb = 1.0e-30; - - for (int k = m; k >= 0; --k) { - cf = z * cfb + (k + 1.0) * cfa; - - if (k <= n0) { - cpb[k] = cf; - } - - cfa = cfb; - cfb = cf; - } - - cs0 = ca0 / cfb; - - for (int k = 0; k <= n0; ++k) { - cpb[k] = cs0 * cpb[k]; - } - } - } - - cpd[0] = -0.5 * z * cpb[0]; - - if (n >= 0) { - for (int k = 1; k <= n; ++k) { - cpd[k] = -0.5 * z * cpb[k] + static_cast(k) * cpb[k - 1]; - } - } else { - for (int k = 1; k < n0; ++k) { - cpd[k] = 0.5 * z * cpb[k] - cpb[k - 1]; - } - } -} - - -inline std::complex cpdla(int n, std::complex z) { - - // =========================================================== - // Purpose: Compute complex parabolic cylinder function Dn(z) - // for large argument - // Input: z --- Complex argument of Dn(z) - // n --- Order of Dn(z) (n = 0,±1,±2,…) - // Output: CDN --- Dn(z) - // =========================================================== - - int k; - std::complex cb0, cr, cdn; - - cb0 = std::pow(z, n)*std::exp(-0.25*z*z); - cr = 1.0; - cdn = 1.0; - for (k = 1; k <= 16; k++) { - cr = - 0.5 * cr * (2.0 * k - n - 1.0) * (2.0 * k - n - 2.0) / (static_cast(k) * z * z); - cdn += cr; - if (std::abs(cr) < std::abs(cdn) * 1e-12) { break; } - } - return cdn * cb0; -} - - -inline std::complex cpdsa(int n, std::complex z) { - - // =========================================================== - // Purpose: Compute complex parabolic cylinder function Dn(z) - // for small argument - // Input: z --- Complex argument of D(z) - // n --- Order of D(z) (n = 0,-1,-2,...) - // Output: CDN --- Dn(z) - // Routine called: GAIH for computing Г(x), x=n/2 (n=1,2,...) - // =========================================================== - - int m; - double va0, pd, vm, vt, xn; - std::complex ca0, cb0, cdn, cr, cdw, g0, g1, ga0, gm; - const double eps = 1.0e-15; - const double pi = 3.141592653589793; - const double sq2 = sqrt(2.0); - - ca0 = std::exp(-0.25 * z * z); - va0 = 0.5 * (1.0 - n); - if (n == 0.0) { - cdn = ca0; - } else { - if (std::abs(z) == 0.0) { - if ((va0 <= 0.0) && (va0 == (int)va0)) { - cdn = 0.0; - } else { - ga0 = gaih(va0); - pd = sqrt(pi) / (pow(2.0, -0.5 * n) * ga0.real()); - cdn = pd; - } - } else { - xn = -n; - g1 = gaih(xn); - cb0 = pow(2.0, -0.5 * n - 1.0) * ca0 / g1; - vt = -0.5 * n; - g0 = gaih(vt); - cdn = g0; - cr = std::complex(1.0, 0.0); - - for (m = 1; m <= 250; m++) { - vm = 0.5 * (m - n); - gm = gaih(vm); - cr = -cr*sq2 * z / static_cast(m); - cdw = gm * cr; - cdn += cdw; - if (std::abs(cdw) < std::abs(cdn) * eps) { - break; - } - } - cdn *= cb0; - } - } - return cdn; -} - - -inline double cv0(double kd, double m, double q) { - - // ===================================================== - // Purpose: Compute the initial characteristic value of - // Mathieu functions for m ≤ 12 or q ≤ 300 or - // q ≥ m*m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Characteristic value - // Routines called: - // (1) CVQM for computing initial characteristic - // value for q ≤ 3*m - // (2) CVQL for computing initial characteristic - // value for q ≥ m*m - // ==================================================== - - double a0 = 0.0, q2 = q*q; - - if (m == 0) { - if (q <= 1.0) { - a0 = (((0.0036392 * q2 - 0.0125868) * q2 + 0.0546875) * q2 - 0.5) * q2; - } else if (q <= 10.0) { - a0 = ((3.999267e-3 * q - 9.638957e-2) * q - 0.88297) * q + 0.5542818; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 1) { - if ((q <= 1.0) && (kd == 2)) { - a0 = (((-6.51e-4 * q - 0.015625) * q - 0.125) * q + 1.0) * q + 1.0; - } else if (q <= 1.0 && kd == 3) { - a0 = (((-6.51e-4 * q + 0.015625) * q - 0.125) * q - 1.0) * q + 1.0; - } else if (q <= 10.0 && kd == 2) { - a0 = (((-4.94603e-4 * q + 1.92917e-2) * q - 0.3089229) * q + 1.33372) * q + 0.811752; - } else if (q <= 10.0 && kd == 3) { - a0 = ((1.971096e-3 * q - 5.482465e-2) * q - 1.152218) * q + 1.10427; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 2) { - if (q <= 1.0 && kd == 1) { - a0 = (((-0.0036391 * q2 + 0.0125888) * q2 - 0.0551939) * q2 + 0.416667) * q2 + 4.0; - } else if (q <= 1.0 && kd == 4) { - a0 = (0.0003617 * q2 - 0.0833333) * q2 + 4.0; - } else if (q <= 15.0 && kd == 1) { - a0 = (((3.200972e-4 * q - 8.667445e-3) * q - 1.829032e-4) * q + 0.9919999) * q + 3.3290504; - } else if (q <= 10.0 && kd == 4) { - a0 = ((2.38446e-3 * q - 0.08725329) * q - 4.732542e-3) * q + 4.00909; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 3) { - if (q <= 1.0 && kd == 2) { - a0 = (((6.348e-4 * q + 0.015625) * q + 0.0625) * q2 + 9.0); - } else if (q <= 1.0 && kd == 3) { - a0 = (((6.348e-4 * q - 0.015625) * q + 0.0625) * q2 + 9.0); - } else if (q <= 20.0 && kd == 2) { - a0 = (((3.035731e-4 * q - 1.453021e-2) * q + 0.19069602) * q - 0.1039356) * q + 8.9449274; - } else if (q <= 15.0 && kd == 3) { - a0 = ((9.369364e-5 * q - 0.03569325) * q + 0.2689874) * q + 8.771735; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 4) { - if (q <= 1.0 && kd == 1) { - a0 = ((-2.1e-6 * q2 + 5.012e-4) * q2 + 0.0333333) * q2 + 16.0; - } else if (q <= 1.0 && kd == 4) { - a0 = ((3.7e-6 * q2 - 3.669e-4) * q2 + 0.0333333) * q2 + 16.0; - } else if (q <= 25.0 && kd == 1) { - a0 = (((1.076676e-4 * q - 7.9684875e-3) * q + 0.17344854) * q - 0.5924058) * q + 16.620847; - } else if (q <= 20.0 && kd == 4) { - a0 = ((-7.08719e-4 * q + 3.8216144e-3) * q + 0.1907493) * q + 15.744; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 5) { - if (q <= 1.0 && kd == 2) { - a0 = ((6.8e-6 * q + 1.42e-5) * q2 + 0.0208333) * q2 + 25.0; - } else if (q <= 1.0 && kd == 3) { - a0 = ((-6.8e-6 * q + 1.42e-5) * q2 + 0.0208333) * q2 + 25.0; - } else if (q <= 35.0 && kd == 2) { - a0 = (((2.238231e-5 * q - 2.983416e-3) * q + 0.10706975) * q - 0.600205) * q + 25.93515; - } else if (q <= 25.0 && kd == 3) { - a0 = ((-7.425364e-4 * q + 2.18225e-2) * q + 4.16399e-2) * q + 24.897; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 6) { - if (q <= 1.0) { - a0 = (4e-6 * q2 + 0.0142857) * q2 + 36.0; - } else if (q <= 40.0 && kd == 1) { - a0 = (((-1.66846e-5 * q + 4.80263e-4) * q + 2.53998e-2) * q - 0.181233) * q + 36.423; - } else if (q <= 35.0 && kd == 4) { - a0 = ((-4.57146e-4 * q + 2.16609e-2) * q - 2.349616e-2) * q + 35.99251; - } else { - a0 = cvql(kd, m, q); - } - } else if (m == 7) { - if (q <= 10.0) { - a0 = cvqm(m, q); - } else if (q <= 50.0 && kd == 2) { - a0 = (((-1.411114e-5 * q + 9.730514e-4) * q - 3.097887e-3) * q + 3.533597e-2) * q + 49.0547; - } else if (q <= 40.0 && kd == 3) { - a0 = ((-3.043872e-4 * q + 2.05511e-2) * q - 9.16292e-2) * q + 49.19035; - } else { - a0 = cvql(kd, m, q); - } - } else if (m >= 8) { - if (q <= 3*m) { - a0 = cvqm(m, q); - } else if (q > m * m) { - a0 = cvql(kd, m, q); - } else { - if (m == 8 && kd == 1) { - a0 = (((8.634308e-6 * q - 2.100289e-3) * q + 0.169072) * q - 4.64336) * q + 109.4211; - } else if (m == 8 && kd == 4) { - a0 = ((-6.7842e-5 * q + 2.2057e-3) * q + 0.48296) * q + 56.59; - } else if (m == 9 && kd == 2) { - a0 = (((2.906435e-6 * q - 1.019893e-3) * q + 0.1101965) * q - 3.821851) * q + 127.6098; - } else if (m == 9 && kd == 3) { - a0 = ((-9.577289e-5 * q + 0.01043839) * q + 0.06588934) * q + 78.0198; - } else if (m == 10 && kd == 1) { - a0 = (((5.44927e-7 * q - 3.926119e-4) * q + 0.0612099) * q - 2.600805) * q + 138.1923; - } else if (m == 10 && kd == 4) { - a0 = ((-7.660143e-5 * q + 0.01132506) * q - 0.09746023) * q + 99.29494; - } else if (m == 11 && kd == 2) { - a0 = (((-5.67615e-7 * q + 7.152722e-6) * q + 0.01920291) * q - 1.081583) * q + 140.88; - } else if (m == 11 && kd == 3) { - a0 = ((-6.310551e-5 * q + 0.0119247) * q - 0.2681195) * q + 123.667; - } else if (m == 12 && kd == 1) { - a0 = (((-2.38351e-7 * q - 2.90139e-5) * q + 0.02023088) * q - 1.289) * q + 171.2723; - } else if (m == 12 && kd == 4) { - a0 = (((3.08902e-7 * q - 1.577869e-4) * q + 0.0247911) * q - 1.05454) * q + 161.471; - } - } - } - return a0; -} - - -inline double cva2(int kd, int m, double q) { - - // ====================================================== - // Purpose: Calculate a specific characteristic value of - // Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // KD --- Case code - // KD=1 for cem(x,q) ( m = 0,2,4,...) - // KD=2 for cem(x,q) ( m = 1,3,5,...) - // KD=3 for sem(x,q) ( m = 1,3,5,...) - // KD=4 for sem(x,q) ( m = 2,4,6,...) - // Output: A --- Characteristic value - // Routines called: - // (1) REFINE for finding accurate characteristic - // value using an iteration method - // (2) CV0 for finding initial characteristic - // values using polynomial approximation - // (3) CVQM for computing initial characteristic - // values for q ≤ 3*m - // (3) CVQL for computing initial characteristic - // values for q ≥ m*m - // ====================================================== - - int ndiv, nn, i; - double a = 0.0, delta, q1, q2, qq, a1, a2; - - if ((m <= 12) || (q <= 3.0 * m) || (q > m * m)) { - a = cv0(kd, m, q); - if ((q != 0.0) && (m != 2)) { a = refine(kd, m, q, a); } - if ((q > 2.0e-3) && (m == 2)) { a = refine(kd, m, q, a); } - } else { - ndiv = 10; - delta = (m - 3.0) * m / ndiv; - - if ((q - 3.0 * m) <= (m * m - q)) { - nn = (int)((q - 3.0 * m) / delta) + 1; - delta = (q - 3.0 * m) / nn; - q1 = 2.0 * m; - a1 = cvqm(m, q1); - q2 = 3.0 * m; - a2 = cvqm(m, q2); - qq = 3.0 * m; - for (i = 1; i <= nn; i++) { - qq = qq + delta; - a = (a1 * q2 - a2 * q1 + (a2 - a1) * qq) / (q2 - q1); - a = refine(kd, m, qq, a); - q1 = q2; - q2 = qq; - a1 = a2; - a2 = a; - } - } else { - nn = (int)((m * m - q) / delta) + 1; - delta = (m * m - q) / nn; - q1 = m * (m - 1.0); - a1 = cvql(kd, m, q1); - q2 = m * m; - a2 = cvql(kd, m, q2); - qq = m * m; - for (i = 1; i <= nn; ++i) { - qq = qq - delta; - a = (a1 * q2 - a2 * q1 + (a2 - a1) * qq) / (q2 - q1); - a = refine(kd, m, qq, a); - q1 = q2; - q2 = qq; - a1 = a2; - a2 = a; - } - } - } - return a; -} - - -inline double cvf(int kd, int m, double q, double a, int mj) { - - // ====================================================== - // Purpose: Compute the value of F for characteristic - // equation of Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // A --- Characteristic value - // Output: F --- Value of F for characteristic equation - // ====================================================== - - int j, ic = m / 2, l = 0, l0 = 0, j0 = 2; - int jf = ic; - double t0 = 0.0, t1 = 0.0, t2 = 0.0, b = a, f; - - if (kd == 1) { - j0 = 3; - l0 = 2; - } - if ((kd == 2) || (kd == 3)) { l = 1; } - if (kd == 4) { jf = ic-1; } - - for (j = mj; j >= ic+1; j--) { - t1 = -q*q/(pow(2.0*j + l, 2) - b + t1); - } - - if (m <= 2) { - if ((kd == 1) && (m == 0)) { t1 += t1; } - if ((kd == 1) && (m == 2)) { t1 = -2.0*q*q/(4.0-b+t1) - 4.0; } - if ((kd == 2) && (m == 1)) { t1 += q; } - if ((kd == 3) && (m == 1)) { t1 -= q; } - } else { - if (kd == 1) { t0 = 4.0 - b + 2.0*q*q / b; } - if (kd == 2) { t0 = 1.0 - b + q; } - if (kd == 3) { t0 = 1.0 - b - q; } - if (kd == 4) { t0 = 4.0 - b; } - t2 = -q*q / t0; - for (j = j0; j <= jf; j++) { - t2 = -q*q/(pow(2.0*j -l-l0, 2.0) - b + t2); - } - } - f = pow(2.0*ic+l, 2) + t1 + t2 - b; - return f; -} - - -inline double cvql(int kd, int m, double q) { - - // ======================================================== - // Purpose: Compute the characteristic value of Mathieu - // functions for q ≥ 3m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Initial characteristic value - // ======================================================== - - double a0, w, w2, w3, w4, w6, d1, d2, d3, d4, c1, p1, p2, cv1, cv2; - - w = 0.0; - if ((kd == 1) || (kd == 2)) { w=2.0*m + 1.0; } - if ((kd == 3) || (kd == 4)) { w=2.0*m - 1.0; } - w2 = w*w; - w3 = w*w2; - w4 = w2*w2; - w6 = w2*w4; - d1 = 5.0+34.0/w2+9.0/w4; - d2 = (33.0 + 410.0/w2 + 405.0/w4)/w; - d3 = (63.0 + 1260.0/w2 + 2943.0/w4 + 486.0/w6)/w2; - d4 = (527.0 + 15617.0/w2 + 69001.0/w4 + 41607.0/w6)/w3; - c1 = 128.0; - p2 = q/w4; - p1 = sqrt(p2); - cv1 = -2.0*q+2.0*w*sqrt(q) - (w2+1.0)/8.0; - cv2 = (w+3.0/w) + d1/(32.0*p1) + d2/(8.0*c1*p2); - cv2 = cv2 + d3/(64.0*c1*p1*p2)+d4/(16.0*c1*c1*p2*p2); - a0 = cv1 - cv2/(c1*p1); - return a0; -} - - -inline double cvqm(int m, double q) { - - // ===================================================== - // Purpose: Compute the characteristic value of Mathieu - // functions for q ≤ m*m - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // Output: A0 --- Initial characteristic value - // ===================================================== - - double hm1, hm3, hm5, a0; - - hm1= 0.5*q/(m*m-1.0); - hm3=.25*pow(hm1, 3)/(m*m - 4.0); - hm5 = hm1*hm3*q/((m*m - 1.0)*(m*m - 9.0)); - a0 = m*m + q*(hm1+(5.0*m*m + 7.0)*hm3 + (9.0*pow(m, 4) + 58.0*m*m + 29.0)*hm5); - return a0; -} - - -inline void cy01(int kf, std::complex z, std::complex *zf, std::complex *zd) { - - // =========================================================== - // Purpose: Compute complex Bessel functions Y0(z), Y1(z) - // and their derivatives - // Input : z --- Complex argument of Yn(z) ( n=0,1 ) - // KF --- Function choice code - // KF=0 for ZF=Y0(z) and ZD=Y0'(z) - // KF=1 for ZF=Y1(z) and ZD=Y1'(z) - // KF=2 for ZF=Y1'(z) and ZD=Y1''(z) - // Output: ZF --- Y0(z) or Y1(z) or Y1'(z) - // ZD --- Y0'(z) or Y1'(z) or Y1''(z) - // =========================================================== - - int k, k0; - double a0, w0, w1; - std::complex cr, cp, cp0, cq0, cu, cp1, cq1, cbj0, cbj1,\ - cby0, cby1, cdy0, cdy1, cs, ct1, ct2, z1, z2; - - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - const double rp2 = 2.0 / pi; - const std::complex ci(0.0, 1.0); - - static const double a[12] = {-0.703125e-01, 0.112152099609375, -0.5725014209747314, - 0.6074042001273483, -0.1100171402692467, 0.3038090510922384, - -0.1188384262567832, 0.6252951493434797, -0.4259392165047669, - 0.3646840080706556, -0.3833534661393944, 0.4854014686852901}; - - static const double b[12] = { 0.732421875e-01, -0.2271080017089844, 0.1727727502584457, - -0.2438052969955606, 0.5513358961220206, -0.1825775547429318, - 0.8328593040162893, -0.5006958953198893, 0.3836255180230433, - -0.3649010818849833, 0.4218971570284096, -0.5827244631566907}; - - static const double a1[12] = { 0.1171875, -0.144195556640625, 0.6765925884246826, - -0.6883914268109947, 0.1215978918765359, -0.3302272294480852, - 0.1276412726461746, -0.6656367718817688, 0.4502786003050393, - -0.3833857520742790, 0.4011838599133198, -0.5060568503314727}; - - static const double b1[12] = {-0.1025390625, 0.2775764465332031, -0.1993531733751297, - 0.2724882731126854, -0.6038440767050702, 0.1971837591223663, - -0.8902978767070678, 0.5310411010968522, -0.4043620325107754, - 0.3827011346598605, -0.4406481417852278, 0.6065091351222699}; - - a0 = std::abs(z); - z1 = z; - z2 = z * z; - if (a0 == 0.0) { - cbj0 = std::complex(1.0, 0.0); - cbj1 = std::complex(0.0, 0.0); - cby0 = std::complex(-1e300, 0.0); - cby1 = std::complex(-1e300, 0.0); - cdy0 = std::complex( 1e300, 0.0); - cdy1 = std::complex( 1e300, 0.0); - if (kf == 0) { - *zf = cby0; - *zd = cdy0; - } else if (kf == 1) { - *zf = cby1; - *zd = cdy1; - } else if (kf == 2) { - *zf = cdy1; - *zd = -cdy1 / z - (1.0 - 1.0 / (z * z)) * cby1; - } - return; - } - - if (z.real() < 0.0) { - z1 = -z; - } - - if (a0 <= 12.0) { - cbj0 = std::complex(1.0, 0.0); - cr = std::complex(1.0, 0.0); - for (k = 1; k <= 40; k++) { - cr = -0.25 * cr * z2 / static_cast(k * k); - cbj0 += cr; - if (std::abs(cr) < std::abs(cbj0) * 1.0e-15) break; - } - - cbj1 = std::complex(1.0, 0.0); - cr = std::complex(1.0, 0.0); - for (k = 1; k <= 40; k++) { - cr = -0.25 * cr * z2 / (k * (k + 1.0)); - cbj1 += cr; - if (std::abs(cr) < std::abs(cbj1) * 1.0e-15) break; - } - - cbj1 *= 0.5 * z1; - w0 = 0.0; - cr = std::complex(1.0, 0.0); - cs = std::complex(0.0, 0.0); - for (k = 1; k <= 40; k++) { - w0 += 1.0 / k; - cr = -0.25 * cr / static_cast(k * k) * z2; - cp = cr * w0; - cs += cp; - if (std::abs(cp) < std::abs(cs) * 1.0e-15) break; - } - - cby0 = rp2 * (std::log(z1 / 2.0) + el) * cbj0 - rp2 * cs; - w1 = 0.0; - cr = 1.0; - cs = 1.0; - for (k = 1; k <= 40; k++) { - w1 += 1.0 / k; - cr = -0.25 * cr / static_cast(k * (k + 1)) * z2; - cp = cr * (2.0 * w1 + 1.0 / (k + 1.0)); - cs += cp; - if (std::abs(cp) < std::abs(cs) * 1.0e-15) break; - } - cby1 = rp2 * ((std::log(z1 / 2.0) + el) * cbj1 - 1.0 / z1 - 0.25 * z1 * cs); - } else { - k0 = 12; - if (a0 >= 35.0) k0 = 10; - if (a0 >= 50.0) k0 = 8; - - ct1 = z1 - 0.25 * pi; - cp0 = 1.0; - for (k = 1; k <= k0; k++) { - cp0 += a[k - 1] * pow(z1, -2 * k); - } - cq0 = -0.125 / z1; - for (k = 1; k <= k0; k++) - cq0 += b[k - 1] * pow(z1, -2 * k - 1); - - cu = std::sqrt(rp2 / z1); - cbj0 = cu * (cp0 * cos(ct1) - cq0 * sin(ct1)); - cby0 = cu * (cp0 * sin(ct1) + cq0 * cos(ct1)); - - ct2 = z1 - 0.75 * pi; - cp1 = 1.0; - for (k = 1; k <= k0; k++) - cp1 += a1[k - 1] * pow(z1, -2 * k); - - cq1 = 0.375 / z1; - for (k = 1; k <= k0; k++) { - cq1 = cq1 + b1[k - 1] * pow(z1, -2 * k - 1); - } - cbj1 = cu * (cp1 * cos(ct2) - cq1 * sin(ct2)); - cby1 = cu * (cp1 * sin(ct2) + cq1 * cos(ct2)); - } - - if (z.real() < 0.0) { - if (z.imag() < 0.0) cby0 = cby0 - 2.0 * ci * cbj0; - if (z.imag() > 0.0) cby0 = cby0 + 2.0 * ci * cbj0; - if (z.imag() < 0.0) cby1 = -(cby1 - 2.0 * ci * cbj1); - if (z.imag() > 0.0) cby1 = -(cby1 + 2.0 * ci * cbj1); - cbj1 = -cbj1; - } - - cdy0 = -cby1; - cdy1 = cby0 - 1.0 / z * cby1; - - if (kf == 0) { - *zf = cby0; - *zd = cdy0; - } else if (kf == 1) { - *zf = cby1; - *zd = cdy1; - } else if (kf == 2) { - *zf = cdy1; - *zd = -cdy1 / z - (1.0 - 1.0 / (z * z)) * cby1; - } - return; -} - - -inline void cyzo(int nt, int kf, int kc, std::complex *zo, std::complex *zv) { - - // =========================================================== - // Purpose : Compute the complex zeros of Y0(z), Y1(z) and - // Y1'(z), and their associated values at the zeros - // using the modified Newton's iteration method - // Input: NT --- Total number of zeros/roots - // KF --- Function choice code - // KF=0 for Y0(z) & Y1(z0) - // KF=1 for Y1(z) & Y0(z1) - // KF=2 for Y1'(z) & Y1(z1') - // KC --- Choice code - // KC=0 for complex roots - // KC=1 for real roots - // Output: ZO(L) --- L-th zero of Y0(z) or Y1(z) or Y1'(z) - // ZV(L) --- Value of Y0'(z) or Y1'(z) or Y1(z) - // at the L-th zero - // Routine called: CY01 for computing Y0(z) and Y1(z), and - // their derivatives - // =========================================================== - - int i, it, j, nr; - double x, h, w, y, w0; - std::complex z, zf, zd, zfd, zgd, zp, zq, zw; - - x = 0.0; - y = 0.0; - h = 0.0; - - if (kc == 0) { - x = -2.4; - y = 0.54; - h = 3.14; - } else if (kc == 1) { - x = 0.89; - y = 0.0; - h = -3.14; - } - - if (kf == 1) { - x = -0.503; - } - - if (kf == 2) { - x = 0.577; - } - z = std::complex(x, y); - w = 0.0; - for (nr = 1; nr <= nt; nr++) { - if (nr > 1) { - z = zo[nr - 2] - h; - } - it = 0; - do { - it += 1; - cy01(kf, z, &zf, &zd); - zp = 1.0; - for (i = 1; i < nr; i++) { - zp *= (z - zo[i - 1]); - } - zfd = zf / zp; - zq = 0.0; - for (i = 1; i < nr; i++) { - zw = 1.0; - for (j = 1; j < nr; j++) { - if (j == i) { continue; } - zw *= (z - zo[j - 1]); - } - zq += zw; - } - zgd = (zd - zq * zfd) / zp; - z -= zfd / zgd; - w0 = w; - w = std::abs(z); - } while ((it <= 50) && (fabs((w - w0) / w) > 1.0e-12)); - - zo[nr - 1] = z; - } - - for (i = 1; i <= nt; i++) { - z = zo[i - 1]; - if ((kf == 0) || (kf == 2)) { - cy01(1, z, &zf, &zd); - zv[i - 1] = zf; - } else if (kf == 1) { - cy01(0, z, &zf, &zd); - zv[i - 1] = zf; - } - } - return; -} - - -template -T e1xb(T x) { - - // ============================================ - // Purpose: Compute exponential integral E1(x) - // Input : x --- Argument of E1(x) - // Output: E1 --- E1(x) ( x > 0 ) - // ============================================ - - int k, m; - T e1, r, t, t0; - const T ga = 0.5772156649015328; - - if (x == 0.0) { - e1 = 1e300; - } else if (x <= 1.0) { - e1 = 1.0; - r = 1.0; - for (k = 1; k < 26; k++) { - r = -r*k*x/pow(k+1.0, 2); - e1 += r; - if (fabs(r) <= fabs(e1)*1e-15) { break; } - } - e1 = -ga - log(x) + x*e1; - } else { - m = 20 + (int)(80.0/x); - t0 = 0.0; - for (k = m; k > 0; k--) { - t0 = k / (1.0 + k / (x+t0)); - } - t = 1.0 / (x + t0); - e1 = exp(-x)*t; - } - return e1; -} - - -template -std::complex e1z(std::complex z) { - - // ==================================================== - // Purpose: Compute complex exponential integral E1(z) - // Input : z --- Argument of E1(z) - // Output: CE1 --- E1(z) - // ==================================================== - - const T pi = 3.141592653589793; - const T el = 0.5772156649015328; - int k; - std::complex ce1, cr, zc, zd, zdc; - T x = z.real(); - T a0 = std::abs(z); - // Continued fraction converges slowly near negative real axis, - // so use power series in a wedge around it until radius 40.0 - T xt = -2.0*fabs(z.imag()); - - if (a0 == 0.0) { return 1e300; } - if ((a0 < 5.0) || ((x < xt) && (a0 < 40.0))) { - // Power series - ce1 = 1.0; - cr = 1.0; - for (k = 1; k < 501; k++) { - cr = -cr*z*static_cast(k / std::pow(k + 1, 2)); - ce1 += cr; - if (std::abs(cr) < std::abs(ce1)*1e-15) { break; } - } - if ((x <= 0.0) && (z.imag() == 0.0)) { - //Careful on the branch cut -- use the sign of the imaginary part - // to get the right sign on the factor if pi. - ce1 = -el - std::log(-z) + z*ce1 - copysign(pi, z.imag())*std::complex(0.0, 1.0); - } else { - ce1 = -el - std::log(z) + z*ce1; - } - } else { - // Continued fraction https://dlmf.nist.gov/6.9 - // 1 1 1 2 2 3 3 - // E1 = exp(-z) * ----- ----- ----- ----- ----- ----- ----- ... - // Z + 1 + Z + 1 + Z + 1 + Z + - zc = 0.0; - zd = static_cast(1) / z; - zdc = zd; - zc += zdc; - for (k = 1; k < 501; k++) { - zd = static_cast(1) / (zd*static_cast(k) + static_cast(1)); - zdc *= (zd - static_cast(1)); - zc += zdc; - - zd = static_cast(1) / (zd*static_cast(k) + z); - zdc *= (z*zd - static_cast(1)); - zc += zdc; - if ((std::abs(zdc) <= std::abs(zc)*1e-15) && (k > 20)) { break; } - } - ce1 = std::exp(-z)*zc; - if ((x <= 0.0) && (z.imag() == 0.0)) { - ce1 -= pi*std::complex(0.0, 1.0); - } - } - return ce1; -} - - -template -T eix(T x) { - - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - const T ga = 0.5772156649015328; - T ei, r; - - if (x == 0.0) { - ei = -1.0e+300; - } else if (x < 0) { - ei = -e1xb(-x); - } else if (fabs(x) <= 40.0) { - // Power series around x=0 - ei = 1.0; - r = 1.0; - - for (int k = 1; k <= 100; k++) { - r = r * k * x / ((k + 1.0) * (k + 1.0)); - ei += r; - if (fabs(r / ei) <= 1.0e-15) { break; } - } - ei = ga + log(x) + x * ei; - } else { - // Asymptotic expansion (the series is not convergent) - ei = 1.0; - r = 1.0; - for (int k = 1; k <= 20; k++) { - r = r * k / x; - ei += r; - } - ei = exp(x) / x * ei; - } - return ei; -} - - -template -std::complex eixz(std::complex z) { - - // ============================================ - // Purpose: Compute exponential integral Ei(x) - // Input : x --- Complex argument of Ei(x) - // Output: EI --- Ei(x) - // ============================================ - - std::complex cei; - const T pi = 3.141592653589793; - cei = - e1z(-z); - if (z.imag() > 0.0) { - cei += std::complex(0.0, pi); - } else if (z.imag() < 0.0 ) { - cei -= std::complex(0.0, pi); - } else { - if (z.real() > 0.0) { - cei += std::complex(0.0, copysign(pi, z.imag())); - } - } - return cei; -} - - -inline void eulerb(int n, double *en) { - - // ====================================== - // Purpose: Compute Euler number En - // Input : n --- Serial number - // Output: EN(n) --- En - // ====================================== - - int k, m, isgn; - double r1, r2, s; - const double hpi = 2.0 / 3.141592653589793; - en[0] = 1.0; - en[2] = -1.0; - r1 = -4.0*pow(hpi, 3); - for (m = 4; m <= n; m += 2) { - r1 = -r1 * (m-1) * m * hpi * hpi; - r2 = 1.0; - isgn = 1; - for (k = 3; k <= 1000; k += 2) { - isgn = -isgn; - s = pow(1.0 / k, m + 1); - r2 += isgn * s; - if (s < 1e-15) { break; } - } - en[m] = r1*r2; - } - return; -} - - -template -void fcoef(int kd, int m, T q, T a, T *fc) { - - // ===================================================== - // Purpose: Compute expansion coefficients for Mathieu - // functions and modified Mathieu functions - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // KD --- Case code - // KD=1 for cem(x,q) ( m = 0,2,4,...) - // KD=2 for cem(x,q) ( m = 1,3,5,...) - // KD=3 for sem(x,q) ( m = 1,3,5,...) - // KD=4 for sem(x,q) ( m = 2,4,6,...) - // A --- Characteristic value of Mathieu - // functions for given m and q - // Output: FC(k) --- Expansion coefficients of Mathieu - // functions ( k= 1,2,...,KM ) - // FC(1),FC(2),FC(3),... correspond to - // A0,A2,A4,... for KD=1 case, A1,A3, - // A5,... for KD=2 case, B1,B3,B5,... - // for KD=3 case and B2,B4,B6,... for - // KD=4 case - // ===================================================== - - int i, k, j, jm = 0, km, kb; - T f1, fnan, qm, s, f, u, v, f2, f3, sp, ss, s0; - - for (i = 0; i < 251; ++i) { fc[i] = 0.0; } - - if (fabs(q) <= 1.0e-7) { - // Expansion up to order Q^1 (Abramowitz & Stegun 20.2.27-28) - if (kd == 1) { - jm = m / 2 + 1; - } else if ((kd == 2) || (kd == 3)) { - jm = (m - 1) / 2 + 1; - } else if (kd == 4) { - jm = m / 2; - } - - if (jm + 1 > 251) { - fnan = NAN; - for (i = 0; i < 251; ++i) { - fc[i] = fnan; - } - return; - } - // Proceed using the simplest expansion - if (kd == 1 || kd == 2) { - if (m == 0) { - fc[0] = 1.0 / sqrt(2.0); - fc[1] = -q / (2.0 * sqrt(2.0)); - } else if (m == 1) { - fc[0] = 1.0; - fc[1] = -q / 8.0; - } else if (m == 2) { - fc[0] = q / 4.0; - fc[1] = 1.0; - fc[2] = -q / 12.0; - } else { - fc[jm - 1] = 1.0; - fc[jm] = -q / (4.0 * (m + 1)); - fc[jm - 2] = q / (4.0 * (m - 1)); - } - } else if (kd == 3 || kd == 4) { - if (m == 1) { - fc[0] = 1.0; - fc[1] = -q / 8.0; - } else if (m == 2) { - fc[0] = 1.0; - fc[1] = -q / 12.0; - } else { - fc[jm - 1] = 1.0; - fc[jm] = -q / (4.0 * (m + 1)); - fc[jm - 2] = q / (4.0 * (m - 1)); - } - } - return; - } else if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - - if (km > 251) { - // Overflow, generate NaNs - for (i = 0; i < 251; ++i) { - fc[i] = NAN; - } - return; - } - - kb = 0; - s = 0.0; - f = 1.0e-100; - u = 0.0; - fc[km - 1] = 0.0; - f2 = 0.0; - - if (kd == 1) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - 4.0 * k * k) * u / q - v; - - if (fabs(f) < fabs(fc[k])) { - kb = k; - fc[0] = 1.0e-100; - sp = 0.0; - f3 = fc[k]; - fc[1] = a / q * fc[0]; - fc[2] = (a - 4.0) * fc[1] / q - 2.0 * fc[0]; - u = fc[1]; - f1 = fc[2]; - - for (i = 3; i <= kb; i++) { - v = u; - u = f1; - f1 = (a - 4.0 * (i - 1.0) * (i - 1.0)) * u / q - v; - fc[i] = f1; - - if (i == kb) { f2 = f1; } - if (i != kb) { sp += f1*f1; } - } - - sp += 2.0*fc[0]*fc[0] + fc[1]*fc[1] + fc[2]*fc[2]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j <= kb + 1) { - fc[j - 1] = s0 * fc[j-1] * f3 / f2; - } else { - fc[j - 1] *= s0; - } - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; - } else { - fc[k - 1] = f; - s += f*f; - } - } - - fc[1] = q * fc[2] / (a - 4.0 - 2.0 * q * q / a); - fc[0] = q / a * fc[1]; - s += 2.0 * fc[0] * fc[0] + fc[1] * fc[1]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - } else if ((kd == 2) || (kd == 3)) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - (2.0 * k - 1) * (2.0 * k - 1)) * u / q - v; - - if (fabs(f) >= fabs(fc[k - 1])) { - fc[k - 2] = f; - s += f * f; - } else { - kb = k; - f3 = fc[k - 1]; - goto L45; - } - } - - fc[0] = q / (a - 1.0 - pow(-1, kd) * q) * fc[1]; - s += fc[0] * fc[0]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -L45: - fc[0] = 1.0e-100; - fc[1] = (a - 1.0 - pow(-1, kd) * q) / q * fc[0]; - sp = 0.0; - u = fc[0]; - f1 = fc[1]; - - for (i = 2; i <= kb - 1; i++) { - v = u; - u = f1; - f1 = (a - (2.0 * i - 1) * (2.0 * i - 1)) * u / q - v; - - if (i != kb - 1) { - fc[i] = f1; - sp += f1 * f1; - } else { - f2 = f1; - } - } - - sp += fc[0] * fc[0] + fc[1] * fc[1]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j < kb) { - fc[j - 1] *= s0 * f3 / f2; - } - - if (j >= kb) { - fc[j - 1] *= s0; - } - } - - } else if (kd == 4) { - for (k = km; k >= 3; k--) { - v = u; - u = f; - f = (a - 4.0 * k * k) * u / q - v; - - if (fabs(f) >= fabs(fc[k])) { - fc[k - 2] = f; - s += f*f; - } else { - kb = k; - f3 = fc[k - 1]; - goto L70; - } - } - - fc[0] = q / (a - 4.0) * fc[1]; - s += fc[0] * fc[0]; - s0 = sqrt(1.0 / s); - - for (k = 1; k <= km; k++) { - fc[k - 1] *= s0; - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -L70: - fc[0] = 1.0e-100; - fc[1] = (a - 4.0) / q * fc[0]; - sp = 0.0; - u = fc[0]; - f1 = fc[1]; - - for (i = 2; i <= kb - 1; i++) { - v = u; - u = f1; - f1 = (a - 4.0 * i * i) * u / q - v; - - if (i != kb - 1) { - fc[i] = f1; - sp = sp + f1 * f1; - } else { - f2 = f1; - } - } - - sp += fc[0] * fc[0] + fc[1] * fc[1]; - ss = s + sp * (f3 / f2) * (f3 / f2); - s0 = sqrt(1.0 / ss); - - for (j = 1; j <= km; j++) { - if (j < kb) { - fc[j - 1] *= s0 * f3 / f2; - } else { - fc[j - 1] *= s0; - } - } - } - if (fc[0] < 0.0) { for (j = 0; j < km; j++) { fc[j] = -fc[j]; } } - return; -} - - -inline double gaih(double x) { - - // ===================================================== - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x), x = n/2, n=1,2,… - // Output: GA --- Г(x) - // ===================================================== - - int k, m; - const double pi = 3.141592653589793; - double ga = 0.0; - - if ((x == (int)x) && (x > 0.0)) { - ga = 1.0; - m = (int)(x - 1.0); - for (k = 2; k < (m+1); k++) { - ga *= k; - } - } else if (((x+0.5) == (int)(x+0.5)) && (x > 0.0)) { - m = (int)x; - ga = sqrt(pi); - for (k = 1; k < (m+1); k++) { - ga *= 0.5*(2.0*k - 1.0); - } - } else { - ga = NAN; - } - return ga; -} - - -inline double gam0(double x) { - - // ================================================ - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x) ( |x| ≤ 1 ) - // Output: GA --- Г(x) - // ================================================ - double gr; - static const double g[25] = { - 1.0e0, - 0.5772156649015329e0, -0.6558780715202538e0, -0.420026350340952e-1, 0.1665386113822915e0, - -0.421977345555443e-1, -0.96219715278770e-2, 0.72189432466630e-2, -0.11651675918591e-2, - -0.2152416741149e-3, 0.1280502823882e-3, -0.201348547807e-4, -0.12504934821e-5, - 0.11330272320e-5, -0.2056338417e-6, 0.61160950e-8, 0.50020075e-8, -0.11812746e-8, - 0.1043427e-9, 0.77823e-11, -0.36968e-11, 0.51e-12, -0.206e-13, -0.54e-14, 0.14e-14 - }; - gr = g[24]; - for (int k = 23; k >= 0; k--) { - gr = gr*x + g[k]; - } - return 1.0 / (gr * x); -} - - -inline double gamma2(double x) { - - // ================================================== - // Purpose: Compute gamma function Г(x) - // Input : x --- Argument of Г(x) - // ( x is not equal to 0,-1,-2,…) - // Output: GA --- Г(x) - // ================================================== - - double ga, gr, r, z; - int k, m; - const double pi = 3.141592653589793; - static const double g[26] = { - 1.0000000000000000e+00, 0.5772156649015329e+00, -0.6558780715202538e+00, -0.4200263503409520e-01, - 0.1665386113822915e+00, -0.4219773455554430e-01, -0.9621971527877000e-02, 0.7218943246663000e-02, - -0.1165167591859100e-02, -0.2152416741149000e-03, 0.1280502823882000e-03, -0.2013485478070000e-04, - -0.1250493482100000e-05, 0.1133027232000000e-05, -0.2056338417000000e-06, 0.6116095000000000e-08, - 0.5002007500000000e-08, -0.1181274600000000e-08, 0.1043427000000000e-09, 0.7782300000000000e-11, - -0.3696800000000000e-11, 0.5100000000000000e-12, -0.2060000000000000e-13, -0.5400000000000000e-14, - 0.1400000000000000e-14, 0.1000000000000000e-15 - }; - if (x == (int)x) { - if (x > 0.0) { - ga = 1.0; - m = (int)(x - 1); - for (k = 2; k < (m+1); k++) { - ga *= k; - } - } else { - ga = 1e300; - } - } else { - r = 1.0; - if (fabs(x) > 1.0) { - z = fabs(x); - m = (int)z; - for (k = 1; k < (m+1); k++) { - r *= (z-k); - } - z -= m; - } else { - z = x; - } - gr = g[25]; - for ( k = 25; k > 0; k--) { - gr *= z; - gr += g[k-1]; - } - ga = 1.0 / (gr*z); - if (fabs(x) > 1.0) { - ga *= r; - if (x < 0.0) { - ga = -pi / (x*ga*sin(pi*x)); - } - } - } - return ga; -} - - -template -inline void gmn(int m, int n, T c, T x, T *bk, T *gf, T *gd) { - - // =========================================================== - // Purpose: Compute gmn(-ic,ix) and its derivative for oblate - // radial functions with a small argument - // =========================================================== - - int ip, k, nm; - T xm, gf0, gw, gd0, gd1; - const T eps = 1.0e-14; - ip = ((n - m) % 2 == 0 ? 0 : 1); - nm = 25 + (int)(0.5 * (n - m) + c); - xm = pow(1.0 + x * x, -0.5 * m); - gf0 = 0.0; - gw = 0.0; - - for (k = 1; k <= nm; k++) { - gf0 += bk[k - 1] * pow(x, 2.0 * k - 2.0); - if ((fabs((gf0 - gw) / gf0) < eps) && (k >= 10)) { break; } - gw = gf0; - } - - *gf = xm * gf0 * pow(x, 1 - ip); - gd1 = -m * x / (1.0 + x * x) * (*gf); - gd0 = 0.0; - - for (k = 1; k < nm; ++k) { - if (ip == 0) { - gd0 += (2.0 * k - 1.0) * bk[k - 1] * pow(x, 2.0 * k - 2.0); - } else { - gd0 += 2.0 * k * bk[k - 1] * pow(x, 2.0 * k - 1.0); - } - if ((fabs((gd0 - gw) / gd0) < eps) && (k >= 10)) { break; } - gw = gd0; - } - *gd = gd1 + xm * gd0; -} - - -inline std::complex hygfz(double a, double b, double c, std::complex z, int *isfer) { - - // ====================================================== - // Purpose: Compute the hypergeometric function for a - // complex argument, F(a,b,c,z) - // Input : a --- Parameter - // b --- Parameter - // c --- Parameter, c <> 0,-1,-2,... - // z --- Complex argument - // Output: ZHF --- F(a,b,c,z) - // ISFER --- Error flag - // Routines called: - // (1) GAMMA2 for computing gamma function - // (2) PSI_SPEC for computing psi function - // ====================================================== - - int L0 = 0, L1 = 0, L2 = 0, L3 = 0, L4 = 0, L5 = 0, L6 = 0; - int j, k=1, m, mab, mcab, nca, ncb, nm; - double a0, aa, bb, ca, cb, g0, g1, g2, g3, ga, gab, gam, gabc, gb, gba, gbm, gc, gcab,\ - gca, gcb, gm, pa, pac, pb, pca, rk1, rk2, rm, sp0, sm, sp, sq, sj1, sj2, w0, ws; - std::complex z00, z1, zc0, zc1, zf0, zf1, zhf = 0.0, zp, zr, zp0, zr0, zr1, zw = 0.0; - double x = z.real(); - double y = z.imag(); - double eps = 1e-15; - double pi = 3.141592653589793; - double el = 0.5772156649015329; - *isfer = 0; - - if ((c == (int)c) && (c < 0.0)) { L0 = 1; } - if ((fabs(1 - x) < eps) && (y == 0.0) && (c-a-b <= 0.0)) { L1 = 1; } - if ((std::abs(z+1.0) < eps) && (fabs(c-a+b - 1.0) < eps)) { L2 = 1; } - if ((a == (int)a) && (a < 0.0)) { L3 = 1; } - if ((b == (int)b) && (b < 0.0)) { L4 = 1; } - if (((c-a) == (int)(c-a)) && (c-a <= 0.0)) { L5 = 1; } - if (((c-b) == (int)(c-b)) && (c-b <= 0.0)) { L6 = 1; } - aa = a; - bb = b; - a0 = std::abs(z); - if (a0 > 0.95) { eps = 1e-8; } - if (L0 || L1) { - *isfer = 3; - return 0.0; - } - - if ((a0 == 0.0) || (a == 0.0) || (b == 0.0)) { - zhf = 1.0; - } else if ((z == 1.0) && (c-a-b > 0.0)) { - gc = gamma2(c); - gcab = gamma2(c-a-b); - gca = gamma2(c-a); - gcb = gamma2(c-b); - zhf = gc*gcab/(gca*gcb); - } else if (L2) { - g0 = sqrt(pi)*pow(2.0, -a); - g1 = gamma2(c); - g2 = gamma2(1.0 + 0.5*a - b); - g3 = gamma2(0.5 + 0.5*a); - zhf = g0*g1/(g2*g3); - } else if (L3 || L4) { - if (L3) { nm = (int)fabs(a); } - if (L4) { nm = (int)fabs(b); } - zhf = 1.0; - zr = 1.0; - for (k = 1; k < (nm+1); k++) { - zr = zr*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - } - } else if (L5 || L6) { - if (L5) { nm = (int)fabs(c-a); } - if (L6) { nm = (int)fabs(c-b); } - zhf = 1.0; - zr = 1.0; - for (k = 1; k < (nm+1); k++) { - zr = zr*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - } - zhf *= std::pow(1.0-z, c-a-b); - } else if (a0 <= 1.0) { - if (x < 0.0) { - z1 = z / (z - 1.0); - if ((c > a) && (b < a) && (b > 0.0)) { - a = aa; - b = bb; - } - zc0 = 1.0 / std::pow(1.0 - z, a); - zhf = 1.0; - zr0 = 1.0; - zw = 0.0; - for (k = 1; k <501; k++) { - zr0 = zr0*(a+k-1.0)*(c-b+k-1.0)/(k*(c+k-1.0))*z1; - zhf += zr0; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf *= zc0; - } else if (a0 >= 0.9) { - gm = 0.0; - mcab = (int)(c-a-b + eps*copysign(1.0, c-a-b)); - if (fabs(c-a-b-mcab) < eps) { - m = (int)(c-a-b); - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gam = gamma2(a+m); - gbm = gamma2(b+m); - pa = psi_spec(a); - pb = psi_spec(b); - if (m != 0) { gm = 1.0; } - for (j = 1; j < abs(m); j++) { - gm *= j; - } - rm = 1.0; - for (j = 1; j < abs(m)+1; j++) { - rm *= j; - } - zf0 = 1.0; - zr0 = 1.0; - zr1 = 1.0; - sp0 = 0.0; - sp = 0.0; - if (m >= 0) { - zc0 = gm*gc/(gam*gbm); - zc1 = -gc*std::pow(z-1.0, m)/(ga*gb*rm); - for (k = 1; k < m; k++) { - zr0 = zr0*(a+k-1.0)*(b+k-1.0)/static_cast(k*(k-m))*(1.0-z); - zf0 += zr0; - } - for (k = 1; k < (m+1); k++) { - sp0 += 1.0/(a+k-1.0) + 1.0/(b+k-1.0) - 1.0/k; - } - zf1 = pa + pb + sp0 + 2.0*el + std::log(1.0 - z); - zw = 0.0; - for (k = 1; k <501; k++) { - sp += (1.0-a)/(k*(a+k-1.0)) + (1.0-b)/(k*(b+k-1.0)); - sm = 0.0; - for (j = 1; j < (m+1); j++) { - sm += (1.0-a)/((j+k)*(a+j+k-1.0)) + 1.0/(b+j+k-1.0); - } - zp = pa + pb + 2.0*el + sp + sm + std::log(1.0 - z); - zr1 = zr1*(a+m+k-1.0)*(b+m+k-1.0) / static_cast(k*(m+k))*(1.0-z); - zf1 += zr1*zp; - if (std::abs(zf1-zw) < std::abs(zf1)*eps) { break; } - zw = zf1; - } - zhf = zf0*zc0 + zf1*zc1; - } else if (m < 0) { - m = -m; - zc0 = gm*gc/(ga*gb*std::pow(1.0 - z, m)); - zc1 = -(pow(-1.0, m))*gc/(gam*gbm*rm); - for (k = 1; k < m; k++) { - zr0 = zr0*(a-m+k-1.0)*(b-m+k-1.0)/static_cast(k*(k-m))*(1.0-z); - zf0 += zr0; - } - for (k = 1; k < (m+1); k++) { - sp0 += 1.0 / k; - } - zf1 = pa + pb -sp0 + 2.0*el + std::log(1.0 - z); - zw = 0.0; - for (k = 1; k <501; k++) { - sp += (1.0-a)/(k*(a+k-1.0)) + (1.0-b)/(k*(b+k-1.0)); - sm = 0.0; - for (j = 1; j < (m+1); j++) { - sm += 1.0/(j+k); - } - zp = pa + pb+2.0*el + sp - sm + std::log(1.0 -z ); - zr1 = zr1*(a+k-1.0)*(b+k-1.0)/static_cast(k*(m+k))*(1.0-z); - zf1 += zr1*zp; - if (std::abs(zf1-zw) < std::abs(zf1)*eps) { break; } - zw = zf1; - } - zhf = zf0*zc0 + zf1*zc1; - } - } else { - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gca = gamma2(c-a); - gcb = gamma2(c-b); - gcab = gamma2(c-a-b); - gabc = gamma2(a+b-c); - zc0 = gc*gcab/(gca*gcb); - zc1 = gc*gabc/(ga*gb)*std::pow(1.0-z, c-a-b); - zhf = 0.0; - zr0 = zc0; - zr1 = zc1; - zw = 0.0; - for (k = 1; k < 501; k++) { - zr0 = zr0*(a+k-1.0)*(b+k-1.0)/(k*(a+b-c+k))*(1.0-z); - zr1 = zr1*(c-a+k-1.0)*(c-b+k-1.0)/(k*(c-a-b+k))*(1.0-z); - zhf += zr0+zr1; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf += zc0 + zc1; - } - } else { - z00 = 1.0; - if ((c-a < a) && (c-b < b)) { - z00 = std::pow(1.0 - z, c-a-b); - a = c-a; - b = c-b; - } - zhf = 1.0; - zr = 1.0; - zw = 0.0; - for (k = 1; k < 1501; k++) { - zr = zr*(a+k-1.0)*(b+k-1.0)/(k*(c+k-1.0))*z; - zhf += zr; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf *= z00; - } - } else if (a0 > 1.0) { - mab = (int)(a - b + eps*copysign(1.0, a - b)); - if ((fabs(a-b-mab) < eps) && (a0 <= 1.1)) { b += eps; } - if (fabs(a-b-mab) > eps) { - ga = gamma2(a); - gb = gamma2(b); - gc = gamma2(c); - gab = gamma2(a-b); - gba = gamma2(b-a); - gca = gamma2(c-a); - gcb = gamma2(c-b); - zc0 = gc*gba/(gca*gb*std::pow(-z, a)); - zc1 = gc*gab/(gcb*ga*std::pow(-z, b)); - zr0 = zc0; - zr1 = zc1; - zhf = 0.0; - for (k = 1; k < 501; k++) { - zr0 = zr0*(a+k-1.0)*(a-c+k)/((a-b+k)*k*z); - zr1 = zr1*(b+k-1.0)*(b-c+k)/((b-a+k)*k*z); - zhf += zr0+zr1; - if (std::abs(zhf-zw) < std::abs(zhf)*eps) { break; } - zw = zhf; - } - zhf += zc0 + zc1; - } else { - if (a-b < 0.0) { - a = bb; - b = aa; - } - ca = c - a; - cb = c - b; - nca = (int)(ca + eps*copysign(1.0, ca)); - ncb = (int)(cb + eps*copysign(1.0, cb)); - if ((fabs(ca-nca) < eps) || (fabs(cb-ncb) < eps)) { c += eps; } - ga = gamma2(a); - gc = gamma2(c); - gcb = gamma2(c-b); - pa = psi_spec(a); - pca = psi_spec(c-a); - pac = psi_spec(a-c); - mab = (int)(a-b+eps); - zc0 = gc / (ga*std::pow(-z, b)); - gm = gamma2(a-b); - zf0 = gm/gcb*zc0; - zr = zc0; - for (k = 1; k < mab; k++) { - zr = zr*(b+k-1.0)/(static_cast(k)*z); - g0 = gamma2(a-b-k); - zf0 += zr*g0/gamma2(c-b-k); - } - if (mab == 0) { zf0 = 0.0; } - zc1 = gc/(ga*gcb*std::pow(-z, a)); - sp = -2.0*el - pa- pca; - for (j = 1; j < (mab+1); j++) { - sp += 1.0 / j; - } - zp0 = sp + std::log(-z); - sq = 1.0; - for (j = 1; j < (mab+1); j++) { - sq = sq * (b+j-1.0)*(b-c+j)/j; - } - zf1 = (sq*zp0)*zc1; - zr = zc1; - rk1 = 1.0; - sj1 = 0.0; - w0 = 0.0; - for (k = 1; k < 10001; k++) { - zr /= z; - rk1 = rk1*(b+k-1.0)*(b-c+k)/(k*k); - rk2 = rk1; - for (j = k+1; j <= (k+mab); j++) { - rk2 = rk2 * (b+j-1.0)*(b-c+j)/j; - } - sj1 += (a-1.0)/(k*(a+k-1.0)) + (a-c-1.0)/(k*(a-c+k-1.0)); - sj2 = sj1; - for (j = k+1; j <= (k+mab); j++) { - sj2 += 1.0 / j; - } - zp= -2.0*el -pa - pac + sj2 - 1.0/(k+a-c) - pi/tan(pi*(k+a-c)) + std::log(-z); - zf1 += rk2*zr*zp; - ws = std::abs(zf1); - if (fabs((ws-w0)/ws) < eps) { break; } - w0 = ws; - } - zhf = zf0 + zf1; - } - } - a = aa; - b = bb; - if (k > 150) { *isfer = 5; } - return zhf; -} - -inline Status jdzo(int nt, double *zo, int *n, int *m, int *p) { - - // =========================================================== - // Purpose: Compute the zeros of Bessel functions Jn(x) and - // Jn'(x), and arrange them in the order of their - // magnitudes - // Input : NT --- Number of total zeros ( NT ≤ 1200 ) - // Output: ZO(L) --- Value of the L-th zero of Jn(x) - // and Jn'(x) - // N(L) --- n, order of Jn(x) or Jn'(x) associated - // with the L-th zero - // M(L) --- m, serial number of the zeros of Jn(x) - // or Jn'(x) associated with the L-th zero - // ( L is the serial number of all the - // zeros of Jn(x) and Jn'(x) ) - // P(L) --- 0 (TM) or 1 (TE), a code for designating the - // zeros of Jn(x) or Jn'(x). - // In the waveguide applications, the zeros - // of Jn(x) correspond to TM modes and - // those of Jn'(x) correspond to TE modes - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routine called: BJNDD for computing Jn(x), Jn'(x) and - // Jn''(x) - // ============================================================= - - int i, j, k, L, L0, L1, L2, mm, nm; - double x, x0, x1, x2, xm; - - auto p1 = std::unique_ptr{new (std::nothrow) int[70]()}; - - // Compared to specfun.f we use a single array instead of separate - // three arrays and use pointer arithmetic to access. Their usage - // is pretty much one-shot hence does not complicate the code. - - // Note: ZO and ZOC arrays are 0-indexed in specfun.f - - // m1, n1, zoc -> 70 + 70 + 71 - auto mnzoc = std::unique_ptr{new (std::nothrow) double[211]()}; - - // bj, dj, fj -> 101 + 101 + 101 - auto bdfj = std::unique_ptr{new (std::nothrow) double[303]()}; - - if (p1.get() == nullptr || mnzoc.get() == nullptr || bdfj.get() == nullptr) { - return Status::NoMemory; - } - - x = 0; - - if (nt < 600) { - xm = -1.0 + 2.248485*sqrt(nt) - 0.0159382*nt + 3.208775e-4*pow(nt, 1.5); - nm = (int)(14.5 + 0.05875*nt); - mm = (int)(0.02*nt) + 6; - } else { - xm = 5.0 + 1.445389*sqrt(nt) + 0.01889876*nt - 2.147763e-4*pow(nt, 1.5); - nm = (int)(27.8 + 0.0327*nt); - mm = (int)(0.01088*nt) + 10; - } - - L0 = 0; - /* 45 LOOP */ - for (i = 1; i < (nm+1); i++) { - x1 = 0.407658 + 0.4795504*sqrt(i-1.0) + 0.983618*(i-1); - x2 = 1.99535 + 0.8333883*sqrt(i-1.0) + 0.984584*(i-1); - L1 = 0; - - /* 30 LOOP */ - for (j = 1; j < (mm+1); j++) { - if ((i != 1) || (j != 1)) { - x = x1; - do - { - bjndd(x, i, &bdfj[0], &bdfj[101], &bdfj[202]); - x0 = x; - x -= bdfj[100+i]/bdfj[201+i]; - if (x1 > xm) { goto L20; } - } while (fabs(x-x0) > 1e-10); - } - /* 15 */ - L1 += 1; - mnzoc[69 + L1] = i-1; /* N[L1] */ - mnzoc[L1-1] = j; /* M[L1] */ - if (i == 1) { mnzoc[L1 - 1] = j-1; } - p1[L1-1] = 1; - mnzoc[140+L1] = x; /* ZOC[L1] */ - if (i <= 15) { - x1 = x + 3.057 + 0.0122*(i-1) + (1.555 + 0.41575*(i-1))/pow(j+1, 2.0); - } else { - x1 = x + 2.918 + 0.01924*(i-1) + (6.26 + 0.13205*(i-1))/pow(j+1, 2.0); - } -L20: - x = x2; - do { - bjndd(x, i, &bdfj[0], &bdfj[101], &bdfj[202]); - x0 = x; - x -= bdfj[i-1]/bdfj[100+i]; - if (x > xm) { goto L30; } /* Need to "continue;" twice hence goto is simpler */ - } while (fabs(x-x0) > 1e-10); - L1 += 1; - mnzoc[69 + L1] = i-1; - mnzoc[L1-1] = j; - p1[L1-1] = 0; - mnzoc[140+L1] = x; - if (i <= 15) { - x2 = x + 3.11 + 0.0138*(i-1) + (0.04832 + 0.2804*(i-1))/pow(j+1, 2); - } else { - x2 = x + 3.001 + 0.0105*(i-1) + (11.52 + 0.48525*(i-1))/pow(j+3, 2); - } -L30: - ; /* Do nothing line to silence compiler */ - } - L = L0 + L1; - L2 = L; - do { - if (L0 == 0) { - for (k = 1; k < (L+1); k++) { - p[k-1] = p1[k-1]; - m[k-1] = mnzoc[k-1]; /* m[k-1] = mnzoc[k-1] */ - n[k-1] = mnzoc[69+k]; /* n[k-1] = mnzoc[70 + (k-1)] */ - zo[k] = mnzoc[140+k]; - } - L1 = 0; - } else if (L0 != 0) { - if (zo[L0] >= mnzoc[140+L1]) { - p[L0+L1-1] = p[L0-1]; - m[L0+L1-1] = m[L0-1]; - n[L0+L1-1] = n[L0-1]; - zo[L0+L1] = zo[L0]; - L0 -= 1; - } else { - p[L0+L1-1] = p1[L1-1]; - m[L0+L1-1] = mnzoc[L1-1]; - n[L0+L1-1] = mnzoc[69+L1]; - zo[L0+L1] = mnzoc[140+L1]; - L1 -= 1; - } - } - } while (L1 != 0); - /* 45 */ - L0 = L2; - } - return Status::OK; -} - - -template -void jynb(int n, T x, int *nm, T *bj, T *dj, T *by, T *dy) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x), Yn(x) and - // their derivatives - // Input : x --- Argument of Jn(x) and Yn(x) ( x ≥ 0 ) - // n --- Order of Jn(x) and Yn(x) - // Output: BJ(n) --- Jn(x) - // DJ(n) --- Jn'(x) - // BY(n) --- Yn(x) - // DY(n) --- Yn'(x) - // NM --- Highest order computed - // Routines called: - // JYNBH to calculate the Jn and Yn - // ===================================================== - - int k; - jynbh(n, 0, x, nm, bj, by); - // Compute derivatives by differentiation formulas - if (x < 1.0e-100) { - for (k = 0; k <= n; k++) { - dj[k] = 0.0; - dy[k] = 1.0e+300; - } - dj[1] = 0.5; - } else { - dj[0] = -bj[1]; - for (k = 1; k <= *nm; k++) { - dj[k] = bj[k - 1] - k / x * bj[k]; - } - - dy[0] = -by[1]; - for (k = 1; k <= *nm; k++) { - dy[k] = by[k - 1] - k * by[k] / x; - } - } - return; -} - - -template -void jynbh(int n, int nmin, T x, int *nm, T *bj, T *by) { - - // ===================================================== - // Purpose: Compute Bessel functions Jn(x), Yn(x) - // Input : x --- Argument of Jn(x) and Yn(x) ( x ≥ 0 ) - // n --- Highest order of Jn(x) and Yn(x) computed ( n ≥ 0 ) - // nmin -- Lowest order computed ( nmin ≥ 0 ) - // Output: BJ(n-NMIN) --- Jn(x) ; if indexing starts at 0 - // BY(n-NMIN) --- Yn(x) ; if indexing starts at 0 - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 to calculate the starting - // point for backward recurrence - // ===================================================== - - int k, m, ky; - T pi = 3.141592653589793; - T r2p = 0.63661977236758; - T bs, s0, su, sv, f2, f1, f; - T bj0, bj1, ec, by0, by1, bjk, byk; - T p0, q0, cu, t1, p1, q1, t2; - - T a[4] = { -0.0703125, 0.112152099609375, -0.5725014209747314, 0.6074042001273483e+01 }; - T b[4] = { 0.0732421875, -0.2271080017089844, 0.1727727502584457e+01, -0.2438052969955606e+02 }; - T a1[4] = { 0.1171875, -0.144195556640625, 0.6765925884246826, -0.6883914268109947e+01 }; - T b1[4] = { -0.1025390625, 0.2775764465332031, -0.1993531733751297e+01, 0.2724882731126854e+02 }; - - *nm = n; - if (x < 1.0e-100) { - for (k = nmin; k <= n; k++) { - bj[k - nmin] = 0.0; - by[k - nmin] = -1.0e+300; - } - - if (nmin == 0) { bj[0] = 1.0; } - return; - } - - if ((x <= 300.0) || (n > (int)(0.9 * x))) { - // Backward recurrence for Jn - if (n == 0) { - *nm = 1; - } - m = msta1(x, 200); - if (m < *nm) { - *nm = m; - } else { - m = msta2(x, *nm, 15); - } - bs = 0.0; - su = 0.0; - sv = 0.0; - f2 = 0.0; - f1 = 1.0e-100; - f = 0.0; - - for (k = m; k >= 0; k--) { - f = 2.0*(k + 1.0)/x*f1 - f2; - if ((k <= *nm) && (k >= nmin)) { - bj[k - nmin] = f; - } - if (k == 2 * (int)(k / 2) && k != 0) { - bs += 2.0 * f; - su += pow(-1, k / 2) * f / k; - } else if (k > 1) { - sv += pow(-1, k / 2) * k / (k * k - 1.0) * f; - } - f2 = f1; - f1 = f; - } - s0 = bs + f; - - for (k = nmin; k <= *nm; k++) { - bj[k - nmin] /= s0; - } - // Estimates for Yn at start of recurrence - bj0 = f1 / s0; - bj1 = f2 / s0; - ec = log(x / 2.0) + 0.5772156649015329; - by0 = r2p * (ec * bj0 - 4.0*su/s0); - by1 = r2p * ((ec - 1.0)*bj1 - bj0/x - 4.0*sv/s0); - - if (0 >= nmin) { by[0 - nmin] = by0; } - if (1 >= nmin) { by[1 - nmin] = by1; } - ky = 2; - } else { - // Hankel expansion - t1 = x - 0.25*pi; - p0 = 1.0; - q0 = -0.125/x; - - for (k = 1; k <= 4; k++) { - p0 += a[k - 1] * pow(x,-2*k); - q0 += b[k - 1] * pow(x, -2*k - 1); - } - - cu = sqrt(r2p / x); - bj0 = cu * (p0*cos(t1) - q0*sin(t1)); - by0 = cu * (p0*sin(t1) + q0*cos(t1)); - - if (0 >= nmin) { - bj[0 - nmin] = bj0; - by[0 - nmin] = by0; - } - - t2 = x - 0.75*pi; - p1 = 1.0; - q1 = 0.375/x; - - for (k = 1; k <= 4; k++) { - p1 += a1[k - 1] * pow(x, -2*k); - q1 += b1[k - 1] * pow(x, -2*k - 1); - } - - bj1 = cu * (p1*cos(t2) - q1*sin(t2)); - by1 = cu * (p1*sin(t2) + q1*cos(t2)); - - if (1 >= nmin) { - bj[1 - nmin] = bj1; - by[1 - nmin] = by1; - } - - for (k = 2; k <= *nm; k++) { - bjk = 2.0*(k - 1.0)/x*bj1 - bj0; - if (k >= nmin) { bj[k - nmin] = bjk; } - bj0 = bj1; - bj1 = bjk; - } - ky = 2; - } - // Forward recurrence for Yn - for (k = ky; k <= *nm; k++) { - byk = 2.0 * (k - 1.0) * by1 / x - by0; - - if (k >= nmin) - by[k - nmin] = byk; - - by0 = by1; - by1 = byk; - } -} - - -inline void jyndd(int n, double x, double *bjn, double *djn, double *fjn, double *byn, double *dyn, double *fyn) { - - // =========================================================== - // purpose: compute bessel functions jn(x) and yn(x), and - // their first and second derivatives - // input: x --- argument of jn(x) and yn(x) ( x > 0 ) - // n --- order of jn(x) and yn(x) - // output: bjn --- jn(x) - // djn --- jn'(x) - // fjn --- jn"(x) - // byn --- yn(x) - // dyn --- yn'(x) - // fyn --- yn"(x) - // routines called: - // jynbh to compute jn and yn - // =========================================================== - - int nm = 0; - double bj[2], by[2]; - - jynbh(n+1, n, x, &nm, bj, by); - // compute derivatives by differentiation formulas - *bjn = bj[0]; - *byn = by[0]; - *djn = -bj[1] + n*bj[0]/x; - *dyn = -by[1] + n*by[0]/x; - *fjn = (n*n/(x*x) - 1.0)*(*bjn) - (*djn)/x; - *fyn = (n*n/(x*x) - 1.0)*(*byn) - (*dyn)/x; - return; -} - - -inline void jyzo(int n, int nt, double *rj0, double *rj1, double *ry0, double *ry1) { - - // ====================================================== - // Purpose: Compute the zeros of Bessel functions Jn(x), - // Yn(x), and their derivatives - // Input : n --- Order of Bessel functions (n >= 0) - // NT --- Number of zeros (roots) - // Output: RJ0(L) --- L-th zero of Jn(x), L=1,2,...,NT - // RJ1(L) --- L-th zero of Jn'(x), L=1,2,...,NT - // RY0(L) --- L-th zero of Yn(x), L=1,2,...,NT - // RY1(L) --- L-th zero of Yn'(x), L=1,2,...,NT - // Routine called: JYNDD for computing Jn(x), Yn(x), and - // their first and second derivatives - // ====================================================== - - /* - * SciPy Note: - * See GH-18859 for additional changes done by SciPy for - * better initial condition selection in Newton iteration - */ - - int L; - double b, h, x, x0, bjn, djn, fjn, byn, dyn, fyn; - const double pi = 3.141592653589793; - // -- Newton method for j_{N,L} - // initial guess for j_{N,1} - if (n == 0) { - x = 2.4; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 1.85576*pow(n, 0.33333) + 1.03315/ pow(n, 0.33333); - } - // iterate - L = 0; -L10: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= bjn/djn; - if (fabs(x - x0) > 1e-11) { goto L10; } - - L += 1; - rj0[L - 1] = x; - // initial guess for j_{N,L+1} - if (L == 1) { - if (n == 0) { - x = 5.52; - } else { - // Expansion from https://dlmf.nist.gov/10.21#E32 and - // coefficients from Olver 1951 - x= n + 3.24460 * pow(n, 0.33333) + 3.15824 / pow(n, 0.33333); - } - } else { - // growth of roots is approximately linear (https://dlmf.nist.gov/10.21#E19) - x = rj0[L - 1] + (rj0[L - 1] - rj0[L - 2]); - } - if (L <= (n + 10)) { - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - h = atan(fabs(djn) / sqrt(fabs(fjn * bjn))); - b = -djn / (bjn * atan(h)); - x -= (h - pi/2) / b; - } - - if (L < nt) { goto L10; } - - // -- Newton method for j_{N,L+1}' - if (n == 0) { - x = 3.8317; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 0.80861 * pow(n, 0.33333) + 0.07249 / pow(n, 0.33333); - } - // iterate - L=0; -L15: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= djn/fjn; - if (fabs(x-x0) > 1e-11) goto L15; - L += 1; - rj1[L - 1] = x; - if (L < nt) { - // https://dlmf.nist.gov/10.21#E20 - x = rj1[L - 1] + (rj0[L] - rj0[L - 1]); - goto L15; - } - - // -- Newton method for y_{N,L} - // initial guess for y_{N,1} - if (n == 0) { - x = 0.89357697; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 0.93158 * pow(n, 0.33333) + 0.26035 / pow(n, 0.33333); - } - // iterate - L=0; -L20: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= byn/dyn; - if (fabs(x - x0) > 1.0e-11) goto L20; - L += 1; - ry0[L - 1] = x; - // initial guess for y_{N,L+1} - if (L == 1) { - if (n == 0) { - x = 3.957678419314858; - } else { - // Expansion from https://dlmf.nist.gov/10.21#E33 and - // coefficients from Olver 1951 - x = n + 2.59626 * pow(n, 0.33333) + 2.022183 / pow(n, 0.33333); - } - } else { - // growth of roots is approximately linear (https://dlmf.nist.gov/10.21#E19) - x = ry0[L - 1] + (ry0[L - 1] - ry0[L - 2]); - } - if (L <= n+10) { - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - h = atan(fabs(dyn) / sqrt(fabs(fyn * byn))); - b = -dyn / (byn * tan(h)); - x -= (h - pi/2) / b; - } - - if (L < nt) goto L20; - - // -- Newton method for y_{N,L+1}' - if (n == 0) { - x = 2.67257; - } else { - // https://dlmf.nist.gov/10.21#E40 - x = n + 1.8211 * pow(n, 0.33333) + 0.94001 / pow(n, 0.33333); - } - // iterate - L=0; -L25: - x0 = x; - jyndd(n, x, &bjn, &djn, &fjn, &byn, &dyn, &fyn); - x -= dyn/fyn; - if (fabs(x-x0) > 1.0e-11) goto L25; - L += 1; - ry1[L - 1] = x; - if (L < nt) { - // https://dlmf.nist.gov/10.21#E20 - x=ry1[L - 1] + (ry0[L] - ry0[L - 1]); - goto L25; - } - return; -} - - -template -inline Status kmn(int m, int n, T c, T cv, int kd, T *df, T *dn, T *ck1, T *ck2) { - - // =================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions - // and joining factors - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // =================================================== - - int nm, nn, ip, k, i, l, j; - T cs, gk0, gk1, gk2, gk3, t, r, dnp, su0, sw, r1, r2, r3, sa0, r4, r5, g0, sb0; - nm = 25 + (int)(0.5 * (n - m) + c); - nn = nm + m; - auto u = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto v = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto w = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto tp = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - auto rk = std::unique_ptr{new (std::nothrow) T[nn + 4]}; - if (u.get() == nullptr || v.get() == nullptr || w.get() == nullptr - || tp.get() == nullptr || rk.get() == nullptr) { - return Status::NoMemory; - } - - const T eps = 1.0e-14; - - cs = c * c * kd; - *ck1 = 0.0; - *ck2 = 0.0; - - ip = ((n - m) % 2 == 0 ? 0 : 1); - k = 0; - - for (i = 1; i <= nn + 3; i++) { - k = (ip == 0 ? -2 * (i - 1) : -(2 * i - 3)); - gk0 = 2.0 * m + k; - gk1 = (m + k) * (m + k + 1.0); - gk2 = 2.0 * (m + k) - 1.0; - gk3 = 2.0 * (m + k) + 3.0; - - u[i - 1] = gk0 * (gk0 - 1.0) * cs / (gk2 * (gk2 + 2.0)); - v[i - 1] = gk1 - cv + (2.0 * (gk1 - m * m) - 1.0) * cs / (gk2 * gk3); - w[i - 1] = (k + 1.0) * (k + 2.0) * cs / ((gk2 + 2.0) * gk3); - } - - for (k = 1; k <= m; k++) { - t = v[m]; - for (l = 0; l <= m - k - 1; l++) - t = v[m - l - 1] - w[m - l] * u[m - l - 1] / t; - - rk[k - 1] = -u[k - 1] / t; - } - - r = 1.0; - for (k = 1; k <= m; k++) { - r = r * rk[k - 1]; - dn[k - 1] = df[0] * r; - } - - tp[nn - 1] = v[nn]; - - for (k = nn - 1; k >= m + 1; k--) { - tp[k - 1] = v[k] - w[k + 1] * u[k] / tp[k]; - - if (k > m + 1) - rk[k - 1] = -u[k - 1] / tp[k - 1]; - } - - dnp = (m == 0 ? df[0] : dn[m - 1]); - dn[m] = pow(-1, ip) * dnp * cs / ((2.0 * m - 1.0) * (2.0 * m + 1.0 - 4.0 * ip) * tp[m]); - - for (k = m + 2; k <= nn; k++) - dn[k - 1] = rk[k - 1] * dn[k - 2]; - - r1 = 1.0; - for (j = 1; j <= (n + m + ip) / 2; j++) { - r1 = r1*(j + 0.5 * (n + m + ip)); - } - r = 1.0; - for (j = 1; j <= 2 * m + ip; ++j){ - r *= j; - } - su0 = r * df[0]; - sw = 0.0; - - for (k = 2; k <= nm; ++k) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - su0 = su0 + r * df[k - 1]; - if (k > (n - m) / 2 && fabs((su0 - sw) / su0) < eps) { break; } - sw = su0; - } - - if (kd != 1) { - r2 = 1.0; - - for (j = 1; j <= m; ++j) - r2 = 2.0 * c * r2 * j; - - r3 = 1.0; - - for (j = 1; j <= (n - m - ip) / 2; ++j) - r3 = r3 * j; - - sa0 = (2.0 * (m + ip) + 1.0) * r1 / (pow(2.0, n) * pow(c, ip) * r2 * r3 * df[0]); - *ck1 = sa0 * su0; - - if (kd == -1) { - return Status::OK; - } - } - - r4 = 1.0; - for (j = 1; j <= (n - m - ip) / 2; ++j) { - r4 *= 4.0 * j; - } - r5 = 1.0; - for (j = 1; j <= m; ++j) - r5 = r5 * (j + m) / c; - - if (m == 0) - g0 = df[0]; - else - g0 = dn[m - 1]; - - sb0 = (ip + 1.0) * pow(c, ip + 1) / (2.0 * ip * (m - 2.0) + 1.0) / (2.0 * m - 1.0); - *ck2 = pow(-1, ip) * sb0 * r4 * r5 * g0 / r1 * su0; - - return Status::OK; -} - - -inline void lamn(int n, double x, int *nm, double *bl, double *dl) { - - // ========================================================= - // Purpose: Compute lambda functions and their derivatives - // Input: x --- Argument of lambda function - // n --- Order of lambda function - // Output: BL(n) --- Lambda function of order n - // DL(n) --- Derivative of lambda function - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the start - // point for backward recurrence - // ========================================================= - - int i, k, m; - double bk, r, uk, bs, f, f0, f1, bg, r0, x2; - - *nm = n; - if (fabs(x) < 1e-100) { - for (k = 0; k <= n; k++) { - bl[k] = 0.0; - dl[k] = 0.0; - } - bl[0] = 1.0; - dl[1] = 0.5; - return; - } - if (x <= 12.0) { - x2 = x * x; - for (k = 0; k <= n; k++) { - bk = 1.0; - r = 1.0; - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + k)); - bk += r; - - if (fabs(r) < fabs(bk) * 1.0e-15) { break; } - } - bl[k] = bk; - if (k >= 1) { - dl[k - 1] = -0.5 * x / k * bk; - } - } - uk = 1.0; - r = 1.0; - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + n + 1.0)); - uk += r; - - if (fabs(r) < fabs(uk) * 1.0e-15) { break; } - } - dl[n] = -0.5 * x / (n + 1.0) * uk; - return; - } - if (n == 0) { - *nm = 1; - } - m = msta1(x, 200); - if (m < *nm) { - *nm = m; - } else { - m = msta2(x, *nm, 15); - } - bs = 0.0; - f = 0.0; - f0 = 0.0; - f1 = 1e-100; - for (k = m; k >= 0; k--) { - f = 2.0 * (k + 1.0) * f1 / x - f0; - if (k <= *nm) { - bl[k] = f; - } - if (k % 2 == 0) { - bs += 2.0 * f; - } - f0 = f1; - f1 = f; - } - bg = bs - f; - for (k = 0; k <= *nm; k++) { - bl[k] /= bg; - } - r0 = 1.0; - for (k = 1; k <= *nm; k++) { - r0 = 2.0 * r0 * k / x; - bl[k] *= r0; - } - dl[0] = -0.5 * x * bl[1]; - for (k = 1; k <= *nm; k++) { - dl[k] = 2.0 * k / x * (bl[k - 1] - bl[k]); - } - return; -} - - -inline void lamv(double v, double x, double *vm, double *vl, double *dl) { - - // ========================================================= - // Purpose: Compute lambda function with arbitrary order v, - // and their derivative - // Input : x --- Argument of lambda function - // v --- Order of lambda function - // Output: VL(n) --- Lambda function of order n+v0 - // DL(n) --- Derivative of lambda function - // VM --- Highest order computed - // Routines called: - // (1) MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // (2) GAM0 for computing gamma function (|x| ≤ 1) - // ========================================================= - - int i, n, k, j, k0, m; - double cs, ga, fac, r0, f0, f1, f2, f, xk, vv; - double x2, v0, vk, bk, r, uk, qx, px, rp, a0, ck, sk, bjv0, bjv1; - const double pi = 3.141592653589793; - const double rp2 = 0.63661977236758; - - x = fabs(x); - x2 = x * x; - n = (int)v; - v0 = v - n; - *vm = v; - - if (x <= 12.0) { - for (k = 0; k <= n; k++) { - vk = v0 + k; - bk = 1.0; - r = 1.0; - - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + vk)); - bk = bk + r; - - if (fabs(r) < fabs(bk) * 1.0e-15) - break; - } - vl[k] = bk; - - uk = 1.0; - r = 1.0; - - for (i = 1; i <= 50; i++) { - r = -0.25 * r * x2 / (i * (i + vk + 1.0)); - uk = uk + r; - - if (fabs(r) < fabs(uk) * 1.0e-15) - break; - } - dl[k] = -0.5 * x / (vk + 1.0) * uk; - } - return; - } - - k0 = (x >= 50.0) ? 8 : ((x >= 35.0) ? 10 : 11); - bjv0 = 0.0; - bjv1 = 0.0; - - for (j = 0; j <= 1; j++) { - vv = 4.0 * (j + v0) * (j + v0); - px = 1.0; - rp = 1.0; - for (k = 1; k <= k0; k++) { - rp = -0.78125e-2 * rp * (vv - pow((4.0 * k - 3.0), 2.0)) * (vv - pow((4.0 * k - 1.0), 2.0)) / (k * (2.0 * k - 1.0) * x2); - px += rp; - } - - qx = 1.0; - rp = 1.0; - for (k = 1; k <= k0; k++) { - rp = -0.78125e-2 * rp * (vv - pow((4.0 * k - 1.0), 2.0)) * (vv - pow((4.0 * k + 1.0), 2.0)) / (k * (2.0 * k + 1.0) * x2); - qx += rp; - } - - qx = 0.125 * (vv - 1.0) * qx / x; - xk = x - (0.5 * (j + v0) + 0.25) * pi; - a0 = sqrt(rp2 / x); - ck = cos(xk); - sk = sin(xk); - - if (j == 0) bjv0 = a0 * (px * ck - qx * sk); - if (j == 1) bjv1 = a0 * (px * ck - qx * sk); - } - - if (v0 == 0.0) { - ga = 1.0; - } else { - ga = gam0(v0); - ga *= v0; - } - - fac = pow(2.0 / x, v0) * ga; - vl[0] = bjv0; - dl[0] = -bjv1 + v0 / x * bjv0; - vl[1] = bjv1; - dl[1] = bjv0 - (1.0 + v0) / x * bjv1; - r0 = 2.0 * (1.0 + v0) / x; - - if (n <= 1) { - vl[0] *= fac; - dl[0] = fac * dl[0] - v0 / x * vl[0]; - vl[1] *= fac * r0; - dl[1] = fac * r0 * dl[1] - (1.0 + v0) / x * vl[1]; - return; - } - - if (n >= 2 && n <= (int)(0.9 * x)) { - f0 = bjv0; - f1 = bjv1; - - for (k = 2; k <= n; k++) { - f = 2.0 * (k + v0 - 1.0) / x * f1 - f0; - f0 = f1; - f1 = f; - vl[k] = f; - } - } else if (n >= 2) { - m = msta1(x, 200); - if (m < n) { - n = m; - } else { - m = msta2(x, n, 15); - } - - f = 0.0; - f2 = 0.0; - f1 = 1.0e-100; - - for (k = m; k >= 0; k--) { - f = 2.0 * (v0 + k + 1.0) / x * f1 - f2; - if (k <= n) vl[k] = f; - f2 = f1; - f1 = f; - } - - cs = 0.0; - if (fabs(bjv0) > fabs(bjv1)) { - cs = bjv0 / f; - } else { - cs = bjv1 / f2; - } - - for (k = 0; k <= n; k++) { - vl[k] *= cs; - } - } - - vl[0] *= fac; - for (j = 1; j <= n; j++) { - vl[j] *= fac * r0; - dl[j - 1] = -0.5 * x / (j + v0) * vl[j]; - r0 = 2.0 * (j + v0 + 1) / x * r0; - } - - dl[n] = 2.0 * (v0 + n) * (vl[n - 1] - vl[n]) / x; - *vm = n + v0; - return; -} - - -template -void lpmns(int m, int n, T x, T* pm, T* pd) { - - // ======================================================== - // Purpose: Compute associated Legendre functions Pmn(x) - // and Pmn'(x) for a given order - // Input : x --- Argument of Pmn(x) - // m --- Order of Pmn(x), m = 0,1,2,...,n - // n --- Degree of Pmn(x), n = 0,1,2,...,N - // Output: PM(n) --- Pmn(x) - // PD(n) --- Pmn'(x) - // ======================================================== - - int k; - T coef, x0, pm0, pm1, pm2, pmk; - for (k = 0; k <= n; k++) { - pm[k] = 0.0; - pd[k] = 0.0; - } - - if (fabs(x) == 1.0) { - for (k = 0; k <= n; k++) { - if (m == 0) { - pm[k] = 1.0; - pd[k] = 0.5 * k * (k + 1.0); - if (x < 0.0) { - pm[k] *= ((k % 2) == 0 ? 1 : -1 ); - pd[k] *= (((k + 1) % 2) == 0 ? 1 : -1 ); - } - } else if (m == 1) { - pd[k] = 1e300; - } else if (m == 2) { - pd[k] = -0.25 * (k + 2.0) * (k + 1.0) * k * (k - 1.0); - if (x < 0.0) - pd[k] *= (((k + 1) % 2) == 0 ? 1 : -1 ); - } - } - return; - } - - x0 = fabs(1.0 - x * x); - pm0 = 1.0; - pmk = pm0; - for (k = 1; k <= m; k++) { - pmk = (2.0 * k - 1.0) * sqrt(x0) * pm0; - pm0 = pmk; - } - pm1 = (2.0 * m + 1.0) * x * pm0; - pm[m] = pmk; - pm[m + 1] = pm1; - for (k = m + 2; k <= n; k++) { - pm2 = ((2.0 * k - 1.0) * x * pm1 - (k + m - 1.0) * pmk) / (k - m); - pm[k] = pm2; - pmk = pm1; - pm1 = pm2; - } - - pd[0] = ((1.0 - m) * pm[1] - x * pm[0]) / (x * x - 1.0); - for (k = 1; k <= n; k++) { - pd[k] = (k * x * pm[k] - (k + m) * pm[k - 1]) / (x * x - 1.0); - } - coef = ((m % 2) == 0 ? 1 : -1 ); - for (k = 1; k <= n; k++) { - pm[k] *= coef; - pd[k] *= coef; - } - return; -} - - -inline double lpmv(double x, int m, double v) { - - // ======================================================= - // Purpose: Compute the associated Legendre function - // Pmv(x) with an integer order and an arbitrary - // degree v, using recursion for large degrees - // Input : x --- Argument of Pm(x) ( -1 ≤ x ≤ 1 ) - // m --- Order of Pmv(x) - // v --- Degree of Pmv(x) - // Output: PMV --- Pmv(x) - // Routine called: LPMV0 - // ======================================================= - - int mx, neg_m, nv, j; - double vx, pmv, v0, p0, p1, g1, g2; - if ((x == -1.0) && (v != (int)v)) { - if (m == 0) { - pmv = -1e300; - } else { - pmv = 1e300; - } - return pmv; - } - vx = v; - mx = m; - // DLMF 14.9.5 - if (v < 0) { vx = -vx -1.0; } - neg_m = 0; - if (m < 0) { - if (((vx+m+1) > 0) || (vx != (int)vx)) { - neg_m = 1; - mx = -m; - } else { - // We don't handle cases where DLMF 14.9.3 doesn't help - return NAN; - } - } - nv = (int)vx; - v0 = vx - nv; - if ((nv > 2) && (nv > mx)) { - // Up-recursion on degree, AMS 8.5.3 / DLMF 14.10.3 - p0 = lpmv0(v0+mx, mx, x); - p1 = lpmv0(v0+mx+1, mx, x); - pmv = p1; - for (j = mx+2; j <= nv; j++) { - pmv = ((2*(v0+j)-1)*x*p1 - (v0+j-1+mx)*p0) / (v0+j-mx); - p0 = p1; - p1 = pmv; - } - } else { - pmv = lpmv0(vx, mx, x); - } - if ((neg_m != 0) && (fabs(pmv) < 1.e300)) { - // DLMF 14.9.3 - g1 = gamma2(vx-mx+1); - g2 = gamma2(vx+mx+1); - pmv = pmv*g1/g2 * pow(-1, mx); - } - return pmv; -} - - -inline double lpmv0(double v, int m, double x) { - - // ======================================================= - // Purpose: Compute the associated Legendre function - // Pmv(x) with an integer order and an arbitrary - // nonnegative degree v - // Input : x --- Argument of Pm(x) ( -1 ≤ x ≤ 1 ) - // m --- Order of Pmv(x) - // v --- Degree of Pmv(x) - // Output: PMV --- Pmv(x) - // Routine called: PSI_SPEC for computing Psi function - // ======================================================= - - int j, k, nv; - double c0, v0, vs, pa, pss, pv0, pmv, r, r0, r1, r2, s, s0, s1, s2, qr, rg, xq; - - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - const double eps = 1e-14; - - nv = (int)v; - v0 = v - nv; - - if (x == -1.0 && v != nv) { - if (m == 0) - return -1.0e+300; - if (m != 0) - return 1.0e+300; - } - - c0 = 1.0; - if (m != 0) { - rg = v * (v + m); - for (j = 1; j <= m - 1; j++) { - rg *= (v * v - j * j); - } - xq = sqrt(1.0 - x*x); - r0 = 1.0; - for (j = 1; j <= m; j++) { - r0 = 0.5*r0*xq/j; - } - c0 = r0*rg; - } - - if (v0 == 0.0) { - // DLMF 14.3.4, 14.7.17, 15.2.4 - pmv = 1.0; - r = 1.0; - for (k = 1; k <= nv - m; k++) { - r = 0.5 * r * (-nv + m + k - 1.0) * (nv + m + k) / (k * (k + m)) * (1.0 + x); - pmv += r; - } - return pow(-1, nv)*c0*pmv; - } else { - if (x >= -0.35) { - // DLMF 14.3.4, 15.2.1 - pmv = 1.0; - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * (-v + m + k - 1.0) * (v + m + k) / (k * (m + k)) * (1.0 - x); - pmv += r; - if (k > 12 && fabs(r / pmv) < eps) { break; } - } - return pow(-1, m)*c0*pmv; - } else { - // DLMF 14.3.5, 15.8.10 - vs = sin(v * pi) / pi; - pv0 = 0.0; - if (m != 0) { - qr = sqrt((1.0 - x) / (1.0 + x)); - r2 = 1.0; - for (j = 1; j <= m; j++) { - r2 *= qr * j; - } - s0 = 1.0; - r1 = 1.0; - for (k = 1; k <= m - 1; k++) { - r1 = 0.5 * r1 * (-v + k - 1) * (v + k) / (k * (k - m)) * (1.0 + x); - s0 += r1; - } - pv0 = -vs * r2 / m * s0; - } - - pa = 2.0 * (psi_spec(v) + el) + pi / tan(pi * v) + 1.0 / v; - s1 = 0.0; - for (j = 1; j <= m; j++) { - s1 += (j * j + v * v) / (j * (j * j - v * v)); - } - pmv = pa + s1 - 1.0 / (m - v) + log(0.5 * (1.0 + x)); - r = 1.0; - for (k = 1; k <= 100; k++) { - r = 0.5 * r * (-v + m + k - 1.0) * (v + m + k) / (k * (k + m)) * (1.0 + x); - s = 0.0; - for (j = 1; j <= m; j++) - s += ((k + j) * (k + j) + v * v) / ((k + j) * ((k + j) * (k + j) - v * v)); - - s2 = 0.0; - for (j = 1; j <= k; j++) - s2 = s2 + 1.0 / (j * (j * j - v * v)); - - pss = pa + s + 2.0 * v * v * s2 - 1.0 / (m + k - v) + log(0.5 * (1.0 + x)); - r2 = pss * r; - pmv += r2; - if (fabs(r2 / pmv) < eps) { break; } - } - return pv0 + pmv * vs * c0; - } - } -} - - -template -inline void lqmns(int m, int n, T x, T *qm, T *qd) { - - // ======================================================== - // Purpose: Compute associated Legendre functions Qmn(x) - // and Qmn'(x) for a given order - // Input : x --- Argument of Qmn(x) - // m --- Order of Qmn(x), m = 0,1,2,... - // n --- Degree of Qmn(x), n = 0,1,2,... - // Output: QM(n) --- Qmn(x) - // QD(n) --- Qmn'(x) - // ======================================================== - - int l, ls, k, km; - T xq, q0, q00, q10, q01, q11, qf0, qf1, qm0, qm1, qg0, qg1, qh0, qh1,\ - qh2, qmk, q0l, q1l, qf2, val; - - val = 0.0; - if (fabs(x) == 1.0) { val = 1e300; } - for (k = 0; k <= n; k++) { - qm[k] = val; - qd[k] = val; - } - - if (fabs(x) == 1.0) { - return; - } - ls = (fabs(x) > 1.0 ? -1 : 1); - - xq = sqrt(ls*(1.0 - x*x)); - q0 = 0.5 * log(fabs((x + 1.0) / (x - 1.0))); - q00 = q0; - q10 = -1.0 / xq; - q01 = x*q0 - 1.0; - q11 = -ls*xq*(q0 + x / (1.0 - x*x)); - qf0 = q00; - qf1 = q10; - qm0 = 0.0; - qm1 = 0.0; - - for (k = 2; k <= m; k++) { - qm0 = -2.0 * (k-1.0) / xq * x * qf1 - ls * (k-1.0) * (2.0 - k) * qf0; - qf0 = qf1; - qf1 = qm0; - } - - if (m == 0) { - qm0 = q00; - } - if (m == 1) { - qm0 = q10; - } - - qm[0] = qm0; - - if (fabs(x) < 1.0001) { - if ((m == 0) && (n > 0)) { - qf0 = q00; - qf1 = q01; - for (k = 2; k <= n; k++) { - qf2 = ((2.0 * k - 1.0) * x * qf1 - (k - 1.0) * qf0) / k; - qm[k] = qf2; - qf0 = qf1; - qf1 = qf2; - } - } - - qg0 = q01; - qg1 = q11; - for (k = 2; k <= m; k++) { - qm1 = -2.0 * (k - 1.0) / xq * x * qg1 - ls * k * (3.0 - k) * qg0; - qg0 = qg1; - qg1 = qm1; - } - - if (m == 0) { - qm1 = q01; - } - if (m == 1) { - qm1 = q11; - } - - qm[1] = qm1; - - if ((m == 1) && (n > 1)) { - qh0 = q10; - qh1 = q11; - for (k = 2; k <= n; k++) { - qh2 = ((2.0 * k - 1.0) * x * qh1 - k * qh0) / (k - 1.0); - qm[k] = qh2; - qh0 = qh1; - qh1 = qh2; - } - } else if (m >= 2) { - qg0 = q00; - qg1 = q01; - qh0 = q10; - qh1 = q11; - qmk = 0.0; - for (l = 2; l <= n; l++) { - q0l = ((2.0 * l - 1.0) * x * qg1 - (l - 1.0) * qg0) / l; - q1l = ((2.0 * l - 1.0) * x * qh1 - l * qh0) / (l - 1.0); - qf0 = q0l; - qf1 = q1l; - for (k = 2; k <= m; k++) { - qmk = -2.0 * (k - 1.0) / xq * x * qf1 - ls * (k + l - 1.0) * (l + 2.0 - k) * qf0; - qf0 = qf1; - qf1 = qmk; - } - qm[l] = qmk; - qg0 = qg1; - qg1 = q0l; - qh0 = qh1; - qh1 = q1l; - } - } - } else { - if (fabs(x) > 1.1) { - km = 40 + m + n; - } - else { - km = (40 + m + n) * (int)(-1.0 - 1.8 * log(x - 1.0)); - } - qf2 = 0.0; - qf1 = 1.0; - for (k = km; k >= 0; k--) { - qf0 = ((2.0 * k + 3.0) * x * qf1 - (k + 2.0 - m) * qf2) / (k + m + 1.0); - if (k <= n) { - qm[k] = qf0; - } - qf2 = qf1; - qf1 = qf0; - } - for (k = 0; k <= n; k++) { - qm[k] = qm[k] * qm0 / qf0; - } - } - - if (fabs(x) < 1.0) { - for (k = 0; k <= n; k++) { - qm[k] = pow(-1, m) * qm[k]; - } - } - - qd[0] = ((1.0 - m) * qm[1] - x * qm[0]) / (x*x - 1.0); - for (k = 1; k <= n; k++) { - qd[k] = (k * x * qm[k] - (k + m) * qm[k-1]) / (x*x - 1.0); - } - return; -} - - -inline int msta1(double x, int mp) { - - // =================================================== - // Purpose: Determine the starting point for backward - // recurrence such that the magnitude of - // Jn(x) at that point is about 10^(-MP) - // Input : x --- Argument of Jn(x) - // MP --- Value of magnitude - // Output: MSTA1 --- Starting point - // =================================================== - - int it, nn, n0, n1; - double a0, f, f0, f1; - - a0 = fabs(x); - n0 = (int)(1.1*a0) + 1; - f0 = 0.5*log10(6.28*n0) - n0*log10(1.36*a0/n0)- mp; - n1 = n0 + 5; - f1 = 0.5*log10(6.28*n1) - n1*log10(1.36*a0/n1) - mp; - for (it = 1; it <= 20; it++) { - nn = n1 - (n1 - n0) / (1.0 - f0/f1); - f = 0.5*log10(6.28*nn) - nn*log10(1.36*a0/nn) - mp; - if (abs(nn-n1) < 1) { break; } - n0 = n1; - f0 = f1; - n1 = nn; - f1 = f; - } - return nn; -} - - -inline int msta2(double x, int n, int mp) { - - // =================================================== - // Purpose: Determine the starting point for backward - // recurrence such that all Jn(x) has MP - // significant digits - // Input : x --- Argument of Jn(x) - // n --- Order of Jn(x) - // MP --- Significant digit - // Output: MSTA2 --- Starting point - // =================================================== - - int it, n0, n1, nn; - double a0, hmp, ejn, obj, f, f0, f1; - - a0 = fabs(x); - hmp = 0.5*mp; - ejn = 0.5*log10(6.28*n) - n*log10(1.36*a0/n); - if (ejn <= hmp ) { - obj = mp; - n0 = (int)(1.1*a0) + 1; - } else { - obj = hmp + ejn; - n0 = n; - } - f0 = 0.5*log10(6.28*n0) - n0*log10(1.36*a0/n0) - obj; - n1 = n0 + 5; - f1 = 0.5*log10(6.28*n1) - n1*log10(1.36*a0/n1) - obj; - for (it = 1; it <= 20; it++) { - nn = n1 - (n1 - n0) / (1.0 - f0/f1); - f = 0.5*log10(6.28*nn) - nn*log10(1.36*a0/nn) - obj; - if (abs(nn-n1) < 1) { break; } - n0 = n1; - f0 = f1; - n1 = nn; - f1 = f; - } - return nn + 10; -} - - -template -Status mtu0(int kf, int m, T q, T x, T *csf, T *csd) { - - // =============================================================== - // Purpose: Compute Mathieu functions cem(x,q) and sem(x,q) - // and their derivatives ( q ≥ 0 ) - // Input : KF --- Function code - // KF=1 for computing cem(x,q) and cem'(x,q) - // KF=2 for computing sem(x,q) and sem'(x,q) - // m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // x --- Argument of Mathieu functions (in degrees) - // Output: CSF --- cem(x,q) or sem(x,q) - // CSD --- cem'x,q) or sem'x,q) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. The output - // values will be set to nan. - // Status::Other - // An internal check failed. For mtu0, this occurs - // when km (see the code) is too big. km is a - // function of q and m. The output values will be - // set to nan. - // - // Routines called: - // (1) CVA2 for computing the characteristic values - // (2) FCOEF for computing the expansion coefficients - // =============================================================== - - int kd = 0, km = 0, ic, k; - T a, qm, xr; - const T eps = 1.0e-14; - const T rd = 1.74532925199433e-2; - - if (kf == 1 && m == 2 * (int)(m / 2)) { kd = 1; } - if (kf == 1 && m != 2 * (int)(m / 2)) { kd = 2; } - if (kf == 2 && m != 2 * (int)(m / 2)) { kd = 3; } - if (kf == 2 && m == 2 * (int)(m / 2)) { kd = 4; } - - a = cva2(kd, m, q); - - if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - - if (km > 251) { - *csf = NAN; - *csd = NAN; - return Status::Other; - } - - auto fg = std::unique_ptr{new (std::nothrow) T[251]()}; - if (fg.get() == nullptr) { - *csf = NAN; - *csd = NAN; - return Status::NoMemory; - } - fcoef(kd, m, q, a, fg.get()); - - ic = (int)(m / 2) + 1; - xr = x * rd; - *csf = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *csf += fg[k - 1] * cos((2*k - 2) * xr); - } else if (kd == 2) { - *csf += fg[k - 1] * cos((2*k - 1) * xr); - } else if (kd == 3) { - *csf += fg[k - 1] * sin((2*k - 1) * xr); - } else if (kd == 4) { - *csf += fg[k - 1] * sin(2*k*xr); - } - if ((k >= ic) && (fabs(fg[k]) < fabs(*csf) * eps)) { - break; - } - } - - *csd = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *csd -= (2*k - 2) * fg[k - 1] * sin((2*k - 2) * xr); - } else if (kd == 2) { - *csd -= (2*k - 1) * fg[k - 1] * sin((2*k - 1) * xr); - } else if (kd == 3) { - *csd += (2*k - 1) * fg[k - 1] * cos((2*k - 1) * xr); - } else if (kd == 4) { - *csd += 2.0 * k * fg[k - 1] * cos(2*k*xr); - } - if ((k >= ic) && (fabs(fg[k - 1]) < fabs(*csd) * eps)) { - break; - } - } - return Status::OK; -} - - -template -Status mtu12(int kf, int kc, int m, T q, T x, T *f1r, T *d1r, T *f2r, T *d2r) { - - // ============================================================== - // Purpose: Compute modified Mathieu functions of the first and - // second kinds, Mcm(1)(2)(x,q) and Msm(1)(2)(x,q), - // and their derivatives - // Input: KF --- Function code - // KF=1 for computing Mcm(x,q) - // KF=2 for computing Msm(x,q) - // KC --- Function Code - // KC=1 for computing the first kind - // KC=2 for computing the second kind - // or Msm(2)(x,q) and Msm(2)'(x,q) - // KC=3 for computing both the first - // and second kinds - // m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions ( q ≥ 0 ) - // x --- Argument of Mathieu functions - // Output: F1R --- Mcm(1)(x,q) or Msm(1)(x,q) - // D1R --- Derivative of Mcm(1)(x,q) or Msm(1)(x,q) - // F2R --- Mcm(2)(x,q) or Msm(2)(x,q) - // D2R --- Derivative of Mcm(2)(x,q) or Msm(2)(x,q) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. The output - // values will be set to nan. - // Status::Other - // An internal check failed. For mtu12, this occurs - // when km (see the code) is too big. km is a - // function of q and m. The output values will be - // set to nan. - // Routines called: - // (1) CVA2 for computing the characteristic values - // (2) FCOEF for computing expansion coefficients - // (3) JYNB for computing Jn(x), Yn(x) and their - // derivatives - // ============================================================== - - T eps = 1.0e-14; - T a, qm, c1, c2, u1, u2, w1, w2; - int kd, km, ic, k, nm = 0; - - if ((kf == 1) && (m % 2 == 0)) { kd = 1; } - if ((kf == 1) && (m % 2 != 0)) { kd = 2; } - if ((kf == 2) && (m % 2 != 0)) { kd = 3; } - if ((kf == 2) && (m % 2 == 0)) { kd = 4; } - - a = cva2(kd, m, q); - - if (q <= 1.0) { - qm = 7.5 + 56.1 * sqrt(q) - 134.7 * q + 90.7 * sqrt(q) * q; - } else { - qm = 17.0 + 3.1 * sqrt(q) - 0.126 * q + 0.0037 * sqrt(q) * q; - } - - km = (int)(qm + 0.5 * m); - if (km >= 251) { - *f1r = NAN; - *d1r = NAN; - *f2r = NAN; - *d2r = NAN; - return Status::Other; - } - - // allocate memory after a possible NAN return - auto fg = std::unique_ptr{new (std::nothrow) T[251]()}; - auto bj1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dj1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto bj2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dj2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto by1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy1 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto by2 = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy2 = std::unique_ptr{new (std::nothrow) T[252]()}; - - if (fg.get() == nullptr || bj1.get() == nullptr || dj1.get() == nullptr - || bj2.get() == nullptr || dj2.get() == nullptr - || by1.get() == nullptr || dy1.get() == nullptr - || by2.get() == nullptr || dy2.get() == nullptr) { - *f1r = NAN; - *d1r = NAN; - *f2r = NAN; - *d2r = NAN; - return Status::NoMemory; - } - - fcoef(kd, m, q, a, fg.get()); - ic = (int)(m / 2) + 1; - if (kd == 4) { ic = m / 2; } - - c1 = exp(-x); - c2 = exp(x); - u1 = sqrt(q) * c1; - u2 = sqrt(q) * c2; - jynb(km+1, u1, &nm, bj1.get(), dj1.get(), by1.get(), dy1.get()); - jynb(km+1, u2, &nm, bj2.get(), dj2.get(), by2.get(), dy2.get()); - w1 = 0.0; - w2 = 0.0; - - if (kc != 2) { - *f1r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *f1r += pow(-1, ic + k) * fg[k - 1] * bj1[k - 1] * bj2[k - 1]; - } else if (kd == 2 || kd == 3) { - *f1r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * bj2[k] + pow(-1, kd) * bj1[k] * bj2[k - 1]); - } else { - *f1r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * bj2[k + 1] - bj1[k + 1] * bj2[k - 1]); - } - - if (k >= 5 && fabs(*f1r - w1) < fabs(*f1r) * eps) { break; } - w1 = *f1r; - } - - *f1r /= fg[0]; - - *d1r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * bj1[k - 1] * dj2[k - 1] - c1 * dj1[k - 1] * bj2[k - 1]); - } else if (kd == 2 || kd == 3) { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dj2[k] + pow(-1, kd) * bj1[k] * dj2[k - 1]) - - c1 * (dj1[k - 1] * bj2[k] + pow(-1, kd) * dj1[k] * bj2[k - 1])); - } else { - *d1r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dj2[k + 1] - bj1[k + 1] * dj2[k - 1]) - - c1 * (dj1[k - 1] * bj2[k + 1] - dj1[k + 1] * bj2[k - 1])); - } - - if (k >= 5 && fabs(*d1r - w2) < fabs(*d1r) * eps) { break; } - w2 = *d1r; - } - *d1r *= sqrt(q) / fg[0]; - if (kc == 1) { - return Status::OK; - } - } - - *f2r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *f2r += pow(-1, ic + k) * fg[k - 1] * bj1[k - 1] * by2[k - 1]; - } else if (kd == 2 || kd == 3) { - *f2r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * by2[k] + pow(-1, kd) * bj1[k] * by2[k - 1]); - } else { - *f2r += pow(-1, ic + k) * fg[k - 1] * (bj1[k - 1] * by2[k + 1] - bj1[k + 1] * by2[k - 1]); - } - - if (k >= 5 && fabs(*f2r - w1) < fabs(*f2r) * eps) { break; } - w1 = *f2r; - } - *f2r /= fg[0]; - - *d2r = 0.0; - for (k = 1; k <= km; k++) { - if (kd == 1) { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * bj1[k - 1] * dy2[k - 1] - c1 * dj1[k - 1] * by2[k - 1]); - } else if (kd == 2 || kd == 3) { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dy2[k] + pow(-1, kd) * bj1[k] * dy2[k - 1]) - - c1 * (dj1[k - 1] * by2[k] + pow(-1, kd) * dj1[k] * by2[k - 1])); - } else { - *d2r += pow(-1, ic + k) * fg[k - 1] * (c2 * (bj1[k - 1] * dy2[k + 1 ] - bj1[k + 1] * dy2[k - 1]) - - c1 * (dj1[k - 1] * by2[k + 1] - dj1[k + 1] * by2[k - 1])); - } - - if (k >= 5 && fabs(*d2r - w2) < fabs(*d2r) * eps) { break; } - w2 = *d2r; - } - *d2r = *d2r * sqrt(q) / fg[0]; - return Status::OK; -} - - -inline double psi_spec(double x) { - - // ====================================== - // Purpose: Compute Psi function - // Input : x --- Argument of psi(x) - // Output: PS --- psi(x) - // ====================================== - - int k, n; - double ps, s = 0.0, x2, xa = fabs(x); - const double pi = 3.141592653589793; - const double el = 0.5772156649015329; - static const double a[8] = { - -0.8333333333333e-01, - 0.83333333333333333e-02, - -0.39682539682539683e-02, - 0.41666666666666667e-02, - -0.75757575757575758e-02, - 0.21092796092796093e-01, - -0.83333333333333333e-01, - 0.4432598039215686 - }; - - if ((x == (int)x) && (x <= 0.0)) { - return 1e300; - } else if (xa == (int)xa) { - n = (int)xa; - for (k = 1; k < n; k++) { - s += 1.0 / k; - } - ps = -el + s; - } else if ((xa + 0.5) == (int)(xa + 0.5)) { - n = (int)(xa - 0.5); - for (k = 1; k < (n+1); k++) { - s += 1.0 / (2.0*k - 1.0); - } - ps = -el + 2.0*s - 1.386294361119891; /* 2*log(2) */ - } else { - if (xa < 10.0) { - n = (10.0 - (int)xa); - for (k = 0; k < n; k++) { - s += 1.0 / (xa + k); - } - xa += n; - } - x2 = 1.0 / (xa*xa); - ps = log(xa) - 0.5 / xa; - ps += x2*(((((((a[7]*x2+a[6])*x2+a[5])*x2+a[4])*x2+a[3])*x2+a[2])*x2+a[1])*x2+a[0]); - ps -= s; - } - if (x < 0.0) { - ps -= pi*cos(pi*x)/sin(pi*x) + 1.0 / x; - } - return ps; -} - - -template -Status qstar(int m, int n, T c, T ck1, T *ck, T *qs, T *qt) { - - // ========================================================== - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================== - - int ip, i, l, k; - T r, s, sk, qs0; - - auto ap = std::unique_ptr{new (std::nothrow) T[200]}; - if (ap.get() == nullptr) { - return Status::NoMemory; - } - ip = ((n - m) == 2 * ((n - m) / 2) ? 0 : 1); - r = 1.0 / pow(ck[0], 2); - ap[0] = r; - - for (i = 1; i <= m; i++) { - s = 0.0; - for (l = 1; l <= i; l++) { - sk = 0.0; - for (k = 0; k <= l; k++) - sk += ck[k] * ck[l - k]; - s += sk * ap[i - l]; - } - ap[i] = -r * s; - } - qs0 = ap[m - 1]; - - for (l = 1; l < m; l++) { - r = 1.0; - for (k = 1; k <= l; ++k) { - r = r * (2.0 * k + ip) * (2.0 * k - 1.0 + ip) / pow(2.0 * k, 2); - } - qs0 += ap[m - l] * r; - } - *qs = pow(-1, ip) * (ck1) * (ck1 * qs0) / c; - *qt = -2.0 / (ck1) * (*qs); - return Status::OK; -} - - -inline double refine(int kd, int m, double q, double a) { - - // ===================================================== - // Purpose: calculate the accurate characteristic value - // by the secant method - // Input : m --- Order of Mathieu functions - // q --- Parameter of Mathieu functions - // A --- Initial characteristic value - // Output: A --- Refineed characteristic value - // Routine called: CVF for computing the value of F for - // characteristic equation - // ======================================================== - - int it, mj; - double x1, x0, x, f, f0, f1; - const double eps = 1e-14; - - mj = 10 + m; - x0 = a; - f0 = cvf(kd, m, q, x0, mj); - x1 = 1.002*a; - f1 = cvf(kd, m, q, x1, mj); - for (it = 1; it <= 100; it++) { - mj += 1; - x = x1 - (x1-x0)/(1.0 - f0/f1); - f = cvf(kd, m, q, x, mj); - if ((fabs(1.0 - x1/x) < eps) || (f == 0.0)) { break; } - x0 = x1; - f0 = f1; - x1 = x; - f1 = f; - } - return x; -} - - -template -inline Status rmn1(int m, int n, T c, T x, int kd, T *df, T *r1f, T *r1d) { - - // ======================================================= - // Purpose: Compute prolate and oblate spheroidal radial - // functions of the first kind for given m, n, - // c and x - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SCKB for computing expansion coefficients c2k - // (2) SPHJ for computing the spherical Bessel - // functions of the first kind - // ======================================================= - - T a0, b0, cx, r, r0, r1, r2, r3, reg, sa0, suc, sud, sum, sw, sw1; - int ip, j, k, l, lg, nm, nm1, nm2, np; - - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto dj = std::unique_ptr{new (std::nothrow) T[252]()}; - auto sj = std::unique_ptr{new (std::nothrow) T[252]()}; - if (ck.get() == nullptr || dj.get() == nullptr || sj.get() == nullptr) { - return Status::NoMemory; - } - const T eps = 1.0e-14; - - nm1 = (int)((n - m) / 2); - ip = (n - m == 2 * nm1 ? 0 : 1); - nm = 25 + nm1 + (int)c; - reg = (m + nm > 80 ? 1.0e-200 : 1.0); - r0 = reg; - - for (j = 1; j <= 2 * m + ip; ++j) { - r0 *= j; - } - r = r0; - suc = r * df[0]; - sw = 0.0; - - for (k = 2; k <= nm; k++) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - suc += r * df[k - 1]; - if ((k > nm1) && (fabs(suc - sw) < fabs(suc) * eps)) { break; } - sw = suc; - } - - if (x == 0.0) { - sckb(m, n, c, df, ck.get()); - - sum = 0.0; - sw1 = 0.0; - for (j = 1; j <= nm; ++j) { - sum += ck[j - 1]; - if (fabs(sum - sw1) < fabs(sum) * eps) { break; } - sw1 = sum; - } - - r1 = 1.0; - for (j = 1; j <= (n + m + ip) / 2; j++) { - r1 = r1 * (j + 0.5 * (n + m + ip)); - } - - r2 = 1.0; - for (j = 1; j <= m; j++) { - r2 *= 2.0 * c * j; - } - - r3 = 1.0; - for (j = 1; j <= (n - m - ip) / 2; j++) { - r3 *= j; - } - sa0 = (2.0 * (m + ip) + 1.0) * r1 / (pow(2.0, n) * pow(c, ip) * r2 * r3); - - if (ip == 0) { - *r1f = sum / (sa0 * suc) * df[0] * reg; - *r1d = 0.0; - } else if (ip == 1) { - *r1f = 0.0; - *r1d = sum / (sa0 * suc) * df[0] * reg; - } - return Status::OK; - } - - cx = c * x; - nm2 = 2 * nm + m; - sphj(cx, nm2, &nm2, sj.get(), dj.get()); - - a0 = pow(1.0 - kd / (x * x), 0.5 * m) / suc; - *r1f = 0.0; - sw = 0.0; - lg = 0; - - for (k = 1; k <= nm; ++k) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - *r1f += lg * r * df[k - 1] * sj[np]; - - if ((k > nm1) && (fabs(*r1f - sw) < fabs(*r1f) * eps)) { break; } - sw = *r1f; - } - - *r1f *= a0; - b0 = kd * m / pow(x, 3.0) / (1.0 - kd / (x * x)) * (*r1f); - - sud = 0.0; - sw = 0.0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - sud = sud + lg * r * df[k - 1] * dj[np]; - - if ((k > nm1) && (fabs(sud - sw) < fabs(sud) * eps)) { break; } - sw = sud; - } - *r1d = b0 + a0 * c * sud; - return Status::OK; -} - - -template -inline Status rmn2l(int m, int n, T c, T x, int Kd, T *Df, T *R2f, T *R2d, int *Id) { - - // ======================================================== - // Purpose: Compute prolate and oblate spheroidal radial - // functions of the second kind for given m, n, - // c and a large cx - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // Status::Other - // An internal convergence check failed. When - // this happens, *Id is set to 10 on return. - // - // Routine called: - // SPHY for computing the spherical Bessel - // functions of the second kind - // ======================================================== - - - int ip, nm1, nm, nm2, np, j, k, l, lg, id1, id2; - T a0, b0, cx, reg, r0, r, suc, sud, sw, eps1, eps2; - const T eps = 1.0e-14; - - auto sy = std::unique_ptr{new (std::nothrow) T[252]()}; - auto dy = std::unique_ptr{new (std::nothrow) T[252]()}; - if (sy.get() == nullptr || dy.get() == nullptr) { - return Status::NoMemory; - } - - ip = 1; - nm1 = (int)((n - m) / 2); - if (n - m == 2 * nm1) { - ip = 0; - } - nm = 25 + nm1 + (int)c; - reg = 1.0; - if (m + nm > 80) { - reg = 1.0e-200; - } - nm2 = 2 * nm + m; - cx = c * x; - sphy(cx, nm2, &nm2, sy.get(), dy.get()); - r0 = reg; - - for (j = 1; j <= 2 * m + ip; ++j) { - r0 *= j; - } - - r = r0; - suc = r * Df[0]; - sw = 0.0; - - for (k = 2; k <= nm; k++) { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - suc += r * Df[k - 1]; - if ((k > nm1) && (fabs(suc - sw) < fabs(suc) * eps)) { break; } - sw = suc; - } - - a0 = pow(1.0 - Kd / (x * x), 0.5 * m) / suc; - *R2f = 0.0; - eps1 = 0.0; - np = 0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - *R2f += lg * r * (Df[k - 1] * sy[np]); - eps1 = fabs(*R2f - sw); - if (k > nm1 && eps1 < fabs(*R2f) * eps) { break; } - sw = *R2f; - } - - id1 = (int)(log10(eps1 / fabs(*R2f) + eps)); - *R2f *= a0; - - if (np >= nm2) { - // On page 584 of "Computation of Special functions" by Shanjie Zhang - // and Jian-Ming Jin, there is a comment next to this code that says - // this condition indicates that convergence is not achieved, so we - // return Status::Other in addition to setting *Id = 10. But the - // functions that call rmn2l (namely rswfp and rswfo) will actually - // look at the value of Id to check for convergence; the returned - // Status value is only checked for the occurrence of Status::NoMemory. - *Id = 10; - return Status::Other; - } - - b0 = Kd * m / pow(x, 3) / (1.0 - Kd / (x * x)) * (*R2f); - sud = 0.0; - eps2 = 0.0; - - for (k = 1; k <= nm; k++) { - l = 2 * k + m - n - 2 + ip; - lg = (l % 4 == 0 ? 1 : -1); - - if (k == 1) { - r = r0; - } else { - r = r * (m + k - 1.0) * (m + k + ip - 1.5) / (k - 1.0) / (k + ip - 1.5); - } - - np = m + 2 * k - 2 + ip; - sud += lg * r * (Df[k - 1] * dy[np]); - eps2 = fabs(sud - sw); - if ((k > nm1) && (eps2 < fabs(sud) * eps)) { break; } - sw = sud; - } - - *R2d = b0 + a0 * c * sud; - id2 = (int)log10(eps2 / fabs(sud) + eps); - *Id = (id1 > id2) ? id1 : id2; - return Status::OK; -} - - -template -inline Status rmn2so(int m, int n, T c, T x, T cv, int kd, T *df, T *r2f, T *r2d) { - - // ============================================================= - // Purpose: Compute oblate radial functions of the second kind - // with a small argument, Rmn(-ic,ix) & Rmn'(-ic,ix) - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SCKB for computing the expansion coefficients c2k - // (2) KMN for computing the joining factors - // (3) QSTAR for computing the factor defined in (15.7.3) - // (4) CBK for computing the expansion coefficient - // defined in (15.7.6) - // (5) GMN for computing the function defined in (15.7.4) - // (6) RMN1 for computing the radial function of the first - // kind - // ============================================================= - - int nm, ip, j; - T ck1, ck2, r1f, r1d, qs, qt, sum, sw, gf, gd, h0; - const T eps = 1.0e-14; - const T pi = 3.141592653589793; - - if (fabs(df[0]) <= 1.0e-280) { - *r2f = 1.0e+300; - *r2d = 1.0e+300; - return Status::OK; - } - - auto bk = std::unique_ptr{new (std::nothrow) T[200]()}; - auto ck = std::unique_ptr{new (std::nothrow) T[200]()}; - auto dn = std::unique_ptr{new (std::nothrow) T[200]()}; - if (bk.get() == nullptr || ck.get() == nullptr || dn.get() == nullptr) { - return Status::NoMemory; - } - - nm = 25 + (int)((n - m) / 2 + c); - ip = (n - m) % 2; - sckb(m, n, c, df, ck.get()); - if (kmn(m, n, c, cv, kd, df, dn.get(), &ck1, &ck2) == Status::NoMemory) { - return Status::NoMemory; - } - if (qstar(m, n, c, ck1, ck.get(), &qs, &qt) == Status::NoMemory) { - return Status::NoMemory; - } - if (cbk(m, n, c, cv, qt, ck.get(), bk.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (x == 0.0) { - sum = 0.0; - sw = 0.0; - - for (j = 0; j < nm; ++j) { - sum += ck[j]; - if (fabs(sum - sw) < fabs(sum) * eps) { break; } - sw = sum; - } - - if (ip == 0) { - r1f = sum / ck1; - *r2f = -0.5 * pi * qs * r1f; - *r2d = qs * r1f + bk[0]; - } else { - r1d = sum / ck1; - *r2f = bk[0]; - *r2d = -0.5 * pi * qs * r1d; - } - } else { - gmn(m, n, c, x, bk.get(), &gf, &gd); - if (rmn1(m, n, c, x, kd, df, &r1f, &r1d) == Status::NoMemory) { - return Status::NoMemory; - } - h0 = atan(x) - 0.5 * pi; - *r2f = qs * r1f * h0 + gf; - *r2d = qs * (r1d * h0 + r1f / (1.0 + x * x)) + gd; - } - return Status::OK; -} - - -template -Status rmn2sp(int m, int n, T c, T x, T cv, int kd, T *df, T *r2f, T *r2d) { - - // ====================================================== - // Purpose: Compute prolate spheroidal radial function - // of the second kind with a small argument - // - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) LPMNS for computing the associated Legendre - // functions of the first kind - // (2) LQMNS for computing the associated Legendre - // functions of the second kind - // (3) KMN for computing expansion coefficients - // and joining factors - // ====================================================== - - int k, j, j1, j2, l1, ki, nm3; - T ip, nm1, nm, nm2, su0, sw, sd0, su1, sd1, sd2, ga, r1, r2, r3, - sf, gb, spl, gc, sd, r4, spd1, spd2, su2, ck1, ck2, sum, sdm; - - // fortran index start from 0 - auto pm = std::unique_ptr{new (std::nothrow) T[252]}; - auto pd = std::unique_ptr{new (std::nothrow) T[252]}; - auto qm = std::unique_ptr{new (std::nothrow) T[252]}; - auto qd = std::unique_ptr{new (std::nothrow) T[252]}; - // fortran index start from 1 - auto dn = std::unique_ptr{new (std::nothrow) T[201]}; - - if (pm.get() == nullptr || pd.get() == nullptr || qm.get() == nullptr - || qd.get() == nullptr || dn.get() == nullptr) { - return Status::NoMemory; - } - - const T eps = 1.0e-14; - - nm1 = (n - m) / 2; - nm = 25.0 + nm1 + c; - nm2 = 2 * nm + m; - ip = (n - m) % 2; - - if (kmn(m, n, c, cv, kd, df, dn.get(), &ck1, &ck2) == Status::NoMemory) { - return Status::NoMemory; - } - lpmns(m, nm2, x, pm.get(), pd.get()); - lqmns(m, nm2, x, qm.get(), qd.get()); - - su0 = 0.0; - sw = 0.0; - for (k = 1; k <= nm; k++) { - j = 2 * k - 2 + m + ip; - su0 += df[k - 1] * qm[j]; - if ((k > nm1) && (fabs(su0 - sw) < fabs(su0) * eps)) { break; } - sw = su0; - } - - sd0 = 0.0; - for (k = 1; k <= nm; k++) { - j = 2 * k - 2 + m + ip; - sd0 += df[k - 1] * qd[j]; - if (k > nm1 && fabs(sd0 - sw) < fabs(sd0) * eps) { break; } - sw = sd0; - } - - su1 = 0.0; - sd1 = 0.0; - for (k = 1; k <= m; k++) { - j = m - 2 * k + ip; - if (j < 0) { - j = -j - 1; - } - su1 += dn[k - 1] * qm[j]; - sd1 += dn[k - 1] * qd[j]; - } - - ga = pow((x - 1.0) / (x + 1.0), 0.5 * m); - for (k = 1; k <= m; k++) { - j = m - 2 * k + ip; - if (j >= 0) { continue; } - if (j < 0) { j = -j - 1; } - r1 = 1.0; - for (j1 = 0; j1 < j; j1++) { - r1 *= (m + j1); - } - r2 = 1.0; - for (j2 = 1; j2 <= (m - j - 2); j2++) { - r2 *= j2; - } - r3 = 1.0; - sf = 1.0; - for (l1 = 1; l1 <= j; l1++) { - r3 = 0.5 * r3 * (-j + l1 - 1.0) * (j + l1) / ((m + l1) * l1) * (1.0 - x); - sf += r3; - } - if (m - j >= 2) { - gb = (m - j - 1.0) * r2; - } - if (m - j <= 1) { - gb = 1.0; - } - spl = r1 * ga * gb * sf; - su1 += pow(-1, (j + m)) * dn[k-1] * spl; - spd1 = m / (x * x - 1.0) * spl; - gc = 0.5 * j * (j + 1.0) / (m + 1.0); - sd = 1.0; - r4 = 1.0; - for (l1 = 1; l1 <= j - 1; l1++) { - r4 = 0.5 * r4 * (-j + l1) * (j + l1 + 1.0) / ((m + l1 + 1.0) * l1) * (1.0 - x); - sd += r4; - } - spd2 = r1 * ga * gb * gc * sd; - sd1 += pow(-1, (j + m)) * dn[k - 1] * (spd1 + spd2); - } - su2 = 0.0; - ki = (2 * m + 1 + ip) / 2; - ki = std::max(1, ki); - assert((ki-1) >= 0); - nm3 = nm + ki; - for (k = ki; k <= nm3; k++) { - j = 2 * k - 1 - m - ip; - su2 += dn[k - 1] * pm[j]; - if ((j > m) && (fabs(su2 - sw) < fabs(su2) * eps)) { break; } - sw = su2; - } - sd2 = 0.0; - for (k = ki; k < nm3; k++) { - j = 2 * k - 1 - m - ip; - sd2 += dn[k - 1] * pd[j]; - if (j > m && fabs(sd2 - sw) < fabs(sd2) * eps) { break; } - sw = sd2; - } - sum = su0 + su1 + su2; - sdm = sd0 + sd1 + sd2; - *r2f = sum / ck2; - *r2d = sdm / ck2; - return Status::OK; -} - - -template -inline Status rswfp(int m, int n, T c, T x, T cv, int kf, T *r1f, T *r1d, T *r2f, T *r2d) { - - // ============================================================== - // Purpose: Compute prolate spheriodal radial functions of the - // first and second kinds, and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,m+2,... - // c --- Spheroidal parameter - // x --- Argument of radial function ( x > 1.0 ) - // cv --- Characteristic value - // KF --- Function code - // KF=1 for the first kind - // KF=2 for the second kind - // KF=3 for both the first and second kinds - // Output: R1F --- Radial function of the first kind - // R1D --- Derivative of the radial function of - // the first kind - // R2F --- Radial function of the second kind - // R2D --- Derivative of the radial function of - // the second kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SDMN for computing expansion coefficients dk - // (2) RMN1 for computing prolate and oblate radial - // functions of the first kind - // (3) RMN2L for computing prolate and oblate radial - // functions of the second kind for a large argument - // (4) RMN2SP for computing the prolate radial function - // of the second kind for a small argument - // ============================================================== - - auto df = std::unique_ptr{new (std::nothrow) T[200]}; - if (df.get() == nullptr) { - return Status::NoMemory; - } - int id, kd = 1; - - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (kf != 2) { - if (rmn1(m, n, c, x, kd, df.get(), r1f, r1d) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (kf > 1) { - if (rmn2l(m, n, c, x, kd, df.get(), r2f, r2d, &id) == Status::NoMemory) { - return Status::NoMemory; - } - if (id > -8) { - if (rmn2sp(m, n, c, x, cv, kd, df.get(), r2f, r2d) == Status::NoMemory) { - return Status::NoMemory; - } - } - } - return Status::OK; -} - - -template -Status rswfo(int m, int n, T c, T x, T cv, int kf, T *r1f, T *r1d, T *r2f, T *r2d) { - - // ========================================================== - // Purpose: Compute oblate radial functions of the first - // and second kinds, and their derivatives - // Input : m --- Mode parameter, m = 0,1,2,... - // n --- Mode parameter, n = m,m+1,m+2,... - // c --- Spheroidal parameter - // x --- Argument (x ≥ 0) - // cv --- Characteristic value - // KF --- Function code - // KF=1 for the first kind - // KF=2 for the second kind - // KF=3 for both the first and second kinds - // Output: R1F --- Radial function of the first kind - // R1D --- Derivative of the radial function of - // the first kind - // R2F --- Radial function of the second kind - // R2D --- Derivative of the radial function of - // the second kind - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // - // Routines called: - // (1) SDMN for computing expansion coefficients dk - // (2) RMN1 for computing prolate or oblate radial - // function of the first kind - // (3) RMN2L for computing prolate or oblate radial - // function of the second kind for a large argument - // (4) RMN2SO for computing oblate radial functions of - // the second kind for a small argument - // ========================================================== - - auto df = std::unique_ptr{new (std::nothrow) T[200]}; - if (df.get() == nullptr) { - return Status::NoMemory; - } - int id, kd = -1; - - if (sdmn(m, n, c, cv, kd, df.get()) == Status::NoMemory) { - return Status::NoMemory; - } - - if (kf != 2) { - if (rmn1(m, n, c, x, kd, df.get(), r1f, r1d) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (kf > 1) { - id = 10; - if (x > 1e-8) { - if (rmn2l(m, n, c, x, kd, df.get(), r2f, r2d, &id) == Status::NoMemory) { - return Status::NoMemory; - } - } - if (id > -1) { - if (rmn2so(m, n, c, x, cv, kd, df.get(), r2f, r2d) == Status::NoMemory) { - return Status::NoMemory; - } - } - } - return Status::OK; -} - - -template -void sckb(int m, int n, T c, T *df, T *ck) { - - // ====================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // DF(k) --- Expansion coefficients dk - // Output: CK(k) --- Expansion coefficients ck; - // CK(1), CK(2), ... correspond to - // c0, c2, ... - // ====================================================== - - int i, ip, i1, i2, k, nm; - T reg, fac, sw, r, d1, d2, d3, sum, r1; - - if (c <= 1.0e-10) { - c = 1.0e-10; - } - nm = 25 + (int)(0.5 * (n - m) + c); - ip = (n - m) % 2; - reg = ((m + nm) > 80 ? 1.0e-200 : 1.0); - fac = -pow(0.5, m); - sw = 0.0; - - for (k = 0; k < nm; k++) { - fac = -fac; - i1 = 2 * k + ip + 1; - r = reg; - - for (i = i1; i <= i1 + 2 * m - 1; i++) { - r *= i; - } - i2 = k + m + ip; - for (i = i2; i <= i2 + k - 1; i++) { - r *= (i + 0.5); - } - sum = r * df[k]; - for (i = k + 1; i <= nm; i++) { - d1 = 2.0 * i + ip; - d2 = 2.0 * m + d1; - d3 = i + m + ip - 0.5; - r = r * d2 * (d2 - 1.0) * i * (d3 + k) / (d1 * (d1 - 1.0) * (i - k) * d3); - sum += r * df[i]; - if (fabs(sw - sum) < fabs(sum) * 1.0e-14) { break; } - sw = sum; - } - r1 = reg; - for (i = 2; i <= m + k; i++) { r1 *= i; } - ck[k] = fac * sum / r1; - } -} - - -template -Status sdmn(int m, int n, T c, T cv, int kd, T *df) { - - // ===================================================== - // Purpose: Compute the expansion coefficients of the - // prolate and oblate spheroidal functions, dk - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // cv --- Characteristic value - // KD --- Function code - // KD=1 for prolate; KD=-1 for oblate - // Output: DF(k) --- Expansion coefficients dk; - // DF(1), DF(2), ... correspond to - // d0, d2, ... for even n-m and d1, - // d3, ... for odd n-m - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ===================================================== - - int nm, ip, k, kb; - T cs, dk0, dk1, dk2, d2k, f, fs, f1, f0, fl, f2, su1,\ - su2, sw, r1, r3, r4, s0; - - nm = 25 + (int)(0.5 * (n - m) + c); - - if (c < 1e-10) { - for (int i = 1; i <= nm; ++i) { - df[i-1] = 0.0; - } - df[(n - m) / 2] = 1.0; - return Status::OK; - } - - auto a = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - auto d = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - auto g = std::unique_ptr{new (std::nothrow) T[nm + 2]()}; - if (a.get() == nullptr || d.get() == nullptr || g.get() == nullptr) { - return Status::NoMemory; - } - cs = c*c*kd; - ip = (n - m) % 2; - - for (int i = 1; i <= nm + 2; ++i) { - k = (ip == 0 ? 2 * (i - 1) : 2 * i - 1); - - dk0 = m + k; - dk1 = m + k + 1; - dk2 = 2 * (m + k); - d2k = 2 * m + k; - - a[i - 1] = (d2k + 2.0) * (d2k + 1.0) / ((dk2 + 3.0) * (dk2 + 5.0)) * cs; - d[i - 1] = dk0 * dk1 + (2.0 * dk0 * dk1 - 2.0 * m * m - 1.0) / ((dk2 - 1.0) * (dk2 + 3.0)) * cs; - g[i - 1] = k * (k - 1.0) / ((dk2 - 3.0) * (dk2 - 1.0)) * cs; - } - - fs = 1.0; - f1 = 0.0; - f0 = 1.0e-100; - kb = 0; - df[nm] = 0.0; - fl = 0.0; - - for (int k = nm; k >= 1; k--) { - f = -((d[k] - cv) * f0 + a[k] * f1) / g[k]; - - if (fabs(f) > fabs(df[k])) { - df[k-1] = f; - f1 = f0; - f0 = f; - - if (fabs(f) > 1.0e+100) { - for (int k1 = k; k1 <= nm; k1++) - df[k1 - 1] *= 1.0e-100; - f1 *= 1.0e-100; - f0 *= 1.0e-100; - } - } else { - kb = k; - fl = df[k]; - f1 = 1.0e-100; - f2 = -((d[0] - cv) / a[0]) * f1; - df[0] = f1; - - if (kb == 1) { - fs = f2; - } else if (kb == 2) { - df[1] = f2; - fs = -((d[1] - cv) * f2 + g[1] * f1) / a[1]; - } else { - df[1] = f2; - - for (int j = 3; j <= kb + 1; j++) { - f = -((d[j - 2] - cv) * f2 + g[j - 2] * f1) / a[j - 2]; - if (j <= kb) { - df[j-1] = f; - } - if (fabs(f) > 1.0e+100) { - for (int k1 = 1; k1 <= j; k1++) { - df[k1 - 1] *= 1.0e-100; - } - f *= 1.0e-100; - f2 *= 1.0e-100; - } - f1 = f2; - f2 = f; - } - fs = f; - } - break; - } - } - - su1 = 0.0; - r1 = 1.0; - - for (int j = m + ip + 1; j <= 2 * (m + ip); j++) { - r1 *= j; - } - su1 = df[0] * r1; - - for (int k = 2; k <= kb; k++) { - r1 = -r1 * (k + m + ip - 1.5) / (k - 1.0); - su1 += r1 * df[k - 1]; - } - - su2 = 0.0; - sw = 0.0; - - for (int k = kb + 1; k <= nm; k++) { - if (k != 1) { - r1 = -r1 * (k + m + ip - 1.5) / (k - 1.0); - } - su2 += r1 * df[k - 1]; - - if (fabs(sw - su2) < fabs(su2) * 1.0e-14) { break; } - sw = su2; - } - r3 = 1.0; - - for (int j = 1; j <= (m + n + ip) / 2; j++) { - r3 *= (j + 0.5 * (n + m + ip)); - } - r4 = 1.0; - - for (int j = 1; j <= (n - m - ip) / 2; j++) { - r4 *= -4.0 * j; - } - s0 = r3 / (fl * (su1 / fs) + su2) / r4; - - for (int k = 1; k <= kb; ++k) { - df[k - 1] *= fl / fs * s0; - } - for (int k = kb + 1; k <= nm; ++k) { - df[k - 1] *= s0; - } - return Status::OK; -} - - -template -Status segv(int m, int n, T c, int kd, T *cv, T *eg) { - - // ========================================================= - // Purpose: Compute the characteristic values of spheroidal - // wave functions - // Input : m --- Mode parameter - // n --- Mode parameter - // c --- Spheroidal parameter - // KD --- Function code - // KD=1 for Prolate; KD=-1 for Oblate - // Output: CV --- Characteristic value for given m, n and c - // EG(L) --- Characteristic value for mode m and n' - // ( L = n' - m + 1 ) - // Return value: - // Status::OK - // Normal return. - // Status::NoMemory - // An internal memory allocation failed. - // ========================================================= - - - int i, icm, j, k, k1, l, nm, nm1; - T cs, dk0, dk1, dk2, d2k, s, t, t1, x1, xa, xb; - // eg[<=200] is supplied by the caller - - if (c < 1e-10) { - for (i = 1; i <= (n-m+1); i++) { - eg[i-1] = (i+m) * (i + m -1); - } - *cv = eg[n-m]; - return Status::OK; - } - - // TODO: Following array sizes should be decided dynamically - auto a = std::unique_ptr{new (std::nothrow) T[300]()}; - auto b = std::unique_ptr{new (std::nothrow) T[100]()}; - auto cv0 = std::unique_ptr{new (std::nothrow) T[100]()}; - auto d = std::unique_ptr{new (std::nothrow) T[300]()}; - auto e = std::unique_ptr{new (std::nothrow) T[300]()}; - auto f = std::unique_ptr{new (std::nothrow) T[300]()}; - auto g = std::unique_ptr{new (std::nothrow) T[300]()}; - auto h = std::unique_ptr{new (std::nothrow) T[100]()}; - - if (a.get() == nullptr || b.get() == nullptr || cv0.get() == nullptr - || d.get() == nullptr || e.get() == nullptr || f.get() == nullptr - || g.get() == nullptr || h.get() == nullptr) { - return Status::NoMemory; - } - icm = (n-m+2)/2; - nm = 10 + (int)(0.5*(n-m)+c); - cs = c*c*kd; - k = 0; - for (l = 0; l <= 1; l++) { - for (i = 1; i <= nm; i++) { - k = (l == 0 ? 2*(i - 1) : 2*i - 1); - dk0 = m + k; - dk1 = m + k + 1; - dk2 = 2*(m + k); - d2k = 2*m + k; - a[i-1] = (d2k+2.0)*(d2k+1.0)/((dk2+3.0)*(dk2+5.0))*cs; - d[i-1] = dk0*dk1+(2.0*dk0*dk1-2.0*m*m-1.0)/((dk2-1.0)*(dk2+3.0))*cs; - g[i-1] = k*(k-1.0)/((dk2-3.0)*(dk2-1.0))*cs; - } - for (k = 2; k <= nm; k++) { - e[k-1] = sqrt(a[k-2]*g[k-1]); - f[k-1] = e[k-1]*e[k-1]; - } - f[0] = 0.0; - e[0] = 0.0; - xa = d[nm-1] + fabs(e[nm-1]); - xb = d[nm-1] - fabs(e[nm-1]); - nm1 = nm-1; - for (i = 1; i <= nm1; i++) { - t = fabs(e[i-1])+fabs(e[i]); - t1 = d[i-1] + t; - if (xa < t1) { xa = t1; } - t1 = d[i-1] - t; - if (t1 < xb) { xb = t1; } - } - for (i = 1; i <= icm; i++) { - b[i-1] = xa; - h[i-1] = xb; - } - for (k = 1; k <= icm; k++) { - for (k1 = k; k1 <= icm; k1++) { - if (b[k1-1] < b[k-1]) { - b[k-1] = b[k1-1]; - break; - } - } - if (k != 1) { - if(h[k-1] < h[k-2]) { h[k-1] = h[k-2]; } - } - while (1) { - x1 = (b[k-1]+h[k-1])/2.0; - cv0[k-1] = x1; - if (fabs((b[k-1] - h[k-1])/x1) < 1e-14) { break; } - j = 0; - s = 1.0; - for (i = 1; i <= nm; i++) { - if (s == 0.0) { s += 1e-30; } - t = f[i-1]/s; - s = d[i-1] - t - x1; - if (s < 0.0) { j += 1; } - } - if (j < k) { - h[k-1] = x1; - } else { - b[k-1] = x1; - if (j >= icm) { - b[icm - 1] = x1; - } else { - if (h[j] < x1) { h[j] = x1; } - if (x1 < b[j-1]) { b[j-1] = x1; } - } - } - } - cv0[k-1] = x1; - if (l == 0) eg[2*k-2] = cv0[k-1]; - if (l == 1) eg[2*k-1] = cv0[k-1]; - } - } - *cv = eg[n-m]; - return Status::OK; -} - - -template -void sphj(T x, int n, int *nm, T *sj, T *dj) { - - // MODIFIED to ALLOW N=0 CASE (ALSO IN SPHY) - // - // ======================================================= - // Purpose: Compute spherical Bessel functions jn(x) and - // their derivatives - // Input : x --- Argument of jn(x) - // n --- Order of jn(x) ( n = 0,1,… ) - // Output: SJ(n) --- jn(x) - // DJ(n) --- jn'(x) - // NM --- Highest order computed - // Routines called: - // MSTA1 and MSTA2 for computing the starting - // point for backward recurrence - // ======================================================= - - int k, m; - T cs, f, f0, f1, sa, sb; - - *nm = n; - if (fabs(x) < 1e-100) { - for (k = 0; k <= n; k++) { - sj[k] = 0.0; - dj[k] = 0.0; - } - sj[0] = 1.0; - if (n > 0) { - dj[1] = 1.0 / 3.0; - } - return; - } - sj[0] = sin(x)/x; - dj[0] = (cos(x) - sin(x)/x)/x; - if (n < 1) { - return; - } - sj[1] = (sj[0] - cos(x))/x; - if (n >= 2) { - sa = sj[0]; - sb = sj[1]; - m = msta1(x, 200); - if (m < n) { - *nm = m; - } else { - m = msta2(x, n, 15); - } - f = 0.0; - f0 = 0.0; - f1 = 1e-100; - for (k = m; k >= 0; k--) { - f = (2.0*k + 3.0)*f1/x - f0; - if (k <= *nm) { sj[k] = f; } - f0 = f1; - f1 = f; - } - cs = (fabs(sa) > fabs(sb) ? sa/f : sb/f0); - for (k = 0; k <= *nm; k++) { - sj[k] *= cs; - } - } - for (k = 1; k <= *nm; k++) { - dj[k] = sj[k - 1] - (k + 1.0)*sj[k]/x; - } - return; -} - - -template -inline void sphy(T x, int n, int *nm, T *sy, T *dy) { - - // ====================================================== - // Purpose: Compute spherical Bessel functions yn(x) and - // their derivatives - // Input : x --- Argument of yn(x) ( x ≥ 0 ) - // n --- Order of yn(x) ( n = 0,1,… ) - // Output: SY(n) --- yn(x) - // DY(n) --- yn'(x) - // NM --- Highest order computed - // ====================================================== - - T f, f0, f1; - - if (x < 1.0e-60) { - for (int k = 0; k <= n; ++k) { - sy[k] = -1.0e300; - dy[k] = 1.0e300; - } - *nm = n; - return; - } - sy[0] = -cos(x) / x; - f0 = sy[0]; - dy[0] = (sin(x) + cos(x) / x) / x; - - if (n < 1) { - *nm = n; - return; - } - - sy[1] = (sy[0] - sin(x)) / x; - f1 = sy[1]; - - for (int k = 2; k <= n; k++) { - f = ((2.0 * k - 1.0) * f1 / x) - f0; - sy[k] = f; - if (fabs(f) >= 1.0e300) { - *nm = k - 1; - return; - } - f0 = f1; - f1 = f; - } - *nm = n - 1; - for (int k = 1; k <= *nm; k++) { - dy[k] = sy[k - 1] - (k + 1.0) * sy[k] / x; - } - return; -} - -} -} diff --git a/scipy/special/xsf/sph_bessel.h b/scipy/special/xsf/sph_bessel.h deleted file mode 100644 index 3791de80944d..000000000000 --- a/scipy/special/xsf/sph_bessel.h +++ /dev/null @@ -1,382 +0,0 @@ -/* - -Implementation of spherical Bessel functions and modified spherical Bessel -functions of the first and second kinds, as well as their derivatives. - -Author: Tadeusz Pudlik - -Distributed under the same license as SciPy. - -I attempt to correctly handle the edge cases (0 and infinity), but this is -tricky: the values of the functions often depend on the direction in which -the limit is taken. At zero, I follow the convention of numpy (1.9.2), -which treats zero differently depending on its type: - - >>> np.cos(0)/0 - inf - >>> np.cos(0+0j)/(0+0j) - inf + nan*j - -So, real zero is assumed to be "positive zero", while complex zero has an -unspecified sign and produces nans. Similarly, complex infinity is taken to -represent the "point at infinity", an ambiguity which for some functions -makes `nan` the correct return value. - -Translated to C++ by SciPy developers in 2024. - -*/ - -#pragma once - -#include "amos.h" -#include "error.h" - -namespace xsf { - -template -T sph_bessel_j(long n, T x) { - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_jn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if ((x == std::numeric_limits::infinity()) || (x == -std::numeric_limits::infinity())) { - return 0; - } - - if (x == 0) { - if (n == 0) { - return 1; - } - - return 0; - } - - if ((n > 0) && (n >= x)) { - return std::sqrt(M_PI_2 / x) * cyl_bessel_j(n + 1 / static_cast(2), x); - } - - T s0 = std::sin(x) / x; - if (n == 0) { - return s0; - } - - T s1 = (s0 - std::cos(x)) / x; - if (n == 1) { - return s1; - } - - T sn; - for (int i = 0; i < n - 1; ++i) { - sn = (2 * i + 3) * s1 / x - s0; - s0 = s1; - s1 = sn; - if (std::isinf(sn)) { - // Overflow occurred already : terminate recurrence. - return sn; - } - } - - return sn; -} - -template -std::complex sph_bessel_j(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_jn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == std::numeric_limits::infinity() || std::real(z) == -std::numeric_limits::infinity()) { - // https://dlmf.nist.gov/10.52.E3 - if (std::imag(z) == 0) { - return 0; - } - - return std::complex(1, 1) * std::numeric_limits::infinity(); - } - - if ((std::real(z) == 0) && (std::imag(z) == 0)) { - if (n == 0) { - return 1; - } - - return 0; - } - - std::complex out = std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_j(n + 1 / static_cast(2), z); - if (std::imag(z) == 0) { - return std::real(out); // Small imaginary part is spurious - } - - return out; -} - -template -T sph_bessel_j_jac(long n, T z) { - if (n == 0) { - return -sph_bessel_j(1, z); - } - - if (z == static_cast(0)) { - // DLMF 10.51.2 doesn't work, so use 10.51.1 to get the exact value - if (n == 1) { - return static_cast(1) / static_cast(3); - } - - return 0; - } - - // DLMF 10.51.2 - return sph_bessel_j(n - 1, z) - static_cast(n + 1) * sph_bessel_j(n, z) / z; -} - -template -T sph_bessel_y(long n, T x) { - T s0, s1, sn; - int idx; - - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_yn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (x < 0) { - return std::pow(-1, n + 1) * sph_bessel_y(n, -x); - } - - if (x == std::numeric_limits::infinity() || x == -std::numeric_limits::infinity()) { - return 0; - } - - if (x == 0) { - return -std::numeric_limits::infinity(); - } - - s0 = -cos(x) / x; - if (n == 0) { - return s0; - } - - s1 = (s0 - sin(x)) / x; - if (n == 1) { - return s1; - } - - for (idx = 0; idx < n - 1; ++idx) { - sn = (2 * idx + 3) * s1 / x - s0; - s0 = s1; - s1 = sn; - if (std::isinf(sn)) { - // Overflow occurred already: terminate recurrence. - return sn; - } - } - - return sn; -} - -inline float sph_bessel_y(long n, float x) { return sph_bessel_y(n, static_cast(x)); } - -template -std::complex sph_bessel_y(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_yn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == 0 && std::imag(z) == 0) { - // https://dlmf.nist.gov/10.52.E2 - return std::numeric_limits::quiet_NaN(); - } - - if (std::real(z) == std::numeric_limits::infinity() || std::real(z) == -std::numeric_limits::infinity()) { - // https://dlmf.nist.gov/10.52.E3 - if (std::imag(z) == 0) { - return 0; - } - - return std::complex(1, 1) * std::numeric_limits::infinity(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_y(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_y_jac(long n, T x) { - if (n == 0) { - return -sph_bessel_y(1, x); - } - - return sph_bessel_y(n - 1, x) - static_cast(n + 1) * sph_bessel_y(n, x) / x; -} - -template -T sph_bessel_i(long n, T x) { - if (std::isnan(x)) { - return x; - } - - if (n < 0) { - set_error("spherical_in", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (x == 0) { - // https://dlmf.nist.gov/10.52.E1 - if (n == 0) { - return 1; - } - return 0; - } - - if (std::isinf(x)) { - // https://dlmf.nist.gov/10.49.E8 - if (x == -std::numeric_limits::infinity()) { - return std::pow(-1, n) * std::numeric_limits::infinity(); - } - - return std::numeric_limits::infinity(); - } - - return sqrt(static_cast(M_PI_2) / x) * cyl_bessel_i(n + 1 / static_cast(2), x); -} - -template -std::complex sph_bessel_i(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_in", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::abs(z) == 0) { - // https://dlmf.nist.gov/10.52.E1 - if (n == 0) { - return 1; - } - - return 0; - } - - if (std::isinf(std::real(z)) || std::isinf(std::imag(z))) { - // https://dlmf.nist.gov/10.52.E5 - if (std::imag(z) == 0) { - if (std::real(z) == -std::numeric_limits::infinity()) { - return std::pow(-1, n) * std::numeric_limits::infinity(); - } - - return std::numeric_limits::infinity(); - } - - return std::numeric_limits::quiet_NaN(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_i(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_i_jac(long n, T z) { - if (n == 0) { - return sph_bessel_i(1, z); - } - - if (z == static_cast(0)) { - if (n == 1) { - return 1./3.; - } - else { - return 0; - } - } - - return sph_bessel_i(n - 1, z) - static_cast(n + 1) * sph_bessel_i(n, z) / z; -} - -template -T sph_bessel_k(long n, T z) { - if (std::isnan(z)) { - return z; - } - - if (n < 0) { - set_error("spherical_kn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (z == 0) { - return std::numeric_limits::infinity(); - } - - if (std::isinf(z)) { - // https://dlmf.nist.gov/10.52.E6 - if (z == std::numeric_limits::infinity()) { - return 0; - } - - return -std::numeric_limits::infinity(); - } - - return std::sqrt(M_PI_2 / z) * cyl_bessel_k(n + 1 / static_cast(2), z); -} - -template -std::complex sph_bessel_k(long n, std::complex z) { - if (std::isnan(std::real(z)) || std::isnan(std::imag(z))) { - return z; - } - - if (n < 0) { - set_error("spherical_kn", SF_ERROR_DOMAIN, nullptr); - return std::numeric_limits::quiet_NaN(); - } - - if (std::abs(z) == 0) { - return std::numeric_limits::quiet_NaN(); - } - - if (std::isinf(std::real(z)) || std::isinf(std::imag(z))) { - // https://dlmf.nist.gov/10.52.E6 - if (std::imag(z) == 0) { - if (std::real(z) == std::numeric_limits::infinity()) { - return 0; - } - - return -std::numeric_limits::infinity(); - } - - return std::numeric_limits::quiet_NaN(); - } - - return std::sqrt(static_cast(M_PI_2) / z) * cyl_bessel_k(n + 1 / static_cast(2), z); -} - -template -T sph_bessel_k_jac(long n, T x) { - if (n == 0) { - return -sph_bessel_k(1, x); - } - - return -sph_bessel_k(n - 1, x) - static_cast(n + 1) * sph_bessel_k(n, x) / x; -} - -} // namespace xsf diff --git a/scipy/special/xsf/sph_harm.h b/scipy/special/xsf/sph_harm.h deleted file mode 100644 index 30bfb2fa08dd..000000000000 --- a/scipy/special/xsf/sph_harm.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "legendre.h" -#include "numbers.h" - -namespace xsf { -namespace detail { - - template - void sph_harm_y_next(int m, T phi, T p, complex &res) { - res = p * exp(numbers::i_v * T(m) * phi); - } - -} // namespace detail - -template -void sph_harm_y_for_each_n(int n, int m, T theta, T phi, complex &res, Func f) { - T p[2]; - sph_legendre_p_for_each_n(n, m, theta, p, [m, phi, &res, &f](int n, const T(&p)[2]) { - detail::sph_harm_y_next(m, phi, p[1], res); - - f(n, m, res); - }); -} - -template -void sph_harm_y_for_each_n_m(int n, int m, T theta, T phi, complex &res, Func f) { - T p[2]; - sph_legendre_p_for_each_n_m(n, m, theta, p, [phi, &res, &f](int n, int m, const T(&p)[2]) { - detail::sph_harm_y_next(m, phi, p[1], res); - - f(n, m, res); - }); -} - -template -complex sph_harm_y(int n, int m, T theta, T phi) { - complex res_n; - sph_harm_y_for_each_n(n, m, theta, phi, res_n, [](int n, int m, const complex &res_n) {}); - - return res_n; -} - -template -void sph_harm_y_all(T theta, T phi, OutputMat res) { - int n_max = res.extent(0) - 1; - int m_max = (res.extent(1) - 1) / 2; - - complex res_n_m; - sph_harm_y_for_each_n_m(n_max, m_max, theta, phi, res_n_m, [m_max, &res](int n, int m, complex &res_n_m) { - if (m >= 0) { - res(n, m) = res_n_m; - } else { - res(n, m + 2 * m_max + 1) = res_n_m; - } - }); -} - -} // namespace xsf diff --git a/scipy/special/xsf/sphd_wave.h b/scipy/special/xsf/sphd_wave.h deleted file mode 100644 index 2f2c2259768e..000000000000 --- a/scipy/special/xsf/sphd_wave.h +++ /dev/null @@ -1,407 +0,0 @@ -#pragma once - -#include "specfun.h" - -namespace xsf { - -template -T prolate_segv(T m, T n, T c) { - int kd = 1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - return cv; -} - -template -T oblate_segv(T m, T n, T c) { - int kd = -1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - return std::numeric_limits::quiet_NaN(); - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_cv", SF_ERROR_MEMORY, "memory allocation error"); - return std::numeric_limits::quiet_NaN(); - } - return cv; -} - -template -void prolate_aswfa_nocv(T m, T n, T c, T x, T &s1f, T &s1d) { - int kd = 1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_ang1", SF_ERROR_DOMAIN, NULL); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::aswfa(x, int_m, int_n, c, kd, cv, &s1f, &s1d) - == specfun::Status::NoMemory) { - set_error("prol_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_aswfa_nocv(T m, T n, T c, T x, T &s1f, T &s1d) { - int kd = -1; - int int_m, int_n; - T cv = 0.0, *eg; - - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_ang1", SF_ERROR_DOMAIN, NULL); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::aswfa(x, int_m, int_n, c, kd, cv, &s1f, &s1d) - == specfun::Status::NoMemory) { - set_error("obl_ang1", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void prolate_aswfa(T m, T n, T c, T cv, T x, T &s1f, T &s1d) { - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_ang1_cv", SF_ERROR_DOMAIN, NULL); - s1f = std::numeric_limits::quiet_NaN(); - s1d = std::numeric_limits::quiet_NaN(); - } else { - specfun::Status status = specfun::aswfa(x, static_cast(m), - static_cast(n), c, 1, cv, - &s1f, &s1d); - if (status == specfun::Status::NoMemory) { - set_error("pro_ang1_cv", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void oblate_aswfa(T m, T n, T c, T cv, T x, T &s1f, T &s1d) { - if ((x >= 1) || (x <= -1) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_ang1_cv", SF_ERROR_DOMAIN, NULL); - s1f = std::numeric_limits::quiet_NaN(); - s1d = std::numeric_limits::quiet_NaN(); - } else { - specfun::Status status = specfun::aswfa(x, static_cast(m), - static_cast(n), c, -1, cv, - &s1f, &s1d); - if (status == specfun::Status::NoMemory) { - set_error("obl_ang1_cv", SF_ERROR_MEMORY, "memory allocation error"); - s1d = std::numeric_limits::quiet_NaN(); - s1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void prolate_radial1_nocv(T m, T n, T c, T x, T &r1f, T &r1d) { - int kf = 1, kd = 1; - T r2f = 0.0, r2d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_rad1", SF_ERROR_DOMAIN, NULL); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - } -} - -template -void prolate_radial2_nocv(T m, T n, T c, T x, T &r2f, T &r2d) { - int kf = 2, kd = 1; - T r1f = 0.0, r1d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("pro_rad2", SF_ERROR_DOMAIN, NULL); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - } -} - -template -void prolate_radial1(T m, T n, T c, T cv, T x, T &r1f, T &r1d) { - int kf = 1; - T r2f = 0.0, r2d = 0.0; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_rad1_cv", SF_ERROR_DOMAIN, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } else { - int_m = (int) m; - int_n = (int) n; - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad1_cv", SF_ERROR_MEMORY, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } - } -} - -template -void prolate_radial2(T m, T n, T c, T cv, T x, T &r2f, T &r2d) { - int kf = 2; - T r1f = 0.0, r1d = 0.0; - int int_m, int_n; - - if ((x <= 1.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("pro_rad2_cv", SF_ERROR_DOMAIN, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } else { - int_m = (int) m; - int_n = (int) n; - if (specfun::rswfp(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("pro_rad2_cv", SF_ERROR_MEMORY, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } - } -} - -template -void oblate_radial1_nocv(T m, T n, T c, T x, T &r1f, T &r1d) { - int kf = 1, kd = -1; - T r2f = 0.0, r2d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_rad1", SF_ERROR_DOMAIN, NULL); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfo(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad1", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_radial2_nocv(T m, T n, T c, T x, T &r2f, T &r2d) { - int kf = 2, kd = -1; - T r1f = 0.0, r1d = 0.0, cv = 0.0, *eg; - int int_m, int_n; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n)) || ((n - m) > 198)) { - set_error("obl_rad2", SF_ERROR_DOMAIN, NULL); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - int_m = (int) m; - int_n = (int) n; - eg = (T *) malloc(sizeof(T) * (n - m + 2)); - if (eg == NULL) { - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - specfun::Status status = specfun::segv(int_m, int_n, c, kd, &cv, eg); - free(eg); - if (status == specfun::Status::NoMemory) { - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - if (specfun::rswfo(int_m, int_n, c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory){ - set_error("obl_rad2", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } -} - -template -void oblate_radial1(T m, T n, T c, T cv, T x, T &r1f, T &r1d) { - int kf = 1; - T r2f = 0.0, r2d = 0.0; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_rad1_cv", SF_ERROR_DOMAIN, NULL); - r1f = std::numeric_limits::quiet_NaN(); - r1d = std::numeric_limits::quiet_NaN(); - } else { - if (specfun::rswfo(static_cast(m), static_cast(n), - c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad1_cv", SF_ERROR_MEMORY, "memory allocation error"); - r1d = std::numeric_limits::quiet_NaN(); - r1f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -template -void oblate_radial2(T m, T n, T c, T cv, T x, T &r2f, T &r2d) { - int kf = 2; - T r1f = 0.0, r1d = 0.0; - - if ((x < 0.0) || (m < 0) || (n < m) || (m != floor(m)) || (n != floor(n))) { - set_error("obl_rad2_cv", SF_ERROR_DOMAIN, NULL); - r2f = std::numeric_limits::quiet_NaN(); - r2d = std::numeric_limits::quiet_NaN(); - } else { - if (specfun::rswfo(static_cast(m), static_cast(n), - c, x, cv, kf, &r1f, &r1d, &r2f, &r2d) - == specfun::Status::NoMemory) { - set_error("obl_rad2_cv", SF_ERROR_MEMORY, "memory allocation error"); - r2d = std::numeric_limits::quiet_NaN(); - r2f = std::numeric_limits::quiet_NaN(); - return; - } - } -} - -} // namespace xsf diff --git a/scipy/special/xsf/stats.h b/scipy/special/xsf/stats.h deleted file mode 100644 index 267bf3c6d00d..000000000000 --- a/scipy/special/xsf/stats.h +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once - -#include "xsf/cephes/bdtr.h" -#include "xsf/cephes/chdtr.h" -#include "xsf/cephes/fdtr.h" -#include "xsf/cephes/gdtr.h" -#include "xsf/cephes/incbet.h" -#include "xsf/cephes/incbi.h" -#include "xsf/cephes/kolmogorov.h" -#include "xsf/cephes/nbdtr.h" -#include "xsf/cephes/ndtr.h" -#include "xsf/cephes/ndtri.h" -#include "xsf/cephes/owens_t.h" -#include "xsf/cephes/pdtr.h" -#include "xsf/cephes/tukey.h" -#include "xsf/erf.h" - -namespace xsf { - -inline double bdtr(double k, int n, double p) { return cephes::bdtr(k, n, p); } - -inline double bdtri(double k, int n, double y) { return cephes::bdtri(k, n, y); } - -inline double bdtrc(double k, int n, double p) { return cephes::bdtrc(k, n, p); } - -inline double chdtr(double df, double x) { return cephes::chdtr(df, x); } - -inline double chdtrc(double df, double x) { return cephes::chdtrc(df, x); } - -inline double chdtri(double df, double y) { return cephes::chdtri(df, y); } - -inline double fdtr(double a, double b, double x) { return cephes::fdtr(a, b, x); } - -inline double fdtrc(double a, double b, double x) { return cephes::fdtrc(a, b, x); } - -inline double fdtri(double a, double b, double y) { return cephes::fdtri(a, b, y); } - -inline double gdtr(double a, double b, double x) { return cephes::gdtr(a, b, x); } - -inline double gdtrc(double a, double b, double x) { return cephes::gdtrc(a, b, x); } - -inline double kolmogorov(double x) { return cephes::kolmogorov(x); } - -inline double kolmogc(double x) { return cephes::kolmogc(x); } - -inline double kolmogi(double x) { return cephes::kolmogi(x); } - -inline double kolmogci(double x) { return cephes::kolmogci(x); } - -inline double kolmogp(double x) { return cephes::kolmogp(x); } - -inline double ndtr(double x) { return cephes::ndtr(x); } - -inline float ndtr(float x) { return ndtr(static_cast(x)); } - -inline std::complex ndtr(std::complex z) { return 0.5 * erfc(-z * M_SQRT1_2); } - -inline std::complex ndtr(std::complex z) { - return static_cast>(ndtr(static_cast>(z))); -} - -/* - * Log of the CDF of the normal distribution for double x. - * - * Let F(x) be the CDF of the standard normal distribution. - * This implementation of log(F(x)) is based on the identities - * - * F(x) = erfc(-x/√2)/2 - * = 1 - erfc(x/√2)/2 - * - * We use the first formula for x < -1, with erfc(z) replaced - * by erfcx(z)*exp(-z**2) to ensure high precision for large - * negative values when we take the logarithm: - * - * log F(x) = log(erfc(-x/√2)/2) - * = log(erfcx(-x/√2)/2)*exp(-x**2/2)) - * = log(erfcx(-x/√2)/2) - x**2/2 - * - * For x >= -1, we use the second formula for F(x): - * - * log F(x) = log(1 - erfc(x/√2)/2) - * = log1p(-erfc(x/√2)/2) - */ -inline double log_ndtr(double x) { - double t = x * M_SQRT1_2; - if (x < -1.0) { - return log(erfcx(-t) / 2) - t * t; - } else { - return log1p(-erfc(t) / 2); - } -} - -inline float log_ndtr(float x) { return log_ndtr(static_cast(x)); } - -/* - * Log of the normal CDF for complex arguments. - * - * This is equivalent to log(ndtr(z)), but is more robust to overflow at $z\to\infty$. - * This implementation uses $\erfc(z) = \exp(-z^2) w(iz)$ taking special care to select - * the principal branch of the log function log( exp(-z^2) w(i z) ) - */ -inline std::complex log_ndtr(std::complex z) { - if (z.real() > 6) { - // Underflow. Close to the real axis, expand the log in log(1 - ndtr(-z)). - std::complex w = -0.5 * erfc(z * M_SQRT1_2); - if (std::abs(w) < 1e-8) { - return w; - } - } - - z *= -M_SQRT1_2; - double x = std::real(z); - double y = std::imag(z); - - /* Compute the principal branch of $log(exp(-z^2))$, using the fact that - * $log(e^t) = log|e^t| + i Arg(e^t)$, and that if $t = r + is$, then - * $e^t = e^r (\cos(s) + i \sin(s))$. - */ - double mRe_z2 = (y - x) * (x + y); // Re(-z^2), being careful of overflow - double mIm_z2 = -2 * x * y; // Im(-z^2) - - double im = fmod(mIm_z2, 2.0 * M_PI); - if (im > M_PI) { - im -= 2.0 * M_PI; - } - - std::complex val1 = std::complex(mRe_z2, im); - - std::complex val2 = log(xsf::wofz(complex(-y, x))); - std::complex result = val1 + val2 - NPY_LOGE2; - - /* Again, select the principal branch: log(z) = log|z| + i arg(z), thus - * the imaginary part of the result should belong to [-pi, pi]. - */ - im = imag(result); - if (im >= M_PI) { - im -= 2 * M_PI; - } - if (im < -M_PI) { - im += 2 * M_PI; - } - - return {result.real(), im}; -} - -inline std::complex log_ndtr(std::complex z) { - return static_cast>(log_ndtr(static_cast>(z))); -} - -inline double nbdtr(int k, int n, double p) { return cephes::nbdtr(k, n, p); } - -inline double nbdtrc(int k, int n, double p) { return cephes::nbdtrc(k, n, p); } - -inline double nbdtri(int k, int n, double p) { return cephes::nbdtri(k, n, p); } - -inline double ndtri(double x) { return cephes::ndtri(x); } - -inline double owens_t(double h, double a) { return cephes::owens_t(h, a); } - -inline double pdtr(double k, double m) { return cephes::pdtr(k, m); } - -inline double pdtrc(double k, double m) { return cephes::pdtrc(k, m); } - -inline double pdtri(int k, double y) { return cephes::pdtri(k, y); } - -inline double smirnov(int n, double x) { return cephes::smirnov(n, x); } - -inline double smirnovc(int n, double x) { return cephes::smirnovc(n, x); } - -inline double smirnovi(int n, double x) { return cephes::smirnovi(n, x); } - -inline double smirnovci(int n, double x) { return cephes::smirnovci(n, x); } - -inline double smirnovp(int n, double x) { return cephes::smirnovp(n, x); } - -inline double tukeylambdacdf(double x, double lmbda) { return cephes::tukeylambdacdf(x, lmbda); } - -inline float tukeylambdacdf(float x, double lmbda) { - return tukeylambdacdf(static_cast(x), static_cast(lmbda)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/struve.h b/scipy/special/xsf/struve.h deleted file mode 100644 index d9541aa22a35..000000000000 --- a/scipy/special/xsf/struve.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -#include "cephes/struve.h" - -namespace xsf { -namespace detail { - - inline double itth0(double x) { - - // =========================================================== - // Purpose: Evaluate the integral H0(t)/t with respect to t - // from x to infinity - // Input : x --- Lower limit ( x ≥ 0 ) - // Output: TTH --- Integration of H0(t)/t from x to infinity - // =========================================================== - - int k; - double f0, g0, r, s, t, tth, tty, xt; - const double pi = 3.141592653589793; - s = 1.0; - r = 1.0; - if (x < 24.5) { - for (k = 1; k < 61; k++) { - r = -r * x * x * (2.0 * k - 1.0) / pow(2.0 * k + 1.0, 3); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - tth = pi / 2.0 - 2.0 / pi * x * s; - } else { - for (k = 1; k < 11; k++) { - r = -r * pow(2.0 * k - 1.0, 3) / ((2.0 * k + 1.0) * x * x); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - tth = 2.0 / (pi * x) * s; - t = 8.0 / x; - xt = x + 0.25 * pi; - f0 = (((((0.18118e-2 * t - 0.91909e-2) * t + 0.017033) * t - 0.9394e-3) * t - 0.051445) * t - 0.11e-5) * t + - 0.7978846; - g0 = - (((((-0.23731e-2 * t + 0.59842e-2) * t + 0.24437e-2) * t - 0.0233178) * t + 0.595e-4) * t + 0.1620695) * - t; - tty = (f0 * sin(xt) - g0 * cos(xt)) / (sqrt(x) * x); - tth = tth + tty; - } - return tth; - } - - inline double itsh0(double x) { - - // =================================================== - // Purpose: Evaluate the integral of Struve function - // H0(t) with respect to t from 0 and x - // Input : x --- Upper limit ( x ≥ 0 ) - // Output: TH0 --- Integration of H0(t) from 0 and x - // =================================================== - - int k; - double a[25], a0, a1, af, bf, bg, r, rd, s, s0, th0, ty, xp; - const double pi = 3.141592653589793; - const double el = 0.57721566490153; - - r = 1.0; - if (x <= 30.0) { - s = 0.5; - for (k = 1; k < 101; k++) { - rd = 1.0; - if (k == 1) { - rd = 0.5; - } - r = -r * rd * k / (k + 1.0) * pow(x / (2.0 * k + 1.0), 2); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - th0 = 2.0 / pi * x * x * s; - } else { - s = 1.0; - for (k = 1; k < 13; k++) { - r = -r * k / (k + 1.0) * pow((2.0 * k + 1.0) / x, 2); - s += r; - if (fabs(r) < fabs(s) * 1.0e-12) { - break; - } - } - s0 = s / (pi * x * x) + 2.0 / pi * (log(2.0 * x) + el); - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - for (k = 1; k < 21; k++) { - af = ((1.5 * (k + 0.5) * (k + 5.0 / 6.0) * a1 - 0.5 * (k + 0.5) * (k + 0.5) * (k - 0.5) * a0)) / - (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - bf = 1.0; - r = 1.0; - for (k = 1; k < 11; k++) { - r = -r / (x * x); - bf += a[2 * k - 1] * r; - } - bg = a[0] * x; - r = 1.0 / x; - for (k = 1; k < 10; k++) { - r = -r / (x * x); - bg += a[2 * k] * r; - } - xp = x + 0.25 * pi; - ty = sqrt(2.0 / (pi * x)) * (bg * cos(xp) - bf * sin(xp)); - th0 = ty + s0; - } - return th0; - } - - inline double itsl0(double x) { - - // =========================================================== - // Purpose: Evaluate the integral of modified Struve function - // L0(t) with respect to t from 0 to x - // Input : x --- Upper limit ( x ≥ 0 ) - // Output: TL0 --- Integration of L0(t) from 0 to x - // =========================================================== - - int k; - double a[18], a0, a1, af, r, rd, s, s0, ti, tl0; - const double pi = 3.141592653589793; - const double el = 0.57721566490153; - r = 1.0; - if (x <= 20.0) { - s = 0.5; - for (k = 1; k < 101; k++) { - rd = 1.0; - if (k == 1) { - rd = 0.5; - } - r = r * rd * k / (k + 1.0) * pow(x / (2.0 * k + 1.0), 2); - s += r; - if (fabs(r / s) < 1.0e-12) { - break; - } - } - tl0 = 2.0 / pi * x * x * s; - } else { - s = 1.0; - for (k = 1; k < 11; k++) { - r = r * k / (k + 1.0) * pow((2.0 * k + 1.0) / x, 2); - s += r; - if (fabs(r / s) < 1.0e-12) { - break; - } - } - s0 = -s / (pi * x * x) + 2.0 / pi * (log(2.0 * x) + el); - a0 = 1.0; - a1 = 5.0 / 8.0; - a[0] = a1; - for (k = 1; k < 11; k++) { - af = ((1.5 * (k + .50) * (k + 5.0 / 6.0) * a1 - 0.5 * pow(k + 0.5, 2) * (k - 0.5) * a0)) / (k + 1.0); - a[k] = af; - a0 = a1; - a1 = af; - } - ti = 1.0; - r = 1.0; - for (k = 1; k < 11; k++) { - r = r / x; - ti += a[k - 1] * r; - } - tl0 = ti / sqrt(2 * pi * x) * exp(x) + s0; - } - return tl0; - } - -} // namespace detail - -template -T itstruve0(T x) { - if (x < 0) { - x = -x; - } - - T out = detail::itsh0(x); - SPECFUN_CONVINF("itstruve0", out); - return out; -} - -template -T it2struve0(T x) { - int flag = 0; - - if (x < 0) { - x = -x; - flag = 1; - } - - T out = detail::itth0(x); - SPECFUN_CONVINF("it2struve0", out); - if (flag) { - out = M_PI - out; - } - return out; -} - -template -T itmodstruve0(T x) { - if (x < 0) { - x = -x; - } - - T out = detail::itsl0(x); - SPECFUN_CONVINF("itmodstruve0", out); - return out; -} - -double struve_h(double v, double z) { return cephes::struve_h(v, z); } - -float struve_h(float v, float z) { return struve_h(static_cast(v), static_cast(z)); } - -double struve_l(double v, double z) { return cephes::struve_l(v, z); } - -float struve_l(float v, float z) { return struve_l(static_cast(v), static_cast(z)); } - -} // namespace xsf diff --git a/scipy/special/xsf/third_party/kokkos/mdspan.hpp b/scipy/special/xsf/third_party/kokkos/mdspan.hpp deleted file mode 100644 index ecfa332cf010..000000000000 --- a/scipy/special/xsf/third_party/kokkos/mdspan.hpp +++ /dev/null @@ -1,5674 +0,0 @@ -#ifndef _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ -#define _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ - -//BEGIN_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#ifndef MDSPAN_HPP_ -#define MDSPAN_HPP_ - -#ifndef MDSPAN_IMPL_STANDARD_NAMESPACE - #define MDSPAN_IMPL_STANDARD_NAMESPACE std -#endif - -#ifndef MDSPAN_IMPL_PROPOSED_NAMESPACE - #define MDSPAN_IMPL_PROPOSED_NAMESPACE experimental -#endif - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#ifndef __has_include -# define __has_include(x) 0 -#endif - -#if __has_include() -# include -#else -# include -# include -#endif - -#ifdef _MSVC_LANG -#define _MDSPAN_CPLUSPLUS _MSVC_LANG -#else -#define _MDSPAN_CPLUSPLUS __cplusplus -#endif - -#define MDSPAN_CXX_STD_14 201402L -#define MDSPAN_CXX_STD_17 201703L -#define MDSPAN_CXX_STD_20 202002L -// Note GCC has not updated this in version 13 -#ifdef __clang__ -#define MDSPAN_CXX_STD_23 202302L -#else -#define MDSPAN_CXX_STD_23 202100L -#endif - -#define MDSPAN_HAS_CXX_14 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14) -#define MDSPAN_HAS_CXX_17 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_17) -#define MDSPAN_HAS_CXX_20 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_20) -#define MDSPAN_HAS_CXX_23 (_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_23) - -static_assert(_MDSPAN_CPLUSPLUS >= MDSPAN_CXX_STD_14, "mdspan requires C++14 or later."); - -#ifndef _MDSPAN_COMPILER_CLANG -# if defined(__clang__) -# define _MDSPAN_COMPILER_CLANG __clang__ -# endif -#endif - -#if !defined(_MDSPAN_COMPILER_MSVC) && !defined(_MDSPAN_COMPILER_MSVC_CLANG) -# if defined(_MSC_VER) -# if !defined(_MDSPAN_COMPILER_CLANG) -# define _MDSPAN_COMPILER_MSVC _MSC_VER -# else -# define _MDSPAN_COMPILER_MSVC_CLANG _MSC_VER -# endif -# endif -#endif - -#ifndef _MDSPAN_COMPILER_INTEL -# ifdef __INTEL_COMPILER -# define _MDSPAN_COMPILER_INTEL __INTEL_COMPILER -# endif -#endif - -#ifndef _MDSPAN_COMPILER_APPLECLANG -# ifdef __apple_build_version__ -# define _MDSPAN_COMPILER_APPLECLANG __apple_build_version__ -# endif -#endif - -#ifndef _MDSPAN_HAS_CUDA -# if defined(__CUDACC__) -# define _MDSPAN_HAS_CUDA __CUDACC__ -# endif -#endif - -#ifndef _MDSPAN_HAS_HIP -# if defined(__HIPCC__) -# define _MDSPAN_HAS_HIP __HIPCC__ -# endif -#endif - -#ifndef _MDSPAN_HAS_SYCL -# if defined(SYCL_LANGUAGE_VERSION) -# define _MDSPAN_HAS_SYCL SYCL_LANGUAGE_VERSION -# endif -#endif - -#ifndef __has_cpp_attribute -# define __has_cpp_attribute(x) 0 -#endif - -#ifndef _MDSPAN_PRESERVE_STANDARD_LAYOUT -// Preserve standard layout by default, but we're not removing the old version -// that turns this off until we're sure this doesn't have an unreasonable cost -// to the compiler or optimizer. -# define _MDSPAN_PRESERVE_STANDARD_LAYOUT 1 -#endif - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -# if ((__has_cpp_attribute(no_unique_address) >= 201803L) && \ - (!defined(__NVCC__) || MDSPAN_HAS_CXX_20) && \ - (!defined(_MDSPAN_COMPILER_MSVC) || MDSPAN_HAS_CXX_20)) -# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 -# define _MDSPAN_NO_UNIQUE_ADDRESS [[no_unique_address]] -# else -# define _MDSPAN_NO_UNIQUE_ADDRESS -# endif -#endif - -// NVCC older than 11.6 chokes on the no-unique-address-emulation -// so just pretend to use it (to avoid the full blown EBO workaround -// which NVCC also doesn't like ...), and leave the macro empty -#ifndef _MDSPAN_NO_UNIQUE_ADDRESS -# if defined(__NVCC__) -# define _MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS 1 -# define _MDSPAN_USE_FAKE_ATTRIBUTE_NO_UNIQUE_ADDRESS -# endif -# define _MDSPAN_NO_UNIQUE_ADDRESS -#endif - -// AMDs HIP compiler seems to have issues with concepts -// it pretends concepts exist, but doesn't ship -#ifndef __HIPCC__ -#ifndef _MDSPAN_USE_CONCEPTS -# if defined(__cpp_concepts) && __cpp_concepts >= 201507L -# define _MDSPAN_USE_CONCEPTS 1 -# endif -#endif -#endif - -#ifndef _MDSPAN_USE_FOLD_EXPRESSIONS -# if (defined(__cpp_fold_expressions) && __cpp_fold_expressions >= 201603L) \ - || (!defined(__cpp_fold_expressions) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_FOLD_EXPRESSIONS 1 -# endif -#endif - -#ifndef _MDSPAN_USE_INLINE_VARIABLES -# if defined(__cpp_inline_variables) && __cpp_inline_variables >= 201606L \ - || (!defined(__cpp_inline_variables) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_INLINE_VARIABLES 1 -# endif -#endif - -#ifndef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS -# if (!(defined(__cpp_lib_type_trait_variable_templates) && __cpp_lib_type_trait_variable_templates >= 201510L) \ - || !MDSPAN_HAS_CXX_17) -# if !(defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS 1 -# endif -# endif -#endif - -#ifndef _MDSPAN_USE_VARIABLE_TEMPLATES -# if (defined(__cpp_variable_templates) && __cpp_variable_templates >= 201304 && MDSPAN_HAS_CXX_17) \ - || (!defined(__cpp_variable_templates) && MDSPAN_HAS_CXX_17) -# define _MDSPAN_USE_VARIABLE_TEMPLATES 1 -# endif -#endif // _MDSPAN_USE_VARIABLE_TEMPLATES - -#ifndef _MDSPAN_USE_CONSTEXPR_14 -# if (defined(__cpp_constexpr) && __cpp_constexpr >= 201304) \ - || (!defined(__cpp_constexpr) && MDSPAN_HAS_CXX_14) \ - && (!(defined(__INTEL_COMPILER) && __INTEL_COMPILER <= 1700)) -# define _MDSPAN_USE_CONSTEXPR_14 1 -# endif -#endif - -#ifndef _MDSPAN_USE_INTEGER_SEQUENCE -# if defined(_MDSPAN_COMPILER_MSVC) -# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# endif -# endif -#endif -#ifndef _MDSPAN_USE_INTEGER_SEQUENCE -# if (defined(__cpp_lib_integer_sequence) && __cpp_lib_integer_sequence >= 201304) \ - || (!defined(__cpp_lib_integer_sequence) && MDSPAN_HAS_CXX_14) \ - /* as far as I can tell, libc++ seems to think this is a C++11 feature... */ \ - || (defined(__GLIBCXX__) && __GLIBCXX__ > 20150422 && __GNUC__ < 5 && !defined(__INTEL_CXX11_MODE__)) - // several compilers lie about integer_sequence working properly unless the C++14 standard is used -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 - // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 making - // integer_sequence work -# define _MDSPAN_USE_INTEGER_SEQUENCE 1 -# endif -#endif - -#ifndef _MDSPAN_USE_RETURN_TYPE_DEDUCTION -# if (defined(__cpp_return_type_deduction) && __cpp_return_type_deduction >= 201304) \ - || (!defined(__cpp_return_type_deduction) && MDSPAN_HAS_CXX_14) -# define _MDSPAN_USE_RETURN_TYPE_DEDUCTION 1 -# endif -#endif - -#ifndef _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION -# if (!defined(__NVCC__) || (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10 >= 1170)) && \ - ((defined(__cpp_deduction_guides) && __cpp_deduction_guides >= 201703) || \ - (!defined(__cpp_deduction_guides) && MDSPAN_HAS_CXX_17)) -# define _MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION 1 -# endif -#endif - -#ifndef _MDSPAN_USE_STANDARD_TRAIT_ALIASES -# if (defined(__cpp_lib_transformation_trait_aliases) && __cpp_lib_transformation_trait_aliases >= 201304) \ - || (!defined(__cpp_lib_transformation_trait_aliases) && MDSPAN_HAS_CXX_14) -# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 -# elif defined(_MDSPAN_COMPILER_APPLECLANG) && MDSPAN_HAS_CXX_14 - // appleclang seems to be missing the __cpp_lib_... macros, but doesn't seem to lie about C++14 -# define _MDSPAN_USE_STANDARD_TRAIT_ALIASES 1 -# endif -#endif - -#ifndef _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND -# ifdef __GNUC__ -# if __GNUC__ < 9 -# define _MDSPAN_DEFAULTED_CONSTRUCTORS_INHERITANCE_WORKAROUND 1 -# endif -# endif -#endif - -#ifndef MDSPAN_CONDITIONAL_EXPLICIT -# if MDSPAN_HAS_CXX_20 -# define MDSPAN_CONDITIONAL_EXPLICIT(COND) explicit(COND) -# else -# define MDSPAN_CONDITIONAL_EXPLICIT(COND) -# endif -#endif - -#ifndef MDSPAN_USE_BRACKET_OPERATOR -# if defined(__cpp_multidimensional_subscript) -# define MDSPAN_USE_BRACKET_OPERATOR 1 -# else -# define MDSPAN_USE_BRACKET_OPERATOR 0 -# endif -#endif - -#ifndef MDSPAN_USE_PAREN_OPERATOR -# if !MDSPAN_USE_BRACKET_OPERATOR -# define MDSPAN_USE_PAREN_OPERATOR 1 -# else -# define MDSPAN_USE_PAREN_OPERATOR 0 -# endif -#endif - -#if MDSPAN_USE_BRACKET_OPERATOR -# define __MDSPAN_OP(mds,...) mds[__VA_ARGS__] -// Corentins demo compiler for subscript chokes on empty [] call, -// though I believe the proposal supports it? -#ifdef MDSPAN_NO_EMPTY_BRACKET_OPERATOR -# define __MDSPAN_OP0(mds) mds.accessor().access(mds.data_handle(),0) -#else -# define __MDSPAN_OP0(mds) mds[] -#endif -# define __MDSPAN_OP1(mds, a) mds[a] -# define __MDSPAN_OP2(mds, a, b) mds[a,b] -# define __MDSPAN_OP3(mds, a, b, c) mds[a,b,c] -# define __MDSPAN_OP4(mds, a, b, c, d) mds[a,b,c,d] -# define __MDSPAN_OP5(mds, a, b, c, d, e) mds[a,b,c,d,e] -# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds[a,b,c,d,e,f] -#else -# define __MDSPAN_OP(mds,...) mds(__VA_ARGS__) -# define __MDSPAN_OP0(mds) mds() -# define __MDSPAN_OP1(mds, a) mds(a) -# define __MDSPAN_OP2(mds, a, b) mds(a,b) -# define __MDSPAN_OP3(mds, a, b, c) mds(a,b,c) -# define __MDSPAN_OP4(mds, a, b, c, d) mds(a,b,c,d) -# define __MDSPAN_OP5(mds, a, b, c, d, e) mds(a,b,c,d,e) -# define __MDSPAN_OP6(mds, a, b, c, d, e, f) mds(a,b,c,d,e,f) -#endif -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/config.hpp - -#include -#include -#include // std::is_void -#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_SYCL) -#include "assert.h" -#endif - -#ifndef _MDSPAN_HOST_DEVICE -# if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) -# define _MDSPAN_HOST_DEVICE __host__ __device__ -# else -# define _MDSPAN_HOST_DEVICE -# endif -#endif - -#ifndef MDSPAN_FORCE_INLINE_FUNCTION -# ifdef _MDSPAN_COMPILER_MSVC // Microsoft compilers -# define MDSPAN_FORCE_INLINE_FUNCTION __forceinline _MDSPAN_HOST_DEVICE -# else -# define MDSPAN_FORCE_INLINE_FUNCTION __attribute__((always_inline)) _MDSPAN_HOST_DEVICE -# endif -#endif - -#ifndef MDSPAN_INLINE_FUNCTION -# define MDSPAN_INLINE_FUNCTION inline _MDSPAN_HOST_DEVICE -#endif - -#ifndef MDSPAN_FUNCTION -# define MDSPAN_FUNCTION _MDSPAN_HOST_DEVICE -#endif - -#ifdef _MDSPAN_HAS_HIP -# define MDSPAN_DEDUCTION_GUIDE _MDSPAN_HOST_DEVICE -#else -# define MDSPAN_DEDUCTION_GUIDE -#endif - -// In CUDA defaulted functions do not need host device markup -#ifndef MDSPAN_INLINE_FUNCTION_DEFAULTED -# define MDSPAN_INLINE_FUNCTION_DEFAULTED -#endif - -//============================================================================== -// {{{1 - -#define MDSPAN_PP_COUNT(...) \ - _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE( \ - _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(__VA_ARGS__) \ - ) - -#define _MDSPAN_PP_INTERNAL_ARGS_AUGMENTER(...) unused, __VA_ARGS__ -#define _MDSPAN_PP_INTERNAL_EXPAND(x) x -#define _MDSPAN_PP_INTERNAL_EXPAND_ARGS_PRIVATE(...) \ - _MDSPAN_PP_INTERNAL_EXPAND( \ - _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ - __VA_ARGS__, 69, 68, 67, 66, 65, 64, 63, 62, 61, \ - 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ - 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, \ - 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, \ - 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, \ - 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 \ - ) \ - ) -# define _MDSPAN_PP_INTERNAL_COUNT_PRIVATE( \ - _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, count, ...) count \ - /**/ - -#define MDSPAN_PP_STRINGIFY_IMPL(x) #x -#define MDSPAN_PP_STRINGIFY(x) MDSPAN_PP_STRINGIFY_IMPL(x) - -#define MDSPAN_PP_CAT_IMPL(x, y) x ## y -#define MDSPAN_PP_CAT(x, y) MDSPAN_PP_CAT_IMPL(x, y) - -#define MDSPAN_PP_EVAL(X, ...) X(__VA_ARGS__) - -#define MDSPAN_PP_REMOVE_PARENS_IMPL(...) __VA_ARGS__ -#define MDSPAN_PP_REMOVE_PARENS(...) MDSPAN_PP_REMOVE_PARENS_IMPL __VA_ARGS__ - -#define MDSPAN_IMPL_STANDARD_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) -#define MDSPAN_IMPL_PROPOSED_NAMESPACE_STRING MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_STANDARD_NAMESPACE) "::" MDSPAN_PP_STRINGIFY(MDSPAN_IMPL_PROPOSED_NAMESPACE) - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -#if defined(_MDSPAN_HAS_CUDA) || defined(_MDSPAN_HAS_HIP) -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - printf("%s:%u: precondition failure: `%s`\n", file, line, cond); - assert(0); -} -#elif defined(_MDSPAN_HAS_SYCL) -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - sycl::ext::oneapi::experimental::printf("%s:%u: precondition failure: `%s`\n", file, line, cond); - assert(0); -} -#else -MDSPAN_FUNCTION inline void default_precondition_violation_handler(const char* cond, const char* file, unsigned line) -{ - std::fprintf(stderr, "%s:%u: precondition failure: `%s`\n", file, line, cond); - std::abort(); -} -#endif - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#ifndef MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER -#define MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line) \ - MDSPAN_IMPL_STANDARD_NAMESPACE::detail::default_precondition_violation_handler(cond, file, line) -#endif - -#ifndef MDSPAN_IMPL_CHECK_PRECONDITION - #ifndef NDEBUG - #define MDSPAN_IMPL_CHECK_PRECONDITION 0 - #else - #define MDSPAN_IMPL_CHECK_PRECONDITION 1 - #endif -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -template -MDSPAN_FUNCTION constexpr void precondition(const char* cond, const char* file, unsigned line) -{ - if (not check) { return; } - // in case the macro doesn't use the arguments for custom macros - (void) cond; - (void) file; - (void) line; - MDSPAN_IMPL_PRECONDITION_VIOLATION_HANDLER(cond, file, line); -} - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#define MDSPAN_IMPL_PRECONDITION(...) \ - do { \ - if (not (__VA_ARGS__)) { \ - MDSPAN_IMPL_STANDARD_NAMESPACE::detail::precondition(#__VA_ARGS__, __FILE__, __LINE__); \ - } \ - } while (0) - -// end Preprocessor helpers }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -// These compatibility macros don't help with partial ordering, but they should do the trick -// for what we need to do with concepts in mdspan -#ifdef _MDSPAN_USE_CONCEPTS -# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) > requires REQ -# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ - MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS requires REQ \ - /**/ -#else -# define MDSPAN_CLOSE_ANGLE_REQUIRES(REQ) , typename ::std::enable_if<(REQ), int>::type = 0> -# define MDSPAN_FUNCTION_REQUIRES(PAREN_PREQUALS, FNAME, PAREN_PARAMS, QUALS, REQ) \ - MDSPAN_TEMPLATE_REQUIRES( \ - class __function_requires_ignored=void, \ - (std::is_void<__function_requires_ignored>::value && REQ) \ - ) MDSPAN_PP_REMOVE_PARENS(PAREN_PREQUALS) FNAME PAREN_PARAMS QUALS \ - /**/ -#endif - -#if defined(_MDSPAN_COMPILER_MSVC) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL) -# define MDSPAN_TEMPLATE_REQUIRES(...) \ - MDSPAN_PP_CAT( \ - MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__))\ - (__VA_ARGS__), \ - ) \ - /**/ -#else -# define MDSPAN_TEMPLATE_REQUIRES(...) \ - MDSPAN_PP_EVAL( \ - MDSPAN_PP_CAT(MDSPAN_TEMPLATE_REQUIRES_, MDSPAN_PP_COUNT(__VA_ARGS__)), \ - __VA_ARGS__ \ - ) \ - /**/ -#endif - -#define MDSPAN_TEMPLATE_REQUIRES_2(TP1, REQ) \ - template end Concept emulation }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#ifdef _MDSPAN_USE_INLINE_VARIABLES -# define _MDSPAN_INLINE_VARIABLE inline -#else -# define _MDSPAN_INLINE_VARIABLE -#endif - -// end inline variables }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION -# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } -# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - decltype(auto) MDSPAN_PP_REMOVE_PARENS(SIGNATURE) { return MDSPAN_PP_REMOVE_PARENS(BODY); } -#else -# define _MDSPAN_DEDUCE_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ - -> std::remove_cv_t> \ - { return MDSPAN_PP_REMOVE_PARENS(BODY); } -# define _MDSPAN_DEDUCE_DECLTYPE_AUTO_RETURN_TYPE_SINGLE_LINE(SIGNATURE, BODY) \ - auto MDSPAN_PP_REMOVE_PARENS(SIGNATURE) \ - -> decltype(BODY) \ - { return MDSPAN_PP_REMOVE_PARENS(BODY); } - -#endif - -// end Return type deduction }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -struct __mdspan_enable_fold_comma { }; - -#ifdef _MDSPAN_USE_FOLD_EXPRESSIONS -# define _MDSPAN_FOLD_AND(...) ((__VA_ARGS__) && ...) -# define _MDSPAN_FOLD_AND_TEMPLATE(...) ((__VA_ARGS__) && ...) -# define _MDSPAN_FOLD_OR(...) ((__VA_ARGS__) || ...) -# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) (INIT = ... = (__VA_ARGS__)) -# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) (PACK = ... = (__VA_ARGS__)) -# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) (PACK * ... * (__VA_ARGS__)) -# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) (PACK + ... + (__VA_ARGS__)) -# define _MDSPAN_FOLD_COMMA(...) ((__VA_ARGS__), ...) -#else - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -namespace __fold_compatibility_impl { - -// We could probably be more clever here, but at the (small) risk of losing some compiler understanding. For the -// few operations we need, it's not worth generalizing over the operation - -#if _MDSPAN_USE_RETURN_TYPE_DEDUCTION - -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_and_impl() { - return true; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_and_impl(Arg&& arg, Args&&... args) { - return ((Arg&&)arg) && __fold_compatibility_impl::__fold_right_and_impl((Args&&)args...); -} - -MDSPAN_FORCE_INLINE_FUNCTION -constexpr decltype(auto) __fold_right_or_impl() { - return false; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_or_impl(Arg&& arg, Args&&... args) { - return ((Arg&&)arg) || __fold_compatibility_impl::__fold_right_or_impl((Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_left_assign_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_left_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return __fold_compatibility_impl::__fold_left_assign_impl((((Arg1&&)arg1) = ((Arg2&&)arg2)), (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_assign_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_assign_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) = __fold_compatibility_impl::__fold_right_assign_impl((Arg2&&)arg2, (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_plus_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_plus_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) + __fold_compatibility_impl::__fold_right_plus_impl((Arg2&&)arg2, (Args&&)args...); -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_times_impl(Arg1&& arg1) { - return (Arg1&&)arg1; -} - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr auto __fold_right_times_impl(Arg1&& arg1, Arg2&& arg2, Args&&... args) { - return ((Arg1&&)arg1) * __fold_compatibility_impl::__fold_right_times_impl((Arg2&&)arg2, (Args&&)args...); -} - -#else - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_and_impl_; -template <> -struct __fold_right_and_impl_<> { - using __rv = bool; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl() noexcept { - return true; - } -}; -template -struct __fold_right_and_impl_ { - using __next_t = __fold_right_and_impl_; - using __rv = decltype(std::declval() && std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg, Args&&... args) noexcept { - return ((Arg&&)arg) && __next_t::__impl((Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_and_impl_::__rv -__fold_right_and_impl(Args&&... args) { - return __fold_right_and_impl_::__impl((Args&&)args...); -} - -// end right and }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_or_impl_; -template <> -struct __fold_right_or_impl_<> { - using __rv = bool; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl() noexcept { - return false; - } -}; -template -struct __fold_right_or_impl_ { - using __next_t = __fold_right_or_impl_; - using __rv = decltype(std::declval() || std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg, Args&&... args) noexcept { - return ((Arg&&)arg) || __next_t::__impl((Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_or_impl_::__rv -__fold_right_or_impl(Args&&... args) { - return __fold_right_or_impl_::__impl((Args&&)args...); -} - -// end right or }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_plus_impl_; -template -struct __fold_right_plus_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_plus_impl_ { - using __next_t = __fold_right_plus_impl_; - using __rv = decltype(std::declval() + std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) + __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_plus_impl_::__rv -__fold_right_plus_impl(Args&&... args) { - return __fold_right_plus_impl_::__impl((Args&&)args...); -} - -// end right plus }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_times_impl_; -template -struct __fold_right_times_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_times_impl_ { - using __next_t = __fold_right_times_impl_; - using __rv = decltype(std::declval() * std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) * __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_times_impl_::__rv -__fold_right_times_impl(Args&&... args) { - return __fold_right_times_impl_::__impl((Args&&)args...); -} - -// end right times }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_right_assign_impl_; -template -struct __fold_right_assign_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_right_assign_impl_ { - using __next_t = __fold_right_assign_impl_; - using __rv = decltype(std::declval() = std::declval()); - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return ((Arg1&&)arg) = __next_t::__impl((Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_right_assign_impl_::__rv -__fold_right_assign_impl(Args&&... args) { - return __fold_right_assign_impl_::__impl((Args&&)args...); -} - -// end right assign }}}2 -//------------------------------------------------------------------------------ - -//------------------------------------------------------------------------------ -// {{{2 - -template -struct __fold_left_assign_impl_; -template -struct __fold_left_assign_impl_ { - using __rv = Arg&&; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg&& arg) noexcept { - return (Arg&&)arg; - } -}; -template -struct __fold_left_assign_impl_ { - using __assign_result_t = decltype(std::declval() = std::declval()); - using __next_t = __fold_left_assign_impl_<__assign_result_t, Args...>; - using __rv = typename __next_t::__rv; - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr __rv - __impl(Arg1&& arg, Arg2&& arg2, Args&&... args) noexcept { - return __next_t::__impl(((Arg1&&)arg) = (Arg2&&)arg2, (Args&&)args...); - } -}; - -template -MDSPAN_FORCE_INLINE_FUNCTION -constexpr typename __fold_left_assign_impl_::__rv -__fold_left_assign_impl(Args&&... args) { - return __fold_left_assign_impl_::__impl((Args&&)args...); -} - -// end left assign }}}2 -//------------------------------------------------------------------------------ - -#endif - - -template -constexpr __mdspan_enable_fold_comma __fold_comma_impl(Args&&... args) noexcept { return { }; } - -template -struct __bools; - -} // __fold_compatibility_impl - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -# define _MDSPAN_FOLD_AND(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_and_impl((__VA_ARGS__)...) -# define _MDSPAN_FOLD_OR(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_or_impl((__VA_ARGS__)...) -# define _MDSPAN_FOLD_ASSIGN_LEFT(INIT, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_left_assign_impl(INIT, (__VA_ARGS__)...) -# define _MDSPAN_FOLD_ASSIGN_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_assign_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_TIMES_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_times_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_PLUS_RIGHT(PACK, ...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_right_plus_impl((PACK)..., __VA_ARGS__) -# define _MDSPAN_FOLD_COMMA(...) MDSPAN_IMPL_STANDARD_NAMESPACE::__fold_compatibility_impl::__fold_comma_impl((__VA_ARGS__)...) - -# define _MDSPAN_FOLD_AND_TEMPLATE(...) \ - _MDSPAN_TRAIT(std::is_same, __fold_compatibility_impl::__bools<(__VA_ARGS__)..., true>, __fold_compatibility_impl::__bools) - -#endif - -// end fold expressions }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_VARIABLE_TEMPLATES -# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT##_v<__VA_ARGS__> -#else -# define _MDSPAN_TRAIT(TRAIT, ...) TRAIT<__VA_ARGS__>::value -#endif - -// end Variable template compatibility }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if _MDSPAN_USE_CONSTEXPR_14 -# define _MDSPAN_CONSTEXPR_14 constexpr -// Workaround for a bug (I think?) in EDG frontends -# ifdef __EDG__ -# define _MDSPAN_CONSTEXPR_14_DEFAULTED -# else -# define _MDSPAN_CONSTEXPR_14_DEFAULTED constexpr -# endif -#else -# define _MDSPAN_CONSTEXPR_14 -# define _MDSPAN_CONSTEXPR_14_DEFAULTED -#endif - -// end Pre-C++14 constexpr }}}1 -//============================================================================== -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/macros.hpp - -#include // size_t - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -template -struct default_accessor { - - using offset_policy = default_accessor; - using element_type = ElementType; - using reference = ElementType&; - using data_handle_type = ElementType*; - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr default_accessor() noexcept = default; - - MDSPAN_TEMPLATE_REQUIRES( - class OtherElementType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, OtherElementType(*)[], element_type(*)[]) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr default_accessor(default_accessor) noexcept {} - - MDSPAN_INLINE_FUNCTION - constexpr data_handle_type - offset(data_handle_type p, size_t i) const noexcept { - return p + i; - } - - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference access(data_handle_type p, size_t i) const noexcept { - return p[i]; - } - -}; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/default_accessor.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -struct full_extent_t { explicit full_extent_t() = default; }; - -_MDSPAN_INLINE_VARIABLE constexpr auto full_extent = full_extent_t{ }; - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/full_extent_t.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER -#ifndef MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ -#define MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ - - -#include -#include // integer_sequence - -//============================================================================== -// {{{1 - -#ifdef _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS - -#if _MDSPAN_USE_VARIABLE_TEMPLATES -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -#define _MDSPAN_BACKPORT_TRAIT(TRAIT) \ - template _MDSPAN_INLINE_VARIABLE constexpr auto TRAIT##_v = TRAIT::value; - -_MDSPAN_BACKPORT_TRAIT(is_assignable) -_MDSPAN_BACKPORT_TRAIT(is_constructible) -_MDSPAN_BACKPORT_TRAIT(is_convertible) -_MDSPAN_BACKPORT_TRAIT(is_default_constructible) -_MDSPAN_BACKPORT_TRAIT(is_trivially_destructible) -_MDSPAN_BACKPORT_TRAIT(is_same) -_MDSPAN_BACKPORT_TRAIT(is_empty) -_MDSPAN_BACKPORT_TRAIT(is_void) - -#undef _MDSPAN_BACKPORT_TRAIT - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif // _MDSPAN_USE_VARIABLE_TEMPLATES - -#endif // _MDSPAN_NEEDS_TRAIT_VARIABLE_TEMPLATE_BACKPORTS - -// end Variable template trait backports (e.g., is_void_v) }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if !defined(_MDSPAN_USE_INTEGER_SEQUENCE) || !_MDSPAN_USE_INTEGER_SEQUENCE - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -template -struct integer_sequence { - static constexpr size_t size() noexcept { return sizeof...(Vals); } - using value_type = T; -}; - -template -using index_sequence = std::integer_sequence; - -namespace __detail { - -template -struct __make_int_seq_impl; - -template -struct __make_int_seq_impl> -{ - using type = integer_sequence; -}; - -template -struct __make_int_seq_impl< - T, N, I, integer_sequence -> : __make_int_seq_impl> -{ }; - -} // end namespace __detail - -template -using make_integer_sequence = typename __detail::__make_int_seq_impl>::type; - -template -using make_index_sequence = typename __detail::__make_int_seq_impl>::type; - -template -using index_sequence_for = make_index_sequence; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif - -// end integer sequence (ugh...) }}}1 -//============================================================================== - -//============================================================================== -// {{{1 - -#if !defined(_MDSPAN_USE_STANDARD_TRAIT_ALIASES) || !_MDSPAN_USE_STANDARD_TRAIT_ALIASES - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -#define _MDSPAN_BACKPORT_TRAIT_ALIAS(TRAIT) \ - template using TRAIT##_t = typename TRAIT::type; - -_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_cv) -_MDSPAN_BACKPORT_TRAIT_ALIAS(remove_reference) - -template -using enable_if_t = typename enable_if<_B, _T>::type; - -#undef _MDSPAN_BACKPORT_TRAIT_ALIAS - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -#endif - -// end standard trait aliases }}}1 -//============================================================================== - -#endif //MDSPAN_INCLUDE_EXPERIMENTAL_BITS_TRAIT_BACKPORTS_HPP_ -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/trait_backports.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#if defined(__cpp_lib_span) -#include -#endif - -#include // size_t -#include // numeric_limits - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -#if defined(__cpp_lib_span) -using std::dynamic_extent; -#else -_MDSPAN_INLINE_VARIABLE constexpr auto dynamic_extent = std::numeric_limits::max(); -#endif -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//============================================================================================================== -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/dynamic_extent.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp - -#include -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// type alias used for rank-based tag dispatch -// -// this is used to enable alternatives to constexpr if when building for C++14 -// -template -using with_rank = std::integral_constant; - -template -constexpr bool common_integral_compare(I1 x, I2 y) -{ - static_assert(std::is_integral::value and - std::is_integral::value, ""); - - using I = std::common_type_t; - return static_cast(x) == static_cast(y); -} - -template -constexpr bool rankwise_equal(with_rank<0>, const T1&, const T2&, F) -{ - return true; -} -template -constexpr bool rankwise_equal(with_rank, const T1& x, const T2& y, F func) -{ - bool match = true; - - for (std::size_t r = 0; r < N; r++) { - match = match && common_integral_compare(func(x, r), func(y, r)); - } - - return match; -} - -constexpr struct -{ - template - constexpr auto operator()(const T& x, I i) const - { - return x.extent(i); - } -} extent; - -constexpr struct -{ - template - constexpr auto operator()(const T& x, I i) const - { - return x.stride(i); - } -} stride; - -} // namespace detail - -constexpr struct mdspan_non_standard_tag { -} mdspan_non_standard; - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/utility.hpp - -#ifdef __cpp_lib_span -#include -#endif -#include -#include - -#include -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// Function used to check compatibility of extents in converting constructor -// can't be a private member function for some reason. -template -static constexpr std::integral_constant __check_compatible_extents( - std::integral_constant, - std::integer_sequence, - std::integer_sequence) noexcept { - return {}; -} - -// This helper prevents ICE's on MSVC. -template -struct __compare_extent_compatible : std::integral_constant -{}; - -template -static constexpr std::integral_constant< - bool, _MDSPAN_FOLD_AND(__compare_extent_compatible::value)> -__check_compatible_extents( - std::integral_constant, - std::integer_sequence, - std::integer_sequence) noexcept { - return {}; -} - -template -MDSPAN_INLINE_FUNCTION -static constexpr bool are_valid_indices() { - return - _MDSPAN_FOLD_AND(std::is_convertible::value) && - _MDSPAN_FOLD_AND(std::is_nothrow_constructible::value); -} - -// ------------------------------------------------------------------ -// ------------ static_array ---------------------------------------- -// ------------------------------------------------------------------ - -// array like class which provides an array of static values with get -// function and operator []. - -// Implementation of Static Array with recursive implementation of get. -template struct static_array_impl; - -template -struct static_array_impl { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t r) { - if (r == R) - return FirstExt; - else - return static_array_impl::get(r); - } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { -#if MDSPAN_HAS_CXX_17 - if constexpr (r == R) - return FirstExt; - else - return static_array_impl::template get(); -#else - get(r); -#endif - } -}; - -// End the recursion -template -struct static_array_impl { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t) { return FirstExt; } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { - return FirstExt; - } -}; - -// Don't start recursion if size 0 -template struct static_array_impl<0, T> { - MDSPAN_INLINE_FUNCTION - constexpr static T get(size_t) { return T(); } - template MDSPAN_INLINE_FUNCTION constexpr static T get() { - return T(); - } -}; - -// Static array, provides get(), get(r) and operator[r] -template struct static_array: - public static_array_impl<0, T, Values...> { - -public: - using value_type = T; - - MDSPAN_INLINE_FUNCTION - constexpr static size_t size() { return sizeof...(Values); } -}; - - -// ------------------------------------------------------------------ -// ------------ index_sequence_scan --------------------------------- -// ------------------------------------------------------------------ - -// index_sequence_scan takes compile time values and provides get(r) -// and get() which return the sum of the first r-1 values. - -// Recursive implementation for get -template struct index_sequence_scan_impl; - -template -struct index_sequence_scan_impl { - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { - if (r > R) - return FirstVal + index_sequence_scan_impl::get(r); - else - return 0; - } -}; - -template -struct index_sequence_scan_impl { -#if defined(__NVCC__) || defined(__NVCOMPILER) || \ - defined(_MDSPAN_COMPILER_INTEL) - // NVCC warns about pointless comparison with 0 for R==0 and r being const - // evaluatable and also 0. - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { - return static_cast(R) > static_cast(r) ? FirstVal : 0; - } -#else - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t r) { return R > r ? FirstVal : 0; } -#endif -}; -template <> struct index_sequence_scan_impl<0> { - MDSPAN_INLINE_FUNCTION - constexpr static size_t get(size_t) { return 0; } -}; - -// ------------------------------------------------------------------ -// ------------ possibly_empty_array ------------------------------- -// ------------------------------------------------------------------ - -// array like class which provides get function and operator [], and -// has a specialization for the size 0 case. -// This is needed to make the maybe_static_array be truly empty, for -// all static values. - -template struct possibly_empty_array { - T vals[N]{}; - MDSPAN_INLINE_FUNCTION - constexpr T &operator[](size_t r) { return vals[r]; } - MDSPAN_INLINE_FUNCTION - constexpr const T &operator[](size_t r) const { return vals[r]; } -}; - -template struct possibly_empty_array { - MDSPAN_INLINE_FUNCTION - constexpr T operator[](size_t) { return T(); } - MDSPAN_INLINE_FUNCTION - constexpr const T operator[](size_t) const { return T(); } -}; - -// ------------------------------------------------------------------ -// ------------ maybe_static_array ---------------------------------- -// ------------------------------------------------------------------ - -// array like class which has a mix of static and runtime values but -// only stores the runtime values. -// The type of the static and the runtime values can be different. -// The position of a dynamic value is indicated through a tag value. -template -struct maybe_static_array { - - static_assert(std::is_convertible::value, "maybe_static_array: TStatic must be convertible to TDynamic"); - static_assert(std::is_convertible::value, "maybe_static_array: TDynamic must be convertible to TStatic"); - -private: - // Static values member - using static_vals_t = static_array; - constexpr static size_t m_size = sizeof...(Values); - constexpr static size_t m_size_dynamic = - _MDSPAN_FOLD_PLUS_RIGHT((Values == dyn_tag), 0); - - // Dynamic values member - _MDSPAN_NO_UNIQUE_ADDRESS possibly_empty_array - m_dyn_vals; - - // static mapping of indices to the position in the dynamic values array - using dyn_map_t = index_sequence_scan_impl<0, static_cast(Values == dyn_tag)...>; -public: - - // two types for static and dynamic values - using value_type = TDynamic; - using static_value_type = TStatic; - // tag value indicating dynamic value - constexpr static static_value_type tag_value = dyn_tag; - - constexpr maybe_static_array() = default; - - // constructor for all static values - // TODO: add precondition check? - MDSPAN_TEMPLATE_REQUIRES(class... Vals, - /* requires */ ((m_size_dynamic == 0) && - (sizeof...(Vals) > 0))) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(Vals...) : m_dyn_vals{} {} - - // constructors from dynamic values only - MDSPAN_TEMPLATE_REQUIRES(class... DynVals, - /* requires */ (sizeof...(DynVals) == - m_size_dynamic && - m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(DynVals... vals) - : m_dyn_vals{static_cast(vals)...} {} - - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &vals) { - for (size_t r = 0; r < N; r++) - m_dyn_vals[r] = static_cast(vals[r]); - } - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N == 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &) : m_dyn_vals{} {} - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &vals) { - for (size_t r = 0; r < N; r++) - m_dyn_vals[r] = static_cast(vals[r]); - } - - MDSPAN_TEMPLATE_REQUIRES(class T, size_t N, - /* requires */ (N == m_size_dynamic && N == 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &) : m_dyn_vals{} {} -#endif - - // constructors from all values - MDSPAN_TEMPLATE_REQUIRES(class... DynVals, - /* requires */ (sizeof...(DynVals) != - m_size_dynamic && - m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(DynVals... vals) - : m_dyn_vals{} { - static_assert((sizeof...(DynVals) == m_size), "Invalid number of values."); - TDynamic values[m_size]{static_cast(vals)...}; - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = values[r]; - } -// Precondition check -#ifdef _MDSPAN_DEBUG - else { - assert(values[r] == static_cast(static_val)); - } -#endif - } - } - - MDSPAN_TEMPLATE_REQUIRES( - class T, size_t N, - /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::array &vals) { - static_assert((N == m_size), "Invalid number of values."); -// Precondition check -#ifdef _MDSPAN_DEBUG - assert(N == m_size); -#endif - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); - } -// Precondition check -#ifdef _MDSPAN_DEBUG - else { - assert(static_cast(vals[r]) == - static_cast(static_val)); - } -#endif - } - } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class T, size_t N, - /* requires */ (N != m_size_dynamic && m_size_dynamic > 0)) - MDSPAN_INLINE_FUNCTION - constexpr maybe_static_array(const std::span &vals) { - static_assert((N == m_size) || (m_size == dynamic_extent)); -#ifdef _MDSPAN_DEBUG - assert(N == m_size); -#endif - for (size_t r = 0; r < m_size; r++) { - TStatic static_val = static_vals_t::get(r); - if (static_val == dyn_tag) { - m_dyn_vals[dyn_map_t::get(r)] = static_cast(vals[r]); - } -#ifdef _MDSPAN_DEBUG - else { - assert(static_cast(vals[r]) == - static_cast(static_val)); - } -#endif - } - } -#endif - - // access functions - MDSPAN_INLINE_FUNCTION - constexpr static TStatic static_value(size_t r) { return static_vals_t::get(r); } - - MDSPAN_INLINE_FUNCTION - constexpr TDynamic value(size_t r) const { - TStatic static_val = static_vals_t::get(r); - return static_val == dyn_tag ? m_dyn_vals[dyn_map_t::get(r)] - : static_cast(static_val); - } - MDSPAN_INLINE_FUNCTION - constexpr TDynamic operator[](size_t r) const { return value(r); } - - - // observers - MDSPAN_INLINE_FUNCTION - constexpr static size_t size() { return m_size; } - MDSPAN_INLINE_FUNCTION - constexpr static size_t size_dynamic() { return m_size_dynamic; } -}; - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -// ------------------------------------------------------------------ -// ------------ extents --------------------------------------------- -// ------------------------------------------------------------------ - -// Class to describe the extents of a multi dimensional array. -// Used by mdspan, mdarray and layout mappings. -// See ISO C++ standard [mdspan.extents] - -template class extents { -public: - // typedefs for integral types used - using index_type = IndexType; - using size_type = std::make_unsigned_t; - using rank_type = size_t; - - static_assert(std::is_integral::value && !std::is_same::value, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents::index_type must be a signed or unsigned integer type"); -private: - constexpr static rank_type m_rank = sizeof...(Extents); - constexpr static rank_type m_rank_dynamic = - _MDSPAN_FOLD_PLUS_RIGHT((Extents == dynamic_extent), /* + ... + */ 0); - - // internal storage type using maybe_static_array - using vals_t = - detail::maybe_static_array; - _MDSPAN_NO_UNIQUE_ADDRESS vals_t m_vals; - -public: - // [mdspan.extents.obs], observers of multidimensional index space - MDSPAN_INLINE_FUNCTION - constexpr static rank_type rank() noexcept { return m_rank; } - MDSPAN_INLINE_FUNCTION - constexpr static rank_type rank_dynamic() noexcept { return m_rank_dynamic; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type extent(rank_type r) const noexcept { return m_vals.value(r); } - MDSPAN_INLINE_FUNCTION - constexpr static size_t static_extent(rank_type r) noexcept { - return vals_t::static_value(r); - } - - // [mdspan.extents.cons], constructors - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr extents() noexcept = default; - - // Construction from just dynamic or all values. - // Precondition check is deferred to maybe_static_array constructor - MDSPAN_TEMPLATE_REQUIRES( - class... OtherIndexTypes, - /* requires */ ( - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, OtherIndexTypes, - index_type) /* && ... */) && - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, - OtherIndexTypes) /* && ... */) && - (sizeof...(OtherIndexTypes) == m_rank || - sizeof...(OtherIndexTypes) == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - constexpr explicit extents(OtherIndexTypes... dynvals) noexcept - : m_vals(static_cast(dynvals)...) {} - - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t N, - /* requires */ - ( - _MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, - const OtherIndexType&) && - (N == m_rank || N == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) - constexpr extents(const std::array &exts) noexcept - : m_vals(std::move(exts)) {} - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t N, - /* requires */ - (_MDSPAN_TRAIT(std::is_convertible, const OtherIndexType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const OtherIndexType&) && - (N == m_rank || N == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT(N != m_rank_dynamic) - constexpr extents(const std::span &exts) noexcept - : m_vals(std::move(exts)) {} -#endif - -private: - // Function to construct extents storage from other extents. - // With C++ 17 the first two variants could be collapsed using if constexpr - // in which case you don't need all the requires clauses. - // in C++ 14 mode that doesn't work due to infinite recursion - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R < m_rank) && (static_extent(R) == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &exts, - DynamicValues... dynamic_values) noexcept { - return __construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), exts, dynamic_values..., - exts.extent(R)); - } - - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R < m_rank) && (static_extent(R) != dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &exts, - DynamicValues... dynamic_values) noexcept { - return __construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), exts, dynamic_values...); - } - - MDSPAN_TEMPLATE_REQUIRES( - size_t DynCount, size_t R, class OtherExtents, class... DynamicValues, - /* requires */ ((R == m_rank) && (DynCount == m_rank_dynamic))) - MDSPAN_INLINE_FUNCTION - constexpr - vals_t __construct_vals_from_extents(std::integral_constant, - std::integral_constant, - const OtherExtents &, - DynamicValues... dynamic_values) noexcept { - return vals_t{static_cast(dynamic_values)...}; - } - -public: - - // Converting constructor from other extents specializations - MDSPAN_TEMPLATE_REQUIRES( - class OtherIndexType, size_t... OtherExtents, - /* requires */ - ( - /* multi-stage check to protect from invalid pack expansion when sizes - don't match? */ - decltype(detail::__check_compatible_extents( - // using: sizeof...(Extents) == sizeof...(OtherExtents) as the second argument fails with MSVC+NVCC with some obscure expansion error - // MSVC: 19.38.33133 NVCC: 12.0 - std::integral_constant::rank() == extents::rank()>{}, - std::integer_sequence{}, - std::integer_sequence{}))::value - ) - ) - MDSPAN_INLINE_FUNCTION - MDSPAN_CONDITIONAL_EXPLICIT((((Extents != dynamic_extent) && - (OtherExtents == dynamic_extent)) || - ...) || - (std::numeric_limits::max() < - std::numeric_limits::max())) - constexpr extents(const extents &other) noexcept - : m_vals(__construct_vals_from_extents( - std::integral_constant(), - std::integral_constant(), other)) {} - - // Comparison operator - template - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const extents &lhs, - const extents &rhs) noexcept { - return - rank() == extents::rank() && - detail::rankwise_equal(detail::with_rank{}, rhs, lhs, detail::extent); - } - -#if !(MDSPAN_HAS_CXX_20) - template - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(extents const &lhs, - extents const &rhs) noexcept { - return !(lhs == rhs); - } -#endif -}; - -// Recursive helper classes to implement dextents alias for extents -namespace detail { - -template > -struct __make_dextents; - -template -struct __make_dextents< - IndexType, Rank, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> -{ - using type = typename __make_dextents< - IndexType, Rank - 1, - ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents>::type; -}; - -template -struct __make_dextents< - IndexType, 0, ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> -{ - using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::extents; -}; - -} // end namespace detail - -// [mdspan.extents.dextents], alias template -template -using dextents = typename detail::__make_dextents::type; - -// Deduction guide for extents -#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) -template -extents(IndexTypes...) - -> extents; -#endif - -// Helper type traits for identifying a class as extents. -namespace detail { - -template struct __is_extents : ::std::false_type {}; - -template -struct __is_extents<::MDSPAN_IMPL_STANDARD_NAMESPACE::extents> - : ::std::true_type {}; - -template -#if MDSPAN_HAS_CXX_17 -inline -#else -static -#endif -constexpr bool __is_extents_v = __is_extents::value; - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_lower_bound(InputIndexType user_index, - ExtentsIndexType /* current_extent */, - std::true_type /* is_signed */) -{ - (void) user_index; // prevent unused variable warning -#ifdef _MDSPAN_DEBUG - assert(static_cast(user_index) >= 0); -#endif -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_lower_bound(InputIndexType /* user_index */, - ExtentsIndexType /* current_extent */, - std::false_type /* is_signed */) -{} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_upper_bound(InputIndexType user_index, - ExtentsIndexType current_extent) -{ - (void) user_index; // prevent unused variable warnings - (void) current_extent; -#ifdef _MDSPAN_DEBUG - assert(static_cast(user_index) < current_extent); -#endif -} - -// Returning true to use AND fold instead of comma -// CPP14 mode doesn't like the use of void expressions -// with the way the _MDSPAN_FOLD_AND is set up -template -MDSPAN_INLINE_FUNCTION -constexpr bool -check_one_index(InputIndex user_index, - ExtentsIndexType current_extent) -{ - check_lower_bound(user_index, current_extent, - std::integral_constant::value>{}); - check_upper_bound(user_index, current_extent); - return true; -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_all_indices_helper(std::index_sequence, - const extents& exts, - Indices... indices) -{ - // Suppress warning about statement has no effect - (void) _MDSPAN_FOLD_AND( - (check_one_index(indices, exts.extent(RankIndices))) - ); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr void -check_all_indices(const extents& exts, - Indices... indices) -{ - check_all_indices_helper(std::make_index_sequence(), - exts, indices...); -} - -} // namespace detail -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/extents.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -//============================================================================== - -template -struct __no_unique_address_emulation { - using __stored_type = _T; - _T __v; - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { - return __v; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { - return __v; - } -}; - -// Empty case -// This doesn't work if _T is final, of course, but we're not using anything -// like that currently. That kind of thing could be added pretty easily though -template -struct __no_unique_address_emulation< - _T, _Disambiguator, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T) && - // If the type isn't trivially destructible, its destructor - // won't be called at the right time, so don't use this - // specialization - _MDSPAN_TRAIT(std::is_trivially_destructible, _T)>> : -#ifdef _MDSPAN_COMPILER_MSVC - // MSVC doesn't allow you to access public static member functions of a type - // when you *happen* to privately inherit from that type. - protected -#else - // But we still want this to be private if possible so that we don't accidentally - // access members of _T directly rather than calling __ref() first, which wouldn't - // work if _T happens to be stateful and thus we're using the unspecialized definition - // of __no_unique_address_emulation above. - private -#endif - _T { - using __stored_type = _T; - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T const &__ref() const noexcept { - return *static_cast<_T const *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T &__ref() noexcept { - return *static_cast<_T *>(this); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation( - __no_unique_address_emulation const &) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __no_unique_address_emulation( - __no_unique_address_emulation &&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & - operator=(__no_unique_address_emulation const &) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __no_unique_address_emulation & - operator=(__no_unique_address_emulation &&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__no_unique_address_emulation() noexcept = default; - - // Explicitly make this not a reference so that the copy or move - // constructor still gets called. - MDSPAN_INLINE_FUNCTION - explicit constexpr __no_unique_address_emulation(_T const& __v) noexcept : _T(__v) {} - MDSPAN_INLINE_FUNCTION - explicit constexpr __no_unique_address_emulation(_T&& __v) noexcept : _T(::std::move(__v)) {} -}; - -//============================================================================== - -} // end namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/no_unique_address.hpp -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// For no unique address emulation, this is the case taken when neither are empty. -// For real `[[no_unique_address]]`, this case is always taken. -template struct __compressed_pair { - _MDSPAN_NO_UNIQUE_ADDRESS _T1 __t1_val{}; - _MDSPAN_NO_UNIQUE_ADDRESS _T2 __t2_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return __t1_val; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return __t2_val; - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : __t1_val((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} -}; - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - -// First empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && !_MDSPAN_TRAIT(std::is_empty, _T2)>> - : private _T1 { - _T2 __t2_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { - return *static_cast<_T1 *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return *static_cast<_T1 const *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { return __t2_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return __t2_val; - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : _T1((_T1Like &&) __t1), __t2_val((_T2Like &&) __t2) {} -}; - -// Second empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t> - : private _T2 { - _T1 __t1_val{}; - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { return __t1_val; } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return __t1_val; - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { - return *static_cast<_T2 *>(this); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return *static_cast<_T2 const *>(this); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) - : _T2((_T2Like &&) __t2), __t1_val((_T1Like &&) __t1) {} -}; - -// Both empty. -template -struct __compressed_pair< - _T1, _T2, - std::enable_if_t<_MDSPAN_TRAIT(std::is_empty, _T1) && _MDSPAN_TRAIT(std::is_empty, _T2)>> - // We need to use the __no_unique_address_emulation wrapper here to avoid - // base class ambiguities. -#ifdef _MDSPAN_COMPILER_MSVC -// MSVC doesn't allow you to access public static member functions of a type -// when you *happen* to privately inherit from that type. - : protected __no_unique_address_emulation<_T1, 0>, - protected __no_unique_address_emulation<_T2, 1> -#else - : private __no_unique_address_emulation<_T1, 0>, - private __no_unique_address_emulation<_T2, 1> -#endif -{ - using __first_base_t = __no_unique_address_emulation<_T1, 0>; - using __second_base_t = __no_unique_address_emulation<_T2, 1>; - - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T1 &__first() noexcept { - return this->__first_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T1 const &__first() const noexcept { - return this->__first_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 _T2 &__second() noexcept { - return this->__second_base_t::__ref(); - } - MDSPAN_FORCE_INLINE_FUNCTION constexpr _T2 const &__second() const noexcept { - return this->__second_base_t::__ref(); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair() = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr __compressed_pair(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair const &) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - _MDSPAN_CONSTEXPR_14_DEFAULTED __compressed_pair & - operator=(__compressed_pair &&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~__compressed_pair() = default; - template - MDSPAN_INLINE_FUNCTION constexpr __compressed_pair(_T1Like &&__t1, _T2Like &&__t2) noexcept - : __first_base_t(_T1((_T1Like &&) __t1)), - __second_base_t(_T2((_T2Like &&) __t2)) - { } -}; - -#endif // !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - -} // end namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/compressed_pair.hpp - -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) -#endif - -#include -#include -#include - -#ifdef __cpp_lib_span -#include -#endif -#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 && defined(__cpp_lib_concepts) -# include -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -struct layout_left { - template - class mapping; -}; -struct layout_right { - template - class mapping; -}; - -namespace detail { - template - constexpr bool __is_mapping_of = - std::is_same, Mapping>::value; - -#if defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20 -# if !defined(__cpp_lib_concepts) - namespace internal { - namespace detail { - template - concept __same_as = std::is_same_v<_Tp, _Up>; - } // namespace detail - template - concept __same_as = detail::__same_as && detail::__same_as; - } // namespace internal -# endif - - template - concept __layout_mapping_alike = requires { - requires __is_extents::value; -#if defined(__cpp_lib_concepts) - { M::is_always_strided() } -> std::same_as; - { M::is_always_exhaustive() } -> std::same_as; - { M::is_always_unique() } -> std::same_as; -#else - { M::is_always_strided() } -> internal::__same_as; - { M::is_always_exhaustive() } -> internal::__same_as; - { M::is_always_unique() } -> internal::__same_as; -#endif - std::bool_constant::value; - std::bool_constant::value; - std::bool_constant::value; - }; -#endif - -} // namespace detail - -struct layout_stride { - template - class mapping -#if !defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : private detail::__no_unique_address_emulation< - detail::__compressed_pair< - Extents, - detail::possibly_empty_array - > - > -#endif - { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_stride; - - // This could be a `requires`, but I think it's better and clearer as a `static_assert`. - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_stride::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - - private: - - //---------------------------------------------------------------------------- - - using __strides_storage_t = detail::possibly_empty_array; - using __member_pair_t = detail::__compressed_pair; - -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - _MDSPAN_NO_UNIQUE_ADDRESS __member_pair_t __members; -#else - using __base_t = detail::__no_unique_address_emulation<__member_pair_t>; -#endif - - MDSPAN_FORCE_INLINE_FUNCTION constexpr __strides_storage_t const& - __strides_storage() const noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__second(); -#else - return this->__base_t::__ref().__second(); -#endif - } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 __strides_storage_t& - __strides_storage() noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__second(); -#else - return this->__base_t::__ref().__second(); -#endif - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __get_size(::MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT( static_cast(extents().extent(Idx)), 1 ); - } - - //---------------------------------------------------------------------------- - - template - friend class mapping; - - //---------------------------------------------------------------------------- - - // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level - template - struct __deduction_workaround; - - template - struct __deduction_workaround> - { - template - MDSPAN_INLINE_FUNCTION - static constexpr bool _eq_impl(mapping const& self, mapping const& other) noexcept { - using common_t = std::common_type_t; - return _MDSPAN_FOLD_AND((static_cast(self.stride(Idxs)) == static_cast(other.stride(Idxs))) /* && ... */) - && _MDSPAN_FOLD_AND((static_cast(self.extents().extent(Idxs)) == static_cast(other.extents().extent(Idxs))) /* || ... */); - } - template - MDSPAN_INLINE_FUNCTION - static constexpr bool _not_eq_impl(mapping const& self, mapping const& other) noexcept { - using common_t = std::common_type_t; - return _MDSPAN_FOLD_OR((static_cast(self.stride(Idxs)) != static_cast(other.stride(Idxs))) /* || ... */) - || _MDSPAN_FOLD_OR((static_cast(self.extents().extent(Idxs)) != static_cast(other.extents().extent(Idxs))) /* || ... */); - } - - template - MDSPAN_FORCE_INLINE_FUNCTION - static constexpr size_t _call_op_impl(mapping const& self, Integral... idxs) noexcept { - return _MDSPAN_FOLD_PLUS_RIGHT((idxs * self.stride(Idxs)), /* + ... + */ 0); - } - - MDSPAN_INLINE_FUNCTION - static constexpr size_t _req_span_size_impl(mapping const& self) noexcept { - // assumes no negative strides; not sure if I'm allowed to assume that or not - return __impl::_call_op_impl(self, (self.extents().template __extent() - 1)...) + 1; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const OtherMapping& map) { - return __strides_storage_t{static_cast(map.stride(Idxs))...}; - } - - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t& fill_strides(const __strides_storage_t& s) { - return s; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const std::array& s) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(mdspan_non_standard_tag, const IntegralType (&s)[extents_type::rank()]) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } - -#ifdef __cpp_lib_span - template - MDSPAN_INLINE_FUNCTION - static constexpr const __strides_storage_t fill_strides(const std::span& s) { - return __strides_storage_t{static_cast(s[Idxs])...}; - } -#endif - - MDSPAN_INLINE_FUNCTION - static constexpr std::array return_strides(const __strides_storage_t& s) { - return std::array{s[Idxs]...}; - } - - template - MDSPAN_INLINE_FUNCTION - static constexpr size_t __return_zero() { return 0; } - - template - MDSPAN_INLINE_FUNCTION - static constexpr typename Mapping::index_type - __OFFSET(const Mapping& m) { return m(__return_zero()...); } - }; - - // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. - using __impl = __deduction_workaround>; - - static constexpr __strides_storage_t strides_storage(detail::with_rank<0>) { - return {}; - } - template - static constexpr __strides_storage_t strides_storage(detail::with_rank) { - __strides_storage_t s{}; - - extents_type e; - index_type stride = 1; - for(int r = static_cast(extents_type::rank() - 1); r >= 0; r--) { - s[r] = stride; - stride *= e.extent(r); - } - - return s; - } - - //---------------------------------------------------------------------------- - -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - MDSPAN_INLINE_FUNCTION constexpr explicit - mapping(__member_pair_t&& __m) : __members(::std::move(__m)) {} -#else - MDSPAN_INLINE_FUNCTION constexpr explicit - mapping(__base_t&& __b) : __base_t(::std::move(__b)) {} -#endif - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - extents_type(), - __strides_storage_t(strides_storage(detail::with_rank{})) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - {} - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - extents_type const& e, - std::array const& s - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - mdspan_non_standard_tag, - extents_type const& e, - IntegralTypes (&s)[extents_type::rank()] - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(mdspan_non_standard, s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class IntegralTypes, - /* requires */ ( - // MSVC 19.32 does not like using index_type here, requires the typename Extents::index_type - // error C2641: cannot deduce template arguments for 'MDSPAN_IMPL_STANDARD_NAMESPACE::layout_stride::mapping' - _MDSPAN_TRAIT(std::is_convertible, const std::remove_const_t&, typename Extents::index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, typename Extents::index_type, const std::remove_const_t&) - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr - mapping( - extents_type const& e, - std::span const& s - ) noexcept -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - e, __strides_storage_t(__impl::fill_strides(s)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - s[i] > 0 is true for all i in the range [0, rank_ ). - * - REQUIRED-SPAN-SIZE(e, s) is a representable value of type index_type ([basic.fundamental]). - * - If rank_ is greater than 0, then there exists a permutation P of the integers in the - * range [0, rank_), such that s[ pi ] >= s[ pi − 1 ] * e.extent( pi − 1 ) is true for - * all i in the range [1, rank_ ), where pi is the ith element of P. - */ - } -#endif // __cpp_lib_span - -#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && - detail::__is_mapping_of && - StridedLayoutMapping::is_always_unique() && - StridedLayoutMapping::is_always_strided() - ) - ) -#else - template - requires( - detail::__layout_mapping_alike && - _MDSPAN_TRAIT(std::is_constructible, extents_type, typename StridedLayoutMapping::extents_type) && - StridedLayoutMapping::is_always_unique() && - StridedLayoutMapping::is_always_strided() - ) -#endif - MDSPAN_CONDITIONAL_EXPLICIT( - !(std::is_convertible::value && - (detail::__is_mapping_of || - detail::__is_mapping_of || - detail::__is_mapping_of)) - ) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(StridedLayoutMapping const& other) noexcept // NOLINT(google-explicit-constructor) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - : __members{ -#else - : __base_t(__base_t{__member_pair_t( -#endif - other.extents(), __strides_storage_t(__impl::fill_strides(other)) -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - } -#else - )}) -#endif - { - /* - * TODO: check preconditions - * - other.stride(i) > 0 is true for all i in the range [0, rank_ ). - * - other.required_span_size() is a representable value of type index_type ([basic.fundamental]). - * - OFFSET(other) == 0 - */ - } - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED - mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { -#if defined(_MDSPAN_USE_ATTRIBUTE_NO_UNIQUE_ADDRESS) - return __members.__first(); -#else - return this->__base_t::__ref().__first(); -#endif - }; - - MDSPAN_INLINE_FUNCTION - constexpr std::array< index_type, extents_type::rank() > strides() const noexcept { - return __impl::return_strides(__strides_storage()); - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type span_size = 1; - for(unsigned r = 0; r < extents_type::rank(); r++) { - // Return early if any of the extents are zero - if(extents().extent(r)==0) return 0; - span_size += ( static_cast(extents().extent(r) - 1 ) * __strides_storage()[r]); - } - return span_size; - } - - - MDSPAN_TEMPLATE_REQUIRES( - class... Indices, - /* requires */ ( - sizeof...(Indices) == Extents::rank() && - (detail::are_valid_indices()) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return static_cast(__impl::_call_op_impl(*this, static_cast(idxs)...)); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return false; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - - private: - constexpr bool exhaustive_for_nonzero_span_size() const - { - return required_span_size() == __get_size(extents(), std::make_index_sequence()); - } - - constexpr bool is_exhaustive_impl(detail::with_rank<0>) const - { - return true; - } - constexpr bool is_exhaustive_impl(detail::with_rank<1>) const - { - if (required_span_size() != static_cast(0)) { - return exhaustive_for_nonzero_span_size(); - } - return stride(0) == 1; - } - template - constexpr bool is_exhaustive_impl(detail::with_rank) const - { - if (required_span_size() != static_cast(0)) { - return exhaustive_for_nonzero_span_size(); - } - - rank_type r_largest = 0; - for (rank_type r = 1; r < extents_type::rank(); r++) { - if (stride(r) > stride(r_largest)) { - r_largest = r; - } - } - for (rank_type r = 0; r < extents_type::rank(); r++) { - if (extents().extent(r) == 0 && r != r_largest) { - return false; - } - } - return true; - } - - public: - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 bool is_exhaustive() const noexcept { - return is_exhaustive_impl(detail::with_rank{}); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type r) const noexcept { - return __strides_storage()[r]; - } - -#if !(defined(_MDSPAN_USE_CONCEPTS) && MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - detail::__is_mapping_of && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) - ) -#else - template - requires( - detail::__layout_mapping_alike && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) -#endif - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(const mapping& x, const StridedLayoutMapping& y) noexcept { - return (x.extents() == y.extents()) && - (__impl::__OFFSET(y) == static_cast(0)) && - detail::rankwise_equal(detail::with_rank{}, x, y, detail::stride); - } - - // This one is not technically part of the proposal. Just here to make implementation a bit more optimal hopefully - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - (extents_type::rank() == OtherExtents::rank()) - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { - return __impl::_eq_impl(lhs, rhs); - } - -#if !MDSPAN_HAS_CXX_20 - MDSPAN_TEMPLATE_REQUIRES( - class StridedLayoutMapping, - /* requires */ ( - detail::__is_mapping_of && - (extents_type::rank() == StridedLayoutMapping::extents_type::rank()) && - StridedLayoutMapping::is_always_strided() - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(const mapping& x, const StridedLayoutMapping& y) noexcept { - return not (x == y); - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - (extents_type::rank() == OtherExtents::rank()) - ) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return __impl::_not_eq_impl(lhs, rhs); - } -#endif - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } - }; -}; - -namespace detail { - -template -constexpr void validate_strides(with_rank<0>, Layout, const Extents&, const Mapping&) -{} - -template -constexpr void validate_strides(with_rank, Layout, const Extents& ext, const Mapping& other) -{ - static_assert(std::is_same::value and - (std::is_same::value or - std::is_same::value) - , "This function is only intended to validate construction of " - "a layout_left or layout_right mapping from a layout_stride mapping."); - - constexpr auto is_left = std::is_same::value; - - typename Extents::index_type stride = 1; - - for (std::size_t r = 0; r < N; r++) { - const std::size_t s = is_left ? r : N - 1 - r; - - MDSPAN_IMPL_PRECONDITION(common_integral_compare(stride, other.stride(s)) - and "invalid strides for layout_{left,right}"); - - stride *= ext.extent(s); - } -} - -} // namespace detail -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_stride.hpp -#if MDSPAN_HAS_CXX_17 -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -template -struct layout_left_padded { - template - class mapping; -}; - -template -struct layout_right_padded { - template - class mapping; -}; - -namespace detail { -// The layout_padded_constants structs are only useful if rank > 1, otherwise they may wrap -template -struct layout_padded_constants; - -template -struct layout_padded_constants, _ExtentsType> -{ - using rank_type = typename _ExtentsType::rank_type; - static constexpr rank_type padded_stride_idx = 1; - static constexpr rank_type extent_to_pad_idx = 0; -}; - -template -struct layout_padded_constants, _ExtentsType> -{ - using rank_type = typename _ExtentsType::rank_type; - static constexpr rank_type padded_stride_idx = _ExtentsType::rank() - 2; - static constexpr rank_type extent_to_pad_idx = _ExtentsType::rank() - 1; -}; - -template -struct is_layout_left_padded : std::false_type {}; - -template -struct is_layout_left_padded> : std::true_type {}; - -template -struct is_layout_left_padded_mapping : std::false_type {}; - -template -struct is_layout_left_padded_mapping<_Mapping, - std::enable_if_t::template mapping>::value>> - : std::true_type {}; - -template -struct is_layout_right_padded : std::false_type {}; - -template -struct is_layout_right_padded> : std::true_type {}; - -template -struct is_layout_right_padded_mapping : std::false_type {}; - -template -struct is_layout_right_padded_mapping<_Mapping, - std::enable_if_t::template mapping>::value>> - : std::true_type {}; - - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>) {} - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>) {} - -template -constexpr void check_padded_layout_converting_constructor_mandates(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank) -{ - using extents_type = typename _PaddedLayoutMappingType::extents_type; - constexpr auto padding_value = _PaddedLayoutMappingType::padding_value; - constexpr auto idx = layout_padded_constants::extent_to_pad_idx; - - constexpr auto statically_determinable = - (_LayoutExtentsType::static_extent(idx) != dynamic_extent) && - (extents_type::static_extent(idx) != dynamic_extent) && - (padding_value != dynamic_extent); - - static_assert(not statically_determinable or - (padding_value == 0 - ? _LayoutExtentsType::static_extent(idx) == 0 - : _LayoutExtentsType::static_extent(idx) % padding_value == 0), - ""); -} - -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<0>, - const _OtherMapping&) {} -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank<1>, - const _OtherMapping&) {} -template -constexpr void check_padded_layout_converting_constructor_preconditions(MDSPAN_IMPL_STANDARD_NAMESPACE::detail::with_rank, - const _OtherMapping &other_mapping) { - constexpr auto padded_stride_idx = - layout_padded_constants::padded_stride_idx; - constexpr auto extent_to_pad_idx = layout_padded_constants::extent_to_pad_idx; - MDSPAN_IMPL_PRECONDITION(other_mapping.stride(padded_stride_idx) == other_mapping.extents().extent(extent_to_pad_idx)); -} - - -} -} -} -//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded_fwd.hpp -#endif - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -//============================================================================== -template -class layout_right::mapping { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_right; - private: - - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_right::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - template - friend class mapping; - - // i0+(i1 + E(1)*(i2 + E(2)*i3)) - template - struct __rank_count {}; - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - index_type offset, __rank_count, const I& i, Indices... idx) const { - return __compute_offset(offset * __extents.extent(r) + i,__rank_count(), idx...); - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count<0,extents_type::rank()>, const I& i, Indices... idx) const { - return __compute_offset(i,__rank_count<1,extents_type::rank()>(),idx...); - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(size_t offset, __rank_count) const { - return static_cast(offset); - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - _MDSPAN_HOST_DEVICE - constexpr mapping(extents_type const& __exts) noexcept - :__extents(__exts) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && - (extents_type::rank() <= 1) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_left::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if _Mapping is a layout_right_padded mapping and - * extents_type is constructible from _Mapping::extents_type. - * - * \note There is currently a difference from p2642r2, where this function is specified as taking - * `layout_right_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. - */ -#if MDSPAN_HAS_CXX_17 - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ ( - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_right_padded_mapping<_Mapping>::value - && std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) - mapping(const _Mapping &__other) noexcept - : __extents(__other.extents()) - { - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_mandates< - extents_type, _Mapping>(detail::with_rank{}); - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_preconditions< - extents_type>(detail::with_rank{}, __other); - } -#endif - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - detail::validate_strides(detail::with_rank{}, layout_right{}, __extents, other); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION - constexpr const extents_type& extents() const noexcept { - return __extents; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type value = 1; - for(rank_type r=0; r != extents_type::rank(); ++r) value*=__extents.extent(r); - return value; - } - - //-------------------------------------------------------------------------------- - - MDSPAN_TEMPLATE_REQUIRES( - class ... Indices, - /* requires */ ( - (sizeof...(Indices) == extents_type::rank()) && - (detail::are_valid_indices()) - ) - ) - _MDSPAN_HOST_DEVICE - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type i) const noexcept -#if MDSPAN_HAS_CXX_20 - requires ( Extents::rank() > 0 ) -#endif - { - index_type value = 1; - for(rank_type r=extents_type::rank()-1; r>i; r--) value*=__extents.extent(r); - return value; - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator==(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() == rhs.extents(); - } - - // In C++ 20 the not equal exists if equal is found -#if !(MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ (Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() != rhs.extents(); - } -#endif - - // Not really public, but currently needed to implement fully constexpr useable submdspan: - template - constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT((Idx>N? __extents.template __extent():1),1); - } - template - constexpr index_type __stride() const noexcept { - return __get_stride(__extents, std::make_index_sequence()); - } - -private: - _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } -}; - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_right.hpp - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -template < - class ElementType, - class Extents, - class LayoutPolicy = layout_right, - class AccessorPolicy = default_accessor -> -class mdspan -{ -private: - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's Extents template parameter must be a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - static_assert(std::is_same::value, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::mdspan's ElementType template parameter must be the same as its AccessorPolicy::element_type."); - - // Workaround for non-deducibility of the index sequence template parameter if it's given at the top level - template - struct __deduction_workaround; - - template - struct __deduction_workaround> - { - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - size_t __size(mdspan const& __self) noexcept { - return _MDSPAN_FOLD_TIMES_RIGHT((__self.__mapping_ref().extents().extent(Idxs)), /* * ... * */ size_t(1)); - } - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - bool __empty(mdspan const& __self) noexcept { - return (__self.rank()>0) && _MDSPAN_FOLD_OR((__self.__mapping_ref().extents().extent(Idxs)==index_type(0))); - } - template - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - ReferenceType __callop(mdspan const& __self, const std::array& indices) noexcept { - return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); - } -#ifdef __cpp_lib_span - template - MDSPAN_FORCE_INLINE_FUNCTION static constexpr - ReferenceType __callop(mdspan const& __self, const std::span& indices) noexcept { - return __self.__accessor_ref().access(__self.__ptr_ref(), __self.__mapping_ref()(indices[Idxs]...)); - } -#endif - }; - -public: - - //-------------------------------------------------------------------------------- - // Domain and codomain types - - using extents_type = Extents; - using layout_type = LayoutPolicy; - using accessor_type = AccessorPolicy; - using mapping_type = typename layout_type::template mapping; - using element_type = ElementType; - using value_type = std::remove_cv_t; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using data_handle_type = typename accessor_type::data_handle_type; - using reference = typename accessor_type::reference; - - MDSPAN_INLINE_FUNCTION static constexpr size_t rank() noexcept { return extents_type::rank(); } - MDSPAN_INLINE_FUNCTION static constexpr size_t rank_dynamic() noexcept { return extents_type::rank_dynamic(); } - MDSPAN_INLINE_FUNCTION static constexpr size_t static_extent(size_t r) noexcept { return extents_type::static_extent(r); } - MDSPAN_INLINE_FUNCTION constexpr index_type extent(size_t r) const noexcept { return __mapping_ref().extents().extent(r); }; - -private: - - // Can't use defaulted parameter in the __deduction_workaround template because of a bug in MSVC warning C4348. - using __impl = __deduction_workaround>; - - using __map_acc_pair_t = detail::__compressed_pair; - -public: - - //-------------------------------------------------------------------------------- - // [mdspan.basic.cons], mdspan constructors, assignment, and destructor - -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() = default; -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan() - requires( - // nvhpc has a bug where using just rank_dynamic() here doesn't work ... - (extents_type::rank_dynamic() > 0) && - _MDSPAN_TRAIT(std::is_default_constructible, data_handle_type) && - _MDSPAN_TRAIT(std::is_default_constructible, mapping_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) = default; -#endif - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(const mdspan&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mdspan(mdspan&&) = default; - - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - ((sizeof...(SizeTypes) == rank()) || (sizeof...(SizeTypes) == rank_dynamic())) && - (detail::are_valid_indices()) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_INLINE_FUNCTION - explicit constexpr mdspan(data_handle_type p, SizeTypes... dynamic_extents) - // TODO @proposal-bug shouldn't I be allowed to do `move(p)` here? - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(static_cast(std::move(dynamic_extents))...)), accessor_type())) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, size_t N, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && - ((N == rank()) || (N == rank_dynamic())) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, const std::array& dynamic_extents) - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(dynamic_extents)), accessor_type())) - { } - -#ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, size_t N, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) && - ((N == rank()) || (N == rank_dynamic())) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, extents_type) && - _MDSPAN_TRAIT(std::is_default_constructible, accessor_type) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT(N != rank_dynamic()) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, std::span dynamic_extents) - : __members(std::move(p), __map_acc_pair_t(mapping_type(extents_type(as_const(dynamic_extents))), accessor_type())) - { } -#endif - - MDSPAN_FUNCTION_REQUIRES( - (MDSPAN_INLINE_FUNCTION constexpr), - mdspan, (data_handle_type p, const extents_type& exts), , - /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type) && - _MDSPAN_TRAIT(std::is_constructible, mapping_type, const extents_type&)) - ) : __members(std::move(p), __map_acc_pair_t(mapping_type(exts), accessor_type())) - { } - - MDSPAN_FUNCTION_REQUIRES( - (MDSPAN_INLINE_FUNCTION constexpr), - mdspan, (data_handle_type p, const mapping_type& m), , - /* requires */ (_MDSPAN_TRAIT(std::is_default_constructible, accessor_type)) - ) : __members(std::move(p), __map_acc_pair_t(m, accessor_type())) - { } - - MDSPAN_INLINE_FUNCTION - constexpr mdspan(data_handle_type p, const mapping_type& m, const accessor_type& a) - : __members(std::move(p), __map_acc_pair_t(m, a)) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherElementType, class OtherExtents, class OtherLayoutPolicy, class OtherAccessor, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, mapping_type, const typename OtherLayoutPolicy::template mapping&) && - _MDSPAN_TRAIT(std::is_constructible, accessor_type, const OtherAccessor&) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT( - !_MDSPAN_TRAIT(std::is_convertible, const typename OtherLayoutPolicy::template mapping&, mapping_type) || - !_MDSPAN_TRAIT(std::is_convertible, const OtherAccessor&, accessor_type) - ) - MDSPAN_INLINE_FUNCTION - constexpr mdspan(const mdspan& other) - : __members(other.__ptr_ref(), __map_acc_pair_t(other.__mapping_ref(), other.__accessor_ref())) - { - static_assert(_MDSPAN_TRAIT(std::is_constructible, data_handle_type, typename OtherAccessor::data_handle_type),"Incompatible data_handle_type for mdspan construction"); - static_assert(_MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents),"Incompatible extents for mdspan construction"); - /* - * TODO: Check precondition - * For each rank index r of extents_type, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r) is true. - */ - } - - /* Might need this on NVIDIA? - MDSPAN_INLINE_FUNCTION_DEFAULTED - ~mdspan() = default; - */ - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(const mdspan&) = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mdspan& operator=(mdspan&&) = default; - - - //-------------------------------------------------------------------------------- - // [mdspan.basic.mapping], mdspan mapping domain multidimensional index to access codomain element - - #if MDSPAN_USE_BRACKET_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, index_type) /* && ... */) && - _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, SizeTypes) /* && ... */) && - (rank() == sizeof...(SizeTypes)) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](SizeTypes... indices) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); - } - #endif - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](const std::array< SizeType, rank()>& indices) const - { - return __impl::template __callop(*this, indices); - } - - #ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](std::span indices) const - { - return __impl::template __callop(*this, indices); - } - #endif // __cpp_lib_span - - #if !MDSPAN_USE_BRACKET_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class Index, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, Index, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, Index) && - extents_type::rank() == 1 - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator[](Index idx) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(idx)))); - } - #endif - - #if MDSPAN_USE_PAREN_OPERATOR - MDSPAN_TEMPLATE_REQUIRES( - class... SizeTypes, - /* requires */ ( - extents_type::rank() == sizeof...(SizeTypes) && - (detail::are_valid_indices()) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(SizeTypes... indices) const - { - return __accessor_ref().access(__ptr_ref(), __mapping_ref()(static_cast(std::move(indices))...)); - } - - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(const std::array& indices) const - { - return __impl::template __callop(*this, indices); - } - - #ifdef __cpp_lib_span - MDSPAN_TEMPLATE_REQUIRES( - class SizeType, - /* requires */ ( - _MDSPAN_TRAIT(std::is_convertible, const SizeType&, index_type) && - _MDSPAN_TRAIT(std::is_nothrow_constructible, index_type, const SizeType&) - ) - ) - MDSPAN_FORCE_INLINE_FUNCTION - constexpr reference operator()(std::span indices) const - { - return __impl::template __callop(*this, indices); - } - #endif // __cpp_lib_span - #endif // MDSPAN_USE_PAREN_OPERATOR - - MDSPAN_INLINE_FUNCTION constexpr size_type size() const noexcept { - return __impl::__size(*this); - }; - - MDSPAN_INLINE_FUNCTION constexpr bool empty() const noexcept { - return __impl::__empty(*this); - }; - - MDSPAN_INLINE_FUNCTION - friend constexpr void swap(mdspan& x, mdspan& y) noexcept { - // can't call the std::swap inside on HIP - #if !defined(_MDSPAN_HAS_HIP) && !defined(_MDSPAN_HAS_CUDA) - using std::swap; - swap(x.__ptr_ref(), y.__ptr_ref()); - swap(x.__mapping_ref(), y.__mapping_ref()); - swap(x.__accessor_ref(), y.__accessor_ref()); - #else - mdspan tmp = y; - y = x; - x = tmp; - #endif - } - - //-------------------------------------------------------------------------------- - // [mdspan.basic.domobs], mdspan observers of the domain multidimensional index space - - - MDSPAN_INLINE_FUNCTION constexpr const extents_type& extents() const noexcept { return __mapping_ref().extents(); }; - MDSPAN_INLINE_FUNCTION constexpr const data_handle_type& data_handle() const noexcept { return __ptr_ref(); }; - MDSPAN_INLINE_FUNCTION constexpr const mapping_type& mapping() const noexcept { return __mapping_ref(); }; - MDSPAN_INLINE_FUNCTION constexpr const accessor_type& accessor() const noexcept { return __accessor_ref(); }; - - //-------------------------------------------------------------------------------- - // [mdspan.basic.obs], mdspan observers of the mapping - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() { return mapping_type::is_always_unique(); }; - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() { return mapping_type::is_always_exhaustive(); }; - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() { return mapping_type::is_always_strided(); }; - - MDSPAN_INLINE_FUNCTION constexpr bool is_unique() const { return __mapping_ref().is_unique(); }; - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const { return __mapping_ref().is_exhaustive(); }; - MDSPAN_INLINE_FUNCTION constexpr bool is_strided() const { return __mapping_ref().is_strided(); }; - MDSPAN_INLINE_FUNCTION constexpr index_type stride(size_t r) const { return __mapping_ref().stride(r); }; - -private: - - detail::__compressed_pair __members{}; - - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 data_handle_type& __ptr_ref() noexcept { return __members.__first(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr data_handle_type const& __ptr_ref() const noexcept { return __members.__first(); } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 mapping_type& __mapping_ref() noexcept { return __members.__second().__first(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr mapping_type const& __mapping_ref() const noexcept { return __members.__second().__first(); } - MDSPAN_FORCE_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 accessor_type& __accessor_ref() noexcept { return __members.__second().__second(); } - MDSPAN_FORCE_INLINE_FUNCTION constexpr accessor_type const& __accessor_ref() const noexcept { return __members.__second().__second(); } - - template - friend class mdspan; - -}; - -#if defined(_MDSPAN_USE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION) -MDSPAN_TEMPLATE_REQUIRES( - class ElementType, class... SizeTypes, - /* requires */ _MDSPAN_FOLD_AND(_MDSPAN_TRAIT(std::is_convertible, SizeTypes, size_t) /* && ... */) && - (sizeof...(SizeTypes) > 0) -) -MDSPAN_DEDUCTION_GUIDE explicit mdspan(ElementType*, SizeTypes...) - -> mdspan>; - -MDSPAN_TEMPLATE_REQUIRES( - class Pointer, - (_MDSPAN_TRAIT(std::is_pointer, std::remove_reference_t)) -) -MDSPAN_DEDUCTION_GUIDE mdspan(Pointer&&) -> mdspan>, extents>; - -MDSPAN_TEMPLATE_REQUIRES( - class CArray, - (_MDSPAN_TRAIT(std::is_array, CArray) && (std::rank_v == 1)) -) -MDSPAN_DEDUCTION_GUIDE mdspan(CArray&) -> mdspan, extents>>; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const ::std::array&) - -> mdspan>; - -#ifdef __cpp_lib_span -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, ::std::span) - -> mdspan>; -#endif - -// This one is necessary because all the constructors take `data_handle_type`s, not -// `ElementType*`s, and `data_handle_type` is taken from `accessor_type::data_handle_type`, which -// seems to throw off automatic deduction guides. -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const extents&) - -> mdspan>; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(ElementType*, const MappingType&) - -> mdspan; - -template -MDSPAN_DEDUCTION_GUIDE mdspan(const typename AccessorType::data_handle_type, const MappingType&, const AccessorType&) - -> mdspan; -#endif - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/mdspan.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#if MDSPAN_HAS_CXX_17 -#endif -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -//============================================================================== - -template -class layout_left::mapping { - public: - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_left; - private: - - static_assert(detail::__is_extents_v, - MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::layout_left::mapping must be instantiated with a specialization of " MDSPAN_IMPL_STANDARD_NAMESPACE_STRING "::extents."); - - template - friend class mapping; - - // i0+(i1 + E(1)*(i2 + E(2)*i3)) - template - struct __rank_count {}; - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count, const I& i, Indices... idx) const { - return __compute_offset(__rank_count(), idx...) * - __extents.extent(r) + i; - } - - template - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset( - __rank_count, const I& i) const { - return i; - } - - _MDSPAN_HOST_DEVICE - constexpr index_type __compute_offset(__rank_count<0,0>) const { return 0; } - - public: - - //-------------------------------------------------------------------------------- - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping() noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(mapping const&) noexcept = default; - - _MDSPAN_HOST_DEVICE - constexpr mapping(extents_type const& __exts) noexcept - :__extents(__exts) - { } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) && - (extents_type::rank() <= 1) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible::value)) // needs two () due to comma - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_right::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - } - -#if MDSPAN_HAS_CXX_17 - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if _Mapping is a layout_left_padded mapping and - * extents_type is constructible from _Mapping::extents_type. - * - * \note There is currently a difference from p2642r2, where this function is specified as taking - * `layout_left_padded< padding_value >::mapping< Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ ( - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail::is_layout_left_padded_mapping<_Mapping>::value - && std::is_constructible_v - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((!std::is_convertible_v)) - mapping(const _Mapping& __other) noexcept - : __extents(__other.extents()) - { - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_mandates< - extents_type, _Mapping>(detail::with_rank{}); - MDSPAN_IMPL_PROPOSED_NAMESPACE::detail:: - check_padded_layout_converting_constructor_preconditions< - extents_type>(detail::with_rank{}, __other); - } -#endif - - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( - _MDSPAN_TRAIT(std::is_constructible, extents_type, OtherExtents) - ) - ) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION _MDSPAN_CONSTEXPR_14 - mapping(layout_stride::mapping const& other) noexcept // NOLINT(google-explicit-constructor) - :__extents(other.extents()) - { - /* - * TODO: check precondition - * other.required_span_size() is a representable value of type index_type - */ - detail::validate_strides(detail::with_rank{}, layout_left{}, __extents, other); - } - - MDSPAN_INLINE_FUNCTION_DEFAULTED _MDSPAN_CONSTEXPR_14_DEFAULTED mapping& operator=(mapping const&) noexcept = default; - - MDSPAN_INLINE_FUNCTION - constexpr const extents_type& extents() const noexcept { - return __extents; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type required_span_size() const noexcept { - index_type value = 1; - for(rank_type r=0; r()) - ) - ) - _MDSPAN_HOST_DEVICE - constexpr index_type operator()(Indices... idxs) const noexcept { -#if ! defined(NDEBUG) - detail::check_all_indices(this->extents(), idxs...); -#endif // ! NDEBUG - return __compute_offset(__rank_count<0, extents_type::rank()>(), static_cast(idxs)...); - } - - - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_exhaustive() noexcept { return true; } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { return true; } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type i) const noexcept -#if MDSPAN_HAS_CXX_20 - requires ( Extents::rank() > 0 ) -#endif - { - index_type value = 1; - for(rank_type r=0; r const& rhs) noexcept { - return lhs.extents() == rhs.extents(); - } - - // In C++ 20 the not equal exists if equal is found -#if !(MDSPAN_HAS_CXX_20) - MDSPAN_TEMPLATE_REQUIRES( - class OtherExtents, - /* requires */ ( Extents::rank() == OtherExtents::rank()) - ) - MDSPAN_INLINE_FUNCTION - friend constexpr bool operator!=(mapping const& lhs, mapping const& rhs) noexcept { - return lhs.extents() != rhs.extents(); - } -#endif - - // Not really public, but currently needed to implement fully constexpr useable submdspan: - template - constexpr index_type __get_stride(MDSPAN_IMPL_STANDARD_NAMESPACE::extents,std::integer_sequence) const { - return _MDSPAN_FOLD_TIMES_RIGHT((Idx():1),1); - } - template - constexpr index_type __stride() const noexcept { - return __get_stride(__extents, std::make_index_sequence()); - } - -private: - _MDSPAN_NO_UNIQUE_ADDRESS extents_type __extents{}; - - // [mdspan.submdspan.mapping], submdspan mapping specialization - template - MDSPAN_INLINE_FUNCTION - constexpr auto submdspan_mapping_impl( - SliceSpecifiers... slices) const; - - template - friend constexpr auto submdspan_mapping( - const mapping& src, SliceSpecifiers... slices) { - return src.submdspan_mapping_impl(slices...); - } -}; - - -} // end namespace MDSPAN_IMPL_STANDARD_NAMESPACE - -//END_FILE_INCLUDE: mdspan/include/experimental/__p0009_bits/layout_left.hpp -#if MDSPAN_HAS_CXX_17 -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -namespace detail { -template -MDSPAN_INLINE_FUNCTION -constexpr _T -find_next_multiple(_T alignment, _T offset) -{ - if ( alignment == 0 ) { - return _T(0); - } else { - return ( ( offset + alignment - 1 ) / alignment) * alignment; - } -} - -template -MDSPAN_INLINE_FUNCTION constexpr size_t get_actual_static_padding_value() { - constexpr auto rank = _ExtentsType::rank(); - - if constexpr (rank <= typename _ExtentsType::rank_type(1)) { - return 0; - } else if constexpr (_PaddingValue != dynamic_extent && - _ExtentsType::static_extent(_ExtentToPadIdx) != - dynamic_extent) { - static_assert( - (_PaddingValue != 0) || - (_ExtentsType::static_extent(_ExtentToPadIdx) == 0), - "padding stride can be 0 only if " - "extents_type::static_extent(extent-to-pad) is 0 or dynamic_extent"); - return find_next_multiple(_PaddingValue, - _ExtentsType::static_extent(_ExtentToPadIdx)); - } else { - return dynamic_extent; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return 0; -#endif -} - -template -struct static_array_type_for_padded_extent -{ - static constexpr size_t padding_value = _PaddingValue; - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using type = ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< - index_type, size_t, dynamic_extent, - detail::get_actual_static_padding_value()>; -}; - -template -struct static_array_type_for_padded_extent<_PaddingValue, _Extents, - _ExtentToPadIdx, Rank, std::enable_if_t> { - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using type = - ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::maybe_static_array< - index_type, size_t, dynamic_extent, 0>; -}; - -template -struct padded_extent { - static constexpr size_t padding_value = _PaddingValue; - using index_type = typename _Extents::index_type; - using extents_type = _Extents; - using static_array_type = typename static_array_type_for_padded_extent< - padding_value, _Extents, _ExtentToPadIdx, _Extents::rank()>::type; - - static constexpr auto static_value() { return static_array_type::static_value(0); } - - MDSPAN_INLINE_FUNCTION - static constexpr static_array_type - init_padding(const _Extents &exts) { - if constexpr ((_Extents::rank() > 1) && (padding_value == dynamic_extent)) { - return {exts.extent(_ExtentToPadIdx)}; - } else { - return init_padding(exts, padding_value); - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } - - MDSPAN_INLINE_FUNCTION static constexpr static_array_type - init_padding([[maybe_unused]] const _Extents &exts, - [[maybe_unused]] index_type pv) { - if constexpr (_Extents::rank() > 1) { - return {find_next_multiple(pv, - exts.extent(_ExtentToPadIdx))}; - } else { - return {}; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } - - template - MDSPAN_INLINE_FUNCTION static constexpr static_array_type - init_padding([[maybe_unused]] const _Mapping &other_mapping, - std::integral_constant) { - if constexpr (_Extents::rank() > 1) { - return {other_mapping.stride(_PaddingStrideIdx)}; - } else { - return {}; - } - // Missing return statement warning from NVCC -#ifdef __NVCC__ - return {}; -#endif - } -}; -} // namespace detail - -template -template -class layout_left_padded::mapping { -public: - static constexpr size_t padding_value = PaddingValue; - - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_left_padded; - -#ifndef MDSPAN_INTERNAL_TEST -private: -#endif // MDSPAN_INTERNAL_TEST - - static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; - static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; - - static_assert((padding_value != 0) - || (extents_type::static_extent(extent_to_pad_idx) == 0) - || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), - "out of bounds access for rank 0"); - - using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; - - static constexpr size_t static_padding_stride = padded_stride_type::static_value(); - - typename padded_stride_type::static_array_type padded_stride = {}; - extents_type exts = {}; - - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence<>) const { - return 0; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, IndexOffset index_offset) const { - return index_offset; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, - IndexOffsets... index_offsets) const { - index_type indices[] = {static_cast(index_offsets)...}; - // self-recursive fold trick from - // https://github.com/llvm/llvm-project/blob/96e1914aa2e6d8966acbfbe2f4d184201f1aa318/libcxx/include/mdspan/layout_left.h#L144 - index_type res = 0; - ((res = indices[extents_type::rank() - 1 - Ranks] + - ((extents_type::rank() - 1 - Ranks) == extent_to_pad_idx - ? padded_stride.value(0) - : exts.extent(extents_type::rank() - 1 - Ranks)) * - res), - ...); - return res; - } - -public: -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - : mapping(extents_type{}) - {} -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - requires(static_padding_stride != dynamic_extent) = default; - - MDSPAN_INLINE_FUNCTION - constexpr mapping() - requires(static_padding_stride == dynamic_extent) - : mapping(extents_type{}) - {} -#endif - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; - - /** - * Initializes the mapping with the given extents. - * - * \param ext the given extents - */ - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type& ext) - : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) - {} - - /** - * Initializes the mapping with the given extents and the specified padding value. - * - * This overload participates in overload resolution only if `is_convertible_v` - * is `true` and `is_nothrow_constructible_v` is `true` - * - * \param ext the given extents - * \param padding_value the padding value - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Size, - /* requires */ ( - std::is_convertible_v<_Size, index_type> - && std::is_nothrow_constructible_v - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) - : padded_stride(padded_stride_type::init_padding(ext, dynamic_padding_value)), exts(ext) - { - assert((padding_value == dynamic_extent) || (static_cast(padding_value) == static_cast(dynamic_padding_value))); - } - - /** - * Converting constructor from `layout_left::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. If - * `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, - * or `OtherExtents::static_extent(0)` must be `dynamic_extent`; otherwise, - * `OtherExtents::static_extent(0)` must be equal to the least multiple of - * `padding_value` greater than or equal to `extents_type::static_extent(0)` - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v<_OtherExtents, extents_type>)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_left::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert( - (_OtherExtents::rank() > 1) || - (static_padding_stride != dynamic_extent) || - (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || - (static_padding_stride == - _OtherExtents::static_extent(extent_to_pad_idx))); - } - - /** - * Converting constructor from `layout_stride::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) {} - - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. Either - * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or - * `padding_value == OtherPaddingStride`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value - &&std::is_constructible_v< - extents_type, typename _Mapping::extents_type>)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && - (padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert(padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent || - padding_value == _Mapping::padding_value); - } - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if - * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value - &&extents_type::rank() <= 1 && - std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) noexcept - : padded_stride(padded_stride_type::init_padding( - other_mapping.extents(), - other_mapping.extents().extent(extent_to_pad_idx))), - exts(other_mapping.extents()) {} - - MDSPAN_INLINE_FUNCTION constexpr const extents_type & - extents() const noexcept { - return exts; - } - - MDSPAN_INLINE_FUNCTION constexpr std::array - strides() const noexcept { - if constexpr (extents_type::rank() == 0) { - return {}; - } else if constexpr (extents_type::rank() == 1) { - return {1}; - } else { - index_type value = 1; - std::array s{}; - s[extent_to_pad_idx] = value; - value *= padded_stride.value(0); - for (rank_type r = extent_to_pad_idx + 1; r < extents_type::rank() - 1; - ++r) { - s[r] = value; - value *= exts.extent(r); - } - s[extents_type::rank() - 1] = value; - return s; - } - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - required_span_size() const noexcept { - if constexpr (extents_type::rank() == 0) { - return 1; - } else if constexpr (extents_type::rank() == 1) { - return exts.extent(0); - } else { - index_type value = padded_stride.value(0); - for (rank_type r = 1; r < extents_type::rank(); ++r) { - value *= exts.extent(r); - } - return value; - } - } - - /** - * Return the mapping given the provided indices per rank. - * - * This overload participates in overload resolution only if: - * - `sizeof...(Indices) == extents_type::rank()`, - * - `(is_convertible_v && ...) is true`, and - * - (is_nothrow_constructible_v && ...) is true. - */ - MDSPAN_TEMPLATE_REQUIRES( - class... _Indices, - /* requires */ (sizeof...(_Indices) == extents_type::rank() && - (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: - are_valid_indices()))) - MDSPAN_INLINE_FUNCTION constexpr size_t - operator()(_Indices... idxs) const noexcept { -#if !defined(NDEBUG) - ::MDSPAN_IMPL_STANDARD_NAMESPACE::detail::check_all_indices(this->extents(), - idxs...); -#endif // ! NDEBUG - return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return (extents_type::rank() <= rank_type(1)) || - (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && - extents_type::static_extent(extent_to_pad_idx) == - padded_stride_type::static_value()); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { - return (extents_type::rank() < 2) || - (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION - constexpr index_type stride(rank_type r) const noexcept { - assert(r < extents_type::rank()); - if (r == 0) - return index_type(1); - - index_type value = padded_stride.value(0); - for (rank_type k = 1; k < r; k++) - value *= exts.extent(k); - - return value; - } - - /** - * Equality operator between `layout_left_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - * - * \note There is currently a difference from p2642r2, where this function is - * specified as taking `layout_left_padded< padding_value >::mapping< - * Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const mapping &left, const _Mapping &right) noexcept { - // Workaround for some compilers not short-circuiting properly with - // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a - // rank 0 mapping - bool strides_equal = true; - if constexpr (extents_type::rank() > rank_type(1)) { - strides_equal = - left.stride(padded_stride_idx) == right.stride(padded_stride_idx); - } - return (left.extents() == right.extents()) && strides_equal; - } - -#if !MDSPAN_HAS_CXX_20 - /** - * Inequality operator between `layout_left_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(const mapping &left, const _Mapping &right) noexcept { - return !(left == right); - } -#endif -}; - -template -template -class layout_right_padded::mapping { -public: - static constexpr size_t padding_value = PaddingValue; - - using extents_type = Extents; - using index_type = typename extents_type::index_type; - using size_type = typename extents_type::size_type; - using rank_type = typename extents_type::rank_type; - using layout_type = layout_right_padded; - -#ifndef MDSPAN_INTERNAL_TEST - private: -#endif // MDSPAN_INTERNAL_TEST - - static constexpr rank_type padded_stride_idx = detail::layout_padded_constants::padded_stride_idx; - static constexpr rank_type extent_to_pad_idx = detail::layout_padded_constants::extent_to_pad_idx; - - static_assert((padding_value != 0) - || (extents_type::static_extent(extent_to_pad_idx) == 0) - || (extents_type::static_extent(extent_to_pad_idx) == dynamic_extent), - "if padding stride is 0, static_extent(extent-to-pad-rank) must also be 0 or dynamic_extent"); - - using padded_stride_type = detail::padded_extent< padding_value, extents_type, extent_to_pad_idx >; - static constexpr size_t static_padding_stride = padded_stride_type::static_value(); - - typename padded_stride_type::static_array_type padded_stride = {}; - extents_type exts = {}; - - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence<>) const { - return 0; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, IndexOffset index_offset) const { - return index_offset; - } - - template - MDSPAN_INLINE_FUNCTION constexpr index_type - compute_offset(std::index_sequence, - IndexOffsets... index_offsets) const { - // self-recursive fold trick from - // https://github.com/llvm/llvm-project/blob/4d9771741d40cc9cfcccb6b033f43689d36b705a/libcxx/include/mdspan/layout_right.h#L141 - index_type res = 0; - ((res = static_cast(index_offsets) + - (Ranks == extent_to_pad_idx ? padded_stride.value(0) - : exts.extent(Ranks)) * - res), - ...); - return res; - } - -public: -#if !MDSPAN_HAS_CXX_20 - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - : mapping(extents_type{}) - {} -#else - MDSPAN_INLINE_FUNCTION_DEFAULTED - constexpr mapping() - requires(static_padding_stride != dynamic_extent) = default; - - MDSPAN_INLINE_FUNCTION - constexpr mapping() - requires(static_padding_stride == dynamic_extent) - : mapping(extents_type{}) - {} -#endif - - MDSPAN_INLINE_FUNCTION_DEFAULTED constexpr mapping(const mapping&) noexcept = default; - MDSPAN_INLINE_FUNCTION_DEFAULTED mapping& operator=(const mapping&) noexcept = default; - - /** - * Initializes the mapping with the given extents. - * - * \param ext the given extents - */ - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext) - : padded_stride(padded_stride_type::init_padding(ext)), exts(ext) {} - - /** - * Initializes the mapping with the given extents and the specified padding value. - * - * This overload participates in overload resolution only if `is_convertible_v` - * is `true` and `is_nothrow_constructible_v` is `true` - * - * \param ext the given extents - * \param padding_value the padding value - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Size, - /* requires */ ( - std::is_convertible_v<_Size, index_type> - && std::is_nothrow_constructible_v - ) - ) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const extents_type &ext, _Size dynamic_padding_value) - : padded_stride(padded_stride_type::init_padding(ext, static_cast(dynamic_padding_value))), - exts(ext) { - assert((padding_value == dynamic_extent) || - (static_cast(padding_value) == static_cast(dynamic_padding_value))); - } - - /** - * Converting constructor from `layout_right::mapping`. - * - * This overload participates in overload resolution only if `is_constructible_v` is true. - * If `OtherExtents::rank() > 1` then one of `padding_value`, `static_extent(0)`, or `OtherExtents::static_extent(0)` must be `dynamic_extent`; - * otherwise, `OtherExtents::static_extent(0)` must be equal to the least multiple of `padding_value` greater than or equal to `extents_type::static_extent(0)` - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v<_OtherExtents, extents_type>)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_right::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert( - (_OtherExtents::rank() > 1) || - (padded_stride_type::static_value() != dynamic_extent) || - (_OtherExtents::static_extent(extent_to_pad_idx) != dynamic_extent) || - (padded_stride_type::static_value() == - _OtherExtents::static_extent(extent_to_pad_idx))); - } - - /** - * Converting constructor from `layout_stride::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true - */ - MDSPAN_TEMPLATE_REQUIRES( - class _OtherExtents, - /* requires */ (std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 0)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const layout_stride::mapping<_OtherExtents> &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) {} - - /** - * Converting constructor from `layout_right_padded::mapping`. - * - * This overload participates in overload resolution only if - * `is_constructible_v` is true. Either - * `padding_value` or `OtherPaddingStride` must be `std::dynamic_extent`, or - * `padding_value == OtherPaddingStride`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value - &&std::is_constructible_v< - extents_type, typename _Mapping::extents_type>)) - MDSPAN_CONDITIONAL_EXPLICIT((extents_type::rank() > 1 && - (padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent))) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) - : padded_stride(padded_stride_type::init_padding( - other_mapping, - std::integral_constant{})), - exts(other_mapping.extents()) { - static_assert(padding_value == dynamic_extent || - _Mapping::padding_value == dynamic_extent || - padding_value == _Mapping::padding_value); - } - - /** - * Converting constructor from `layout_left_padded::mapping`. - * - * This overload participates in overload resolution only if - * `extents_type::rank()` is 0 or 1 and `is_constructible_v` is `true`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_left_padded_mapping<_Mapping>::value - &&extents_type::rank() <= 1 && - std::is_constructible_v)) - MDSPAN_CONDITIONAL_EXPLICIT( - (!std::is_convertible_v)) - MDSPAN_INLINE_FUNCTION - constexpr mapping(const _Mapping &other_mapping) noexcept - : padded_stride(padded_stride_type::init_padding( - other_mapping.extents(), - other_mapping.extents().extent(extent_to_pad_idx))), - exts(other_mapping.extents()) {} - - MDSPAN_INLINE_FUNCTION constexpr const extents_type & - extents() const noexcept { - return exts; - } - - MDSPAN_INLINE_FUNCTION constexpr std::array - strides() const noexcept { - if constexpr (extents_type::rank() == 0) { - return {}; - } else if constexpr (extents_type::rank() == 1) { - return {1}; - } else { - index_type value = 1; - std::array s{}; - s[extent_to_pad_idx] = value; - value *= padded_stride.value(0); - for (rank_type r = extent_to_pad_idx - 1; r > 0; --r) { - s[r] = value; - value *= exts.extent(r); - } - s[0] = value; - return s; - } - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - required_span_size() const noexcept { - if constexpr (extents_type::rank() == 0) { - return 1; - } else if constexpr (extents_type::rank() == 1) { - return exts.extent(0); - } else { - index_type value = 1; - for (rank_type r = 0; r < extent_to_pad_idx; ++r) { - value *= exts.extent(r); - } - return value * padded_stride.value(0); - } - } - - /** - * Return the mapping given the provided indices per rank. - * - * This overload participates in overload resolution only if: - * - `sizeof...(Indices) == extents_type::rank()`, - * - `(is_convertible_v && ...) is true`, and - * - (is_nothrow_constructible_v && ...) is true. - */ - MDSPAN_TEMPLATE_REQUIRES( - class... _Indices, - /* requires */ (sizeof...(_Indices) == extents_type::rank() && - (::MDSPAN_IMPL_STANDARD_NAMESPACE::detail:: - are_valid_indices()))) - MDSPAN_INLINE_FUNCTION constexpr size_t - operator()(_Indices... idxs) const noexcept { - return compute_offset(std::index_sequence_for<_Indices...>{}, idxs...); - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_exhaustive() noexcept { - return (extents_type::rank() <= rank_type(1)) || - (extents_type::static_extent(extent_to_pad_idx) != dynamic_extent && - extents_type::static_extent(extent_to_pad_idx) == - padded_stride_type::static_value()); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_always_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION static constexpr bool is_unique() noexcept { - return true; - } - MDSPAN_INLINE_FUNCTION constexpr bool is_exhaustive() const noexcept { - return (extents_type::rank() < 2) || - (exts.extent(extent_to_pad_idx) == padded_stride.value(0)); - } - MDSPAN_INLINE_FUNCTION static constexpr bool is_strided() noexcept { - return true; - } - - MDSPAN_INLINE_FUNCTION constexpr index_type - stride(rank_type r) const noexcept { - assert(r < extents_type::rank()); - if (r == extents_type::rank() - 1) - return index_type(1); - - index_type value = padded_stride.value(0); - for (rank_type k = extents_type::rank() - 2; k > r; k--) - value *= exts.extent(k); - - return value; - } - - /** - * Equality operator between `layout_right_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - * - * \note There is currently a difference from p2642r2, where this function is - * specified as taking `layout_right_padded< padding_value >::mapping< - * Extents>`. However, this makes `padding_value` non-deducible. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator==(const mapping &left, const _Mapping &right) noexcept { - // Workaround for some compilers not short-circuiting properly with - // compile-time checks i.e. we can't access stride(_padding_stride_idx) of a - // rank 0 mapping - bool strides_equal = true; - if constexpr (extents_type::rank() > rank_type(1)) { - strides_equal = - left.stride(padded_stride_idx) == right.stride(padded_stride_idx); - } - return (left.extents() == right.extents()) && strides_equal; - } - -#if !MDSPAN_HAS_CXX_20 - /** - * Inequality operator between `layout_right_padded`s - * - * This overload only participates in overload resolution if - * `OtherExtents::rank() == extents_type::rank()`. - */ - MDSPAN_TEMPLATE_REQUIRES( - class _Mapping, - /* requires */ (detail::is_layout_right_padded_mapping<_Mapping>::value && - (_Mapping::extents_type::rank() == extents_type::rank()))) - MDSPAN_INLINE_FUNCTION friend constexpr bool - operator!=(const mapping &left, const _Mapping &right) noexcept { - return !(left == right); - } -#endif -}; -} -} -//END_FILE_INCLUDE: mdspan/include/experimental/__p2642_bits/layout_padded.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include - -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp - -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { - -namespace { - template - struct __mdspan_is_integral_constant: std::false_type {}; - - template - struct __mdspan_is_integral_constant>: std::true_type {}; -} - -// Slice Specifier allowing for strides and compile time extent -template -struct strided_slice { - using offset_type = OffsetType; - using extent_type = ExtentType; - using stride_type = StrideType; - - _MDSPAN_NO_UNIQUE_ADDRESS OffsetType offset{}; - _MDSPAN_NO_UNIQUE_ADDRESS ExtentType extent{}; - _MDSPAN_NO_UNIQUE_ADDRESS StrideType stride{}; - - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); - static_assert(std::is_integral_v || __mdspan_is_integral_constant::value); -}; - -} // MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/strided_slice.hpp -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace detail { - -// Mapping from submapping ranks to srcmapping ranks -// InvMapRank is an index_sequence, which we build recursively -// to contain the mapped indices. -// end of recursion specialization containing the final index_sequence -template -MDSPAN_INLINE_FUNCTION -constexpr auto inv_map_rank(std::integral_constant, std::index_sequence) { - return std::index_sequence(); -} - -// specialization reducing rank by one (i.e., integral slice specifier) -template -MDSPAN_INLINE_FUNCTION -constexpr auto inv_map_rank(std::integral_constant, std::index_sequence, Slice, - SliceSpecifiers... slices) { - using next_idx_seq_t = std::conditional_t, - std::index_sequence, - std::index_sequence>; - - return inv_map_rank(std::integral_constant(), next_idx_seq_t(), - slices...); -} - -// Helper for identifying strided_slice -template struct is_strided_slice : std::false_type {}; - -template -struct is_strided_slice< - strided_slice> : std::true_type {}; - -// first_of(slice): getting begin of slice specifier range -MDSPAN_TEMPLATE_REQUIRES( - class Integral, - /* requires */(std::is_convertible_v) -) -MDSPAN_INLINE_FUNCTION -constexpr Integral first_of(const Integral &i) { - return i; -} - -MDSPAN_INLINE_FUNCTION -constexpr std::integral_constant -first_of(const ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t &) { - return std::integral_constant(); -} - -MDSPAN_TEMPLATE_REQUIRES( - class Slice, - /* requires */(std::is_convertible_v>) -) -MDSPAN_INLINE_FUNCTION -constexpr auto first_of(const Slice &i) { - return std::get<0>(i); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr OffsetType -first_of(const strided_slice &r) { - return r.offset; -} - -// last_of(slice): getting end of slice specifier range -// We need however not just the slice but also the extents -// of the original view and which rank from the extents. -// This is needed in the case of slice being full_extent_t. -MDSPAN_TEMPLATE_REQUIRES( - size_t k, class Extents, class Integral, - /* requires */(std::is_convertible_v) -) -MDSPAN_INLINE_FUNCTION -constexpr Integral - last_of(std::integral_constant, const Extents &, const Integral &i) { - return i; -} - -MDSPAN_TEMPLATE_REQUIRES( - size_t k, class Extents, class Slice, - /* requires */(std::is_convertible_v>) -) -MDSPAN_INLINE_FUNCTION -constexpr auto last_of(std::integral_constant, const Extents &, - const Slice &i) { - return std::get<1>(i); -} - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -MDSPAN_INLINE_FUNCTION -constexpr auto last_of(std::integral_constant, const Extents &ext, - ::MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent_t) { - if constexpr (Extents::static_extent(k) == dynamic_extent) { - return ext.extent(k); - } else { - return std::integral_constant(); - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - // Even with CUDA_ARCH protection this thing warns about calling host function - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -template -MDSPAN_INLINE_FUNCTION -constexpr OffsetType -last_of(std::integral_constant, const Extents &, - const strided_slice &r) { - return r.extent; -} - -// get stride of slices -template -MDSPAN_INLINE_FUNCTION -constexpr auto stride_of(const T &) { - return std::integral_constant(); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto -stride_of(const strided_slice &r) { - return r.stride; -} - -// divide which can deal with integral constant preservation -template -MDSPAN_INLINE_FUNCTION -constexpr auto divide(const T0 &v0, const T1 &v1) { - return IndexT(v0) / IndexT(v1); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto divide(const std::integral_constant &, - const std::integral_constant &) { - // cutting short division by zero - // this is used for strided_slice with zero extent/stride - return std::integral_constant(); -} - -// multiply which can deal with integral constant preservation -template -MDSPAN_INLINE_FUNCTION -constexpr auto multiply(const T0 &v0, const T1 &v1) { - return IndexT(v0) * IndexT(v1); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr auto multiply(const std::integral_constant &, - const std::integral_constant &) { - return std::integral_constant(); -} - -// compute new static extent from range, preserving static knowledge -template struct StaticExtentFromRange { - constexpr static size_t value = dynamic_extent; -}; - -template -struct StaticExtentFromRange, - std::integral_constant> { - constexpr static size_t value = val1 - val0; -}; - -// compute new static extent from strided_slice, preserving static -// knowledge -template struct StaticExtentFromStridedRange { - constexpr static size_t value = dynamic_extent; -}; - -template -struct StaticExtentFromStridedRange, - std::integral_constant> { - constexpr static size_t value = val0 > 0 ? 1 + (val0 - 1) / val1 : 0; -}; - -// creates new extents through recursive calls to next_extent member function -// next_extent has different overloads for different types of stride specifiers -template -struct extents_constructor { - MDSPAN_TEMPLATE_REQUIRES( - class Slice, class... SlicesAndExtents, - /* requires */(!std::is_convertible_v && - !is_strided_slice::value) - ) - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &ext, const Slice &sl, - SlicesAndExtents... slices_and_extents) { - constexpr size_t new_static_extent = StaticExtentFromRange< - decltype(first_of(std::declval())), - decltype(last_of(std::integral_constant(), - std::declval(), - std::declval()))>::value; - - using next_t = - extents_constructor; - using index_t = typename Extents::index_type; - return next_t::next_extent( - ext, slices_and_extents..., - index_t(last_of(std::integral_constant(), ext, - sl)) - - index_t(first_of(sl))); - } - - MDSPAN_TEMPLATE_REQUIRES( - class Slice, class... SlicesAndExtents, - /* requires */ (std::is_convertible_v) - ) - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &ext, const Slice &, - SlicesAndExtents... slices_and_extents) { - using next_t = extents_constructor; - return next_t::next_extent(ext, slices_and_extents...); - } - - template - MDSPAN_INLINE_FUNCTION - constexpr static auto - next_extent(const Extents &ext, - const strided_slice &r, - SlicesAndExtents... slices_and_extents) { - using index_t = typename Extents::index_type; - using new_static_extent_t = - StaticExtentFromStridedRange; - if constexpr (new_static_extent_t::value == dynamic_extent) { - using next_t = - extents_constructor; - return next_t::next_extent( - ext, slices_and_extents..., - r.extent > 0 ? 1 + divide(r.extent - 1, r.stride) : 0); - } else { - constexpr size_t new_static_extent = new_static_extent_t::value; - using next_t = - extents_constructor; - return next_t::next_extent( - ext, slices_and_extents..., index_t(divide(ExtentType(), StrideType()))); - } - } -}; - -template -struct extents_constructor<0, Extents, NewStaticExtents...> { - - template - MDSPAN_INLINE_FUNCTION - constexpr static auto next_extent(const Extents &, NewExtents... new_exts) { - return extents( - new_exts...); - } -}; - -} // namespace detail - -// submdspan_extents creates new extents given src extents and submdspan slice -// specifiers -template -MDSPAN_INLINE_FUNCTION -constexpr auto submdspan_extents(const extents &src_exts, - SliceSpecifiers... slices) { - - using ext_t = extents; - return detail::extents_constructor::next_extent( - src_exts, slices...); -} -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_extents.hpp -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -#include -#include -#include -#include // index_sequence - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -//****************************************** -// Return type of submdspan_mapping overloads -//****************************************** -template struct submdspan_mapping_result { - _MDSPAN_NO_UNIQUE_ADDRESS LayoutMapping mapping{}; - size_t offset; -}; - -namespace detail { - -// We use const Slice& and not Slice&& because the various -// submdspan_mapping_impl overloads use their slices arguments -// multiple times. This makes perfect forwarding not useful, but we -// still don't want to pass those (possibly of size 64 x 3 bits) -// objects by value. -template -MDSPAN_INLINE_FUNCTION -constexpr bool -one_slice_out_of_bounds(const IndexType& extent, const Slice& slice) -{ - using common_t = std::common_type_t; - return static_cast(detail::first_of(slice)) == static_cast(extent); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr bool -any_slice_out_of_bounds_helper(std::index_sequence, - const extents& exts, - const Slices& ... slices) -{ - return _MDSPAN_FOLD_OR( - (one_slice_out_of_bounds(exts.extent(RankIndices), slices)) - ); -} - -template -MDSPAN_INLINE_FUNCTION -constexpr bool -any_slice_out_of_bounds(const extents& exts, - const Slices& ... slices) -{ - return any_slice_out_of_bounds_helper( - std::make_index_sequence(), - exts, slices...); -} - -// constructs sub strides -template -MDSPAN_INLINE_FUNCTION -constexpr auto -construct_sub_strides(const SrcMapping &src_mapping, - std::index_sequence, - const std::tuple &slices_stride_factor) { - using index_type = typename SrcMapping::index_type; - return std::array{ - (static_cast(src_mapping.stride(InvMapIdxs)) * - static_cast(std::get(slices_stride_factor)))...}; -} -} // namespace detail - -//********************************** -// layout_left submdspan_mapping -//********************************* -namespace detail { - -// Figure out whether to preserve layout_left -template -struct preserve_layout_left_mapping; - -template -struct preserve_layout_left_mapping, SubRank, - SliceSpecifiers...> { - constexpr static bool value = - // Preserve layout for rank 0 - (SubRank == 0) || - ( - // Slice specifiers up to subrank need to be full_extent_t - except - // for the last one which could also be tuple but not a strided index - // range slice specifiers after subrank are integrals - ((Idx > SubRank - 1) || // these are only integral slice specifiers - (std::is_same_v) || - ((Idx == SubRank - 1) && - std::is_convertible_v>)) && - ...); -}; -} // namespace detail - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -// Actual submdspan mapping call -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_left::mapping::submdspan_mapping_impl(SliceSpecifiers... slices) const { - - // compute sub extents - using src_ext_t = Extents; - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - - // figure out sub layout type - constexpr bool preserve_layout = detail::preserve_layout_left_mapping< - decltype(std::make_index_sequence()), dst_ext_t::rank(), - SliceSpecifiers...>::value; - using dst_layout_t = - std::conditional_t; - using dst_mapping_t = typename dst_layout_t::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - if constexpr (std::is_same_v) { - // layout_left case - return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; - } else { - // layout_stride case - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether - #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) - std::tuple{detail::stride_of(slices)...})), - #else - std::tuple{detail::stride_of(slices)...})), - #endif - offset}; - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -//********************************** -// layout_right submdspan_mapping -//********************************* -namespace detail { - -// Figure out whether to preserve layout_right -template -struct preserve_layout_right_mapping; - -template -struct preserve_layout_right_mapping, SubRank, - SliceSpecifiers...> { - constexpr static size_t SrcRank = sizeof...(SliceSpecifiers); - constexpr static bool value = - // Preserve layout for rank 0 - (SubRank == 0) || - ( - // The last subrank slice specifiers need to be full_extent_t - except - // for the srcrank-subrank one which could also be tuple but not a - // strided index range slice specifiers before srcrank-subrank are - // integrals - ((Idx < - SrcRank - SubRank) || // these are only integral slice specifiers - (std::is_same_v) || - ((Idx == SrcRank - SubRank) && - std::is_convertible_v>)) && - ...); -}; -} // namespace detail - -// Suppress spurious warning with NVCC about no return statement. -// This is a known issue in NVCC and NVC++ -// Depending on the CUDA and GCC version we need both the builtin -// and the diagnostic push. I tried really hard to find something shorter -// but no luck ... -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic push - #pragma nv_diag_suppress = implicit_return_from_non_void_function - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic push - #pragma diag_suppress implicit_return_from_non_void_function - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic push - #pragma diag_suppress = implicit_return_from_non_void_function -#endif -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_right::mapping::submdspan_mapping_impl( - SliceSpecifiers... slices) const { - // get sub extents - using src_ext_t = Extents; - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - - // determine new layout type - constexpr bool preserve_layout = detail::preserve_layout_right_mapping< - decltype(std::make_index_sequence()), dst_ext_t::rank(), - SliceSpecifiers...>::value; - using dst_layout_t = - std::conditional_t; - using dst_mapping_t = typename dst_layout_t::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - if constexpr (std::is_same_v) { - // layout_right case - return submdspan_mapping_result{dst_mapping_t(dst_ext), offset}; - } else { - // layout_stride case - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - // But Clang-CUDA also doesn't accept the use of deduction guide so disable it for CUDA alltogether - #if defined(_MDSPAN_HAS_HIP) || defined(_MDSPAN_HAS_CUDA) - std::tuple{detail::stride_of(slices)...})), - #else - std::tuple{detail::stride_of(slices)...})), - #endif - offset}; - } -#if defined(__NVCC__) && !defined(__CUDA_ARCH__) && defined(__GNUC__) - __builtin_unreachable(); -#endif -} -#if defined __NVCC__ - #ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ - #pragma nv_diagnostic pop - #else - #ifdef __CUDA_ARCH__ - #pragma diagnostic pop - #endif - #endif -#elif defined __NVCOMPILER - #pragma diagnostic pop -#endif - -//********************************** -// layout_stride submdspan_mapping -//********************************* -template -template -MDSPAN_INLINE_FUNCTION -constexpr auto -layout_stride::mapping::submdspan_mapping_impl( - SliceSpecifiers... slices) const { - auto dst_ext = submdspan_extents(extents(), slices...); - using dst_ext_t = decltype(dst_ext); - auto inv_map = detail::inv_map_rank( - std::integral_constant(), - std::index_sequence<>(), - slices...); - using dst_mapping_t = typename layout_stride::template mapping; - - // Figure out if any slice's lower bound equals the corresponding extent. - // If so, bypass evaluating the layout mapping. This fixes LWG Issue 4060. - const bool out_of_bounds = - detail::any_slice_out_of_bounds(this->extents(), slices...); - auto offset = static_cast( - out_of_bounds ? - this->required_span_size() : - this->operator()(detail::first_of(slices)...) - ); - - return submdspan_mapping_result{ - dst_mapping_t(dst_ext, detail::construct_sub_strides( - *this, inv_map, - // HIP needs deduction guides to have markups so we need to be explicit - // NVCC 11.0 has a bug with deduction guide here, tested that 11.2 does not have the issue - #if defined(_MDSPAN_HAS_HIP) || (defined(__NVCC__) && (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__ * 10) < 1120) - std::tuple(detail::stride_of(slices)...))), -#else - std::tuple(detail::stride_of(slices)...))), -#endif - offset}; -} - -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan_mapping.hpp - -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -template -MDSPAN_INLINE_FUNCTION -constexpr auto -submdspan(const mdspan &src, - SliceSpecifiers... slices) { - const auto sub_submdspan_mapping_result = submdspan_mapping(src.mapping(), slices...); - // NVCC has a problem with the deduction so lets figure out the type - using sub_mapping_t = std::remove_cv_t; - using sub_extents_t = typename sub_mapping_t::extents_type; - using sub_layout_t = typename sub_mapping_t::layout_type; - using sub_accessor_t = typename AccessorPolicy::offset_policy; - return mdspan( - src.accessor().offset(src.data_handle(), sub_submdspan_mapping_result.offset), - sub_submdspan_mapping_result.mapping, - sub_accessor_t(src.accessor())); -} -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2630_bits/submdspan.hpp -#endif -//BEGIN_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp -//@HEADER -// ************************************************************************ -// -// Kokkos v. 4.0 -// Copyright (2022) National Technology & Engineering -// Solutions of Sandia, LLC (NTESS). -// -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software. -// -// Part of Kokkos, under the Apache License v2.0 with LLVM Exceptions. -// See https://kokkos.org/LICENSE for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//@HEADER - - -// backward compatibility import into experimental -namespace MDSPAN_IMPL_STANDARD_NAMESPACE { -namespace MDSPAN_IMPL_PROPOSED_NAMESPACE { - -template< ::std::size_t Rank, class IndexType = std::size_t> -using dims = - :: MDSPAN_IMPL_STANDARD_NAMESPACE :: dextents; - -} // namespace MDSPAN_IMPL_PROPOSED_NAMESPACE -} // namespace MDSPAN_IMPL_STANDARD_NAMESPACE -//END_FILE_INCLUDE: mdspan/include/experimental/__p2389_bits/dims.hpp - -#endif // MDSPAN_HPP_ -//END_FILE_INCLUDE: mdspan/include/mdspan/mdspan.hpp -#endif // _MDSPAN_SINGLE_HEADER_INCLUDE_GUARD_ - diff --git a/scipy/special/xsf/tools.h b/scipy/special/xsf/tools.h deleted file mode 100644 index e349f6a5fb4f..000000000000 --- a/scipy/special/xsf/tools.h +++ /dev/null @@ -1,427 +0,0 @@ -/* Building blocks for implementing special functions */ - -#pragma once - -#include "config.h" -#include "error.h" - -namespace xsf { -namespace detail { - - /* Result type of a "generator", a callable object that produces a value - * each time it is called. - */ - template - using generator_result_t = typename std::decay::type>::type; - - /* Used to deduce the type of the numerator/denominator of a fraction. */ - template - struct pair_traits; - - template - struct pair_traits> { - using value_type = T; - }; - - template - using pair_value_t = typename pair_traits::value_type; - - /* Used to extract the "value type" of a complex type. */ - template - struct real_type { - using type = T; - }; - - template - struct real_type> { - using type = T; - }; - - template - using real_type_t = typename real_type::type; - - // Return NaN, handling both real and complex types. - template - XSF_HOST_DEVICE inline typename std::enable_if::value, T>::type maybe_complex_NaN() { - return std::numeric_limits::quiet_NaN(); - } - - template - XSF_HOST_DEVICE inline typename std::enable_if::value, T>::type maybe_complex_NaN() { - using V = typename T::value_type; - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - - // Series evaluators. - template > - XSF_HOST_DEVICE T - series_eval(Generator &g, T init_val, real_type_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(); - } - - template > - XSF_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 - XSF_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 > - XSF_HOST_DEVICE std::pair - series_eval_kahan(Generator &&g, real_type_t tol, std::uint64_t max_terms, T init_val = T(0)) { - - using std::abs; - T sum = init_val; - T comp = T(0); - for (std::uint64_t i = 0; i < max_terms; ++i) { - T term = g(); - kahan_step(sum, comp, term); - if (abs(term) <= tol * 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]. - * Let v[n], n >= 1 denote the terms of the series. Then - * - * v[1] = a[1] / b[1] - * v[n] = v[n-1] * r[n-1], n >= 2 - * - * where - * - * -(a[n] + a[n] * r[n-1]) - * r[1] = 0, r[n] = ------------------------------------------, n >= 2 - * (a[n] + a[n] * r[n-1]) + (b[n] * b[n-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 >> - class ContinuedFractionSeriesGenerator { - - public: - XSF_HOST_DEVICE explicit ContinuedFractionSeriesGenerator(Generator &cf) : cf_(cf) { init(); } - - XSF_HOST_DEVICE T operator()() { - T v = v_; - advance(); - return v; - } - - private: - XSF_HOST_DEVICE void init() { - auto [num, denom] = cf_(); - T a = num; - T b = denom; - r_ = T(0); - v_ = a / b; - b_ = b; - } - - XSF_HOST_DEVICE void advance() { - auto [num, denom] = cf_(); - T a = num; - T b = denom; - T p = a + a * r_; - T q = p + b * b_; - r_ = -p / q; - v_ = v_ * r_; - b_ = b; - } - - Generator &cf_; // reference to continued fraction generator - T v_; // v[n] == f[n] - f[n-1], n >= 1 - T r_; // r[1] = 0, r[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 >> - XSF_HOST_DEVICE ContinuedFractionSeriesGenerator continued_fraction_series(Generator &cf) { - return ContinuedFractionSeriesGenerator(cf); - } - - /* Find initial bracket for a bracketing scalar root finder. A valid bracket is a pair of points a < b for - * which the signs of f(a) and f(b) differ. If f(x0) = 0, where x0 is the initial guess, this bracket finder - * will return the bracket (x0, x0). It is expected that the rootfinder will check if the bracket - * endpoints are roots. - * - * This is a private function intended specifically for the situation where - * the goal is to invert a CDF function F for a parametrized family of distributions with respect to one - * parameter, when the other parameters are known, and where F is monotonic with respect to the unknown parameter. - */ - template - XSF_HOST_DEVICE inline std::tuple bracket_root_for_cdf_inversion( - Function func, double x0, double xmin, double xmax, double step0_left, - double step0_right, double factor_left, double factor_right, bool increasing, std::uint64_t maxiter - ) { - double y0 = func(x0); - - if (y0 == 0) { - // Initial guess is correct. - return {x0, x0, y0, y0, 0}; - } - - double y0_sgn = std::signbit(y0); - - bool search_left; - /* The frontier is the new leading endpoint of the expanding bracket. The - * interior endpoint trails behind the frontier. In each step, the old frontier - * endpoint becomes the new interior endpoint. */ - double interior, frontier, y_interior, y_frontier, y_interior_sgn, y_frontier_sgn, boundary, factor; - if ((increasing && y0 < 0) || (!increasing && y0 > 0)) { - /* If func is increasing and func(x_right) < 0 or if func is decreasing and - * f(y_right) > 0, we should expand the bracket to the right. */ - interior = x0, y_interior = y0; - frontier = x0 + step0_right; - y_interior_sgn = y0_sgn; - search_left = false; - boundary = xmax; - factor = factor_right; - } else { - /* Otherwise we move and expand the bracket to the left. */ - interior = x0, y_interior = y0; - frontier = x0 + step0_left; - y_interior_sgn = y0_sgn; - search_left = true; - boundary = xmin; - factor = factor_left; - } - - bool reached_boundary = false; - for (std::uint64_t i = 0; i < maxiter; i++) { - y_frontier = func(frontier); - y_frontier_sgn = std::signbit(y_frontier); - if (y_frontier_sgn != y_interior_sgn || (y_frontier == 0.0)) { - /* Stopping condition, func evaluated at endpoints of bracket has opposing signs, - * meeting requirement for bracketing root finder. (Or endpoint has reached a - * zero.) */ - if (search_left) { - /* Ensure we return an interval (a, b) with a < b. */ - std::swap(interior, frontier); - std::swap(y_interior, y_frontier); - } - return {interior, frontier, y_interior, y_frontier, 0}; - } - if (reached_boundary) { - /* We've reached a boundary point without finding a root . */ - return { - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - search_left ? 1 : 2 - }; - } - double step = (frontier - interior) * factor; - interior = frontier; - y_interior = y_frontier; - y_interior_sgn = y_frontier_sgn; - frontier += step; - if ((search_left && frontier <= boundary) || (!search_left && frontier >= boundary)) { - /* If the frontier has reached the boundary, set a flag so the algorithm will know - * not to search beyond this point. */ - frontier = boundary; - reached_boundary = true; - } - } - /* Failed to converge within maxiter iterations. If maxiter is sufficiently high and - * factor_left and factor_right are set appropriately, this should only happen due to - * a bug in this function. Limiting the number of iterations is a defensive programming measure. */ - return { - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), - std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), 3 - }; - } - - /* Find root of a scalar function using Chandrupatla's algorithm */ - template - XSF_HOST_DEVICE inline std::pair find_root_chandrupatla( - Function func, double x1, double x2, double f1, double f2, double rtol, - double atol, std::uint64_t maxiter - ) { - if (f1 == 0) { - return {x1, 0}; - } - if (f2 == 0) { - return {x2, 0}; - } - double t = 0.5, x3, f3; - for (uint64_t i = 0; i < maxiter; i++) { - double x = x1 + t * (x2 - x1); - double f = func(x); - if (std::signbit(f) == std::signbit(f1)) { - x3 = x1; - x1 = x; - f3 = f1; - f1 = f; - } else { - x3 = x2; - x2 = x1; - x1 = x; - f3 = f2; - f2 = f1; - f1 = f; - } - double xm, fm; - if (std::abs(f2) < std::abs(f1)) { - xm = x2; - fm = f2; - } else { - xm = x1; - fm = f1; - } - double tol = 2.0 * rtol * std::abs(xm) + 0.5 * atol; - double tl = tol / std::abs(x2 - x1); - if (tl > 0.5 || fm == 0) { - return {xm, 0}; - } - double xi = (x1 - x2) / (x3 - x2); - double phi = (f1 - f2) / (f3 - f2); - double fl = 1.0 - std::sqrt(1.0 - xi); - double fh = std::sqrt(xi); - - if ((fl < phi) && (phi < fh)) { - t = (f1 / (f2 - f1)) * (f3 / (f2 - f3)) + (f1 / (f3 - f1)) * (f2 / (f3 - f2)) * ((x3 - x1) / (x2 - x1)); - } else { - t = 0.5; - } - t = std::fmin(std::fmax(t, tl), 1.0 - tl); - } - return {std::numeric_limits::quiet_NaN(), 1}; - } - -} // namespace detail -} // namespace xsf diff --git a/scipy/special/xsf/trig.h b/scipy/special/xsf/trig.h deleted file mode 100644 index a0221e00bbe3..000000000000 --- a/scipy/special/xsf/trig.h +++ /dev/null @@ -1,164 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * - * Original author: Josh Wilson, 2016. - */ - -/* Implement sin(pi*z) and cos(pi*z) for complex z. Since the periods - * of these functions are integral (and thus better representable in - * floating point), it's possible to compute them with greater accuracy - * than sin(z), cos(z). - */ - -#pragma once - -#include "cephes/sindg.h" -#include "cephes/tandg.h" -#include "cephes/trig.h" -#include "cephes/unity.h" -#include "config.h" -#include "evalpoly.h" - -namespace xsf { - -template -XSF_HOST_DEVICE T sinpi(T x) { - return cephes::sinpi(x); -} - -template -XSF_HOST_DEVICE std::complex sinpi(std::complex z) { - T x = z.real(); - T piy = M_PI * z.imag(); - T abspiy = std::abs(piy); - T sinpix = cephes::sinpi(x); - T cospix = cephes::cospi(x); - - if (abspiy < 700) { - return {sinpix * std::cosh(piy), cospix * std::sinh(piy)}; - } - - /* Have to be careful--sinh/cosh could overflow while cos/sin are small. - * At this large of values - * - * cosh(y) ~ exp(y)/2 - * sinh(y) ~ sgn(y)*exp(y)/2 - * - * so we can compute exp(y/2), scale by the right factor of sin/cos - * and then multiply by exp(y/2) to avoid overflow. */ - T exphpiy = std::exp(abspiy / 2); - T coshfac; - T sinhfac; - if (exphpiy == std::numeric_limits::infinity()) { - if (sinpix == 0.0) { - // Preserve the sign of zero. - coshfac = std::copysign(0.0, sinpix); - } else { - coshfac = std::copysign(std::numeric_limits::infinity(), sinpix); - } - if (cospix == 0.0) { - // Preserve the sign of zero. - sinhfac = std::copysign(0.0, cospix); - } else { - sinhfac = std::copysign(std::numeric_limits::infinity(), cospix); - } - return {coshfac, sinhfac}; - } - - coshfac = 0.5 * sinpix * exphpiy; - sinhfac = 0.5 * cospix * exphpiy; - return {coshfac * exphpiy, sinhfac * exphpiy}; -} - -template -XSF_HOST_DEVICE T cospi(T x) { - return cephes::cospi(x); -} - -template -XSF_HOST_DEVICE std::complex cospi(std::complex z) { - T x = z.real(); - T piy = M_PI * z.imag(); - T abspiy = std::abs(piy); - T sinpix = cephes::sinpi(x); - T cospix = cephes::cospi(x); - - if (abspiy < 700) { - return {cospix * std::cosh(piy), -sinpix * std::sinh(piy)}; - } - - // See csinpi(z) for an idea of what's going on here. - T exphpiy = std::exp(abspiy / 2); - T coshfac; - T sinhfac; - if (exphpiy == std::numeric_limits::infinity()) { - if (sinpix == 0.0) { - // Preserve the sign of zero. - coshfac = std::copysign(0.0, cospix); - } else { - coshfac = std::copysign(std::numeric_limits::infinity(), cospix); - } - if (cospix == 0.0) { - // Preserve the sign of zero. - sinhfac = std::copysign(0.0, sinpix); - } else { - sinhfac = std::copysign(std::numeric_limits::infinity(), sinpix); - } - return {coshfac, sinhfac}; - } - - coshfac = 0.5 * cospix * exphpiy; - sinhfac = 0.5 * sinpix * exphpiy; - return {coshfac * exphpiy, sinhfac * exphpiy}; -} - -template -XSF_HOST_DEVICE T sindg(T x) { - return cephes::sindg(x); -} - -template <> -XSF_HOST_DEVICE inline float sindg(float x) { - return sindg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T cosdg(T x) { - return cephes::cosdg(x); -} - -template <> -XSF_HOST_DEVICE inline float cosdg(float x) { - return cosdg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T tandg(T x) { - return cephes::tandg(x); -} - -template <> -XSF_HOST_DEVICE inline float tandg(float x) { - return tandg(static_cast(x)); -} - -template -XSF_HOST_DEVICE T cotdg(T x) { - return cephes::cotdg(x); -} - -template <> -XSF_HOST_DEVICE inline float cotdg(float x) { - return cotdg(static_cast(x)); -} - -inline double radian(double d, double m, double s) { return cephes::radian(d, m, s); } - -inline float radian(float d, float m, float s) { - return radian(static_cast(d), static_cast(m), static_cast(s)); -} - -inline double cosm1(double x) { return cephes::cosm1(x); } - -inline float cosm1(float x) { return cosm1(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/wright_bessel.h b/scipy/special/xsf/wright_bessel.h deleted file mode 100644 index 77cf165a0fc3..000000000000 --- a/scipy/special/xsf/wright_bessel.h +++ /dev/null @@ -1,843 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * Original header with Copyright information appears below. - */ - -/* Implementation of Wright's generalized Bessel function Phi, see - * https://dlmf.nist.gov/10.46.E1 - * - * Copyright: Christian Lorentzen - * - * Distributed under the same license as SciPy - * - * - * Implementation Overview: - * - * First, different functions are implemented valid for certain domains of the - * three arguments. - * Finally they are put together in wright_bessel. See the docstring of - * that function for more details. - */ - -#pragma once - -#include "cephes/lanczos.h" -#include "cephes/polevl.h" -#include "cephes/rgamma.h" -#include "config.h" -#include "digamma.h" -#include "error.h" - -namespace xsf { - -namespace detail { - // rgamma_zero: smallest value x for which rgamma(x) == 0 as x gets large - constexpr double rgamma_zero = 178.47241115886637; - - XSF_HOST_DEVICE inline double exp_rgamma(double x, double y) { - /* Compute exp(x) / gamma(y) = exp(x) * rgamma(y). - * - * This helper function avoids overflow by using the lanczos - * approximation of the gamma function. - */ - return std::exp(x + (1 - std::log(y + cephes::lanczos_g - 0.5)) * (y - 0.5)) / - cephes::lanczos_sum_expg_scaled(y); - } - - XSF_HOST_DEVICE inline double wb_series(double a, double b, double x, unsigned int nstart, unsigned int nstop) { - /* 1. Taylor series expansion in x=0 for x <= 1. - * - * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b) - * - * Note that every term, and therefore also Phi(a, b, x) is - * monotone decreasing with increasing a or b. - */ - double xk_k = std::pow(x, nstart) * cephes::rgamma(nstart + 1); // x^k/k! - double res = xk_k * cephes::rgamma(nstart * a + b); - // term k=nstart+1, +2, +3, ... - if (nstop > nstart) { - // series expansion until term k such that a*k+b <= rgamma_zero - unsigned int k_max = std::floor((rgamma_zero - b) / a); - if (nstop > k_max) { - nstop = k_max; - } - for (unsigned int k = nstart + 1; k < nstop; k++) { - xk_k *= x / k; - res += xk_k * cephes::rgamma(a * k + b); - } - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_large_a(double a, double b, double x, int n) { - /* 2. Taylor series expansion in x=0, for large a. - * - * Phi(a, b, x) = sum_k x^k / k! / Gamma(a*k + b) - * - * Use Stirling's formula to find k=k_max, the maximum term. - * Then use n terms of Taylor series around k_max. - */ - int k_max = static_cast(std::pow(std::pow(a, -a) * x, 1.0 / (1 + a))); - - int nstart = k_max - n / 2; - if (nstart < 0) { - nstart = 0; - } - - double res = 0; - double lnx = std::log(x); - // For numerical stability, we factor out the maximum term exp(..) with k=k_max - // but only if it is larger than 0. - double max_exponent = std::fmax(0, k_max * lnx - cephes::lgam(k_max + 1) - cephes::lgam(a * k_max + b)); - for (int k = nstart; k < nstart + n; k++) { - res += std::exp(k * lnx - cephes::lgam(k + 1) - cephes::lgam(a * k + b) - max_exponent); - } - - if (!log_wb) { - res *= std::exp(max_exponent); - } else { - // logarithm of Wright's function - res = max_exponent + std::log(res); - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_small_a(double a, double b, double x, int order) { - /* 3. Taylor series in a=0 up to order 5, for tiny a and not too large x - * - * Phi(a, b, x) = exp(x)/Gamma(b) - * (1 - a*x * Psi(b) + a^2/2*x*(1+x) * (Psi(b)^2 - Psi'(b) - + ... ) - + O(a^6)) - * - * where Psi is the digamma function. - * - * Parameter order takes effect only when b > 1e-3 and 2 <= order <= 5, - * otherwise it defaults to 2, or if b <= 1e-3, to 5. The lower order is, - * the fewer polygamma functions have to be computed. - * - * Call: python _precompute/wright_bessel.py 1 - * - * For small b, i.e. b <= 1e-3, cancellation of poles of digamma(b)/Gamma(b) - * and polygamma needs to be carried out => series expansion in a=0 to order 5 - * and in b=0 to order 4. - * Call: python _precompute/wright_bessel.py 2 - */ - double A[6]; // coefficients of a^k (1, -x * Psi(b), ...) - double B[6]; // powers of b^k/k! or terms in polygamma functions - constexpr double C[5] = { // coefficients of a^k1 * b^k2 - 1.0000000000000000, // C[0] - 1.1544313298030657, // C[1] - -3.9352684291215233, // C[2] - -1.0080632408182857, // C[3] - 19.984633365874979, // C[4] - }; - double X[6] = { // polynomials in x; - 1, // X[0] - x, // X[1] - x * (x + 1), // X[2] - x * (x * (x + 3) + 1), // X[3] - x * (x * (x * (x + 6) + 7) + 1), // X[4] - x * (x * (x * (x * (x + 10) + 25) + 15) + 1), // X[5] - }; - double res; - - if (b <= 1E-3) { - /* Series expansion of both a and b up to order 5: - * M_PI = pi - * M_EG = Euler Gamma aka Euler Mascheroni constant - * M_Z3 = zeta(3) - * C[0] = 1 - * C[1] = 2*M_EG - * C[2] = 3*M_EG^2 - M_PI^2/2 - * C[3] = 4*M_EG^3 - 2*M_EG*M_PI^2 + 8*M_Z3 - * C[4] = 5*M_EG^4 - 5*M_EG^2*M_PI^2 + 40*M_EG*M_Z3 + M_PI^4/12 - */ - B[0] = 1.; - for (int k = 1; k < 5; k++) { - B[k] = b / k * B[k - 1]; - } - // Note that polevl assumes inverse ordering => A[5] = 0th term - A[5] = cephes::rgamma(b); - A[4] = X[1] * (C[0] + C[1] * b + C[2] * B[2] + C[3] * B[3] + C[4] * B[4]); - A[3] = X[2] / 2. * (C[1] + C[2] * b + C[3] * B[2] + C[4] * B[3]); - A[2] = X[3] / 6. * (C[2] + C[3] * b + C[4] * B[2]); - A[1] = X[4] / 24. * (C[3] + C[4] * b); - A[0] = X[5] / 120. * C[4]; - // res = exp(x) * (A[5] + A[4] * a + A[3] * a^2 + A[2] * a^3 + ...) - if (!log_wb) { - res = exp(x) * cephes::polevl(a, A, 5); - } else { - // logarithm of Wright's function - res = x + std::log(cephes::polevl(a, A, 5)); - } - } else { - /* Phi(a, b, x) = exp(x)/gamma(b) * sum(A[i] * X[i] * B[i], i=0..5) - * A[n] = a^n/n! - * But here, we repurpose A[n] = X[n] * B[n] / n! - * Note that polevl assumes inverse ordering => A[order] = 0th term */ - double dg = digamma(b); - // pg1 = polygamma(1, b) - double pg1 = cephes::zeta(2, b); - if (order <= 2) { - res = 1 + a * x * (-dg + 0.5 * a * (1 + x) * (dg * dg - pg1)); - } else { - if (order > 5) { - order = 5; - } - // pg2 = polygamma(2, b) - double pg2 = -2 * cephes::zeta(3, b); - B[0] = 1; - B[1] = -dg; - B[2] = dg * dg - pg1; - B[3] = (-dg * dg + 3 * pg1) * dg - pg2; - A[order] = 1; - A[order - 1] = X[1] * B[1]; - A[order - 2] = X[2] * B[2] / 2.; - A[order - 3] = X[3] * B[3] / 6.; - if (order >= 4) { - // double pg3 = polygamma(3, b) - double pg3 = 6 * cephes::zeta(4, b); - B[4] = ((dg * dg - 6 * pg1) * dg + 4 * pg2) * dg + 3 * pg1 * pg1 - pg3; - A[order - 4] = X[4] * B[4] / 24.; - if (order >= 5) { - // pg4 = polygamma(4, b) - double pg4 = -24 * cephes::zeta(5, b); - B[5] = - ((((-dg * dg + 10 * pg1) * dg - 10 * pg2) * dg - 15 * pg1 * pg1 + 5 * pg3) * dg + - 10 * pg1 * pg2 - pg4); - A[order - 5] = X[5] * B[5] / 120.; - } - } - res = cephes::polevl(a, A, order); - } - // res *= exp(x) * rgamma(b) - if (!log_wb) { - res *= exp_rgamma(x, b); - } else { - // logarithm of Wright's function - res = x - cephes::lgam(b) + std::log(res); - } - } - return res; - } - - template - XSF_HOST_DEVICE inline double wb_asymptotic(double a, double b, double x) { - /* 4. Asymptotic expansion for large x up to order 8 - * - * Phi(a, b, x) ~ Z^(1/2-b) * exp((1+a)/a * Z) * sum_k (-1)^k * C_k / Z^k - * - * with Z = (a*x)^(1/(1+a)). - * Call: python _precompute/wright_bessel.py 3 - */ - double A[15]; // powers of a - double B[17]; // powers of b - double Ap1[9]; // powers of (1+a) - double C[9]; // coefficients of asymptotic series a_k - - A[0] = 1.; - B[0] = 1.; - Ap1[0] = 1.; - for (int k = 1; k < 15; k++) { - A[k] = A[k - 1] * a; - } - for (int k = 1; k < 17; k++) { - B[k] = B[k - 1] * b; - } - for (int k = 1; k < 9; k++) { - Ap1[k] = Ap1[k - 1] * (1 + a); - } - - C[0] = 1. / std::sqrt(2. * M_PI * Ap1[1]); - - C[1] = C[0] / (24 * Ap1[1]); - C[1] *= (2 * a + 1) * (2 + a) - 12 * b * (1 + a - b); - - C[2] = C[0] / (1152 * Ap1[2]); - C[2] *= - (144 * B[4] - 96 * B[3] * (5 * a + 1) + 24 * B[2] * (20 * A[2] + 5 * a - 4) - - 24 * b * Ap1[1] * (6 * A[2] - 7 * a - 2) + (a + 2) * (2 * a + 1) * (2 * A[2] - 19 * a + 2)); - - C[3] = C[0] / (414720 * Ap1[3]); - C[3] *= - (8640 * B[6] - 8640 * B[5] * (7 * a - 1) + 10800 * B[4] * (14 * A[2] - 7 * a - 2) - - 1440 * B[3] * (112 * A[3] - 147 * A[2] - 63 * a + 8) + - 180 * B[2] * (364 * A[4] - 1288 * A[3] - 567 * A[2] + 392 * a + 76) - - 180 * b * Ap1[1] * (20 * A[4] - 516 * A[3] + 417 * A[2] + 172 * a - 12) - - (a + 2) * (2 * a + 1) * (556 * A[4] + 1628 * A[3] - 9093 * A[2] + 1628 * a + 556)); - - C[4] = C[0] / (39813120 * Ap1[4]); - C[4] *= - (103680 * B[8] - 414720 * B[7] * (3 * a - 1) + 725760 * B[6] * a * (8 * a - 7) - - 48384 * B[5] * (274 * A[3] - 489 * A[2] + 39 * a + 26) + - 30240 * B[4] * (500 * A[4] - 1740 * A[3] + 495 * A[2] + 340 * a - 12) - - 2880 * B[3] * (2588 * A[5] - 19780 * A[4] + 14453 * A[3] + 9697 * A[2] - 1892 * a - 404) + - 48 * B[2] * - (11488 * A[6] - 547836 * A[5] + 1007484 * A[4] + 593353 * A[3] - 411276 * A[2] - 114396 * a + 4288) + - 48 * b * Ap1[1] * - (7784 * A[6] + 48180 * A[5] - 491202 * A[4] + 336347 * A[3] + 163734 * A[2] - 28908 * a - 5560) - - (a + 2) * (2 * a + 1) * - (4568 * A[6] - 226668 * A[5] - 465702 * A[4] + 2013479 * A[3] - 465702 * A[2] - 226668 * a + 4568)); - - C[5] = C[0] / (6688604160. * Ap1[5]); - C[5] *= - (1741824 * B[10] - 2903040 * B[9] * (11 * a - 5) + 2177280 * B[8] * (110 * A[2] - 121 * a + 14) - - 580608 * B[7] * (1628 * A[3] - 3333 * A[2] + 1023 * a + 52) + - 169344 * B[6] * (12364 * A[4] - 43648 * A[3] + 26763 * A[2] + 1232 * a - 788) - - 24192 * B[5] * (104852 * A[5] - 646624 * A[4] + 721391 * A[3] - 16841 * A[2] - 74096 * a + 148) + - 2016 * B[4] * - (710248 * A[6] - 8878716 * A[5] + 17928834 * A[4] - 3333407 * A[3] - 4339566 * A[2] + 287364 * a + - 89128) - - 1344 * B[3] * - (87824 * A[7] - 7150220 * A[6] + 29202756 * A[5] - 15113527 * A[4] - 14223011 * A[3] + 3462492 * A[2] + - 1137092 * a - 18896) - - 84 * B[2] * - (1690480 * A[8] + 14139136 * A[7] - 232575464 * A[6] + 296712592 * A[5] + 215856619 * A[4] - - 152181392 * A[3] - 47718440 * A[2] + 5813632 * a + 943216) + - 84 * b * Ap1[1] * - (82224 * A[8] - 5628896 * A[7] - 26466520 * A[6] + 168779208 * A[5] - 104808005 * A[4] - - 56259736 * A[3] + 15879912 * A[2] + 4020640 * a - 63952) + - (a + 2) * (2 * a + 1) * - (2622064 * A[8] + 12598624 * A[7] - 167685080 * A[6] - 302008904 * A[5] + 1115235367. * A[4] - - 302008904 * A[3] - 167685080 * A[2] + 12598624 * a + 2622064)); - - C[6] = C[0] / (4815794995200. * Ap1[6]); - C[6] *= - (104509440 * B[12] - 209018880 * B[11] * (13 * a - 7) + 574801920 * B[10] * (52 * A[2] - 65 * a + 12) - - 63866880 * B[9] * (2834 * A[3] - 6279 * A[2] + 2769 * a - 134) + - 23950080 * B[8] * (27404 * A[4] - 98228 * A[3] + 78663 * A[2] - 10868 * a - 1012) - - 13685760 * B[7] * (105612 * A[5] - 599196 * A[4] + 791843 * A[3] - 224913 * A[2] - 27612 * a + 4540) + - 2661120 * B[6] * - (693680 * A[6] - 6473532 * A[5] + 13736424 * A[4] - 7047469 * A[3] - 723840 * A[2] + 471588 * a + 7376 - ) - - 2661120 * B[5] * - (432536 * A[7] - 7850804 * A[6] + 27531114 * A[5] - 24234457 * A[4] - 703001 * A[3] + 3633474 * A[2] - - 36244 * a - 45128) + - 166320 * B[4] * - (548912 * A[8] - 75660832 * A[7] + 502902712 * A[6] - 764807992 * A[5] + 91248287 * A[4] + - 217811464 * A[3] - 20365384 * A[2] - 9776416 * a + 37936) + - 10080 * B[3] * - (18759728 * A[9] + 165932208 * A[8] - 4710418440. * A[7] + 13686052536. * A[6] - 5456818809. * A[5] - - 6834514245. * A[4] + 1919299512. * A[3] + 752176152 * A[2] - 45661200 * a - 8616848) - - 360 * B[2] * - (32743360 * A[10] - 3381871792. * A[9] - 21488827776. * A[8] + 200389923864. * A[7] - - 198708005340. * A[6] - 171633799779. * A[5] + 123124874028. * A[4] + 40072774872. * A[3] - - 9137993280. * A[2] - 1895843248. * a + 18929728) - - 360 * b * Ap1[1] * - (57685408 * A[10] + 406929456 * A[9] - 6125375760. * A[8] - 27094918920. * A[7] + - 128752249410. * A[6] - 74866710561. * A[5] - 42917416470. * A[4] + 16256951352. * A[3] + - 4375268400. * A[2] - 316500688 * a - 47197152) + - (a + 2) * (2 * a + 1) * - (167898208 * A[10] - 22774946512. * A[9] - 88280004528. * A[8] + 611863976472. * A[7] + - 1041430242126. * A[6] - 3446851131657. * A[5] + 1041430242126. * A[4] + 611863976472. * A[3] - - 88280004528. * A[2] - 22774946512. * a + 167898208)); - - C[7] = C[0] / (115579079884800. * Ap1[7]); - C[7] *= - (179159040 * B[14] - 1254113280. * B[13] * (5 * a - 3) + 1358622720. * B[12] * (70 * A[2] - 95 * a + 22) - - 905748480 * B[11] * (904 * A[3] - 2109 * A[2] + 1119 * a - 112) + - 1245404160. * B[10] * (3532 * A[4] - 12824 * A[3] + 11829 * A[2] - 2824 * a + 44) - - 59304960 * B[9] * (256820 * A[5] - 1397680 * A[4] + 2025545 * A[3] - 869495 * A[2] + 52000 * a + 8788) + - 14826240 * B[8] * - (2274536 * A[6] - 18601572 * A[5] + 40698318 * A[4] - 28230079 * A[3] + 3916398 * A[2] + 832668 * a - - 65176) - - 59304960 * B[7] * - (760224 * A[7] - 9849164 * A[6] + 32495784 * A[5] - 34813869 * A[4] + 9175207 * A[3] + 1898688 * A[2] - - 469788 * a - 13184) + - 25945920 * B[6] * - (1167504 * A[8] - 28779840 * A[7] + 149752856 * A[6] - 246026112 * A[5] + 111944073 * A[4] + - 18341600 * A[3] - 12131496 * A[2] - 274368 * a + 102800) - - 157248 * B[5] * - (12341872 * A[9] - 3122991216. * A[8] + 29900054232. * A[7] - 78024816720. * A[6] + - 58914656739. * A[5] + 4637150811. * A[4] - 11523402480. * A[3] + 236218968 * A[2] + 337923216 * a + - 1592048) - - 28080 * B[4] * - (265154912 * A[10] + 2276098704. * A[9] - 105569461008. * A[8] + 496560666360. * A[7] - - 627891462858. * A[6] + 41935358025. * A[5] + 203913875814. * A[4] - 23984801544. * A[3] - - 13869306000. * A[2] + 372786832 * a + 103532640) + - 1440 * B[3] * - (310292864 * A[11] - 55169117872. * A[10] - 358957020112. * A[9] + 5714152556088. * A[8] - - 13241597459352. * A[7] + 4220720097141. * A[6] + 6845418090249. * A[5] - 2129559215808. * A[4] - - 909225098472. * A[3] + 107518582576. * A[2] + 25619444368. * a - 113832704) + - 12 * B[2] * - (135319651136. * A[12] + 1119107842176. * A[11] - 22193518174320. * A[10] - 133421793595520. * A[9] + - 860103051087996. * A[8] - 703353374803080. * A[7] - 704240127687381. * A[6] + - 513111704637960. * A[5] + 166909061348316. * A[4] - 57671564069120. * A[3] - 12453426246000. * A[2] + - 695901207936. * a + 93786157376.) - - 12 * b * Ap1[1] * - (4365353408. * A[12] - 720248637504. * A[11] - 4222331152560. * A[10] + 29413934270560. * A[9] + - 132123980710980. * A[8] - 511247376962820. * A[7] + 283403639131779. * A[6] + - 170415792320940. * A[5] - 79274388426588. * A[4] - 21009953050400. * A[3] + 3284035340880. * A[2] + - 589294339776. * a - 3693760576.) - - (a + 2) * (2 * a + 1) * - (34221025984. * A[12] + 226022948160. * A[11] - 5067505612464. * A[10] - 18868361443936. * A[9] + - 86215425028308. * A[8] + 143500920544692. * A[7] - 437682618704613. * A[6] + 143500920544692. * A[5] + - 86215425028308. * A[4] - 18868361443936. * A[3] - 5067505612464. * A[2] + 226022948160. * a + - 34221025984.)); - - C[8] = C[0] / (22191183337881600. * Ap1[8]); - C[8] *= - (2149908480. * B[16] - 5733089280. * B[15] * (17 * a - 11) + - 7166361600. * B[14] * (272 * A[2] - 391 * a + 104) - - 3344302080. * B[13] * (6766 * A[3] - 16371 * A[2] + 9741 * a - 1306) + - 1811496960. * B[12] * (93092 * A[4] - 341564 * A[3] + 344199 * A[2] - 104924 * a + 6308) - - 517570560 * B[11] * - (1626220 * A[5] - 8641508 * A[4] + 13274773 * A[3] - 6952303 * A[2] + 1007420 * a + 5564) + - 284663808 * B[10] * - (9979136 * A[6] - 75766892 * A[5] + 169256148 * A[4] - 136824959 * A[3] + 35714348 * A[2] - - 463692 * a - 293664) - - 1423319040. * B[9] * - (4466648 * A[7] - 49231116 * A[6] + 157507414 * A[5] - 187114257 * A[4] + 78372295 * A[3] - - 4470082 * A[2] - 1913996 * a + 82424) + - 266872320 * B[8] * - (33133136 * A[8] - 564264544 * A[7] + 2618606424. * A[6] - 4491310104. * A[5] + 2853943765. * A[4] - - 374694552 * A[3] - 135365288 * A[2] + 17623968 * a + 696912) - - 2156544 * B[7] * - (2914256144. * A[9] - 93491712432. * A[8] + 664876176984. * A[7] - 1661362937880. * A[6] + - 1563719627313. * A[5] - 382840842843. * A[4] - 115399415640. * A[3] + 34565562936. * A[2] + - 1609337232. * a - 217321904) + - 179712 * B[6] * - (1266018560. * A[10] - 789261834512. * A[9] + 10186841596896. * A[8] - 38877799073352. * A[7] + - 54334425968952. * A[6] - 22529574889533. * A[5] - 5132942328000. * A[4] + 3438377465592. * A[3] + - 84287641248. * A[2] - 72493479440. * a - 807415936) + - 13824 * B[5] * - (156356794976. * A[11] + 1180898077328. * A[10] - 90615270907936. * A[9] + 609258947056248. * A[8] - - 1312655191366722. * A[7] + 885900509321745. * A[6] + 112162151855265. * A[5] - - 212803071513258. * A[4] + 6805217831352. * A[3] + 10051742651296. * A[2] - 55035924848. * a - - 52946379296.) - - 576 * B[4] * - (143943926464. * A[12] - 60115486481856. * A[11] - 376366989757200. * A[10] + - 9534223075576160. * A[9] - 35603777465262396. * A[8] + 39375990156664980. * A[7] - - 868175004137259. * A[6] - 14279180718355020. * A[5] + 1985747535239364. * A[4] + - 1264001337603680. * A[3] - 75972792514320. * A[2] - 23855850572736. * a - 4996648256.) - - 384 * B[3] * - (2038525473856. * A[13] + 16057322146112. * A[12] - 502133360559024. * A[11] - - 2985686417468080. * A[10] + 32418922182093292. * A[9] - 63665380623022452. * A[8] + - 16481208821092575. * A[7] + 34161547357596099. * A[6] - 11490298497454932. * A[5] - - 5117272758337156. * A[4] + 933703210750480. * A[3] + 234855186762000. * A[2] - 7860524600000. * a - - 1226607567040.) + - 96 * B[2] * - (324439754752. * A[14] - 77231415197120. * A[13] - 539102931841856. * A[12] + - 4618258299956336. * A[11] + 28588485529469792. * A[10] - 141383982651179428. * A[9] + - 98783147840417772. * A[8] + 112831723492305801. * A[7] - 83329761150975036. * A[6] - - 26553582937192900. * A[5] + 12469117738765952. * A[4] + 2587165396642160. * A[3] - - 340406368038080. * A[2] - 53659641606080. * a + 219671272960.) + - 96 * b * Ap1[1] * - (1026630779520. * A[14] + 8781958472768. * A[13] - 210659786204384. * A[12] - - 1222283505284208. * A[11] + 5064251967491416. * A[10] + 24013052207628140. * A[9] - - 79710880160087370. * A[8] + 42596558293213227. * A[7] + 26570293386695790. * A[6] - - 14407831324576884. * A[5] - 3617322833922440. * A[4] + 950664948554384. * A[3] + - 172358006894496. * A[2] - 7430887938496. * a - 889746675584.) - - (a + 2) * (2 * a + 1) * - (573840801152. * A[14] - 156998277198784. * A[13] - 898376974770592. * A[12] + - 8622589006459984. * A[11] + 32874204024803560. * A[10] - 111492707520083828. * A[9] - - 184768503480287646. * A[8] + 528612016938984183. * A[7] - 184768503480287646. * A[6] - - 111492707520083828. * A[5] + 32874204024803560. * A[4] + 8622589006459984. * A[3] - - 898376974770592. * A[2] - 156998277198784. * a + 573840801152.)); - - double Z = std::pow(a * x, 1 / Ap1[1]); - double Zp = 1.; - double res = C[0]; - for (int k = 1; k < 9; k++) { - Zp /= Z; - res += (k % 2 == 0 ? 1 : -1) * C[k] * Zp; - } - if (!log_wb) { - res *= std::pow(Z, 0.5 - b) * std::exp(Ap1[1] / a * Z); - } else { - // logarithm of Wright's function - res = std::log(Z) * (0.5 - b) + Ap1[1] / a * Z + std::log(res); - } - return res; - } - - XSF_HOST_DEVICE inline double wb_Kmod(double exp_term, double eps, double a, double b, double x, double r) { - /* Compute integrand Kmod(eps, a, b, x, r) for Gauss-Laguerre quadrature. - * - * K(a, b, x, r+eps) = exp(-r-eps) * Kmod(eps, a, b, x, r) - * - * Kmod(eps, a, b, x, r) = exp(x * (r+eps)^(-a) * cos(pi*a)) * (r+eps)^(-b) - * * sin(x * (r+eps)^(-a) * sin(pi*a) + pi * b) - * - * Note that we additionally factor out exp(exp_term) which helps with large - * terms in the exponent of exp(...) - */ - double x_r_a = x * std::pow(r + eps, -a); - return std::exp(x_r_a * cephes::cospi(a) + exp_term) * std::pow(r + eps, -b) * - std::sin(x_r_a * cephes::sinpi(a) + M_PI * b); - } - - XSF_HOST_DEVICE inline double wb_P(double exp_term, double eps, double a, double b, double x, double phi) { - /* Compute integrand P for Gauss-Legendre quadrature. - * - * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) - * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) - * + (1-b)*phi) - * - * Note that we additionally factor out exp(exp_term) which helps with large - * terms in the exponent of exp(...) - */ - double x_eps_a = x * std::pow(eps, -a); - return std::exp(eps * std::cos(phi) + x_eps_a * std::cos(a * phi) + exp_term) * - std::cos(eps * std::sin(phi) - x_eps_a * std::sin(a * phi) + (1 - b) * phi); - } - - /* roots of laguerre polynomial of order 50 - * scipy.special.roots_laguerre(50)[0] or - * sympy.integrals.quadrature.import gauss_laguerre(50, 16)[0] */ - constexpr double wb_x_laguerre[] = { - 0.02863051833937908, 0.1508829356769337, 0.3709487815348964, 0.6890906998810479, 1.105625023539913, - 1.620961751102501, 2.23561037591518, 2.950183366641835, 3.765399774405782, 4.682089387559285, - 5.70119757478489, 6.823790909794551, 8.051063669390792, 9.384345308258407, 10.82510903154915, - 12.37498160875746, 14.03575459982991, 15.80939719784467, 17.69807093335025, 19.70414653546156, - 21.83022330657825, 24.0791514444115, 26.45405784125298, 28.95837601193738, 31.59588095662286, - 34.37072996309045, 37.28751061055049, 40.35129757358607, 43.56772026999502, 46.94304399160304, - 50.48426796312992, 54.19924488016862, 58.09682801724853, 62.18705417568891, 66.48137387844482, - 70.99294482661949, 75.73701154772731, 80.73140480247769, 85.99721113646323, 91.55969041253388, - 97.44956561485056, 103.7048912366923, 110.3738588076403, 117.5191982031112, 125.2254701334734, - 133.6120279227287, 142.8583254892541, 153.2603719726036, 165.3856433166825, 180.6983437092145 - }; - /* weights for laguerre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_laguerre(50, 16)[1] */ - constexpr double wb_w_laguerre[] = { - 0.07140472613518988, 0.1471486069645884, 0.1856716275748313, 0.1843853825273539, - 0.1542011686063556, 0.1116853699022688, 0.07105288549019586, 0.04002027691150833, - 0.02005062308007171, 0.008960851203646281, 0.00357811241531566, 0.00127761715678905, - 0.0004080302449837189, 0.0001165288322309724, 2.974170493694165e-5, 6.777842526542028e-6, - 1.37747950317136e-6, 2.492886181720092e-7, 4.010354350427827e-8, 5.723331748141425e-9, - 7.229434249182665e-10, 8.061710142281779e-11, 7.913393099943723e-12, 6.81573661767678e-13, - 5.13242671658949e-14, 3.365624762437814e-15, 1.913476326965035e-16, 9.385589781827253e-18, - 3.950069964503411e-19, 1.417749517827512e-20, 4.309970276292175e-22, 1.101257519845548e-23, - 2.344617755608987e-25, 4.11854415463823e-27, 5.902246763596448e-29, 6.812008916553065e-31, - 6.237449498812102e-33, 4.452440579683377e-35, 2.426862352250487e-37, 9.852971481049686e-40, - 2.891078872318428e-42, 5.906162708112361e-45, 8.01287459750397e-48, 6.789575424396417e-51, - 3.308173010849252e-54, 8.250964876440456e-58, 8.848728128298018e-62, 3.064894889844417e-66, - 1.988708229330752e-71, 6.049567152238783e-78 - }; - /* roots of legendre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_legendre(50, 16)[0] */ - constexpr double wb_x_legendre[] = { - -0.998866404420071, -0.9940319694320907, -0.9853540840480059, -0.9728643851066921, -0.9566109552428079, - -0.9366566189448779, -0.9130785566557919, -0.885967979523613, -0.8554297694299461, -0.8215820708593359, - -0.7845558329003993, -0.7444943022260685, -0.7015524687068223, -0.6558964656854394, -0.6077029271849502, - -0.5571583045146501, -0.5044581449074642, -0.4498063349740388, -0.3934143118975651, -0.3355002454194374, - -0.276288193779532, -0.2160072368760418, -0.1548905899981459, -0.09317470156008614, -0.03109833832718888, - 0.03109833832718888, 0.09317470156008614, 0.1548905899981459, 0.2160072368760418, 0.276288193779532, - 0.3355002454194374, 0.3934143118975651, 0.4498063349740388, 0.5044581449074642, 0.5571583045146501, - 0.6077029271849502, 0.6558964656854394, 0.7015524687068223, 0.7444943022260685, 0.7845558329003993, - 0.8215820708593359, 0.8554297694299461, 0.885967979523613, 0.9130785566557919, 0.9366566189448779, - 0.9566109552428079, 0.9728643851066921, 0.9853540840480059, 0.9940319694320907, 0.998866404420071 - }; - /* weights for legendre polynomial of order 50 - * sympy.integrals.quadrature.import gauss_legendre(50, 16)[1] */ - constexpr double wb_w_legendre[] = { - 0.002908622553155141, 0.006759799195745401, 0.01059054838365097, 0.01438082276148557, 0.01811556071348939, - 0.02178024317012479, 0.02536067357001239, 0.0288429935805352, 0.03221372822357802, 0.03545983561514615, - 0.03856875661258768, 0.0415284630901477, 0.04432750433880328, 0.04695505130394843, 0.04940093844946632, - 0.05165570306958114, 0.05371062188899625, 0.05555774480621252, 0.05718992564772838, 0.05860084981322245, - 0.05978505870426546, 0.06073797084177022, 0.06145589959031666, 0.06193606742068324, 0.06217661665534726, - 0.06217661665534726, 0.06193606742068324, 0.06145589959031666, 0.06073797084177022, 0.05978505870426546, - 0.05860084981322245, 0.05718992564772838, 0.05555774480621252, 0.05371062188899625, 0.05165570306958114, - 0.04940093844946632, 0.04695505130394843, 0.04432750433880328, 0.0415284630901477, 0.03856875661258768, - 0.03545983561514615, 0.03221372822357802, 0.0288429935805352, 0.02536067357001239, 0.02178024317012479, - 0.01811556071348939, 0.01438082276148557, 0.01059054838365097, 0.006759799195745401, 0.002908622553155141 - }; - /* Fitted parameters for optimal choice of eps - * Call: python _precompute/wright_bessel.py 4 */ - constexpr double wb_A[] = {0.41037, 0.30833, 6.9952, 18.382, -2.8566, 2.1122}; - - template - XSF_HOST_DEVICE inline double wright_bessel_integral(double a, double b, double x) { - /* 5. Integral representation - * - * K(a, b, x, r) = exp(-r + x * r^(-a) * cos(pi*a)) * r^(-b) - * * sin(x * r^(-a) * sin(pi*a) + pi * b) - * P(eps, a, b, x, phi) = exp(eps * cos(phi) + x * eps^(-a) * cos(a*phi)) - * * cos(eps * sin(phi) - x * eps^(-a) * sin(a*phi) - * + (1-b)*phi) - * - * Phi(a, b, x) = 1/pi * int_eps^inf K(a, b, x, r) * dr - * + eps^(1-b)/pi * int_0^pi P(eps, a, b, x, phi) * dphi - * - * for any eps > 0. - * - * Note that P has a misprint in Luchko (2008) Eq. 9, the cos(phi(beta-1)) at - * the end of the first line should be removed and the −sin(phi(beta−1)) at - * the end of the second line should read +(1-b)*phi. - * This integral representation introduced the free parameter eps (from the - * radius of complex contour integration). We try to choose eps such that - * the integrand behaves smoothly. Note that this is quite diffrent from how - * Luchko (2008) deals with eps: he is either looking for the limit eps -> 0 - * or he sets (silently) eps=1. But having the freedom to set eps is much more - * powerful for numerical evaluation. - * - * As K has a leading exp(-r), we factor this out and apply Gauss-Laguerre - * quadrature rule: - * - * int_0^inf K(a, b, x, r+eps) dr = exp(-eps) int_0^inf exp(-r) Kmod(.., r) dr - * - * Note the shift r -> r+eps to have integation from 0 to infinity. - * The integral over P is done via a Gauss-Legendre quadrature rule. - * - * Note: Hardest argument range is large z, large b and small eps. - */ - - /* We use the free choice of eps to make the integral better behaved. - * 1. Concern is oscillatory behaviour of P. Therefore, we'd like to - * make the change in the argument of cosine small, i.e. make arc length - * int_0^phi sqrt(1 + f'(phi)^2) dphi small, with - * f(phi) = eps * sin(phi) - x * eps^(-a) * sin(a*phi) + (1-b)*phi - * Proxy, make |f'(phi)| small. - * 2. Concern is int_0 K ~ int_0 (r+eps)^(-b) .. dr - * This is difficult as r -> 0 for large b. It behaves better for larger - * values of eps. - */ - - // Minimize oscillatory behavoir of P - double eps = - (wb_A[0] * b * std::exp(-0.5 * a) + - std::exp( - wb_A[1] + 1 / (1 + a) * std::log(x) - wb_A[2] * std::exp(-wb_A[3] * a) + - wb_A[4] / (1 + std::exp(wb_A[5] * a)) - )); - - if (a >= 4 && x >= 100) { - eps += 1; // This part is hard to fit - } - - // Large b - if (b >= 8) { - /* Make P small compared to K by setting eps large enough. - * int K ~ exp(-eps) and int P ~ eps^(1-b) */ - eps = std::fmax(eps, std::pow(b, -b / (1. - b)) + 0.1 * b); - } - - // safeguard, higher better for larger a, lower better for tiny a. - eps = std::fmin(eps, 150.); - eps = std::fmax(eps, 3.); // 3 seems to be a pretty good choice in general. - - // We factor out exp(-exp_term) from wb_Kmod and wb_P to avoid overflow of - // exp(..). - double exp_term = 0; - // From the exponent of K: - double r = wb_x_laguerre[50-1]; // largest value of x used in wb_Kmod - double x_r_a = x * std::pow(r + eps, -a); - exp_term = std::fmax(exp_term, x_r_a * cephes::cospi(a)); - // From the exponent of P: - double x_eps_a = x * std::pow(eps, -a); - // phi = 0 => cos(phi) = cos(a * phi) = 1 - exp_term = std::fmax(exp_term, eps + x_eps_a); - // phi = pi => cos(phi) = -1 - exp_term = std::fmax(exp_term, -eps + x_eps_a * cephes::cospi(a)); - - double res1 = 0; - double res2 = 0; - - double y; - for (int k = 0; k < 50; k++) { - res1 += wb_w_laguerre[k] * wb_Kmod(-exp_term, eps, a, b, x, wb_x_laguerre[k]); - // y = (b-a)*(x+1)/2.0 + a for integration from a=0 to b=pi - y = M_PI * (wb_x_legendre[k] + 1) / 2.0; - res2 += wb_w_legendre[k] * wb_P(-exp_term, eps, a, b, x, y); - } - res1 *= std::exp(-eps); - // (b-a)/2.0 * np.sum(w*func(y, *args), axis=-1) - res2 *= M_PI / 2.0; - res2 *= std::pow(eps, 1 - b); - - if (!log_wb) { - // Remember the factored out exp_term from wb_Kmod and wb_P - return std::exp(exp_term) / M_PI * (res1 + res2); - } else { - // logarithm of Wright's function - return exp_term + std::log((res1 + res2) / M_PI); - } - } -} // namespace detail - -template -XSF_HOST_DEVICE inline double wright_bessel_t(double a, double b, double x) { - /* Compute Wright's generalized Bessel function for scalar arguments. - * - * According to [1], it is an entire function defined as - * - * .. math:: \Phi(a, b; x) = \sum_{k=0}^\infty \frac{x^k}{k! \Gamma(a k + b)} - * - * So far, only non-negative values of rho=a, beta=b and z=x are implemented. - * There are 5 different approaches depending on the ranges of the arguments: - * - * 1. Taylor series expansion in x=0 [1], for x <= 1. - * Involves gamma funtions in each term. - * 2. Taylor series expansion in x=0 [2], for large a. - * 3. Taylor series in a=0, for tiny a and not too large x. - * 4. Asymptotic expansion for large x [3, 4]. - * Suitable for large x while still small a and b. - * 5. Integral representation [5], in principle for all arguments. - * - * References - * ---------- - * [1] https://dlmf.nist.gov/10.46.E1 - * [2] P. K. Dunn, G. K. Smyth (2005), Series evaluation of Tweedie exponential - * dispersion model densities. Statistics and Computing 15 (2005): 267-280. - * [3] E. M. Wright (1935), The asymptotic expansion of the generalized Bessel - * function. Proc. London Math. Soc. (2) 38, pp. 257-270. - * https://doi.org/10.1112/plms/s2-38.1.257 - * [4] R. B. Paris (2017), The asymptotics of the generalised Bessel function, - * Mathematica Aeterna, Vol. 7, 2017, no. 4, 381 - 406, - * https://arxiv.org/abs/1711.03006 - * [5] Y. F. Luchko (2008), Algorithms for Evaluation of the Wright Function for - * the Real Arguments' Values, Fractional Calculus and Applied Analysis 11(1) - * http://sci-gems.math.bas.bg/jspui/bitstream/10525/1298/1/fcaa-vol11-num1-2008-57p-75p.pdf - */ - if (std::isnan(a) || std::isnan(b) || std::isnan(x)) { - return std::numeric_limits::quiet_NaN(); - } - if (a < 0 || b < 0 || x < 0) { - set_error("wright_bessel", SF_ERROR_DOMAIN, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (std::isinf(x)) { - if (std::isinf(a) || std::isinf(b)) { - return std::numeric_limits::quiet_NaN(); - } - return std::numeric_limits::infinity(); - } - if (std::isinf(a) || std::isinf(b)) { - return std::numeric_limits::quiet_NaN(); // or 0 - } - if (a >= detail::rgamma_zero || b >= detail::rgamma_zero) { - set_error("wright_bessel", SF_ERROR_OVERFLOW, NULL); - return std::numeric_limits::quiet_NaN(); - } - if (x == 0) { - // return rgamma(b) - if (!log_wb) { - return cephes::rgamma(b); - } else { - // logarithm of Wright's function - return -cephes::lgam(b); - } - } - if (a == 0) { - // return exp(x) * rgamma(b) - if (!log_wb) { - return detail::exp_rgamma(x, b); - } else { - // logarithm of Wright's function - return x - cephes::lgam(b); - } - } - - constexpr double exp_inf = 709.78271289338403; - int order; - if ((a <= 1e-3 && b <= 50 && x <= 9) || (a <= 1e-4 && b <= 70 && x <= 100) || - (a <= 1e-5 && b <= 170 && (x < exp_inf || (log_wb && x <= 1e3)))) { - /* Taylor Series expansion in a=0 to order=order => precision <= 1e-11 - * If beta is also small => precision <= 1e-11. - * max order = 5 */ - if (a <= 1e-5) { - if (x <= 1) { - order = 2; - } else if (x <= 10) { - order = 3; - } else if (x <= 100) { - order = 4; - } else { // x < exp_inf - order = 5; - } - } else if (a <= 1e-4) { - if (x <= 1e-2) { - order = 2; - } else if (x <= 1) { - order = 3; - } else if (x <= 10) { - order = 4; - } else { // x <= 100 - order = 5; - } - } else { // a <= 1e-3 - if (x <= 1e-5) { - order = 2; - } else if (x <= 1e-1) { - order = 3; - } else if (x <= 1) { - order = 4; - } else { // x <= 9 - order = 5; - } - } - - return detail::wb_small_a(a, b, x, order); - } - - if (x <= 1) { - // 18 term Taylor Series => error mostly smaller 5e-14 - double res = detail::wb_series(a, b, x, 0, 18); - if (log_wb) res = std::log(res); - return res; - } - if (x <= 2) { - // 20 term Taylor Series => error mostly smaller 1e-12 to 1e-13 - double res = detail::wb_series(a, b, x, 0, 20); - if (log_wb) res = std::log(res); - return res; - } - if (a >= 5) { - /* Taylor series around the approximate maximum term. - * Set number of terms=order. */ - if (a >= 10) { - if (x <= 1e11) { - order = 6; - } else { - order = static_cast(std::fmin(std::log10(x) - 5 + b / 10, 30)); - } - } else { - if (x <= 1e4) { - order = 6; - } else if (x <= 1e8) { - order = static_cast(2 * std::log10(x)); - } else if (x <= 1e10) { - order = static_cast(4 * std::log10(x) - 16); - } else { - order = static_cast(std::fmin(6 * std::log10(x) - 36, 100)); - } - } - return detail::wb_large_a(a, b, x, order); - } - if (std::pow(a * x, 1 / (1. + a)) >= 14 + b * b / (2 * (1 + a))) { - /* Asymptotic expansion in Z = (a*x)^(1/(1+a)) up to 8th term 1/Z^8. - * For 1/Z^k, the highest term in b is b^(2*k) * a0 / (2^k k! (1+a)^k). - * As a0 is a common factor to all orders, this explains a bit the - * domain of good convergence set above. - * => precision ~ 1e-11 but can go down to ~1e-8 or 1e-7 - * Note: We ensured a <= 5 as this is a bad approximation for large a. */ - return detail::wb_asymptotic(a, b, x); - } - if (0.5 <= a && a <= 1.8 && 100 <= b && 1e5 <= x) { - // This is a very hard domain. This condition is placed after wb_asymptotic. - // TODO: Explore ways to cover this domain. - return std::numeric_limits::quiet_NaN(); - } - return detail::wright_bessel_integral(a, b, x); -} - - -XSF_HOST_DEVICE inline double wright_bessel(double a, double b, double x) { - return wright_bessel_t(a, b, x); -} - -XSF_HOST_DEVICE inline float wright_bessel(float a, float b, float x) { - return wright_bessel(static_cast(a), static_cast(b), static_cast(x)); -} - -XSF_HOST_DEVICE inline double log_wright_bessel(double a, double b, double x) { - return wright_bessel_t(a, b, x); -} - -XSF_HOST_DEVICE inline float log_wright_bessel(float a, float b, float x) { - return log_wright_bessel(static_cast(a), static_cast(b), static_cast(x)); -} - -} // namespace xsf diff --git a/scipy/special/xsf/zeta.h b/scipy/special/xsf/zeta.h deleted file mode 100644 index ff52f6e7c83a..000000000000 --- a/scipy/special/xsf/zeta.h +++ /dev/null @@ -1,406 +0,0 @@ -/* Complex riemann-zeta function implementation based on Python implementation - * written by Matt Haberland (@mdhaber) in: - * https://colab.research.google.com/drive/1zMDSAJlXCLRqMMtJ0e9nDGjQ8iZCnmn5?usp=sharing - */ - -#pragma once - -#include "config.h" -#include "error.h" -#include "gamma.h" -#include "trig.h" - -#include "cephes/const.h" -#include "cephes/zeta.h" -#include "cephes/zetac.h" - -namespace xsf { - -namespace detail { - - /* Log of absolute value of expansion coefficients for Euler-Maclaurin - * summation formula. log(|B2k / (2k)!|) - * - * See https://en.wikipedia.org/wiki/Riemann_zeta_function#Numerical_algorithms - * - * Generated with the script - * - * import numpy as np - * from mpmath import mp - * - * mp.dps = 10000 - * - * results = [] - * for k in range(51): - * results.append( - * float( - * mp.log(abs(mp.bernoulli(2*k)/(mp.factorial(2*k)))) - * ) - * ) - */ - constexpr double zeta_em_log_abs_coeff_lookup[] = { - 0.0, - -2.4849066497880004, - -6.579251212010101, - -10.31692083029347, - -14.005800284407405, - -17.68462940266784, - -21.361131560073222, - -25.037070502911423, - -28.712870599846948, - -32.388636197522295, - -36.06439319366539, - -39.740148041995184, - -43.41590235365616, - -47.091656531181485, - -50.76741067517639, - -54.44316481078909, - -58.11891894430628, - -61.79467307729959, - -65.47042721016194, - -69.14618134299154, - -72.82193547581296, - -76.49768960863234, - -80.1734437414512, - -83.84919787426993, - -87.52495200708863, - -91.20070613990733, - -94.87646027272602, - -98.55221440554472, - -102.2279685383634, - -105.9037226711821, - -109.57947680400078, - -113.25523093681947, - -116.93098506963817, - -120.60673920245685, - -124.28249333527555, - -127.95824746809424, - -131.63400160091294, - -135.30975573373163, - -138.9855098665503, - -142.661263999369, - -146.3370181321877, - -150.0127722650064, - -153.6885263978251, - -157.36428053064375, - -161.04003466346245, - -164.71578879628115, - -168.39154292909984, - -172.06729706191854, - -175.74305119473723, - -179.4188053275559, - -183.0945594603746 - }; - - // Complex log of expansion coefficients for Euler-Maclaurin summation formula. - XSF_HOST_DEVICE inline std::complex zeta_em_log_coeff(std::size_t n) { - std::complex J(0.0, 1.0); - std::complex result; - if (n < 50) { - result = zeta_em_log_abs_coeff_lookup[n]; - } else { - /* Asymptotic formula - * Uses https://dlmf.nist.gov/24.11#E1 to approximate B_{2n} and - * Stirling's approximation for (2n)!. - */ - result = std::log(2.0) - 2.0*n*std::log(2*M_PI); - } - if (n % 2 == 0) { - /* B_{2n}/(2n)! is negative for even n. This contributes a term - * pi*i when taking the log. */ - result += M_PI * J; - } - return result; - } - - /* Compute riemann_zeta for complex input z using the Euler-Maclaurin formula. - * Computation of individual terms in expansion are logarithmized to avoid - * overflow. TODO: only logarithmize when necessary. */ - XSF_HOST_DEVICE inline std::complex zeta_euler_maclaurin(std::complex z) { - if (z == 1.0) { - /* Return NaN at pole since value depends on how z approaches 1.0. */ - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - std::size_t n = static_cast(std::max(std::abs(z.imag()) / 4.0, 50.0)); - std::size_t m = n; - std::complex result = 0.0; - for (std::size_t i = 1; i < n; i++) { - std::complex term = std::pow(static_cast(i), -z); - result += term; - // When z.real() > 1, series converges and we can consider early termination - if (z.real() > 1 && std::abs(term) / std::abs(result) <= std::numeric_limits::epsilon()) { - return result; - } - } - double N = static_cast(n); - std::complex b = std::pow(n, -z); - result += b * (0.5 + N / (z - 1.0)); - /* The terms of the Euler-Maclaurin - * expansion below are T(k, n) = B2k/(2k)! * n^(1 - z - 2k) * z(z+1)...(z+2k-2). - * We work with logarithms to avoid overflow in all cases at the expense of - * some accuracy. At the start of iteration k: - * log_poch will equal log(z(z+1)...(z+2k-2)) - * log_factor will equal log(n^(1 - z - 2k)) - * These are updated one extra time after the loop completes for use in the - * Euler-Maclaurin error estimate. - */ - std::complex log_poch = std::log(z); - std::complex log_factor = -(z + 1.0) * std::log(N); - for (std::size_t k = 1; k <= m; k++) { - std::complex term = std::exp(zeta_em_log_coeff(k) + log_factor + log_poch); - result += term; - if (std::abs(term)/std::abs(result) <= std::numeric_limits::epsilon()) { - return result; - } - log_poch += std::log(z + static_cast(2*k - 1)) + std::log(z + static_cast(2*k)); - log_factor -= 2*std::log(N); - } - /* Euler-maclaurin absolute error estimate. - * The error is bounded above by |(z + 2m + 1)/(z.real + 2m + 1) * T(m+1, n)| - * See https://en.wikipedia.org/wiki/Riemann_zeta_function#Numerical_algorithms - */ - double error; - error = std::abs(std::exp(zeta_em_log_coeff(m + 1) + log_factor + log_poch)); - error *= std::abs((z + 2.0*m + 1.0)/(z.real() + 2.0*m + 1.0)); - // convert to relative error estimate - error /= std::abs(result); - if (error > 1e-8) { - if (error > 1e-1) { - /* If error estimate predicts we don't even get 1 digit of precision, return NaN - * and signal no result */ - set_error("zeta", SF_ERROR_NO_RESULT, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - // Signal reduced precision. - set_error("zeta", SF_ERROR_LOSS, NULL); - } - return result; - } - - /* Lookup table of coefficients for Algorithm 2 from Borwein 1995 - * Borwein, Peter B.. “An efficient algorithm for the Riemann zeta function.” (1995). - * - * Stores coefficients as dk / dn, where dn is the final coefficient. - * - * Generated with the Python script: - * - * import numpy as np - * import math - * from mpmath import mp - * - * mp.dps = 1000 - * n = 50 - * - * coeffs = [] - * S = mp.zero - * for i in range(n + 1): - * num = math.factorial(n + i - 1) * 4**i - * den = math.factorial(n - i) * math.factorial(2*i) - * S += mp.mpf(num) / mp.mpf(den) - * coeffs.append(S*n) - * - * dn = coeffs[-1] - * coeffs = [float(dk/dn) for dk in coeffs[:-1]] - * coeffs = np.asarray(coeffs) - */ - constexpr double zeta_borwein_coeff[] = { - 1.0555078361382878e-38, - 5.278594688527578e-35, - 4.4014687322044963e-32, - 1.467453546497519e-29, - 2.617862196688831e-27, - 2.900097799958025e-25, - 2.184440361492933e-23, - 1.1890977312913296e-21, - 4.8871396166872276e-20, - 1.5672253698802734e-18, - 4.022931234264572e-17, - 8.435973533351745e-16, - 1.469296379914116e-14, - 2.1548747054571902e-13, - 2.6919530537535124e-12, - 2.8925409162768484e-11, - 2.6957505693699856e-10, - 2.194772239130839e-09, - 1.57078229370057e-08, - 9.936187220749133e-08, - 5.581721578217702e-07, - 2.796271112037765e-06, - 1.253886254275813e-05, - 5.049261002939051e-05, - 0.00018312884459703666, - 0.0005997690328552426, - 0.0017780501082460968, - 0.004781802283665968, - 0.011690432287131671, - 0.026034302929535964, - 0.052922982472754856, - 0.09842471411648858, - 0.16789610796540344, - 0.26350429194768626, - 0.3819442810600665, - 0.5137731385068898, - 0.645292538541866, - 0.7625449322050362, - 0.8556063057019102, - 0.921056062886525, - 0.9616100580028147, - 0.9835905431606953, - 0.9939187229336753, - 0.9980782525166648, - 0.9994930141459889, - 0.999891478844585, - 0.9999819091990203, - 0.9999977981288319, - 0.9999998260580315, - 0.9999999933099243 - }; - - /* Compute riemann_zeta for complex input z using Algorithm 2 from Borwein 1995. */ - XSF_HOST_DEVICE inline std::complex zeta_borwein(std::complex z) { - std::complex result = 0.0; - // Sum in reverse order because smaller terms come later. - for (int k = 49; k >= 0; k--) { - double sign = std::pow(-1.0, k); - std::complex den = std::pow(k + 1, z); - std::complex term = sign * (zeta_borwein_coeff[k] - 1.0) / den; - result += term; - } - return result * -1.0/(1.0 - std::pow(2.0, 1.0 - z)); - } - - /* Compute riemann zeta for complex z and real part >= 0 */ - XSF_HOST_DEVICE inline std::complex zeta_right_halfplane(std::complex z) { - if (z == 1.0) { - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - /* Cutoff for using Euler-MacLaurin chosen based on cursory empirical search. - * TODO: Choose cutoffs in a more principled way. */ - if (z.real() < 50.0 && std::abs(z.imag()) > 50.0) { - if (z.real() >= 0.0 && z.real() < 2.5 && std::abs(z.imag()) > 1e9) { - /* Euler-MacLaurin summation starts to take an unreasonable amount of time in this - * region, so just give up and return NaN instead. */ - set_error("zeta", SF_ERROR_NO_RESULT, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; - } - return zeta_euler_maclaurin(z); - } - return zeta_borwein(z); - } - - XSF_HOST_DEVICE inline std::complex exppi(std::complex z) { - // exp(pi*z) for complex z. - double x = z.real(); - double y = z.imag(); - std::complex factor1(xsf::cospi(y), xsf::sinpi(y)); - double factor2 = std::exp(M_PI*x); - return factor1 * factor2; - } - - XSF_HOST_DEVICE inline std::complex logsinpi(std::complex z) { - /* log(sinpi(z)) using sin(z) = (exp(i*pi*z) - exp(-i*pi*z)) / 2i - * - * No attempt is made to choose any particular branch of the logarithm. - * This is an internal function and the intent is that this that the - * result of log(sinpi(z)) will be added to other terms, and the sum - * will then be exponentiated, making the choice of a specific branch - * unnecessary. - */ - std::complex result = std::log(xsf::sinpi(z)); - // If it doesn't overflow, just do the regular calculation. - if (std::isfinite(result.real()) && !std::isfinite(result.imag())) { - return result; - } - /* Otherwise factor before taking log. This is where we may end up - * taking a branch other than the principal branch. */ - std::complex J(0.0, 1.0); - /* Calculating log((exp(i*pi*z) - exp(-i*pi*z)) / 2i). Factor out term - * with larger magnitude before taking log. */ - if (z.imag() > 0 ) { - /* if z.imag() > 0 then, exp(-i*pi*z) has greatest magnitude. Factor it - * out to get: - * log(exp(-i*pi*z)*((exp(2*i*pi*z) - 1.0)/(2i)) = - * log(exp(-i*pi*z)) + log((exp(2*i*pi*z) - 1.0)/(2i)) = - * -i*pi*z + log((exp(2*i*pi*z) - 1.0)/(2i)) */ - return -J * M_PI * z + std::log((exppi(2.0 * z * J) - 1.0) / (2.0*J)); - } - /* if z.imag() < 0 then, exp(i*pi*z) has greatest magnitude. Factor similarly - * to above */ - return J * M_PI * z + std::log((1.0 - exppi(-2.0 * z * J)) / (2.0*J)); - } - - /* Leading factor in reflection formula for zeta function. - * zeta(z) = 2^z * pi^(z-1) * sin(pi*z/2) * gamma(1 - z) * zeta(1 - z) - * This computes 2^z * pi^(z - 1) * sin(pi*z/2) * gamma(1 - z) - * - * Computation is logarithimized to prevent overflow. - * TODO: Complexify the cephes zeta_reflection implementation, which uses - * the lanczos approximation for the gamma function. */ - XSF_HOST_DEVICE inline std::complex zeta_reflection_factor_with_logs(std::complex z) { - std::complex t1 = z * M_LN2; - std::complex t2 = (z - 1.0) * xsf::cephes::detail::LOGPI; - std::complex t3 = logsinpi(z / 2.0); - std::complex t4 = xsf::loggamma(1.0 - z); - std::complex factor = std::exp(t1 + t2 + t3 + t4); - return factor; - } - - XSF_HOST_DEVICE inline std::complex zeta_reflection(std::complex z) { - std::complex factor = 2.0 * std::pow(2*M_PI, z - 1.0) * xsf::sinpi(z/2.0) * xsf::gamma(1.0 - z); - if (!std::isfinite(factor.real()) || !std::isfinite(factor.imag())) { - // Try again with logs if standard calculation had overflow. - factor = zeta_reflection_factor_with_logs(z); - } - std::complex result = zeta_right_halfplane(1.0 - z); - /* zeta tends to 1.0 as real part tends to +inf. In cases where - * the real part of zeta tends to -inf, then zeta(1 - z) in the - * reflection formula will tend to 1.0. Factor overflows then, - * factor * result below will become NaN. In this case, we just - * return factor to preserve complex infinity. Only zeta(1 - z) == 1.0 - * is handled because this is the only practical case where we should - * expect zeta(1 - z) == x for a real number x when z is not on the - * real line. */ - return (result == 1.0) ? factor : factor * result; - } -} - -XSF_HOST_DEVICE inline std::complex riemann_zeta(std::complex z) { - if (z.imag() == 0.0) { - return cephes::riemann_zeta(z.real()); - } - if (z.real() >= 0.5) { - return detail::zeta_right_halfplane(z); - } - return detail::zeta_reflection(z); -} - -XSF_HOST_DEVICE inline std::complex riemann_zeta(std::complex z) { - return static_cast>(riemann_zeta(static_cast>(z))); -} - -XSF_HOST_DEVICE inline double riemann_zeta(double x) { return cephes::riemann_zeta(x); } - -XSF_HOST_DEVICE inline float riemann_zeta(float x) { return riemann_zeta(static_cast(x)); } - -XSF_HOST_DEVICE inline double zeta(double x, double q) { return cephes::zeta(x, q); } - -XSF_HOST_DEVICE inline float zeta(float x, float q) { return zeta(static_cast(x), static_cast(q)); } - -XSF_HOST_DEVICE inline std::complex zeta(std::complex z, double q) { - if (z.imag() == 0.0) { - return zeta(z.real(), q); - } - // Complex input for Hurwitz Zeta is not currently supported. - set_error("zeta", SF_ERROR_DOMAIN, NULL); - return {std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN()}; -} - -XSF_HOST_DEVICE inline std::complex zeta(std::complex z, float q) { - return static_cast>(zeta(static_cast>(z), static_cast(q))); -} - -XSF_HOST_DEVICE inline double zetac(double x) { return cephes::zetac(x); } - -XSF_HOST_DEVICE inline float zetac(float x) { return zetac(static_cast(x)); } - -} // namespace xsf diff --git a/scipy/special/xsf/zlog1.h b/scipy/special/xsf/zlog1.h deleted file mode 100644 index 64e83ca390a0..000000000000 --- a/scipy/special/xsf/zlog1.h +++ /dev/null @@ -1,35 +0,0 @@ -/* Translated from Cython into C++ by SciPy developers in 2023. - * - * Original author: Josh Wilson, 2016. - */ - -#pragma once - -#include "config.h" - -namespace xsf { -namespace detail { - - XSF_HOST_DEVICE inline std::complex zlog1(std::complex z) { - /* Compute log, paying special attention to accuracy around 1. We - * implement this ourselves because some systems (most notably the - * Travis CI machines) are weak in this regime. */ - std::complex coeff = -1.0; - std::complex res = 0.0; - - if (std::abs(z - 1.0) > 0.1) { - return std::log(z); - } - - z -= 1.0; - for (int n = 1; n < 17; n++) { - coeff *= -z; - res += coeff / static_cast(n); - if (std::abs(res / coeff) < std::numeric_limits::epsilon()) { - break; - } - } - return res; - } -} // namespace detail -} // namespace xsf diff --git a/scipy/special/xsf_special.h b/scipy/special/xsf_special.h index 2a7000bb3d24..3bd13e7a827c 100644 --- a/scipy/special/xsf_special.h +++ b/scipy/special/xsf_special.h @@ -2,8 +2,8 @@ #include "Python.h" -#include "xsf/bessel.h" -#include "xsf/sph_harm.h" +#include +#include // This header exists to add behaviors to special functions from the xsf library, // either because they involve some Python-specific features or because there are diff --git a/scipy/special/xsf_wrappers.cpp b/scipy/special/xsf_wrappers.cpp index 9550f10cf6ba..52f7b8bb9a86 100644 --- a/scipy/special/xsf_wrappers.cpp +++ b/scipy/special/xsf_wrappers.cpp @@ -1,55 +1,54 @@ #include "xsf_wrappers.h" -#include "xsf/airy.h" -#include "xsf/alg.h" -#include "xsf/amos.h" -#include "xsf/bessel.h" -#include "xsf/beta.h" -#include "xsf/binom.h" -#include "xsf/cdflib.h" -#include "xsf/digamma.h" -#include "xsf/ellip.h" -#include "xsf/erf.h" -#include "xsf/exp.h" -#include "xsf/expint.h" -#include "xsf/fresnel.h" -#include "xsf/gamma.h" -#include "xsf/hyp2f1.h" -#include "xsf/kelvin.h" -#include "xsf/lambertw.h" -#include "xsf/log.h" -#include "xsf/log_exp.h" -#include "xsf/loggamma.h" -#include "xsf/mathieu.h" -#include "xsf/par_cyl.h" -#include "xsf/sici.h" -#include "xsf/specfun.h" -#include "xsf/sph_bessel.h" -#include "xsf/sph_harm.h" -#include "xsf/sphd_wave.h" -#include "xsf/stats.h" -#include "xsf/struve.h" -#include "xsf/trig.h" -#include "xsf/wright_bessel.h" -#include "xsf/zeta.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "xsf_special.h" -#include "xsf/cephes/cbrt.h" -#include "xsf/cephes/erfinv.h" -#include "xsf/cephes/expn.h" -#include "xsf/cephes/fresnl.h" -#include "xsf/cephes/hyperg.h" -#include "xsf/cephes/igam.h" -#include "xsf/cephes/igami.h" -#include "xsf/cephes/jv.h" -#include "xsf/cephes/lanczos.h" -#include "xsf/cephes/poch.h" -#include "xsf/cephes/rgamma.h" -#include "xsf/cephes/round.h" -#include "xsf/cephes/scipy_iv.h" -#include "xsf/cephes/spence.h" -#include "xsf/cephes/trig.h" -#include "xsf/cephes/unity.h" -#include "xsf/cephes/yn.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include using namespace std; @@ -61,7 +60,7 @@ npy_cdouble to_ccomplex(complex z) { return {z.real(), z.imag()}; } } // namespace -npy_cdouble chyp1f1_wrap(double a, double b, npy_cdouble z) { return to_ccomplex(xsf::chyp1f1(a, b, to_complex(z))); } +npy_cdouble chyp1f1_wrap(double a, double b, npy_cdouble z) { return to_ccomplex(xsf::hyp1f1(a, b, to_complex(z))); } double hypU_wrap(double a, double b, double x) { return xsf::hypu(a, b, x); } @@ -310,17 +309,17 @@ int cephes_ellpj_wrap(double u, double m, double *sn, double *cn, double *dn, do return xsf::cephes::ellpj(u, m, sn, cn, dn, ph); } -int xsf_sici(double x, double *si, double *ci) { return xsf::sici(x, si, ci); } +int xsf_sici(double x, double *si, double *ci) { return xsf::sici(x, *si, *ci); } -int xsf_shichi(double x, double *si, double *ci) { return xsf::shichi(x, si, ci); } +int xsf_shichi(double x, double *si, double *ci) { return xsf::shichi(x, *si, *ci); } int xsf_csici(npy_cdouble x, npy_cdouble *si, npy_cdouble *ci) { - return xsf::sici(to_complex(x), reinterpret_cast *>(si), reinterpret_cast *>(ci)); + return xsf::sici(to_complex(x), *reinterpret_cast *>(si), *reinterpret_cast *>(ci)); } int xsf_cshichi(npy_cdouble x, npy_cdouble *shi, npy_cdouble *chi) { - return xsf::shichi(to_complex(x), reinterpret_cast *>(shi), - reinterpret_cast *>(chi)); + return xsf::shichi(to_complex(x), *reinterpret_cast *>(shi), + *reinterpret_cast *>(chi)); } double cephes__struve_asymp_large_z(double v, double z, Py_ssize_t is_h, double *err) { @@ -352,7 +351,7 @@ double xsf_beta(double a, double b) { return xsf::beta(a, b); } double xsf_betaln(double a, double b) { return xsf::betaln(a, b); } -double xsf_cbrt(double x) { return xsf::cbrt(x); } +double xsf_cbrt(double x) { return xsf::cephes::cbrt(x); } double xsf_gamma(double x) { return xsf::gamma(x); } diff --git a/subprojects/xsf b/subprojects/xsf new file mode 160000 index 000000000000..4fff9b2cb2b5 --- /dev/null +++ b/subprojects/xsf @@ -0,0 +1 @@ +Subproject commit 4fff9b2cb2b5c31a0cf0b0f609d2699a5eeac53b From b93cdcc424cacaadd89c1a7e883f7b76c62cf594 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 28 Apr 2025 07:47:47 +0200 Subject: [PATCH 065/251] DEV: add editable install support for `spin` Running `spin install` will perform an editable install in the active environment. After that, `spin test` and other commands will pick up the editable install as expected. Two things that are not supported are measuring coverage and running Mypy. Those seem to require a regular install with all files on disk, they cannot use the import hook for editable installs. Hence that will remain unsupported. It's a pretty minor limitation, everything else should work as advertised. --- .spin/cmds.py | 87 ++++++++++++++++++++++++++++++++------------------ pyproject.toml | 3 +- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index 0cea23d65941..d0dae620eaea 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -205,32 +205,41 @@ def test(*, parent_callback, pytest_args, tests, coverage, build_dir = os.path.abspath(kwargs['build_dir']) site_package_dir = get_site_packages(build_dir) - if site_package_dir is None and coverage: - raise FileNotFoundError( - "SciPy build not found, please execute " - "``spin build`` before calling ``spin test --coverage``. " - "We need it to figure out whether ``lcov`` can be called or not.") - - if site_package_dir is not None: - with working_dir(site_package_dir): - sys.path.insert(0, site_package_dir) - os.environ['PYTHONPATH'] = \ - os.pathsep.join((site_package_dir, os.environ.get('PYTHONPATH', ''))) - was_built_with_gcov_flag = len(list(Path(build_dir).rglob("*.gcno"))) > 0 - if was_built_with_gcov_flag: - config = importlib.import_module("scipy.__config__").show(mode='dicts') - compilers_config = config['Compilers'] - cpp = compilers_config['c++']['name'] - c = compilers_config['c']['name'] - fortran = compilers_config['fortran']['name'] - if not (c == 'gcc' and cpp == 'gcc' and fortran == 'gcc'): - print("SciPy was built with --gcov flag which requires " - "LCOV while running tests.\nFurther, LCOV usage " - "requires GCC for C, C++ and Fortran codes in SciPy.\n" - "Compilers used currently are:\n" - f" C: {c}\n C++: {cpp}\n Fortran: {fortran}\n" - "Therefore, exiting without running tests.") - exit(1) # Exit because tests will give missing symbol error + if coverage: + if is_editable_install(): + click.secho( + "Error: cannot generate coverage report for editable installs", + fg="bright_red", + ) + raise SystemExit(1) + elif site_package_dir is None: + raise FileNotFoundError( + "SciPy build not found, please execute " + "``spin build`` before calling ``spin test --coverage``. " + "We need it to figure out whether ``lcov`` can be called or not.") + else: + # Check needed to ensure gcov functions correctly. + with working_dir(site_package_dir): + sys.path.insert(0, site_package_dir) + os.environ['PYTHONPATH'] = os.pathsep.join( + (site_package_dir, os.environ.get('PYTHONPATH', ''))) + was_built_with_gcov_flag = len(list( + Path(build_dir).rglob("*.gcno"))) > 0 + if was_built_with_gcov_flag: + config = importlib.import_module( + "scipy.__config__").show(mode='dicts') + compilers_config = config['Compilers'] + cpp = compilers_config['c++']['name'] + c = compilers_config['c']['name'] + fortran = compilers_config['fortran']['name'] + if not (c == 'gcc' and cpp == 'gcc' and fortran == 'gcc'): + print("SciPy was built with --gcov flag which requires " + "LCOV while running tests.\nFurther, LCOV usage " + "requires GCC for C, C++ and Fortran codes in SciPy.\n" + "Compilers used currently are:\n" + f" C: {c}\n C++: {cpp}\n Fortran: {fortran}\n" + "Therefore, exiting without running tests.") + exit(1) # Exit because tests will give missing symbol error if submodule: tests = PROJECT_MODULE + "." + submodule @@ -389,11 +398,18 @@ def working_dir(new_dir): def mypy(ctx, build_dir=None): """🦆 Run Mypy tests for SciPy """ - click.secho( - "Invoking `build` prior to running mypy tests:", - bold=True, fg="bright_green" + if is_editable_install(): + click.secho( + "Error: Mypy does not work (well) for editable installs", + fg="bright_red", ) - ctx.invoke(build) + raise SystemExit(1) + else: + click.secho( + "Invoking `build` prior to running mypy tests:", + bold=True, fg="bright_green" + ) + ctx.invoke(build) try: import mypy.api @@ -405,9 +421,9 @@ def mypy(ctx, build_dir=None): build_dir = os.path.abspath(build_dir) root = Path(build_dir).parent - install_dir = meson._get_site_packages(build_dir) config = os.path.join(root, "mypy.ini") check_path = PROJECT_MODULE + install_dir = meson._get_site_packages(build_dir) with working_dir(install_dir): os.environ['MYPY_FORCE_COLOR'] = '1' @@ -1029,7 +1045,16 @@ def cpu_count(only_physical_cores=False): return aggregate_cpu_count def get_site_packages(build_dir): + """site-packages directory is path to installed in-tree build. + + Returns None if `scipy` wasn't build at all. + Returns an empty string (from spin.meson call) for an editable install. + """ try: return meson._get_site_packages(build_dir) except FileNotFoundError: return None + + +def is_editable_install(): + return meson._is_editable_install_of_same_source('scipy') diff --git a/pyproject.toml b/pyproject.toml index 81b6d5dcab9f..be113ca46f9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,7 +175,8 @@ PKG_CONFIG_PATH = "{project}" ".spin/cmds.py:build", ".spin/cmds.py:test", ".spin/cmds.py:mypy", - ".spin/cmds.py:lint" + ".spin/cmds.py:lint", + "spin.cmds.pip.install" ] "Environments" = [ "spin.cmds.meson.run", From 51df0a221ddb06f251f9e4926ce9e2582be953aa Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 28 Apr 2025 08:42:30 +0200 Subject: [PATCH 066/251] TST: fix norecurse list of directories in `pytest.ini` Pytest was picking up some tests in array-api-extra and pyprima when run with `spin test` and an editable install. --- pytest.ini | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 5228e5ea6e22..01641cee4231 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,14 @@ [pytest] addopts = -l -norecursedirs = doc tools scipy/_lib/array_api_compat scipy/_lib/cobyqa scipy/_lib/highs junit_family=xunit2 +norecursedirs = + doc + tools + scipy/_lib/array_api_compat + scipy/_lib/array_api_extra + scipy/_lib/cobyqa + scipy/_lib/highs + scipy/_lib/pyprima filterwarnings = error From 87d2a399cf041ee6bbec98593312c79ea55dd771 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 28 Apr 2025 17:58:01 +0200 Subject: [PATCH 067/251] DEV: fix broken emoji's in `spin --help` output --- .spin/cmds.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index d0dae620eaea..071017330cde 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -504,7 +504,7 @@ def smoke_docs(*, parent_callback, pytest_args, **kwargs): @meson.build_dir_option @click.pass_context def refguide_check(ctx, build_dir=None, *args, **kwargs): - """:wrench: Run refguide check.""" + """🔧 Run refguide check.""" click.secho( "Invoking `build` prior to running refguide-check:", bold=True, fg="bright_green" @@ -604,7 +604,7 @@ def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs help="Do not run cython-lint.") @click.pass_context def lint(ctx, fix, diff_against, files, all, no_cython): - """:dash: Run linter on modified files and check for + """🔦 Run linter on modified files and check for disallowed Unicode characters and possibly-invalid test names.""" root = Path(__file__).parent.parent @@ -725,7 +725,7 @@ def _dirty_git_working_dir(): @click.pass_context def bench(ctx, tests, submodule, compare, verbose, quick, commits, build_dir=None, *args, **kwargs): - """:wrench: Run benchmarks. + """🔧 Run benchmarks. \b ```python From b25ae6f8acda8b595336c75e52ab149da7d0fd7d Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 28 Apr 2025 17:58:35 +0200 Subject: [PATCH 068/251] BLD: make git hash check in doc build more robust This change ensures that building the docs with an editable install in verbose mode works as well. meson-python will print output on import, so the previous incantation would fail on trying to interpret that as a git hash. --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index 64a55bc028fa..43e7efdf59a6 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -54,7 +54,7 @@ UPLOAD_DIR=/srv/docs_scipy_org/doc/scipy-$(RELEASE) # `set -o pipefail` is specific to bash SHELL = /bin/bash -SCIPYVER:=$(shell $(PYTHON) -c "import scipy; print(scipy.version.git_revision[:10])" 2>/dev/null) +SCIPYVER:=$(shell $(PYTHON) -c "import scipy; print(scipy.version.git_revision[:10])" | tail -c11 2>/dev/null) GITVER ?= $(shell (cd ..; set -o pipefail && git rev-parse HEAD 2>/dev/null | cut -c1-10) || echo Unknown) version-check: From 15e2c69384dd699fa5b520078b2e911ee4a0b048 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 28 Apr 2025 18:15:58 +0200 Subject: [PATCH 069/251] DEV: make `spin smoke-docs` work with editable installs This resolves all import errors that prevent the tool from running. It should pass when the same dependency versions are installed as are checked for a regular build in CI. --- scipy/_lib/_unuran_utils.py | 9 --------- scipy/conftest.py | 19 ++++++++++++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) delete mode 100644 scipy/_lib/_unuran_utils.py diff --git a/scipy/_lib/_unuran_utils.py b/scipy/_lib/_unuran_utils.py deleted file mode 100644 index 7b6ffbdda8c4..000000000000 --- a/scipy/_lib/_unuran_utils.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Helper functions to get location of UNU.RAN source files.""" - -import pathlib - - -def _unuran_dir(ret_path: bool = False) -> pathlib.Path | str: - """Directory where root unuran/ directory lives.""" - p = pathlib.Path(__file__).parent / "unuran" - return p if ret_path else str(p) diff --git a/scipy/conftest.py b/scipy/conftest.py index b3fc5a96d8a5..b3ea9ec20187 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -216,7 +216,7 @@ def num_parallel_threads(): # by default, use all available backends if ( - isinstance(SCIPY_ARRAY_API, str) + isinstance(SCIPY_ARRAY_API, str) and SCIPY_ARRAY_API.lower() not in ("1", "true", "all") ): SCIPY_ARRAY_API_ = json.loads(SCIPY_ARRAY_API) @@ -285,7 +285,7 @@ def xp(request, monkeypatch): def _backends_kwargs_from_request(request, skip_or_xfail): """A helper for {skip,xfail}_xp_backends. - + Return dict of {backend to skip/xfail: top reason to skip/xfail it} """ markers = list(request.node.iter_markers(f'{skip_or_xfail}_xp_backends')) @@ -343,7 +343,7 @@ def _backends_kwargs_from_request(request, skip_or_xfail): f"Please specify only one backend per marker: {marker.args}" ) - return {backend: backend_reasons[0] + return {backend: backend_reasons[0] for backend, backend_reasons in reasons.items() if backend_reasons} @@ -611,12 +611,25 @@ def warnings_errors_and_rng(test=None): # equivalent to "pytest --ignore=path/to/file" "scipy/special/_precompute", "scipy/interpolate/_interpnd_info.py", + "scipy/interpolate/_rbfinterp_pythran.py", + "scipy/_build_utils/tempita.py", "scipy/_lib/array_api_compat", "scipy/_lib/highs", "scipy/_lib/unuran", "scipy/_lib/_gcutils.py", "scipy/_lib/doccer.py", "scipy/_lib/_uarray", + "scipy/linalg/_cython_signature_generator.py", + "scipy/linalg/_generate_pyx.py", + "scipy/linalg/_linalg_pythran.py", + "scipy/linalg/_matfuncs_sqrtm_triu.py", + "scipy/ndimage/utils/generate_label_testvectors.py", + "scipy/optimize/_group_columns.py", + "scipy/optimize/_max_len_seq_inner.py", + "scipy/signal/_max_len_seq_inner.py", + "scipy/sparse/_generate_sparsetools.py", + "scipy/special/_generate_pyx.py", + "scipy/stats/_stats_pythran.py", ] dt_config.pytest_extra_xfail = { From 801c664181ccd3ab2cdcc748085ef78c7e68975a Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Mon, 28 Apr 2025 18:45:55 +0100 Subject: [PATCH 070/251] MAINT: bump array-api submodules (#22899) --- scipy/_lib/array_api_compat | 2 +- scipy/_lib/array_api_extra | 2 +- scipy/cluster/tests/test_hierarchy.py | 14 +++++++------- scipy/cluster/vq.py | 2 +- scipy/stats/tests/test_stats.py | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scipy/_lib/array_api_compat b/scipy/_lib/array_api_compat index 621494be1bd8..e600449a645c 160000 --- a/scipy/_lib/array_api_compat +++ b/scipy/_lib/array_api_compat @@ -1 +1 @@ -Subproject commit 621494be1bd8682f1d76ae874272c12464953d3d +Subproject commit e600449a645c2e6ce5a2276da0006491f097c096 diff --git a/scipy/_lib/array_api_extra b/scipy/_lib/array_api_extra index 0d26a7462a3f..bb6129b1bfe3 160000 --- a/scipy/_lib/array_api_extra +++ b/scipy/_lib/array_api_extra @@ -1 +1 @@ -Subproject commit 0d26a7462a3fbf5ed9e42e261bdb3b39f25e2faf +Subproject commit bb6129b1bfe344b9807a2f28451fe9211efe0b1b diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 50622ace2f93..29f848ace459 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -78,7 +78,7 @@ lazy_xp_function(ward) lazy_xp_function(linkage, static_argnames=('method', 'metric', 'optimal_ordering')) lazy_xp_function(cut_tree, static_argnames=('n_clusters', 'height')) -lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=999, +lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=True, static_argnames=('rd', )) lazy_xp_function(optimal_leaf_ordering, static_argnames=('metric',)) lazy_xp_function(cophenet, jax_jit=False, allow_dask_compute=2) @@ -94,16 +94,16 @@ lazy_xp_function(num_obs_linkage) lazy_xp_function(correspond) -lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=999, +lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=True, static_argnames=('criterion', 'depth')) -lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=999, +lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=True, static_argnames=('criterion', 'metric', 'depth', 'method')) lazy_xp_function(leaves_list, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=999) +lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=True) lazy_xp_function(is_isomorphic, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(maxdists, jax_jit=False, allow_dask_compute=999) -lazy_xp_function(maxinconsts, jax_jit=False, allow_dask_compute=999) -lazy_xp_function(maxRstat, jax_jit=False, allow_dask_compute=999, +lazy_xp_function(maxdists, jax_jit=False, allow_dask_compute=True) +lazy_xp_function(maxinconsts, jax_jit=False, allow_dask_compute=True) +lazy_xp_function(maxRstat, jax_jit=False, allow_dask_compute=True, static_argnames=('i',)) # Returns data-dependent shape diff --git a/scipy/cluster/vq.py b/scipy/cluster/vq.py index b21d824da67f..8fba4b2e032d 100644 --- a/scipy/cluster/vq.py +++ b/scipy/cluster/vq.py @@ -614,7 +614,7 @@ def _kpp(data, k, rng, xp): cumprobs = probs.cumsum() r = rng.uniform() cumprobs = np.asarray(cumprobs) - data_idx = np.searchsorted(cumprobs, r) + data_idx = int(np.searchsorted(cumprobs, r)) init = xpx.at(init)[i, :].set(data[data_idx, :]) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 49a08e604949..fdff65953581 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3719,7 +3719,7 @@ def test_skew_propagate_nan(self, xp): def test_skew_constant_value(self, xp): # Skewness of a constant input should be NaN (gh-16061) - a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available + a = xp.repeat(xp.asarray([-0.27829495]), 10) with eager_warns(a, RuntimeWarning, match="Precision loss occurred"): xp_assert_equal(stats.skew(a), xp.asarray(xp.nan)) From 998093b06407e19fa2ee5a8dafd7880982c58a4f Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Mon, 28 Apr 2025 18:48:14 +0100 Subject: [PATCH 071/251] MAINT: fix `np.copyto` warnings on Dask (#22900) * ENH: fix `np.copyto` warnings on Dask * Fix torch float32 regression * Code review --- scipy/_lib/_array_api.py | 14 ++++++++++++++ scipy/_lib/_util.py | 10 +++++++--- scipy/special/tests/test_logsumexp.py | 5 +---- scipy/stats/_stats_py.py | 3 +-- scipy/stats/_variation.py | 10 ++++------ scipy/stats/tests/test_stats.py | 15 ++------------- scipy/stats/tests/test_variation.py | 4 ---- 7 files changed, 29 insertions(+), 32 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index a73ad16f4af9..54033aee0092 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -628,6 +628,20 @@ def xp_default_dtype(xp): return xp.float64 +def xp_result_device(*args): + """Return the device of an array in `args`, for the purpose of + input-output device propagation. + If there are multiple devices, return an arbitrary one. + If there are no arrays, return None (this typically happens only on NumPy). + """ + for arg in args: + # Do not do a duck-type test for the .device attribute, as many backends today + # don't have it yet. See workarouunds in array_api_compat.device(). + if is_array_api_obj(arg): + return xp_device(arg) + return None + + def is_marray(xp): """Returns True if `xp` is an MArray namespace; False otherwise.""" return "marray" in xp.__name__ diff --git a/scipy/_lib/_util.py b/scipy/_lib/_util.py index 2de7552b45d9..79b916757e8e 100644 --- a/scipy/_lib/_util.py +++ b/scipy/_lib/_util.py @@ -12,7 +12,8 @@ import numpy as np from scipy._lib._array_api import (Array, array_namespace, is_lazy_array, - is_numpy, is_marray, xp_size, xp_result_type) + is_numpy, is_marray, xp_result_device, + xp_size, xp_result_type) from scipy._lib._docscrape import FunctionDoc, Parameter from scipy._lib._sparse import issparse @@ -1009,11 +1010,14 @@ def _rng_spawn(rng, n_children): return child_rngs -def _get_nan(*data, xp=None): +def _get_nan(*data, shape=(), xp=None): xp = array_namespace(*data) if xp is None else xp # Get NaN of appropriate dtype for data dtype = xp_result_type(*data, force_floating=True, xp=xp) - res = xp.asarray(xp.nan, dtype=dtype)[()] + device = xp_result_device(*data) + res = xp.full(shape, xp.nan, dtype=dtype, device=device) + if not shape: + res = res[()] # whenever mdhaber/marray#89 is resolved, could just return `res` return res.data if is_marray(xp) else res diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index 225d6bb800fb..02cd288260c5 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -222,9 +222,6 @@ def test_gh18295(self, xp): ref = xp.logaddexp(a[0], a[1]) xp_assert_close(res, ref) - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('dtype', ['complex64', 'complex128']) def test_gh21610(self, xp, dtype): # gh-21610 noted that `logsumexp` could return imaginary components @@ -240,7 +237,7 @@ def test_gh21610(self, xp, dtype): res = logsumexp(x, axis=1) ref = xp.log(xp.sum(xp.exp(x), axis=1)) - max = xp.full_like(xp.imag(res), xp.asarray(xp.pi)) + max = xp.full_like(xp.imag(res), xp.pi) xp_assert_less(xp.abs(xp.imag(res)), max) xp_assert_close(res, ref) diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 84aa28749a9e..561ae7821f44 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -6762,8 +6762,7 @@ def ttest_ind(a, b, *, axis=0, equal_var=True, nan_policy='propagate', raise NotImplementedError(message) result_shape = _broadcast_array_shapes_remove_axis((a, b), axis=axis) - NaN = xp.full(result_shape, _get_nan(a, b, xp=xp)) - NaN = NaN[()] if NaN.ndim == 0 else NaN + NaN = _get_nan(a, b, shape=result_shape, xp=xp) if xp_size(a) == 0 or xp_size(b) == 0: return TtestResult(NaN, NaN, df=NaN, alternative=NaN, standard_error=NaN, estimate=NaN) diff --git a/scipy/stats/_variation.py b/scipy/stats/_variation.py index 09f18f6896d9..595098c5a3ee 100644 --- a/scipy/stats/_variation.py +++ b/scipy/stats/_variation.py @@ -103,22 +103,20 @@ def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): axis = 0 n = a.shape[axis] - NaN = _get_nan(a) if a.size == 0 or ddof > n: # Handle as a special case to avoid spurious warnings. # The return values, if any, are all nan. - shp = list(a.shape) - shp.pop(axis) - result = xp.full(shp, fill_value=NaN) - return result[()] if result.ndim == 0 else result + shape = list(a.shape) + shape.pop(axis) + return _get_nan(a, shape=tuple(shape), xp=xp) mean_a = xp.mean(a, axis=axis) if ddof == n: # Another special case. Result is either inf or nan. std_a = xp.std(a, axis=axis, correction=0) - result = xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), NaN) + result = xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), xp.nan) return result[()] if result.ndim == 0 else result with np.errstate(divide='ignore', invalid='ignore'): diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index fdff65953581..8bea176b58d1 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -3063,9 +3063,6 @@ def test_zscore_constant_input_1d(self, xp): z = stats.zscore(x) xp_assert_equal(z, xp.full(x.shape, xp.nan)) - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_zscore_constant_input_2d(self, xp): x = xp.asarray([[10.0, 10.0, 10.0, 10.0], @@ -3086,7 +3083,7 @@ def test_zscore_constant_input_2d(self, xp): y = xp.ones((3, 6)) with eager_warns(y, RuntimeWarning, match="Precision loss occurred..."): z = stats.zscore(y, axis=None) - xp_assert_equal(z, xp.full(y.shape, xp.asarray(xp.nan))) + xp_assert_equal(z, xp.full(y.shape, xp.nan)) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_zscore_constant_input_2d_nan_policy_omit(self, xp): @@ -6120,10 +6117,6 @@ def test_ttest_ind_nan_2nd_arg(self): assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901), atol=1e-15) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) def test_ttest_ind_empty_1d_returns_nan(self, xp): # Two empty inputs should return a TtestResult containing nan # for both values. @@ -6137,10 +6130,6 @@ def test_ttest_ind_empty_1d_returns_nan(self, xp): xp_assert_equal(res.statistic, NaN) xp_assert_equal(res.pvalue, NaN) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('b, expected_shape', [(np.empty((1, 5, 0)), (3, 5)), (np.empty((1, 0, 0)), (3, 0))]) @@ -6149,7 +6138,7 @@ def test_ttest_ind_axis_size_zero(self, b, expected_shape, xp): # The results should be arrays containing nan with shape # given by the broadcast nonaxis dimensions. a = xp.empty((3, 1, 0)) - b = xp.asarray(b) + b = xp.asarray(b, dtype=a.dtype) with np.testing.suppress_warnings() as sup: # first case should warn, second shouldn't? sup.filter(SmallSampleWarning, too_small_nd_not_omit) diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index 5fc906530a07..5660f733b4f7 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -133,10 +133,6 @@ def test_return_nan(self, x, xp): y = variation(x) xp_assert_equal(y, xp.asarray(xp.nan, dtype=x.dtype)) - # internal dask warning we can't do anything about - @pytest.mark.filterwarnings( - "ignore:The `numpy.copyto` function is not implemented:FutureWarning:dask" - ) @pytest.mark.parametrize('axis, expected', [(0, []), (1, [np.nan]*3), (None, np.nan)]) def test_2d_size_zero_with_axis(self, axis, expected, xp): From ecbf201f261f0f489146b4c88e063f9194da6a0c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 29 Apr 2025 08:47:42 -0700 Subject: [PATCH 072/251] MAINT: special.logsumexp: fix bug when weight of largest magnitude component is negative --- scipy/special/_logsumexp.py | 33 +++++++++++++++------------ scipy/special/tests/test_logsumexp.py | 14 ++++++++++++ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/scipy/special/_logsumexp.py b/scipy/special/_logsumexp.py index 01f102d9d531..b7338c3f881d 100644 --- a/scipy/special/_logsumexp.py +++ b/scipy/special/_logsumexp.py @@ -227,24 +227,29 @@ def _logsumexp(a, b, *, axis, return_sign, xp): s = xp.where(s == 0, s, s/m) # Separate sign/magnitude information - sgn = None - if return_sign: - # Use the numpy>=2.0 convention for sign. - # When all array libraries agree, this can become sng = xp.sign(s). - sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp) - - if xp.isdtype(s.dtype, "real floating"): - # The log functions need positive arguments - s = xp.where(s < -1, -s - 2, s) - m = xp.abs(m) - else: - # `a_max` can have a sign component for complex input - sgn = sgn * xp.exp(xp.imag(a_max) * 1.0j) + # Originally, this was only performed if `return_sign=True`. + # However, this is also needed if any elements of `m < 0` or `s < -1`. + # An improvement would be to perform the calculations only on these entries. + + # Use the numpy>=2.0 convention for sign. + # When all array libraries agree, this can become sng = xp.sign(s). + sgn = _sign(s + 1, xp=xp) * _sign(m, xp=xp) + + if xp.isdtype(s.dtype, "real floating"): + # The log functions need positive arguments + s = xp.where(s < -1, -s - 2, s) + m = xp.abs(m) + else: + # `a_max` can have a sign component for complex input + sgn = sgn * xp.exp(xp.imag(a_max) * 1.0j) # Take log and undo shift out = xp.log1p(s) + xp.log(m) + a_max - out = xp.real(out) if return_sign else out + if return_sign: + out = xp.real(out) + elif xp.isdtype(out.dtype, 'real floating'): + out = xpx.at(out)[sgn < 0].set(xp.nan) return out, sgn diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index 225d6bb800fb..f6679d3d72b4 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -311,6 +311,20 @@ def test_device(self, x_raw, xp, devices): assert xp_device(logsumexp(x)) == xp_device(x) assert xp_device(logsumexp(x, b=x)) == xp_device(x) + def test_gh22903(self, xp): + # gh-22903 reported that `logsumexp` produced NaN where the weight associated + # with the max magnitude element was negative and `return_sign=False`, even if + # the net result should be the log of a positive number. + + # result is log of positive number + a = xp.asarray([3.06409428, 0.37251854, 3.87471931]) + b = xp.asarray([1.88190708, 2.84174795, -0.85016884]) + xp_assert_close(logsumexp(a, b=b), logsumexp(a, b=b, return_sign=True)[0]) + + # result is log of negative number + b = xp.asarray([1.88190708, 2.84174795, -3.85016884]) + xp_assert_close(logsumexp(a, b=b), xp.asarray(xp.nan)) + @make_skip_xp_backends(softmax) class TestSoftmax: From 4edd31493af39b957a2cea9c9fda400193413c2e Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Wed, 30 Apr 2025 08:02:08 +0100 Subject: [PATCH 073/251] MAINT: bump qhull to 2020.2 (#22908) * MAINT: bump qhull to 2020.2 * add txt files [skip ci] --- scipy/spatial/qhull_src/Announce.txt | 7 +- scipy/spatial/qhull_src/COPYING.txt | 11 +- scipy/spatial/qhull_src/README.txt | 203 +++++++++++++++-------- scipy/spatial/qhull_src/src/geom2_r.c | 9 +- scipy/spatial/qhull_src/src/geom_r.c | 6 +- scipy/spatial/qhull_src/src/geom_r.h | 6 +- scipy/spatial/qhull_src/src/global_r.c | 24 +-- scipy/spatial/qhull_src/src/io_r.c | 100 ++++++----- scipy/spatial/qhull_src/src/io_r.h | 6 +- scipy/spatial/qhull_src/src/libqhull_r.c | 6 +- scipy/spatial/qhull_src/src/libqhull_r.h | 12 +- scipy/spatial/qhull_src/src/mem_r.c | 6 +- scipy/spatial/qhull_src/src/mem_r.h | 6 +- scipy/spatial/qhull_src/src/merge_r.c | 14 +- scipy/spatial/qhull_src/src/merge_r.h | 6 +- scipy/spatial/qhull_src/src/poly2_r.c | 36 +++- scipy/spatial/qhull_src/src/poly_r.c | 6 +- scipy/spatial/qhull_src/src/poly_r.h | 7 +- scipy/spatial/qhull_src/src/qhull_ra.h | 6 +- scipy/spatial/qhull_src/src/qset_r.c | 6 +- scipy/spatial/qhull_src/src/qset_r.h | 6 +- scipy/spatial/qhull_src/src/random_r.h | 6 +- scipy/spatial/qhull_src/src/stat_r.c | 8 +- scipy/spatial/qhull_src/src/stat_r.h | 6 +- scipy/spatial/qhull_src/src/user_r.c | 2 +- scipy/spatial/qhull_src/src/user_r.h | 12 +- 26 files changed, 321 insertions(+), 202 deletions(-) diff --git a/scipy/spatial/qhull_src/Announce.txt b/scipy/spatial/qhull_src/Announce.txt index b2333a5861f5..704cb5259f49 100644 --- a/scipy/spatial/qhull_src/Announce.txt +++ b/scipy/spatial/qhull_src/Announce.txt @@ -1,5 +1,5 @@ - Qhull 2019.1 2019/06/21 + Qhull 2020.2 2020/08/31 (8.0.2) http://www.qhull.org http://github.com/qhull/qhull/wiki @@ -17,11 +17,12 @@ input transformations, randomization, tracing, multiple output formats, and execution statistics. The program can be called from within your application. You can view the results in 2-d, 3-d and 4-d with Geomview. -To download Qhull: +Download Qhull: + http://www.qhull.org/download git@github.com:qhull/qhull.git -Download qhull-96.ps for: +Reference: Barber, C. B., D.P. Dobkin, and H.T. Huhdanpaa, "The Quickhull Algorithm for Convex Hulls," ACM Trans. on diff --git a/scipy/spatial/qhull_src/COPYING.txt b/scipy/spatial/qhull_src/COPYING.txt index 4ac02a07f45d..14e122d71f77 100644 --- a/scipy/spatial/qhull_src/COPYING.txt +++ b/scipy/spatial/qhull_src/COPYING.txt @@ -1,4 +1,4 @@ - Qhull, Copyright (c) 1993-2019 + Qhull, Copyright (c) 1993-2020 C.B. Barber Arlington, MA @@ -13,9 +13,10 @@ email: qhull@qhull.org This software includes Qhull from C.B. Barber and The Geometry Center. -Qhull is copyrighted as noted above. Qhull is free software and may -be obtained via http from www.qhull.org. It may be freely copied, modified, -and redistributed under the following conditions: +Files derived from Qhull 1.0 are copyrighted by the Geometry Center. The +remaining files are copyrighted by C.B. Barber. Qhull is free software +and may be obtained via http from www.qhull.org. It may be freely copied, +modified, and redistributed under the following conditions: 1. All copyright notices must remain intact in all files. @@ -35,4 +36,4 @@ and redistributed under the following conditions: 5. There is no warranty or other guarantee of fitness for Qhull, it is provided solely "as is". Bug reports or fixes may be sent to qhull_bug@qhull.org; the authors may or may not act on them as - they desire. + they desire. \ No newline at end of file diff --git a/scipy/spatial/qhull_src/README.txt b/scipy/spatial/qhull_src/README.txt index eb18af9e02e5..37cad1d99e80 100644 --- a/scipy/spatial/qhull_src/README.txt +++ b/scipy/spatial/qhull_src/README.txt @@ -1,9 +1,9 @@ Name - qhull, rbox 2019.1 2019/06/21 + qhull, rbox 2020.2 2020/08/31 (8.0.2) Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection - + Documentation: html/index.htm @@ -12,14 +12,14 @@ Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection (git@github.com:qhull/qhull.git) - + News and a paper: Version 1 (simplicial only): - + Purpose Qhull is a general dimension convex hull program that reads a set @@ -31,7 +31,7 @@ Purpose Rbox is a useful tool in generating input for Qhull; it generates hypercubes, diamonds, cones, circles, simplices, spirals, lattices, and random points. - + Qhull produces graphical output for Geomview. This helps with understanding the output. @@ -40,10 +40,10 @@ Environment requirements Qhull and rbox should run on all 32-bit and 64-bit computers. Use an ANSI C or C++ compiler to compile the program. The software is self-contained. It comes with examples and test scripts. - + Qhull's C++ interface uses the STL. The C++ test program uses QTestLib from the Qt Framework. - + Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt before using or distributing Qhull. @@ -57,22 +57,23 @@ To modify Qhull, particularly the C++ interface Qhull is on GitHub (http://github.com/qhull/qhull/wiki, git@github.com:qhull/qhull.git) - + For internal documentation, see html/qh-code.htm To install Qhull Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. - + Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files for Qt Creator. It compiles under Windows with mingw. - + () + Install and build instructions follow. - + See the end of this document for a list of distributed files. ------------------ +------------------ Index Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT @@ -83,29 +84,29 @@ Working with Qhull's C++ interface Calling Qhull from C programs Compiling Qhull with Microsoft Visual C++ Compiling Qhull with Qt Creator -Compiling Qhull with mingw on Windows +Compiling Qhull with mingw/gcc on Windows Compiling Qhull with cygwin on Windows Compiling from Makfile without gcc Compiling on other machines and compilers Distributed files Authors ------------------ +------------------ Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant library while the other executables use the non-reentrant library. - + To install Qhull: - Unzip the files into a directory (e.g., named 'qhull') - Click on QHULL-GO or open a command window into Qhull's bin directory. - Test with 'rbox D4 | qhull' - + To uninstall Qhull - Delete the qhull directory - + To learn about Qhull: - Execute 'qconvex' for a synopsis and examples. Or 'qconvex --help' or 'qconvex -?' @@ -134,69 +135,95 @@ Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT http://www.msys2.org/ https://github.com/msys2/msys2/wiki [mar'19] Git for Windows v2.21 requires 'qhull --help' + Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' fails www.cygwin.com www.mingw.org/wiki/msys # for Windows XP Road Bash (www.qhull.org/bash) # based on MSYS ------------------ +------------------ Installing Qhull on Unix with gcc To build Qhull, static libraries, shared library, and C++ interface - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - make - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH + - make test + + 'make install' installs Qhull at '/usr/local/'. It installs pkg-config files + at '/usr/local/lib/pkgconfig'. Change the install directory with DESTDIR and PREFIX. + + To build 32-bit Qhull on a 64-bit host (uses 33% less memory in 4-d) + - make new M32=-m32 + + To build 32-bit Qhull without -fpic (may be faster, but shared library may fail) + - make new M32=-m32 FPIC= The Makefiles may be edited for other compilers. If 'testqset' exits with an error, qhull is broken - + A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. To build the Qhull executables and libqhullstatic - Extract Qhull from qhull...tgz or qhull...zip - cd src/libqhull_r # cd src/libqhull - make - ------------------ + +------------------ Installing Qhull with CMake 2.6 or later See CMakeLists.txt for examples and further build instructions - + To build Qhull, static libraries, shared library, and C++ interface - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - cd build - - cmake --help # List build generators - - make -G "" .. && cmake .. - - cmake .. + - cmake --help # List build generators + - cmake -G "" .. # e.g., for MINGW-w64 -- cmake -G "MSYS Makefiles" .. + - cmake .. - make - - make install + - ctest + - make install # If MSYS or UNIX, default CMAKE_INSTALL_PREFIX is '/usr/local' + # otherwise if WINDOWS, installs to ../bin, ../include, and ../lib + - make uninstall # Delete the files in install_manifest.txt The ".." is important. It refers to the parent directory (i.e., qhull/) + + CMake installs lib/pkgconfig/qhull*.pc for use with pkg-config - On Windows, CMake installs to C:/Program Files/qhull. 64-bit generators - have a "Win64" tag. Qhull's data structures are substantial larger as - 64-bit code than as 32-bit code. This may slow down Qhull. + If CMAKE_INSTALL_PREFIX is C:/Program Files/qhull, you may need to give 'Users' "full control" + to qhull's sub-directories: bin, doc, include, lib, and man (folder > Properties > Security > Edit > Users). - If creating a qhull package, please include a pkg-config file based on build/qhull*.pc.in + On Windows, CMake's 64-bit generators have a "Win64" tag. Qhull's data structures + are substantial larger as 64-bit code than as 32-bit code. This may slow down Qhull. If cmake fails with "No CMAKE_C_COMPILER could be found" - cmake was not able to find the build environment specified by -G "..." - ------------------ + + If cmake's gcc smoketest fails after a Windows update + - Reinstall MINGW-w64 and delete CMakeCache.txt. A Windows update can break gcc process creation for cc1. + +------------------ Installing Qhull with Qt - To build Qhull, including its C++ test (qhulltest) + To build Qhull, including its C++ test program (qhulltest) - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - Load src/qhull-all.pro into QtCreator + - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' + If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' - Build + + - Build qhulltest with a C++11 or later compiler - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied - into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., qt/5.11.2/msvc2017_64/bin - - If qhulltest fails without an error message, check for missing Q54Core.dll and Qt5Test.dll + into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin + - If qhulltest fails with exit status 127 and no error message, + check for missing Q5Core.dll and Qt5Test.dll -------------------- +------------------ Working with Qhull's C++ interface See html/qh-code.htm#cpp for calling Qhull from C++ programs + Class and method documentation is limited + See html/qh-code.htm#reentrant for converting from Qhull-2012 Examples of using the C++ interface @@ -212,16 +239,29 @@ Working with Qhull's C++ interface git checkout next ... git pull origin next - + Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries use the C routines setjmp() and longjmp() for error handling. They must be compiled with the same compiler. - -------------------- + + Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc + With back-ticks, you can compile your C++ program with the Qhull libraries: + g++ `pkg-config --cflags --libs qhullcpp qhullstatic_r` -o my_app my_app.cpp + or + g++ `pkg-config --cflags --libs qhullcpp qhull_r` -o my_app my_app.cpp + + qhullcpp must be linked before qhull_r, otherwise the linker reports + an error -- "QhullUser ... multiple definition of `qh_fprintf'" + +------------------ Calling Qhull from C programs See html/qh-code.htm#library for calling Qhull from C programs + Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc + With back-ticks, you can compile your C program with the Qhull library + gcc `pkg-config --cflags --libs qhull_r` -o my_app my_app.c + See html/qh-code.htm#reentrant for converting from Qhull-2012 Warning: You will need to understand Qhull's data structures and read the @@ -236,14 +276,15 @@ Calling Qhull from C programs This allows the same code to use static memory or heap memory. If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; otherwise qh_qh is a global static data structure of type qhT. - + ------------------ Compiling Qhull with Microsoft Visual C++ To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull-32.sln + - Load solution build/qhull-32.sln - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset + File > Save All - Build target 'Win32' - Project qhulltest requires Qt for DevStudio (http://www.qt.io) Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) @@ -255,14 +296,18 @@ Compiling Qhull with Microsoft Visual C++ - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - Load solution build/qhull-64.sln - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset + File > Save All - Build target 'x64' + - If build as 32-bit fails, use solution build/qhull-32.sln - Project qhulltest requires Qt for DevStudio (http://www.qt.io) Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' If error -- MSB8020: The build tools for Visual Studio 2012 (Platform Toolset = 'v110') cannot be found. - 'Project > Retarget solution' for both qhull-32.sln and qhull-64.sln + - 'File > Open' your preferred solution (qhull-32.sln or qhull-64.sln) - 'Save All' both projects + - DevStudio may need a restart To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) - Download and extract Qhull (either GitHub, .tgz file, or .zip file) @@ -271,46 +316,58 @@ Compiling Qhull with Microsoft Visual C++ - Project qhulltest requires Qt for DevStudio (http://www.qt.io) Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - ------------------ + +------------------ Compiling Qhull with Qt Creator Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) - + To compile Qhull with Qt Creator - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - Download the Qt SDK - Start Qt Creator - Load src/qhull-all.pro + - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' + If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' - Build + + - Build qhulltest with a C++11 or later compiler + - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied + into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin + - If qhulltest fails with exit status 127 and no error message, + check for missing Q5Core.dll and Qt5Test.dll ------------------ +------------------ Compiling Qhull with mingw/gcc on Windows To compile Qhull with MINGW - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - Install GitForWindows (https://gitforwindows.org/) or MSYS2 (http://www.msys2.org/) - - Install MINGW-w64 - with gcc (https://mingw-w64.org/) - Download installer (https://sourceforge.net/projects/mingw-w64/files/) - Select i686/posix/dwarf - Install in C:\mingw-w64\... # Not 'Program Files\...' - Rename mingw32/bin/mingw32-make.exe to make.exe - Add the 'bin' directory to your $PATH environment variable + Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' will not work + - Install MINGW-w64 with gcc (https://mingw-w64.org/) + 1) Goto sourceforge -- https://sourceforge.net/projects/mingw-w64/files/ + 2) in folder -- mingw-w64 + 3) download installer -- MinGW-W64-install.exe + Run the installer + 1) Select i686/posix/dwarf + 2) Install in 'C:\mingw-w64' # Not 'Program Files\...' + Rename /c/mingw-w64/mingw32/bin/mingw32-make.exe to make.exe + Add the 'C:\mingw-w64\mingw32\bin' directory to your $PATH environment variable + Execute 'which make' to check that 'make' is mingw-w64's make - Compile Qhull from the home directory make help make - + Notes - Mingw is included with Qt SDK in qt/Tools/mingw53_32 - - For Windows XP + - If you use Windows XP Install Road Bash (http://www.qhull.org/bash) or MSYS (http://www.mingw.org/wiki/msys) Install MINGW (http://mingw.org/) - ------------------ + +------------------ Compiling Qhull with cygwin on Windows To compile Qhull with cygwin @@ -319,18 +376,18 @@ Compiling Qhull with cygwin on Windows - Include packages for gcc, make, ar, and ln - make ------------------ +------------------ Compiling from Makfile without gcc The file, qhull-src.tgz, contains documentation and source files for qhull and rbox. - + To unpack the tgz file - tar zxf qhull-src.tgz - cd qhull - Use qhull/Makefile Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile - + Compiling qhull and rbox with Makefile - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines - the defaults are gcc and enscript @@ -374,16 +431,16 @@ Compiling on other machines and compilers Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set the ANSI option in Options:Project:Compiler:Source:Language-compliance. - + Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". Use the same options for Qhull 1.0. [D. Zwick] - + If you have troubles with the memory manager, you can turn it off by defining qh_NOmem in mem.h. ------------------ +------------------ Distributed files README.txt // Instructions for installing Qhull @@ -392,21 +449,23 @@ Distributed files QHULL-GO.lnk // Windows icon for eg/qhull-go.bat Announce.txt // Announcement CMakeLists.txt // CMake build file (2.6 or later) - CMakeModules/CheckLFS.cmake // enables Large File Support in cmake File_id.diz // Package descriptor index.htm // Home page Makefile // Makefile for gcc and other compilers qhull*.md5sum // md5sum for all files bin/* // Qhull executables and dll (.zip only) + build/CMakeModules/CheckLFS.cmake // enables Large File Support in CMake build/config.cmake.in // extract target variables - build/qhull*.pc.in // pkg-config templates for qhull_r, qhull, and qhull_p + build/qhull.pc.in // pkg-config template for creating lib/pkgconfig/qhull*.pc build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) build/*-32.vcxproj build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) build/*-64.vcxproj build/qhull.sln // DevStudio solution and project files (2005 and 2009) build/*.vcproj + build/qhulltest/ // DevStudio project files for qhulltest (C++ and Qt) + build/README-build.txt // Contents of build/ eg/* // Test scripts and geomview files from q_eg html/index.htm // Manual html/qh-faq.htm // Frequently asked questions @@ -571,7 +630,7 @@ src/libqhullcpp/ Qhull.cpp // Calls libqhull_r.c from C++ Qhull.h qt-qhull.cpp // Supporting methods for Qt - + Coordinates.cpp // input classes Coordinates.h @@ -591,7 +650,7 @@ src/libqhullcpp/ QhullRidge.h QhullVertex.cpp QhullVertex.h - + QhullFacetList.cpp // collection classes QhullFacetList.h QhullFacetSet.cpp @@ -615,6 +674,8 @@ src/libqhullcpp/ QhullQh.h QhullStat.cpp QhullStat.h + QhullUser.cpp + QhullUser.h RoadError.cpp // Supporting base classes RoadError.h RoadLogEvent.cpp @@ -623,10 +684,10 @@ src/libqhullcpp/ src/libqhullstatic/ libqhullstatic.pro // Qt project for non-reentrant, static library - + src/libqhullstatic_r/ libqhullstatic_r.pro // Qt project for reentrant, static library - + src/qhulltest/ qhulltest.pro // Qt project for test of C++ interface Coordinates_test.cpp // Test of each class @@ -648,12 +709,12 @@ src/qhulltest/ RoadTest.cpp // Run multiple test files with QTestLib RoadTest.h ------------------ +------------------ Authors C. Bradford Barber Hannu Huhdanpaa (Version 1.0) bradb@shore.net hannu@qhull.org - + Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard - University. If you find Qhull useful, please let us know. + University. If you find Qhull useful, please let us know. \ No newline at end of file diff --git a/scipy/spatial/qhull_src/src/geom2_r.c b/scipy/spatial/qhull_src/src/geom2_r.c index e8e189a80872..9e0f997f6327 100644 --- a/scipy/spatial/qhull_src/src/geom2_r.c +++ b/scipy/spatial/qhull_src/src/geom2_r.c @@ -7,9 +7,9 @@ see qh-geom_r.htm and geom_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom2_r.c#15 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom2_r.c#17 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ frequently used code goes into geom_r.c */ @@ -27,7 +27,8 @@ notes: qh_free the returned points to avoid a memory leak */ -coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) { +coordT *qh_copypoints(qhT *qh, coordT *points, int numpoints, int dimension) +{ int size; coordT *newpoints; diff --git a/scipy/spatial/qhull_src/src/geom_r.c b/scipy/spatial/qhull_src/src/geom_r.c index a7a1cb0015f2..22faead499fb 100644 --- a/scipy/spatial/qhull_src/src/geom_r.c +++ b/scipy/spatial/qhull_src/src/geom_r.c @@ -6,9 +6,9 @@ see qh-geom_r.htm and geom_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom_r.c#4 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom_r.c#5 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ infrequent code goes into geom2_r.c */ diff --git a/scipy/spatial/qhull_src/src/geom_r.h b/scipy/spatial/qhull_src/src/geom_r.h index a5f76452f5f1..f3f8ee814002 100644 --- a/scipy/spatial/qhull_src/src/geom_r.h +++ b/scipy/spatial/qhull_src/src/geom_r.h @@ -6,9 +6,9 @@ see qh-geom_r.htm and geom_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/geom_r.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/geom_r.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFgeom diff --git a/scipy/spatial/qhull_src/src/global_r.c b/scipy/spatial/qhull_src/src/global_r.c index 3e6919f5c422..04b9b4d74ec2 100644 --- a/scipy/spatial/qhull_src/src/global_r.c +++ b/scipy/spatial/qhull_src/src/global_r.c @@ -11,9 +11,9 @@ see qhull_ra.h for internal functions - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/global_r.c#12 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/global_r.c#19 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -33,14 +33,14 @@ change date: Changes.txt, Announce.txt, index.htm, README.txt, qhull-news.html, Eudora signatures, CMakeLists.txt change version: README.txt, qh-get.htm, File_id.diz, Makefile.txt, CMakeLists.txt - check that CmakeLists @version is the same as qh_version2 + check that CMakeLists.txt @version is the same as qh_version2 change year: Copying.txt check download size recompile user_eg_r.c, rbox_r.c, libqhull_r.c, qconvex_r.c, qdelaun_r.c qvoronoi_r.c, qhalf_r.c, testqset_r.c */ -const char qh_version[]= "2019.1.r 2019/06/21"; -const char qh_version2[]= "qhull_r 7.3.2 (2019.1.r 2019/06/21)"; +const char qh_version[]= "2020.2.r 2020/08/31"; +const char qh_version2[]= "qhull_r 8.0.2 (2020.2.r 2020/08/31)"; /*--------------------------------- @@ -1517,7 +1517,7 @@ void qh_initflags(qhT *qh, char *command) { lastwarning= command; } if (lastwarning && !qh->ALLOWwarning) { - qh_fprintf(qh, qh->ferr, 6035, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", + qh_fprintf(qh, qh->ferr, 6035, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", command, (int)(lastwarning-command)); qh_errexit(qh, qh_ERRinput, NULL, NULL); } @@ -1626,7 +1626,7 @@ void qh_initqhull_globals(qhT *qh, coordT *points, int numpoints, int dim, boolT #endif } if (qh->TRIangulate && qh->JOGGLEmax < REALmax/2 && !qh->PREmerge && !qh->POSTmerge && qh->PRINTprecision) - qh_fprintf(qh, qh->ferr, 7038, "qhull option warning: joggle ('QJ') produces simplicial output (i.e., triangles in 2-D). Unless merging is requested, option 'Qt' has no effect\n"); + qh_fprintf(qh, qh->ferr, 7038, "qhull option warning: joggle ('QJ') produces simplicial output (i.e., triangles in 2-D). Unless merging is requested, option 'Qt' has no effect\n"); if (qh->JOGGLEmax < REALmax/2 && qh->DELAUNAY && !qh->SCALEinput && !qh->SCALElast) { qh->SCALElast= True; qh_option(qh, "Qbbound-last-qj", NULL, NULL); @@ -2099,14 +2099,14 @@ void qh_initthresholds(qhT *qh, char *command) { if (!isdigit(*s)) { qh_fprintf(qh, qh->ferr, 7047, "qhull option warning: no dimension given for Qhull option 'Q%c'\n", key); - lastwarning= lastoption; + lastwarning= lastoption; continue; } idx= qh_strtol(s, &s); if (idx >= maxdim) { qh_fprintf(qh, qh->ferr, 7048, "qhull option warning: dimension %d for Qhull option 'Q%c' is >= %d. Ignored\n", idx, key, maxdim); - lastwarning= lastoption; + lastwarning= lastoption; continue; } if (*s == ':') { @@ -2141,7 +2141,7 @@ void qh_initthresholds(qhT *qh, char *command) { qh->GOODthreshold= True; } if (lastwarning && !qh->ALLOWwarning) { - qh_fprintf(qh, qh->ferr, 6036, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", + qh_fprintf(qh, qh->ferr, 6036, "qhull option error: see previous warnings, use 'Qw' to override: '%s' (last offset %d)\n", command, (int)(lastwarning-command)); qh_errexit(qh, qh_ERRinput, NULL, NULL); } @@ -2205,7 +2205,7 @@ void qh_lib_check(int qhullLibraryType, int qhTsize, int vertexTsize, int ridgeT last_errcode= 6254; } if (last_errcode) { - qh_fprintf_stderr(6259, "qhull internal error (qh_lib_check): Cannot continue due to QH%d. '%s' is not reentrant (e.g., qhull.so) or out-of-date. Exit with %d\n", + qh_fprintf_stderr(6259, "qhull internal error (qh_lib_check): Cannot continue due to QH%d. '%s' is not reentrant (e.g., qhull.so) or out-of-date. Exit with %d\n", last_errcode, qh_version2, last_errcode - 6200); qh_exit(last_errcode - 6200); /* can not use qh_errexit(), must be less than 255 */ } diff --git a/scipy/spatial/qhull_src/src/io_r.c b/scipy/spatial/qhull_src/src/io_r.c index 4c05920da901..a80a5b14a47c 100644 --- a/scipy/spatial/qhull_src/src/io_r.c +++ b/scipy/spatial/qhull_src/src/io_r.c @@ -13,9 +13,9 @@ unix_r.c and user_r.c are the only callers of io_r.c functions This allows the user to avoid loading io_r.o from qhull.a - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/io_r.c#7 $$Change: 2683 $ - $DateTime: 2019/06/14 16:05:16 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/io_r.c#12 $$Change: 2965 $ + $DateTime: 2020/06/04 15:37:41 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -383,6 +383,7 @@ pointT *qh_detvnorm(qhT *qh, vertexT *vertex, vertexT *vertexA, setT *centers, r normal= gmcoord; qh_sethyperplane_gauss(qh, dim, qh->gm_row, point0, True, normal, &offset, &nearzero); + /* nearzero is true for axis-parallel hyperplanes (e.g., a bounding box). Should detect degenerate hyperplanes. See 'Tv' check following */ if (qh->GOODvertexp == vertexA->point) inpoint= vertexA->point; else @@ -1016,43 +1017,75 @@ setT *qh_markvoronoi(qhT *qh, facetT *facetlist, setT *facets, boolT printall, b >-------------------------------- qh_order_vertexneighbors(qh, vertex ) - order facet neighbors of a 2-d or 3-d vertex by adjacency + order facet neighbors of vertex by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id) notes: - does not orient the neighbors - - design: + error if qh_vertexneighbors not called beforehand + only 2-d orients the neighbors + for 4-d and higher + set or clear f.visitid for qh_compare_facetvisit + for example, use qh_markvoronoi (e.g., qh_printvornoi) or qh_countfacets (e.g., qh_printvneighbors) + + design (2-d): + see qh_printextremes_2d + design (3-d): initialize a new neighbor set with the first facet in vertex->neighbors while vertex->neighbors non-empty select next neighbor in the previous facet's neighbor set set vertex->neighbors to the new neighbor set + design (n-d): + qsort by f.visitid, or f.facetid (qh_compare_facetvisit) + facet_id is negated (sorted before visit_id facets) */ void qh_order_vertexneighbors(qhT *qh, vertexT *vertex) { setT *newset; - facetT *facet, *neighbor, **neighborp; + facetT *facet, *facetA, *facetB, *neighbor, **neighborp; + vertexT *vertexA; + int numneighbors; - trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order neighbors of v%d for 3-d\n", vertex->id)); - newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors)); - facet= (facetT *)qh_setdellast(vertex->neighbors); - qh_setappend(qh, &newset, facet); - while (qh_setsize(qh, vertex->neighbors)) { - FOREACHneighbor_(vertex) { - if (qh_setin(facet->neighbors, neighbor)) { - qh_setdel(vertex->neighbors, neighbor); - qh_setappend(qh, &newset, neighbor); - facet= neighbor; - break; - } + trace4((qh, qh->ferr, 4018, "qh_order_vertexneighbors: order facet neighbors of v%d by 2-d (orientation), 3-d (adjacency), or n-d (f.visitid,id)\n", vertex->id)); + if (!qh->VERTEXneighbors) { + qh_fprintf(qh, qh->ferr, 6428, "qhull internal error (qh_order_vertexneighbors): call qh_vertexneighbors before calling qh_order_vertexneighbors\n"); + qh_errexit(qh, qh_ERRqhull, NULL, NULL); + } + if (qh->hull_dim == 2) { + facetA= SETfirstt_(vertex->neighbors, facetT); + if (facetA->toporient ^ qh_ORIENTclock) + vertexA= SETfirstt_(facetA->vertices, vertexT); + else + vertexA= SETsecondt_(facetA->vertices, vertexT); + if (vertexA!=vertex) { + facetB= SETsecondt_(vertex->neighbors, facetT); + SETfirst_(vertex->neighbors)= facetB; + SETsecond_(vertex->neighbors)= facetA; } - if (!neighbor) { - qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n", - vertex->id, facet->id); - qh_errexit(qh, qh_ERRqhull, facet, NULL); + }else if (qh->hull_dim == 3) { + newset= qh_settemp(qh, qh_setsize(qh, vertex->neighbors)); + facet= (facetT *)qh_setdellast(vertex->neighbors); + qh_setappend(qh, &newset, facet); + while (qh_setsize(qh, vertex->neighbors)) { + FOREACHneighbor_(vertex) { + if (qh_setin(facet->neighbors, neighbor)) { + qh_setdel(vertex->neighbors, neighbor); + qh_setappend(qh, &newset, neighbor); + facet= neighbor; + break; + } + } + if (!neighbor) { + qh_fprintf(qh, qh->ferr, 6066, "qhull internal error (qh_order_vertexneighbors): no neighbor of v%d for f%d\n", + vertex->id, facet->id); + qh_errexit(qh, qh_ERRqhull, facet, NULL); + } } + qh_setfree(qh, &vertex->neighbors); + qh_settemppop(qh); + vertex->neighbors= newset; + }else { /* qh.hull_dim >= 4 */ + numneighbors= qh_setsize(qh, vertex->neighbors); + qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors, + sizeof(facetT *), qh_compare_facetvisit); } - qh_setfree(qh, &vertex->neighbors); - qh_settemppop(qh); - vertex->neighbors= newset; } /* order_vertexneighbors */ /*-neighbors); qh_fprintf(qh, fp, 9249, "%d", numneighbors); - if (qh->hull_dim == 3) - qh_order_vertexneighbors(qh, vertex); - else if (qh->hull_dim >= 4) - qsort(SETaddr_(vertex->neighbors, facetT), (size_t)numneighbors, - sizeof(facetT *), qh_compare_facetvisit); + qh_order_vertexneighbors(qh, vertex); FOREACHneighbor_(vertex) qh_fprintf(qh, fp, 9250, " %d", neighbor->visitid ? neighbor->visitid - 1 : 0 - neighbor->id); @@ -3467,12 +3496,7 @@ void qh_printvoronoi(qhT *qh, FILE *fp, qh_PRINT format, facetT *facetlist, setT numneighbors= 0; numinf=0; if (vertex) { - if (qh->hull_dim == 3) - qh_order_vertexneighbors(qh, vertex); - else if (qh->hull_dim >= 4) - qsort(SETaddr_(vertex->neighbors, facetT), - (size_t)qh_setsize(qh, vertex->neighbors), - sizeof(facetT *), qh_compare_facetvisit); + qh_order_vertexneighbors(qh, vertex); FOREACHneighbor_(vertex) { if (neighbor->visitid == 0) numinf= 1; diff --git a/scipy/spatial/qhull_src/src/io_r.h b/scipy/spatial/qhull_src/src/io_r.h index 94ea9c1329b0..eb3c75149238 100644 --- a/scipy/spatial/qhull_src/src/io_r.h +++ b/scipy/spatial/qhull_src/src/io_r.h @@ -6,9 +6,9 @@ see README, libqhull_r.h and io_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/io_r.h#2 $$Change: 2671 $ - $DateTime: 2019/06/06 11:24:01 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/io_r.h#3 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFio diff --git a/scipy/spatial/qhull_src/src/libqhull_r.c b/scipy/spatial/qhull_src/src/libqhull_r.c index 7754fa5fad35..0d41d7be0539 100644 --- a/scipy/spatial/qhull_src/src/libqhull_r.c +++ b/scipy/spatial/qhull_src/src/libqhull_r.c @@ -10,9 +10,9 @@ see qhull_ra.h for internal functions - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.c#16 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.c#17 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" diff --git a/scipy/spatial/qhull_src/src/libqhull_r.h b/scipy/spatial/qhull_src/src/libqhull_r.h index a48dd4e585dd..376c1e20ff41 100644 --- a/scipy/spatial/qhull_src/src/libqhull_r.h +++ b/scipy/spatial/qhull_src/src/libqhull_r.h @@ -6,9 +6,9 @@ see qh-qhull_r.htm, qhull_ra.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.h#13 $$Change: 2714 $ - $DateTime: 2019/06/28 16:16:13 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/libqhull_r.h#16 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ includes function prototypes for libqhull_r.c, geom_r.c, global_r.c, io_r.c, user_r.c @@ -188,7 +188,7 @@ typedef enum {qh_PRINTnone= 0, #define qh_ERRother 6 /* other error detected */ #define qh_ERRtopology 7 /* topology error, maybe due to nearly adjacent vertices, calls qh_printhelp_topology */ #define qh_ERRwide 8 /* wide facet error, maybe due to nearly adjacent vertices, calls qh_printhelp_wide */ -#define qh_ERRdebug 9 /* qh_errexit from debugging code */ +#define qh_ERRdebug 9 /* qh_errexit from debugging code */ /*---------------------------------- @@ -424,7 +424,7 @@ struct vertexT { initialized in io_r.c or after first merge qh_update_vertices for qh_addpoint or qh_triangulate updated by merges - qh_order_vertexneighbors for 2-d and 3-d */ + qh_order_vertexneighbors by 2-d (orientation) 3-d (adjacency), n-d (f.visitid,id) */ unsigned int id; /* unique identifier, 1..qh.vertex_id, 0 for sentinel, printed as 'r%d' */ unsigned int visitid; /* for use with qh.vertex_visit, size must match */ flagT seen:1; /* used to perform operations only once */ @@ -851,6 +851,8 @@ struct qhT { int rbox_isinteger; double rbox_out_offset; void * cpp_object; /* C++ pointer. Currently used by RboxPoints.qh_fprintf_rbox */ + void * cpp_other; /* C++ pointer. Reserved for other users */ + void * cpp_user; /* C++ pointer. Currently used by QhullUser.qh_fprintf */ /* Last, otherwise zero'd by qh_initqhull_start2 (global_r.c */ qhmemT qhmem; /* Qhull managed memory (mem_r.h) */ diff --git a/scipy/spatial/qhull_src/src/mem_r.c b/scipy/spatial/qhull_src/src/mem_r.c index 1f865f4f69b9..7d5509eb4f4d 100644 --- a/scipy/spatial/qhull_src/src/mem_r.c +++ b/scipy/spatial/qhull_src/src/mem_r.c @@ -29,9 +29,9 @@ qh-mem_r.htm and mem_r.h global_r.c (qh_initbuffers) for an example of using mem_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/mem_r.c#6 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/mem_r.c#7 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "libqhull_r.h" /* includes user_r.h and mem_r.h */ diff --git a/scipy/spatial/qhull_src/src/mem_r.h b/scipy/spatial/qhull_src/src/mem_r.h index f38aabd066c6..aeb761b100ef 100644 --- a/scipy/spatial/qhull_src/src/mem_r.h +++ b/scipy/spatial/qhull_src/src/mem_r.h @@ -11,9 +11,9 @@ and qh_errexit(qhT *qh, qhmem_ERRqhull, NULL, NULL) otherwise - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/mem_r.h#5 $$Change: 2698 $ - $DateTime: 2019/06/24 14:52:34 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/mem_r.h#6 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFmem diff --git a/scipy/spatial/qhull_src/src/merge_r.c b/scipy/spatial/qhull_src/src/merge_r.c index ee6b3116520d..f3c899cd6e66 100644 --- a/scipy/spatial/qhull_src/src/merge_r.c +++ b/scipy/spatial/qhull_src/src/merge_r.c @@ -20,9 +20,9 @@ merges occur in qh_mergefacet and in qh_mergecycle vertex->neighbors not set until the first merge occurs - Copyright (c) 1993-2019 C.B. Barber. - $Id: //main/2019/qhull/src/libqhull_r/merge_r.c#12 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 C.B. Barber. + $Id: //main/2019/qhull/src/libqhull_r/merge_r.c#14 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -452,7 +452,7 @@ void qh_appendmergeset(qhT *qh, facetT *facet, facetT *neighbor, mergeType merge merge->ridge1= NULL; merge->ridge2= NULL; merge->mergetype= mergetype; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -528,7 +528,7 @@ void qh_appendvertexmerge(qhT *qh, vertexT *vertex, vertexT *destination, mergeT merge->ridge1= ridge1; merge->ridge2= ridge2; merge->mergetype= mergetype; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -3367,7 +3367,7 @@ void qh_mergefacet(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype, int tracerestore=0, nummerge; const char *mergename; - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; @@ -5298,7 +5298,7 @@ void qh_tracemerge(qhT *qh, facetT *facet1, facetT *facet2, mergeType mergetype) const char *mergename; #ifndef qh_NOtrace - if(mergetype > 0 && mergetype <= sizeof(mergetypes)) + if(mergetype > 0 && mergetype < sizeof(mergetypes)/sizeof(char *)) mergename= mergetypes[mergetype]; else mergename= mergetypes[MRGnone]; diff --git a/scipy/spatial/qhull_src/src/merge_r.h b/scipy/spatial/qhull_src/src/merge_r.h index ded13a629852..a0d4091ec87a 100644 --- a/scipy/spatial/qhull_src/src/merge_r.h +++ b/scipy/spatial/qhull_src/src/merge_r.h @@ -6,9 +6,9 @@ see qh-merge_r.htm and merge_r.c - Copyright (c) 1993-2019 C.B. Barber. - $Id: //main/2019/qhull/src/libqhull_r/merge_r.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 C.B. Barber. + $Id: //main/2019/qhull/src/libqhull_r/merge_r.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFmerge diff --git a/scipy/spatial/qhull_src/src/poly2_r.c b/scipy/spatial/qhull_src/src/poly2_r.c index 11ea4e223a61..1ab52444ffc3 100644 --- a/scipy/spatial/qhull_src/src/poly2_r.c +++ b/scipy/spatial/qhull_src/src/poly2_r.c @@ -8,9 +8,9 @@ frequently used code is in poly_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly2_r.c#18 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly2_r.c#20 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -2941,10 +2941,38 @@ vertexT *qh_newvertex(qhT *qh, pointT *point) { return(vertex); } /* newvertex */ +/*--------------------------------- + + qh_nextfacet2d( facet, &nextvertex ) + return next facet and vertex for a 2d facet in qh_ORIENTclock order + returns NULL on error + + notes: + in qh_ORIENTclock order (default counter-clockwise) + nextvertex is in between the two facets + does not use qhT or qh_errexit [QhullFacet.cpp] + + design: + see io_r.c/qh_printextremes_2d +*/ +facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp) { + facetT *nextfacet; + + if (facet->toporient ^ qh_ORIENTclock) { + *nextvertexp= SETfirstt_(facet->vertices, vertexT); + nextfacet= SETfirstt_(facet->neighbors, facetT); + }else { + *nextvertexp= SETsecondt_(facet->vertices, vertexT); + nextfacet= SETsecondt_(facet->neighbors, facetT); + } + return nextfacet; +} /* nextfacet2d */ + /*--------------------------------- - qh_nextridge3d( atridge, facet, vertex ) + qh_nextridge3d( atridge, facet, &vertex ) return next ridge and vertex for a 3d facet returns NULL on error [for QhullFacet::nextRidge3d] Does not call qh_errexit nor access qhT. diff --git a/scipy/spatial/qhull_src/src/poly_r.c b/scipy/spatial/qhull_src/src/poly_r.c index 6e769437c3eb..d6a5e7a3d8c5 100644 --- a/scipy/spatial/qhull_src/src/poly_r.c +++ b/scipy/spatial/qhull_src/src/poly_r.c @@ -9,9 +9,9 @@ infrequent code is in poly2_r.c (all but top 50 and their callers 12/3/95) - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly_r.c#7 $$Change: 2705 $ - $DateTime: 2019/06/26 16:34:45 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly_r.c#8 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "qhull_ra.h" diff --git a/scipy/spatial/qhull_src/src/poly_r.h b/scipy/spatial/qhull_src/src/poly_r.h index 13dbbf4e23c8..83c59140de7d 100644 --- a/scipy/spatial/qhull_src/src/poly_r.h +++ b/scipy/spatial/qhull_src/src/poly_r.h @@ -6,9 +6,9 @@ see qh-poly_r.htm, libqhull_r.h and poly_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/poly_r.h#3 $$Change: 2701 $ - $DateTime: 2019/06/25 15:24:47 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/poly_r.h#5 $$Change: 2963 $ + $DateTime: 2020/06/03 19:31:01 $$Author: bbarber $ */ #ifndef qhDEFpoly @@ -279,6 +279,7 @@ void qh_nearcoplanar(qhT *qh /* qh.facet_list */); vertexT *qh_nearvertex(qhT *qh, facetT *facet, pointT *point, realT *bestdistp); int qh_newhashtable(qhT *qh, int newsize); vertexT *qh_newvertex(qhT *qh, pointT *point); +facetT *qh_nextfacet2d(facetT *facet, vertexT **nextvertexp); ridgeT *qh_nextridge3d(ridgeT *atridge, facetT *facet, vertexT **vertexp); vertexT *qh_opposite_vertex(qhT *qh, facetT *facetA, facetT *neighbor); void qh_outcoplanar(qhT *qh /* qh.facet_list */); diff --git a/scipy/spatial/qhull_src/src/qhull_ra.h b/scipy/spatial/qhull_src/src/qhull_ra.h index a3ba3d2d6350..52ccd85a02a8 100644 --- a/scipy/spatial/qhull_src/src/qhull_ra.h +++ b/scipy/spatial/qhull_src/src/qhull_ra.h @@ -13,9 +13,9 @@ defines internal functions for libqhull_r.c global_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qhull_ra.h#1 $$Change: 2661 $ - $DateTime: 2019/05/24 20:09:58 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qhull_ra.h#2 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ Notes: grep for ((" and (" to catch fprintf("lkasdjf"); full parens around (x?y:z) diff --git a/scipy/spatial/qhull_src/src/qset_r.c b/scipy/spatial/qhull_src/src/qset_r.c index 5f7095b4cb7d..c3bec5ffa49c 100644 --- a/scipy/spatial/qhull_src/src/qset_r.c +++ b/scipy/spatial/qhull_src/src/qset_r.c @@ -13,9 +13,9 @@ Only reference qh for qhmem or qhstat. Otherwise the matching code in qset.c will bring in qhT - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qset_r.c#7 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qset_r.c#8 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #include "libqhull_r.h" /* for qhT and QHULL_CRTDBG */ diff --git a/scipy/spatial/qhull_src/src/qset_r.h b/scipy/spatial/qhull_src/src/qset_r.h index 81c16dc2f300..b41dac0084ee 100644 --- a/scipy/spatial/qhull_src/src/qset_r.h +++ b/scipy/spatial/qhull_src/src/qset_r.h @@ -16,9 +16,9 @@ - every set is NULL terminated - sets may be sorted or unsorted, the caller must distinguish this - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/qset_r.h#3 $$Change: 2700 $ - $DateTime: 2019/06/25 05:52:18 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/qset_r.h#4 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFset diff --git a/scipy/spatial/qhull_src/src/random_r.h b/scipy/spatial/qhull_src/src/random_r.h index 70cfe1c9b515..a17549d3b933 100644 --- a/scipy/spatial/qhull_src/src/random_r.h +++ b/scipy/spatial/qhull_src/src/random_r.h @@ -6,9 +6,9 @@ see qh-geom_r.htm and random_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/random_r.h#2 $$Change: 2666 $ - $DateTime: 2019/05/30 10:11:25 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/random_r.h#3 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ */ #ifndef qhDEFrandom diff --git a/scipy/spatial/qhull_src/src/stat_r.c b/scipy/spatial/qhull_src/src/stat_r.c index efc16ffc6808..5661e010e45b 100644 --- a/scipy/spatial/qhull_src/src/stat_r.c +++ b/scipy/spatial/qhull_src/src/stat_r.c @@ -6,9 +6,9 @@ see qh-stat_r.htm and stat_r.h - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/stat_r.c#7 $$Change: 2712 $ - $DateTime: 2019/06/28 12:57:00 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/stat_r.c#9 $$Change: 3037 $ + $DateTime: 2020/09/03 17:28:32 $$Author: bbarber $ */ #include "qhull_ra.h" @@ -714,7 +714,7 @@ realT qh_stddev(qhT *qh, int num, realT tot, realT tot2, realT *ave) { /* for qh QHULL_UNUSED(tot) QHULL_UNUSED(tot2) QHULL_UNUSED(ave) - + return 0.0; } #endif /* qh_KEEPstatistics */ diff --git a/scipy/spatial/qhull_src/src/stat_r.h b/scipy/spatial/qhull_src/src/stat_r.h index 4aaef8681bbd..41b6e5171ddc 100644 --- a/scipy/spatial/qhull_src/src/stat_r.h +++ b/scipy/spatial/qhull_src/src/stat_r.h @@ -6,9 +6,9 @@ see qh-stat_r.htm and stat_r.c - Copyright (c) 1993-2019 The Geometry Center. - $Id: //main/2019/qhull/src/libqhull_r/stat_r.h#3 $$Change: 2711 $ - $DateTime: 2019/06/27 22:34:56 $$Author: bbarber $ + Copyright (c) 1993-2020 The Geometry Center. + $Id: //main/2019/qhull/src/libqhull_r/stat_r.h#4 $$Change: 2953 $ + $DateTime: 2020/05/21 22:05:32 $$Author: bbarber $ recompile qhull if you change this file diff --git a/scipy/spatial/qhull_src/src/user_r.c b/scipy/spatial/qhull_src/src/user_r.c index 745c50ff3055..4f554a45964a 100644 --- a/scipy/spatial/qhull_src/src/user_r.c +++ b/scipy/spatial/qhull_src/src/user_r.c @@ -149,7 +149,7 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc } trace1((qh, qh->ferr, 1044, "qh_new_qhull: build new Qhull for %d %d-d points with %s\n", numpoints, dim, qhull_cmd)); exitcode= setjmp(qh->errexit); - if (!exitcode){ + if (!exitcode) { qh->NOerrexit= False; qh_initflags(qh, qhull_cmd); if (qh->DELAUNAY) diff --git a/scipy/spatial/qhull_src/src/user_r.h b/scipy/spatial/qhull_src/src/user_r.h index 71128e46aeb4..8c100fac098b 100644 --- a/scipy/spatial/qhull_src/src/user_r.h +++ b/scipy/spatial/qhull_src/src/user_r.h @@ -65,7 +65,7 @@ Code flags -- Cannot use '0031' since it would be octal def counters = [31/32/33/38, 1067, 2113, 3079, 4097, 5006, - 6428, 7027/7028/7035/7068/7070/7102, 8163, 9428, 10000, 11034] + 6429, 7027/7028/7035/7068/7070/7102, 8163, 9428, 10000, 11034] See: qh_ERR* [libqhull_r.h] */ @@ -227,24 +227,24 @@ typedef int countT; #if (qh_CLOCKtype == 1) #if defined(CLOCKS_PER_SECOND) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLOCKS_PER_SECOND #elif defined(CLOCKS_PER_SEC) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLOCKS_PER_SEC #elif defined(CLK_TCK) -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks CLK_TCK #else -#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock */ +#define qh_CPUclock ((unsigned long)clock()) /* return CPU clock, may be converted to approximate double */ #define qh_SECticks 1E6 #endif #elif (qh_CLOCKtype == 2) -#define qh_CPUclock qh_clock() /* return CPU clock */ +#define qh_CPUclock qh_clock() /* return CPU clock, may be converted to approximate double */ #define qh_SECticks 100 #else /* qh_CLOCKtype == ? */ From 4b8ae1a7a8df1d7d2f061d8c77dcc70f1510ee71 Mon Sep 17 00:00:00 2001 From: vfdev Date: Wed, 30 Apr 2025 15:00:35 +0200 Subject: [PATCH 074/251] BUG: fix syntax warning break in finally block under 3.14 (#22913) --- scipy/optimize/_optimize.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index 11fab66f1396..00d847619a40 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -928,15 +928,14 @@ def _minimize_neldermead(func, x0, args=(), callback=None, iterations += 1 except _MaxFuncCallError: pass - finally: - ind = np.argsort(fsim) - sim = np.take(sim, ind, 0) - fsim = np.take(fsim, ind, 0) - if retall: - allvecs.append(sim[0]) - intermediate_result = OptimizeResult(x=sim[0], fun=fsim[0]) - if _call_callback_maybe_halt(callback, intermediate_result): - break + ind = np.argsort(fsim) + sim = np.take(sim, ind, 0) + fsim = np.take(fsim, ind, 0) + if retall: + allvecs.append(sim[0]) + intermediate_result = OptimizeResult(x=sim[0], fun=fsim[0]) + if _call_callback_maybe_halt(callback, intermediate_result): + break x = sim[0] fval = np.min(fsim) From ff28517be863e091b5c524cc85d289e671eb3148 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 30 Apr 2025 21:14:41 +0200 Subject: [PATCH 075/251] MAINT: remove unused images from doc/source/_static/gitpod --- doc/source/_static/gitpod/github-gitpod.png | Bin 22137 -> 0 bytes .../gitpod/gitpod-edit-permissions-gh.png | Bin 23309 -> 0 bytes doc/source/_static/gitpod/gitpod-workspace.png | Bin 72929 -> 0 bytes .../_static/gitpod/gitpod_ci_build_flow.png | Bin 135207 -> 0 bytes .../_static/gitpod/installing-gitpod-io.png | Bin 33186 -> 0 bytes doc/source/_static/gitpod/rst-rendering.png | Bin 228437 -> 0 bytes doc/source/_static/gitpod/scipy-github.png | Bin 14623 -> 0 bytes .../_static/gitpod/scipy-gitpod-branches.png | Bin 96825 -> 0 bytes doc/source/_static/gitpod/vscode-rst.png | Bin 16443 -> 0 bytes doc/source/_static/gitpod/vscode-statusbar.png | Bin 4492 -> 0 bytes 10 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/source/_static/gitpod/github-gitpod.png delete mode 100644 doc/source/_static/gitpod/gitpod-edit-permissions-gh.png delete mode 100644 doc/source/_static/gitpod/gitpod-workspace.png delete mode 100644 doc/source/_static/gitpod/gitpod_ci_build_flow.png delete mode 100644 doc/source/_static/gitpod/installing-gitpod-io.png delete mode 100644 doc/source/_static/gitpod/rst-rendering.png delete mode 100644 doc/source/_static/gitpod/scipy-github.png delete mode 100644 doc/source/_static/gitpod/scipy-gitpod-branches.png delete mode 100644 doc/source/_static/gitpod/vscode-rst.png delete mode 100644 doc/source/_static/gitpod/vscode-statusbar.png diff --git a/doc/source/_static/gitpod/github-gitpod.png b/doc/source/_static/gitpod/github-gitpod.png deleted file mode 100644 index 54a78b016f3ec3019e959f9d8e42f4814b2b88c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22137 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 diff --git a/doc/source/_static/gitpod/gitpod_ci_build_flow.png b/doc/source/_static/gitpod/gitpod_ci_build_flow.png deleted file mode 100644 index 971e89d022a58156a55f25f0df000ac64ef0b6da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135207 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 z

  • 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Μ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

    V*PI@(163>202hP%MYEQW!Ph-nK zj<$=O^8m{G|Dch=gY5K^<}u~Jq)DMm8$+zo3GZj=Vy&5x`E8JLYQuV#A>zn^3Ra{| z;)GelTU|s2!7G+xYCi!+8BQcZJ+da32nQzj?Fp2gnq|P3p#ulpM6~k_>kvV7kYu6O z%hwRoY#8I$j(bW^BzLkXb9A%SBXU8p{qcs8-cQJdn0cSfi$dJb?9unmeK!x4i6qHa z4OAk~IT5GtU;Gfzr9|-z5YR@B!2NjYEKVuOpOQh=XKC_wOk^Ba1T$m&@o4w^Xes6_ zBVrB}1i8PBO~wx zmVQGjMIjNevqxuFBIdZt0%^>f`%!`gvfVza%>MD3un%+Q!}T{3t~L(QGnRd8{%7bJ z>Bmpl2S%Y5F}AoED@Nm^PIhRa&ATja&uDnm=ovotliO(PVUtioPJo(pM$(r4rB`2MpzL-K!OERfo!`t6AXiSR(hYo8|6X<6Bg z3knsuCPl7>g9LdG?9AOshPk`}zekszGXfZQx{5S2Eo4$V8IU#>Xrf0z@nXSo^Ne6U znZlT4On*jr8F%V1>k@soTN zKNRiX!ffEkL!RU?4w!u=%4Asly}dAKTmZ>I+by4Ls^i7j;-sp zNbbL~Sj|p`Gz`m}xFWh~p1*$1HR_2WKSwiixs`_n>iU_rlqgT$nx-dpw{P;trN07O zycdJmdY?fhnXJ+JRQ=FM;8d9#Ob z4epvWxDNY8uuy^{(ipX*I+FuUXr>2Oo~Om1TZD;1;_Y6T27Pp#rcUZF8*J!a&n~a_ zt~phf1V|=O{`?QS^Qd5~g3q*4UhFIXJ;oCmZw#)E%j&VzF{5$O<(Hd-sF|n2r{Ex> z@U5Bu(nVgzz@uDMJN88wyF7zv?Bm@|Fk(YQWcs00qz!J*c*91By=t}q6a?JwUSTiQ z({Awy$ZyooCXw~rRUIxEf+{eC8j2a)(T^)8Gd|GUFS0Km=?|AZb=PktO>8&maD1AjW*Bq4GE+6THKc7Nq-9yd>6Oe$ z|Ef>QBsV#wEsn+Ul>b9yK%Y+9_Gp4^V#gV<>_VibAc*tl{bWE%Nb!uE=1N9<^Bhx$ z6Q>Kb{bbGrM8Q%&dGLcjOGMk+Jk@S=DGKVWvb$qD5ZaO|C5nEe6$|t?lSpDBU^LvpO9mQUbCR(KZgphP1&%0^U3*W?p2y1BVvlDzLldn7y498xyWA|t z+jISekvENw!%8s&DY^-?LsEOZXz7^YcHmd6hLOffTA4}|A85Uqv{yLNHZd}$XK4$l zNedfJpSu}SV1-I8gbV*^@O4xI3YKB1eurQ$>HcT0^kg^y>tQtgV|#8bjS{lh*iRMS zFa9v+a@3iK-#DMLy5&)s<8FV#7hW=cYHU2{CR89K!h3N22E1aeg3R3tjms_Bilukp zj2XjTcc@U9!z_8~t=I#mm+;s5F0_4A4Y@(pI-8Lr>c>TZ9h1W91SWBq&_Q ztU-V_6MV-s)u2`^JFw`&R1bCGk`-xq`$m876<+mnq38}0xBjZPt2e@OM1)<|dQ3{J zTIWsZn9)NG81_5(3kMOGVPn}yJ6Va!pjB3j`-Hq2ygoa@hel`j7>+5lJ?=5ev!>0g zYsgJ5&Gk82^u=WRpMXrt54k&69xOA6Q^E{ozfWt}R~!7NV4CN%-_uvks=Jdxt`UBy z-O9z@L)ICFQq4-s*dYUSYaO3^v3|ccPQI^iF)@4;4`nh>!75@P7~z-uKiZiX$|*gm zEU4)6rt{nSH7G9KElr7Xffr~#y}9A3?&qB!Dd71~ron>B6 zSbX|~Vl?hYCqcejvA-Yb-^%y%f12lAPBFjo)_>YBv>f-Xe5~{`4w^m^`+5+~BG22c zhi0E}@*~dkJFf;l6Gfsyn$MbLKS<)v;7os;DccdkmDRRtmZKcRsjNmdA`UdTz#cWB zFl9#48n{R)eT>AmxN7hdFs=Ogv85465v`Ty8@%djZBzWZN6kd%@Jk(M49x&`T!ZWtOIL_iurbU+&Ep+&kI z1SyAxAp~gz>F)0TIbPR&KhN(u{x3Xy;epD@KKJbXU2A>TX(ansMJ-1dRi@*MIj;Nf zrr^m*9;VF|B@L?vr<@;u4Dp(!88$aD5~1~!Sew7#f5;-Mw$@y+O`4kI@p*@4Gr<#7 zq$u5#(<^=f7;o#+L)jvUgKW5MwOqp&U= z+-uqNL->cVP;8E^;i>yJKE0=PBBXlCc1k^tp|miLRfl7Y#aK1jealv?a@tNL-g;qo z3;M<@K+Q!`$m5O5DlG|0H{mzD+?;NU|2O67ddwT9=m3#s!nmhJ`V99MvW zupdg^Y&j{*r1jq=HK-i&-3`!0Qkq|1nx|ca0usR$%1E3s&yn1EF=Wh8lf|p2IcvKq zOysTve=vj<&m;F2ma$+GmRR?tc}YckRM zsN-mmKPgKBjVi)G6Y6)dsGn*VOeCy((tb*dYd)3i&j{L2#zI8RwH*8;C}3>2<XP#K)i2qen6KXYR0uFpmluJN!hi9b zs?*7492lxV_rM%4%M`HTJH_7oktJa4b^gBCXb-e-?E2=886o*EuG*;WftF zu*~bRuVOCy${2o?0|0m>-{eL3N=5AKByCa*Q36VXk<+s zdJt83B^uuFgs4*OvZ1kV5HEy#a|`S*sZ65fVWacqNR0ZZm|}O@o6C(yR6d&f&r#=i z?9sigO$;mPK2aGF7UU5x_}Na_-}O!*g&RIY>-6_e zDYbO~B;*U}5A*{$vwwslmrD(ae(6Q%yXn~|w6_?XT6=O+)jS&HUU6JW52O6+rO6QO z>qGx!4;{*k)-0^RGtmbZQH?*3@nR^!9A)VWf)Z;|P|4qag zHu(9ci*@)^iX+CVXuUg;?KW>$m#R+~NI#snJMz}4x4JG5hl6mHVx2uJugo&lYs;+f zoF;{2^_fPR34+v$IPcx6;|>Ed+Ht+M%_iF-Xf{m$7&6 z>iiaU{rvM{zdqdn4!|pU7W-~I>Gt`xx!L(tQbxVXatk1p7hu7iM2-|q?KDJ(+*xdC zv`*kotG16=_0@tr4l9Vj`p613N9cl}Dm-1ZUDujn%?4UsWMPXJS^4gp8KT7EF_A4~ zHm^#c-Z4>>eR~Ud0hLI*%&G*cvMU-{!c4%e6?vym!(v3UpPUsP-;q7B(;MAeIaXmp zZ0mP6lOo@Ha)&H>n!>zIn=R7g^2NaG^`sW)FmJQzF<;2Ai5sVHqOJ*#@V&J6*KSDU zU^o|Rczs8gpTTYuc~|*lP0dH>!@kNU7h>RUDPLI^aVB715|@ahTpE|C?utS zu!zuKB&EGulXaX$sieib90-d_d=gX|{zJ#y4$f6B(oTZ_)F?eS7yuUlwlAnBVjJjC z`jk9ho(2gXnm=ss03%y&GR&cDkc>vN4s4}shWl&S0(>#N43PqS2UJ}x9j@T#BcmOw zkC1)U&l*9ZI?JqQLJ`jK37oqO;ZD=EoOP5FEw-vcLg!Q+l4(X>?g$!=adxMw&H*h( z3qodFV2$PA6wkH;orf2riFPJF-1a_hNmhS!PUArnysh853b|FpRI%MM_}YAY1F_#y zB@*l$b}T`iZp0;FNs-Ck4*GjRF2G6)&=W2_6*>&R>^gI=yB9kWVu@atLylQKSyH1? zMJs&}JGpT5oafD=ZsgNDFDzpca+5bd0?rBU7NT$0qYFkG`aMo8{ysqa5*dIfsqzm| z5{)gs=Os`O%QR!=G@Db|4TzL1Q<`Q~&fjkY4fbOrQ5Xw;q{mUf+VLx~&&z*L09N1J zFBHz^&i0WMS`n{uADl`+2d8RMwZG`XwJzNGb-lOpm&G%loZ)XvGTgDXVSdST3xKcM zCmfa^RM#nyM(EBICnkH3SyPMh2P+hp#~Ygl8S#m9goXJ=m2x`?Oqd0b(ei|QlG2v?lV`JrwZRVt_#qZGIWE+#VC#kWNc2Ph%jw=?pjwauH_jw0Uj}sU?RjUR3Bl@Bx z9qhgjfebC`(-E*h?NVnO+ZI55&)=5-&-)*}=Gn>N2g`=UgyGc(u5T$Hgt0#ap0twS zG9F<4Xq(`C#RhAbh;LOo@E2D-FEPcSwmDTU}~=O<16~~8uKsa(Necw=rZs% z1HE&O+u;qqC5t_>E=)W+kXpiDssBGNlY6-aW+8@}Jo*-8W?h4e$GDm+g-P4?Kv9e= z+$zDTE;?!30N9?9fOR|1`t}Tr{An-6o&tKc^j|r)skN_AJ6dEFPH4-m>t}lK$^T?S zBfw{mY;`f5M-r&jI9)VrW6Mt)`PUTpkM*lNKDfc);DbcnP|#>UJzyj_h&7dlyS=9* z1Z4aX!0c7vic-5*&p1#g4E0ufvcjZ7bd<()b&t1KvJDHYv3q^_KmINUMUz@JB?jn) zhYaNNq^s(o@e#}ZESi^v#qeX8#X#+EWwE`!fi?$Ka{jOP#mYRpmfjSnTAI9X5}T>x ztzR(S{!&g&S7-VE<87jGu4>R@-(0bW(?cR#3+SNfa@8bU29 zzbrqAWEV4DcXQ<6eggKi`V5yDOEUbHZ{od@Ks9b7jtKL-y;!6E+?8AIzZ)97VU_&f z@#qUteeVVw2*-Dql@-59o#!4z%ys^WnjMQz^zy z0j5XHLdKJcsC8ObTH9K=rHT}WGpbnUhoRjVJ>RRHsNJYrI_H8=h6vwx7yor=MX;0W z7NR4CjcBccHzbh0r`w@PQ0SDzV`*TI#p}{gtpZ-OWRV+0D~*-e37#8?t>X>d+^NQi z9ktcxk6}#!-Cyc-(#pjsm1Lc+g?&ygyMR+pN;#CDp^V|3+#@3O?tv+K-0#9V_3r@R z&f~!+q4%mB8xPvE{RyNxtb;$e#x^z*b{56coHwB~OsAn=6S)Hi7LcUxBl%BLD#4Ah<%E`^<= zM}zWt>44et?6(Cfl*((lg^DntCmlYT(`?g>FkKSm4})SLRnn8U&BqPHcd$xcrc0F8Rdz zTfNdLpZIal-mWolvY5fiBW|~)k5z=&y>2$7EK7a+9vbjTf;66YD9XMpGH^YyPyXkD zL9qMaVV!e^jlH3Q64LgMLUUefXVTD)UeJlu@=4lvctEf$=R8IT!QFGY!|--Fg((ce!w?d{>r%KSa9Vt%O=m@l#8aP2ba{h@S+ zNg4wMjW1kT`=G?0{2a2$T@OSI8vsk1H9~cWfur1Tm2EqMG5K^jb05a)_3X!cPO*X( z#ok9!*KdeR`OzFr8`8ZNL$M}hv^BATc8Vd*A1KF#U7CwL(W=W7=D;^;xC3sUepTzb z98XbKT2cec{Z5sp%~V3N#@MJWHVyBvw4{X5nfy-V5uf33?r7)*(1m24p1Mir+C$Gk zDr?a6sPwqptCJ(Xqx^GPN=gs3@H>lu&SQ&Ck5M%1ei()hlN3{m4=m}cPxn(Dn`lA} z&mZnC-0-D!R6?@{2oq=aA~q7b3@tdw2j5a)=~$3*FqSpG|{-!UDH z0@Sx{ZuX!n!42Uz=e@?(5YOZF16TE@CCpp}g<-?Z^BMqGoHm$lSK<~kVv+UyKA@wuWOqzn_jtu4(fQEdOzNfm?+nR!* z%2SBJm9)0&j`-UU$$$)E7|J357UbK0n^>o!WFS!w7;{%&_EQ>}DZL{?sJ0`S#O&Z~ zYz}a%*k)j;C3e%er6Ob^b$2yBAT&UtAR$)oA^*6!Id|}6Y&J`uiM!9pIu=F`Jz|6z zDkR>$HBVR0Bv}iOI|c_+`4yZyM#YbHt?Dyu^QW~V$*&NHnKovtPR*iAgEmjmM5x|4oxPIx(@h(PzB zHY!)eXSHC%?jpt}I!L`b@998wNKOu6`%IPSoih<$V|iXUsao zak$Xe2#HKYOsvW;7H#4$&mWaEGg&B*38aIR#WTOnU9SqQRoKHdI;H?8>V=+Tpg_Mk z#`lH@yW*xdiajY~ta(>q?fre<<+<%%7J3O(QX3ue?&)g7m=xc$FD*}gd-KmS7k-MA z{voPOBR1bZ+v~eUa6>cZ7WoR2=vtmu2g6XOEdO^H`t*134WGr|u z;O;7!5C<0I7zP?6#2)h5)Xpr5sv<5Vmw7$9MD^ptw=xg6!$;*DA0bjmJ=~LsM@4f= z#tzcWR&E0n;73+dW!;e+3k(hzyrK1RyKK7)!^Mk3#Q6y)N0DN3%N{!q#03o zjqVCkffF|y#^5Jp<-)c#AIql=w+s!L`u}h;1qLi&tXEdhR0*(@NVqufFm8Vu6L@Te z3Ae;`#(;2dZH;iX|Jq189|5OQ9#Wzt%rz$9sFgwl5&WnofSj9W0Pa}8%Kk|Mqok}1 z$lBOu@}=C^qdc8 z4cHs>eY0vRv`cmY7{f!@>CPt1iQO)w{Or1kY-fr`woMUutKUhnt2cOF?F7fLZ}T8% zn)ZWuW}{(KS~;}?)QBr0riyoNBGvf?d)XuUaa=Evo3GeGrG~F3j4_tNysYI|?TO)U zfK%r~K}elFiz?H}*rAb!P)qF5I1>^Us2RU~Px+6waM(&L+X23W!B>x%=IKot45ADM z_VM)#vm89DPT7#5kH_V+!=W}d86>s{r3Sr2QY(`JNz>2eaQT+l5WmQSnD>_9Og%Wh ztwn5F7|zNs*-~lZoU>4^TjpG$mwNWHwb6Ou5?nZI%%vDMCm!JH&|{A+h5PNCVmx&cr*(h4Gdyr{lYw8hk5@1+{dM)PumL}^%;w>z$uX=EP-J|z_t9Yug(>{ zWJDj_6#Gl|B)$5<^pKZ$5L&u!z1M03%NuX|U_=!7UA#gF1ZNWODGy%RL7{&Q+6;300B<$Jy*b z08Eo%4>y{$rf4-!GV>`+wp53!;d`9pz2YftNCj7%gX6Flq+?P*8ZPUGNtqQ}K7sSs zbBe})fG|M4JIv6Nb4N5A0U zQt(e)8^$On>yJI+Fl4v=Op(&bcM3J@y$?C%*eiFn)fc<^F=sO*z?I|KlrSZk)Jl=7 zC63*}^Xg4`!^2+9oEuw#FaxrmhJ_EdL&DRtRhg>~dKNrQIbLNIxi{%Pe^DgA@%F@2 z55vh=#Gb=Oy>9HPE49qpqvm2lx(u zqIYVF-yDzPw|<_pu`zp!^nN>4qTD|0Vs=Bki_VkY2y^lF3FeJ4wU~a)Z^06InNUf- zSg$;lD6j+vxot4yUMD3?@x44a0wa?j_Bbs06I{^yL}DM+;14i~)=W1%4rWridYt#P zyt;m@CetA@M`&!TZ0*IX4da8QQEwmVRR})e zs!grld%SaHl2t?4R0p$s?k4Slz*0p;Nq}zmM>0XZRnySV)UGzAtLAd>{8g1r%eEoZ zOCs-9sPne-(uVsBU>?Y&R5oDF1`|N?-jwmP=Z16P6RWNe;sc3R!>6;T%HO^;Su2(H zwD!4`UEGidFL^%ZlGw4u0hmFFkh&M`cnzNnsmCRd!F2iNJIubcunY3Do5kv>_J#l$Vv6 z@)ut0fwiqt&RvQ|vmkJ!_(DlW5%Yp0M+hd8_nWLPhsPIMo#Abr)nY;3W|G*k%2*>J zCXd6m3M54x5qSSg#84^r8;$;4j2I`j)rz`6RQwXK?M*wPH;a418Fy)FP4QGKURw#X zs3#ES!~hmbPEqSdBU~kvv6rg}!}_#F;mlz3x+1wea)uQ9=nIbPsb*|oK9+O{GfvDV z{8^~EySj#r(w!Op#piuYUAPr6GR)Lm3Tuo2nY(n7P95c) z4VxI1zh%i_(0JweNI)qYlFhL1;mfQ9?xzxer@Wm(0q+3)*??xpObm7et-w`ac9#SY#UC1)ULZ{YTg7XT&K!yI2DUd(3Z``)Egkhk- z8QaN?!sunnMit(YG@~#7sDhL}`PT0$_Qw#xvWUu%)>Vy2O||&!;M9P0t16W|?fPbpJ^TNP}N3053TvO(lS zIRaFw1NFap20lLjjWUlqtW~yvyfYT&my0Qeh3_ZxH6@f%ajc%dK>J^_e+-1p`u1zfK^3Y^ciz_$X1{a5t_{k*P1@095FjQGrBc zCW>0XYZXuYvwDeBHhd8A`lZLqNcp2+WH<4VdrQ%gM%>naI7`s@WzA1YdZsEpKm^{cSp^`;Tw<@EIsO#f`QQ zuDM7`geVcK9SSqcj-+T!krI0FO*LCe;ywlU7FXSm6zy)ekh6J*zDv=EzSuSy6AV%g z2)`{;DY5HWjtZO1v)Q{pl8i{+33`bcO@5Uw&b|&uL#{&-%#bJXI!=buaKK?M2qItI7r6&+F7lUrDgHdB*Q17v@ zy>#lT{4c=N15^9*OHB@5tkLdxZO!hu6$KAX6pu`cz-4P+pL#6czZ_AMi1L-~->>CcU5>617MSJjuiIjdiC>idi=Uo4`I1+P-EA=Cq5#InG zE7I#^6GpkhvYV(<2i?#0tO`m|H;)4)eegsbX$G{?%{Er6>^9>nre>siHa7R;JxF0gO|tOQB^9D+g2=}N zDw3H2SG=!C)mhe#BvAdbD=7_1>aBbmpsC6`kMuLaXo*2}-hhCl6Oks+^82uvQW1Qelay zA_qJd*mz=CVASS7# zyxOR)5G#_?ZmlB9acoG>t1$x@B{}!625?`!!E&p+R?&XJ@+_D$P+Z_=6{fkSS)sO7z#pGGcC{%`fS|cG)`DTV@DoGd(YnMz8qbo@#*34>*w)_n>Dm`Z6J z7i=K%KSGeWW!_4@z)a=#Q0ou72ooPO5@CxeJCR>;()?oQv#7(h2^y7E>j_KOF(Gs4KD7dSV}hjg{USwgs}q=Wsn zqL9G|BX3(Dz%WnR9pRosbH8BvI;4e}2irQ97sP_()vWrx^xTDbHDMN~NL;E*d<;Cq zlk}mg57QX>BHnAbEe-!k$SBVh)pR#YNTdvmQU>1qXxv`C)s?_a)Svv@;)P6%Mo3cXaA&n%+MCTvuAacK4FVN)dJI?vGY>zI?rG9MhVNANnA{iuEeH((EjkG(9j$=9vi zDx8z8CMT@{c!?@`^a^@c)(bT)+`usjUy=nGbKMoq~nBkgYM5D(?!v2 z-hAUms5U(_<&R-@o$Laxc*VsS5inrKyng)l_{i~70QY%MrL>nP-W7h+U`BrzL>WGD zB}y&xVoqyMfo^@xOAf1AfgkeZ3zm}w4_lbz)4p!O90R>H_AEm0T3M6l-3epwpUW&W z$0(e${23JR?NW^}ZD;6-^3JwuZG`fSOqFTvb_#oFt^(fQ0>OSJEZJ~rsdC9sDemvm zq>kT`5NPyvA?@UCP)TQx^AeJQ$uEm7qBpJAGuQ^$#iXOb+ZuW!N#$V-NUFd~ z32Z->D$x3TiMIFEQ?Z{%W$|AGzn}b$2Q8g?mx-%b7Ivl8&9Pf`ebDt^x*2}OCnOd| z0^P?NmDx>>J3@oh6%z}l;>jkcVfeR9s8waDbLxKsRKL7FLEk_S&n#y`G2E^6@r!mq zpLq{5o=53fz!`oUh{^QclY?L39eJ>#qL}(~UK~7At^VgR&a?jAnQ3na+4OImXrZ@1 zo6kZohTZ20|6oG*B$Kd6fBx`uq~gM!p>Z5@u}#?5!5fZn-e7nDx9s}#MCCYX4r()Q zEDvv|QJqiBRJ;&r5|u7N7`#blR(suFbWhQ$MK2(Tty3&!+NOQ(`qiC*o(qqLb6pLN z3dp(4W{7<)xr*zdAkLp+h1A&_{lIW>Rysr?rBrM&QXreV;@;;?Yc1FmPCii|?QROp zTj^u;WFG17&DX{fD%1B48qkzP)@Okz#2Eta)R(w#{ZWej1E6jXr0%#41l7h8iv!B* zlrycjmaWxCrkG$1h>fN>de@(r9UpABh|_+>+ApG%YsIaDR=`PS!Z3#a+jV|j2$M5$ zR)y)`=IU37CNm1DCECwuk>l4?ISj5#CyF0}&?85j<&ue3KQokTJWjAGAmVGdFOsS% z{%oczg?I8za;NX{>dc!XP+&Z*|A2$HP>|c>p72(=m7ijU`Ffvz@<|29$!Wh{qD!$| ztIeDs}~J#M*UPCUVO7QWOT25R#$@c!VqqAbBft}@7bEcV#A4)^e01I=&&Bc~vo zY)UlEwQ$vg1D*TshU*m8H)15$Uqw>uL!u~_0tW})8ujb1ZJ z3gvMYf9L^{FVBm0aG6-CNFO+(msf)t&0v(3hI?Bj;=1Kko|=X@UxAKssjq<-fHO`d zyA1otic$_vJ%ba!!0Re_V@tUG@>ymR)+_wAaG7$>eGeZeShuuL#Gjt-H$ zllocIwKQHH$Rq;6|Lp|eV;Pqr?uh9*corkWXUcPk|@;48po?s#{^jD z^d3u1h#AlSF&=5y+QE=@0bk9NnF2tlvtZ2NtAoz@f?JJ5=o z>YYAT?8>bzDqFS(d6WVd$*91`9dE;d4o$taf)cx(-2?^eN=}sMsk*H)Ah9Q|X(Xzp zAKk*(lO#M)z%TSjfKm0I)Ka+#hg?7#Q)(h&RW8kctX50xJ?O8XI|rhQWI!Us$no>f zD;67ArpUZ#LuVzQwKo8PR3O60Pjw{QFIk=USXce2vMpgxsm3eOn?4C-WuF>g5T&|TxhV7uU+*e?Yk7hr5YYXb z(M5SioN)mn$NvFGR@%VlI1^OhKY-ET3G24;!1zZU zOjpx_y>mk-%ma#nlJfU?0HKVFjAV{ z`m{KtiM1?NHKkZL@XZP(2)6^ZkZF|0!6P8xR@8e-3rRd-7q7#vc3Tm(h%!Li1`h82 z0GgHh0fIHtkMGJiFg@xY1^-_z$T8*W19lVTRo6>q!XlbFJZuZQ%07Ac4>TCpkXjYd zDh<#(o~$GYeEWRD02^qH-4@{BAe~~H*&!`kyWh*N4)Kp#Q(N90jaM#etBc54Ji4Om zrg2lnI#Mw^BsLeyT-)@zyP9`q8k=~hdBcMIPi6uE)P}@n-W=+e*^|woR^7kxe)<1+ z`NZYC|Cg6fKEWy>T?HUfqmsjUW|&aQ{X&3MwE8XiL-2)4s{&9WG|n=0xv>I7c!kx7 zR-04TD9YdMyb>{(w;OXtT!)t5Ez!}vYi#`(C3Usl-x?ww-n60MDY3JBM0~dyZF-aK zyGPPM^6#=3SB|Id5L@7V4bUV|jOhn;kK(!LZq^kh#(o2=g)7&!slA|n9^@zmHr?(1 zr%}DD<|yDEKs0&w?fS64^Oeb}(NtQdy zW&3fTwC3?b`Vn$g10ceas-cP%0LRcXpk+&cIynfCf>30t>Og*gOa=HS@ZD0a%a!2* zXQlIZ{x=)ZE-XGPKq1*gzu9S(J`idq%c!c50%~wNzI@p89`c2Hy)gAV{;A+wplI@T zqRf;b(xO!^nAv02q#GA*_ip6-=1-K0F9$FGwiqVVyrG81n*KJH=;=rc7HN%in?LoP zNso3@bK+yI*W{*eUwYU}o)l^%2LPe{dKCu>u3uX~Z%ewDly^JzTt$w&O0l`74MxkK z54cR<4*NK2cYr4}_ju|0Xh=W8`z2lf|FQDh{k8JXU$2S%#eOXQFu&^`+Pw+~OC12f z7m|kR;eX!EIk&ZpHSsl(=&QS>sUd&Co$ja2o%($2@h6fY?;#LV=9M6Fv-5`m`wxrP zR1-CGNYsU&pYOL51pEv)dSYjH?)s|4_0 z$Ox?yM-XX{ZE)CzIBjx8lZ~hQoPjeh@W}csr0qzAJB)WIu-C18K>(B_u{Gao+@+hA zH8OvO=(#|#cu0Y#JH1A4g}KQzL30=1k7VX9tCeJ0v23R-$Ka$ip5|k%P6Ms%+%bZp zKDg8>w7R>4wOLDv#|`~$R)JHjsqk@0zs98djm)x$kl&Vux~PJMT^8_y0N2O}`X#3N zS=aD7hDv%?4d3Ze%nioR&_B=1y$4>IU5+HZ#(-d&mUIir!gr{iEqsYmgDY^gCcObv zJV(l|M;Dj7o~<+bXc_yb_mvC^n>@urKz3exTnHpb0(RfQG(dJ4EoOK7dZ^BdJXuT% z-}W0%;rn9(=@-WJNc_iO$P?0)HKZVfNZflLs^kGs4S1$d^nYA1pZN+N_)#r046r0P z>BG)ei!+AtUp)Lpl%SULL^-9RdsxPy;sxdqu3#@cvJqrXj&+nTb+Z9eqkD;4t^_AP zRHb*)SS?N(){ud}oYz&YN*c2t*c`&HR$eKtHN?*4r2+!vYS+iN54mqHxfD!2xX__p zlel;9HoU)^`SuE+iQ)e9fna;M8>8^V&^$TR5W>h(MP1Xgh_2yoiz;g z8d9}>1^&ufbJC5m7dXO)|e8D$Fo5IegeQu=O0ZA3hy-m zNUWymLaT(hyyvTnwen-}ggnLr&+e*+Bz=|Py&`~P=qM*(6`ZMrslr71p*!9Gas%g& zxxewHtheOd#gN7=-9?W=hfY5MUykvpCC(R^W6H}Kyq9dnwf8g>bKj_Q^gfq#%@$L- zByZUdx}N?ubo05+DudNs-M`q;ry{2`vlCHWr?gu7j9OX;XGw>ciRJA;~J;SL#mPj8ZV-!$Kv2?=ObjH-({Q9=Z> z^&;>({nlmaj404G0xm~iedbJ6&2uicogfl@HzhqH@D}P|M>X-{bxGk%!-m}ODT`J> zhS$4InML}a+Q*N!34X0&{Bm=Ipz@`F&Hv4448{v|O=c0GwFCD;5{3TJNIH>F#E2uOG?&!=y=0AoD)sa`whGRgep|Cn8f?Saz-AEQ|?Z z%fMh^({3A~W4lJB@>`w@GFv)2CYmqyTj(bKp0Hg1+>O?=LTd4Sv59na>v(| z*$z4wr8=lXgMdq~I)q$(hRDBT4O$I$LAc64iC2Tm+2>9WQG}d>${a7A2jl!{xHJkT z{<0YD6UC3Ahe3LV^NEw0uQbpwiy`X+rD@>xTK>GYQR7w(9)-#rPs}G;54b78v9OVL zXy(=I8V*ObW6}03-~6q*{D;_%9+B(h{q3K4{C){{m_9`n&@^fgpS}#cf~*mftpJt3 zO0XwZb^M9H!zXclzH`7PlyTh*&}(T51Oi2jb+VFNL%gdidEu&?P6F&qdV@ctl_%ib zIwY0l`OaSo+Z*xS<6F$aQfA+pn@K}3PTfMCX6*w=f5Zv=?3O&`)sdr;`jJ>F;QpSH z30?d1k0p}QkR)ggMxV;5Q~f+9t{HGL``e6;doGr5&T%-&f1j%}8PZ?KY#YXa!}P-4!mcI!nQKO>{*9PfnQ;R|JLT13-!egCxJGVC#bEjU_P zyKSNBaC@aV>Zv96`sm`uQC*?8e7@j#x@uHfoeHl?eAuU7N6 z$jPP)W*kq&JZPdBI<26pZyW`3>G1X8i4cm{%epQU_e!0s09 znk`V&H-A^_Wg?WH6fL@0aJLh1H4gv{UtpnthWMl~E?-eq;4%ofb?ah(0a~Lb3eQDk z`?yf8N}m~NhCLv9hz0Jl#R(MOBbtJf=Mh0GJQmaU*s(G zOGT`_N}yNE(K=PSIT5Eq^A`&;NW1)^FEhjAB|_+Kof|45*#;~LtM9-S@N&VWa)S9$ z@SMSLI6v7ctRX5j*u*e|Fn00Ny74fap&EI4 zr%ZF+*CMe}^V{6)TXvH6yyI6(&jrlm$Zu*Zj7VuAnD_MFZ6}_X?z1`Lf*MkY06c#(|8Zoy^FbJROYQ{wXAnL90i`O>U2KdIVsJ$11tE z2MkP|HA-dRL138(U5$v;tsnc;B(@#U>87+hIX5#5y>lOYz$YR&xeq5G=23Sw5KQjS zGpBkILJX!*Y-I7-z$mFN91ua{EMDW0BeM0=r5b-A(hG%MvEQeL-e&}#OWh6Kjp~!= z#3LtNKOZ3%X+80Wy>5cQ*>W!ds3eDOn*U3NlM{y(ERJq&GtVTCBx5zp>b zr>Ey$#_i2Hs}!a6SMXv36%y^-<8i0F69cW+zE2204`4JE?i;@u$7CyS+$`b+BuuVI zG=S5fIP(+vF~TUEf#fa%_Yr2A}mAmYv zyb?T{fKS)ld#SX^`tEW;p~dBjHqAHPb!_ z;Hdb3Y2hV3u`XQ#Xu{=r554Yv%;bXb)9%CxacqZ^`ZG08oohdJ_)SyP+e`K#42I`b zG=#Rtd@DRI4OrlGdUwmu=8@Q7v7!4A765+jzW5+_>e1u}b(?nt_z<(x85^fmvNntS z4F@!`KBWgZLW;LGtyGv4ufHhLS7L~<&?9>;epyPvrUpOkzX$R3gW^?ZeBN>J_bUr#k3Heo9-Wbn(OqxI)s!b9O?k_}M4Uhlv(k|>^! zKctJw4BLECMeFEUU;wrncbzs`7K%CP4Vgc!tM3$_VT57adxaIi_80ZJ`NXzw2S9y# z338HVPga?QCqWZW+x!Jtrm*5SYl6LB?Lo#h+%Oy-gpj%o-|&ii=@^)=wc6l;2f;yU z^?rQERcb_Ip5D%M6ZU5B2xx!Fy(ER2{OV?bc$~TZZb$Cv4}y;FC-z+W!qbt1&;L0f zOliM~rf5m5wPxy6?CR9QdJ}I196_!>{h}~3_Na)uyXc4{f1>Qjnc4X472bQ`^}Us= z|4JCn9*W^W>MjjGn&uM=oF;`qsg!Y@GK>K{S36&v zPV@_xWYGCn`Ch-rNOngmln9|G%Q0=I| zG^80uIr=Q__hlbKkbOWDJ+qGtQkJLEuR~>IBZt_MiF8!3ZH7&?Jt15@IgNhqL-Z>9BitCg%LMq^CJwF}qngzphx_;}B*(QK{I z&bdYiG%uxo0yw9703UbMHMRU%^4$%do1ee{FlBSL#B2;rQS*1&VwByU>NOn>x3YD;2a%}qQg)^F1^;0V*nk*x-(8p_yJsr4p9aZoXgJn8-$8Bw2LS$S&{{xwTV>sB#&EfxEhTN{MnA zveK6pgy`n_v8vOt+#y7z;_~5W_GKavb&LU0$H?7r`n&0wDhca*cC8Ua>lS>4^#Vq3 zxuhtP(SesSZ!n6~%D+?PsH_koZ*LWA3Zn!rL;}f*9HPp}mHbSn=++BA=cx zE{z}Pur&yneJfy*34pUk39%r1f_>zEm5F|!xH?&hP`-+FHo;|){LezYSEIk#0)+AGE5Bhq>J2St!2ygYd7=0a z%4u;h*+@L5L6@L4je(Jw6nL7(CWc}UM>Meu+Zz?YpO+AaGT<%hE8z4GnOz58bJ z@TD46?OzcYZJ*^v8p}ARg{42-QA_sZS)64Xj`lc4%?r74n&w`=Yhy0)wUOR8=+czSBH?pTjmU0q`4}%QaSEAKnmMz^ zrbFuB-P3Plta12}$Vef9rK4${wy=Is78TAiW^GE2=mOHB$3ZnPjgxGz`Voja(mjGQ z?+mmzRm}?Uhi$B>{~rLbN8c<%*Kpp@Kf`$xaC!h><4VfKG&ZPiKVy_2$nJX*=3DPX z9oe-?#PKe_e9EW7zx?RYa~G@3tUgjKNR)Ptfu-I216Q90d$%d8nRg+^?ivP|(K#kQZI@>~BM0E2sZg!l#%RG=m|9DS8tX3!a+6vNxAy22)L zVGyfg*?iwg5LN2^*H=I3n)U!U{&jf68KBrpm=p`lUGS$;%f`zMupJj+AuMU^3FLa!Y^Fyc&)1Kva9kA$mU*WCEv zIujV6Ev(^zi=nV&)YQ376-8wRzW%>dtR^mCI_Pt;kKLIWv}Uy8@D!Gu;f=DedK@;X zRuv|R&}X9vu(}>tfJIuYEdo>x471ylXZ``e{-wzS`Cf11FF);%59tYCi+kP^v>D8* z%tduhC4T2=OU^dKOiPXhzRSX-bhEBR02U1>9qoYZW*%^JlI9O|Bd0${4gTqTmg_y| z&iaFV;XAFrZ^{X-4}}P@_f#gm!1-9>iIA^0HX^Ylc$zvj&s2Gt{9Cn z>)8z~dhyMI>~r#7<}#}^&pB^OdTvyN)zxu`fJ71pe4S~$Bgb7}ufZ&zs*hfI)&GnyM--G47-`)hVO`26_Yt^QxwfJ=J=$UKe2sf&g(JXUq8Bn?0f&JR$0 zab7Eg-^rN1omnFI#*Ae#PD3(T>*Ws+bG6e5ZqsRP`ANLaz)(JH3UABh^#Zvq?((8G z+lq8%a{M3lUuQrG@3dOeIOrn*))5fIWO&#(T=YTVMSUS#-|O*TJA9Zym zfN0T?>ATx-*{=x7$vi2s)4c>>X|jWJAO6{OC-D$*uV@S3c=xj0#XFs%LCn4xvg`A% z8L@=~i>SBWTSFxFGo?1u|6}!VfQN(5C|7l{k4!!1-~CQ(u?!0p zKtYC2c_IENiR}#*e;pSjPow2{tDeSvsur)p`SPbJX#fz%Sc@;d6{|h63Ajst2P{Bg zuy-e^J{HK5C^P_PslAI>>9U70TGcXXI%p$n^mH@iSDpy6`^NW~Ce%E1>Elsy=FPU< zMvKSJG;{8oMXB>21eeMkce87f`4l0MsvUnmL+vGglK|GN!a-cKeY8NGHK2uHKSAFt zl$gd1$*c5U@Qc~o?FaVTo&12e2{&9S4dB5lZLn&w6B}`SV(|BPxa`+Kq+oyF4{rgW zPoWZXc@9mAKdKbJFgQ9c`kxe=bKNf|oyyp^ual*EC;{F?sB|i3-&5wWdC9_jW}ckG zk@OsVNuRV`B0dq6&HdQJqZ5>8x4)((p!(>+=H?x2m7gxtC2=G@ccw6nUGoCb>Z>C?t}NeTk5%OO_QHog=HUCfiQ@W zQ2mXNh-20xyG}2^R?A2UjQ43`;N6i+{JqJlFT!kW5W$MF`Z{idE(Js7j6ahFMiqFP zEFHyFGz4PSWSM3`B6e#}nSudr_lC0@{sM(a@ zEDkq)UBCSG69JB(!LhJSYGSq$gZnWO`r7ucgI2a6)3s0(ne_tBo6=*GGofy}v7jU_ z&55_6V2}m;@dCTb4|fJI>l+mr67CrSBY?Zd<%bHXJ=6FTeZ1GKVfH;8&Ttkhv)U}^ zmCyFvrtP~uZF;JX$XXo(e$18uu1K4>BB+~%VcT`aq~Ay9=k!{C8f0U3ZL}9^tmOT( z*k)%VcJ(^CJu=!t2A=vHmw3O3&?G8%N0#=8HHZ;X1tsaSJ!{>ojfM=e&|qr+lQ0H7 z|8y)IrtcbTiyhX}5G!@h%4ZJnc{#Yq#XpWPf!DOpU;(~+wE(WqAwHyDfu@k(IB(*v zF{LcKUqqqezjjKcN#CPxr{JNO5!SEFLjq;$`f=N`u_+l+KJ8VIxg$`{empQ0X?Ny5$>;xDrU znGVMev8dLB0|zOY95lbsJcRfnl`6=nXOG#yP%vl>fso9ZY((jCB;&@bmrA$yGxW`~ z2E6aP-@Y-Bb1YH4ebxbr>xNQN*D(S`HlqArXe8GoJM&68UG(h-vOp@{%vb~N(Bl02 z+NsAxrw=LcZc*3Jp0hqBf356LaMFG2|d7~&aMT3mq@Ohy`TzdM@&k1?8W z6-@g+IwbDVJ%(3z9O{|X1z#U9e{H$O$5<#fIc;XT`4J&=mYllx^Q-4iwTY!o%KFZh zYh}1Q^Wk|6 z{5CtAO7ssO96!{_FKemRxj%1&s>s|*_Eo5sJxQ0+#*tmv zB!zz%#6dRM#`isBr0E+}&E5XK)=bl8+5}t0bbsS&K7^d%?l5HG4)m>dbMZo8s%{Q+ zg&Vd=UCFv$pjmHcKFnS8sB_5r&LihB?R4RlL0WML!>FH(1hv;-zRxO;Y_RW7NB!;S zbPT%rUzlm|TuI^jC`(FHm34(g6;lDq2eAUBtj&ElwAn%riVAVxr1q(w68*qqnJ&sn zN>j1WjO+I4Vi9Dc$bBnDzFH#V`HD*2QYXSblrATe@hy!U`WPZzeuFWB_UR9H*0;qf zt=izP^Q&tFyygRj{{rDSMt9ysy1zOV_`&dlsU^jgcJL zCl-}Y;`I3<9jpF%M(PYAGshT-9DCEqRDhfU(lo zVd-Bwx}&(H684&pIW4PwhO-5?^2Z8&v)hL7jk7;n-6QwY71iVc(Y(*~fBDITj)*ff z$lqihPx|e9d!>Y4w_aIR(DG>^O;yfK;4U%)8Cmc%YcK=#i5{I|ff~Kvj1Xca;F0+U z3s!nvX^;68P&Vx`WQq-P*vN!rFp=Pk#V4DmrS~>~P+#!B8Ai_&e9ws!yHm5eckerQ zTd9E%$0zmESk|mKWLC@w+AmOB*B+5gXF_;V))9ub^F2adJ1kCjauW9~LG;xd-rg>109Hoq@=VD*V>5j;F+~!`-VjZ{GfO^Nw=f?Co+mSjX zsuoe=qxto5GJ7dw8w8q<%hBP1HkBF5H;883qLZ>DY{fF;aK}YbQlCX` zj2Eh2Dym0gs0zapWUdiU;EHdSrFDI$FdvCsmsM7AlEQgFS>oG6+3?K6Y8D+Wdky#l zjKsXE8AIzQTEbtcuiBn=`wXt91_g1$g+o4lep~827{emv8n>YDB~ZJ~U@)j*K}*YK zMt!;WXVW#eyW`Z83k^am6S?=pH9TMb^UK;p(#5PZ{tSK`Cc(4k3)aegszL=sH_bS< zaj74b2UuK(8Z3rztavKdH`Vci(~#q=gXOq!mY}f%t%x*IdzuF=kIC=T7c)Ht zBnmX_nN4h5tRugE2aQoMUFWc9M70hdzI+L6<$C#GPb9tfP?lbF6wem;U$|kR4@rx2JmJKBM*G zOkQgOd#|{aI;z@EK-2;^HB!9XM4;y!7;oG|7K<%=Pi#v~dK`U}dSe348`1%Ah(&dN z-6lk(wpZaJgCmu~6a`7*o&yL7=>i&jCP&WAN6A`W_P&;JA*D2Oj8Try!l9uRns6s; zwz!W_XG@z-lpb1+m|ue~PMt8w;FEyAeWc%Gk7U|@Njswy$9MaJBPKX@kQ470z5{Ft z*JswYF|^E7AP@1iDoCsD%R+^%ju()cRB`?>x~pEzxld`vUH{Sp-5*CltrnMO$k@pE zvZC&)Bq>tar=GW7`#s9wSYxVq^8jK1YN>Cu-}EGGD7u_fHaN^@&(p^tYX99RO_s1@A9=7 z)mGq?%%b7y;pv4ee4aAxXYt?3?!|n-cCWfO6GGtUE^d+<8}&?yMsF!S>K@yo@A629 zW<5B>(sApIL+6-(iBC)cr6-`&EzddukIe_UPeX@PyXT$xr3ULtnL#-h+d{L3pb>O9 zM*k8qx_{8}4xyyweetix;?-9XwzM-YA5*gCb##j|AtOFP+?5_kxl z%6S%pL(CgmH{Y5E;(TcIo6=%gRtHiDky-D+*(<~Qr-ANdD)9O@d!FDREbWg_*=3yw zFZ+2IN7x98S(5zcRvw)beC@8Tx|b@dp&N?Wq`CTr9K%ttH2TO8GjzhY6xU#2OsRSm z%R_HXSS^ZErLo+n<@C-Q?%_&5R>Sn- z+-)Gw>}n_}UJYufQk3hdZqCn8MOe%xv~5n7S(F;SIi6_{KI*IGWUnBG2zSV%>P6i3 z6*Qsta1il__K(=YYrV7R+ZEuo)Q6sHY?)4-cJNM4@xTLdz&pr&P~7VY;X1R1vK{4gOvtbT#+QCgKeCHI@C2^ht^KJGD;c#cDW z(M`AKD*7_$&~%9)vHSkb2K#GuN7IGx@~R(kB3;W?zd^{Clq~H&cbr>PjR_k&)b;&&TNG|VMp1+3+i(4$U*M4GMKGT+jCu;y zIDT9?51iI8v2zRDh)0NLm$VY;8KkNgSd&J2>PWX8I; zI;`;GRh&}JDnbEXU_7p3tHT6??0%=Un)`OqpRK80=V}{>T?kbKT%YUqRspmACd@#B zddBiFiZ>qTUD@&vBG?HSKs_sg-Rc&$ls= zr?O~2n4i3mDmp0siDpd#<0(^})7!eKiNkx{S*nN%;q5QVaoXIoq1NrkIz1*YR8l?| ztUV*t3I&t#j{qk<9x+0NMi_@FM#F~C9%6UhrZ1=3Rg~gpDF*O8+HjyewS_J6I=}G*)_#)HnEw3 zc8_EDn?sHPj_US{J|a%vA9qXyftFMaI^Y7->01&1H$a*ANllH7GI%oAZE8Zri6atx zFg&*|Hb9G4PlrNnyJG`kAw-(=$3P@7+LLdYuXYx6PH*cg7n17!J)=PsYYW>8P_qOG zg|QaK>xJ#QHNFzIw!vtCS6Xk_+wU0gpYwY=U&fGfldy$p#tlrmzIQTFqm*I)c-ac! z6ngx*`|%pPwqEK3m-A1}agVPb(04dqb2mXQ4eYV|XS1qU5y%T$>{>L?2&L8v;Y{aMam+#!V8&px`Z{m#c-guDp=iA0Sve*b6&Xz3x!IF|> zJ;@DY>(?!-)~zw9TE^aBX~qzwrzNp%oSxM7p{7h=9OioN2=3LXo(_csE^-rYiWbQu z)WdCNyQKx`b0aG*ZR^-uq$6Pw0u<;Nr$_Zp>XxL+{jvwc!fPIWUmHDLq zOC6_$2N|@80+n@S3*GM0))f`_2?1q)m0N;O!&e-+Kb>Q)QOt!9VA(uCtiX zq|+>V%45XeJNsevFu1OiehiU=pBC9t5O~Zhcm(p|iB1PzCqSsFetzw7(4_mL%Qv^v zaY7A_4gLdxoQ}t^W0M2iGR5X`8HI?2^j9{pyr;KDWPjG;dDYOdc)(0@Nm#z^75(eW ze}FGqgNYnoIohe6a4Yedj)Oq?q&I)cZ&fE!x%)1HGi@Nk*_LTCB`k^sDH4fhB zZmeru9OP^v-Bsl&F`_d+HzX$L^M_ocOV!y{uacD~SqIe0U$~CXPUDm_>@?aZPao|L zmKYZ-q=I-w`F2m|R&Vctai>{%H-1g%3gRW*J8Y@7{U>FUmlC^5{OjQn*AEXZJ|wBU zs_U<^UM?xM5}6^b^+lA{Rm=If*~529%OhN=<38dV*Z>8`Q^i2c}R8M7|`|#83*10y(2cSlz*dmT|ufF z9|}g^J2uSSSwT+Tw*iv>SIvkg^YB6NPxA*~Ms)hU`h2&t248WruhF`ej;dkuc?LLl z`VMe8I_JZ3SwWRNh1gGuDbE#!@aE>V;>pR@Ff421Brf=huTRyIF?dIROfcxHx z`vh>0QfU2I$@=vT(eC`k6eIwbe@TP>6qq?xEwQOv^R<156*?3kS$iQ^Z{X}_Lh;)l zB>B2%Yew$AhTIh#kg^RFDc<^%@HF;m@lVViM9Rd4HH72l+346c@((Kl|3Q^=bJ1xO z#KoR$&(5J?kn~?8;MkYz_IILKaAy9u>l?vB19BA+LW3+Bt>(+Ujt?Ynt7+tcn%!Or zDqetwnFE#nr^6E1Qxtz|76oPLU2-B@P15bV0%+O9zS)nx_uIlO`VN`-wi3eA$Kf%m zoKiKuB<^#Dc35_=Ke=496hB5&*E-GZRDf^a5$lAt{jPNud1mqcjnd%bHYxcrAW z?=2~>-7fzdYf*(FQzUdpl6x!^Tw9iPb9rNI0 zvu$OQ0`<7MuMO|b>L{KC2;yRYS2m3CXj_sCP-Y+Z?j$8Ty+J`Y+LxHA-L z{OJYk?V}U&tA6t2O5>dHD3fsO=v(L`K#aj1is8K?7oF9sb4Z2NW1QUIA2lbeWjpF z7h@&3eVRNm?G{6I`%=9#^@jqJYo(VHBH?Hbb#>c{EwY?O7g{8S%KC76GQ%IZ@1vI_ zq4b&1Gt7V%A7-*e!5-S^&F>Kfj9>2{dDjg+JWxZ7U=O-h@mypz%0GaGxOg~ln_(!z z7bEoni3YA90AG(Rx2ehM#g|aO6m8&oxIZgnzQ4}OI#(kBgJexv?wjaxMR{}NF&5P!Im9t!TPn`=*8>bOWtMPSz zrIR|-JaMddka@MT)}%7bdN(A|tTZFSGjyviG)Z*wNMLyGA^abT{PP$AT^{~Eo|05< zh(7-oWj&ssY;v&~XhSzuL_h|)mK^V24&Gw29U~vZE1hqG!WR?&#!&V!6Zh`j_xsI1 ze$}^B)uOqu6?{Hq9(87hBt=Z{kO7x(Z=bzmylMAvYlLJkd)WHXrRfx?%Vkx@y?a zmvm-BBF%vCVtT@plp)H`))fRWSN%>4uuEM(lub<*!Ne2C*k|?XPExPt=I2ecsM!sO z^?zv}Tu^X?RDp=GB9Rlhg^5*@%A#86HYfVKdfKSLhWg7Voi4tQ&H{9UE>SIcV!O&* zt}~^s+A!r`vmhQuV7p!GXWAZ99zuvVb9jTWKef%)p*x@Y{> zKQR&Y#Zd1-IG`N9$U{fNNXzl3*g9%cj*=MvjgLWld%4R`cl873 zI0AArtg>bDhv4htE-Y=PT^2q&K3}4uVGZ+qRwk*>|NX)vrO%#FG>sTx^MnValrEn1 z#H4NejG2Qx(x`hVgc)~FzZ|ecw(q!CEugdD%nJ=!2L#?Eq!?yob{{2ITR+cI-gA}( z{-I}w$UyW&UemQUuv?iWWqx=jyZYX{!{*JwmWGvPex*yr&6rJuY1x3z9r|y}{Tw&S>TK-j9)PZp1 zicyc_k$mBsZ6sR&Jb>2djk;>MWm+V{a3+tnfTX2AuD_r@A%to4*=C>}lw0oHH>I+g zCk;eu*dAuEh4z3<#5(=M5TQ&l=bhHP;2uljCrzBu+$3{0@Md7t7JAWNu!o``7h#F# z<73*jiRH9pi8D{pD!J?oU-%q_u=bSJmd;k>IgdZ{b$B9;^H1%>VN zQh=fIs*wF7>MA*&+c+QR7>yg<@MCAwMmH``ajyQXQp897J1^pJwD75R)oUqiPX7+&poRq@IBKogr6BPijh#d z9(&CM-j6?o8Z$z#~TM*~6;x59op0o*f+8_RFZ;^z5aqe*s zyFuwA_(y7r#67_4x5Z(Vi&meqgE#lETz+6j{QdwMtl1z8wNGIGsMn|k^SG&Gdyd4` zn}1#kaTNr#bS{`eC{nOASw~{1M=V6iij%F&i*wA<2iNib+G~oqs1X){AmV8~7|OL8 zi>N$0dmVm2R3AdWHNqGB*vRzE_~n8KY|&aDCgu#&9y~h7&17U$Xu;cjL1%sX=-58~ zz!LQ?!kCS8)WI0GSl=VL%l3j5hy~(6#76)Uzv2>Ypb2Fs)6H-%Te!=v@g-Yp*lSIV zwLH}-K3lPAlQCg3;^jBm#(TQ>>h&&SuK5PO6~7FCy@r#yXq9Y zwB62mb5s`ChGH+P8~J*0n;fqoVuQ}D7R^kowRtra*mI7g0%DZUKG#q)rY!koMuvW% zT&yn<>KzrIFDVJ?>s@Db)Ofz|VzyMpYX$vD6P)9=0^p-PWuhx?FkNd z!qdyQtfFc{lW|sT9?E=3c#SK1nW7!$!-%a*nTTDk(xFTr_q;*HYx#po=oD|N-5x;D zYw}Y#@%cNtSPCO*EE4bphKsx-r}+`6j;F{wLaT!&6nO4mdpp3X`dTwc#hogJ(Ade( zu<@;XKGvlohc-pOBl|t#Sr(}q(-=#tEAzgS(Q8774@9{Xf|$N8eIEQCgAqRvLAi!w z!&9K+HA+{fS%_*# z8~fs|)K&dn$*T=DMTtzeo;=4K{+-peb*Avz4e9v?4Q@+}HCQbf}n@#dQ4OentEV=>bKJBQT75V`~bw`-55MI}WPm&v#6F%`f zqV)!V-E+im$e(S&gDQI0$Kg>!A#sFA9WANZ^$uhp!Lf8Wuc)z=acZ!p>_Syp?Xzkp zrxEK*TgT+YCA6UeSF!vUuI%rcrJTQE`kk!iC$ z(H9QQ&K=&g#>smhiHu;cJv2zR2gduKHJg}6LnxvoBRn?fk>CnY5rta#0jFdFFdaF@ zlXR5XUJ}}7^nk59oXR{GWS+hLhH)By{|)2d5SU=N-CcPDbZ0YjjHYXm_$^FpQQ2u! zVV>|V1 zF!QtvH;L5HG7MkMs#)`Vr&JRh>s&TQt|t<*lQFP@4k9-_VrIj zu|KxxTOS8<)@Pi$<;B+b^`Y``_alu`%s!)~?)Q20!gJ9OeusObF>6>QZ<8WtLV60n z1Hzp#XEB8e4oAj^iXvlWIE~+;stdPWJ6X(pM1ns)M3|Zpfdx8!W7agcCLk~%5Lh#{ z**$uoow^<=k9*&8(n_lFTh|&{HN^dGAlh6o5Ip0AMp#>FD>hMW?>+9Jl~cCpgozEh zclf#P?B;b1NWP%z(8GL$DRi9jn=ilJ*0O-vjY_VdmITY)&^*U;*{D@wxw#iqx^?DpIDJygBz0t}?s!<%6MrR3$4ZpKNoPH3BM{Mcv$ z9lip3{^*A#)J}G+l<#q&qK_eMv@Um(B;MgbZPg6N2LeRw!y%V0CU#^l!j>?rkN2mt z2ceOao*ac~rVQ9<_AxcJd4g5Z7Br2-$CF_rJU%}VYq;vPM_<(l{;)#1;GPdB1Y7KJ@?$#JkRBnI@dCDYIUa;;7x?= z>fhXx6j81qPZBbSL#N-}#R4RRlnn0E~fm;CA@#R`A!h^CDkM zBO6#{SDh+0^sVB2H#So55CqnATQadI-08vZ{Fdo+Cj6?=*aI}K*arI|8pY*eA^N_! z(M%ICN@&qL?2dzne&3#`_UYb9Xblx9iXYK3U(NGE!If^1DKlO7%rjg_i+<2jtW|oA zj%j}%OosMz*XSQlR!WK0ueuEv*uTq~NUEqgFmb_YcNNS1HK$n(eo8!0IBDpchMjAF z6n(MtgixSYb965)ov2k|HY!5}_j}r=`te2T^xjDvZe3sEv0F|P83YC)jbE_1+ZF&f zHV&JVaSOYL#s7Q2_{@09Zm%TG42Oru>nRc@8PA;H%%}zR$W?quLrE`2tc3~?lD{=r zB;N-{F&Sm;vfR-A$tcI(qg#0XNzha7b#>Fv{YbSBF=Bh-tnmiBk>-l<{^RA6%7D4m zV7LAT7IEdTn=5V;Mhu837IoUGaLVeJn8#Q0Xnvj-3FvoGjBoxF;%%?EXGopOKD!po zB(crc54casI90OT@Y-^>>w5Az|KjR^`#lRCBrUZuHF9A-shuC z8EXRvwe64;f-SZ!V)4@Xl@tFWFaixF;-`Utg1`uMrEQmyzC3)g=IO$?lu?-M&bZu2 zX?WVWo;puw!F~K<8|_S=W0y5h;H6lBjZLJ26090 zto0*48c_=D_&6_RjcWZ`oZXz3vLbRaiyVsl9rr38Ee(xnvWy9VHrOWBp1FYj`fCwx z%C^Y0?P~t!=47~MOWMAhK#AL=*!y%#J56{&G+Rdah~DK)cf-Vo*qqGK0+c1&NWjh_ z$!N#mXe-xMaYzoLf-1(0q6te=&#S>_@fZ1IP>;ZzlJN6^`#}~%QW2~%sc=~G5YZq*Zx>0#DSfIU#P_f~bR?gtKvIT;YZ!(>O5 z4^<>_BLJhCM8}J!VBiV<9rR2XDmrLCxBb-=zEAv}rP$S{d5T;7!(Mm@nPGp9XSPbL zjiv#Wa?GUbOIAr`Lp!*k#!_XHdbM#r1hLj|B&X4@#uIyZQUY6q9B`A*R21`A#iZ|P z;A7Ua)h*gL*wkn;DlQia{|<;%5dOoTS^N{5@BIAGf8>-H2oPx-fucP zi0s;TT$Eu#+-9}a4ANb=qyGo)<kmkRwrz&gi_>8@-VzZy0=aj%=VNAe(W%qFmEO^2U1GA!k( zM-+Mw4{m?+=!14$GyBmjwCItK=TJA^UD2+S^~+97p^rth4y-FxZN=&$JD`7i64#xv zC2$VF0QntvqC>X#J-}q_c&kiyLrnsbJ38?wh62KhP%$rACO4}4vaI~OH1;(ZHZY&~ zyhzRxVU0(P6Gj$_BWSb1T`4de!c4@7=uZQ;rwkzx-^l|u7j9dWhxHT@qI_@bf`h}) zKM#hb->SlH*1F6Cynp@J{`xNXC{d1$%^ZzKlDBNUZcdc!0=u>j-wm-49CnY>)nZrg zyTcdn{@AA1BmMSSBB~vM-y!Na1L?3u$`LACXmrZSIpf(6^TX^-P7nyyXR|pj_g$|1 z^3Xlp&E$>mb2^x4eX(K1B8In=jV4^qbnPI9%rygq9XAMV9pmOuf@zlXx{uW8{n1YL*F3WGQwmmjv zRa15*gV?s%te67639tH5^E>=BnHBIx>94%h;alN-NkWd)=*rAiJrzH`(?0o0DTcHE z$SwqHiJDT9$RYN;VcFg3DXev-(k6w6LF0@QwckJyzJD~Y-+mB8)??y4JcR_S0>7hX zEDxL%-(!HElf}OqP?xP8ge^Xr_8T{5!kT`d9jao((^)uCdklUXoy~IrWNEf?c#?lve^Gkp^!DlG0RgfuOc6yVF7uf%C z!DZb0L6fe!e=H8cVek6hZTvlzyV7S==v)&^H)fJvKoaNNX9akm&4fuEn-N)d2k7El zEZM*e1)iG_)kiVHiyAg&>}&TOgzR*VfUqMs@W7ai_IJLOjp%HjWvf+>`aR=|oj$Ov z#BfZ#w;jw-{LruJ-*gTbD#9sZR<8T>VZJNhJQ;&G?YpI86Yp%e5$4YWC!W||)AJ8s zSax&3dU34VEV5g;|f4 zhv}XJr6)nsy>2D;`5H3WV*`;hc`MaabNcUD*%$L)kPM|YHCr{P<=P}#(G+`{NW3un zQVn+U{@W08Hm{!I3EEDDPVTe8bJa&%xSj z=smP{L%8AEPo8E8ua+you>uZ76n?u48^N__fvn_UDKy+r`cFS!%$*mOG5%#H&Md+G z?js1Ez6FW0@mZ{T)c1jJx+ewmQfsyO2^rj>U5;d07XnaQ=WyJ?ZQ=gCg>8y9&Fu}H zhZ)pOLNLSL)hf+eECS4WX=3C(W2oEGTqcCuprcew(AFk0x$)KP`HLK*)adr3d84iB$8{XcrH)9$(w?RlRF8XH_FeI( z_Ld#7R1VwgXKSQ4FG#QxOZutrev_#x%^9q>iJi}PdUeP>2lG8ny(8gkEYK&xHo}C` zYC{G8&E!bvM_zP~r=+(zUa>g;g~xg8jcRy|g8rkAKt~<~>|gh~!^D}|?`Y|x-{2h# zaEY!r(l%pAHxCtTGJfG(fkX5!;fn*vY4Z36j1-65a7yMh*A^+M7*p>(ZgsBb6P8p^ z=NKikJyWurt7x~W=jc-z~ret4^mi*j^FzzyN_C*k5eihybDZTWpYS+8bhh>N>`qv`|EC`a-6*)KIZ zxWUw8X~o!ExXTQ?(Xx7JPd4iY`EKv}2R~q7GC5BLn55^|g2Q(%NkM#U@pe_0S0#TQ z_@~r-j2n^dd5+ay#ovR_T#BhH99Rg5m9K1EMA(MfYSFbe?wjUg(w@n%4v(sy5{RkqRBYPd=YAY@c85sY4?N|HrD~iNj3XG4NYaa1)2tVg z=cZ4f@t8XmPn6^!*tDPNCxb+rbkAA>;KOO~;9Iy}- ziuG~wPZIUQMnjorv0lgP zHI7TyB^di(XfAyS%2}a#_h!jxl|sU8t5N}e?A@oJzwCbV+W$Il^{y=;hQCg)6||gR z4Ki4c#=?OYLg^kCUCr!}o`;6{1)d>my7%z_A^f;(e3(yX0SqM`1uCPm^;(28gw^r{ z&LwccK8nKzkQylRpXoYDihfwZ*1IcGf2oc_avnj{KQ@H5D)G)JsC%%f8Ve8qu>GE6 zUTZs(U{8|hN%bgwaQ8Qp{CD;wNbk3Dx$-OJ@WoJBkUoW|KGT*( z8z&WkR1_?hWV(Da78`H$wPu#cW}3`h70HnoGM0<=6<>pfzKjM%7!3wJWlJC8ke5c! zep#?<5un-QcIiki zJuB`yTrSQESh?&vm<{v_H*85#dv7=6%mvM8(yLu%`Myp(=hhu{;Q@Te0?Y4_Z|H28 z@gc!;E!ZXKZ?(%LS@JMdC~;o6EAS`juLdDEiV;asi1QVb(NBXi{ldz z9rXFJswRtdmFJyV5(l5w4N=$a!t!Eco1<6MY}*-W$)sqkCD;(_kJddk$~_;r`L^PK z#f4_YCHBaSi5BE6Xuv7>4^-&o3YuWj7#rJa>G^mN?CSkd#H| z!0d2eGgdC{&gIY?r}6M#kn*I5WkTToTR?dLiI@MF96HD6d^zCFT+CjQ24;*aF~&$F zo=g&ds?=ZpOKv2E#&U*|%f)hOs$9)D(Vu7KJm2;~c40KKj z+U|V8EJIMv1H4RI5_n)Z9G%hyCsw{y_2`lj$?F*6-seEGVCq&)pdl4ML!^2EKG|n1n^=Az+zy}f=ZvJS6_j4KJVkYxuSqSW4-V8K$Me##X?%3GFP~D*?N3>r%64giqsHc zTEwTadp3z`X`)vk`%ZtyT>UOITgJfzU8~fxfbjj*7zxsK6yD-4g5!V0S+Bqg>O)`B z@KVT@9EJReY>iaCT2aH~nvMr1|0E|zKKPaDoa;Ua^S!Dd1Ozh$&9w*)0WZ5PpSa4E z$35lSMKG~PHn@&83l;r!Rh`4r4p&b|jr$dzmW&If$wZ&&LK7;_RQOYGjK{hJdiGlU z?cuJ!1)x=?A$tb8D8i|zLtlK|%;!(V9fCa>?Jy~Bt*W;W5KI9+ByN8qH_sDq)$|_~Ds=^mV$+z{TTGaK$4BK~xC9&B< z%*V=?4&^3Ck@Sv#zuMsE|W5AGZD2}FG)6~Rn*Dz$mBDqKY>UGcX-7Y)&4#Cd1V=|n@1iH zu8t?rpPQtzY&HR1V1+wyaA%P2O=twJ*x)VvllGz!9S%p6e@F55L29@drtb;WxCvNH zo0-;4wsQZ#!+G1$)a*c5+KFf|B~KWE3H0K@N^qObtiLexf6u`-asNUC9$#1$2VP3? zl`mZ84h4Qe!+fNSns1O9b&nrqT9wtkWPwFD4IaosVvdm`d55*IVuYpL`jqA z9r@5(7PJrN`C)HCq`Nd zZmufUy{K~g8N;TFl9@zrol%Y6*Zvz`UC3tIc{?CXmEwk_aodOL%8vcmdh_L>v6@Az zH-@eO1vDJcUMs8QD?e_RtFm^fSMzpKv&1iU8=Ud%dSi%wTb(wW%XT%(vYIH|%$85L zSr;<$e(8qd9XLZ5(NbS!wGkT5Wqt?9mwIOt7nI$xsqOHgf2wazyN~QNX+hzb?{?&$ z1J%s!DYOO+S=77{=xAIB`UAa1p!B@%N!emsd>?kpOkW&PzyK(j1dN`{a<~fh59kWY z-z<~Ue@}8~(PvnW%!9nXb`l(DHt<>q3S2(Ml2lYr(lE>L&0PNYy+srk1d}bDMH(Va z)%WYunqv76eLJ?zteh8iR8kevy8X7CPwO!x=M;yd7I(gATts-c*^7Tzvq0oa+!yky zK7DoSkZFh6l!<=E$E5ynOY5t&X6e;?SeWuFbaIW!&NTXou@u#*U;kR9C2H@Fu$ZFR zDT?U38T1z8rbne2?cgdIwIk!K0MiozNgOLlMKK-+(MUjoU%G1a-fO$}yx1HZCQ7Et zw2-^a1?=YP+`>fjahEOz8IJ!3kzdZ6&~5?)if;JPQ0MXo8@hr!%?}tmV~FN1-KEQP|Ztb)@&*atR5hSKf%^k1&^(GW4iJLo8-;IbPzod@SX>DYB;kJ0~~js@H0 z&oZ%Igr9>hh;$mdf4#W=IsX;jvLp01Ktw=D?8l?!Gw6-<-F=5KU{va-fOb4P@(=BJ zpqLQpb@8{^B-62&9^ZTgg@RbLYKO4Uso?0`Fu79XKv`+M;@c3%@oxq|)Js z2d_PeYdFccX;^cSIuCihKo+eLxOUCkn~!6kY7`0 zabM*>1Pncy6DCX|zY?;7$!az$L`oKCSeB+V^E#}*dR;ozd}E~nic@eE zrCHyMZ_n$+t%weTWq!3KlT+OqHf36>Z{}O*&v|P25>$jc;$JXn)ra@Gw?YOD=>95*BdTuJN@qs1C=Rf~N z-TJQ#?em0x#W0@=D0XIQIf~W%_^TE=OwbsP5fbW%^%H|p83Q8Qa=Z8X6JbQii&nj; zSjcS*Q|Pm41c=kx&ge8FZY;o~Y~Q)+<}SCtBg+|{i%+klRF4UA&JU)#Xd7;J!d--# zj1aT#@RNn#z2rz*3fhf136%o?E18oam!BO!Q*nMl?#WzkRyPgm{_}0JtDKQX}-Nd_hpeuEScQ7xza})GgY|K-U2d5V)VS z)`Bj!@=I$~L`ABlKe6%+TN_q7I9Y8})Gg!FP+~$fjqTuce9gylg|uFk4$tAPrh~hI4f+jq*!B{9cR;8;Orz4;mEaszr+gGB!iWR}?B*Y@LV8L9pJ ztKDV&U#Q{O5ghm{fY+q_Hi3uW zqJb7!|M?|^n0)?!pFRsf{|qTfuaP`Y-ZiN$8cZYsVgS(3@yYQkQLk_{jh7pT>^egG z3@F>ZekRH0>bUN)NCRH`R5mU_LVaLTQJq9QzHWctd0h!G#oNJI}iOM#pTOK$#id^3V2Zt>WZBIuS z+v5I-+PxBOzE|nS8mV0mVCPZXRfNnz+3+!`DG3dk`n&gS@TnA00hluU{QuPT7C=$9 zegC(LA`JoxNFyn=G)Sv-DpI1r5=%--w=5;yON&TJ3IZZ24ZDCyx6~q`bl3k_^}6rt zd7k%m#&O1R0M{139y!4!k6e1i~ZJajR#N~1DunjF9|yPn zU*<`Nx=42^HG)&~^U`(%ZM<#Q%F^3j^Jukh@PWC2|+e# z=9d@ZI^4r6dw_KuQ`2pfeuTT66eN9|n05o5bul9?+{abF+-QXNr)%{0;`D)x?@yVZ zR%o&5``VD&mq_!tNMQ|P2>1ycDITY9p)exbYQVz<0G$j0RB7VdKaL6@3%QO_p+WI8 zUjNvY(Wz4`=A(dF3Fgz*x{aIX$yw8EQ~&xfL@X$CpXZ;=5% zycFGrLw0b5h9Ra%IRsM0wGSddCJ**VDQ2cfLDXc%pAy8z?G6Kv?*H?;f4)}c8_4W; zcx*p&u@*ApvNwBvbi#Y%pYyo!65K}r*0Tj9chb2Px={~8nzw008IVz?PZcxYqO;(G08Q;)F(f1NfYTrlMH14UxJsn#dM(`(^+#1%N zhxSUjYdJ7dRN+k>mQYJ?1`y+{lYb3;>wU!;VWm%Q6G>>1IWi_?SkW3uLF3OCKT~A` z`oO-9eFJAn3OP+-83W$BEQC=#%m$|S8X~xXW3e_QB^VrT_z`-*q9qZk0A3TYC}*U`FPXO?8gZcaP{#ztGco z{>Y|3{eHc*P}skfSo&0s*K>f&vuWvpPDh!J#>J{`)6+W=fapMcFsV-&aW8TcWg+D} zf&A#aHyh6LS<&q5paq{-^)w$KVmhVIw{W8at{s&$1UG1-FVAcSixj9_@eS9(wYOk^ z+j|bCeKdP|_$H7jmEXWFFl#4$Ig~gPREwDgMjyBI73{fMMY6|p63Aux(0MGz`W@%{ zJ>^LPGTsS@;Db$y)WR0NK@_IzP%XhG7idL-;2s5ND$M331A6Y(LKC>*6ZSi?^7rYk zOxj|=_-aGn+g@xZ7St@@2vkMH`5hsRq;^mOk61~)nYT045!#DF09~@WGCVD%*m9i! z+CkMu52~a-UHz4voVEWkf6m@h;@O1Xp12dKPg$Aoyiu}|?-Bj^9DV8Gu4UK(3v@rP za`Yy^_Jm6T{Ek4WAnS0!v9-0f6ljr)yY#?!J3dQQ{BEwRJLt-L??$&5By^;Dr^dhr zPNz{)QJGhNzE?Kt2Bb^t=OHQd2$|>JFjW)eU*!@kz^)}jzozSio?}9;tLE?5MSITA zsL(d_6xNM5Fz=MW#tl|*GPGKto;;j8@%ezi3ZT1C^}A#1r(8vm3tzmY^8gO;b<^A;{GDKwVdIW*`j#yilV+bur1wXL|??IJc1KD)n0q!EAy^5n-&|ngnJszPzz7bgR(|xTjc)hbhU`-kmSo>$) z4)Gjd4Y+ic>0ZI$L9SSopYHTpFf`ts)UY5U@C^ozk;IZVA9^P$CbQYIPi#3x6Z3ba zoLL`E)q3}~lba+2`T1*TOm>tIM6#q8#TJtIDze5E;y|j;&+`4O_|}g_{(=M&abF@^ zr=B0*w8=Vvjf(_*igY$-FV?ue6dPEsG5m$>6vFjnI+lGgpjlD_;7x^H+wHvsjrV%CfP8>B>HSyO4%KRafx#fwTj|>q%R*mYUSL*P2J~8-oGK<5hz!0#d_i^oO zu~B7NL6qb1HM9Y@4(oU|?j6bTNCF(Hfo_4dtxa%`8%q14K|cn1Iv zij7l|WdAU=t0GTf7_cVal{EP*CCPh{{@NQ2fP=lh^hjvysvWe#<;DPiH{Cn^?<#r? zm!Liqfs0N?Rl}dg=5KO9JKoL(d}42jQB2AmmYraBt$qFORpjoL|568PTWlp6AN9yz^1Z_W{MfOE2by^2*2qtu$<_dp2YZQvlIIN2oyA zw738t?lJyguR?}DY_$q}W8?@><{@sYaAsRj)$BzTp({ELN^_Z9PN}m{icw*E70(IE z@~m=*{?6I`hI)h%1YhE zY1T!hzq@HOSvX{r4{ho9LLQDOb(EoXj-b*sFMF{5!8rl|1{d-%z$%p^fLIS|P60SU z7ZR8_C2{GsqER%cj(wC&FdzAJLCLu2k;asfv72MB(C9zo12u$_eT$1~f?HNOdHUVZ z31xAG4sMsF3Nw6u5!o1R?l78bgh%?4n1_>ERgL+3B_)IA9k!6yS!C0iyuw8{l{&)< zb@<>bsZ3@H_%$%8i05SLd|$g{Jruhit(dCuQD%~%JyI`2N6h7JV70>rBzat;@|kei zGTqY{kYB_?2cDocFVdNEa6=LffE%gM0 zt{v$&3sEIGYR^XwxkMi$j>2rLmuu}e?>hXnF)kOiFdPFj@6@OQZy#DRm~&(DN5e@z z!JuirvP(aGRI#@ok1^ozVY7%WLsovymGO9y=a%T<| z`dJIBR!8}=Q^}KGQ0(i51dP}>@}8a1_hCs?%flB?qYF97XY8rmN5MIXKVx_1< z8a&c({V`@5E#!}88Rsn*-XZ!NK<{}Fv4Iu*_Y0X7CHI`mfe~eUXW){pHQp5yvnd)O zxOw+2(UPpM^-45^iCx1xRLI%I=^x{IKh?V#J>*St?n`KW6ACN6DkhuF6LT` zC)^hHxd_Y`y>6goJ>XT+DzIpxCB&c0#A-1*@*u;`TtuA56;aJ*-Hh-fJG(}+pQ)22 z%pbJHYyAP!9r=Nno|$Nc7V`}0+P~ks&-`kUc{Np+z8fx4OA`HgjOc6TBlx^9R?9QF z+^`JiQE>TN@ZtJt&;viP8xBEAt^6=#pw8=neReSiD?N71r# znIj$BSQ;}*2>1Inz!l$>Loj-=L^@V!Zic|}n#!W#f*YH&@rT?@n)2(X{0y+L?Hwk7RFp8*G(4&-1eMb-;zqi~9BfbEE5F4;HZ-q#01k zuf|lno7P<4y_;8$H}Sc0P3^UmnyDtm3+*Y<;CT>5!=4kIsgu!qTl*fxM_=x!iL3mf zSYqjxO6&1wCg}cGCnH_^PFCEVijID-Cy^1=?enQ;`$H)4dcM_X%zdi4oondIF{kC5 zGi&g1t#4dm2lsD6``pVN7X*CKNnczO^Wm4MSHA&M(J3cfU(_dANY2yf2KsY|KED57 z<=g$_kMga7^@<;a@PnDZ56OTn+3ex8`j^)ji0BG>WW%rN{ACBj^$fGTayICXvCex( zi}5e4LTeb?mZ2=BA|{apYA)MwzTh^r-PuPOBehStP}m1syA~&_VEy{glm{w3np9># zw0B#m1+h(*Z1oPylE0pp7Q3U%_0!l=Db}5i7^&&7*jMy9*h>;#FCTg_HN>a;iIOva zxbUoz>w{`~QoY+M{*>BfFWKdJXNJ>lSX-CnLrGz6us+s=nYK-sPt{ru^kzq2`s>hc z-HSxDKl4A3QS)C=Ulb+*hKX}+-+4!x99F`G-QAvn|4JZdMX!BNGrH^omf+F;$3^{Q;cQYzXKn>a}Lwz_BTuqI%$%x-nV-+!CoN@ z%s5N3NAq|F)3|cCnawh%xsD94-#8snuY^PL-Jfclus6Aqu~rbj<`%K~5fXV-`0YN9 ziMIMzyaNHca-lgU#Z+}Zi}H>Bor zr8?%pN@|OGh-};fHn?wEwhE)H@+E^-Zswc!m3l_(;Rxwkk( zSO?`0Ec^G2*MZiFyiK1I-?VEesL+U{(o5)fObKphwC{l47Kkt5!}T>8bH5%`*&l~z zee?yN+Okv|$GMt#_tk!XHV`sR`yZspA@&nzn@K`NY>O>?cO`?GwhTl(A1nj`LD?)V`sJj4MDdTg=@C?SWy( zPYbniE9EcdUR*r!^E8jaI%41&A{jb{8IY6>3=Ap5%qUf}3%^a$3JxHGm6r_QN2c^A zh#I-(gr7}6a($PW=;d~cJuaKguZ-xn-Uvz1_*Br)%v`QmUdq>kvg6;id$X%e`oyUI z>QQ!BYoMw_Ut7*EQaH7<9-FO-9+bdsSU*qtz)~RfjBTY#>1+g@au+Epial(|ZA9Vp z)ss~8{+ml3K2MwVlEWrE3a;X- z#rghJ=UZEg*4t#W#L*moCOaV{9aAqsH;t8+>Z{9%Rhx43LVJBxT>6?$4se&pWjBqC zO^+U)6Pb0XCX<*Ds4lyDW}i}&y!V6R&m+AAV%-9Mn25_siw;Ib&wrhE|M^z^SuBLC z^UPY4E|Q+U{(BR15;Lm;^hS0D@|)_7yN|D{@fq?tGSOZU=Bbt_O3h4U%!c=*^lj@$ zl_@)xR2;YLcYd2-C;TC+d#Hl2! zUhsIJ?k9MT)^7~Aj5&2M4!uxyng47}n5k!NE&XT$+-qZa zg(C=!D`Lx8kZ2Q*+JteH9xK<_A1}E|RoIO3HnbFPv5on^x70I`@Zh*5hel3}B`ugcFvCUQ=vpQ(Fjp6F-s|ibNZDkzq>tYTB6V z)G8hzkoWa84y~t%ahwkwjty^@9*r`i_G@BL?*#eR{U`|&p-aXQNpxd&%A)Uo=@MWm zUNIHKMYE0z$uvODb9sb!$cM|6ant$XWvt&=)wZcDr+@x(ud8=IDxqL-B`-`~Cc<%6 zKLvc|ph3<9O1|!yhUHB9DIZ?I1{B};lHxY~$zbuU$@fQ8)@)W2nd|e5EOj?gi_X?> zq?M%ry#Z($9`u)}?qJ*Ur`S~po=h1;Q+suCzki0)t?x6sE1%ZDs|`g3wn;v3_eNtBRPm`&k*$4Y$}DkD^RA8J*a&dh4&vui_*7Z(F`#Uz?`9qaG^B_@`!wo5FKl~ZN!r&DfROThJ#QMM^#qU)pks zwSmyq^$>k940n|qBVxgk8F^xZjn64fVNW?UZ+16f$9tJ{_LkApRB(ZKfl2g|T+To! z?We$bI900NT*h|Kpx1C=fKLR;^tW$i6pS71>Dlg1LC6i=m-df%Pa-20M$?i7gGrqk%n062GirNvSa_pN#sC@YPjyVyfH5M}Q$=9}rsnje|Q~ zw*%?J>JKib#ZD6KN2n$f%#Ddcs>58;Q+S?HC<$XTL;O-ELi3-=Dso*)nU4%fM|~;M zKc}Rw644K_z#H%5A}j0#aFxH3NEh1}KbFFP~eWyISmShnR?iQ@6amzL{h63Rmx`V&)+w zQV0```J+EyXC$aCa0*iC<|1j~`U7WYKmj;AN%J2#`#}0{ID40Jt7-!QO7~F-rk8Dj z_qQN^-tN${|qQf-Ex|%iA4Tsv=~AIdJ(= zP~!|JdWrh}_Y^a>4+s+2ohkz44c~;i>I8Q#m0%ZAah3#Pn&$r~6k#>b9iO+Ko(32} z2mE%XC#waZ!ncupR@c_w4K0ze{{mNQEI!T|XZrTy`v@#j%t-T(s)|%tN%DEsHQyan z*vT7;N8;o;&bCM(0Oi*iuZ07J5c3;#t*c0^H6268q@W%kGNJma-c&uZl1!wT^Ftbc zWkBynKcl0k4}q2PdAZOlsCs}k+9>u3X(?rWa)ljZ>rHBD7b-SAc2&Vv{<13%tnzo_ z&7-4gPb&O+6T8KJ`5A*Up}(H3VO@pf+RNdy>)cnar@7nGN)!Fqm#YZY3xZ6pa%R#{ zXFnESJ6?GKFMVNL-QaL3SmcvXf6XUf6jwFL2FLt^0ibjJ{a_ygN#g3?SnP{M;@%|v zzkY|xPFO&=K(dmnAPS`mdd4o7DGfmEOE6;WTuE?dhe2Lw!!Awf)i*El5(zh^@wz;+ zAv;*zV9>GlVF7n1QDg@2V$0}#7pZGzx7gS+oO`tW3@(m=St_o~AY{rxlY;VU?X|h` zR-(yEP~o}{y~0HLU7{+|l1{cCMGI{E%ZM5@Ma7d)imatAGzLEeP}jNsk*wtZdLvYP z^d^L|2Jv&>h&msg-3MBZ*x6l-)(*N9aN~Jjs>gmXe~AkeB_OmIs3Tmm(NV5$C(QxY zJtC&&;^ZBW=Z+7uaVzVR*XV$_xJt0YEnRDXp%Se$vY}KD*5*4etIEWUuYyQR`SWD2 z(_7?SXd!~}rqwGl`vUzQea>$W#Ku@DX1IsV7xO$Uf36U3?K%laR73Gx$_U~S^N(+A? z*W=paF{}@@5wjhQ7ZC-F? z=mx9B?*#*tviLHL2 zV6SVhbU)^Ri5Q_f^r*3jtxGuFBaCW#K zc|*5n_S^QOK@kFuh56_n7nGVJZG6!u`xmR&mcvMp*6>wsePAo7m+? zs{Aq4CVH5HA){MSoQb?&E;bq$!4uw&?+;;iE_K=p?N+T?zsA7O1z zN-+cAZ~@eJ^+E*jHk=;y`!)VAXyAv`>Ksz{)*b38_QdNi+5OiE3_z8=E{R%ztqLvJM|b;Ftyb&t2G3?p3Xk@Idi( zN~|bp+^77iYw}i6_gM0vw*Tg(yX3XGJx=kYmq&2y{Z1x9H>+nmr{Nr+MzNsoeP(uQ z@&vL}tqj6(Wm$5=L%AT;_wndeI-lKvo^`em8Yi4=;VlAvmUu+tak_Kg4K0^PcPH^y zf7R^lKOt>;ob#lKPMP-@aD2>AIf7+o=Syr+yrthXTP|mvo)L>2Zkof=kptl`0S&9~?VgxhXKb%sU3rBt`^ zW<_NEPR0i6!LVIl2WR}3G;zNHG*-8Lq84wcV_7~dmch;S6$g`KTHo;cFwQ-<%t@By zXK$&lbp!^Wr31hG%#7n+3*Bq<*;y?vg!1Ur8%O_ip4)W-SWoAAGYS$kjWITST-d}4 zp%z%+gwbJz-7y{!^lo@?if78)1DqY0Y6T`(`ge`m=?L&5(N=lW8HAHzk?8>&!&!;H z^%PM+dpxK3PElkr-m`nEWwF-b(lFtWHbrG_Fh*z+=;*Pw32TfR`!GnL0BO#a^F)p<$9?hi!kf)5mlT6ko& zX~UF0%r9~9!I&DZIvKEsEmYkmSz!7#$U6Qov@QC{yHtwjF9-PPTvl?EmHkPS*9Tgz z{!n;Pz|kJ*!Z+f%l&Fj5XuL}ts5i1_)z5Av_v(7Ct3`%O(qT&l>~fea)^Uk?Axt%3 zfD%r2P$c02!3&Yi7b3bfSKHAIiXRVV4jOgqi}Si?wxZn3(!WdG`{=aKwdL+hD7O)M z2ztj*zX>Fw^3V-5Ybx_%7 z{%DM#LlK%JJ`U=GdKhtzAdY4RZ6Db#g z_7zBA6)PObhf4&NbYaQ1wqmPGmI%hEidM`zJ(cE#2vG9CxN*Q@)zQcB&jfDiln!=c zcObP{^!r5)cL$h5-tkr3QHok+4XA0{D|x7T;fXYuxt~XlAcJ*2B-0p^-0r#)UU4rt z**!rVo)RVJFO%-RN^Zo$s)-jQKao}o-;Kn=*;;^7(FI7Z@Wh)!zp+Tz} zVS#$>4&!)yb`Xc_>ATmoo)0>>w{DLqkveoYCccnNpy84T8u9A2G1Lla9)8<5X#L>I z89!aFBCqdOIMOB+_JxnGD!YSqyfNwE&NFsNq~3E0-G&yc)zu%y`jyNVW{-osf$m4W zoyW9t#T~c^ei_O6t}50lB;#UPLX8H(m`&rKO>A3Ri`jgfhX|4H6cs4>+-1~#jL$lr z!((tCF}}lQC~&OI@(FFMSN=xlQV>T!lpL{==b=D6Axy>NG|hr_hdXEC?G_0!(aHM%6@0m2%8);jNZ$Fao z_;O$TiI}3OG7OsK;y+HdMkxTk^Jgk8H0sT$FA2LfCs4K!1e>UWSqMkU__HzEuiuL@ zG4s*cGAwkL?2cLsrzg;wxK4QSy+&?}O`q#EW7tCx!cZRX`8BX?D`frJc(c$ib-c~X z)E_2&fSmQ5=w1%H2p5pva+5xM^n3A#dvxA_y}CNS1?YGLgGNps5tQwr`4LQxeYno> zX_-YJ-Fsig%fjh3&!lZpMY8!{3^j$8!|^WXB?6g=DOUH&Yx1}To(FcOOnvNpuyh;( z>LC}gvsW>59MC+sZOdy!@T{!}i-$OQ&DrFMkMR#*Xp1e9 zn+18$=~n_>s=NgS@4Ja+IDuUzF9$e##76e;AKdte>(_OTG@k6&klY*M0KIXb3}34m zNLnlX%Yo27do>}!hHBy%0~f&=u%YyrB*NJ-HnvPLdG|egW*z{>n`#3oUIaia++eB* zwJUp}E&iaQ9&7*6L(feeyYD{@3i?1k_;Wxlcs=6jALHu|PB-M4c(y;50A3kdUnvQ< zF;DJN<{ORiYkDqUsw@;`9hX=6U3TQEe)^er7IIOyGAUwz9+uZvRMzM@?)MYku*kwY z4$|dOv3Pc;YzD*@&us^OmQ1NGp?}c==if%{@^!b_a5leWL+_aRwA?}-Ev}fhM3jHm6Kv&;G59R%D?>iRL6>fYw3s*G? z+kzf1IzA`?D_vnDMvO6TJ4zF86dLVrp@ zugL!qdQZK}w3~`Miwq`7+WK@=$Eo;sFYZnOGKmEC#E2mePYwYtWWcPoKB9B zfExR!8De>#myVypJ@8`rx}(BvG5u9a!V?mb%>AokMn>m_Wc5jKIA4!?>)oXQ4yVYcfiD)e8lC%!jGy*xw!!k_FM$gSOsg_ZxV zofd@7IKoq6Xv-uXew=6ip)3gJB!!`qKNxXSiNI43l!UJHKHLLhCVUfs-#(7EbX>McfSZ)-eCcb{-y+e3Qo``G+XU z_!(Y~z-{EA{u=kfqsJY})e#->{RP>u&?qqJZiT7Hc3OY@=<%fj7C@7laqAl;jt3{w z@cYo+XO;n{3`auh?-wGV-K(K4(+)6lLL32}cSZ?}XH{>Y>XF#btM5S#ogwcegH*iq zQ!Z84M?iiriVo9Xn#Vx7XT7CR47y3^(U8s+l9$>go|UehB+{PWkID(4bcDfF;2$JX zyPp76IeE`zB?-E^tKz+ql6W_300MT<>inSi(wcwIYOFALT$AfhX_5Y&K2L0eadLt>ED#st%iOorP-SOT7|3?q45aTdvsINO9 zyj8q6VnDI#Q@a>qzgSXjI_TxQ2^UV9bM+)pVC_Zovz;W4Gx|a|DwQ;e3`z9^Xhm6e z$?U^PU-Q-Qs~{}+FS@ThCcQ|UsW**NtG`_ssz|p<@Q@F?)XY$^Bzvq{F%r#O{f+Zo zoA(b6C7eom;VKpjS61iPwZ(W1oid4bT?5(bGjeUDcnc$|(V1>4c%J`VldJUG{Q7(F z`Igx47?@wz<^0@sDrda5Qv7xfN-%vAUhU8%` zrKEi!<~Tc+5ERbNVacZPH(xsckSdnw>UIXzN3N}=#O7>)4SYQ;pn*&pjds6G)=zUOZUUG zoic}~t-g#MoFDGJFHU|jH;{d7bl5^MC1#hQKIlPMem2GKKUeh0TeVI+^*?RBw&NBb zd(d$zjmIzeqWCZ!q^D;u+Hi%yUM)eIB=yc;?& zkcH%aCwJ$&`<2}5DiQ^(TOtYmYsGhOeu14oWR^t_4%R=;)2Af1`DqTP`YK@*U;7R0 z)bN|59b@sOKk`Uz+1TL4YXK*Dr=yK+xgqx>yOW>W!C%sK0IqF@87si3+YG-7HiDynWjJ_yfwsD zZL?EPFnTW@B-_p$*c^YgLIpG80NL#R#LE%;w-6jQDVooLRH>qaQhhJMy zy&8VLUV~rE?TeQ$(a-plZMOTlRhGA+CPBBzZ)56N&{qT`_nO|?UOTpT95*E$+fHw@ z;c3f#tB)jRK7H3oscJ|mA~*xB8$ij%O@*?yE7bA;;{G@ez0A?sOG<0|G|&3UL?6k} zAu)BUF(Oqy`SGKR)i=YO;qk>{rT0yR2hBN&upc}j8-CE~BVs%05ll@_ndUdpfyHt0 zoD%DrvXt~Q$r^%S7q_-s^**_(r4VwDJ`2X`8Y(NP{M#^g!b**6p-QgxW|QHv2xp8P z2!P+SkdmkZWz~wGMyKM?s4oY2AnNw-=Ck<%jkO|qqgY5d^koB@3f6qqV(Q{yB9Z&u z=+m#=+B^yCRpy*nVar=%WtS7KDrv#>-P;9F97>knP{SMOCE=R;h)Cr8PQkVW-x7EX zH>-AZhhN@lPlpIdsEGD*a;_{H7t_aBAT%1#oPM4Ya#!dZ&(mHa!|H$LODiDkkGiL? zL;ft{nBBGcM;fp|bwEC}Ok-OL-jB}L8Q0e$<+!9|npoJ1n(GF`Lkk>%YMPd3>0%aO zMy?Fw3eLUDIr;~g{xZoi;p{{JH7dn3boyuju3t0u1%YV?E3*2?ef4I!tWmgP5o`rU zfg^AwEMuf2>_s$xcZWdii&TRx&G!?aYD45i2`ziqvh$2$jQ5U(_cwVJ-@3~!=}!xE z8z(FUx33}`1SkpxGy5Dosi3gYCt+0;#4fJ6&Yl;A(D`mqG~R8@@C7w%82d9g4`Xd3?R`P%P3Ao{ zbGG_86?@@9FLFN27H*$X?mX$2XnKa%bC-JaMxa;`@jZWKnRMT5T;bq?WLhEMRD!1@ zMNJ|9^3@GF$@4ONOOz2p4X}*!T1#tvqI+RC_~T7Ou791r*BWsNf3vb3!RR^x>`}>~ zABz|-n=X|90tub0kb@qYtdFwyBiFCu>ux)@M7)3<3n;TcckCPLqkA{U^gxv|D@7$EoKh0{oh%2%` zm(BX`9Rkvia_&wgcW(0Vju+Vcf7T7zg0eqHLhx1|O9HzM^tj(U!{4B*>FK(BKk4&s z4eXTf_J@Y3u&u`dZCbf_ShQRnDg*T4OPUYAzrG}v;IEUU)ch*hHiNC0B8*`y5Ar?o z^OT>Y!8nJb4;Z#r1cvL8dnh5)VTBm?K*P1Wh!z2&i44A&1Va7}q#Zsm6G{ zqp$qO#a%Wlj7idA1q*20?91+z9ia#)=FjdikA4mpV;yg%6NihX*J674RcR46xz8$q zvT6geTsMksa-X)=N8?mR5*~En(tN^bx4#ov)R>s6AT*Qy>3)h(?!P-zbbkRp)cz}_ zYH2M=W8@M`nv(D$9f&3*AKsHb`P8d5^{*-LA7`G(28&ludbn^h+GCYT+}6W7n)W+L zkFI~<29(iV)DjC&VEng=$1|0LW4xS8$Xirf0dTD}|EEnZc)a(S{)sq5Vh-tP>%VI& zItNHq?UQ;~P?jJMGFG91!SV=nyZWc_377YPF=V(oj)7}%O0Wl}w<7O<&LYv^e*Ujjdc5ga%)(kAyd7;gH`{5fHNvs_^Ky~ot{ z2_XQ25DrO*&-d5>IcV!J5er4)Weca$GhaYO@rgGR2%=m{5(H2~$T6oB0FrjL^=>5; zStJ1e`6Omis)nHV1n|e11LTn;D*AMP)^{Iy-Hjb8Jf0HG1LpZa5ZH1tnlCasdq25< zl}@W%*VsikSw^25JF9 zJu3byx>JmkwAgll%%bg4^`xD1Ft}qx1*!@D0@cJ@Xv$0`Z~Z8(A#(}f(31GleR z7W6(x6tT-~em_3jO8^v1oAg=(#jI~XZ#5%SzMoFy3&@u+#|BwQEKr#kp3YNDe%k>D zjY$|8%pU6mbdqO+aE;GlEB&o8YrVJU--*v@@NiLPTyVU&aKSoDZa1GQl{g#_1~ z&3SJid8Bp}h(q80o<^+8?NXty-Ua0wPRA_&mB(2l{jcE|WTu&cUN(R&6NtB+XW!!& z1&Q_LyD*h{FGNkY$!HD`}PJNJNOvs#riLjE;WGqU(2|Ng?5MqZP`pH@4Yn}l+ z7blVA$gtBEZs~`aen)8`)%i-oHri8TFLn5?(|d1{Wp2WMq<^8y>=HxSU8jAiIj)Wj zi4w#XW6Sd1#^g-mjzLC+Um)iqbuaUDkxk12H*Gs}J0U*;cV{kyzLe5Q^!#u^fVS?{ z#)^jD=f`Jr^yk^Ot=t+CTWzLMVtMNuC$5($jnye#Omq237G4>5)&-r5b%1TfLyYFDy z%*)*Y1>TRSRJ}?H+wd+mES=@24I{(+#h)-+lOE0LV@tErrgwM>AR-1-rR-xT(ba++ z>+czU=SPG4qmobf5sPw?xinFC@6~+ zb-nqx6VOJkCPiwQqT8^l{o0~{i*uODB0cszPW5|jzmMGwm?>+@GtZUWu=@GeoF7pa zr_vYw{UEKwQ@cpoqD+R?{t8x|pS$?Cc2QFKd{AVIoN384$zDZ&`BLE^Z&&YQm~BzE z0EpJ7m?^ZbQ||0O<#5Pg5b>>{J~I)3yCLcYcrc*lZ4>90hG*11ZB*4%2^&ag-uY^T?cy(b=k>&z=#`+5es@ zL09%X*x0}DUN(Z^fAC&+FbIY2-(o$8cihnXmsqcU?{}90mTHAA8_HgK>tWBTi+N_& z`pXvY-eFzopBFo9e}t{yS0qCPu|t(5)Zo p1h9!ni1YDH!b&fMzWv3;r6YUkMz^r|&P(8zlAQX(()-4*{vVn?mJ0v? diff --git a/doc/source/_static/gitpod/scipy-github.png b/doc/source/_static/gitpod/scipy-github.png deleted file mode 100644 index 063194dbee1bac75a900e658eff60b1d4f3c2dea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14623 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 diff --git a/doc/source/_static/gitpod/scipy-gitpod-branches.png b/doc/source/_static/gitpod/scipy-gitpod-branches.png deleted file mode 100644 index b4d46a81101328036b27697e8f1c2bdb7ed9cea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96825 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 IJ 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

    d9`^%sn- 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

    !!)ZmLq>{b9dj2lz{Ggzly3d8>|D2}m zuwE{Y4q^&(&{L6JnVFd>(j!99#-TG`vqXLAfK!%78J$=Ou6l*DK`Yhn*O8juZ_)jj&#^s#<4G!FwQMDN!IS)&O^Qr>yxrUeL-++viMOH^~#W` zQS2+-orI6yt5=>c9#0r2wB%?47}vGD1x{XHdELxtI3yV`Z2n0BFhU;c9?xDqVVf|A z2Z#|IH10~hD}y6ON5ER3YlGI1ow5yxWV$2*p~0*VY-shmS^590B@PGIcg%TN>&5`rQU<}#S#Gp8MuY0T z0^SeQ@Ay`WNg!I&`g-25sW0bjHw!9dRQT=uKt>mMrKYQi&YgnB6zQ5FIwzNn%7qe?<#JaDx0c2EkJ~h zT#1Q^U?Vf*(?(|}r>uhr8_Nmj(59$8{KMv}MA_R8si)Y79oRjyMZnfTNX5(HPF$5{ z;h-wBK1#^~XV=3A$E=^M4$$8lqUo?-`cL=rUNl${1e%i8Qw$9)LB!_O^{~e87$(XD zicp*EaXH#nka5S!?!*gKRaRPxFUW@EQHCOrniu3l4ZlqS4-xoS&8iW~dj4^C)(QnI+4;>|}Yn)?jBK&|^t~ z-Vy+q*u^zUvDtY+NX?X&N8T`>`U6H!Ks#YgQ^h zI6#ib1>&2LoidK+uq|Y@2*eHCu`zt_xax^0wzNmevVZ%ua-GXWSJCIt+ts(n>X+wX zu06qfK1%KB$}7=vsCxA4o-uv@T>~t!;Xa9O@F*#gZdX zPEs}|ZWlIhz8JpIiIUQ-l@j@nn223B^}zdXS#F2I2T~FBS$47ZS*rQEp2o9(m)GTv zIFNckJ>Jy2OmW6xceJYAY!1`(UK_V$3NzqD#LPVeXop6BUmd8PI-_j!R?OL8dwy7P8A zN$5=Q$cH+65?|oz+CvW1IAk`r)}F{0Sl_qOUs<3GWogt9b5`9;2UT0ipz6A5gA1iS zuf4f*ZgWO#Pl>ceCn#C$>D%LQXz1XVj97T`xx8sLv)&=}#Y~7wYl*oF<4owbwPAH( z!Gs|D0G;a>W7*!-6{;A=*xcMiaQRNW-|xqu?wQlmom&@yVyhe+C$bWP+5%NoGhRTj z)tI^Ypeqo2D&Z9rH_dcQf+!X65jz})PlQ~mHN+r&2;dguRq)%8g$|(_F#?m8mNvkA zA{h622MxYvfmjcIi8A$TA$74S>Z_5YHCdAvF+AK?bUy){-%m-bq1Xh_78X`wU;~cSVM| fi`dG53OynbVLB+toBRH!08rB63S#9VhW`Hp**^PC diff --git a/doc/source/_static/gitpod/vscode-statusbar.png b/doc/source/_static/gitpod/vscode-statusbar.png deleted file mode 100644 index 3febbcee0ee5e4c66c92c1fab111fe2f956059a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4492 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 Date: Mon, 28 Apr 2025 12:26:25 +0200 Subject: [PATCH 076/251] BLD: optimize sdist contents through a dist script This reduces the size of the sdist by about 50%, from 60 MB to 30 MB. --- .gitattributes | 5 +++++ meson.build | 2 ++ tools/trim_sdist_content.py | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 tools/trim_sdist_content.py diff --git a/.gitattributes b/.gitattributes index 7f967de4f0d1..6e8481e9c91f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,11 @@ # Note: when adding to this list, be aware that you need to commit your changes # before they take effect (can be confusing during testing) +# +# Note: git submodules cannot be handled in this file. For removing +# files and directories from git submodules, see `tools/trim_sdist_content.py` +# This is a Meson "dist script", run during sdist generation only. + .circleci/* export-ignore .github/* export-ignore ci/* export-ignore diff --git a/meson.build b/meson.build index 7c9fd45eab69..19fdc85be865 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,8 @@ project( ], ) +meson.add_dist_script('tools/trim_sdist_content.py') + py3 = import('python').find_installation(pure: false) py3_dep = py3.dependency() diff --git a/tools/trim_sdist_content.py b/tools/trim_sdist_content.py new file mode 100644 index 000000000000..2f7391d02536 --- /dev/null +++ b/tools/trim_sdist_content.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +The purpose of this script is to remove files from the sdist that are not +needed and bloat the sdist size too much. This deals with files from +git submodules, because those cannot be removed by using `export-ignore` +in the top-level `.gitattributes` file. +""" + +import os +import pathlib +import shutil + +dist_root = pathlib.Path(os.environ['MESON_DIST_ROOT']) + +for name in [dist_root / d for d in ( + 'subprojects/boost_math/math/.github', + 'subprojects/boost_math/math/build', + 'subprojects/boost_math/math/config', + 'subprojects/boost_math/math/doc', + 'subprojects/boost_math/math/example', + 'subprojects/boost_math/math/meta', + 'subprojects/boost_math/math/reporting', + 'subprojects/boost_math/math/src', + 'subprojects/boost_math/math/test', + 'subprojects/boost_math/math/tools', + 'subprojects/highs/.github', + 'subprojects/highs/app', + 'subprojects/highs/check', + 'subprojects/highs/docs', + 'subprojects/highs/examples', + 'subprojects/highs/nuget', + 'subprojects/highs/scripts', + 'subprojects/highs/tests', + 'subprojects/xsf/.github', + 'subprojects/xsf/pixi.lock', + 'subprojects/xsf/tests', + )]: + if name.is_file(): + name.unlink() + else: + shutil.rmtree(name) + From 6e210a351671fa3112574a7f7d709c3592152fe7 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Wed, 30 Apr 2025 21:09:41 -0700 Subject: [PATCH 077/251] Fix invocation of linter on Windows On that platform, depending on the shell, the `#!/usr/bin/env python` may not be used. To work around that problem, launch `sys.executable`, instead of trying to run the Python script directly. Since `spin` is always run from the root of the repo, also no need to provide the full path to the linting tool. --- .spin/cmds.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index 071017330cde..433cf2ecf652 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -606,10 +606,12 @@ def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs def lint(ctx, fix, diff_against, files, all, no_cython): """🔦 Run linter on modified files and check for disallowed Unicode characters and possibly-invalid test names.""" - root = Path(__file__).parent.parent + cmd_prefix = [sys.executable] if sys.platform == "win32" else [] - cmd_lint = [os.path.join(root, 'tools', 'lint.py'), - f'--diff-against={diff_against}'] + cmd_lint = cmd_prefix + [ + os.path.join('tools', 'lint.py'), + f'--diff-against={diff_against}' + ] if files != "": cmd_lint += [f'--files={files}'] if all: @@ -620,10 +622,14 @@ def lint(ctx, fix, diff_against, files, all, no_cython): cmd_lint += ['--fix'] util.run(cmd_lint) - cmd_unicode = [os.path.join(root, 'tools', 'check_unicode.py')] + cmd_unicode = cmd_prefix + [ + os.path.join('tools', 'check_unicode.py') + ] util.run(cmd_unicode) - cmd_check_test_name = [os.path.join(root, 'tools', 'check_test_name.py')] + cmd_check_test_name = cmd_prefix + [ + os.path.join('tools', 'check_test_name.py') + ] util.run(cmd_check_test_name) # From scipy: benchmarks/benchmarks/common.py From b0398c7bc544ab334d03c2636d7916bdaa0618b6 Mon Sep 17 00:00:00 2001 From: Sasha Hafner <35272876+sashahafner@users.noreply.github.com> Date: Thu, 1 May 2025 04:37:40 -0400 Subject: [PATCH 078/251] DOC: integrate.solve_bvp: add missing reference details (#22916) --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/integrate/_bvp.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scipy/integrate/_bvp.py b/scipy/integrate/_bvp.py index 74406c89a689..fe2e0ae50b25 100644 --- a/scipy/integrate/_bvp.py +++ b/scipy/integrate/_bvp.py @@ -402,7 +402,9 @@ def solve_newton(n, m, h, col_fun, bc, jac, y, p, B, bvp_tol, bc_tol): References ---------- .. [1] U. Ascher, R. Mattheij and R. Russell "Numerical Solution of - Boundary Value Problems for Ordinary Differential Equations" + Boundary Value Problems for Ordinary Differential Equations", + Philidelphia, PA: Society for Industrial and Applied Mathematics, + 1995. """ # We know that the solution residuals at the middle points of the mesh # are connected with collocation residuals r_middle = 1.5 * col_res / h. @@ -872,9 +874,13 @@ def solve_bvp(fun, bc, x, y, p=None, S=None, fun_jac=None, bc_jac=None, Control and the Maltab PSE", ACM Trans. Math. Softw., Vol. 27, Number 3, pp. 299-316, 2001. .. [2] L.F. Shampine, P. H. Muir and H. Xu, "A User-Friendly Fortran BVP - Solver". + Solver", J. Numer. Anal., Ind. Appl. Math. (JNAIAM), Vol. 1, + Number 2, pp. 201-217, 2006. .. [3] U. Ascher, R. Mattheij and R. Russell "Numerical Solution of - Boundary Value Problems for Ordinary Differential Equations". + Boundary Value Problems for Ordinary Differential Equations", + Philidelphia, PA: Society for Industrial and Applied Mathematics, + 1995. + :doi:`10.1137/1.9781611971231` .. [4] `Cauchy-Riemann equations `_ on Wikipedia. From 7ecbcb8c2ebcbb8c87d2fc98a98bbc9f7e34f497 Mon Sep 17 00:00:00 2001 From: Eric Zitong Zhou Date: Thu, 1 May 2025 02:07:29 -0700 Subject: [PATCH 079/251] TST: linalg.cosin: add test coverage to exception handling for invalid shapes (#22918) --- scipy/linalg/tests/test_decomp_cossin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scipy/linalg/tests/test_decomp_cossin.py b/scipy/linalg/tests/test_decomp_cossin.py index df112f0e4cf7..c43d3e643d3f 100644 --- a/scipy/linalg/tests/test_decomp_cossin.py +++ b/scipy/linalg/tests/test_decomp_cossin.py @@ -112,6 +112,20 @@ def test_cossin_error_non_iterable(): with pytest.raises(ValueError, match="containing the subblocks of X"): cossin(12j) +def test_cossin_error_invalid_shape(): + # Invalid x12 dimensions + p, q = 3, 4 + invalid_x12 = np.ones((p, q + 2)) + valid_ones = np.ones((p, q)) + with pytest.raises(ValueError, + match=r"Invalid x12 dimensions: desired \(3, 4\), got \(3, 6\)"): + cossin((valid_ones, invalid_x12, valid_ones, valid_ones)) + + # Invalid x21 dimensions + invalid_x21 = np.ones(p + 2) + with pytest.raises(ValueError, + match=r"Invalid x21 dimensions: desired \(3, 4\), got \(1, 5\)"): + cossin((valid_ones, valid_ones, invalid_x21, valid_ones)) def test_cossin_error_non_square(): with pytest.raises(ValueError, match="only supports square"): From 4674ab33af519eddc34709c9f75106f95eaf0482 Mon Sep 17 00:00:00 2001 From: Eric Zitong Zhou Date: Thu, 1 May 2025 14:39:31 -0700 Subject: [PATCH 080/251] TST: optimize.least_squares: add test coverage to callback warning (#22919) --- scipy/optimize/tests/test_least_squares.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scipy/optimize/tests/test_least_squares.py b/scipy/optimize/tests/test_least_squares.py index 234fbfad9e4a..c93b92d60efa 100644 --- a/scipy/optimize/tests/test_least_squares.py +++ b/scipy/optimize/tests/test_least_squares.py @@ -813,6 +813,17 @@ def test_loss(self): assert_raises(ValueError, least_squares, fun_trivial, 2.0, method='lm', loss='huber') + + def test_callback_with_lm_method(self): + def callback(x): + assert(False) # Dummy callback function + + with suppress_warnings() as sup: + sup.filter( + UserWarning, + "Callback function specified, but not supported with `lm` method." + ) + least_squares(fun_trivial, x0=[0], method='lm', callback=callback) def test_basic(): From 4446ee13f797ec51ee7c1bc14986c01936a89c5a Mon Sep 17 00:00:00 2001 From: Sturla Molden Date: Sat, 3 May 2025 11:59:47 +0200 Subject: [PATCH 081/251] remove prefetch, likely and unlikely --- scipy/spatial/_ckdtree.pyx | 12 +++------ scipy/spatial/ckdtree/src/build.cxx | 8 +++--- scipy/spatial/ckdtree/src/ckdtree_decl.h | 3 --- scipy/spatial/ckdtree/src/count_neighbors.cxx | 21 +++------------- scipy/spatial/ckdtree/src/distance.h | 10 ++++---- scipy/spatial/ckdtree/src/query.cxx | 25 +++++++------------ .../spatial/ckdtree/src/query_ball_point.cxx | 13 +++------- scipy/spatial/ckdtree/src/query_ball_tree.cxx | 22 +++------------- scipy/spatial/ckdtree/src/query_pairs.cxx | 20 +++------------ scipy/spatial/ckdtree/src/rectangle.h | 8 +++--- .../spatial/ckdtree/src/sparse_distances.cxx | 22 ++++------------ 11 files changed, 43 insertions(+), 121 deletions(-) diff --git a/scipy/spatial/_ckdtree.pyx b/scipy/spatial/_ckdtree.pyx index e0e9244593e6..279c61c883ae 100644 --- a/scipy/spatial/_ckdtree.pyx +++ b/scipy/spatial/_ckdtree.pyx @@ -31,10 +31,6 @@ cdef extern from "": __all__ = ['cKDTree'] -cdef extern from *: - int NPY_LIKELY(int) - int NPY_UNLIKELY(int) - # C++ implementations # =================== @@ -188,7 +184,7 @@ cdef class coo_entries: _dtype = [('i',np.intp),('j',np.intp),('v',np.float64)] res_dtype = np.dtype(_dtype, align = True) n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() uintptr = ( pr) dtype = np.dtype(np.uint8) @@ -211,7 +207,7 @@ cdef class coo_entries: coo_entry *pr dict res_dict n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() res_dict = dict() for k in range(n): @@ -261,7 +257,7 @@ cdef class ordered_pairs: np.uintp_t uintptr np.intp_t n n = self.buf.size() - if NPY_LIKELY(n > 0): + if (n > 0): pr = self.buf.data() uintptr = ( pr) dtype = np.dtype(np.intp) @@ -1076,7 +1072,7 @@ cdef class cKDTree: results = n * [None] for i in range(n): m = (vvres[i].size()) - if NPY_LIKELY(m > 0): + if (m > 0): tmp = m * [None] cur = vvres[i].data() for j in range(m): diff --git a/scipy/spatial/ckdtree/src/build.cxx b/scipy/spatial/ckdtree/src/build.cxx index 1083fc16f7b2..97c07b396604 100644 --- a/scipy/spatial/ckdtree/src/build.cxx +++ b/scipy/spatial/ckdtree/src/build.cxx @@ -52,7 +52,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, } else { - if (CKDTREE_LIKELY(_compact)) { + if (_compact) { /* Recompute hyperrectangle bounds. This should lead to a more * compact kd-tree but comes at the expense of larger construction * time. However, construction time is usually dwarfed by the @@ -107,7 +107,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, return partition_ptr - indices; }; - if (CKDTREE_LIKELY(_median)) { + if (_median) { /* split on median to create a balanced tree * adopted from scikit-learn */ @@ -143,7 +143,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, p = partition_pivot(indices + start_idx, indices + end_idx, split); } - if (CKDTREE_UNLIKELY(p == start_idx || p == end_idx)) { + if (p == start_idx || p == end_idx) { // All children are equal in this dimension, try again with new bounds assert(!_compact); self->tree_buffer->pop_back(); @@ -160,7 +160,7 @@ build(ckdtree *self, ckdtree_intp_t start_idx, intptr_t end_idx, return build(self, start_idx, end_idx, tmp_maxes, tmp_mins, _median, _compact); } - if (CKDTREE_LIKELY(_compact)) { + if (_compact) { _less = build(self, start_idx, p, maxes, mins, _median, _compact); _greater = build(self, p, end_idx, maxes, mins, _median, _compact); } diff --git a/scipy/spatial/ckdtree/src/ckdtree_decl.h b/scipy/spatial/ckdtree/src/ckdtree_decl.h index a89fe5f4de02..d1d577255db0 100644 --- a/scipy/spatial/ckdtree/src/ckdtree_decl.h +++ b/scipy/spatial/ckdtree/src/ckdtree_decl.h @@ -7,9 +7,6 @@ * */ #include #include -#define CKDTREE_LIKELY(x) NPY_LIKELY(x) -#define CKDTREE_UNLIKELY(x) NPY_UNLIKELY(x) -#define CKDTREE_PREFETCH(x, rw, loc) NPY_PREFETCH(x, rw, loc) #define ckdtree_intp_t npy_intp #define ckdtree_fmin(x, y) fmin(x, y) diff --git a/scipy/spatial/ckdtree/src/count_neighbors.cxx b/scipy/spatial/ckdtree/src/count_neighbors.cxx index 0f0bc78d6fd6..9e36fefc32f5 100644 --- a/scipy/spatial/ckdtree/src/count_neighbors.cxx +++ b/scipy/spatial/ckdtree/src/count_neighbors.cxx @@ -100,27 +100,12 @@ traverse( const ckdtree_intp_t end1 = node1->end_idx; const ckdtree_intp_t end2 = node2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); /* brute-force */ for (i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - - if (start2 < end2 - 1) - CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m); - for (j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - double d = MinMaxDist::point_point_p(params->self.tree, sdata + sindices[i] * m, odata + oindices[j] * m, @@ -210,14 +195,14 @@ count_neighbors(struct CNBParams *params, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/distance.h b/scipy/spatial/ckdtree/src/distance.h index f07c75a34b43..036c99a97b62 100644 --- a/scipy/spatial/ckdtree/src/distance.h +++ b/scipy/spatial/ckdtree/src/distance.h @@ -117,10 +117,10 @@ struct BoxDist1D { * * We will fix the convention later. * */ - if (CKDTREE_UNLIKELY(full <= 0)) { + if (full <= 0) { /* A non-periodic dimension */ /* \/ */ - if(max <= 0 || min >= 0) { + if (max <= 0 || min >= 0) { /* do not pass though 0 */ min = ckdtree_fabs(min); max = ckdtree_fabs(max); @@ -230,7 +230,7 @@ struct BoxDist1D { tmax = x - max; tmin = x - min; /* is the test point in this range */ - if(CKDTREE_LIKELY(tmax < 0 && tmin > 0)) { + if(tmax < 0 && tmin > 0) { /* yes. min distance is 0 */ return 0; } @@ -262,8 +262,8 @@ struct BoxDist1D { wrap_distance(const double x, const double hb, const double fb) { double x1; - if (CKDTREE_UNLIKELY(x < -hb)) x1 = fb + x; - else if (CKDTREE_UNLIKELY(x > hb)) x1 = x - fb; + if (x < -hb) x1 = fb + x; + else if (x > hb) x1 = x - fb; else x1 = x; #if 0 printf("ckdtree_fabs_b x : %g x1 %g\n", x, x1); diff --git a/scipy/spatial/ckdtree/src/query.cxx b/scipy/spatial/ckdtree/src/query.cxx index e8bad724a1e9..add06991851f 100644 --- a/scipy/spatial/ckdtree/src/query.cxx +++ b/scipy/spatial/ckdtree/src/query.cxx @@ -133,7 +133,7 @@ struct nodeinfo { } inline void update_side_distance(const int d, const double new_side_distance, const double p) { - if (CKDTREE_UNLIKELY(std::isinf(p))) { + if (std::isinf(p)) { min_distance = ckdtree_fmax(min_distance, new_side_distance); } else { min_distance += new_side_distance - side_distances()[d]; @@ -261,7 +261,7 @@ query_single_point(const ckdtree *self, } /* fiddle approximation factor */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = 1. + eps; epsfac = 1. / (tmp*tmp); } @@ -273,7 +273,7 @@ query_single_point(const ckdtree *self, epsfac = 1. / std::pow((1. + eps), p); /* internally we represent all distances as distance**p */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = distance_upper_bound; distance_upper_bound = tmp*tmp; } @@ -292,15 +292,8 @@ query_single_point(const ckdtree *self, const double *data = self->raw_data; const ckdtree_intp_t *indices = self->raw_indices; - CKDTREE_PREFETCH(data+indices[start_idx]*m, 0, m); - if (start_idx < end_idx - 1) - CKDTREE_PREFETCH(data+indices[start_idx+1]*m, 0, m); - for (i=start_idx; iraw_boxsize_data == NULL)) { + if (self->raw_boxsize_data == NULL) { /* * non periodic : the 'near' node is know from the * relative distance to the split, and @@ -449,13 +442,13 @@ query_single_point(const ckdtree *self, /* fill output arrays with sorted neighbors */ for (i = 0; i < nk; ++i) { - if(CKDTREE_UNLIKELY(k[i] - 1 >= nnb)) { + if (k[i] - 1 >= nnb) { result_indices[i] = self->n; result_distances[i] = inf; } else { neighbor = sorted_neighbors[k[i] - 1]; result_indices[i] = neighbor.contents.intdata; - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) result_distances[i] = std::sqrt(-neighbor.priority); else if ((p == 1.) || (std::isinf(p))) result_distances[i] = -neighbor.priority; @@ -488,12 +481,12 @@ query_knn(const ckdtree *self, ckdtree_intp_t m = self->m; ckdtree_intp_t i; - if(CKDTREE_LIKELY(!self->raw_boxsize_data)) { + if (!self->raw_boxsize_data) { for (i=0; iraw_boxsize_data[j]); } - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) {} diff --git a/scipy/spatial/ckdtree/src/query_ball_point.cxx b/scipy/spatial/ckdtree/src/query_ball_point.cxx index 917d2be20d53..a3ebe0748a41 100644 --- a/scipy/spatial/ckdtree/src/query_ball_point.cxx +++ b/scipy/spatial/ckdtree/src/query_ball_point.cxx @@ -75,15 +75,8 @@ traverse_checking(const ckdtree *self, const ckdtree_intp_t start = lnode->start_idx; const ckdtree_intp_t end = lnode->end_idx; - CKDTREE_PREFETCH(data + indices[start] * m, 0, m); - if (start < end - 1) - CKDTREE_PREFETCH(data + indices[start+1] * m, 0, m); - for (i = start; i < end; ++i) { - if (i < end -2 ) - CKDTREE_PREFETCH(data + indices[i+2] * m, 0, m); - d = MinMaxDist::point_point_p(self, data + indices[i] * m, tpt, p, m, tub); if (d <= tub) { @@ -124,9 +117,9 @@ query_ball_point(const ckdtree *self, const double *x, for (ckdtree_intp_t i=0; i < n_queries; ++i) { const ckdtree_intp_t m = self->m; Rectangle rect(m, self->raw_mins, self->raw_maxes); - if (CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { + if (self->raw_boxsize_data == NULL) { Rectangle point(m, x + i * m, x + i * m); - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) @@ -137,7 +130,7 @@ query_ball_point(const ckdtree *self, const double *x, for(j=0; jraw_boxsize_data[j]); } - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/query_ball_tree.cxx b/scipy/spatial/ckdtree/src/query_ball_tree.cxx index b0af311f289c..9e87e0ff5c43 100644 --- a/scipy/spatial/ckdtree/src/query_ball_tree.cxx +++ b/scipy/spatial/ckdtree/src/query_ball_tree.cxx @@ -90,28 +90,12 @@ traverse_checking(const ckdtree *self, const ckdtree *other, const ckdtree_intp_t end1 = lnode1->end_idx; const ckdtree_intp_t end2 = lnode2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); - for (i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - - if (start2 < end2 - 1) - CKDTREE_PREFETCH(odata + oindices[start2+1] * m, 0, m); - auto &results_i = results[sindices[i]]; for (j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - d = MinMaxDist::point_point_p( self, sdata + sindices[i] * m, @@ -195,14 +179,14 @@ query_ball_tree(const ckdtree *self, const ckdtree *other, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/query_pairs.cxx b/scipy/spatial/ckdtree/src/query_pairs.cxx index 90d8f495dd2c..d3835fb12491 100644 --- a/scipy/spatial/ckdtree/src/query_pairs.cxx +++ b/scipy/spatial/ckdtree/src/query_pairs.cxx @@ -106,31 +106,17 @@ traverse_checking(const ckdtree *self, const ckdtree_intp_t end1 = lnode1->end_idx; const ckdtree_intp_t end2 = lnode2->end_idx; - CKDTREE_PREFETCH(data+indices[start1]*m, 0, m); - if (start1 < end1 - 1) - CKDTREE_PREFETCH(data+indices[start1+1]*m, 0, m); for(i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(data+indices[i+2]*m, 0, m); - /* Special care here to avoid duplicate pairs */ if (node1 == node2) min_j = i + 1; else min_j = start2; - if (min_j < end2) - CKDTREE_PREFETCH(data+indices[min_j]*m, 0, m); - if (min_j < end2 - 1) - CKDTREE_PREFETCH(data+indices[min_j+1]*m, 0, m); - for (j = min_j; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(data+indices[j+2]*m, 0, m); - d = MinMaxDist::point_point_p( self, data + indices[i] * m, @@ -215,14 +201,14 @@ query_pairs(const ckdtree *self, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(self->m, self->raw_mins, self->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if (self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) diff --git a/scipy/spatial/ckdtree/src/rectangle.h b/scipy/spatial/ckdtree/src/rectangle.h index 39e9e0591c55..352def0bbb7e 100644 --- a/scipy/spatial/ckdtree/src/rectangle.h +++ b/scipy/spatial/ckdtree/src/rectangle.h @@ -128,7 +128,7 @@ template p = _p; /* internally we represent all distances as distance ** p */ - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) upper_bound = _upper_bound * _upper_bound; else if ((!std::isinf(p)) && (!std::isinf(_upper_bound))) upper_bound = std::pow(_upper_bound,p); @@ -136,7 +136,7 @@ template upper_bound = _upper_bound; /* fiddle approximation factor */ - if (CKDTREE_LIKELY(p == 2.0)) { + if (p == 2.0) { double tmp = 1. + eps; epsfac = 1. / (tmp*tmp); } @@ -212,7 +212,7 @@ template subnomial = subnomial || ((min2 != 0 && min2 < inaccurate_distance_limit) || max2 < inaccurate_distance_limit); subnomial = subnomial || (min_distance < inaccurate_distance_limit || max_distance < inaccurate_distance_limit); - if (CKDTREE_UNLIKELY(subnomial)) { + if (subnomial) { MinMaxDist::rect_rect_p(tree, rect1, rect2, p, &min_distance, &max_distance); } else { min_distance += (min2 - min1); @@ -235,7 +235,7 @@ template --stack_size; /* assert stack_size >= 0 */ - if (CKDTREE_UNLIKELY(stack_size < 0)) { + if (stack_size < 0) { const char *msg = "Bad stack size. This error should never occur."; throw std::logic_error(msg); } diff --git a/scipy/spatial/ckdtree/src/sparse_distances.cxx b/scipy/spatial/ckdtree/src/sparse_distances.cxx index 1229d1f7bf56..fa7537e0b630 100644 --- a/scipy/spatial/ckdtree/src/sparse_distances.cxx +++ b/scipy/spatial/ckdtree/src/sparse_distances.cxx @@ -23,6 +23,7 @@ traverse(const ckdtree *self, const ckdtree *other, if (tracker->min_distance > tracker->upper_bound) return; + else if (node1->split_dim == -1) { /* 1 is leaf node */ if (node2->split_dim == -1) { /* 1 & 2 are leaves */ @@ -39,24 +40,11 @@ traverse(const ckdtree *self, const ckdtree *other, const ckdtree_intp_t end1 = node1->end_idx; const ckdtree_intp_t end2 = node2->end_idx; - CKDTREE_PREFETCH(sdata + sindices[start1] * m, 0, m); - if (start1 < end1 - 1) - CKDTREE_PREFETCH(sdata + sindices[start1+1] * m, 0, m); for (ckdtree_intp_t i = start1; i < end1; ++i) { - if (i < end1 - 2) - CKDTREE_PREFETCH(sdata + sindices[i+2] * m, 0, m); - - CKDTREE_PREFETCH(odata + oindices[start2] * m, 0, m); - if (start2 < end2 - 1) - CKDTREE_PREFETCH(sdata + oindices[start2+1] * m, 0, m); - for (ckdtree_intp_t j = start2; j < end2; ++j) { - if (j < end2 - 2) - CKDTREE_PREFETCH(odata + oindices[j+2] * m, 0, m); - double d = MinMaxDist::point_point_p( self, sdata + sindices[i] * m, @@ -64,7 +52,7 @@ traverse(const ckdtree *self, const ckdtree *other, p, m, tub); if (d <= tub) { - if (CKDTREE_LIKELY(p == 2.0)) + if (p == 2.0) d = std::sqrt(d); else if ((p != 1) && (!std::isinf(p))) d = std::pow(d, 1. / p); @@ -136,14 +124,14 @@ sparse_distance_matrix(const ckdtree *self, const ckdtree *other, Rectangle r1(self->m, self->raw_mins, self->raw_maxes); Rectangle r2(other->m, other->raw_mins, other->raw_maxes); - if(CKDTREE_LIKELY(self->raw_boxsize_data == NULL)) { - HANDLE(CKDTREE_LIKELY(p == 2), MinkowskiDistP2) + if(self->raw_boxsize_data == NULL) { + HANDLE(p == 2, MinkowskiDistP2) HANDLE(p == 1, MinkowskiDistP1) HANDLE(std::isinf(p), MinkowskiDistPinf) HANDLE(1, MinkowskiDistPp) {} } else { - HANDLE(CKDTREE_LIKELY(p == 2), BoxMinkowskiDistP2) + HANDLE(p == 2, BoxMinkowskiDistP2) HANDLE(p == 1, BoxMinkowskiDistP1) HANDLE(std::isinf(p), BoxMinkowskiDistPinf) HANDLE(1, BoxMinkowskiDistPp) From f129f09bd3868718ff742a2854ebfabfc0730b90 Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Sat, 3 May 2025 12:25:59 +0100 Subject: [PATCH 082/251] Merge pull request #22927 from j-bowhay/gh-22920 MAINT: tools/check_test_name: specify encoding --- tools/check_test_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check_test_name.py b/tools/check_test_name.py index 5d9e6531ae18..ac0dfa81d27c 100755 --- a/tools/check_test_name.py +++ b/tools/check_test_name.py @@ -141,7 +141,7 @@ def main(content: str, file: str) -> int: Path("scipy").rglob("**/tests/**/test*.py"), ["scipy/_lib/_testutils.py"], ): - with open(os.path.join(_file)) as fd: + with open(os.path.join(_file), encoding="utf-8") as fd: _content = fd.read() if f"self.{_node.name}" in _content: should_continue = True From 0242738d32cbdcb028191967fd352b50d7c71ae3 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Sun, 4 May 2025 14:03:38 +0100 Subject: [PATCH 083/251] TST/DOC: `lazy_xp_backends` in `xp_capabilities` (#22686) * Move lazy_xp_backends to xp_capabilities * xp_capabilities support for lazy_xp_function * Support explicit reason=None * Rework table * Code review * allow_dask_compute=True * Update doc/source/dev/api-dev/array_api.rst Co-authored-by: Lucas Colley --------- Co-authored-by: Lucas Colley --- doc/source/dev/api-dev/array_api.rst | 57 +++-- scipy/_lib/_array_api.py | 305 ++++++++++++++++-------- scipy/conftest.py | 24 +- scipy/constants/_constants.py | 2 +- scipy/constants/tests/test_constants.py | 2 +- scipy/optimize/_optimize.py | 6 +- scipy/optimize/tests/test_optimize.py | 2 +- scipy/special/_logsumexp.py | 6 +- scipy/special/tests/test_logsumexp.py | 7 - scipy/stats/_stats_py.py | 80 ++++--- scipy/stats/tests/test_stats.py | 10 +- tools/check_unicode.py | 2 +- 12 files changed, 309 insertions(+), 194 deletions(-) diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 11ffa7130dc9..c4d07a431ced 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -101,8 +101,14 @@ variable is set: - `scipy.fft` - `scipy.io` - `scipy.ndimage` +- `scipy.stats` -Support is provided in `scipy.special` for the following functions: +Individual functions in the above modules provide a capability table in the +documentation like the one below. If the table is absent, the function does not +yet support backends other than NumPy. + +Additionally, support is provided in `scipy.special` for the following functions, even +if they do not have a capability table in the documentation: `scipy.special.log_ndtr`, `scipy.special.ndtr`, `scipy.special.ndtri`, `scipy.special.erf`, `scipy.special.erfc`, `scipy.special.i0`, `scipy.special.i0e`, `scipy.special.i1`, `scipy.special.i1e`, @@ -111,38 +117,31 @@ Support is provided in `scipy.special` for the following functions: `scipy.special.rel_entr`, `scipy.special.rel_entr`, `scipy.special.xlogy`, and `scipy.special.chdtrc`. -Support is provided in `scipy.stats` for the following functions: -`scipy.stats.describe`, `scipy.stats.moment`, `scipy.stats.skew`, -`scipy.stats.kurtosis`, `scipy.stats.kstat`, `scipy.stats.kstatvar`, -`scipy.stats.circmean`, `scipy.stats.circvar`, `scipy.stats.circstd`, -`scipy.stats.entropy`, `scipy.stats.variation` , `scipy.stats.sem`, -`scipy.stats.ttest_1samp`, `scipy.stats.pearsonr`, `scipy.stats.chisquare`, -`scipy.stats.skewtest`, `scipy.stats.kurtosistest`, `scipy.stats.normaltest`, -`scipy.stats.jarque_bera`, `scipy.stats.bartlett`, `scipy.stats.power_divergence`, -and `scipy.stats.monte_carlo_test`. - -Some features provide a capability table in the documentation like this: - -+---------+-------------+-------------+ -| Library | CPU | GPU | -+=========+=============+=============+ -| NumPy | ✓ | n/a | -+---------+-------------+-------------+ -| CuPy | n/a | ✓ | -+---------+-------------+-------------+ -| PyTorch | ✓ | ✗ | -+---------+-------------+-------------+ -| JAX | ✓ | ✓ | -+---------+-------------+-------------+ -| Dask | ✗ | ✗ | -+---------+-------------+-------------+ +Example capabilities table +-------------------------- + +========= ========= ========= +Library CPU GPU +========= ========= ========= +NumPy ✅ n/a +CuPy n/a ✅ +PyTorch ✅ ✅ +JAX ⚠️ no JIT ⛔ +Dask ⛔ n/a +========= ========= ========= In the example above, the feature has some support for NumPy, CuPy, PyTorch, and JAX arrays, but no support for Dask arrays. Some backends, like JAX and PyTorch, natively support multiple devices (CPU and GPU), but SciPy support for such arrays may be -limited; for instance, this SciPy feature is only expected to work with PyTorch arrays -located on the CPU. While the elements of the table marked with "n/a" are inherently -out of scope, we are continually working on filling in the rest. +limited; for instance, this SciPy feature is only expected to work with JAX arrays +located on the CPU. Additionally, some backends can have major caveats; in the example +the function will fail when running inside ``jax.jit``. +Additional caveats may be listed in the docstring of the function. + +While the elements of the table marked with "n/a" are inherently out of scope, we are +continually working on filling in the rest. +Dask wrapping around backends other than NumPy (notably, CuPy) is currently out of scope +but it may change in the future. Please see `the tracker issue`_ for updates. diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 54033aee0092..3ff8d17d260e 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -647,101 +647,154 @@ def is_marray(xp): return "marray" in xp.__name__ - -def _make_capabilities(skip_backends=None, cpu_only=False, np_only=False, - exceptions=None, reason=None): - skip_backends = [] if skip_backends is None else skip_backends - exceptions = [] if exceptions is None else exceptions - - capabilities = dataclasses.make_dataclass('capabilities', ['cpu', 'gpu']) +@dataclasses.dataclass(repr=False) +class _XPSphinxCapability: + cpu: bool | None # None if not applicable + gpu: bool | None + warnings: str | None = None + + def _render(self, value): + if value is None: + return "n/a" + if not value: + return "⛔" + if self.warnings: + res = f"⚠️ {self.warnings}" + assert len(res) <= 20 + return res + return "✅" + + def __str__(self): + cpu = self._render(self.cpu) + gpu = self._render(self.gpu) + return f"{cpu:20} {gpu:20}" + + +def _make_sphinx_capabilities( + # lists of tuples [(module name, reason), ...] + skip_backends=(), xfail_backends=(), + # @pytest.mark.skip/xfail_xp_backends kwargs + cpu_only=False, np_only=False, exceptions=(), + # xpx.lazy_xp_backends kwargs + allow_dask_compute=False, jax_jit=True, + # unused in documentation + reason=None, static_argnums=None, static_argnames=None, +): + exceptions = set(exceptions) # Default capabilities - numpy = capabilities(cpu=True, gpu=False) - strict = capabilities(cpu=True, gpu=False) - cupy = capabilities(cpu=False, gpu=True) - torch = capabilities(cpu=True, gpu=True) - jax = capabilities(cpu=True, gpu=True) - dask = capabilities(cpu=True, gpu=True) - - capabilities = dict(numpy=numpy, array_api_strict=strict, cupy=cupy, - torch=torch, jax=jax, dask=dask) - - for backend, _ in skip_backends: # ignoring the reason - setattr(capabilities[backend], 'cpu', False) - setattr(capabilities[backend], 'gpu', False) - - other_backends = {'cupy', 'torch', 'jax', 'dask'} - - if cpu_only: - for backend in other_backends - set(exceptions): - setattr(capabilities[backend], 'gpu', False) - - if np_only: - for backend in other_backends: - setattr(capabilities[backend], 'cpu', False) - setattr(capabilities[backend], 'gpu', False) + capabilities = { + "numpy": _XPSphinxCapability(cpu=True, gpu=None), + "array_api_strict": _XPSphinxCapability(cpu=True, gpu=None), + "cupy": _XPSphinxCapability(cpu=None, gpu=True), + "torch": _XPSphinxCapability(cpu=True, gpu=True), + "jax.numpy": _XPSphinxCapability(cpu=True, gpu=True, + warnings=None if jax_jit else "no JIT"), + # Note: Dask+CuPy is currently untested and unsupported + "dask.array": _XPSphinxCapability(cpu=True, gpu=None, + warnings="computes graph" if allow_dask_compute else None), + } + + # documentation doesn't display the reason + for module, _ in list(skip_backends) + list(xfail_backends): + backend = capabilities[module] + if backend.cpu is not None: + backend.cpu = False + if backend.gpu is not None: + backend.gpu = False + + for module, backend in capabilities.items(): + if np_only and module not in exceptions | {"numpy"}: + if backend.cpu is not None: + backend.cpu = False + if backend.gpu is not None: + backend.gpu = False + elif cpu_only and module not in exceptions and backend.gpu is not None: + backend.gpu = False return capabilities -def _make_capabilities_table(capabilities): - numpy = capabilities['numpy'] - cupy = capabilities['cupy'] - torch = capabilities['torch'] - jax = capabilities['jax'] - dask = capabilities['dask'] - - for backend in [numpy, cupy, torch, jax, dask]: - for attr in ['cpu', 'gpu']: - val = "✓" if getattr(backend, attr) else "✗" - val += " " * (len('{numpy.}') + len(attr) - 1) - setattr(backend, attr, val) - - table = f""" - +---------+-------------+-------------+ - | Library | CPU | GPU | - +=========+=============+=============+ - | NumPy | {numpy.cpu} | n/a | - +---------+-------------+-------------+ - | CuPy | n/a | {cupy.gpu } | - +---------+-------------+-------------+ - | PyTorch | {torch.cpu} | {torch.gpu} | - +---------+-------------+-------------+ - | JAX | {jax.cpu } | {jax.gpu } | - +---------+-------------+-------------+ - | Dask | {dask.cpu } | {dask.gpu } | - +---------+-------------+-------------+ - """ - return table - - def _make_capabilities_note(fun_name, capabilities): - table = _make_capabilities_table(capabilities) + # Note: deliberately not documenting array-api-strict note = f""" `{fun_name}` has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable ``SCIPY_ARRAY_API=1`` and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported. - {table} + + ==================== ==================== ==================== + Library CPU GPU + ==================== ==================== ==================== + NumPy {capabilities['numpy'] } + CuPy {capabilities['cupy'] } + PyTorch {capabilities['torch'] } + JAX {capabilities['jax.numpy'] } + Dask {capabilities['dask.array'] } + ==================== ==================== ==================== + See :ref:`dev-arrayapi` for more information. """ return textwrap.dedent(note) -def xp_capabilities(capabilities_table=None, **kwargs): +def xp_capabilities( + *, + # Alternative capabilities table. + # Used only for testing this decorator. + capabilities_table=None, + # Generate pytest.mark.skip/xfail_xp_backends. + # See documentation in conftest.py. + # lists of tuples [(module name, reason), ...] + skip_backends=(), xfail_backends=(), + cpu_only=False, np_only=False, reason=None, exceptions=(), + # xpx.testing.lazy_xp_function kwargs. + # Refer to array-api-extra documentation. + allow_dask_compute=False, jax_jit=True, + static_argnums=None, static_argnames=None, +): + """Decorator for a function that states its support among various + Array API compatible backends. + + This decorator has two effects: + 1. It allows tagging tests with ``@make_skip_xp_backends`` or + ``make_xp_pytest_param`` (see below) to automatically generate + SKIP/XFAIL markers and perform additional backend-specific + testing, such as extra validation for Dask and JAX; + 2. It automatically adds a note to the function's docstring, containing + a table matching what has been tested. + + See Also + -------- + make_skip_xp_backends + make_xp_pytest_param + array_api_extra.testing.lazy_xp_function + """ capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) + capabilities = dict( + skip_backends=skip_backends, + xfail_backends=xfail_backends, + cpu_only=cpu_only, + np_only=np_only, + reason=reason, + exceptions=exceptions, + allow_dask_compute=allow_dask_compute, + jax_jit=jax_jit, + static_argnums=static_argnums, + static_argnames=static_argnames, + ) + sphinx_capabilities = _make_sphinx_capabilities(**capabilities) + def decorator(f): @functools.wraps(f) def wrapper(*args, **kwargs): return f(*args, **kwargs) - capabilities_table[wrapper] = kwargs - capabilities = _make_capabilities(**capabilities_table[wrapper]) - - note = _make_capabilities_note(f.__name__, capabilities) + capabilities_table[wrapper] = capabilities + note = _make_capabilities_note(f.__name__, sphinx_capabilities) doc = FunctionDoc(wrapper) doc['Notes'].append(note) wrapper.__doc__ = str(doc).split("\n", 1)[1] # remove signature @@ -750,41 +803,95 @@ def wrapper(*args, **kwargs): return decorator -def make_skip_xp_backends(*funs, capabilities_table=None): +def _make_xp_pytest_marks(*funcs, capabilities_table=None): capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) + import pytest + from scipy._lib.array_api_extra.testing import lazy_xp_function + + marks = [] + for func in funcs: + capabilities = capabilities_table[func] + exceptions = capabilities['exceptions'] + reason = capabilities['reason'] + + if capabilities['cpu_only']: + marks.append(pytest.mark.skip_xp_backends( + cpu_only=True, exceptions=exceptions, reason=reason)) + if capabilities['np_only']: + marks.append(pytest.mark.skip_xp_backends( + np_only=True, exceptions=exceptions, reason=reason)) + + for mod_name, reason in capabilities['skip_backends']: + marks.append(pytest.mark.skip_xp_backends(mod_name, reason=reason)) + for mod_name, reason in capabilities['xfail_backends']: + marks.append(pytest.mark.xfail_xp_backends(mod_name, reason=reason)) + + lazy_kwargs = {k: capabilities[k] + for k in ('allow_dask_compute', 'jax_jit', + 'static_argnums', 'static_argnames')} + lazy_xp_function(func, **lazy_kwargs) + + return marks + + +def make_skip_xp_backends(*funcs, capabilities_table=None): + capabilities_table = (xp_capabilities_table if capabilities_table is None + else capabilities_table) + """Generate pytest decorator for a test function that tests functionality + of one or more Array API compatible functions. + Read the parameters of the ``@xp_capabilities`` decorator applied to the + listed functions and: + + - Generate the ``@pytest.mark.skip_xp_backends`` and + ``@pytest.mark.xfail_xp_backends`` decorators + for the decorated test function + - Tag the function with `xpx.testing.lazy_xp_function` + + See Also + -------- + xp_capabilities + make_xp_pytest_param + array_api_extra.testing.lazy_xp_function + """ + marks = _make_xp_pytest_marks(*funcs, capabilities_table=capabilities_table) + return lambda func: functools.reduce(lambda f, g: g(f), marks, func) + + +def make_xp_pytest_param(func, capabilities_table=None): + """Variant of ``make_skip_xp_backends`` that returns a pytest.param for a function, + with all necessary skip_xp_backends and xfail_xp_backends marks applied:: + + @pytest.mark.parametrize( + func, [make_xp_pytest_param(f1), make_xp_pytest_param(f2)] + ) + def test(func, xp): + ... + + The above is equivalent to: + @pytest.mark.parametrize( + func, [ + pytest.param(f1, marks=[ + pytest.mark.skip_xp_backends(...), + pytest.mark.xfail_xp_backends(...), ...]), + pytest.param(f2, marks=[ + pytest.mark.skip_xp_backends(...), + pytest.mark.xfail_xp_backends(...), ...]), + ) + def test(func, xp): + ... + + See Also + -------- + xp_capabilities + make_skip_xp_backends + array_api_extra.testing.lazy_xp_function + """ import pytest - skip_backends = [] - cpu_only = False - cpu_only_reason = set() - np_only = False - exceptions = [] - - for fun in funs: - skip_backends += capabilities_table[fun].get('skip_backends', []) - cpu_only |= capabilities_table[fun].get('cpu_only', False) - # Empty reason causes the decorator to have no effect - cpu_only_reason.add(capabilities_table[fun].get('reason', "No reason given.")) - np_only |= capabilities_table[fun].get('np_only', False) - exceptions += capabilities_table[fun].get('exceptions', []) - - decorators = [] - if cpu_only: - kwargs = dict(cpu_only=True, exceptions=exceptions) - kwargs |= {'reason': "\n".join(cpu_only_reason)} - decorators.append(pytest.mark.skip_xp_backends(**kwargs)) - - if np_only: - decorators.append(pytest.mark.skip_xp_backends(np_only=True)) - - for backend, reason in skip_backends: - backends = {'dask': 'dask.array', 'jax': 'jax.numpy'} - backend = backends.get(backend, backend) - decorators.append(pytest.mark.skip_xp_backends(backend, reason=reason)) - - return lambda fun: functools.reduce(lambda f, g: g(f), decorators, fun) + marks = _make_xp_pytest_marks(func, capabilities_table=capabilities_table) + return pytest.param(func, marks=marks) # Is it OK to have a dictionary that is mutated (once upon import) in many places? diff --git a/scipy/conftest.py b/scipy/conftest.py index b3ea9ec20187..1ed736a2d3c9 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -304,21 +304,20 @@ def _backends_kwargs_from_request(request, skip_or_xfail): f"must be a subset of {list(xp_known_backends)}") if marker.kwargs.get('np_only', False): - reason = marker.kwargs.get( - "reason", "do not run with non-NumPy backends") + reason = marker.kwargs.get("reason") or "do not run with non-NumPy backends" for backend in xp_skip_np_only_backends - exceptions: reasons[backend].append(reason) elif marker.kwargs.get('cpu_only', False): - reason = marker.kwargs.get( - "reason", "no array-agnostic implementation or delegation available " - "for this backend and device") + reason = marker.kwargs.get("reason") or ( + "no array-agnostic implementation or delegation available " + "for this backend and device") for backend in xp_skip_cpu_only_backends - exceptions: reasons[backend].append(reason) elif marker.kwargs.get('eager_only', False): - reason = marker.kwargs.get( - "reason", "eager checks not executed on lazy backends") + reason = marker.kwargs.get("reason") or ( + "eager checks not executed on lazy backends") for backend in xp_skip_eager_only_backends - exceptions: reasons[backend].append(reason) @@ -328,8 +327,8 @@ def _backends_kwargs_from_request(request, skip_or_xfail): if backend not in xp_known_backends: raise ValueError(f"Unknown backend: {backend}; " f"must be one of {list(xp_known_backends)}") - reason = marker.kwargs.get( - "reason", f"do not run with array API backend: {backend}") + reason = marker.kwargs.get("reason") or ( + f"do not run with array API backend: {backend}") # reason overrides the ones from cpu_only, np_only, and eager_only. # This is regardless of order of appearence of the markers. reasons[backend].insert(0, reason) @@ -403,9 +402,10 @@ def skip_or_xfail_xp_backends(request: pytest.FixtureRequest, request, skip_or_xfail=skip_or_xfail ) xp = request.param - skip_or_xfail = getattr(pytest, skip_or_xfail) - reason = skip_xfail_reasons.get(xp.__name__) - if reason: + if xp.__name__ in skip_xfail_reasons: + reason = skip_xfail_reasons[xp.__name__] + assert reason # Default reason applied above + skip_or_xfail = getattr(pytest, skip_or_xfail) skip_or_xfail(reason=reason) diff --git a/scipy/constants/_constants.py b/scipy/constants/_constants.py index fc4bdf83d60e..2cbe5dac272a 100644 --- a/scipy/constants/_constants.py +++ b/scipy/constants/_constants.py @@ -225,7 +225,7 @@ # functions for conversions that are not linear -@xp_capabilities() +@xp_capabilities(static_argnames=("old_scale", "new_scale")) def convert_temperature( val: "npt.ArrayLike", old_scale: str, diff --git a/scipy/constants/tests/test_constants.py b/scipy/constants/tests/test_constants.py index e4f40fbed4cd..e1628ce31081 100644 --- a/scipy/constants/tests/test_constants.py +++ b/scipy/constants/tests/test_constants.py @@ -4,7 +4,7 @@ from scipy._lib._array_api_no_0d import xp_assert_equal, xp_assert_close from scipy._lib._array_api import make_skip_xp_backends -skip_xp_backends = pytest.mark.skip_xp_backends +lazy_xp_modules = [sc] @make_skip_xp_backends(sc.convert_temperature) diff --git a/scipy/optimize/_optimize.py b/scipy/optimize/_optimize.py index 00d847619a40..15c65f0ccfac 100644 --- a/scipy/optimize/_optimize.py +++ b/scipy/optimize/_optimize.py @@ -388,7 +388,7 @@ def rosen(x): return r -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_der(x): """ The derivative (i.e. gradient) of the Rosenbrock function. @@ -429,7 +429,7 @@ def rosen_der(x): return der -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_hess(x): """ The Hessian matrix of the Rosenbrock function. @@ -472,7 +472,7 @@ def rosen_hess(x): return H + xpx.create_diagonal(diagonal, xp=xp) -@xp_capabilities(skip_backends=[('jax', "JAX doesn't allow item assignment.")]) +@xp_capabilities(skip_backends=[('jax.numpy', "JAX doesn't allow item assignment.")]) def rosen_hess_prod(x, p): """ Product of the Hessian matrix of the Rosenbrock function with a vector. diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index 5886ad0b2f48..ca56aeb9d63c 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -38,7 +38,7 @@ from scipy._lib._array_api import make_skip_xp_backends from scipy._lib._util import MapWrapper -skip_xp_backends = pytest.mark.skip_xp_backends +lazy_xp_modules = [optimize] def test_check_grad(): diff --git a/scipy/special/_logsumexp.py b/scipy/special/_logsumexp.py index 01f102d9d531..b6ad697d2d2d 100644 --- a/scipy/special/_logsumexp.py +++ b/scipy/special/_logsumexp.py @@ -12,7 +12,7 @@ __all__ = ["logsumexp", "softmax", "log_softmax"] -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "keepdims", "return_sign")) def logsumexp(a, axis=None, b=None, keepdims=False, return_sign=False): """Compute the log of the sum of exponentials of input elements. @@ -249,7 +249,7 @@ def _logsumexp(a, b, *, axis, return_sign, xp): return out, sgn -@xp_capabilities() +@xp_capabilities(static_argnames="axis") def softmax(x, axis=None): r"""Compute the softmax function. @@ -348,7 +348,7 @@ def softmax(x, axis=None): return exp_x_shifted / xp.sum(exp_x_shifted, axis=axis, keepdims=True) -@xp_capabilities() +@xp_capabilities(static_argnames="axis") def log_softmax(x, axis=None): r"""Compute the logarithm of the softmax function. diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index 02cd288260c5..19776c60a4ab 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -12,17 +12,10 @@ from scipy.special import log_softmax, logsumexp, softmax from scipy.special._logsumexp import _wrap_radians -from scipy._lib.array_api_extra.testing import lazy_xp_function - dtypes = ['float32', 'float64', 'int32', 'int64', 'complex64', 'complex128'] integral_dtypes = ['int32', 'int64'] -lazy_xp_function(_wrap_radians, static_argnames="xp") -lazy_xp_function(logsumexp, static_argnames=("axis", "keepdims", "return_sign")) -lazy_xp_function(softmax, static_argnames="axis") -lazy_xp_function(log_softmax, static_argnames="axis") - def test_wrap_radians(xp): x = xp.asarray([-math.pi-1, -math.pi, -1, -1e-300, diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 561ae7821f44..a8a6ee6ae6e6 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -162,7 +162,7 @@ def _unpack_CorrelationResult(res): # note that `weights` are paired with `x` -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "dtype")) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -246,7 +246,8 @@ def gmean(a, axis=0, dtype=None, weights=None): return xp.exp(_xp_mean(log_a, axis=axis, weights=weights)) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "dtype"), + jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -347,7 +348,8 @@ def hmean(a, axis=0, dtype=None, *, weights=None): return 1.0 / _xp_mean(1.0 / a, axis=axis, weights=weights) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "dtype"), + jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -628,7 +630,7 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): return a, mask -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, result_to_tuple=lambda x: (x,) @@ -684,7 +686,7 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): return mean[()] if mean.ndim == 0 else mean -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -744,7 +746,7 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): return _xp_var(a, correction=ddof, axis=axis, nan_policy='omit', xp=xp) -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -808,7 +810,7 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): return res[()] if res.ndim == 0 else res -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -871,7 +873,7 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): return res[()] if res.ndim == 0 else res -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -924,7 +926,7 @@ def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): return tvar(a, limits, inclusive, axis, ddof, _no_deco=True)**0.5 -@xp_capabilities() +@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -1016,7 +1018,6 @@ def _moment_tuple(x, n_out): return tuple(x) if n_out > 1 else (x,) -@xp_capabilities() # `moment` fits into the `_axis_nan_policy` pattern, but it is a bit unusual # because the number of outputs is variable. Specifically, # `result_to_tuple=lambda x: (x,)` may be surprising for a function that @@ -1040,6 +1041,8 @@ def _moment_tuple(x, n_out): # empty, there is no distinction between the `moment` function being called # with parameter `order=1` and `order=[1]`; the latter *should* produce # the same as the former but with a singleton zeroth dimension. +@xp_capabilities(static_argnames=("axis", "nan_policy"), + jax_jit=False, allow_dask_compute=True) @_rename_parameter('moment', 'order') @_axis_nan_policy_factory( # noqa: E302 _moment_result_object, n_samples=1, result_to_tuple=_moment_tuple, @@ -1260,7 +1263,8 @@ def _share_masks(*args, xp): return args[0] if len(args) == 1 else args -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "bias", "nan_policy"), + jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 ) @@ -1361,7 +1365,8 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): return vals[()] if vals.ndim == 0 else vals -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "fisher", "bias", "nan_policy"), + jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 ) @@ -1475,7 +1480,8 @@ def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): 'kurtosis')) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "ddof", "bias", "nan_policy"), + jax_jit=False, allow_dask_compute=True) def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): """Compute several descriptive statistics of the passed array. @@ -1599,7 +1605,8 @@ def _get_pvalue(statistic, distribution, alternative, symmetric=True, xp=None): SkewtestResult = namedtuple('SkewtestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "nan_policy", "alternative"), + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SkewtestResult, n_samples=1, too_small=7) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1709,7 +1716,8 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): KurtosistestResult = namedtuple('KurtosistestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "nan_policy", "alternative"), + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(KurtosistestResult, n_samples=1, too_small=4) def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): r"""Test whether a dataset has normal kurtosis. @@ -1823,7 +1831,8 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): NormaltestResult = namedtuple('NormaltestResult', ('statistic', 'pvalue')) -@xp_capabilities() +@xp_capabilities(static_argnames=("axis", "nan_policy"), + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(NormaltestResult, n_samples=1, too_small=7) def normaltest(a, axis=0, nan_policy='propagate'): r"""Test whether a sample differs from a normal distribution. @@ -1901,7 +1910,7 @@ def normaltest(a, axis=0, nan_policy='propagate'): return NormaltestResult(statistic, pvalue) -@xp_capabilities() +@xp_capabilities(static_argnames="axis", jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) def jarque_bera(x, *, axis=None): r"""Perform the Jarque-Bera goodness of fit test on sample data. @@ -2679,7 +2688,7 @@ def _isconst(x): return (y[0] == y).all(keepdims=True) -@xp_capabilities() +@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) def zscore(a, axis=0, ddof=0, nan_policy='propagate'): """ Compute the z score. @@ -2765,7 +2774,7 @@ def zscore(a, axis=0, ddof=0, nan_policy='propagate'): return zmap(a, a, axis=axis, ddof=ddof, nan_policy=nan_policy) -@xp_capabilities() +@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) def gzscore(a, *, axis=0, ddof=0, nan_policy='propagate'): """ Compute the geometric standard score. @@ -2860,7 +2869,7 @@ def gzscore(a, *, axis=0, ddof=0, nan_policy='propagate'): return zscore(log(a), axis=axis, ddof=ddof, nan_policy=nan_policy) -@xp_capabilities() +@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): """ Calculate the relative z-scores. @@ -2941,7 +2950,7 @@ def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): return z -@xp_capabilities() +@xp_capabilities(static_argnames=('axis', 'ddof', 'keepdims', 'nan_policy')) def gstd(a, axis=0, ddof=1, *, keepdims=False, nan_policy='propagate'): r""" Calculate the geometric standard deviation of an array. @@ -4384,7 +4393,9 @@ def confidence_interval(self, confidence_level=0.95, method=None): return ci -@xp_capabilities(cpu_only=True, exceptions=['cupy']) +@xp_capabilities(cpu_only=True, exceptions=['cupy'], + static_argnames=("alternative", "method", "axis"), + jax_jit=False, allow_dask_compute=True) def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): r""" Pearson correlation coefficient and p-value for testing non-correlation. @@ -6028,7 +6039,8 @@ def unpack_TtestResult(res): res._standard_error, res._estimate) -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, result_to_tuple=unpack_TtestResult, n_outputs=6) # nan_policy handled by `_axis_nan_policy`, but needs to be left @@ -6317,7 +6329,8 @@ def _equal_var_ttest_denom(v1, n1, v2, n2, xp=None): Ttest_indResult = namedtuple('Ttest_indResult', ('statistic', 'pvalue')) -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + static_argnames=("equal_var", "alternative")) def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, equal_var=True, alternative="two-sided"): r""" @@ -6463,7 +6476,13 @@ def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, _ttest_ind_dep_msg = "Use ``method`` to perform a permutation test." -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + # FIXME this is a test artefact + # xpx.lazy_xp_function can't repack returned object TtestResult + # https://github.com/data-apis/array-api-extra/issues/270 + jax_jit=False, + static_argnames=("axis", "equal_var", "nan_policy", "permutations", + "random_state", "alternative", "trim", "method")) @_deprecate_positional_args(version='1.17.0', deprecated_args={'permutations', 'random_state'}, custom_message=_ttest_ind_dep_msg) @@ -7051,6 +7070,8 @@ def _get_len(a, axis, msg): return n +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(pack_TtestResult, default_axis=0, n_samples=2, result_to_tuple=unpack_TtestResult, n_outputs=6, paired=True) @@ -7176,7 +7197,8 @@ def _pd_nsamples(kwargs): return 2 if kwargs.get('f_exp', None) is not None else 1 -@xp_capabilities() +@xp_capabilities(static_argnames=("ddof", "axis", "lambda_"), + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): @@ -7417,7 +7439,8 @@ def _power_divergence(f_obs, f_exp, ddof, axis, lambda_, sum_check=True): -@xp_capabilities() +@xp_capabilities(static_argnames=("ddof, axis", "sum_check"), + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def chisquare(f_obs, f_exp=None, ddof=0, axis=0, *, sum_check=True): @@ -8906,7 +8929,8 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", @xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], - reason=('Delegation for `special.stdtr` only implemented for CuPy and JAX.')) + reason=('Delegation for `special.stdtr` only implemented for CuPy and JAX.'), + static_argnames=("method", "axis"), jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, kwd_samples=['weights'], paired=True) def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): """ diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 8bea176b58d1..ac8f7ac0810a 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -45,8 +45,8 @@ SCIPY_ARRAY_API, make_skip_xp_backends) from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal import scipy._lib.array_api_extra as xpx -from scipy._lib.array_api_extra.testing import lazy_xp_function +lazy_xp_modules = [stats] skip_xp_backends = pytest.mark.skip_xp_backends @@ -74,14 +74,6 @@ TINY = array([1e-12,2e-12,3e-12,4e-12,5e-12,6e-12,7e-12,8e-12,9e-12], float) ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float) -lazy_xp_modules = [stats] -lazy_xp_function(stats.tmean, static_argnames=("inclusive", "axis")) -lazy_xp_function(stats.tvar, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tstd, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tsem, static_argnames=("inclusive", "axis", "ddof")) -lazy_xp_function(stats.tmin, static_argnames=("inclusive", "axis")) -lazy_xp_function(stats.tmax, static_argnames=("inclusive", "axis")) - class TestTrimmedStats: # TODO: write these tests to handle missing values properly diff --git a/tools/check_unicode.py b/tools/check_unicode.py index 3075be1e34aa..893b97685984 100755 --- a/tools/check_unicode.py +++ b/tools/check_unicode.py @@ -18,7 +18,7 @@ latin1_letters = set(chr(cp) for cp in range(192, 256)) greek_letters = set('αβγδεζηθικλμνξoπρστυϕχψω' + 'ΓΔΘΛΞΠΣϒΦΨΩ') box_drawing_chars = set(chr(cp) for cp in range(0x2500, 0x2580)) -extra_symbols = set('®ő∫≠≥≤±∞²³·→√✓✗') +extra_symbols = set('®ő∫≠≥≤±∞²³·→√✅⛔⚠️') allowed = latin1_letters | greek_letters | box_drawing_chars | extra_symbols # END_INCLUDE_RST (do not change this line!) From f6c799cf100f2567922934dbcc366870cb26f9d4 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Sun, 4 May 2025 18:45:12 +0200 Subject: [PATCH 084/251] DOC: linalg: update roadmap entry for BLAS/LAPACK bindings The Accelerate info was outdated, it's been supported for quite a while. Closes gh-22921 [docs only] --- doc/source/dev/roadmap-detailed.rst | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/doc/source/dev/roadmap-detailed.rst b/doc/source/dev/roadmap-detailed.rst index b1ebf31edb5c..495d49772b5c 100644 --- a/doc/source/dev/roadmap-detailed.rst +++ b/doc/source/dev/roadmap-detailed.rst @@ -234,6 +234,8 @@ Ideas for new features: - Add type-generic wrappers in the Cython BLAS and LAPACK - Make many of the linear algebra routines into gufuncs +- Complete support for batched operations (see + `gh-21466 `__) **BLAS and LAPACK** @@ -241,20 +243,15 @@ The Python and Cython interfaces to BLAS and LAPACK in ``scipy.linalg`` are one of the most important things that SciPy provides. In general ``scipy.linalg`` is in good shape, however we can make a number of improvements: -1. Library support. Our released wheels now ship with OpenBLAS, which is - currently the only feasible performant option (ATLAS is too slow, MKL cannot - be the default due to licensing issues, Accelerate support is dropped - because Apple doesn't update Accelerate anymore). OpenBLAS isn't very stable - though, sometimes its releases break things and it has issues with threading - (currently the only issue for using SciPy with PyPy3). We need at the very - least better support for debugging OpenBLAS issues, and better documentation - on how to build SciPy with it. An option is to use BLIS for a BLAS - interface (see `numpy gh-7372 `__). - -2. Support for newer LAPACK features. In SciPy 1.2.0 we increased the minimum - supported version of LAPACK to 3.4.0. Now that we dropped Python 2.7, we - can increase that version further (MKL + Python 2.7 was the blocker for - >3.4.0 previously) and start adding support for new features in LAPACK. +1. Add support for ILP64 (64-bit) BLAS and LAPACK (see + `gh-21889 `__) +2. Unify the two sets of low-level BLAS/LAPACK wrappers, probably dropping the + ``f2py``-based ones (see + `gh-20682 `__) +3. Improve and document the various ways we link to BLAS and LAPACK from C + and C++ code internally in SciPy (see + `gh-20002 `__ and + `gh-21130 `__) misc From faf582ce790ff27ca208103b89529ca8f394f077 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 5 May 2025 09:31:11 +0200 Subject: [PATCH 085/251] BUG: interpolate: do not call PyArray macros on non-arrays closes gh-22931 --- scipy/interpolate/src/_dierckxmodule.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scipy/interpolate/src/_dierckxmodule.cc b/scipy/interpolate/src/_dierckxmodule.cc index 6d51f20dca81..ba8a21851441 100644 --- a/scipy/interpolate/src/_dierckxmodule.cc +++ b/scipy/interpolate/src/_dierckxmodule.cc @@ -23,8 +23,7 @@ check_array(PyObject *obj, npy_intp ndim, int typenum) { if(!cond) { std::string msg = ("Expected a " + std::to_string(ndim) + "-dim C contiguous array " + - " of dtype = " + std::to_string(typenum) + "( got " + - std::to_string(PyArray_TYPE((PyArrayObject*)obj)) +" )\n"); + " of dtype = " + std::to_string(typenum) + "\n"); // XXX: name the dtype from typenum? Also type of arg if not array PyErr_SetString(PyExc_ValueError, msg.c_str()); return 0; From e4bd43322918a4364f8e26c6c1abf65ba1212405 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 5 May 2025 10:28:25 +0200 Subject: [PATCH 086/251] MAINT: signal: address review comments --- scipy/_lib/array_api_compat | 2 +- scipy/_lib/array_api_extra | 2 +- scipy/signal/_filter_design.py | 14 +++------ scipy/signal/_polyutils.py | 7 ++--- scipy/signal/tests/test_filter_design.py | 40 +++++++++--------------- 5 files changed, 24 insertions(+), 41 deletions(-) diff --git a/scipy/_lib/array_api_compat b/scipy/_lib/array_api_compat index 621494be1bd8..e600449a645c 160000 --- a/scipy/_lib/array_api_compat +++ b/scipy/_lib/array_api_compat @@ -1 +1 @@ -Subproject commit 621494be1bd8682f1d76ae874272c12464953d3d +Subproject commit e600449a645c2e6ce5a2276da0006491f097c096 diff --git a/scipy/_lib/array_api_extra b/scipy/_lib/array_api_extra index 0d26a7462a3f..bb6129b1bfe3 160000 --- a/scipy/_lib/array_api_extra +++ b/scipy/_lib/array_api_extra @@ -1 +1 @@ -Subproject commit 0d26a7462a3fbf5ed9e42e261bdb3b39f25e2faf +Subproject commit bb6129b1bfe344b9807a2f28451fe9211efe0b1b diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 000112178a48..34c1a8bd2eb2 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -18,7 +18,7 @@ import scipy._lib.array_api_extra as xpx from scipy._lib._array_api import ( - array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax + array_namespace, xp_promote, xp_size, xp_default_dtype, is_jax, xp_float_to_complex, ) from scipy._lib.array_api_compat import numpy as np_compat @@ -132,14 +132,8 @@ def findfreqs(num, den, N, kind='ba'): else: raise ValueError("input must be one of {'ba', 'zp'}") - # XXX a use case for .astype("complex floating"), finally - if xp.isdtype(ep.dtype, 'real floating'): - tgt_dtype = xp.complex64 if ep.dtype == xp.float32 else xp.complex128 - ep = xp.astype(ep, tgt_dtype) - - if xp.isdtype(tz.dtype, 'real floating'): - tgt_dtype = xp.complex64 if tz.dtype == xp.float32 else xp.complex128 - tz = xp.astype(tz, tgt_dtype) + ep = xp_float_to_complex(ep, xp=xp) + tz = xp_float_to_complex(tz, xp=xp) if ep.shape[0] == 0: ep = xp.asarray([-1000], dtype=ep.dtype) @@ -154,6 +148,8 @@ def findfreqs(num, den, N, kind='ba'): xp.log10(xp.max(3*xp.abs(xp.real(ez) + integ) + 1.5*xp.imag(ez))) + 0.5 ) + # the fudge factor is for backwards compatibility: round(-1.5) can be -1 or -2 + # depending on the the floating-point jitter in -1.5 fudge = 1e-14 if is_jax(xp) else 0 lfreq = xp.round( xp.log10(0.1*xp.min(xp.abs(xp.real(ez + integ)) + 2*xp.imag(ez))) - 0.5 - fudge diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py index 6a074dc3a730..757c62ab2ca1 100644 --- a/scipy/signal/_polyutils.py +++ b/scipy/signal/_polyutils.py @@ -16,7 +16,8 @@ def polyroots(coef, *, xp): root_func = getattr(xp, 'roots', None) if root_func: - # OK on numpy and jax, is broken on cupy + # NB: cupy.roots is broken in CuPy 13.x, but CuPy is handled via delegation + # so we never hit this code path with xp being cupy return root_func(coef) # companion matrix @@ -39,8 +40,6 @@ def polyroots(coef, *, xp): def polyval(p, x, *, xp): """ Old-style polynomial, `np.polyval` """ - p = xp.asarray(p) - x = xp.asarray(x) y = xp.zeros_like(x) for pv in p: @@ -53,7 +52,6 @@ def polyval(p, x, *, xp): # https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L845-L894 def npp_polyval(x, c, *, xp, tensor=True): - c = xp.asarray(c, copy=None) c = xpx.atleast_nd(c, ndim=1, xp=xp) if isinstance(x, tuple | list): x = xp.asarray(x) @@ -68,7 +66,6 @@ def npp_polyval(x, c, *, xp, tensor=True): # https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L758-L842 def npp_polyvalfromroots(x, r, *, xp, tensor=True): - r = xp.asarray(r, copy=None) r = xpx.atleast_nd(r, ndim=1, xp=xp) # if r.dtype.char in '?bBhHiIlLqQpP': # r = r.astype(np.double) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index c3536a5e74ee..e9476ac18826 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -593,8 +593,7 @@ def test_backward_compat(self, xp): assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - @skip_xp_backends(np_only=True, reason="numpy scalars") - def test_w_or_N_types(self, xp): + def test_w_or_N_types(self): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): @@ -663,8 +662,7 @@ def test_backward_compat(self, xp): assert_array_almost_equal(w1, w2) assert_array_almost_equal(h1, h2) - @skip_xp_backends(np_only=True, reason="numpy scalars") - def test_w_or_N_types(self, xp): + def test_w_or_N_types(self): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): @@ -727,14 +725,13 @@ def test_basic2(self, xp): assert_array_almost_equal(w, expected_w) assert_array_almost_equal(h, h_whole) - @skip_xp_backends(np_only=True, reason="numpy scalars") - def test_basic3(self, xp): - t = xp.linspace(0, 1, 4, endpoint=False) - expected_w = xp.linspace(0, 2 * xp.pi, 4, endpoint=False) + def test_basic3(self): + t = np.linspace(0, 1, 4, endpoint=False) + expected_w = np.linspace(0, 2 * np.pi, 4, endpoint=False) for b, a, h_whole in zip( - (xp.asarray([1., 0, 0, 0]), xp.sin(2 * xp.pi * t)), - (xp.asarray([1., 0, 0, 0]), xp.asarray([0.5, 0, 0, 0])), - (xp.asarray([1., 1., 1., 1.]), xp.asarray([0, -4j, 0, 4j])) + (np.asarray([1., 0, 0, 0]), np.sin(2 * np.pi * t)), + (np.asarray([1., 0, 0, 0]), np.asarray([0.5, 0, 0, 0])), + (np.asarray([1., 1., 1., 1.]), np.asarray([0, -4j, 0, 4j])) ): w, h = freqz(b, a, worN=np.int32(4), whole=True) @@ -857,7 +854,7 @@ def test_broadcasting2(self, xp): b = np.random.rand(3, 5, 1) b = xp.asarray(b) for whole in [False, True]: - for worN in [16, 17, xp.linspace(0, 1, 10)]: # FIXME + for worN in [16, 17, xp.linspace(0, 1, 10)]: w, h = freqz(b, worN=worN, whole=whole) for k in range(b.shape[1]): bk = b[:, k, 0] @@ -902,8 +899,7 @@ def test_broadcasting4(self, xp): xp_assert_close(ww, xp.reshape(worN, (-1,)), rtol=1e-14) xp_assert_close(hh, xp.reshape(h[k, :, :], (-1,))) - @skip_xp_backends(np_only=True) - def test_backward_compat(self, xp): + def test_backward_compat(self): # For backward compatibility, test if None act as a wrapper for default w1, h1 = freqz([1.0], 1) w2, h2 = freqz([1.0], 1, None) @@ -949,8 +945,7 @@ def test_fs_param(self, xp): xp_assert_close(h1, h2) xp_assert_close(w1, xp.asarray(w), check_dtype=False) - @skip_xp_backends(np_only=True, reason="numpy scalars") - def test_w_or_N_types(self, xp): + def test_w_or_N_types(self): # Measure at 7 (polyval) or 8 (fft) equally-spaced points for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), np.array(7), @@ -968,9 +963,9 @@ def test_w_or_N_types(self, xp): # Measure at frequency 8 Hz for w in (8.0, 8.0+0j): # Only makes sense when fs is specified - w_out, h = freqz(xp.asarray([1.0]), worN=w, fs=100) - assert_array_almost_equal(w_out, xp.asarray([8])) - assert_array_almost_equal(h, xp.asarray(1.), check_0d=False) + w_out, h = freqz(np.asarray([1.0]), worN=w, fs=100) + assert_array_almost_equal(w_out, np.asarray([8])) + assert_array_almost_equal(h, np.asarray(1.), check_0d=False) def test_nyquist(self, xp): w, h = freqz(xp.asarray([1.0]), worN=8, include_nyquist=True) @@ -1009,7 +1004,6 @@ def test_17289(self, whole, nyquist, worN, xp): _, Dpoly = freqz(d, worN=w) xp_assert_close(Drfft, Dpoly) - @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): freqz([1.0], fs=np.array([10, 20])) @@ -1259,7 +1253,6 @@ def test_fs_param2(self, xp): xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) - @skip_xp_backends(np_only=True, reason="numpy scalars") def test_w_or_N_types(self): # Measure at 7 (polyval) or 8 (fft) equally-spaced points for N in (7, np.int8(7), np.int16(7), np.int32(7), np.int64(7), @@ -1282,7 +1275,6 @@ def test_w_or_N_types(self): assert_array_almost_equal(w_out, [8]) assert_array_almost_equal(h, [1]) - @skip_xp_backends(np_only=True) def test_fs_validation(self): sos = butter(4, 0.2, output='sos') with pytest.raises(ValueError, match="Sampling.*single scalar"): @@ -1382,8 +1374,7 @@ def test_fs_param2(self, xp): xp_assert_close(h1, h2) xp_assert_close(w, w1, check_dtype=False) - @skip_xp_backends(np_only=True, reason="numpy scalars") - def test_w_or_N_types(self, xp): + def test_w_or_N_types(self): # Measure at 8 equally-spaced points for N in (8, np.int8(8), np.int16(8), np.int32(8), np.int64(8), np.array(8)): @@ -1403,7 +1394,6 @@ def test_w_or_N_types(self, xp): assert_array_almost_equal(w_out, [8]) assert_array_almost_equal(h, [1]) - @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): freqz_zpk([1.0], [1.0], [1.0], fs=np.array([10., 20])) From 4a865002be303c2f9b53dd77b4b7126ba325b789 Mon Sep 17 00:00:00 2001 From: Christian Veenhuis <124370897+ChVeen@users.noreply.github.com> Date: Mon, 5 May 2025 21:26:57 +0200 Subject: [PATCH 087/251] fix error message in scipy.optimize.zeros --- scipy/optimize/zeros.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/zeros.c b/scipy/optimize/zeros.c index d8393519c4a0..fb35a8f88778 100644 --- a/scipy/optimize/zeros.c +++ b/scipy/optimize/zeros.c @@ -96,7 +96,7 @@ call_solver(solver_type solver, PyObject *self, PyObject *args) return NULL; } if (iter < 0) { - PyErr_SetString(PyExc_ValueError, "maxiter should be > 0"); + PyErr_SetString(PyExc_ValueError, "iter must be >= 0"); return NULL; } From 45ff8a2ec333de8ae1c3bdb0780b0f85bbf599bb Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Mon, 5 May 2025 14:52:12 -0600 Subject: [PATCH 088/251] BUG: spatial.HalfspaceIntersection: raise on non-feasible half space (#20035) * Fixes #19865 * Rather than returning a problematic half space intersection polytope, we raise an error for incremental processing when a new half space is added for which the interior point is no longer clearly interior --------- Co-authored-by: Richard Strong Bowen --- scipy/spatial/_qhull.pyx | 16 ++++++++ scipy/spatial/tests/test_qhull.py | 66 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index 8765b8db5755..61a293f5239e 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -2935,6 +2935,22 @@ class HalfspaceIntersection(_QhullUser): of halfspaces is also not possible after `close` has been called. """ + if halfspaces.ndim > 2: + raise ValueError("`halfspaces` should be provided as a 2D array") + # We check for non-feasibility of incremental additions + # in a manner similar to `qh_sethalfspace` + halfspaces = np.atleast_2d(halfspaces) + dists = np.dot(halfspaces[:, :self.ndim], self.interior_point) + halfspaces[:, -1] + # HalfspaceIntersection uses closed half spaces so + # the feasible point also cannot be directly on the boundary + viols = dists >= 0 + if viols.any(): + # error out with an indication of the first violating + # half space discovered + first_viol = np.nonzero(viols)[0].min() + bad_hs = halfspaces[first_viol, :] + msg = f"feasible point is not clearly inside halfspace: {bad_hs}" + raise QhullError(msg) self._add_points(halfspaces, restart, self.interior_point) @property diff --git a/scipy/spatial/tests/test_qhull.py b/scipy/spatial/tests/test_qhull.py index 3ff733a7a6af..c22b91e7f65f 100644 --- a/scipy/spatial/tests/test_qhull.py +++ b/scipy/spatial/tests/test_qhull.py @@ -1220,6 +1220,72 @@ def test_halfspace_batch(self, k): ind2 = np.lexsort((expected_intersections[:, 1], expected_intersections[:, 0])) assert_allclose(actual_intersections[ind1], expected_intersections[ind2]) + + @pytest.mark.parametrize("halfspaces", [ + (np.array([-0.70613882, -0.45589431, 0.04178256])), + (np.array([[-0.70613882, -0.45589431, 0.04178256], + [0.70807342, -0.45464871, -0.45969769], + [0., 0.76515026, -0.35614825]])), + ]) + def test_gh_19865(self, halfspaces): + # starting off with a feasible interior point and + # adding halfspaces for which it is no longer feasible + # should result in an error rather than a problematic + # intersection polytope + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*-0.706.*"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_gh_19865_3d(self): + # 3d case where closed half space is enforced for + # feasibility + halfspaces = np.array([[1, 1, 1, -1], # doesn't exclude origin + [-1, -1, -1, -1], # doesn't exclude origin + [1, 0, 0, 0]]) # the origin is on the line + initial_cube = np.array([[1, 0, 0, -1], + [-1, 0, 0, -1], + [0, 1, 0, -1], + [0, -1, 0, -1], + [0, 0, 1, -1], + [0, 0, -1, -1]]) + incremental_intersector = qhull.HalfspaceIntersection(initial_cube, + np.zeros(3), + incremental=True) + with pytest.raises(qhull.QhullError, match="feasible.*[1 0 0 0]"): + incremental_intersector.add_halfspaces(halfspaces) + + + def test_2d_add_halfspace_input(self): + # incrementally added halfspaces should respect the 2D + # array shape requirement + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + with pytest.raises(ValueError, match="2D array"): + incremental_intersector.add_halfspaces(np.ones((4, 4, 4))) + + def test_1d_add_halfspace_input(self): + # we do allow 1D `halfspaces` input to add_halfspaces() + initial_square = np.array( + [[1, 0, -1], [0, 1, -1], [-1, 0, -1], [0, -1, -1]] + ) + incremental_intersector = qhull.HalfspaceIntersection(initial_square, + np.zeros(2), + incremental=True) + assert_allclose(incremental_intersector.dual_vertices, np.arange(4)) + incremental_intersector.add_halfspaces(np.array([2, 2, -1])) + assert_allclose(incremental_intersector.dual_vertices, np.arange(5)) + + @pytest.mark.parametrize("diagram_type", [Voronoi, qhull.Delaunay]) def test_gh_20623(diagram_type): rng = np.random.default_rng(123) From b320297c43bab6b69b2c0b74d69a151755823f39 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 16:23:19 +0200 Subject: [PATCH 089/251] TST: signal: convert transfter function / representation tests to Array API --- scipy/signal/tests/test_filter_design.py | 268 +++++++++++++---------- 1 file changed, 154 insertions(+), 114 deletions(-) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index e9476ac18826..a1591b7cdee8 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -13,10 +13,10 @@ from pytest import raises as assert_raises from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, - assert_array_almost_equal, xp_size, xp_default_dtype, + assert_array_almost_equal, xp_size, xp_default_dtype, is_numpy ) -from numpy import array, spacing, sin, pi, sort +from numpy import array, spacing, sin, pi from scipy.signal import (argrelextrema, BadCoefficients, bessel, besselap, bilinear, buttap, butter, buttord, cheb1ap, cheb1ord, cheb2ap, cheb2ord, cheby1, cheby2, ellip, ellipap, ellipord, @@ -29,6 +29,8 @@ from scipy.signal._filter_design import (_cplxreal, _cplxpair, _norm_factor, _bessel_poly, _bessel_zeros) from scipy.signal._filter_design import _logspace +from scipy.signal import _polyutils as _pu +from scipy.signal._polyutils import _sort_cmplx skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -176,27 +178,32 @@ def test_real_integer_input(self): class TestTf2zpk: - @pytest.mark.parametrize('dt', (np.float64, np.complex128)) - def test_simple(self, dt): - z_r = np.array([0.5, -0.5]) - p_r = np.array([1.j / np.sqrt(2), -1.j / np.sqrt(2)]) + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @pytest.mark.parametrize('dt', ('float64', 'complex128')) + def test_simple(self, dt, xp): + dtyp = getattr(xp, dt) + + z_r = xp.asarray([0.5, -0.5]) + p_r = xp.asarray([1.j / math.sqrt(2), -1.j / math.sqrt(2)]) # Sort the zeros/poles so that we don't fail the test if the order # changes - z_r.sort() - p_r.sort() - b = np.poly(z_r).astype(dt) - a = np.poly(p_r).astype(dt) + z_r = _sort_cmplx(z_r, xp=xp) + p_r = _sort_cmplx(p_r, xp=xp) + + b = xp.astype(_pu.poly(z_r, xp=xp), dtyp) + a = xp.astype(_pu.poly(p_r, xp=xp), dtyp) z, p, k = tf2zpk(b, a) - z.sort() + z = _sort_cmplx(z, xp=xp) # The real part of `p` is ~0.0, so sort by imaginary part - p = p[np.argsort(p.imag)] + p = p[xp.argsort(xp.imag(p))] assert_array_almost_equal(z, z_r) assert_array_almost_equal(p, p_r) - assert_array_almost_equal(k, 1.) - assert k.dtype == dt + assert math.isclose(xp.real(k), 1.) + assert k.dtype == dtyp + @skip_xp_backends(np_only=True) def test_bad_filter(self): # Regression test for #651: better handling of badly conditioned # filter coefficients. @@ -207,47 +214,52 @@ def test_bad_filter(self): class TestZpk2Tf: - def test_identity(self): + def test_identity(self, xp): """Test the identity transfer function.""" - z = [] - p = [] + z = xp.asarray([]) + p = xp.asarray([]) k = 1. b, a = zpk2tf(z, p, k) - b_r = np.array([1.]) # desired result - a_r = np.array([1.]) # desired result + b_r = xp.asarray([1.]) # desired result + a_r = xp.asarray([1.]) # desired result # The test for the *type* of the return values is a regression # test for ticket #1095. In the case p=[], zpk2tf used to # return the scalar 1.0 instead of array([1.0]). xp_assert_equal(b, b_r) - assert isinstance(b, np.ndarray) xp_assert_equal(a, a_r) - assert isinstance(a, np.ndarray) + if is_numpy(xp): + assert isinstance(b, np.ndarray) + assert isinstance(a, np.ndarray) - def test_conj_pair(self): + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_conj_pair(self, xp): # conjugate pairs give real-coeff num & den - z = np.array([1j, -1j, 2j, -2j]) + z = xp.asarray([1j, -1j, 2j, -2j]) # shouldn't need elements of pairs to be adjacent - p = np.array([1+1j, 3-100j, 3+100j, 1-1j]) + p = xp.asarray([1+1j, 3-100j, 3+100j, 1-1j]) k = 23 # np.poly should do the right thing, but be explicit about # taking real part - b = k * np.poly(z).real - a = np.poly(p).real + z_np, p_np = map(np.asarray, (z, p)) + b_np = k * np.poly(z_np).real + a_np = np.poly(p_np).real + b, a = map(xp.asarray, (b_np, a_np)) bp, ap = zpk2tf(z, p, k) xp_assert_close(b, bp) xp_assert_close(a, ap) - assert np.isrealobj(bp) - assert np.isrealobj(ap) + assert xp.isdtype(bp.dtype, 'real floating') + assert xp.isdtype(ap.dtype, 'real floating') - def test_complexk(self): + @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + def test_complexk(self, xp): # regression: z, p real, k complex k gave real b, a - b, a = np.array([1j, 1j]), np.array([1.0, 2]) + b, a = xp.asarray([1j, 1j]), xp.asarray([1.0, 2]) z, p, k = tf2zpk(b, a) - xp_assert_close(k, 1j) + xp_assert_close(k, xp.asarray(1j), check_0d=False) bp, ap = zpk2tf(z, p, k) xp_assert_close(b, bp) xp_assert_close(a, ap) @@ -255,28 +267,33 @@ def test_complexk(self): class TestSos2Zpk: - def test_basic(self): + @skip_xp_backends("dask.array", reason="it https://github.com/dask/dask/issues/11883") + def test_basic(self, xp): sos = [[1, 0, 1, 1, 0, -0.81], [1, 0, 0, 1, 0, +0.49]] + sos = xp.asarray(sos) z, p, k = sos2zpk(sos) - z2 = [1j, -1j, 0, 0] - p2 = [0.9, -0.9, 0.7j, -0.7j] - k2 = 1 - assert_array_almost_equal(sort(z), sort(z2), decimal=4) - assert_array_almost_equal(sort(p), sort(p2), decimal=4) - assert_array_almost_equal(k, k2) + z2 = xp.asarray([1j, -1j, 0, 0]) + p2 = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) + k2 = 1. + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) + assert math.isclose(k, k2) sos = [[1.00000, +0.61803, 1.0000, 1.00000, +0.60515, 0.95873], [1.00000, -1.61803, 1.0000, 1.00000, -1.58430, 0.95873], [1.00000, +1.00000, 0.0000, 1.00000, +0.97915, 0.00000]] + sos = xp.asarray(sos) z, p, k = sos2zpk(sos) z2 = [-0.3090 + 0.9511j, -0.3090 - 0.9511j, 0.8090 + 0.5878j, 0.8090 - 0.5878j, -1.0000 + 0.0000j, 0] p2 = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, 0.7922 - 0.5755j, -0.9791 + 0.0000j, 0] + z2 = xp.asarray(z2) + p2 = xp.asarray(p2) k2 = 1 - assert_array_almost_equal(sort(z), sort(z2), decimal=4) - assert_array_almost_equal(sort(p), sort(p2), decimal=4) + assert_array_almost_equal(_sort_cmplx(z, xp), _sort_cmplx(z2, xp), decimal=4) + assert_array_almost_equal(_sort_cmplx(p, xp), _sort_cmplx(p2, xp), decimal=4) sos = array([[1, 2, 3, 1, 0.2, 0.3], [4, 5, 6, 1, 0.4, 0.5]]) @@ -284,138 +301,157 @@ def test_basic(self): -0.625 - 1.05326872164704j, -0.625 + 1.05326872164704j]) p = array([-0.2 - 0.678232998312527j, -0.2 + 0.678232998312527j, -0.1 - 0.538516480713450j, -0.1 + 0.538516480713450j]) + sos, z, p = map(xp.asarray, (sos, z, p)) k = 4 z2, p2, k2 = sos2zpk(sos) - xp_assert_close(_cplxpair(z2), z) - xp_assert_close(_cplxpair(p2), p) + + xp_assert_close(_sort_cmplx(z2, xp=xp), _sort_cmplx(z, xp=xp)) + xp_assert_close(_sort_cmplx(p2, xp=xp), _sort_cmplx(p, xp=xp)) assert k2 == k @pytest.mark.thread_unsafe - def test_fewer_zeros(self): + def test_fewer_zeros(self, xp): """Test not the expected number of p/z (effectively at origin).""" sos = butter(3, 0.1, output='sos') + sos = xp.asarray(sos) # XXX convert butter z, p, k = sos2zpk(sos) - assert len(z) == 4 - assert len(p) == 4 + assert z.shape[0] == 4 + assert p.shape[0] == 4 sos = butter(12, [5., 30.], 'bandpass', fs=1200., analog=False, output='sos') + xp = xp.asarray(sos) with pytest.warns(BadCoefficients, match='Badly conditioned'): z, p, k = sos2zpk(sos) - assert len(z) == 24 - assert len(p) == 24 + assert z.shape[0] == 24 + assert p.shape[0] == 24 class TestSos2Tf: - def test_basic(self): - sos = [[1, 1, 1, 1, 0, -1], + def test_basic(self, xp): + sos = [[1.0, 1, 1, 1, 0, -1], [-2, 3, 1, 1, 10, 1]] + sos = xp.asarray(sos) b, a = sos2tf(sos) - assert_array_almost_equal(b, [-2, 1, 2, 4, 1]) - assert_array_almost_equal(a, [1, 10, 0, -10, -1]) + assert_array_almost_equal(b, xp.asarray([-2.0, 1, 2, 4, 1])) + assert_array_almost_equal(a, xp.asarray([1.0, 10, 0, -10, -1])) class TestTf2Sos: - def test_basic(self): - num = [2, 16, 44, 56, 32] - den = [3, 3, -15, 18, -12] + def test_basic(self, xp): + num = xp.asarray([2., 16, 44, 56, 32]) + den = xp.asarray([3., 3, -15, 18, -12]) sos = tf2sos(num, den) sos2 = [[0.6667, 4.0000, 5.3333, 1.0000, +2.0000, -4.0000], [1.0000, 2.0000, 2.0000, 1.0000, -1.0000, +1.0000]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) - b = [1, -3, 11, -27, 18] - a = [16, 12, 2, -4, -1] + b = xp.asarray([1.0, -3, 11, -27, 18]) + a = xp.asarray([16.0, 12, 2, -4, -1]) sos = tf2sos(b, a) sos2 = [[0.0625, -0.1875, 0.1250, 1.0000, -0.2500, -0.1250], [1.0000, +0.0000, 9.0000, 1.0000, +1.0000, +0.5000]] - # assert_array_almost_equal(sos, sos2, decimal=4) + sos2 = xp.asarray(sos2) + #assert_array_almost_equal(sos, sos2, decimal=4) @pytest.mark.parametrize('b, a, analog, sos', - [([1], [1], False, [[1., 0., 0., 1., 0., 0.]]), - ([1], [1], True, [[0., 0., 1., 0., 0., 1.]]), - ([1], [1., 0., -1.01, 0, 0.01], False, + [([1.0], [1.0], False, [[1., 0., 0., 1., 0., 0.]]), + ([1.0], [1.0], True, [[0., 0., 1., 0., 0., 1.]]), + ([1.0], [1., 0., -1.01, 0, 0.01], False, [[1., 0., 0., 1., 0., -0.01], [1., 0., 0., 1., 0., -1]]), - ([1], [1., 0., -1.01, 0, 0.01], True, + ([1.0], [1., 0., -1.01, 0, 0.01], True, [[0., 0., 1., 1., 0., -1], [0., 0., 1., 1., 0., -0.01]])]) - def test_analog(self, b, a, analog, sos): + def test_analog(self, b, a, analog, sos, xp): + b, a, sos = map(xp.asarray, (b, a, sos)) sos2 = tf2sos(b, a, analog=analog) assert_array_almost_equal(sos, sos2, decimal=4) class TestZpk2Sos: - @pytest.mark.parametrize('dt', 'fdgFDG') +# @pytest.mark.parametrize('dt', 'fdgFDG') + # XXX: quietly remove float128 and complex256 + @pytest.mark.parametrize('dt', ['float32', 'float64', 'complex64', 'complex128']) @pytest.mark.parametrize('pairing, analog', [('nearest', False), ('keep_odd', False), ('minimal', False), ('minimal', True)]) - def test_dtypes(self, dt, pairing, analog): - z = np.array([-1, -1]).astype(dt) - ct = dt.upper() # the poles have to be complex - p = np.array([0.57149 + 0.29360j, 0.57149 - 0.29360j]).astype(ct) - k = np.array(1).astype(dt) + def test_dtypes(self, dt, pairing, analog, xp): + dtype = getattr(xp, dt) + # the poles have to be complex + cdtype = (xp.empty(1, dtype=dtype) + 1j*xp.empty(1, dtype=dtype)).dtype + + z = xp.asarray([-1, -1], dtype=dtype) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j], dtype=cdtype) + k = xp.asarray(1, dtype=dtype) sos = zpk2sos(z, p, k, pairing=pairing, analog=analog) - sos2 = [[1, 2, 1, 1, -1.14298, 0.41280]] # octave & MATLAB + # octave & MATLAB + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]], dtype=dtype) assert_array_almost_equal(sos, sos2, decimal=4) - def test_basic(self): + def test_basic(self, xp): for pairing in ('nearest', 'keep_odd'): # # Cases that match octave # - z = [-1, -1] - p = [0.57149 + 0.29360j, 0.57149 - 0.29360j] + z = xp.asarray([-1.0, -1.0]) + p = xp.asarray([0.57149 + 0.29360j, 0.57149 - 0.29360j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) - sos2 = [[1, 2, 1, 1, -1.14298, 0.41280]] # octave & MATLAB + sos2 = xp.asarray([[1, 2, 1, 1, -1.14298, 0.41280]]) # octave & MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [1j, -1j] - p = [0.9, -0.9, 0.7j, -0.7j] + z = xp.asarray([1j, -1j]) + p = xp.asarray([0.9, -0.9, 0.7j, -0.7j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1, 0, 1, 1, 0, +0.49], [1, 0, 0, 1, 0, -0.81]] # octave + sos2 = xp.asarray(sos2) # sos2 = [[0, 0, 1, 1, -0.9, 0], # [1, 0, 1, 1, 0.9, 0]] # MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [] - p = [0.8, -0.5+0.25j, -0.5-0.25j] + z = xp.asarray([]) + p = xp.asarray([0.8, -0.5+0.25j, -0.5-0.25j]) k = 1. sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1., 0., 0., 1., 1., 0.3125], [1., 0., 0., 1., -0.8, 0.]] # octave, MATLAB fails + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) - z = [1., 1., 0.9j, -0.9j] - p = [0.99+0.01j, 0.99-0.01j, 0.1+0.9j, 0.1-0.9j] + z = xp.asarray([1., 1., 0.9j, -0.9j]) + p = xp.asarray([0.99+0.01j, 0.99-0.01j, 0.1+0.9j, 0.1-0.9j]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1, 0, 0.81, 1, -0.2, 0.82], [1, -2, 1, 1, -1.98, 0.9802]] # octave + sos2 = xp.asarray(sos2) # sos2 = [[1, -2, 1, 1, -0.2, 0.82], # [1, 0, 0.81, 1, -1.98, 0.9802]] # MATLAB assert_array_almost_equal(sos, sos2, decimal=4) - z = [0.9+0.1j, 0.9-0.1j, -0.9] - p = [0.75+0.25j, 0.75-0.25j, 0.9] + z = xp.asarray([0.9+0.1j, 0.9-0.1j, -0.9]) + p = xp.asarray([0.75+0.25j, 0.75-0.25j, 0.9]) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) if pairing == 'keep_odd': sos2 = [[1, -1.8, 0.82, 1, -1.5, 0.625], [1, 0.9, 0, 1, -0.9, 0]] # octave; MATLAB fails + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) else: # pairing == 'nearest' sos2 = [[1, 0.9, 0, 1, -1.5, 0.625], [1, -1.8, 0.82, 1, -0.9, 0]] # our algorithm + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # @@ -426,6 +462,8 @@ def test_basic(self): +0.8090 - 0.5878j, -1.0000 + 0.0000j] p = [-0.3026 + 0.9312j, -0.3026 - 0.9312j, 0.7922 + 0.5755j, +0.7922 - 0.5755j, -0.9791 + 0.0000j] + z = xp.asarray(z) + p = xp.asarray(p) k = 1 sos = zpk2sos(z, p, k, pairing=pairing) # sos2 = [[1, 0.618, 1, 1, 0.6052, 0.95870], @@ -434,26 +472,31 @@ def test_basic(self): sos2 = [[1, 1, 0, 1, +0.97915, 0], [1, 0.61803, 1, 1, +0.60515, 0.95873], [1, -1.61803, 1, 1, -1.58430, 0.95873]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) z = [-1 - 1.4142j, -1 + 1.4142j, -0.625 - 1.0533j, -0.625 + 1.0533j] p = [-0.2 - 0.6782j, -0.2 + 0.6782j, -0.1 - 0.5385j, -0.1 + 0.5385j] + z = xp.asarray(z) + p = xp.asarray(p) k = 4 sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[4, 8, 12, 1, 0.2, 0.3], [1, 1.25, 1.5, 1, 0.4, 0.5]] # MATLAB + sos2 = xp.asarray(sos2, dtype=xp.float64) # sos2 = [[4, 8, 12, 1, 0.4, 0.5], # [1, 1.25, 1.5, 1, 0.2, 0.3]] # octave xp_assert_close(sos, sos2, rtol=1e-4, atol=1e-4) - z = [] - p = [0.2, -0.5+0.25j, -0.5-0.25j] + z = xp.asarray([]) + p = xp.asarray([0.2, -0.5+0.25j, -0.5-0.25j]) k = 1. sos = zpk2sos(z, p, k, pairing=pairing) sos2 = [[1., 0., 0., 1., -0.2, 0.], [1., 0., 0., 1., 1., 0.3125]] + sos2 = xp.asarray(sos2) # sos2 = [[1., 0., 0., 1., 1., 0.3125], # [1., 0., 0., 1., -0.2, 0]] # octave, MATLAB fails assert_array_almost_equal(sos, sos2, decimal=4) @@ -462,17 +505,19 @@ def test_basic(self): # "Digital Filters and Signal Processing (1995) p.400: # http://books.google.com/books?id=VZ8uabI1pNMC&lpg=PA400&ots=gRD9pi8Jua&dq=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&pg=PA400#v=onepage&q=Pole%2Fzero%20pairing%20for%20minimum%20roundoff%20noise%20in%20BSF.&f=false - deg2rad = np.pi / 180. + deg2rad = xp.pi / 180. k = 1. # first example - thetas = [22.5, 45, 77.5] - mags = [0.8, 0.6, 0.9] - z = np.array([np.exp(theta * deg2rad * 1j) for theta in thetas]) - z = np.concatenate((z, np.conj(z))) - p = np.array([mag * np.exp(theta * deg2rad * 1j) - for theta, mag in zip(thetas, mags)]) - p = np.concatenate((p, np.conj(p))) + thetas = xp.asarray([22.5, 45, 77.5]) + mags = xp.asarray([0.8, 0.6, 0.9]) + # z = xp.asarray([xp.exp(theta * deg2rad * 1j) for theta in thetas]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z))) +# p = xp.asarray([mag * np.exp(theta * deg2rad * 1j) +# for theta, mag in zip(thetas, mags)]) + p = xp.exp(1j * deg2rad * thetas) * mags + p = xp.concat((p, xp.conj(p))) sos = zpk2sos(z, p, k) # sos2 = [[1, -0.43288, 1, 1, -0.38959, 0.81], # octave, # [1, -1.41421, 1, 1, -0.84853, 0.36], # MATLAB fails @@ -481,12 +526,15 @@ def test_basic(self): sos2 = [[1, -1.41421, 1, 1, -0.84853, 0.36], [1, -1.84776, 1, 1, -1.47821, 0.64], [1, -0.43288, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # second example - z = np.array([np.exp(theta * deg2rad * 1j) - for theta in (85., 10.)]) - z = np.concatenate((z, np.conj(z), [1, -1])) +# z = xp.asarray([xp.exp(theta * deg2rad * 1j) +# for theta in (85., 10.)]) + thetas = xp.asarray([85., 10.]) + z = xp.exp(1j * deg2rad * thetas) + z = xp.concat((z, xp.conj(z), xp.asarray([1.0, -1.0]))) sos = zpk2sos(z, p, k) # sos2 = [[1, -0.17431, 1, 1, -0.38959, 0.81], # octave "wrong", @@ -496,6 +544,7 @@ def test_basic(self): sos2 = [[1, 0, -1, 1, -0.84853, 0.36], [1, -1.96962, 1, 1, -1.47821, 0.64], [1, -0.17431, 1, 1, -0.38959, 0.81]] + sos2 = xp.asarray(sos2) assert_array_almost_equal(sos, sos2, decimal=4) # these examples are taken from the doc string, and show the @@ -510,9 +559,10 @@ def test_basic(self): ('minimal', np.array([[0., 1., 1., 0., 1., -0.75], [1., 1., 0.5, 1., -1.6, 0.65]]))]) - def test_pairing(self, pairing, sos): - z1 = np.array([-1, -0.5-0.5j, -0.5+0.5j]) - p1 = np.array([0.75, 0.8+0.1j, 0.8-0.1j]) + def test_pairing(self, pairing, sos, xp): + sos = xp.asarray(sos) + z1 = xp.asarray([-1, -0.5-0.5j, -0.5+0.5j]) + p1 = xp.asarray([0.75, 0.8+0.1j, 0.8-0.1j]) sos2 = zpk2sos(z1, p1, 1, pairing=pairing) assert_array_almost_equal(sos, sos2, decimal=4) @@ -523,15 +573,18 @@ def test_pairing(self, pairing, sos): ([-0.7071+0.7071j, -0.7071-0.7071j, -0.1j, 0.1j], [[0., 0., 1., 1., 0., 0.01], [0., 0., 1., 1., 1.4142, 1.]])]) - def test_analog(self, p, sos_dt): + def test_analog(self, p, sos_dt, xp): # test `analog` argument # for discrete time, poles closest to unit circle should appear last # for cont. time, poles closest to imaginary axis should appear last - sos2_dt = zpk2sos([], p, 1, pairing='minimal', analog=False) - sos2_ct = zpk2sos([], p, 1, pairing='minimal', analog=True) + z, p = xp.asarray([]), xp.asarray(p) + sos_dt = xp.asarray(sos_dt) + sos2_dt = zpk2sos(z, p, 1, pairing='minimal', analog=False) + sos2_ct = zpk2sos(z, p, 1, pairing='minimal', analog=True) assert_array_almost_equal(sos_dt, sos2_dt, decimal=4) - assert_array_almost_equal(sos_dt[::-1], sos2_ct, decimal=4) + assert_array_almost_equal(xp.flip(sos_dt, axis=0), sos2_ct, decimal=4) + @skip_xp_backends(np_only=True) def test_bad_args(self): with pytest.raises(ValueError, match=r'pairing must be one of'): zpk2sos([1], [2], 1, pairing='no_such_pairing') @@ -1602,19 +1655,6 @@ def test_fs_validation(self): bilinear(b, a, fs=None) -def _sort_cmplx(arr, xp): - # xp.sort is undefined for complex dtypes. Here we only need some - # consistent way to sort a complex array, including equal magnitude elements. - arr = xp.asarray(arr) - if xp.isdtype(arr.dtype, 'complex floating'): - sorter = abs(arr) + xp.real(arr) + xp.imag(arr)**3 - else: - sorter = arr - - idxs = xp.argsort(sorter) - return arr[idxs] - - class TestLp2lp_zpk: @xfail_xp_backends( From 616fd379e4f0ef283084cd51f4fb14bed230fc69 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 16:23:46 +0200 Subject: [PATCH 090/251] MAINT: signal: add more np.polynomial routines --- scipy/signal/_polyutils.py | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py index 757c62ab2ca1..6090563d9bae 100644 --- a/scipy/signal/_polyutils.py +++ b/scipy/signal/_polyutils.py @@ -8,6 +8,19 @@ import scipy._lib.array_api_extra as xpx +def _sort_cmplx(arr, xp): + # xp.sort is undefined for complex dtypes. Here we only need some + # consistent way to sort a complex array, including equal magnitude elements. + arr = xp.asarray(arr) + if xp.isdtype(arr.dtype, 'complex floating'): + sorter = abs(arr) + xp.real(arr) + xp.imag(arr)**3 + else: + sorter = arr + + idxs = xp.argsort(sorter) + return arr[idxs] + + def polyroots(coef, *, xp): """numpy.roots, best-effor replacement """ @@ -33,9 +46,42 @@ def polyroots(coef, *, xp): return xp.asarray(np.linalg.eigvals(np.asarray(a))) +# https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_function_base_impl.py#L1874-L1925 +def _trim_zeros(filt, trim='fb'): + first = 0 + trim = trim.upper() + if 'F' in trim: + for i in filt: + if i != 0.: + break + else: + first = first + 1 + last = filt.shape[0] + if 'B' in trim: + for i in filt[::-1]: + if i != 0.: + break + else: + last = last - 1 + return filt[first:last] + + # ### Old-style routines ### +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L1232 +def _poly1d(c_or_r, *, xp): + """ Constructor of np.poly1d object from an array of coefficients (r=False) + """ + c_or_r = xpx.atleast_nd(c_or_r, ndim=1, xp=xp) + if c_or_r.ndim > 1: + raise ValueError("Polynomial must be 1d only.") + c_or_r = _trim_zeros(c_or_r, trim='f') + if c_or_r.shape[0] == 0: + c_or_r = xp.asarray([0], dtype=c_or_r.dtype) + return c_or_r + + # https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L702-L779 def polyval(p, x, *, xp): """ Old-style polynomial, `np.polyval` @@ -47,6 +93,48 @@ def polyval(p, x, *, xp): return y +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L34-L157 +def poly(seq_of_zeros, *, xp): + # Only reproduce the 1D variant of np.poly + seq_of_zeros = xp.asarray(seq_of_zeros) + seq_of_zeros = xpx.atleast_nd(seq_of_zeros, ndim=1, xp=xp) + + if seq_of_zeros.shape[0] == 0: + return 1.0 + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + dt = seq_of_zeros.dtype + a = xp.ones((1,), dtype=dt) + one = xp.ones_like(seq_of_zeros[0]) + for zero in seq_of_zeros: + a = convolve_func(a, xp.stack((one, -zero)), mode='full') + + if xp.isdtype(a.dtype, 'complex floating'): + # if complex roots are all complex conjugates, the roots are real. + roots = xp.asarray(seq_of_zeros, dtype=xp.complex128) + if xp.all(_sort_cmplx(roots, xp) == _sort_cmplx(xp.conj(roots), xp)): + a = xp.asarray(xp.real(a), copy=True) + + return a + + +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/lib/_polynomial_impl.py#L912 +def polymul(a1, a2, *, xp): + a1, a2 = _poly1d(a1, xp=xp), _poly1d(a2, xp=xp) + + # prefer np.convolve etc, if available + convolve_func = getattr(xp, 'convolve', None) + if convolve_func is None: + from scipy.signal import convolve as convolve_func + + val = convolve_func(a1, a2) + return val + + # ### New-style routines ### From 64e6ba7624455a10d53d88699e29cc3ba56cb54b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 16:24:57 +0200 Subject: [PATCH 091/251] ENH: signal: array API for tf2zpk et al --- scipy/signal/_filter_design.py | 116 ++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 34c1a8bd2eb2..ad5ae21d21ff 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -4,7 +4,7 @@ import warnings import numpy as np -from numpy import (atleast_1d, poly, roots, asarray, +from numpy import (atleast_1d, asarray, pi, absolute, sqrt, tan, log10, arcsinh, sin, exp, cosh, arccosh, ceil, conjugate, sinh, concatenate, prod, array) @@ -1214,13 +1214,21 @@ def tf2zpk(b, a): array([ -2.5+2.59807621j , -2.5-2.59807621j]), 3.0) """ + xp = array_namespace(b, a) b, a = normalize(b, a) - b = (b + 0.0) / a[0] - a = (a + 0.0) / a[0] + + if xp.isdtype(b.dtype, 'integral'): + b = xp.astype(b, xp.float64) + if xp.isdtype(a.dtype, 'integral'): + a = xp.astype(a, xp.float64) + + b = b / a[0] + a = a / a[0] + k = b[0] - b /= b[0] - z = roots(b) - p = roots(a) + b = b / b[0] + z = _pu.polyroots(b, xp=xp) + p = _pu.polyroots(a, xp=xp) return z, p, k @@ -1262,18 +1270,26 @@ def zpk2tf(z, p, k): >>> zpk2tf(z, p, k) ( array([ 5., -40., 60.]), array([ 1., -9., 8.])) """ - z = atleast_1d(z) - k = atleast_1d(k) - if len(z.shape) > 1: - temp = poly(z[0]) - b = np.empty((z.shape[0], z.shape[1] + 1), temp.dtype.char) - if len(k) == 1: + xp = array_namespace(z, p) + z, p, k = map(xp.asarray, (z, p, k)) + + z = xpx.atleast_nd(z, ndim=1, xp=xp) + k = xpx.atleast_nd(k, ndim=1, xp=xp) + if xp.isdtype(k.dtype, 'integral'): + k = xp.astype(k, xp_default_dtype(xp)) + + if z.ndim > 1: + temp = _pu.poly(z[0], xp=xp) + b = xp.empty((z.shape[0], z.shape[1] + 1), dtype=temp.dtype) + if k.shape[0] == 1: k = [k[0]] * z.shape[0] for i in range(z.shape[0]): - b[i] = k[i] * poly(z[i]) + b[i] = k[i] * _pu.poly(z[i], xp=xp) else: - b = k * poly(z) - a = atleast_1d(poly(p)) + b = k * _pu.poly(z, xp=xp) + + a = _pu.poly(p, xp=xp) + a = xpx.atleast_nd(xp.asarray(a), ndim=1, xp=xp) return b, a @@ -1369,17 +1385,20 @@ def sos2tf(sos): ( array([0.91256522, 0.91256522, 0. ]), array([1. , 0.82513043, 0. ])) """ - sos = np.asarray(sos) + xp = array_namespace(sos) + sos = xp.asarray(sos) + result_type = sos.dtype - if result_type.kind in 'bui': - result_type = np.float64 + if xp.isdtype(result_type, 'integral'): + result_type = xp_default_dtype(xp) + + b = xp.asarray([1], dtype=result_type) + a = xp.asarray([1], dtype=result_type) - b = np.array([1], dtype=result_type) - a = np.array([1], dtype=result_type) n_sections = sos.shape[0] for section in range(n_sections): - b = np.polymul(b, sos[section, :3]) - a = np.polymul(a, sos[section, 3:]) + b = _pu.polymul(b, sos[section, :3], xp=xp) + a = _pu.polymul(a, sos[section, 3:], xp=xp) return b, a @@ -1410,15 +1429,17 @@ def sos2zpk(sos): .. versionadded:: 0.16.0 """ - sos = np.asarray(sos) + xp = array_namespace(sos) + sos = xp.asarray(sos) + n_sections = sos.shape[0] - z = np.zeros(n_sections*2, np.complex128) - p = np.zeros(n_sections*2, np.complex128) + z = xp.zeros(n_sections*2, dtype=xp.complex128) + p = xp.zeros(n_sections*2, dtype=xp.complex128) k = 1. for section in range(n_sections): zpk = tf2zpk(sos[section, :3], sos[section, 3:]) - z[2*section:2*section+len(zpk[0])] = zpk[0] - p[2*section:2*section+len(zpk[1])] = zpk[1] + z[2*section:2*section + zpk[0].shape[0]] = zpk[0] + p[2*section:2*section + zpk[1].shape[0]] = zpk[1] k *= zpk[2] return z, p, k @@ -1633,6 +1654,19 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): # 4. Further optimizations of the section ordering / pole-zero pairing. # See the wiki for other potential issues. + xp = array_namespace(z, p) + + # convert to numpy, convert back on exit XXX + z, p = map(np.asarray, (z, p)) + + k = xp.asarray(k) + if xp.isdtype(k.dtype, 'complex floating'): + if xp.imag(k) != 0: + raise ValueError('k must be real') + k = float(xp.real(k)) + else: + k = float(k) + if pairing is None: pairing = 'minimal' if analog else 'nearest' @@ -1646,9 +1680,9 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): if len(z) == len(p) == 0: if not analog: - return np.array([[k, 0., 0., 1., 0., 0.]]) + return xp.asarray([[k, 0., 0., 1., 0., 0.]]) else: - return np.array([[0., 0., k, 0., 0., 1.]]) + return xp.asarray([[0., 0., k, 0., 0., 1.]]) if pairing != 'minimal': # ensure we have the same number of poles and zeros, and make copies @@ -1759,7 +1793,7 @@ def idx_worst(p): # put gain in first sos sos[0][:3] *= k - return sos + return xp.asarray(sos) def _align_nums(nums, xp): @@ -1808,26 +1842,6 @@ def _align_nums(nums, xp): return aligned_nums -def _trim_zeros(filt, trim='fb'): - # https://github.com/numpy/numpy/blob/v2.1.0/numpy/lib/_function_base_impl.py#L1874-L1925 - first = 0 - trim = trim.upper() - if 'F' in trim: - for i in filt: - if i != 0.: - break - else: - first = first + 1 - last = filt.shape[0] - if 'B' in trim: - for i in filt[::-1]: - if i != 0.: - break - else: - last = last - 1 - return filt[first:last] - - def normalize(b, a): """Normalize numerator/denominator of a continuous-time transfer function. @@ -1905,7 +1919,7 @@ def normalize(b, a): raise ValueError("Denominator must have at least on nonzero element.") # Trim leading zeros in denominator, leave at least one. - den = _trim_zeros(den, 'f') + den = _pu._trim_zeros(den, 'f') # Normalize transfer function num, den = num / den[0], den / den[0] From 9cc930692e5739996fe90e416ef776b834d9f79c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 18:36:16 +0000 Subject: [PATCH 092/251] MAINT: signal: use appropriate hierogliphs for jax inplace ops --- scipy/signal/_filter_design.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index ad5ae21d21ff..ee98f11a7d09 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -1438,8 +1438,8 @@ def sos2zpk(sos): k = 1. for section in range(n_sections): zpk = tf2zpk(sos[section, :3], sos[section, 3:]) - z[2*section:2*section + zpk[0].shape[0]] = zpk[0] - p[2*section:2*section + zpk[1].shape[0]] = zpk[1] + z = xpx.at(z, slice(2*section, 2*section + zpk[0].shape[0])).set(zpk[0]) + p = xpx.at(p, slice(2*section, 2*section + zpk[1].shape[0])).set(zpk[1]) k *= zpk[2] return z, p, k From 761aefd0d4a61bcb7c7d0cdc81ae5170d05da4ab Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 19:43:19 +0000 Subject: [PATCH 093/251] TST: signal: add skips for cpu-only tests of LTI convertsions --- scipy/signal/tests/test_filter_design.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index a1591b7cdee8..8d0409b57933 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -178,6 +178,9 @@ def test_real_integer_input(self): class TestTf2zpk: + @skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] + ) @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") @pytest.mark.parametrize('dt', ('float64', 'complex128')) def test_simple(self, dt, xp): @@ -232,6 +235,7 @@ def test_identity(self, xp): assert isinstance(a, np.ndarray) @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @skip_xp_backends(cpu_only=True, reason="XXX zpk2sos is numpy-only") def test_conj_pair(self, xp): # conjugate pairs give real-coeff num & den z = xp.asarray([1j, -1j, 2j, -2j]) @@ -255,6 +259,9 @@ def test_conj_pair(self, xp): assert xp.isdtype(ap.dtype, 'real floating') @skip_xp_backends("dask.array", reason="https://github.com/dask/dask/issues/11883") + @skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] + ) def test_complexk(self, xp): # regression: z, p real, k complex k gave real b, a b, a = xp.asarray([1j, 1j]), xp.asarray([1.0, 2]) @@ -327,6 +334,9 @@ def test_fewer_zeros(self, xp): assert p.shape[0] == 24 +@skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] +) class TestSos2Tf: def test_basic(self, xp): @@ -338,6 +348,7 @@ def test_basic(self, xp): assert_array_almost_equal(a, xp.asarray([1.0, 10, 0, -10, -1])) +@skip_xp_backends(cpu_only=True, reason="XXX zpk2sos is numpy-only") class TestTf2Sos: def test_basic(self, xp): @@ -372,6 +383,9 @@ def test_analog(self, b, a, analog, sos, xp): assert_array_almost_equal(sos, sos2, decimal=4) +@skip_xp_backends( + cpu_only=True, reason="XXX zpk2sos is numpy-only", exceptions=['cupy'] +) class TestZpk2Sos: # @pytest.mark.parametrize('dt', 'fdgFDG') From 30a32c1c7c988d82260d5e46f5fbb28394728f5c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sun, 27 Apr 2025 22:24:43 +0200 Subject: [PATCH 094/251] TST: signal: add skips for eig in jax on CUDA --- scipy/signal/tests/test_filter_design.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 8d0409b57933..3c0ea09abeac 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -272,6 +272,7 @@ def test_complexk(self, xp): xp_assert_close(a, ap) +@skip_xp_backends("jax.numpy", reason='no eig in JAX on GPU.') class TestSos2Zpk: @skip_xp_backends("dask.array", reason="it https://github.com/dask/dask/issues/11883") From 9d80169b2c86308b7914edadfd6caace311896e9 Mon Sep 17 00:00:00 2001 From: aiudirog Date: Mon, 5 May 2025 18:00:21 -0400 Subject: [PATCH 095/251] BUG: ndimage.median_filter: fix segfault when using `mode='mirror'` (#22608) * It appears there was an off-by-one error in the loop that finalizes the mirror mode. * Fix another memory issue in `_rank_filter()` based on feedback at https://github.com/scipy/scipy/pull/22608#discussion_r2067771870. This seems to allow many repeats of the property-based test to finally pass on this branch via i.e., `spin test -t scipy/ndimage/tests/test_filters.py::test_gh_22586_crash_property -- --count=50` --------- Co-authored-by: Tyler Reddy --- scipy/ndimage/src/_rank_filter_1d.cpp | 9 +++------ scipy/ndimage/tests/test_filters.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/scipy/ndimage/src/_rank_filter_1d.cpp b/scipy/ndimage/src/_rank_filter_1d.cpp index d1dc8a602008..ebd72441d769 100644 --- a/scipy/ndimage/src/_rank_filter_1d.cpp +++ b/scipy/ndimage/src/_rank_filter_1d.cpp @@ -155,13 +155,10 @@ void _rank_filter(T *in_arr, int rank, int arr_len, int win_len, T *out_arr, int mode, T cval, int origin) { int i, arr_len_thresh, lim = (win_len - 1) / 2 - origin; int lim2 = arr_len - lim; + if (lim2 < 0) return; int offset; Mediator *m = MediatorNew(win_len, rank); - T *data = new T[win_len]; - for (int i = 0; i < win_len; ++i) { - data[i] = 0; - } - + T *data = new T[win_len](); switch (mode) { case REFLECT: @@ -227,7 +224,7 @@ void _rank_filter(T *in_arr, int rank, int arr_len, int win_len, T *out_arr, break; case MIRROR: arr_len_thresh = arr_len - 2; - for (i = 0; i < lim + 1; i++) { + for (i = 0; i < lim; i++) { MediatorInsert(data, m, in_arr[arr_len_thresh - i]); out_arr[lim2 + i] = data[m->heap[0]]; } diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index f2542aebce9a..3e98639914dd 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -7,6 +7,9 @@ import numpy as np import pytest from numpy.testing import suppress_warnings, assert_allclose, assert_array_equal +from hypothesis import strategies as st +from hypothesis import given +import hypothesis.extra.numpy as npst from pytest import raises as assert_raises from scipy import ndimage from scipy._lib._array_api import ( @@ -2982,3 +2985,14 @@ def test_edge_cases(self, xp): res = ndimage.vectorized_filter(input, function, size=21) ref = ndimage.vectorized_filter(input, function, size=21) xp_assert_close(res, ref) + + +@given(x=npst.arrays(dtype=np.float64, + shape=st.integers(min_value=1, max_value=1000)), + size=st.integers(min_value=1, max_value=50), + mode=st.sampled_from(["constant", "mirror", "wrap", "reflect", + "nearest"]), + ) +def test_gh_22586_crash_property(x, size, mode): + # property-based test for median_filter resilience to hard crashing + ndimage.median_filter(x, size=size, mode=mode) From e33b541216d138389a66890aa9474eeebd3308c1 Mon Sep 17 00:00:00 2001 From: Christian Veenhuis <124370897+ChVeen@users.noreply.github.com> Date: Tue, 6 May 2025 09:14:02 +0200 Subject: [PATCH 096/251] change to maxiter Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/optimize/zeros.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/zeros.c b/scipy/optimize/zeros.c index fb35a8f88778..b23652736bd4 100644 --- a/scipy/optimize/zeros.c +++ b/scipy/optimize/zeros.c @@ -96,7 +96,7 @@ call_solver(solver_type solver, PyObject *self, PyObject *args) return NULL; } if (iter < 0) { - PyErr_SetString(PyExc_ValueError, "iter must be >= 0"); + PyErr_SetString(PyExc_ValueError, "maxiter must be >= 0"); return NULL; } From 448412a4eb37abd292dce2d931cd901e69b62a6e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Tue, 6 May 2025 10:11:09 +0200 Subject: [PATCH 097/251] TST: signal: rm unneeded skips --- scipy/signal/tests/test_filter_design.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index 3c0ea09abeac..c9efc2840fd2 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -206,7 +206,6 @@ def test_simple(self, dt, xp): assert math.isclose(xp.real(k), 1.) assert k.dtype == dtyp - @skip_xp_backends(np_only=True) def test_bad_filter(self): # Regression test for #651: better handling of badly conditioned # filter coefficients. @@ -599,7 +598,6 @@ def test_analog(self, p, sos_dt, xp): assert_array_almost_equal(sos_dt, sos2_dt, decimal=4) assert_array_almost_equal(xp.flip(sos_dt, axis=0), sos2_ct, decimal=4) - @skip_xp_backends(np_only=True) def test_bad_args(self): with pytest.raises(ValueError, match=r'pairing must be one of'): zpk2sos([1], [2], 1, pairing='no_such_pairing') From 7dc6f897be00a5fd3bc028cf01665f006be20286 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:23:33 -0400 Subject: [PATCH 098/251] MAINT: Add formulas for binomial distribution --- scipy/stats/_new_distributions.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 8d1dd86732c6..ff71384dbc93 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -392,7 +392,28 @@ def __init__(self, *, n, p, **kwargs): super().__init__(n=n, p=p, **kwargs) def _pmf_formula(self, x, *, n, p, **kwargs): - return special.binom(n, x) * p**x * (1 - p)**(n - x) + return special._ufuncs._binom_pmf(x, n, p) + + def _logpmf_formula(self, x, *, n, p, **kwargs): + # This implementation is from the ``scipy.stats.binom`` and could be improved + # by using a more numerically sound implementation of the absolute value of + # the binomial coefficient. + combiln = ( + special.gammaln(n+1) - (special.gammaln(x+1) + special.gammaln(n-x+1)) + ) + return combiln + special.xlogy(x, p) + special.xlog1py(n-x, -p) + + def _cdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_cdf(x, n, p) + + def _ccdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_sf(x, n, p) + + def _icdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_ppf(x, n, p) + + def _iccdf_formula(self, x, *, n, p, **kwargs): + return special._ufuncs._binom_isf(x, n, p) # Distribution classes need only define the summary and beginning of the extended From a7231fcb70c87a13842303d300a9106c9a8059f5 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Sun, 13 Apr 2025 20:24:35 -0400 Subject: [PATCH 099/251] Change _SimpleDomain to _SimpleInterval to avoid ambiguity A simply connected domain in topology is a simply connected open set, but not all of these are open --- scipy/stats/_distribution_infrastructure.py | 52 ++++++++++----------- scipy/stats/_new_distributions.py | 36 +++++++------- scipy/stats/tests/test_continuous.py | 44 ++++++++--------- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 261bd713cc9d..b2047454fc67 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -235,8 +235,8 @@ def __str__(self): raise NotImplementedError() -class _SimpleDomain(_Domain): - r""" Representation of a simply-connected domain defined by two endpoints. +class _Interval(_Domain): + r""" Representation of an interval defined by two endpoints. Each endpoint may be a finite scalar, positive or negative infinity, or be given by a single parameter. The domain may include the endpoints or @@ -406,7 +406,7 @@ def draw(self, n, type_, min, max, squeezed_base_shape, rng=None): rng = np.random.default_rng(rng) def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) - uniform = rng.uniform if isinstance(self, _RealDomain) else ints + uniform = rng.uniform if isinstance(self, _RealInterval) else ints # get copies of min and max with no nans so that uniform doesn't fail min_nn, max_nn = min.copy(), max.copy() @@ -438,11 +438,11 @@ def ints(*args, **kwargs): return rng.integers(*args, **kwargs, endpoint=True) return z -class _RealDomain(_SimpleDomain): +class _RealInterval(_Interval): r""" Represents a simply-connected subset of the real line; i.e., an interval - Completes the implementation of the `_SimpleDomain` class for simple - domains on the real line. + Completes the implementation of the `_Interval` class for intervals + on the real line. Methods ------- @@ -480,10 +480,10 @@ def _get_endpoint_str(self, endpoint, funcname): return self.symbols.get(endpoint, f"{endpoint}") -class _IntegerDomain(_SimpleDomain): +class _IntegerInterval(_Interval): r""" Represents an interval of integers - Completes the implementation of the `_SimpleDomain` class for simple + Completes the implementation of the `_Interval` class for simple domains on the integers. Methods @@ -503,7 +503,6 @@ class _IntegerDomain(_SimpleDomain): Returns a string representation of the domain, e.g. "{a, a+1, ..., b-1, b}". """ - def contains(self, item, parameter_values=None): super_contains = super().contains(item, parameter_values) integral = (item == np.round(item)) @@ -4030,7 +4029,7 @@ def _make_distribution_rv_generic(dist): names = [] support = getattr(dist, '_support', (dist.a, dist.b)) for shape_info in dist._shape_info(): - domain = _RealDomain(endpoints=shape_info.endpoints, + domain = _RealInterval(endpoints=shape_info.endpoints, inclusive=shape_info.inclusive) param = _RealParameter(shape_info.name, domain=domain) parameters.append(param) @@ -4059,7 +4058,7 @@ def right(**parameter_values): else: endpoints = support - _x_support = _RealDomain(endpoints=endpoints, inclusive=(True, True)) + _x_support = _RealInterval(endpoints=endpoints, inclusive=(True, True)) _x_param = _RealParameter('x', domain=_x_support, typical=(-1, 1)) class CustomDistribution(new_class): @@ -4173,15 +4172,16 @@ def _make_distribution_custom(dist): # the infrastructure. parameters = [] - for name, info in parameterization.items(): - domain_info, typical = _get_domain_info(info) - domain = _RealDomain(**domain_info) - param = _RealParameter(name, domain=domain, typical=typical) - parameters.append(param) - parameterizations.append(_Parameterization(*parameters) if parameters else []) + for name, info in dist.parameters.items(): + domain = _RealInterval(endpoints=info['endpoints'], + inclusive=info['inclusive']) + param = _RealParameter(name, domain=domain) + parameters.append(param) + + endpoints = dist.support["endpoints"] + inclusive = dist.support.get("inclusive", (True, True)) - domain_info, _ = _get_domain_info(dist.support) - _x_support = _RealDomain(**domain_info) + _x_support = _RealInterval(endpoints=endpoints, inclusive=inclusive) _x_param = _RealParameter('x', domain=_x_support) repr_str = dist.__class__.__name__ @@ -4370,11 +4370,11 @@ class TruncatedDistribution(TransformedDistribution): # - if the mode of `_dist` is within the support, it's still the mode # - rejection sampling might be more efficient than inverse transform - _lb_domain = _RealDomain(endpoints=(-inf, 'ub'), inclusive=(True, False)) + _lb_domain = _RealInterval(endpoints=(-inf, 'ub'), inclusive=(True, False)) _lb_param = _RealParameter('lb', symbol=r'b_l', domain=_lb_domain, typical=(0.1, 0.2)) - _ub_domain = _RealDomain(endpoints=('lb', inf), inclusive=(False, True)) + _ub_domain = _RealInterval(endpoints=('lb', inf), inclusive=(False, True)) _ub_param = _RealParameter('ub', symbol=r'b_u', domain=_ub_domain, typical=(0.8, 0.9)) @@ -4516,11 +4516,11 @@ def truncate(X, lb=-np.inf, ub=np.inf): class ShiftedScaledDistribution(TransformedDistribution): """Distribution with a standard shift/scale transformation.""" # Unclear whether infinite loc/scale will work reasonably in all cases - _loc_domain = _RealDomain(endpoints=(-inf, inf), inclusive=(True, True)) + _loc_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) _loc_param = _RealParameter('loc', symbol=r'\mu', domain=_loc_domain, typical=(1, 2)) - _scale_domain = _RealDomain(endpoints=(-inf, inf), inclusive=(True, True)) + _scale_domain = _RealInterval(endpoints=(-inf, inf), inclusive=(True, True)) _scale_param = _RealParameter('scale', symbol=r'\sigma', domain=_scale_domain, typical=(0.1, 10)) @@ -4797,12 +4797,12 @@ class OrderStatisticDistribution(TransformedDistribution): """ - # These can be restricted to _IntegerDomain/_IntegerParameter in a separate + # These can be restricted to _IntegerInterval/_IntegerParameter in a separate # PR if desired. - _r_domain = _RealDomain(endpoints=(1, 'n'), inclusive=(True, True)) + _r_domain = _RealInterval(endpoints=(1, 'n'), inclusive=(True, True)) _r_param = _RealParameter('r', domain=_r_domain, typical=(1, 2)) - _n_domain = _RealDomain(endpoints=(1, np.inf), inclusive=(True, True)) + _n_domain = _RealInterval(endpoints=(1, np.inf), inclusive=(True, True)) _n_param = _RealParameter('n', domain=_n_domain, typical=(1, 4)) _r_domain.define_parameters(_n_param) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index ff71384dbc93..4d9939d2dca8 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -5,7 +5,7 @@ from scipy import special from scipy.stats._distribution_infrastructure import ( - ContinuousDistribution, DiscreteDistribution, _RealDomain, _IntegerDomain, + ContinuousDistribution, DiscreteDistribution, _RealInterval, _IntegerInterval, _RealParameter, _Parameterization, _combine_docs) __all__ = ['Normal', 'Uniform', 'Binomial'] @@ -25,9 +25,9 @@ class Normal(ContinuousDistribution): # `ShiftedScaledDistribution` allows this to be generated automatically from # an instance of `StandardNormal`, but the normal distribution is so frequently # used that it's worth a bit of code duplication to get better performance. - _mu_domain = _RealDomain(endpoints=(-inf, inf)) - _sigma_domain = _RealDomain(endpoints=(0, inf)) - _x_support = _RealDomain(endpoints=(-inf, inf)) + _mu_domain = _RealInterval(endpoints=(-inf, inf)) + _sigma_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(-inf, inf)) _mu_param = _RealParameter('mu', symbol=r'\mu', domain=_mu_domain, typical=(-1, 1)) @@ -132,7 +132,7 @@ class StandardNormal(Normal): f(x) = \frac{1}{\sqrt{2 \pi}} \exp \left( -\frac{1}{2} x^2 \right) """ - _x_support = _RealDomain(endpoints=(-inf, inf)) + _x_support = _RealInterval(endpoints=(-inf, inf)) _x_param = _RealParameter('x', domain=_x_support, typical=(-5, 5)) _variable = _x_param _parameterizations = [] @@ -217,11 +217,11 @@ class _LogUniform(ContinuousDistribution): """ - _a_domain = _RealDomain(endpoints=(0, inf)) - _b_domain = _RealDomain(endpoints=('a', inf)) - _log_a_domain = _RealDomain(endpoints=(-inf, inf)) - _log_b_domain = _RealDomain(endpoints=('log_a', inf)) - _x_support = _RealDomain(endpoints=('a', 'b'), inclusive=(True, True)) + _a_domain = _RealInterval(endpoints=(0, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _log_a_domain = _RealInterval(endpoints=(-inf, inf)) + _log_b_domain = _RealInterval(endpoints=('log_a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) @@ -279,9 +279,9 @@ class Uniform(ContinuousDistribution): """ - _a_domain = _RealDomain(endpoints=(-inf, inf)) - _b_domain = _RealDomain(endpoints=('a', inf)) - _x_support = _RealDomain(endpoints=('a', 'b'), inclusive=(True, True)) + _a_domain = _RealInterval(endpoints=(-inf, inf)) + _b_domain = _RealInterval(endpoints=('a', inf)) + _x_support = _RealInterval(endpoints=('a', 'b'), inclusive=(True, True)) _a_param = _RealParameter('a', domain=_a_domain, typical=(1e-3, 0.9)) _b_param = _RealParameter('b', domain=_b_domain, typical=(1.1, 1e3)) @@ -354,8 +354,8 @@ def _sample_formula(self, full_shape, rng, a, b, ab, **kwargs): class _Gamma(ContinuousDistribution): # Gamma distribution for testing only - _a_domain = _RealDomain(endpoints=(0, inf)) - _x_support = _RealDomain(endpoints=(0, inf), inclusive=(False, False)) + _a_domain = _RealInterval(endpoints=(0, inf)) + _x_support = _RealInterval(endpoints=(0, inf), inclusive=(False, False)) _a_param = _RealParameter('a', domain=_a_domain, typical=(0.1, 10)) _x_param = _RealParameter('x', domain=_x_support, typical=(0.1, 10)) @@ -377,9 +377,9 @@ class Binomial(DiscreteDistribution): f(x) = {n \choose x} p^x (1 - p)^{n-x} """ - _n_domain = _IntegerDomain(endpoints=(0, inf), inclusive=(True, False)) - _p_domain = _RealDomain(endpoints=(0, 1), inclusive=(True, True)) - _x_support = _IntegerDomain(endpoints=(0, 'n'), inclusive=(True, True)) + _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(True, False)) + _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(True, True)) + _x_support = _IntegerInterval(endpoints=(0, 'n'), inclusive=(True, True)) _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) _p_param = _RealParameter('p', domain=_p_domain, typical=(0.25, 0.75)) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 5d2e023ad1c8..9371529d9948 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -17,18 +17,18 @@ from scipy.stats import qmc from scipy.stats._distr_params import distcont, distdiscrete from scipy.stats._distribution_infrastructure import ( - _Domain, _RealDomain, _Parameter, _Parameterization, _RealParameter, + _Domain, _RealInterval, _Parameter, _Parameterization, _RealParameter, ContinuousDistribution, ShiftedScaledDistribution, _fiinfo, _generate_domain_support, Mixture) from scipy.stats._new_distributions import StandardNormal, _LogUniform, _Gamma from scipy.stats import Normal, Uniform -class Test_RealDomain: +class Test_RealInterval: rng = np.random.default_rng(349849812549824) def test_iv(self): - domain = _RealDomain(endpoints=('a', 'b')) + domain = _RealInterval(endpoints=('a', 'b')) message = "The endpoints of the distribution are defined..." with pytest.raises(TypeError, match=message): domain.get_numerical_endpoints(dict) @@ -39,7 +39,7 @@ def test_iv(self): def test_contains_simple(self, x): # Test `contains` when endpoints are defined by constants a, b = -np.inf, np.pi - domain = _RealDomain(endpoints=(a, b), inclusive=(False, True)) + domain = _RealInterval(endpoints=(a, b), inclusive=(False, True)) assert_equal(domain.contains(x), (a < x) & (x <= b)) @pytest.mark.slow @@ -70,10 +70,10 @@ def test_contains(self, shapes, inclusive_a, inclusive_b, data): np.linspace(a, b, 10), np.linspace(b, b+d, 10)]) # Domain is defined by two parameters, 'a' and 'b' - domain = _RealDomain(endpoints=('a', 'b'), + domain = _RealInterval(endpoints=('a', 'b'), inclusive=(inclusive_a, inclusive_b)) - domain.define_parameters(_RealParameter('a', domain=_RealDomain()), - _RealParameter('b', domain=_RealDomain())) + domain.define_parameters(_RealParameter('a', domain=_RealInterval()), + _RealParameter('b', domain=_RealInterval())) # Check that domain and string evaluation give the same result res = domain.contains(x, dict(a=a, b=b)) @@ -93,7 +93,7 @@ def test_contains(self, shapes, inclusive_a, inclusive_b, data): def test_contains_function_endpoints(self, inclusive, a, b): # Test `contains` when endpoints are defined by functions. endpoints = (lambda a, b: (a - b) / 2, lambda a, b: (a + b) / 2) - domain = _RealDomain(endpoints=endpoints, inclusive=inclusive) + domain = _RealInterval(endpoints=endpoints, inclusive=inclusive) x = np.asarray([(a - 2*b)/2, (a - b)/2, a/2, (a + b)/2, (a + 2*b)/2]) res = domain.contains(x, dict(a=a, b=b)) @@ -112,7 +112,7 @@ def test_contains_function_endpoints(self, inclusive, a, b): ('a', 5, True, False, "[a, 5)") ]) def test_str(self, case): - domain = _RealDomain(endpoints=case[:2], inclusive=case[2:4]) + domain = _RealInterval(endpoints=case[:2], inclusive=case[2:4]) assert str(domain) == case[4] @pytest.mark.slow @@ -135,7 +135,7 @@ def test_str2(self, a, b, inclusive_a, inclusive_b): b = _Domain.symbols.get(b, b) left_bracket = '[' if inclusive_a else '(' right_bracket = ']' if inclusive_b else ')' - domain = _RealDomain(endpoints=(a, b), + domain = _RealInterval(endpoints=(a, b), inclusive=(inclusive_a, inclusive_b)) ref = f"{left_bracket}{a}, {b}{right_bracket}" assert str(domain) == ref @@ -143,8 +143,8 @@ def test_str2(self, a, b, inclusive_a, inclusive_b): def test_symbols_gh22137(self): # `symbols` was accidentally shared between instances originally # Check that this is no longer the case - domain1 = _RealDomain(endpoints=(0, 1)) - domain2 = _RealDomain(endpoints=(0, 1)) + domain1 = _RealInterval(endpoints=(0, 1)) + domain2 = _RealInterval(endpoints=(0, 1)) assert domain1.symbols is not domain2.symbols @@ -176,7 +176,7 @@ def draw_distribution_from_family(family, data, rng, proportions, min_side=0): y = dist._variable.draw(y_shape, parameter_values=dist._parameters, proportions=proportions, rng=rng, region='typical') xy_result_shape = np.broadcast_shapes(y_shape, x_result_shape) - p_domain = _RealDomain((0, 1), (True, True)) + p_domain = _RealInterval((0, 1), (True, True)) p_var = _RealParameter('p', domain=p_domain) p = p_var.draw(x_shape, proportions=proportions, rng=rng) with np.errstate(divide='ignore', invalid='ignore'): @@ -552,9 +552,9 @@ def check_nans_and_edges(dist, fname, arg, res): valid_parameters = get_valid_parameters(dist) if fname in {'icdf', 'iccdf'}: - arg_domain = _RealDomain(endpoints=(0, 1), inclusive=(True, True)) + arg_domain = _RealInterval(endpoints=(0, 1), inclusive=(True, True)) elif fname in {'ilogcdf', 'ilogccdf'}: - arg_domain = _RealDomain(endpoints=(-inf, 0), inclusive=(True, True)) + arg_domain = _RealInterval(endpoints=(-inf, 0), inclusive=(True, True)) else: arg_domain = dist._variable.domain @@ -853,7 +853,7 @@ def classify_arg(dist, arg, arg_domain): def test_input_validation(): class Test(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain()) + _variable = _RealParameter('x', domain=_RealInterval()) message = ("The `Test` distribution family does not accept parameters, " "but parameters `{'a'}` were provided.") @@ -882,10 +882,10 @@ class Test(ContinuousDistribution): Test().moment(2, kind='coconut') class Test2(ContinuousDistribution): - _p1 = _RealParameter('c', domain=_RealDomain()) - _p2 = _RealParameter('d', domain=_RealDomain()) + _p1 = _RealParameter('c', domain=_RealInterval()) + _p2 = _RealParameter('d', domain=_RealInterval()) _parameterizations = [_Parameterization(_p1, _p2)] - _variable = _RealParameter('x', domain=_RealDomain()) + _variable = _RealParameter('x', domain=_RealInterval()) message = ("The provided parameters `{a}` do not match a supported " "parameterization of the `Test2` distribution family.") @@ -1872,7 +1872,7 @@ def test_Parameter(self): [(np.float16, np.float16), (np.int16, np.float64)]) def test_RealParameter_uncommon_dtypes(self, dtype_in, dtype_out): - domain = _RealDomain((-1, 1)) + domain = _RealInterval((-1, 1)) parameter = _RealParameter('x', domain=domain) x = np.asarray([0.5, 2.5], dtype=dtype_in) @@ -1887,7 +1887,7 @@ def test_ContinuousDistribution_set_invalid_nan(self): # to return the right shape and dytpe, but this would need to be # configurable. class TestDist(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain(endpoints=(0., 1.))) + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(0., 1.))) def _logpdf_formula(self, x, *args, **kwargs): return 0 @@ -2014,7 +2014,7 @@ def test_not_too_long(self, dist): class MixedDist(ContinuousDistribution): - _variable = _RealParameter('x', domain=_RealDomain(endpoints=(-np.inf, np.inf))) + _variable = _RealParameter('x', domain=_RealInterval(endpoints=(-np.inf, np.inf))) def _pdf_formula(self, x, *args, **kwargs): return (0.4 * 1/(1.1 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x+0.25)/1.1)**2) + 0.6 * 1/(0.9 * np.sqrt(2*np.pi)) * np.exp(-0.5*((x-0.5)/0.9)**2)) From 841b23dea5c0aab35cef49179a95efa9a472b47e Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Sat, 19 Apr 2025 10:56:52 -0400 Subject: [PATCH 100/251] MAINT: Improve __str__ for _IntegerInterval --- scipy/stats/_distribution_infrastructure.py | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index b2047454fc67..7a93d2d0c8a4 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -510,11 +510,42 @@ def contains(self, item, parameter_values=None): def __str__(self): a, b = self.endpoints - a = self.symbols.get(a, f"{a}") - b = self.symbols.get(b, f"{b}") - ap1 = f"{a} + 1" if isinstance(a, str) else f"{a + 1}" - bm1 = f"{b} - 1" if isinstance(b, str) else f"{b - 1}" - return f"{{{a}, {ap1}, ..., {bm1}, {b}}}" + a = self.symbols.get(a, a) + b = self.symbols.get(b, b) + + a_str, b_str = isinstance(a, str), isinstance(b, str) + a_inf = a == r"-\infty" if a_str else np.isinf(a) + b_inf = b == r"\infty" if b_str else np.isinf(b) + + # This doesn't work well for cases where ``a`` is floating point + # number large enough that ``nextafter(a, inf) > a + 1``, and + # similarly for ``b`` and nextafter(b, -inf). There may not be any + # distributions fit for SciPy where we would actually need to handle these + # cases though. + ap1 = f"{a} + 1" if a_str else f"{a + 1}" + bm1 = f"{b} - 1" if b_str else f"{b - 1}" + + if not a_str and not b_str: + gap = b - a + if gap == 3: + return f"\\{{{a}, {ap1}, {bm1}, {b}\\}}" + if gap == 2: + return f"\\{{{a}, {ap1}, {b}\\}}" + if gap == 1: + return f"\\{{{a}, {b}\\}}" + if gap == 0: + return f"\\{{{a}\\}}" + + if not a_inf and b_inf: + ap2 = f"{a} + 2" if a_str else f"{a + 2}" + return f"\\{{{a}, {ap1}, {ap2}, ...\\}}" + if a_inf and not b_inf: + bm2 = f"{b} - 2" if b_str else f"{b - 2}" + return f"\\{{{b}, {bm1}, {bm2}, ...\\}}" + if a_inf and b_inf: + return "\\{..., -2, -1, 0, 1, 2, ...\\}" + + return f"\\{{{a}, {ap1}, ..., {bm1}, {b}\\}}" class _Parameter(ABC): From 02819acf234f0e01c41c983594a52bcd6443239e Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 21 Apr 2025 14:48:01 -0400 Subject: [PATCH 101/251] MAINT: Update cdf2 and friends to use probability definition --- scipy/stats/_distribution_infrastructure.py | 37 +++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 7a93d2d0c8a4..f36a7de755c9 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1186,14 +1186,21 @@ def wrapped(self, x, y, *args, **kwargs): # transform the result later. i_swap = y < x x[i_swap], y[i_swap] = y[i_swap], x[i_swap] - i = x < low - x[i] = low[i] - i = y < low - y[i] = low[i] - i = x > high - x[i] = high[i] - i = y > high - y[i] = high[i] + + x_below = (x < low) + y_below = (y < low) + x_above = (x > high) + y_above = (y > high) + x[x_below] = low[x_below] + y[y_below] = low[y_below] + x[x_above] = high[x_above] + y[y_above] = high[y_above] + + # If x and y are both less than low (greater than high) + # than cdf2(x, y) should be 0 and replacing out-of-domain + # arguments with endpoints will give the wrong result. + both_beyond = (x_below & y_below) | (x_above & y_above) + degenerate = i_swap | both_beyond res = f(self, x, y, *args, **kwargs) @@ -1206,17 +1213,13 @@ def wrapped(self, x, y, *args, **kwargs): # Transform the result to account for swapped argument order res = np.asarray(res) if func_name == '_cdf2': - res[i_swap] *= -1. + res[degenerate] = 0 elif func_name == '_ccdf2': - res[i_swap] *= -1 - res[i_swap] += 2. + res[degenerate] = 1 elif func_name == '_logcdf2': - res = np.asarray(res + 0j) if np.any(i_swap) else res - res[i_swap] = res[i_swap] + np.pi*1j - else: - # res[i_swap] is always positive and less than 1, so it's - # safe to ensure that the result is real - res[i_swap] = _logexpxmexpy(np.log(2), res[i_swap]).real + res[degenerate] = -np.inf + elif func_name == '_logccdf2': + res[degenerate] = 0 return res[()] return wrapped From e171a151fd4340ef04bcda36bc2e1fe00d9a2523 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 21 Apr 2025 14:48:48 -0400 Subject: [PATCH 102/251] TST: Update tests in response to changes in cdf2 and friends --- scipy/stats/tests/test_continuous.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 9371529d9948..8a9c5f26539e 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -326,7 +326,7 @@ def test_subtraction_safe(self): # Safe subtraction is needed in special cases x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) - p0 = X.pdf(0)*(y-x) + p0 = np.where(x > y, 0.0, X.pdf(0)*(y-x))[()] p1 = X.cdf(x, y, method='subtraction_safe') p2 = X.cdf(x, y, method='subtraction') assert_equal(p2, 0) @@ -504,7 +504,7 @@ def check_cdf2(dist, log, x, y, result_shape, methods): or dist._overrides('_logccdf_formula')): methods.add('log/exp') - ref = dist.cdf(y) - dist.cdf(x) + ref = np.where(x > y, 0.0, dist.cdf(y) - dist.cdf(x))[()] np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): @@ -532,7 +532,7 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): if dist._overrides(f'_{"log" if log else ""}ccdf2_formula'): methods.add('formula') - ref = dist.cdf(x) + dist.ccdf(y) + ref = np.where(x > y, 1.0, dist.cdf(x) + dist.ccdf(y))[()] np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): From 29adf2b8f59148ec8e38596a0825bd2bceb44c48 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:24:02 -0400 Subject: [PATCH 103/251] DOC: Update definition in docs for two arg ccdf --- scipy/stats/_probability_distribution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index 0e3c915f8cc9..ff2343bd77bf 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -1236,7 +1236,7 @@ def ccdf(self, x, y, /, *, method): .. math:: - G(x, y) = 1 - F(x, y) = P(X < x \text{ or } X > y) + G(x, y) = 1 - F(x, y) = P(\text{not } x \leq X \leq y) `ccdf` accepts `x` for :math:`x` and `y` for :math:`y`. From 142f1dc8ca460283e22ed05ff11b104f34de4263 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:27:22 -0400 Subject: [PATCH 104/251] MAINT: Have error message leave open future support for transforms on discrete RVs --- scipy/stats/_distribution_infrastructure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index f36a7de755c9..ce82ebd45df8 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -4347,7 +4347,7 @@ def wrapped(self, p, *args, loc, scale, sign, **kwargs): class TransformedDistribution(ContinuousDistribution): def __init__(self, X, /, *args, **kwargs): if not isinstance(X, ContinuousDistribution): - message = "Transformations are only supported for continuous RVs." + message = "Transformations are currently only supported for continuous RVs." raise NotImplementedError(message) self._copy_parameterization() self._variable = X._variable From f93805ddc249abefedbb17121d27010849b72f52 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:01:32 -0400 Subject: [PATCH 105/251] MAINT: Fix pdf of discrete distribution outside of support --- scipy/stats/_distribution_infrastructure.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index ce82ebd45df8..7cbc27d61158 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -997,8 +997,11 @@ def filtered(self, x, *args, **kwargs): else np.any(mask_endpoint)) # Check for non-integral arguments to PMF method + # or PDF of a discrete distribution. any_non_integral = False - if method_name in replace_non_integral: + if ( + method_name in replace_non_integral + or discrete and method_name in {"pdf", "logpdf"}): mask_non_integral = (x != np.floor(x)) & ~np.isnan(x) any_non_integral = (mask_non_integral if mask_non_integral.shape == () else np.any(mask_non_integral)) @@ -1028,7 +1031,7 @@ def filtered(self, x, *args, **kwargs): # For non-integral arguments to PMF, replace with zero if any_non_integral: - zero = -np.inf if method_name == 'logpmf' else 0 + zero = -np.inf if method_name in {'logpmf', 'logpdf'} else 0 res[mask_non_integral] = zero # For arguments outside the function domain, replace results From 4b8f8564c2679ee33eead5051807d9aa463e3ec3 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:12:18 -0400 Subject: [PATCH 106/251] MAINT: Handle edge-case in discrete mode calculation --- scipy/stats/_distribution_infrastructure.py | 28 +++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 7cbc27d61158..a73e2ccba9d8 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3701,16 +3701,28 @@ def _ilogccdf_inversion(self, x, **params): def _mode_optimization(self, **params): # If `x` is the true mode of a unimodal continuous function, we can find - # the mode among the integers by rounding in each direction and checking - # which is better. I think `xatol` should be able to be 1 if the documented - # convergence criterion were implemented, but the actual convergence - # criterion is different, and I'm not sure if it controls the total width - # of the bracket. `xatol=0.1` might not be safe as-implemented. - x = super()._mode_optimization(xatol=0.1, **params) + # the mode among integers by rounding in each direction and checking + # which is better. If the difference between `x` and the nearest integer + # is less than `xatol`, the computed value of `x` may end up on the wrong + # side of the nearest integer. Setting `xatol=0.5` guarantees that at most + # three integers need to be checked, the two nearest integers, ``floor(x)`` + # and ``round(x)`` and the nearest integer other than these. + x = super()._mode_optimization(xatol=0.5, **params) + low, high = self.support() xl, xr = np.floor(x), np.ceil(x) - fl, fr = self._pmf_dispatch(xl, **params), self._pmf_dispatch(xr, **params) + nearest = np.round(x) + # Clip to stay within support. There will be redundant calculation + # when clipping since `xo` will be one of `xl` or `xr`, but let's + # keep the implementation simple for now. + xo = np.clip(nearest + np.copysign(1, nearest - x), low, high) + fl = self._pmf_dispatch(xl, **params) + fr = self._pmf_dispatch(xr, **params) + fo = self._pmf_dispatch(xo, **params) mode = np.asarray(xl) - mode[fr > fl] = xr + comp1 = (fr > fl) + comp2 = (fo > fr) + mode[comp1 & ~comp2] = xr + mode[comp1 & comp2] = xo return mode def _logentropy_quadrature(self, **params): From 101503e04022470d9a9982536f1ca7bef59355b9 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:18:15 -0400 Subject: [PATCH 107/251] TST: Update a changed warning message in test --- scipy/stats/tests/test_continuous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 8a9c5f26539e..f0724dadaa6b 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1394,7 +1394,7 @@ def test_ContinuousDistribution_only(self): X = stats.Binomial(n=10, p=0.5) # This is applied at the top level TransformedDistribution, # so testing one subclass is enough - message = "Transformations are only supported for continuous RVs." + message = "Transformations are currently only supported for continuous RVs." with pytest.raises(NotImplementedError, match=message): stats.exp(X) From ff42255d0815d946285ece1a4585f25b678ca7f6 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:21:23 -0400 Subject: [PATCH 108/251] MAINT: Improvements to help tests pass --- scipy/stats/_distribution_infrastructure.py | 38 ++++++++++++---- scipy/stats/_new_distributions.py | 32 ++++++++++++++ scipy/stats/tests/test_continuous.py | 49 +++++++++++++++------ 3 files changed, 98 insertions(+), 21 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index a73e2ccba9d8..05dff1e0150f 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -321,7 +321,6 @@ def get_numerical_endpoints(self, parameter_values): Numerical values of the endpoints """ - # TODO: ensure outputs are floats a, b = self.endpoints # If `a` (`b`) is a string - the name of the parameter that defines # the endpoint of the domain - then corresponding numerical values @@ -345,7 +344,9 @@ def get_numerical_endpoints(self, parameter_values): "all required distribution parameters as keyword " "arguments.") raise TypeError(message) from e - return a, b + # Floating point types are used for even integer parameters. + # Convert to float here to ensure consistency throughout framework. + return a.astype(np.float64), b.astype(np.float64) def contains(self, item, parameter_values=None): r"""Determine whether the argument is contained within the domain. @@ -3264,6 +3265,11 @@ def _moment_central_normalize(self, order, **params): if standard_moment is None: return None var = self._moment_central_dispatch(2, methods=self._moment_methods, **params) + if np.any(var == 0): + # In degenerate cases where var == 0, ``method='normalize'`` will + # not work. To keep things simple for now, don't use this method at all + # even if only one entry from an array valued ``var`` is zero. + return None return standard_moment*var**(order/2) def _moment_central_general(self, order, **params): @@ -3618,12 +3624,28 @@ def _overrides(self, method_name): return True return super()._overrides(method_name) - # These should probably check whether pmf = 0 def _logpdf_formula(self, x, **params): - return np.full_like(x, np.inf) + logpmf = self.logpmf(x) + out_of_support = np.isneginf(logpmf) + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, + np.where(out_of_support, -np.inf, np.inf))[()] def _pdf_formula(self, x, **params): - return np.full_like(x, np.inf) + logpmf = self.logpmf(x) + out_of_support = np.isneginf(logpmf) + if params: + p = next(iter(params.values())) + nan_result = np.isnan(x) | np.isnan(p) + else: + nan_result = np.isnan(x) + return np.where(nan_result, np.nan, + np.where(out_of_support, 0.0, np.inf))[()] + def _pxf_dispatch(self, x, *, method=None, **params): return self._pmf_dispatch(x, method=method, **params) @@ -3721,8 +3743,8 @@ def _mode_optimization(self, **params): mode = np.asarray(xl) comp1 = (fr > fl) comp2 = (fo > fr) - mode[comp1 & ~comp2] = xr - mode[comp1 & comp2] = xo + mode[comp1 & ~comp2] = xr[comp1 & ~comp2] + mode[comp1 & comp2] = xo[comp1 & comp2] return mode def _logentropy_quadrature(self, **params): @@ -3733,7 +3755,7 @@ def logintegrand(x, **params): # so logpmf is always negative, and so log(logpmf) = log(-logpmf) + pi*j. # The two imaginary components "cancel" each other out (which we would # expect because each term of the entropy summand is positive). - return logpmf + np.log(-logpmf) + return np.where(np.isfinite(logpmf), logpmf + np.log(-logpmf), -np.inf) return self._quadrature(logintegrand, params=params, log=True) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 4d9939d2dca8..e3edd702613e 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -415,6 +415,38 @@ def _icdf_formula(self, x, *, n, p, **kwargs): def _iccdf_formula(self, x, *, n, p, **kwargs): return special._ufuncs._binom_isf(x, n, p) + def _mode_formula(self, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Mode + mode = np.floor((n+1)*p) + mode = np.where(p == 1, mode - 1, mode) + return mode[()] + + def _moment_raw_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 0: + return np.ones_like(n) + if order == 1: + return n*p + if order == 2: + return n*p*(1 - p + n*p) + return None + _moment_raw_formula.orders = [0, 1, 2] # type: ignore[attr-defined] + + def _moment_central_formula(self, order, *, n, p, **kwargs): + # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments + if order == 0: + return np.ones_like(n) + if order == 1: + return np.zeros_like(n) + if order == 2: + return n*p*(1 - p) + if order == 3: + return n*p*(1 - p)*(1 - 2*p) + if order == 4: + return n*p*(1 - p)*(1 + (3*n - 6)*p*(1 - p)) + return None + _moment_central_formula.orders = [0, 1, 2, 3, 4] # type: ignore[attr-defined] + # Distribution classes need only define the summary and beginning of the extended # summary portion of the class documentation. All other documentation, including diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index f0724dadaa6b..a93927e737b9 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -21,7 +21,8 @@ ContinuousDistribution, ShiftedScaledDistribution, _fiinfo, _generate_domain_support, Mixture) from scipy.stats._new_distributions import StandardNormal, _LogUniform, _Gamma -from scipy.stats import Normal, Uniform +from scipy.stats._new_distributions import DiscreteDistribution +from scipy.stats import Normal, Uniform, Binomial class Test_RealInterval: @@ -185,18 +186,24 @@ def draw_distribution_from_family(family, data, rng, proportions, min_side=0): return dist, x, y, p, logp, result_shape, x_result_shape, xy_result_shape -families = [ +continuous_families = [ StandardNormal, Normal, Uniform, _LogUniform ] +discrete_families = [ + Binomial, +] + +families = continuous_families + discrete_families + class TestDistributions: @pytest.mark.fail_slow(60) # need to break up check_moment_funcs @settings(max_examples=20) - @pytest.mark.parametrize('family', families) + @pytest.mark.parametrize('family', discrete_families) @given(data=strategies.data(), seed=strategies.integers(min_value=0)) @pytest.mark.thread_unsafe def test_support_moments_sample(self, family, data, seed): @@ -411,7 +418,11 @@ def check_sample_shape_NaNs(dist, fname, sample_shape, result_shape, rng): sample1 = sample_method(sample_shape, method=method, rng=42) sample2 = sample_method(sample_shape, method=method, rng=42) - assert not np.any(np.equal(res, sample1)) + if not isinstance(dist, DiscreteDistribution): + # The idea is that it's very unlikely that the random sample + # for a randomly chosen seed will match that for seed 42, + # but it is not so unlikely if `dist` is a discrete distribution. + assert not np.any(np.equal(res, sample1)) assert_equal(sample1, sample2) @@ -581,13 +592,15 @@ def check_nans_and_edges(dist, fname, arg, res): if fname in {'logpdf'}: assert_equal(res[outside_arg_minus], -np.inf) assert_equal(res[outside_arg_plus], -np.inf) - assert_equal(res[endpoint_arg_minus & ~valid_arg], -np.inf) - assert_equal(res[endpoint_arg_plus & ~valid_arg], -np.inf) + ref = -np.inf if not isinstance(dist, DiscreteDistribution) else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) elif fname in {'pdf'}: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], 0) - assert_equal(res[endpoint_arg_minus & ~valid_arg], 0) - assert_equal(res[endpoint_arg_plus & ~valid_arg], 0) + ref = 0 if not isinstance(dist, DiscreteDistribution) else np.inf + assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) + assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) elif fname in {'logcdf'}: assert_equal(res[outside_arg_minus], -inf) assert_equal(res[outside_arg_plus], 0) @@ -619,7 +632,11 @@ def check_nans_and_edges(dist, fname, arg, res): assert_equal(res[endpoint_arg == -1], b[endpoint_arg == -1]) assert_equal(res[endpoint_arg == 1], a[endpoint_arg == 1]) - if fname not in {'logmean', 'mean', 'logskewness', 'skewness', 'support'}: + exclude = {'logmean', 'mean', 'logskewness', 'skewness', 'support'} + if isinstance(dist, DiscreteDistribution): + exclude.update({'pdf', 'logpdf'}) + + if fname not in exclude: assert np.isfinite(res[all_valid & (endpoint_arg == 0)]).all() @@ -649,7 +666,6 @@ def has_formula(order, kind): orders = getattr(formula, 'orders', set(range(6))) return order in orders - dist.reset_cache() ### Check Raw Moments ### @@ -702,6 +718,7 @@ def has_formula(order, kind): dist.moment(i, 'raw') check(i, 'central', 'transform', ref) + variance = dist.variance() dist.reset_cache() # If we have standard moment formulas, or if there are @@ -712,9 +729,9 @@ def has_formula(order, kind): for i in range(3, 6): ref = dist.moment(i, 'central', method='quadrature') check(i, 'central', 'normalize', ref, - success=has_formula(i, 'standardized')) + success=has_formula(i, 'standardized') and not np.any(variance == 0)) dist.moment(i, 'standardized') # build up the cache - check(i, 'central', 'normalize', ref) + check(i, 'central', 'normalize', ref, success=not np.any(variance == 0)) ### Check Standardized Moments ### @@ -727,7 +744,13 @@ def has_formula(order, kind): assert ref.shape == result_shape check(i, 'standardized', 'formula', ref, success=has_formula(i, 'standardized')) - check(i, 'standardized', 'general', ref, success=i <= 2) + if not ( + isinstance(dist, Binomial) + and np.any((dist.n == 0) | (dist.p == 0) | (dist.p == 1)) + ): + # This test will fail for degenerate case where binomial distribution + # is a point distribution. + check(i, 'standardized', 'general', ref, success=i <= 2) check(i, 'standardized', 'normalize', ref) if isinstance(dist, ShiftedScaledDistribution): From 3767f853413300590eccea13de8f8b12bda48701 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 13:13:02 -0400 Subject: [PATCH 109/251] MAINT: Fix edge case for pdf/pmf of discrete distribution - non-integral x, invalid parameters --- scipy/stats/_distribution_infrastructure.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 05dff1e0150f..7ad7edea742b 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1030,10 +1030,11 @@ def filtered(self, x, *args, **kwargs): if res_needs_copy: res = np.array(res, dtype=dtype, copy=True) - # For non-integral arguments to PMF, replace with zero + # For non-integral arguments to PMF (and PDF of discrete distribution) + # replace with zero. if any_non_integral: zero = -np.inf if method_name in {'logpmf', 'logpdf'} else 0 - res[mask_non_integral] = zero + res[mask_non_integral & ~np.isnan(res)] = zero # For arguments outside the function domain, replace results if any_invalid: From 5cb080c9b0d913230c7b26dffeef4c03b3b98b85 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:03:58 -0400 Subject: [PATCH 110/251] TST: Skip some tests that don't make sense for discrete distributions --- scipy/stats/tests/test_continuous.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index a93927e737b9..d018363e96e1 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -587,46 +587,48 @@ def check_nans_and_edges(dist, fname, arg, res): outside_arg_plus = (outside_arg == 1) & valid_parameters endpoint_arg_minus = (endpoint_arg == -1) & valid_parameters endpoint_arg_plus = (endpoint_arg == 1) & valid_parameters + + is_discrete = isinstance(dist, DiscreteDistribution) # Writing this independently of how the are set in the distribution # infrastructure. That is very compact; this is very verbose. if fname in {'logpdf'}: assert_equal(res[outside_arg_minus], -np.inf) assert_equal(res[outside_arg_plus], -np.inf) - ref = -np.inf if not isinstance(dist, DiscreteDistribution) else np.inf + ref = -np.inf if not is_discrete else np.inf assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) elif fname in {'pdf'}: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], 0) - ref = 0 if not isinstance(dist, DiscreteDistribution) else np.inf + ref = 0 if not is_discrete else np.inf assert_equal(res[endpoint_arg_minus & ~valid_arg], ref) assert_equal(res[endpoint_arg_plus & ~valid_arg], ref) - elif fname in {'logcdf'}: + elif fname in {'logcdf'} and not is_discrete: assert_equal(res[outside_arg_minus], -inf) assert_equal(res[outside_arg_plus], 0) assert_equal(res[endpoint_arg_minus], -inf) assert_equal(res[endpoint_arg_plus], 0) - elif fname in {'cdf'}: + elif fname in {'cdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], 1) assert_equal(res[endpoint_arg_minus], 0) assert_equal(res[endpoint_arg_plus], 1) - elif fname in {'logccdf'}: + elif fname in {'logccdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 0) assert_equal(res[outside_arg_plus], -inf) assert_equal(res[endpoint_arg_minus], 0) assert_equal(res[endpoint_arg_plus], -inf) - elif fname in {'ccdf'}: + elif fname in {'ccdf'} and not is_discrete: assert_equal(res[outside_arg_minus], 1) assert_equal(res[outside_arg_plus], 0) assert_equal(res[endpoint_arg_minus], 1) assert_equal(res[endpoint_arg_plus], 0) - elif fname in {'ilogcdf', 'icdf'}: + elif fname in {'ilogcdf', 'icdf'} and not is_discrete: assert_equal(res[outside_arg == -1], np.nan) assert_equal(res[outside_arg == 1], np.nan) assert_equal(res[endpoint_arg == -1], a[endpoint_arg == -1]) assert_equal(res[endpoint_arg == 1], b[endpoint_arg == 1]) - elif fname in {'ilogccdf', 'iccdf'}: + elif fname in {'ilogccdf', 'iccdf'} and not is_discrete: assert_equal(res[outside_arg == -1], np.nan) assert_equal(res[outside_arg == 1], np.nan) assert_equal(res[endpoint_arg == -1], b[endpoint_arg == -1]) From 1727a3b0205bf06a70a9c2439987b1c44be8f2ef Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:29:36 -0400 Subject: [PATCH 111/251] Fix discrete cdf/logcdf for nan inputs --- scipy/stats/_distribution_infrastructure.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 7ad7edea742b..41296dc324f4 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2101,7 +2101,11 @@ def _quadrature(self, integrand, limits=None, args=None, else: res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum res = np.asarray(res) - res[a > b] = -np.inf if log else 0 # fix in nsum? + res[(a > b)] = -np.inf if log else 0 # fix in nsum? + # The result should be nan when parameters are nan, so need to special + # case this. + cond = np.isnan(next(iter(params.values()))) if params else np.True_ + res[cond] = np.nan return res[()] def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): From b5fd0eedb280a25bc19bfe1f69be2e94020535a7 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:38:26 -0400 Subject: [PATCH 112/251] BUG: Fix unhandled NaN input in _binom_cdf --- scipy/special/boost_special_functions.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scipy/special/boost_special_functions.h b/scipy/special/boost_special_functions.h index 7eaeba33d404..4d865f8e5ce6 100644 --- a/scipy/special/boost_special_functions.h +++ b/scipy/special/boost_special_functions.h @@ -1318,6 +1318,9 @@ binom_cdf_wrap(const Real x, const Real n, const Real p) return boost::math::cdf( boost::math::binomial_distribution(n, p), x); } + if (std::isnan(x)) { + return std::numeric_limits::quiet_NaN(); + } // -inf => 0, inf => 1 return 1 - std::signbit(x); } From 9db4861177837b663672919149d3dbe14e1a9991 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:06:30 -0400 Subject: [PATCH 113/251] TST: Fix cdf2 tests to accomodate discrete distributions --- scipy/stats/tests/test_continuous.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index d018363e96e1..4694553dbaa6 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -515,7 +515,7 @@ def check_cdf2(dist, log, x, y, result_shape, methods): or dist._overrides('_logccdf_formula')): methods.add('log/exp') - ref = np.where(x > y, 0.0, dist.cdf(y) - dist.cdf(x))[()] + ref = np.where(x > y, 0.0, dist.cdf(y) - dist.cdf(x) + dist.pmf(x))[()] np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): @@ -543,7 +543,8 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): if dist._overrides(f'_{"log" if log else ""}ccdf2_formula'): methods.add('formula') - ref = np.where(x > y, 1.0, dist.cdf(x) + dist.ccdf(y))[()] + ref = np.where(x > y, 1.0, + dist.cdf(x) + dist.ccdf(y) - dist.pmf(x) - dist.pmf(y))[()] np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): From b48a369759857e37fcb0c93fb36838eb5c9c2cda Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:46:34 -0400 Subject: [PATCH 114/251] MAINT: Fix typo * instead of ** --- scipy/stats/_distribution_infrastructure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 41296dc324f4..f87eec8843ab 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2682,7 +2682,7 @@ def _cdf2_subtraction_safe(self, x, y, **params): params_mask = {key: np.broadcast_to(val, mask.shape)[mask] for key, val in params.items()} out = np.asarray(out) - out[mask] = self._cdf2_quadrature(x[mask], y[mask], *params_mask) + out[mask] = self._cdf2_quadrature(x[mask], y[mask], **params_mask) return out[()] def _cdf2_quadrature(self, x, y, **params): From e1699bac907c1fb0949ad19ad77a2d06f2f96976 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:46:54 -0400 Subject: [PATCH 115/251] MAINT: Broadcast a boolean array to correct shape --- scipy/stats/_distribution_infrastructure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index f87eec8843ab..63e11e1b874f 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2105,6 +2105,7 @@ def _quadrature(self, integrand, limits=None, args=None, # The result should be nan when parameters are nan, so need to special # case this. cond = np.isnan(next(iter(params.values()))) if params else np.True_ + cond = np.broadcast_to(cond, a.shape) res[cond] = np.nan return res[()] From e76c1c588386703bcd39c2c46fe71c61f5e7e36f Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:47:17 -0400 Subject: [PATCH 116/251] MAINT: Add cdf2 safe subtraction methods to discrete dists - dispatch was going to the parents, which isn't correct --- scipy/stats/_distribution_infrastructure.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 63e11e1b874f..2fbdf49e4218 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3683,12 +3683,24 @@ def _cdf2_subtraction(self, x, y, **params): pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) return cdf_ymx + pmf_x + def _cdf2_subtraction_safe(self, x, y, **params): + x_, y_ = np.floor(x), np.floor(y) + cdf_ymx = super()._cdf2_subtraction_safe(x_, y_, **params) + pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) + return cdf_ymx + pmf_x + def _logcdf2_subtraction(self, x, y, **params): x_, y_ = np.floor(x), np.floor(y) logcdf_ymx = super()._logcdf2_subtraction(x_, y_, **params) logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) + def _logcdf2_subtraction_safe(self, x, y, **params): + x_, y_ = np.floor(x), np.floor(y) + logcdf_ymx = super()._logcdf2_subtraction_safe(x_, y_, **params) + logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) + return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) + def _ccdf2_addition(self, x, y, **params): a, _ = self._support(**params) ccdf_y = self._ccdf_dispatch(y, **params) From 82a94d718a0520af78df1146bb00ae45931007d8 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:58:14 -0400 Subject: [PATCH 117/251] MAINT: Exclude degenerate case from tests --- scipy/stats/tests/test_continuous.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 4694553dbaa6..63a5f5effc47 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -639,7 +639,14 @@ def check_nans_and_edges(dist, fname, arg, res): if isinstance(dist, DiscreteDistribution): exclude.update({'pdf', 'logpdf'}) - if fname not in exclude: + if ( + fname not in exclude + and not (isinstance(dist, Binomial) + and np.any((dist.n == 0) | (dist.p == 0) | (dist.p == 1)))): + # This can fail in degenerate case where Binomial distribution is a point + # distribution. Further on, we could factor out an is_degenerate function + # for the tests, or think about storing info about degeneracy in the + # instances. assert np.isfinite(res[all_valid & (endpoint_arg == 0)]).all() From 7c534e7968577ecd75c195c8b8165125382b6829 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:06:20 -0400 Subject: [PATCH 118/251] MAINT: Fix bug, using subtraction safe when x == y --- scipy/stats/_distribution_infrastructure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 2fbdf49e4218..3cc202e81adf 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2677,7 +2677,7 @@ def _cdf2_subtraction_safe(self, x, y, **params): cdf_max = np.maximum(cdf_x, cdf_y) ccdf_max = np.maximum(ccdf_x, ccdf_y) spacing = np.spacing(np.where(i, ccdf_max, cdf_max)) - mask = np.abs(tol * out) < spacing + mask = (np.abs(tol * out) < spacing) & (out != 0) if np.any(mask): params_mask = {key: np.broadcast_to(val, mask.shape)[mask] From efc63916095f0460198f0a752402ce0a4754d163 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:17:37 -0400 Subject: [PATCH 119/251] MAINT: Fix reference formula for ccdf2 --- scipy/stats/tests/test_continuous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 63a5f5effc47..eeb64e979739 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -544,7 +544,7 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): methods.add('formula') ref = np.where(x > y, 1.0, - dist.cdf(x) + dist.ccdf(y) - dist.pmf(x) - dist.pmf(y))[()] + dist.cdf(x) + dist.ccdf(y) - dist.pmf(x))[()] np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): From d7f91acd7e0d4f48a6747c43db7b3179394171ea Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 22:08:59 -0400 Subject: [PATCH 120/251] MAINT: Fix icdf inversion and attempt to fix iccdf inversion --- scipy/stats/_distribution_infrastructure.py | 26 ++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 3cc202e81adf..78cb6015b9e9 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3716,26 +3716,36 @@ def _logccdf2_addition(self, x, y, **params): return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) def _icdf_inversion(self, x, **params): - res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=1) - res = np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + # For discrete distributions, icdf(p) is defined as the minimum x + # such that cdf(x) >= p. + # np.nextafter(xatol + res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=0.9) + # If f(x) >= 0, then icdf(p) must be less than x, otherwise greater. + res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) + # If cdf(res) < 0, then the true result must be one greater. + res = np.where(self.cdf(res) >= 0, res, res + 1.0) res[np.isnan(x)] = np.nan return res def _ilogcdf_inversion(self, x, **params): - res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=1) - res = np.where(res.fl >= 0, np.floor(res.xl), np.floor(res.xr)) + # follows same logic as _icdf_inversion + res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=0.9) + res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) + res = np.where(self.cdf(res), res, res + 1.0) res[np.isnan(x)] = np.nan return res def _iccdf_inversion(self, x, **params): - res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=1) - res = np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=0.9) + res = np.where(res.fun >= 0, np.ceil(res.x), np.floor(res.x)) + res = np.where(self.cdf(res), res, res - 1.0) res[np.isnan(x)] = np.nan return res def _ilogccdf_inversion(self, x, **params): - res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=1) - res = np.where(res.fl <= 0, np.floor(res.xl), np.floor(res.xr)) + res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=0.9) + res = np.where(res.fun >= 0, np.ceil(res.x), np.floor(res.x)) + res = np.where(self.cdf(res), res, res - 1.0) res[np.isnan(x)] = np.nan return res From 3cd50e9f2567c3d5c3c0018176911c2b03b7ba37 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:36:16 -0400 Subject: [PATCH 121/251] MAINT: Fix icdf and friends inversion for real --- scipy/stats/_distribution_infrastructure.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 78cb6015b9e9..76a79498205e 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3720,10 +3720,9 @@ def _icdf_inversion(self, x, **params): # such that cdf(x) >= p. # np.nextafter(xatol res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=0.9) - # If f(x) >= 0, then icdf(p) must be less than x, otherwise greater. res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) - # If cdf(res) < 0, then the true result must be one greater. - res = np.where(self.cdf(res) >= 0, res, res + 1.0) + # If cdf(res) < x, then the true result must be one greater. + res = np.where(self.cdf(res) >= x, res, res + 1.0) res[np.isnan(x)] = np.nan return res @@ -3731,21 +3730,21 @@ def _ilogcdf_inversion(self, x, **params): # follows same logic as _icdf_inversion res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=0.9) res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) - res = np.where(self.cdf(res), res, res + 1.0) + res = np.where(self.logcdf(res) >= x, res, res + 1.0) res[np.isnan(x)] = np.nan return res def _iccdf_inversion(self, x, **params): res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun >= 0, np.ceil(res.x), np.floor(res.x)) - res = np.where(self.cdf(res), res, res - 1.0) + res = np.where(res.fun <= 0, np.floor(res.x), np.ceil(res.x)) + res = np.where(self.ccdf(res) <= x, res, res + 1.0) res[np.isnan(x)] = np.nan return res def _ilogccdf_inversion(self, x, **params): res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun >= 0, np.ceil(res.x), np.floor(res.x)) - res = np.where(self.cdf(res), res, res - 1.0) + res = np.where(res.fun <= 0, np.floor(res.x), np.ceil(res.x)) + res = np.where(self.logccdf(res) <= x, res, res + 1.0) res[np.isnan(x)] = np.nan return res From b1549878529df5a4d3b1cc461f1f6bcf515216da Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:45:48 -0400 Subject: [PATCH 122/251] MAINT: Fix bug I introduced by changing things for debugging purposes --- scipy/stats/_distribution_infrastructure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 76a79498205e..d97512a7df23 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2677,7 +2677,7 @@ def _cdf2_subtraction_safe(self, x, y, **params): cdf_max = np.maximum(cdf_x, cdf_y) ccdf_max = np.maximum(ccdf_x, ccdf_y) spacing = np.spacing(np.where(i, ccdf_max, cdf_max)) - mask = (np.abs(tol * out) < spacing) & (out != 0) + mask = (np.abs(tol * out) < spacing) if np.any(mask): params_mask = {key: np.broadcast_to(val, mask.shape)[mask] From 1329c10cbe441dcd417870668d6643b4053bee02 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:48:34 -0400 Subject: [PATCH 123/251] MAINT: Introduce continuous distributions back into test_support_moments_sample --- scipy/stats/tests/test_continuous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index eeb64e979739..5a0ff832cecf 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -203,7 +203,7 @@ def draw_distribution_from_family(family, data, rng, proportions, min_side=0): class TestDistributions: @pytest.mark.fail_slow(60) # need to break up check_moment_funcs @settings(max_examples=20) - @pytest.mark.parametrize('family', discrete_families) + @pytest.mark.parametrize('family', families) @given(data=strategies.data(), seed=strategies.integers(min_value=0)) @pytest.mark.thread_unsafe def test_support_moments_sample(self, family, data, seed): From ad6048f1a59edf4c379e05c0680dcdd3aade6bc9 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Thu, 24 Apr 2025 00:19:57 -0400 Subject: [PATCH 124/251] TST: Add skips for rv_generic to get all tests to pass - these seem to be due to using float internally as integer params in discrete distributions in new infra, but using int sometimes for discrete distributions in old infra --- scipy/stats/tests/test_continuous.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 5a0ff832cecf..c8e252cbbb11 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1110,6 +1110,7 @@ def test_rv_generic(self, i, distdata): 'nchypergeom_fisher', # distribution functions don't accept NaN 'nchypergeom_wallenius', # distribution functions don't accept NaN 'skellam', # during `entropy`, Fatal Python error: Aborted! + 'zipfian', # during init, value error due to unexpected nans }: return @@ -1124,6 +1125,7 @@ def test_rv_generic(self, i, distdata): 3: {'pareto'}, # stats.pareto is just wrong 4: {'invgamma'}} # tolerance issue skip_standardized = {'exponpow', 'ksone'} # tolerances + skip_median = {'nhypergeom', 'yulesimon'} # nan mismatch dist = getattr(stats, distname) params = dict(zip(dist.shapes.split(', '), distdata[1])) if dist.shapes else {} @@ -1145,7 +1147,8 @@ def test_rv_generic(self, i, distdata): # some continuous distributions have trouble with `logentropy` because # it uses complex numbers assert_allclose(np.exp(X.logentropy()), Y.entropy(), rtol=rtol) - assert_allclose(X.median(), Y.median(), rtol=rtol) + if distname not in skip_median: + assert_allclose(X.median(), Y.median(), rtol=rtol) assert_allclose(X.mean(), m, rtol=rtol, atol=atol) assert_allclose(X.variance(), v, rtol=rtol, atol=atol) if distname not in skip_skewness: From 896031b09d026d3513439940f0379e89b014bfc2 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Thu, 24 Apr 2025 11:43:34 -0400 Subject: [PATCH 125/251] MAINT: Remove unnecessary parens mistakenly left in --- scipy/stats/_distribution_infrastructure.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index d97512a7df23..6460d9a5910b 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2677,7 +2677,7 @@ def _cdf2_subtraction_safe(self, x, y, **params): cdf_max = np.maximum(cdf_x, cdf_y) ccdf_max = np.maximum(ccdf_x, ccdf_y) spacing = np.spacing(np.where(i, ccdf_max, cdf_max)) - mask = (np.abs(tol * out) < spacing) + mask = np.abs(tol * out) < spacing if np.any(mask): params_mask = {key: np.broadcast_to(val, mask.shape)[mask] From 26f55f095efe42056e9e5cc9dd61d450e6efc90d Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Sat, 26 Apr 2025 20:08:56 -0400 Subject: [PATCH 126/251] MAINT: Remove unnecessary out_of_support in pdf/logpdf formulas --- scipy/stats/_distribution_infrastructure.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 6460d9a5910b..79e0b63d71f6 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3631,27 +3631,20 @@ def _overrides(self, method_name): return super()._overrides(method_name) def _logpdf_formula(self, x, **params): - logpmf = self.logpmf(x) - out_of_support = np.isneginf(logpmf) if params: p = next(iter(params.values())) nan_result = np.isnan(x) | np.isnan(p) else: nan_result = np.isnan(x) - return np.where(nan_result, np.nan, - np.where(out_of_support, -np.inf, np.inf))[()] + return np.where(nan_result, np.nan, np.inf) def _pdf_formula(self, x, **params): - logpmf = self.logpmf(x) - out_of_support = np.isneginf(logpmf) if params: p = next(iter(params.values())) nan_result = np.isnan(x) | np.isnan(p) else: nan_result = np.isnan(x) - return np.where(nan_result, np.nan, - np.where(out_of_support, 0.0, np.inf))[()] - + return np.where(nan_result, np.nan, np.inf) def _pxf_dispatch(self, x, *, method=None, **params): return self._pmf_dispatch(x, method=method, **params) From 5aad7ddfcfc4deac731bf86185c1a1d7e5a80645 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Sat, 26 Apr 2025 20:09:21 -0400 Subject: [PATCH 127/251] MAINT: Remove unnecessary order == 0 cases in moment formulas --- scipy/stats/_new_distributions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index e3edd702613e..0e1712895884 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -423,8 +423,6 @@ def _mode_formula(self, *, n, p, **kwargs): def _moment_raw_formula(self, order, *, n, p, **kwargs): # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments - if order == 0: - return np.ones_like(n) if order == 1: return n*p if order == 2: @@ -434,8 +432,6 @@ def _moment_raw_formula(self, order, *, n, p, **kwargs): def _moment_central_formula(self, order, *, n, p, **kwargs): # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments - if order == 0: - return np.ones_like(n) if order == 1: return np.zeros_like(n) if order == 2: From c3031b84804cf94b8948046c650814d1d83b3644 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:05:57 -0400 Subject: [PATCH 128/251] Get rid of excessively complex transform logic for x > y case --- scipy/stats/_distribution_infrastructure.py | 43 ++------------------- 1 file changed, 4 insertions(+), 39 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 79e0b63d71f6..b6bc4c369e7b 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -937,8 +937,7 @@ def _set_invalid_nan(f): clip = {'_cdf1', '_ccdf1'} clip_log = {'_logcdf1', '_logccdf1'} # relevant to discrete distributions only - replace_non_integral = {'pmf', 'logpmf'} - + replace_non_integral = {'pmf', 'logpmf', 'pdf', 'logpdf'} @functools.wraps(f) def filtered(self, x, *args, **kwargs): @@ -1000,10 +999,8 @@ def filtered(self, x, *args, **kwargs): # Check for non-integral arguments to PMF method # or PDF of a discrete distribution. any_non_integral = False - if ( - method_name in replace_non_integral - or discrete and method_name in {"pdf", "logpdf"}): - mask_non_integral = (x != np.floor(x)) & ~np.isnan(x) + if discrete and method_name in replace_non_integral: + mask_non_integral = (x != np.floor(x)) any_non_integral = (mask_non_integral if mask_non_integral.shape == () else np.any(mask_non_integral)) @@ -1186,27 +1183,6 @@ def wrapped(self, x, y, *args, **kwargs): # yes, copy to avoid modifying input arrays x, y = x.astype(dtype, copy=True), y.astype(dtype, copy=True) - # Swap arguments to ensure that x < y, and replace - # out-of domain arguments with domain endpoints. We'll - # transform the result later. - i_swap = y < x - x[i_swap], y[i_swap] = y[i_swap], x[i_swap] - - x_below = (x < low) - y_below = (y < low) - x_above = (x > high) - y_above = (y > high) - x[x_below] = low[x_below] - y[y_below] = low[y_below] - x[x_above] = high[x_above] - y[y_above] = high[y_above] - - # If x and y are both less than low (greater than high) - # than cdf2(x, y) should be 0 and replacing out-of-domain - # arguments with endpoints will give the wrong result. - both_beyond = (x_below & y_below) | (x_above & y_above) - degenerate = i_swap | both_beyond - res = f(self, x, y, *args, **kwargs) # Clipping probability to [0, 1] @@ -1214,18 +1190,7 @@ def wrapped(self, x, y, *args, **kwargs): res = np.clip(res, 0., 1.) else: res = np.clip(res, None, 0.) # exp(res) < 1 - - # Transform the result to account for swapped argument order - res = np.asarray(res) - if func_name == '_cdf2': - res[degenerate] = 0 - elif func_name == '_ccdf2': - res[degenerate] = 1 - elif func_name == '_logcdf2': - res[degenerate] = -np.inf - elif func_name == '_logccdf2': - res[degenerate] = 0 - return res[()] + return np.asarray(res)[()] return wrapped From 1c88ee2b9501b4994fed6fa9bda8c2b5a2628b13 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Thu, 1 May 2025 13:55:28 -0400 Subject: [PATCH 129/251] MAINT: Fix typo, fillvalue instead of fill_value --- scipy/stats/_distribution_infrastructure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index b6bc4c369e7b..75ea3adfb3d7 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3663,14 +3663,14 @@ def _ccdf2_addition(self, x, y, **params): a, _ = self._support(**params) ccdf_y = self._ccdf_dispatch(y, **params) _cdf, args = _kwargs2args(self._cdf_dispatch, kwargs=params) - cdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _cdf, fillvalue=0) + cdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _cdf, fill_value=0) return ccdf_y + cdf_xm1 def _logccdf2_addition(self, x, y, **params): a, _ = self._support(**params) logccdf_y = self._logccdf_dispatch(y, **params) _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) - logcdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _logcdf, fillvalue=-np.inf) + logcdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _logcdf, fill_value=-np.inf) return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) def _icdf_inversion(self, x, **params): From c00613bfb4e02f18e73303cff4c7376bd81a6350 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Thu, 1 May 2025 15:06:10 -0400 Subject: [PATCH 130/251] MAINT: Take order 0 out of list of moment formula orders --- scipy/stats/_new_distributions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 0e1712895884..7f60587bb78b 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -428,7 +428,7 @@ def _moment_raw_formula(self, order, *, n, p, **kwargs): if order == 2: return n*p*(1 - p + n*p) return None - _moment_raw_formula.orders = [0, 1, 2] # type: ignore[attr-defined] + _moment_raw_formula.orders = [1, 2] # type: ignore[attr-defined] def _moment_central_formula(self, order, *, n, p, **kwargs): # https://en.wikipedia.org/wiki/Binomial_distribution#Higher_moments @@ -441,7 +441,7 @@ def _moment_central_formula(self, order, *, n, p, **kwargs): if order == 4: return n*p*(1 - p)*(1 + (3*n - 6)*p*(1 - p)) return None - _moment_central_formula.orders = [0, 1, 2, 3, 4] # type: ignore[attr-defined] + _moment_central_formula.orders = [1, 2, 3, 4] # type: ignore[attr-defined] # Distribution classes need only define the summary and beginning of the extended From f7d002777fffdf7ed39e3fb7f4571e19e88eabc7 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Fri, 2 May 2025 19:03:42 -0400 Subject: [PATCH 131/251] MAINT: Ensure apply_where gets tuples not lists for args --- scipy/stats/_distribution_infrastructure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 75ea3adfb3d7..982e5961d2f8 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1219,7 +1219,7 @@ def _kwargs2args(f, args=None, kwargs=None): def wrapped(x, *args): return f(x, *args[:n_args], **dict(zip(names, args[n_args:]))) - args = list(args) + list(kwargs.values()) + args = tuple(args) + tuple(kwargs.values()) return wrapped, args @@ -3663,14 +3663,14 @@ def _ccdf2_addition(self, x, y, **params): a, _ = self._support(**params) ccdf_y = self._ccdf_dispatch(y, **params) _cdf, args = _kwargs2args(self._cdf_dispatch, kwargs=params) - cdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _cdf, fill_value=0) + cdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _cdf, fill_value=0) return ccdf_y + cdf_xm1 def _logccdf2_addition(self, x, y, **params): a, _ = self._support(**params) logccdf_y = self._logccdf_dispatch(y, **params) _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) - logcdf_xm1 = apply_where(x - 1 >= a, [x - 1] + args, _logcdf, fill_value=-np.inf) + logcdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _logcdf, fill_value=-np.inf) return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) def _icdf_inversion(self, x, **params): From e0998af09d0ffd58a8a0abb479a37b535d84d4c9 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Fri, 2 May 2025 19:04:04 -0400 Subject: [PATCH 132/251] MAINT: Use xp_promote to convert numerical endpoints to float type --- scipy/stats/_distribution_infrastructure.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 982e5961d2f8..e7c589954792 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -7,6 +7,7 @@ import numpy as np from numpy import inf +from scipy._lib._array_api import xp_promote from scipy._lib.array_api_extra import apply_where from scipy._lib._util import _rng_spawn, _RichResult from scipy._lib._docscrape import ClassDoc, NumpyDocString @@ -346,7 +347,8 @@ def get_numerical_endpoints(self, parameter_values): raise TypeError(message) from e # Floating point types are used for even integer parameters. # Convert to float here to ensure consistency throughout framework. - return a.astype(np.float64), b.astype(np.float64) + a, b = xp_promote(a, b, force_floating=True, xp=np) + return a, b def contains(self, item, parameter_values=None): r"""Determine whether the argument is contained within the domain. From 63d4adde7d5cd909f96c6594bf736fe7fc6e0432 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Fri, 2 May 2025 20:30:28 -0400 Subject: [PATCH 133/251] TST: Fix reference values for some cdf stuff --- scipy/stats/tests/test_continuous.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index c8e252cbbb11..bdb303474533 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -333,7 +333,9 @@ def test_subtraction_safe(self): # Safe subtraction is needed in special cases x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) - p0 = np.where(x > y, 0.0, X.pdf(0)*(y-x))[()] + p0 = np.asarray(X.pdf(0)*(y-x)) + p0[(x > y) & ~np.isnan(p0)] = 0.0 + p0 = p0[()] p1 = X.cdf(x, y, method='subtraction_safe') p2 = X.cdf(x, y, method='subtraction') assert_equal(p2, 0) @@ -515,7 +517,10 @@ def check_cdf2(dist, log, x, y, result_shape, methods): or dist._overrides('_logccdf_formula')): methods.add('log/exp') - ref = np.where(x > y, 0.0, dist.cdf(y) - dist.cdf(x) + dist.pmf(x))[()] + ref = np.asarray(dist.cdf(y) - dist.cdf(x) + dist.pmf(x)) + ref[~np.isnan(ref) & (x > y)] = 0.0 + ref = ref[()] + np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): @@ -543,8 +548,10 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): if dist._overrides(f'_{"log" if log else ""}ccdf2_formula'): methods.add('formula') - ref = np.where(x > y, 1.0, - dist.cdf(x) + dist.ccdf(y) - dist.pmf(x))[()] + ref = np.asarray(dist.cdf(x) + dist.ccdf(y) - dist.pmf(x)) + ref[~np.isnan(ref) & (x > y)] = 1.0 + ref = ref[()] + np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): From f81fb243095df05305941848bf82edee0f3d0ee6 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 5 May 2025 14:39:38 -0400 Subject: [PATCH 134/251] MAINT: Simplification and refactor for for discrete inversion --- scipy/stats/_distribution_infrastructure.py | 54 +++++++++++---------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index e7c589954792..5654338e48c7 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3675,38 +3675,42 @@ def _logccdf2_addition(self, x, y, **params): logcdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _logcdf, fill_value=-np.inf) return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) + def _base_discrete_inversion(self, p, func, comp, /, **params): + # For discrete distributions, icdf(p) is defined as the minimum n + # such that cdf(n) >= p. iccdf(p) is defined as the minimum n such + # that ccdf(n) <= p, or equivalently as iccdf(p) = icdf(1 - p). + + res = self._solve_bounded(func, p, params=params, xatol=0.9) + # First try to find where cdf(x) == p for the continuous extension of the + # cdf. res.xl and res.xr will be a bracket for this root. The parameter + # xatol in solve_bounded controls the bracket width. We thus know that + # know cdf(res.xr) >= p, cdf(res.xl) <= p, and |res.xr - res.xl| <= 0.9. + # This means the minimum integer n such that cdf(n) >= p is either floor(x) + # or floor(x) + 1. + res, fr = np.asarray(np.floor(res.xr)), res.fr + # xr is a bracket endpoint, and will usually be a finite value even when + # the computed result should be nan. We need to explicitly handle this + # case. + res[np.isnan(fr)] = np.nan + # comp should be <= for ccdf, >= for cdf. + res = np.where(comp(func(res, **params), p), res, res + 1.0) + return res[()] + def _icdf_inversion(self, x, **params): - # For discrete distributions, icdf(p) is defined as the minimum x - # such that cdf(x) >= p. - # np.nextafter(xatol - res = self._solve_bounded(self._cdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) - # If cdf(res) < x, then the true result must be one greater. - res = np.where(self.cdf(res) >= x, res, res + 1.0) - res[np.isnan(x)] = np.nan - return res + return self._base_discrete_inversion(x, self._cdf_dispatch, + np.greater_equal, **params) def _ilogcdf_inversion(self, x, **params): - # follows same logic as _icdf_inversion - res = self._solve_bounded(self._logcdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun >= 0, np.floor(res.x), np.ceil(res.x)) - res = np.where(self.logcdf(res) >= x, res, res + 1.0) - res[np.isnan(x)] = np.nan - return res + return self._base_discrete_inversion(x, self._logcdf_dispatch, + np.greater_equal, **params) def _iccdf_inversion(self, x, **params): - res = self._solve_bounded(self._ccdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun <= 0, np.floor(res.x), np.ceil(res.x)) - res = np.where(self.ccdf(res) <= x, res, res + 1.0) - res[np.isnan(x)] = np.nan - return res + return self._base_discrete_inversion(x, self._ccdf_dispatch, + np.less_equal, **params) def _ilogccdf_inversion(self, x, **params): - res = self._solve_bounded(self._logccdf_dispatch, x, params=params, xatol=0.9) - res = np.where(res.fun <= 0, np.floor(res.x), np.ceil(res.x)) - res = np.where(self.logccdf(res) <= x, res, res + 1.0) - res[np.isnan(x)] = np.nan - return res + return self._base_discrete_inversion(x, self._logccdf_dispatch, + np.less_equal, **params) def _mode_optimization(self, **params): # If `x` is the true mode of a unimodal continuous function, we can find From 263df7fc7075f0fb8e6e217a17f7ebd445bba0bd Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 5 May 2025 15:17:52 -0400 Subject: [PATCH 135/251] MAINT: Vectorize pmf call in discrete mode calculation --- scipy/stats/_distribution_infrastructure.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 5654338e48c7..0941e768b768 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3728,15 +3728,9 @@ def _mode_optimization(self, **params): # when clipping since `xo` will be one of `xl` or `xr`, but let's # keep the implementation simple for now. xo = np.clip(nearest + np.copysign(1, nearest - x), low, high) - fl = self._pmf_dispatch(xl, **params) - fr = self._pmf_dispatch(xr, **params) - fo = self._pmf_dispatch(xo, **params) - mode = np.asarray(xl) - comp1 = (fr > fl) - comp2 = (fo > fr) - mode[comp1 & ~comp2] = xr[comp1 & ~comp2] - mode[comp1 & comp2] = xo[comp1 & comp2] - return mode + x = np.stack([xl, xo, xr]) + idx = np.argmax(self._pmf_dispatch(x, **params), axis=0) + return np.choose(idx, [xl, xo, xr]) def _logentropy_quadrature(self, **params): def logintegrand(x, **params): From c90d2cd044d99ae5c20afa901932ea74b94bbae2 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 5 May 2025 16:21:18 -0400 Subject: [PATCH 136/251] MAINT: Remove unnecessary edge case handling --- scipy/stats/_distribution_infrastructure.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 0941e768b768..041979dfbaea 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3238,11 +3238,6 @@ def _moment_central_normalize(self, order, **params): if standard_moment is None: return None var = self._moment_central_dispatch(2, methods=self._moment_methods, **params) - if np.any(var == 0): - # In degenerate cases where var == 0, ``method='normalize'`` will - # not work. To keep things simple for now, don't use this method at all - # even if only one entry from an array valued ``var`` is zero. - return None return standard_moment*var**(order/2) def _moment_central_general(self, order, **params): From fbdb09bc514955853d91dd17bc2abf709a465dbd Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Mon, 5 May 2025 16:21:35 -0400 Subject: [PATCH 137/251] MAINT: Fix section that was mangled when resolving merge conflicts --- scipy/stats/_distribution_infrastructure.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 041979dfbaea..8f412f334b45 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -4223,11 +4223,12 @@ def _make_distribution_custom(dist): # the infrastructure. parameters = [] - for name, info in dist.parameters.items(): - domain = _RealInterval(endpoints=info['endpoints'], - inclusive=info['inclusive']) - param = _RealParameter(name, domain=domain) - parameters.append(param) + for name, info in dist.parameters.items(): + domain = _RealInterval(endpoints=info['endpoints'], + inclusive=info['inclusive']) + param = _RealParameter(name, domain=domain) + parameters.append(param) + parameterizations.append(_Parameterization(*parameters) if parameters else []) endpoints = dist.support["endpoints"] inclusive = dist.support.get("inclusive", (True, True)) From 5a1c2c9de7ca68b745d49f6a0fe3763edd2158b1 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 6 May 2025 15:06:27 -0400 Subject: [PATCH 138/251] MAINT: Use ugly fix _cdf2_input_validation --- scipy/stats/_distribution_infrastructure.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 8f412f334b45..cc98127b3174 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1184,15 +1184,32 @@ def wrapped(self, x, y, *args, **kwargs): dtype = np.result_type(x.dtype, y.dtype, self._dtype) # yes, copy to avoid modifying input arrays x, y = x.astype(dtype, copy=True), y.astype(dtype, copy=True) + all_ood = (x < low) & (y < low) | (x > high) & (y > high) + swap = x > y + + x[x < low] = low[x < low] + x[x > high] = high[x > high] + y[y > high] = high[y > high] + y[y < low] = low[y < low] res = f(self, x, y, *args, **kwargs) + if func_name in {'_cdf2', '_logccdf2'}: + fill_value = 0. + elif func_name == '_ccdf2': + fill_value = 1. + else: + fill_value = -np.inf + + res = np.asarray(res) + res[(all_ood | swap) & ~np.isnan(res)] = fill_value # Clipping probability to [0, 1] if func_name in {'_cdf2', '_ccdf2'}: res = np.clip(res, 0., 1.) else: res = np.clip(res, None, 0.) # exp(res) < 1 - return np.asarray(res)[()] + + return res return wrapped From 040fc0949834a76c0235405948e30c43981c2a90 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 6 May 2025 15:44:52 -0400 Subject: [PATCH 139/251] MAINT: Refector _cdf2_subtraction/safe --- scipy/stats/_distribution_infrastructure.py | 31 +++++++++++---------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index cc98127b3174..2fea625e4e85 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3649,29 +3649,32 @@ def _cdf2_quadrature(self, x, y, **params): def _logcdf2_quadrature(self, x, y, **params): return super()._logcdf2_quadrature(np.ceil(x), np.floor(y), **params) - def _cdf2_subtraction(self, x, y, **params): - x_, y_ = np.floor(x), np.floor(y) - cdf_ymx = super()._cdf2_subtraction(x_, y_, **params) - pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) - return cdf_ymx + pmf_x - - def _cdf2_subtraction_safe(self, x, y, **params): + def _cdf2_subtraction_base(self, x, y, func, **params): x_, y_ = np.floor(x), np.floor(y) - cdf_ymx = super()._cdf2_subtraction_safe(x_, y_, **params) + cdf_ymx = func(x_, y_, **params) pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) return cdf_ymx + pmf_x - def _logcdf2_subtraction(self, x, y, **params): + def _logcdf2_subtraction_base(self, x, y, func, **params): x_, y_ = np.floor(x), np.floor(y) - logcdf_ymx = super()._logcdf2_subtraction(x_, y_, **params) + logcdf_ymx = func(x_, y_, **params) logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) + def _cdf2_subtraction(self, x, y, **params): + return self._cdf2_subtraction_base(x, y, super()._cdf2_subtraction, **params) + + def _cdf2_subtraction_safe(self, x, y, **params): + return self._cdf2_subtraction_base(x, y, super()._cdf2_subtraction_safe, + **params) + + def _logcdf2_subtraction(self, x, y, **params): + return self._logcdf2_subtraction_base(x, y, super()._logcdf2_subtraction, + **params) + def _logcdf2_subtraction_safe(self, x, y, **params): - x_, y_ = np.floor(x), np.floor(y) - logcdf_ymx = super()._logcdf2_subtraction_safe(x_, y_, **params) - logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) - return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) + return self._logcdf2_subtraction_base(x, y, super()._logcdf2_subtraction, + **params) def _ccdf2_addition(self, x, y, **params): a, _ = self._support(**params) From c997fac6bb427f13f119ca7beb90124a2690e090 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 6 May 2025 16:49:52 -0400 Subject: [PATCH 140/251] MAINT: Change way of accessing one parameters array --- scipy/stats/_distribution_infrastructure.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 2fea625e4e85..09b20486afc1 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2085,12 +2085,13 @@ def _quadrature(self, integrand, limits=None, args=None, else: res = nsum(f, a, b, args=args, log=log, tolerances=dict(rtol=rtol)).sum res = np.asarray(res) - res[(a > b)] = -np.inf if log else 0 # fix in nsum? # The result should be nan when parameters are nan, so need to special # case this. - cond = np.isnan(next(iter(params.values()))) if params else np.True_ + cond = np.isnan(params.popitem()[1]) if params else np.True_ cond = np.broadcast_to(cond, a.shape) + res[(a > b)] = -np.inf if log else 0 # fix in nsum? res[cond] = np.nan + return res[()] def _solve_bounded(self, f, p, *, bounds=None, params=None, xatol=None): From e2281c9db9f94d445c602973cd4af1d7df8a68b0 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Tue, 6 May 2025 21:06:53 -0400 Subject: [PATCH 141/251] MAINT: Fix previous mistake resolving merge conflicts --- scipy/stats/_distribution_infrastructure.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 09b20486afc1..b24dc9fec1a7 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -4244,10 +4244,10 @@ def _make_distribution_custom(dist): # the infrastructure. parameters = [] - for name, info in dist.parameters.items(): - domain = _RealInterval(endpoints=info['endpoints'], - inclusive=info['inclusive']) - param = _RealParameter(name, domain=domain) + for name, info in parameterization.items(): + domain_info, typical = _get_domain_info(info) + domain = _RealInterval(**domain_info) + param = _RealParameter(name, domain=domain, typical=typical) parameters.append(param) parameterizations.append(_Parameterization(*parameters) if parameters else []) From 5b00e150b30765b6f79df8472ceabf24407cf042 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Wed, 7 May 2025 08:35:26 +0200 Subject: [PATCH 142/251] Small Improvement to docstr fo `signal._spectral_helper` --- scipy/signal/_spectral_py.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 1302040b9342..9e633ced38ff 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -2019,7 +2019,7 @@ def _spectral_helper(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, .. legacy:: function This function is soley used by the legacy functions `spectrogram` and `stft` - (which are also in this file). + (which are also in this same source file `scipy/signal/_spectral_py.py`). This is a helper function that implements the commonality between the stft, psd, csd, and spectrogram functions. It is not designed to From 0755cf3a54a632f026486fde528891d2d7a9cfd1 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 7 May 2025 09:49:51 +0200 Subject: [PATCH 143/251] MAINT: stats.DiscreteDistribution: fix mistake in merge / accidental reversion --- scipy/stats/_distribution_infrastructure.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index b24dc9fec1a7..00766faf7b7c 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -4251,10 +4251,8 @@ def _make_distribution_custom(dist): parameters.append(param) parameterizations.append(_Parameterization(*parameters) if parameters else []) - endpoints = dist.support["endpoints"] - inclusive = dist.support.get("inclusive", (True, True)) - - _x_support = _RealInterval(endpoints=endpoints, inclusive=inclusive) + domain_info, _ = _get_domain_info(dist.support) + _x_support = _RealInterval(**domain_info) _x_param = _RealParameter('x', domain=_x_support) repr_str = dist.__class__.__name__ From f3b64ef3b69c8077d719e602959d8b8eca1fe2de Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Wed, 7 May 2025 09:52:53 +0200 Subject: [PATCH 144/251] STY: stats.DiscreteDistribution: fix PEP8 line length --- scipy/stats/_distribution_infrastructure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 00766faf7b7c..24bb6903cfde 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3688,7 +3688,8 @@ def _logccdf2_addition(self, x, y, **params): a, _ = self._support(**params) logccdf_y = self._logccdf_dispatch(y, **params) _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) - logcdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _logcdf, fill_value=-np.inf) + args = (x - 1,) + args + logcdf_xm1 = apply_where(x - 1 >= a, args, _logcdf, fill_value=-np.inf) return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) def _base_discrete_inversion(self, p, func, comp, /, **params): From 66651606d726b111a8d7ba52d7a8aa0cabe62d5e Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Wed, 7 May 2025 10:51:06 +0100 Subject: [PATCH 145/251] MAINT: stats.qmc.Sobol: fix stacklevel of warning --- scipy/stats/_qmc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_qmc.py b/scipy/stats/_qmc.py index 69767a086840..32f06ad17114 100644 --- a/scipy/stats/_qmc.py +++ b/scipy/stats/_qmc.py @@ -1863,7 +1863,7 @@ def _random( # verify n is 2**n if not (n & (n - 1) == 0): warnings.warn("The balance properties of Sobol' points require" - " n to be a power of 2.", stacklevel=2) + " n to be a power of 2.", stacklevel=3) if n == 1: sample = self._first_point From 218d796fb8a0ee25841870aeff1f239f4c5c9077 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Wed, 7 May 2025 11:11:33 +0100 Subject: [PATCH 146/251] Merge pull request #22718 from crusaderky/tst_suppor_alternative_backends TST: special: overhaul test_support_alternative_backends --- .../special/_support_alternative_backends.py | 1 + .../test_support_alternative_backends.py | 212 +++++++++++++----- 2 files changed, 155 insertions(+), 58 deletions(-) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 65d98ac92059..74a1c4f32418 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -86,6 +86,7 @@ def __rel_entr(x, y, *, xp=xp): def _xlogy(xp, spx): def __xlogy(x, y, *, xp=xp): + x, y = xp_promote(x, y, force_floating=True, xp=xp) with np.errstate(divide='ignore', invalid='ignore'): temp = x * xp.log(y) return xp.where(x == 0., 0., temp) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 8f24a881344f..78e20046df9d 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -1,13 +1,15 @@ +from functools import partial from types import ModuleType - import pytest +from hypothesis import given, strategies +import hypothesis.extra.numpy as npst from scipy import special from scipy.special._support_alternative_backends import (get_array_special_func, array_special_func_map) from scipy._lib._array_api_no_0d import xp_assert_close from scipy._lib._array_api import (is_cupy, is_dask, is_jax, is_torch, - SCIPY_ARRAY_API, SCIPY_DEVICE) + xp_default_dtype, SCIPY_ARRAY_API, SCIPY_DEVICE) from scipy._lib.array_api_compat import numpy as np from scipy._lib.array_api_extra.testing import lazy_xp_function @@ -30,38 +32,18 @@ def test_dispatch_to_unrecognized_library(): xp_assert_close(res, ref) -@pytest.mark.skipif(not SCIPY_ARRAY_API, - reason="xp_promote won't accept non-numpy objects") -@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) -def test_rel_entr_generic(dtype): - xp = pytest.importorskip("array_api_strict") - f = get_array_special_func('rel_entr', xp=xp) - dtype_np = getattr(np, dtype) - dtype_xp = getattr(xp, dtype) - x = [-1, 0, 0, 1] - y = [1, 0, 2, 3] - - x_xp = xp.asarray(x, dtype=dtype_xp) - y_xp = xp.asarray(y, dtype=dtype_xp) - res = f(x_xp, y_xp) - - x_np = np.asarray(x, dtype=dtype_np) - y_np = np.asarray(y, dtype=dtype_np) - ref = special.rel_entr(x_np[-1], y_np[-1]) - ref = np.asarray([np.inf, 0, 0, ref], dtype=ref.dtype) - ref = xp.asarray(ref) - - xp_assert_close(res, ref) - +def _skip_or_tweak_alternative_backends(xp, f_name, dtypes): + """Skip tests for specific intersections of scipy.special functions + vs. backends vs. dtypes vs. devices. + Also suggest bespoke tweaks. -@pytest.mark.fail_slow(5) -# `reversed` is for developer convenience: test new function first = less waiting -@pytest.mark.parametrize('f_name,n_args', reversed(array_special_func_map.items())) -@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -@pytest.mark.parametrize('dtype', ['float32', 'float64']) -@pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, - [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) -def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): + Returns + ------- + positive_only : bool + Whether you should exclusively test positive inputs. + dtypes_np_ref : list[str] + The dtypes to use for the reference NumPy arrays. + """ if (SCIPY_DEVICE != 'cpu' and is_torch(xp) and f_name in {'stdtr', 'stdtrit', 'betaincc', 'betainc'} @@ -71,36 +53,72 @@ def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): if is_jax(xp) and f_name == "stdtrit": pytest.skip(f"`{f_name}` requires scipy.optimize support for immutable arrays") - shapes = shapes[:n_args] + if ((is_jax(xp) and f_name == 'gammaincc') # google/jax#20699 + # gh-20972 + or ((is_cupy(xp) or is_jax(xp) or is_torch(xp)) and f_name == 'chdtrc')): + positive_only = True + else: + positive_only = False + + if not any('int' in dtype for dtype in dtypes): + return positive_only, dtypes + + # Integer-specific issues from this point onwards + + if ((is_torch(xp) and f_name in {'gammainc', 'gammaincc'}) + or (is_cupy(xp) and f_name in {'stdtr', 'i0e', 'i1e'}) + or (is_jax(xp) and f_name in {'stdtr', 'ndtr', 'ndtri', 'log_ndtr'}) + ): + pytest.skip(f"`{f_name}` does not support integer types") + + # int/float mismatched args support is sketchy + if (any('float' in dtype for dtype in dtypes) + and ((is_torch(xp) and f_name in ('rel_entr', 'xlogy')) + or (is_jax(xp) and f_name in ('gammainc', 'gammaincc', + 'rel_entr', 'xlogy'))) + ): + pytest.xfail("dtypes do not match") + + dtypes_np_ref = dtypes + if (is_torch(xp) and xp_default_dtype(xp) == xp.float32 + and f_name not in {'betainc', 'betaincc', 'stdtr', 'stdtrit'} + ): + # On PyTorch with float32 default dtype, sometimes ints are promoted + # to float32, and sometimes to float64. + # When they are promoted to float32, explicitly convert the reference + # numpy arrays to float32 to prevent them from being automatically promoted + # to float64 instead. + dtypes_np_ref = ['float32' if 'int' in dtype else dtype for dtype in dtypes] + + return positive_only, dtypes_np_ref + + +@pytest.mark.parametrize('f_name,n_args', array_special_func_map.items()) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) +@pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, + [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) +def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): + positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( + xp, f_name, [dtype]) f = getattr(special, f_name) # Unwrapped fw = getattr(special_wrapped, f_name) # Wrapped by lazy_xp_function - dtype_np = getattr(np, dtype) dtype_xp = getattr(xp, dtype) - # # To test the robustness of the alternative backend's implementation, - # # use Hypothesis to generate arguments - # from hypothesis import given, strategies, reproduce_failure, assume - # import hypothesis.extra.numpy as npst - # @given(data=strategies.data()) - # mbs = npst.mutually_broadcastable_shapes(num_shapes=n_args) - # shapes, final_shape = data.draw(mbs) - # elements = dict(allow_subnormal=False) # consider min_value, max_value - # args_np = [np.asarray(data.draw(npst.arrays(dtype_np, shape, elements=elements)), - # dtype=dtype_np) - # for shape in shapes] - - # For CI, be a little more forgiving; just generate normally distributed arguments + shapes = shapes[:n_args] rng = np.random.default_rng(984254252920492019) - args_np = [rng.standard_normal(size=shape, dtype=dtype_np) for shape in shapes] - - if ((is_jax(xp) and f_name == 'gammaincc') # google/jax#20699 - # gh-20972 - or ((is_cupy(xp) or is_jax(xp) or is_torch(xp)) and f_name == 'chdtrc')): - args_np[0] = np.abs(args_np[0]) - args_np[1] = np.abs(args_np[1]) + if 'int' in dtype: + iinfo = np.iinfo(dtype_np) + rand = partial(rng.integers, iinfo.min, iinfo.max + 1) + else: + rand = rng.standard_normal + args_np = [rand(size=shape, dtype=dtype_np) for shape in shapes] + if positive_only: + args_np = [np.abs(arg) for arg in args_np] args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] + args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] if is_dask(xp): # We're using map_blocks to dispatch the function to Dask. @@ -110,10 +128,88 @@ def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): args_xp = [arg.rechunk(5) for arg in args_xp] res = fw(*args_xp) - ref = xp.asarray(f(*args_np), dtype=dtype_xp) + ref = f(*args_np) + + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.parametrize('f_name,n_args', + [(f_name, n_args) + for f_name, n_args in array_special_func_map.items() + if n_args >= 2]) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +def test_support_alternative_backends_mismatched_dtypes(xp, f_name, n_args): + """Test mix-n-match of int and float arguments""" + assert n_args <= 3 + dtypes = ['int64', 'float32', 'float64'][:n_args] + dtypes_xp = [xp.int64, xp.float32, xp.float64][:n_args] + positive_only, dtypes_np_ref = _skip_or_tweak_alternative_backends( + xp, f_name, dtypes) + f = getattr(special, f_name) + + rng = np.random.default_rng(984254252920492019) + iinfo = np.iinfo(np.int64) + randint = partial(rng.integers, iinfo.min, iinfo.max + 1) + args_np = [ + randint(size=1, dtype=np.int64), + rng.standard_normal(size=1, dtype=np.float32), + rng.standard_normal(size=1, dtype=np.float64), + ][:n_args] + if positive_only: + args_np = [np.abs(arg) for arg in args_np] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) + for arg, dtype_xp in zip(args_np, dtypes_xp)] + args_np = [np.asarray(arg, dtype=dtype_np_ref) + for arg, dtype_np_ref in zip(args_np, dtypes_np_ref)] + + res = f(*args_xp) + ref = f(*args_np) + + atol = 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) + + +@pytest.mark.xslow +@given(data=strategies.data()) +@pytest.mark.fail_slow(5) +# `reversed` is for developer convenience: test new function first = less waiting +@pytest.mark.parametrize('f_name,n_args', reversed(array_special_func_map.items())) +@pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings( + "ignore:overflow encountered:RuntimeWarning:array_api_strict" +) +def test_support_alternative_backends_hypothesis(xp, f_name, n_args, data): + dtype = data.draw(strategies.sampled_from(['float32', 'float64', 'int64'])) + positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( + xp, f_name, [dtype]) + f = getattr(special, f_name) + dtype_np = getattr(np, dtype) + dtype_xp = getattr(xp, dtype) + + elements = {'allow_subnormal': False} + # Most failures are due to NaN or infinity; uncomment to suppress them + # elements['allow_infinity'] = False + # elements['allow_nan'] = False + if positive_only: + elements['min_value'] = 0 + + shapes, _ = data.draw(npst.mutually_broadcastable_shapes(num_shapes=n_args)) + args_np = [data.draw(npst.arrays(dtype_np, shape, elements=elements)) + for shape in shapes] + + args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] + args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] + + res = f(*args_xp) + ref = f(*args_np) - eps = np.finfo(dtype_np).eps - xp_assert_close(res, ref, atol=10*eps) + # When dtype_np is integer, the output dtype can be float + atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps + xp_assert_close(res, xp.asarray(ref), atol=atol) def test_chdtr_gh21311(xp): From a2fd0413b5c97e4062fa8564a0993e808b914782 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 14:55:19 -0400 Subject: [PATCH 147/251] Revert "MAINT: Update cdf2 and friends to use probability definition" This reverts commit 02819acf234f0e01c41c983594a52bcd6443239e. --- scipy/stats/_distribution_infrastructure.py | 44 +++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index b24dc9fec1a7..a0f4222e37cd 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -1184,24 +1184,22 @@ def wrapped(self, x, y, *args, **kwargs): dtype = np.result_type(x.dtype, y.dtype, self._dtype) # yes, copy to avoid modifying input arrays x, y = x.astype(dtype, copy=True), y.astype(dtype, copy=True) - all_ood = (x < low) & (y < low) | (x > high) & (y > high) - swap = x > y - x[x < low] = low[x < low] - x[x > high] = high[x > high] - y[y > high] = high[y > high] - y[y < low] = low[y < low] + # Swap arguments to ensure that x < y, and replace + # out-of domain arguments with domain endpoints. We'll + # transform the result later. + i_swap = y < x + x[i_swap], y[i_swap] = y[i_swap], x[i_swap] + i = x < low + x[i] = low[i] + i = y < low + y[i] = low[i] + i = x > high + x[i] = high[i] + i = y > high + y[i] = high[i] res = f(self, x, y, *args, **kwargs) - if func_name in {'_cdf2', '_logccdf2'}: - fill_value = 0. - elif func_name == '_ccdf2': - fill_value = 1. - else: - fill_value = -np.inf - - res = np.asarray(res) - res[(all_ood | swap) & ~np.isnan(res)] = fill_value # Clipping probability to [0, 1] if func_name in {'_cdf2', '_ccdf2'}: @@ -1209,7 +1207,21 @@ def wrapped(self, x, y, *args, **kwargs): else: res = np.clip(res, None, 0.) # exp(res) < 1 - return res + # Transform the result to account for swapped argument order + res = np.asarray(res) + if func_name == '_cdf2': + res[i_swap] *= -1. + elif func_name == '_ccdf2': + res[i_swap] *= -1 + res[i_swap] += 2. + elif func_name == '_logcdf2': + res = np.asarray(res + 0j) if np.any(i_swap) else res + res[i_swap] = res[i_swap] + np.pi*1j + else: + # res[i_swap] is always positive and less than 1, so it's + # safe to ensure that the result is real + res[i_swap] = _logexpxmexpy(np.log(2), res[i_swap]).real + return res[()] return wrapped From 0b7ff412d58686b02c4b397a8c5bff61ffa306c1 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 14:57:21 -0400 Subject: [PATCH 148/251] Revert "TST: Update tests in response to changes in cdf2 and friends" This reverts commit e171a151fd4340ef04bcda36bc2e1fe00d9a2523. --- scipy/stats/tests/test_continuous.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index bdb303474533..d570ff70f7c5 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -333,9 +333,8 @@ def test_subtraction_safe(self): # Safe subtraction is needed in special cases x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) - p0 = np.asarray(X.pdf(0)*(y-x)) - p0[(x > y) & ~np.isnan(p0)] = 0.0 - p0 = p0[()] + + p0 = X.pdf(0)*(y-x) p1 = X.cdf(x, y, method='subtraction_safe') p2 = X.cdf(x, y, method='subtraction') assert_equal(p2, 0) @@ -517,10 +516,7 @@ def check_cdf2(dist, log, x, y, result_shape, methods): or dist._overrides('_logccdf_formula')): methods.add('log/exp') - ref = np.asarray(dist.cdf(y) - dist.cdf(x) + dist.pmf(x)) - ref[~np.isnan(ref) & (x > y)] = 0.0 - ref = ref[()] - + ref = dist.cdf(y) - dist.cdf(x) np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): @@ -548,10 +544,7 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): if dist._overrides(f'_{"log" if log else ""}ccdf2_formula'): methods.add('formula') - ref = np.asarray(dist.cdf(x) + dist.ccdf(y) - dist.pmf(x)) - ref[~np.isnan(ref) & (x > y)] = 1.0 - ref = ref[()] - + ref = dist.cdf(x) + dist.ccdf(y) np.testing.assert_equal(ref.shape, result_shape) if result_shape == tuple(): From 657adc8ae661a14d1855e2bb25c925e3c2e22f05 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 14:57:37 -0400 Subject: [PATCH 149/251] Revert "DOC: Update definition in docs for two arg ccdf" This reverts commit 29adf2b8f59148ec8e38596a0825bd2bceb44c48. --- scipy/stats/_probability_distribution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_probability_distribution.py b/scipy/stats/_probability_distribution.py index ff2343bd77bf..0e3c915f8cc9 100644 --- a/scipy/stats/_probability_distribution.py +++ b/scipy/stats/_probability_distribution.py @@ -1236,7 +1236,7 @@ def ccdf(self, x, y, /, *, method): .. math:: - G(x, y) = 1 - F(x, y) = P(\text{not } x \leq X \leq y) + G(x, y) = 1 - F(x, y) = P(X < x \text{ or } X > y) `ccdf` accepts `x` for :math:`x` and `y` for :math:`y`. From 724f6a425ceb3c014594eb7dc7e7df84fa5d6f6e Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 15:02:22 -0400 Subject: [PATCH 150/251] Revert "TST: Fix reference values for some cdf stuff" This reverts commit 63d4adde7d5cd909f96c6594bf736fe7fc6e0432. --- scipy/stats/tests/test_continuous.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index d570ff70f7c5..eec45023f92f 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -334,6 +334,7 @@ def test_subtraction_safe(self): x = np.asarray([-1e-20, -1e-21, 1e-20, 1e-21, -1e-20]) y = np.asarray([-1e-21, -1e-20, 1e-21, 1e-20, 1e-20]) + p0 = X.pdf(0)*(y-x) p1 = X.cdf(x, y, method='subtraction_safe') p2 = X.cdf(x, y, method='subtraction') From 52f27161fe30651d824794fd6a3ca8213587ace8 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 20:09:29 -0400 Subject: [PATCH 151/251] MAINT: Make two argument cdf raise for discrete distributions --- scipy/stats/_distribution_infrastructure.py | 59 ++++++--------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index a0f4222e37cd..b16321ccb3d8 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3656,52 +3656,25 @@ def _ccdf_quadrature(self, x, **params): def _logccdf_quadrature(self, x, **params): return super()._logccdf_quadrature(np.floor(x + 1), **params) - def _cdf2_quadrature(self, x, y, **params): - return super()._cdf2_quadrature(np.ceil(x), np.floor(y), **params) - - def _logcdf2_quadrature(self, x, y, **params): - return super()._logcdf2_quadrature(np.ceil(x), np.floor(y), **params) - - def _cdf2_subtraction_base(self, x, y, func, **params): - x_, y_ = np.floor(x), np.floor(y) - cdf_ymx = func(x_, y_, **params) - pmf_x = np.where(x_ == x, self._pmf_dispatch(x_, **params), 0) - return cdf_ymx + pmf_x - - def _logcdf2_subtraction_base(self, x, y, func, **params): - x_, y_ = np.floor(x), np.floor(y) - logcdf_ymx = func(x_, y_, **params) - logpmf_x = np.where(x_ == x, self._logpmf_dispatch(x_, **params), -np.inf) - return special.logsumexp([logcdf_ymx, logpmf_x], axis=0) - - def _cdf2_subtraction(self, x, y, **params): - return self._cdf2_subtraction_base(x, y, super()._cdf2_subtraction, **params) - - def _cdf2_subtraction_safe(self, x, y, **params): - return self._cdf2_subtraction_base(x, y, super()._cdf2_subtraction_safe, - **params) - - def _logcdf2_subtraction(self, x, y, **params): - return self._logcdf2_subtraction_base(x, y, super()._logcdf2_subtraction, - **params) + def _cdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") - def _logcdf2_subtraction_safe(self, x, y, **params): - return self._logcdf2_subtraction_base(x, y, super()._logcdf2_subtraction, - **params) + def _ccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") - def _ccdf2_addition(self, x, y, **params): - a, _ = self._support(**params) - ccdf_y = self._ccdf_dispatch(y, **params) - _cdf, args = _kwargs2args(self._cdf_dispatch, kwargs=params) - cdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _cdf, fill_value=0) - return ccdf_y + cdf_xm1 + def _logcdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") - def _logccdf2_addition(self, x, y, **params): - a, _ = self._support(**params) - logccdf_y = self._logccdf_dispatch(y, **params) - _logcdf, args = _kwargs2args(self._logcdf_dispatch, kwargs=params) - logcdf_xm1 = apply_where(x - 1 >= a, (x - 1,) + args, _logcdf, fill_value=-np.inf) - return special.logsumexp([logccdf_y, logcdf_xm1], axis=0) + def _logccdf2(self, x, y, *, method): + raise NotImplementedError( + "Two argument cdf functions are currently only supported for " + "continuous distributions.") def _base_discrete_inversion(self, p, func, comp, /, **params): # For discrete distributions, icdf(p) is defined as the minimum n From 47ea9431cc1cb76680f8be417e02bfb943aa7fa0 Mon Sep 17 00:00:00 2001 From: steppi <1953382+steppi@users.noreply.github.com> Date: Wed, 7 May 2025 21:22:24 -0400 Subject: [PATCH 152/251] TST: Test that two arg cdf functions for discrete distributions raise --- scipy/stats/tests/test_continuous.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index eec45023f92f..5132d54eebf8 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -524,6 +524,13 @@ def check_cdf2(dist, log, x, y, result_shape, methods): assert np.isscalar(ref) for method in methods: + if isinstance(dist, DiscreteDistribution): + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logcdf(x, y, method=method)) if log + else dist.cdf(x, y, method=method)) + continue res = (np.exp(dist.logcdf(x, y, method=method)) if log else dist.cdf(x, y, method=method)) np.testing.assert_allclose(res, ref, atol=1e-14) @@ -552,6 +559,13 @@ def check_ccdf2(dist, log, x, y, result_shape, methods): assert np.isscalar(ref) for method in methods: + message = ("Two argument cdf functions are currently only supported for " + "continuous distributions.") + if isinstance(dist, DiscreteDistribution): + with pytest.raises(NotImplementedError, match=message): + res = (np.exp(dist.logccdf(x, y, method=method)) if log + else dist.ccdf(x, y, method=method)) + continue res = (np.exp(dist.logccdf(x, y, method=method)) if log else dist.ccdf(x, y, method=method)) np.testing.assert_allclose(res, ref, atol=1e-14) From d1040d6589ef7fa60eb4461034e848368e6dd529 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 8 May 2025 11:26:20 +0200 Subject: [PATCH 153/251] STY: stats.DiscreteDistribution: fix PEP8 --- scipy/stats/_distribution_infrastructure.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index cd5ff1d44443..13b8380c5a95 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -8,7 +8,6 @@ from numpy import inf from scipy._lib._array_api import xp_promote -from scipy._lib.array_api_extra import apply_where from scipy._lib._util import _rng_spawn, _RichResult from scipy._lib._docscrape import ClassDoc, NumpyDocString from scipy import special, stats From 6986ab6265939228585febabf5abebe505b3576f Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 8 May 2025 11:40:12 +0200 Subject: [PATCH 154/251] MAINT: stats.Binomial: make p exclusive of endpoints --- scipy/stats/_new_distributions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 7f60587bb78b..63143c1eb70a 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -378,7 +378,7 @@ class Binomial(DiscreteDistribution): """ _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(True, False)) - _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(True, True)) + _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(False, False)) _x_support = _IntegerInterval(endpoints=(0, 'n'), inclusive=(True, True)) _n_param = _RealParameter('n', domain=_n_domain, typical=(10, 20)) From 8baa14c73f5e006ac0e920fb403d7ae43897ecf9 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 8 May 2025 11:48:07 +0200 Subject: [PATCH 155/251] MAINT: stats.Binomial: exclude n=0 degenerate distribution --- scipy/stats/_new_distributions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/_new_distributions.py b/scipy/stats/_new_distributions.py index 63143c1eb70a..96257801a0d2 100644 --- a/scipy/stats/_new_distributions.py +++ b/scipy/stats/_new_distributions.py @@ -377,7 +377,7 @@ class Binomial(DiscreteDistribution): f(x) = {n \choose x} p^x (1 - p)^{n-x} """ - _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(True, False)) + _n_domain = _IntegerInterval(endpoints=(0, inf), inclusive=(False, False)) _p_domain = _RealInterval(endpoints=(0, 1), inclusive=(False, False)) _x_support = _IntegerInterval(endpoints=(0, 'n'), inclusive=(True, True)) From b3c199349fd5cc424dbbaa77f3b615881156a666 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Thu, 8 May 2025 14:51:26 +0200 Subject: [PATCH 156/251] MAINT: signal.resample: Improve docstr examples [docs only] --- scipy/signal/_signaltools.py | 172 ++++++++++++++++++++++++----------- 1 file changed, 121 insertions(+), 51 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index b7cffac57305..578533cd1ae8 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -3506,7 +3506,7 @@ def invresz(r, p, k, tol=1e-3, rtype='avg'): def resample(x, num, t=None, axis=0, window=None, domain='time'): - """Resample `x` to `num` samples using the Fourier method along the given `axis`. + r"""Resample `x` to `num` samples using the Fourier method along the given `axis`. The resampling is performed by shortening or zero-padding the FFT of `x`. This has the advantages of providing an ideal antialiasing filter and allowing arbitrary @@ -3538,7 +3538,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): ``X = W * fft(x, axis=axis)``. ``W`` may be interpreted as a spectral windowing function ``W(f_X)`` which consumes the frequencies ``f_X = fftfreq(n_x, T)``. - If `window` is a 1d array of length `n_x` then ``W=window``. + If `window` is a 1d array of length ``n_x`` then ``W=window``. If `window` is a callable then ``W = window(f_X)``. Otherwise, `window` is passed to `~scipy.signal.get_window`, i.e., ``W = fftshift(signal.get_window(window, n_x))``. Default is ``None``. @@ -3617,62 +3617,132 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): >>> ax0.grid(True) >>> plt.show() - - The following example illustrates that a `~scipy.fft.rfft` / `~scipy.fft.irfft` - combination does not always produce the correct resampling result, while - `resample` does: - + The following example compares this function with a naive `~scipy.fft.rfft` / + `~scipy.fft.irfft` combination: An input signal with a sampling interval of one + second is upsampled by a factor of eight. The first figure depicts an odd number of + input samples whereas the second figure an even number. The upper subplots show the + signals over time: The input samples are marked by large green dots, the upsampled + signals by a continuous and a dashed line. The lower subplots show the magnitude + spectrum: The FFT values of the input are depicted by large green dots, which lie + in the frequency interval [-0.5, 0.5] Hz, whereas the frequency interval of the + upsampled signal is [-4, 4] Hz. The continuous green line depicts the upsampled + spectrum without antialiasing filter, which is a periodic continuation of the input + spectrum. The blue x's and orange dots depict the FFT values of the signal created + by the naive approach as well as this function's result. + + >>> import matplotlib.pyplot as plt >>> import numpy as np - >>> from scipy.fft import irfft, fft, fftfreq, rfft - >>> from scipy.signal import resample - ... - >>> n0, n1 = 8, 4 # number of samples - >>> t0, t1 = (np.linspace(0, 2, n_, endpoint=False) for n_ in (n0, n1)) # in s - >>> x0, x1 = (np.cos(2*np.pi*t_) for t_ in (t0, t1)) # 1 Hz cosine signal - >>> y1_r = resample(x0, num=n1) - >>> np.allclose(y1_r, x1) # correct result, as expected - True - >>> y1_f = irfft(rfft(x0), n=n1) * n1/n0 - >>> np.allclose(y1_f, x1) # wrong result - False - >>> # Compare FFTs: - >>> fftfreq(n0, t0[1]-t0[0]) # frequencies of fft(x0) - array([ 0. , 0.5, 1. , 1.5, -2. , -1.5, -1. , -0.5]) - >>> np.round(fft(x0), 3) # FFT of x0 - array([-0.-0.j, -0.+0.j, 4.-0.j, 0.+0.j, 0.-0.j, 0.-0.j, 4.+0.j, -0.-0.j]) - >>> fftfreq(n1, t1[1]-t1[0]) # frequencies of fft(x1) and fft(y1_f) - array([ 0. , 0.5, -1. , -0.5]) - >>> np.round(fft(x1), 3) # reference FFT - array([0.+0.j, 0.+0.j, 4.+0.j, 0.+0.j]) - >>> np.round(fft(y1_f), 3) # irfft/rfft off by factor 2 in the Nyuist frequency - array([0.+0.j, 0.+0.j, 2.+0.j, 0.+0.j]) - - The reason for the different results lies in `resample` correctly treating unpaired - frequency bins. I.e., the input `x1` has a bin pair ±1 Hz, whereas the output has - only one unpaired bin at -1 Hz, which demands rescaling of that bin. Special - treatment is required if ``n_x != num`` and ``min(n_x, num)`` is even. If the bin - values at `±m` are zero, the, obviously, no special treatment is needed. Consult - the source code of `resample` for details. - - The following code snippet shows how to use `resample_poly` to speed up the - down-sampling: + >>> from scipy.fft import fftshift, fftfreq, fft, rfft, irfft + >>> from scipy.signal import resample, resample_poly + ... + >>> fac, T0, T1 = 8, 1, 1/8 # upsampling factor and sampling intervals + >>> for n0 in (15, 16): # number of samples of input signal + ... n1 = fac * n0 # number of samples of upsampled signal + ... t0, t1 = T0 * np.arange(n0), T1 * np.arange(n1) # time stamps + ... x0 = np.zeros(n0) # input signal has two non-zero sample values + ... x0[n0//2], x0[n0//2+1] = n0 // 2, -(n0 // 2) + ... + ... x1n = irfft(rfft(x0), n=n1) * n1 / n0 # naive resampling + ... x1r = resample(x0, n1) # resample signal + ... + ... # Determine magnitude spectrum: + ... x0_up = np.zeros_like(x1r) # upsampling without antialiasing filter + ... x0_up[::n1 // n0] = x0 + ... X0, X0_up = (fftshift(fft(x_)) / n0 for x_ in (x0, x0_up)) + ... XX1 = (fftshift(fft(x_)) / n1 for x_ in (x1n, x1r)) + ... f0, f1 = fftshift(fftfreq(n0, T0)), fftshift(fftfreq(n1, T1)) # frequencies + ... df = f0[1] - f0[0] # frequency resolution + ... + ... fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + ... ax0.set_title(rf"Upsampling ${fac}\times$ from {n0} to {n1} samples") + ... ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", + ... xlim=(0, n1*T1)) + ... ax0.step(t0, x0, 'C2o-', where='post', alpha=.3, linewidth=2, + ... label="$x_0(t)$ / $X_0(f)$") + ... for x_, l_ in zip((x1n, x1r), ('C0--', 'C1-')): + ... ax0.plot(t1, x_, l_, alpha=.5, label=None) + ... ax0.grid() + ... ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {df*1e3:.1f}\,$mHz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(-0.7, 0.7)) + ... ax1.axvspan(0.5/T0, f1[-1], color='gray', alpha=.2) + ... ax1.axvspan(f1[0], -0.5/T0, color='gray', alpha=.2) + ... ax1.plot(f1, abs(X0_up), 'C2-', f0, abs(X0), 'C2o', alpha=.3, linewidth=2) + ... for X_, n_, l_ in zip(XX1, ("naive", "resample"), ('C0x--', 'C1.-')): + ... ax1.plot(f1, abs(X_), l_, alpha=.5, label=n_) + ... ax1.grid() + ... fig.legend(loc='outside lower center', ncols=4) + >>> plt.show() + + The first figure shows that upsampling an odd number of samples produces identical + results. The second figure illustrates that the signal produced with the naive + approach (dashed blue line) from an even number of samples does not touch all + original samples. This deviation is due to `resample` correctly treating unpaired + frequency bins. I.e., the input `x1` has a bin pair ±0.5 Hz, whereas the output has + only one unpaired bin at -0.5 Hz, which demands rescaling of that bin pair. + Generally, special treatment is required if ``n_x != num`` and ``min(n_x, num)`` is + even. If the bin values at `±m` are zero, obviously, no special treatment is + needed. Consult the source code of `resample` for details. + + The final example shows how to utilize `resample_poly` to speed up the + down-sampling: The input signal a non-zero value at :math:`t=0` and is downsampled + from 19937 to 128 samples. Since 19937 is prime, the FFT is expected to be slow. To + speed matters up, `resample_poly` is used to downsample first by a factor of ``n0 + // n1 = 155`` and then pass the result to `resample`. Two parameterization of + `resample_poly` are used: Passing ``padtype='wrap'`` treats the input as being + periodic wheras the default parametrization performs zero-padding. The upper + subplot shows the resulting signals over time whereas the lower subplot depicts the + resulting one-sided magnitude spectra. + >>> import matplotlib.pyplot as plt >>> import numpy as np + >>> from scipy.fft import rfftfreq, rfft >>> from scipy.signal import resample, resample_poly - ... + ... >>> n0 = 19937 # number of input samples - prime >>> n1 = 128 # number of output samples - fast FFT length - >>> x0 = np.random.rand(n0) # input signal - ... - >>> y1 = resample(x0, n1) # slow due to n0 being prime + >>> T0, T1 = 1/n0, 1/n1 # sampling intervals + >>> t0, t1 = np.arange(n0)*T0, np.arange(n1)*T1 # time stamps + ... + >>> x0 = np.zeros(n0) # Input has one non-zero sample + >>> x0[0] = n0 + >>> + >>> x1r = resample(x0, n1) # slow due to n0 being prime >>> # This is faster: - >>> y1_p = resample(resample_poly(x0, 1, n0 // n1, padtype='wrap'), n1) - - Note that the `y1` and `y1_p` are not identical due to the differences in the - utilized antialiasing filter in each function. In both functions the antialiasing - filter can be customized by modifying the `window` parameter, though `resample` - interprets it as a spectral window function, whereas `resample_poly` assumes it to - be some form of impulse response. + >>> x1p = resample(resample_poly(x0, 1, n0 // n1, padtype='wrap'), n1) # periodic + >>> x2p = resample(resample_poly(x0, 1, n0 // n1), n1) # with zero-padding + ... + >>> X0 = rfft(x0) / n0 + >>> X1r, X1p, X2p = rfft(x1r) / n1, rfft(x1p) / n1, rfft(x2p) / n1 + >>> f0, f1 = rfftfreq(n0, T0), rfftfreq(n1, T1) + ... + >>> fig, (ax0, ax1) = plt.subplots(2, 1, layout='constrained', figsize=(5, 4)) + >>> ax0.set_title(f"Dowsampled Impulse response (from {n0} to {n1} samples)") + >>> ax0.set(xlabel="Time $t$ in seconds", ylabel="Amplitude $x(t)$", xlim=(-T1, 1)) + >>> for x_ in (x1r, x1p, x2p): + ... ax0.plot(t1, x_, alpha=.5) + >>> ax0.grid() + >>> ax1.set(xlabel=rf"Frequency $f$ in hertz ($\Delta f = {f1[1]}\,$Hz)", + ... ylabel="Magnitude $|X(f)|$", xlim=(0, 0.55/T1)) + >>> ax1.axvspan(0.5/T1, f0[-1], color='gray', alpha=.2) + >>> ax1.plot(f1, abs(X1r), 'C0.-', alpha=.5, label="resample") + >>> ax1.plot(f1, abs(X1p), 'C1.-', alpha=.5, label="resample_poly(padtype='wrap')") + >>> ax1.plot(f1, abs(X2p), 'C2x-', alpha=.5, label="resample_poly") + >>> ax1.grid() + >>> fig.legend(loc='outside lower center', ncols=2) + >>> plt.show() + + The plots show that the results of the "pure" `resample` and the usage of the + default parameters of `resample_poly` agree well. The periodic padding of + `resample_poly` (``padtype='wrap'``) on the other hand produces significant + deviations. This is caused by the disconiuity at the beginning of the signal, for + which the default filter of `resample_poly` is not suited well. This example + illustrates that for some use cases, adpating the `resample_poly` parameters may + be beneficial. `resample` has a big advantage in this regard: It uses the ideal + antialiasing filter with the maximum bandwidth by default. + + Note that the doubled spectral magnitude at the Nyqist frequency of 64 Hz is due the + even number of ``n1=128`` output samples, which requires a special treatment as + discussed in the previous example. """ if domain not in ('time', 'freq'): raise ValueError(f"Parameter {domain=} not in ('time', 'freq')!") From 1ece50bf2c62b47e9ca7177e3f62b9fd79daa021 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Thu, 8 May 2025 15:07:04 +0100 Subject: [PATCH 157/251] MAINT: refresh gpu-ci pixi.lock --- .github/workflows/pixi.lock | 5502 ++++++++++++++--------------------- environment.yml | 2 +- 2 files changed, 2162 insertions(+), 3342 deletions(-) diff --git a/.github/workflows/pixi.lock b/.github/workflows/pixi.lock index 6778e2f838ea..02d032b5278b 100644 --- a/.github/workflows/pixi.lock +++ b/.github/workflows/pixi.lock @@ -1,124 +1,124 @@ -version: 5 +version: 6 environments: array-api: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -127,199 +127,202 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda array-api-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-hc2c308b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -328,40 +331,42 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda array-api-strict: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -369,95 +374,95 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -466,32 +471,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda cupy: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -499,109 +504,109 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -610,32 +615,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda default: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -643,94 +648,94 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -739,32 +744,32 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda jax: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -772,104 +777,103 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -878,34 +882,34 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda jax-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ @@ -913,131 +917,130 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1046,144 +1049,145 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda torch: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1192,171 +1196,179 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda torch-cuda: channels: - url: https://conda.anaconda.org/conda-forge/ packages: linux-64: - - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda @@ -1365,57 +1377,49 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda packages: -- kind: conda - name: _libgcc_mutex - version: '0.1' - build: conda_forge - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726 md5: d7c89558ba9fa0495403155b64376d81 license: None size: 2562 timestamp: 1578324546067 -- kind: conda - name: _openmp_mutex - version: '4.5' - build: 2_gnu +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22 md5: 73aaf86a425cc6e73fcf236a5a46396d depends: @@ -1427,46 +1431,27 @@ packages: license_family: BSD size: 23621 timestamp: 1650670423406 -- kind: conda - name: _openmp_mutex - version: '4.5' - build: 2_kmp_llvm - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_kmp_llvm.tar.bz2 - sha256: 84a66275da3a66e3f3e70e9d8f10496d807d01a9e4ec16cd2274cc5e28c478fc - md5: 562b26ba2e19059551a811e72ab7f793 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-3_kmp_llvm.conda + build_number: 3 + sha256: cec7343e76c9da6a42c7e7cba53391daa6b46155054ef61a5ef522ea27c5a058 + md5: ee5c2118262e30b972bc0b4db8ef0ba5 depends: - - _libgcc_mutex 0.1 conda_forge - llvm-openmp >=9.0.1 license: BSD-3-Clause license_family: BSD - size: 5744 - timestamp: 1650742457817 -- kind: conda - name: array-api-strict - version: '2.2' - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.2-pyhd8ed1ab_1.conda - sha256: 79bf4d2b5f55c816f832cd7180e66ca527b55a8353a3014fe3084690a8c7f6aa - md5: 02e7a32986412d3aaf97095d17120757 + size: 7649 + timestamp: 1741390353130 +- conda: https://conda.anaconda.org/conda-forge/noarch/array-api-strict-2.3.1-pyhd8ed1ab_0.conda + sha256: fda42d9e952c4c39354e31d43f1b7e7708a2e66c386074cd995097fe98be9150 + md5: 11107d0aeb8c590a34fee0894909816b depends: - numpy - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 53675 - timestamp: 1734907462139 -- kind: conda - name: attr - version: 2.5.1 - build: h166bdaf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 + size: 56647 + timestamp: 1742521671631 +- conda: https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2 sha256: 82c13b1772c21fc4a17441734de471d3aabf82b61db9b11f4a1bd04a9c4ac324 md5: d9c69a24ad678ffce24c6543a0176b00 depends: @@ -1475,29 +1460,16 @@ packages: license_family: GPL size: 71042 timestamp: 1660065501192 -- kind: conda - name: attrs - version: 24.3.0 - build: pyh71513ae_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/attrs-24.3.0-pyh71513ae_0.conda - sha256: 750186af694a7130eaf7119fbb56db0d2326d8995ad5b8eae23c622b85fea29a - md5: 356927ace43302bf6f5926e2a58dae6a +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-25.3.0-pyh71513ae_0.conda + sha256: 99c53ffbcb5dc58084faf18587b215f9ac8ced36bbfb55fa807c00967e419019 + md5: a10d11958cadc13fdb43df75f8b1903f depends: - python >=3.9 license: MIT license_family: MIT - size: 56354 - timestamp: 1734348889193 -- kind: conda - name: beniget - version: 0.4.2.post1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda + size: 57181 + timestamp: 1741918625732 +- conda: https://conda.anaconda.org/conda-forge/noarch/beniget-0.4.2.post1-pyhd8ed1ab_1.conda sha256: f1d0f6d3170524357cdc5c05594d3f10dde6308cd11b6d7a321221677223260e md5: a4c67d46a8662fc39609648dee4938c3 depends: @@ -1507,98 +1479,64 @@ packages: license_family: BSD size: 21702 timestamp: 1733845912915 -- kind: conda - name: binutils - version: '2.43' - build: h4852527_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_2.conda - sha256: 92be0f8ccd501ceeb3c782e2182e6ea04dca46799038176de40a57bca45512c5 - md5: 348619f90eee04901f4a70615efff35b +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils-2.43-h4852527_4.conda + sha256: 99a94eead18e7704225ac43682cce3f316fd33bc483749c093eaadef1d31de75 + md5: 29782348a527eda3ecfc673109d28e93 depends: - binutils_impl_linux-64 >=2.43,<2.44.0a0 license: GPL-3.0-only license_family: GPL - size: 33876 - timestamp: 1729655402186 -- kind: conda - name: binutils_impl_linux-64 - version: '2.43' - build: h4bf12b8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_2.conda - sha256: 267e78990247369b13234bda270f31beb56a600b4851a8244e31dd9ad85b3b17 - md5: cf0c5521ac2a20dfa6c662a4009eeef6 - depends: - - ld_impl_linux-64 2.43 h712a8e2_2 + size: 34646 + timestamp: 1740155498138 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.43-h4bf12b8_4.conda + sha256: 194d771be287dc973f6057c0747010ce28adf960f38d6e03ce3e828d7b74833e + md5: ef67db625ad0d2dce398837102f875ed + depends: + - ld_impl_linux-64 2.43 h712a8e2_4 - sysroot_linux-64 license: GPL-3.0-only license_family: GPL - size: 5682777 - timestamp: 1729655371045 -- kind: conda - name: binutils_linux-64 - version: '2.43' - build: h4852527_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_2.conda - sha256: df52bd8b8b2a20a0c529d9ad08aaf66093ac318aa8a33d270f18274341a77062 - md5: 18aba879ddf1f8f28145ca6fcb873d8c - depends: - - binutils_impl_linux-64 2.43 h4bf12b8_2 + size: 6111717 + timestamp: 1740155471052 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_linux-64-2.43-h4852527_4.conda + sha256: fe662a038dc14334617940f42ede9ba26d4160771255057cb14fb1a81ee12ac1 + md5: c87e146f5b685672d4aa6b527c6d3b5e + depends: + - binutils_impl_linux-64 2.43 h4bf12b8_4 license: GPL-3.0-only license_family: GPL - size: 34945 - timestamp: 1729655404893 -- kind: conda - name: blas-devel - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_mkl.conda - sha256: 24d5023acabe1e20fcaa00ff59fbddc944722f33e690068e4a8f3e8f25e63d68 - md5: 261acc954f47b7bf11d841ad8dd91d08 - depends: - - libblas 3.9.0 26_linux64_mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapack 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl + size: 35657 + timestamp: 1740155500723 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_h1ea3ea9_openblas.conda + build_number: 31 + sha256: afb47fb55992d68c216dfc11436db48feb7df40da469ecb92ce9fc3aa8bc1c55 + md5: ba652ee0576396d4765e567f043c57f9 + depends: + - libblas 3.9.0 31_h59b9bed_openblas + - libcblas 3.9.0 31_he106b2a_openblas + - liblapack 3.9.0 31_h7ac8fdf_openblas + - liblapacke 3.9.0 31_he2f377e_openblas + - openblas 0.3.29.* + license: BSD-3-Clause + license_family: BSD + size: 16818 + timestamp: 1740088024940 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-31_hcf00494_mkl.conda + build_number: 31 + sha256: 4639197a36d61f0a252ffaf8432fa13cb1809a0a34146e44b5ef1ea9c91553a5 + md5: 368c93bde87a67d24a74de15bf4c49fd + depends: + - libblas 3.9.0 31_hfdb39a5_mkl + - libcblas 3.9.0 31_h372d94f_mkl + - liblapack 3.9.0 31_hc41d3b0_mkl + - liblapacke 3.9.0 31_hbc6e62b_mkl - mkl >=2024.2.2,<2025.0a0 - mkl-devel 2024.2.* license: BSD-3-Clause license_family: BSD - size: 16148 - timestamp: 1734432567576 -- kind: conda - name: blas-devel - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.9.0-26_linux64_openblas.conda - sha256: 500680680cbe1eb4983e871ecd5b11b08dad1a7540252941711c754d8affe530 - md5: da61c3ef2fbe100b0613cbc2b01b502d - depends: - - libblas 3.9.0 26_linux64_openblas - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - openblas 0.3.28.* - license: BSD-3-Clause - license_family: BSD - size: 16274 - timestamp: 1734432589013 -- kind: conda - name: brotli-python - version: 1.1.0 - build: py312h2ec8cdc_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda + size: 16624 + timestamp: 1740087754 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.1.0-py312h2ec8cdc_2.conda sha256: f2a59ccd20b4816dea9a2a5cb917eb69728271dbf1aeab4e1b7e609330a50b6f md5: b0b867af6fc74b2a0aa206da29c0f3cf depends: @@ -1613,13 +1551,7 @@ packages: license_family: MIT size: 349867 timestamp: 1725267732089 -- kind: conda - name: bzip2 - version: 1.0.8 - build: h4bc722e_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h4bc722e_7.conda sha256: 5ced96500d945fb286c9c838e54fa759aa04a7129c59800f0846b4335cee770d md5: 62ee74e96c5ebb0af99386de58cf9553 depends: @@ -1629,87 +1561,58 @@ packages: license_family: BSD size: 252783 timestamp: 1720974456583 -- kind: conda - name: c-ares - version: 1.34.4 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.4-hb9d3cd8_0.conda - sha256: d4f28d87b6339b94f74762c0076e29c8ef8ddfff51a564a92da2843573c18320 - md5: e2775acf57efd5af15b8e3d1d74d72d3 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.5-hb9d3cd8_0.conda + sha256: f8003bef369f57396593ccd03d08a8e21966157269426f71e943f96e4b579aeb + md5: f7f0d6cc2dc986d42ac2689ec88192be depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 license: MIT license_family: MIT - size: 206085 - timestamp: 1734208189009 -- kind: conda - name: c-compiler - version: 1.8.0 - build: h2b85faf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.8.0-h2b85faf_1.conda - sha256: 009fced27be14e5ac750a04111a07eda79d73f80009300c1538cb83d5da71879 - md5: fa7b3bf2965b9d74a81a0702d9bb49ee + size: 206884 + timestamp: 1744127994291 +- conda: https://conda.anaconda.org/conda-forge/linux-64/c-compiler-1.9.0-h2b85faf_0.conda + sha256: 1e4b86b0f3d4ce9f3787b8f62e9f2c5683287f19593131640eed01cbdad38168 + md5: 3cb814f83f1f71ac1985013697f80cc1 depends: - binutils - gcc - gcc_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6085 - timestamp: 1728985300402 -- kind: conda - name: ca-certificates - version: 2024.12.14 - build: hbcca054_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2024.12.14-hbcca054_0.conda - sha256: 1afd7274cbc9a334d6d0bc62fa760acc7afdaceb0b91a8df370ec01fd75dc7dd - md5: 720523eb0d6a9b0f6120c16b2aa4e7de + size: 6196 + timestamp: 1736437002021 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2025.4.26-hbd8a1cb_0.conda + sha256: 2a70ed95ace8a3f8a29e6cd1476a943df294a7111dfb3e152e3478c4c889b7ac + md5: 95db94f75ba080a22eb623590993167b + depends: + - __unix license: ISC - size: 157088 - timestamp: 1734208393264 -- kind: conda - name: ccache - version: 4.10.1 - build: h065aff2_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.10.1-h065aff2_0.conda - sha256: 8ca3531bde782746a388f2e6193c090fa6e4afcdf2054f595e33937148560d85 - md5: d6b48c138e0c8170a6fe9c136e063540 + size: 152283 + timestamp: 1745653616541 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ccache-4.11.3-h80c52d3_0.conda + sha256: ac9464a60a7b085b5a999aaf33d882705390d7749b35e320f639614ae0cc9474 + md5: eb517c6a2b960c3ccb6f1db1005f063a depends: + - libgcc >=13 + - libstdcxx >=13 + - libgcc >=13 - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - zstd >=1.5.7,<1.6.0a0 - libhiredis >=1.0.2,<1.1.0a0 - - libstdcxx-ng >=12 - - zstd >=1.5.6,<1.6.0a0 license: GPL-3.0-only license_family: GPL - size: 627561 - timestamp: 1719847277140 -- kind: conda - name: certifi - version: 2024.12.14 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/certifi-2024.12.14-pyhd8ed1ab_0.conda - sha256: 048c16a9cbcb1fbad02083414d3bc7c1d0eea4b39aee6aa6bf8d1d5089ca8bad - md5: 6feb87357ecd66733be3279f16a8c400 + size: 708908 + timestamp: 1746271484780 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.4.26-pyhd8ed1ab_0.conda + sha256: 52aa837642fd851b3f7ad3b1f66afc5366d133c1d452323f786b0378a391915c + md5: c33eeaaa33f45031be34cda513df39b6 depends: - python >=3.9 license: ISC - size: 161642 - timestamp: 1734380604767 -- kind: conda - name: cffi - version: 1.17.1 - build: py312h06ac9bb_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda + size: 157200 + timestamp: 1746569627830 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-1.17.1-py312h06ac9bb_0.conda sha256: cba6ea83c4b0b4f5b5dc59cb19830519b28f95d7ebef7c9c5cf1c14843621457 md5: a861504bbea4161a9170b85d4d2be840 depends: @@ -1723,28 +1626,16 @@ packages: license_family: MIT size: 294403 timestamp: 1725560714366 -- kind: conda - name: charset-normalizer - version: 3.4.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.1-pyhd8ed1ab_0.conda - sha256: 4e0ee91b97e5de3e74567bdacea27f0139709fceca4db8adffbe24deffccb09b - md5: e83a31202d1c0a000fce3e9cf3825875 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.4.2-pyhd8ed1ab_0.conda + sha256: 535ae5dcda8022e31c6dc063eb344c80804c537a5a04afba43a845fa6fa130f5 + md5: 40fe4284b8b5835a9073a645139f35af depends: - python >=3.9 license: MIT license_family: MIT - size: 47438 - timestamp: 1735929811779 -- kind: conda - name: click - version: 8.1.8 - build: pyh707e725_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda + size: 50481 + timestamp: 1746214981991 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.1.8-pyh707e725_0.conda sha256: c920d23cd1fcf565031c679adb62d848af60d6fbb0edc2d50ba475cea4f0d8ab md5: f22f4d4970e09d68a10b922cbb0408d3 depends: @@ -1754,30 +1645,16 @@ packages: license_family: BSD size: 84705 timestamp: 1734858922844 -- kind: conda - name: cloudpickle - version: 3.1.0 - build: pyhd8ed1ab_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.0-pyhd8ed1ab_2.conda - sha256: 918151ad25558a37721055a02c0357ce9a2f51f07da1b238608e48ef17d35260 - md5: 1f76b7e2b3ab88def5aa2f158322c7e6 +- conda: https://conda.anaconda.org/conda-forge/noarch/cloudpickle-3.1.1-pyhd8ed1ab_0.conda + sha256: 21ecead7268241007bf65691610cd7314da68c1f88113092af690203b5780db5 + md5: 364ba6c9fb03886ac979b482f39ebb92 depends: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 25975 - timestamp: 1735328713686 -- kind: conda - name: colorama - version: 0.4.6 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + size: 25870 + timestamp: 1736947650712 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 md5: 962b9857ee8e7018c22f2776ffa0b2d7 depends: @@ -1786,14 +1663,7 @@ packages: license_family: BSD size: 27011 timestamp: 1733218222191 -- kind: conda - name: colorlog - version: 6.9.0 - build: pyh707e725_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/colorlog-6.9.0-pyh707e725_1.conda sha256: 9a0dc9a0611d3ad33846a52b913346a5ca5cd9f0aa67a53fd89386652d07874b md5: f00fc375bd02bdbbf791f9fe26ae96ec depends: @@ -1803,295 +1673,217 @@ packages: license_family: MIT size: 15522 timestamp: 1733258500721 -- kind: conda - name: compilers - version: 1.8.0 - build: ha770c72_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.8.0-ha770c72_1.conda - sha256: d2fa2f8cb3df79f543758c8e288f1a74a2acca57245f1e03919bffa3e40aad2d - md5: 061e111d02f33a99548f0de07169d9fb - depends: - - c-compiler 1.8.0 h2b85faf_1 - - cxx-compiler 1.8.0 h1a2810e_1 - - fortran-compiler 1.8.0 h36df796_1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/compilers-1.9.0-ha770c72_0.conda + sha256: 97d90aeba05089bbf3124030ebd96890754b8c8dc2c880490d38a3075941de28 + md5: 5859096e397aba423340d0bbbb11ec64 + depends: + - c-compiler 1.9.0 h2b85faf_0 + - cxx-compiler 1.9.0 h1a2810e_0 + - fortran-compiler 1.9.0 h36df796_0 license: BSD-3-Clause license_family: BSD - size: 6932 - timestamp: 1728985303287 -- kind: conda - name: cpython - version: 3.12.8 - build: py312hd8ed1ab_1 - build_number: 1 - subdir: noarch + size: 7014 + timestamp: 1736437002774 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.10-py312hd8ed1ab_0.conda noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.8-py312hd8ed1ab_1.conda - sha256: 05413d84485086301e5bd7c03fca2caae91f75474d99d9fc815cec912332452b - md5: caa04d37126e82822468d6bdf50f5ebd + sha256: acb47715abf1cd8177a5c20f42a34555b5d9cebb68ff39a58706e84effe218e2 + md5: 7584a4b1e802afa25c89c0dcc72d0826 depends: - - python 3.12.8.* + - python >=3.12,<3.13.0a0 - python_abi * *_cp312 license: Python-2.0 - size: 44751 - timestamp: 1733407917248 -- kind: conda - name: cuda-cccl_linux-64 - version: 12.6.77 - build: ha770c72_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.6.77-ha770c72_0.conda - sha256: 00a7de1d084896758dc2d24b1faf4bf59e596790b22a3a08bf163a810bbacde8 - md5: 365a924cf93535157d61debac807e9e4 + size: 45861 + timestamp: 1744323195619 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cccl_linux-64-12.9.27-ha770c72_0.conda + sha256: 2ee3b9564ca326226e5cda41d11b251482df8e7c757e333d28ec75213c75d126 + md5: 87ff6381e33b76e5b9b179a2cdd005ec depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 1067930 - timestamp: 1727807050610 -- kind: conda - name: cuda-crt-dev_linux-64 - version: 12.6.85 - build: ha770c72_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.6.85-ha770c72_0.conda - sha256: 2515c1bddde769ad8628411e08deb31a7eafe6ace9e46bea33a3a99fbb95aea0 - md5: 4b14e78e12daa061dcdbe3ceed95cb57 + size: 1150650 + timestamp: 1746189825236 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-crt-dev_linux-64-12.9.41-ha770c72_0.conda + sha256: 54e00942d92e21c35adcd2c55af7987719a48b01975abcefe0f936f3e2995e17 + md5: 1b8184d441b383f0b1cf36005598fc05 depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 88743 - timestamp: 1732132177211 -- kind: conda - name: cuda-crt-tools - version: 12.6.85 - build: ha770c72_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.6.85-ha770c72_0.conda - sha256: 83b6f3332a17bc891f2ecdc9b1424658009e37e14e888d0bd0458b6aa4db59a2 - md5: 4ab193b5fcdcf8d7b094221e3977a112 - depends: - - cuda-version >=12.6,<12.7.0a0 + size: 93781 + timestamp: 1746198278062 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-crt-tools-12.9.41-ha770c72_0.conda + sha256: e291e3468396ab2dc9fc17607754fed19eac6cdcb3a5f30cf9063c18916ec491 + md5: 452ec0ccbf67954a0a03c4ec0b1fa7a5 + depends: + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 27135 - timestamp: 1732132181193 -- kind: conda - name: cuda-cudart - version: 12.6.77 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.6.77-h5888daf_0.conda - sha256: e7a256a61d5b8c9d7d31932b5f4f35a8fda5a18c789cb971d98dca266fdd8792 - md5: feb533cb1e5f7ffbbb82d8465e0adaad + size: 28214 + timestamp: 1746198287537 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cudart-12.9.37-h5888daf_0.conda + sha256: 5bf59a9cb7d581339daa291e2cb8d541a6c2bf264ae71dc516fa38720bc11ab4 + md5: d874c87fba16e4ddf005f7e191da0775 depends: - __glibc >=2.17,<3.0.a0 - - cuda-cudart_linux-64 12.6.77 h3f2d84a_0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-cudart_linux-64 12.9.37 h3f2d84a_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 22397 - timestamp: 1727810461651 -- kind: conda - name: cuda-cudart-dev_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.6.77-h3f2d84a_0.conda - sha256: 60847bd8c74b02ca17d68d742fe545db84a18bf808344eb99929f32f79bffcf9 - md5: f967e2449b6c066f6d09497fff12d803 + size: 23165 + timestamp: 1746194366557 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-dev_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 369bf15b6ab428279620fa9a806db6e6adb7987c6137654054b07a192b8a8252 + md5: 9ae200ef917b953d39c60d45ba78bebb depends: - cuda-cccl_linux-64 - cuda-cudart-static_linux-64 - cuda-cudart_linux-64 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 365370 - timestamp: 1727810466552 -- kind: conda - name: cuda-cudart-static_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.6.77-h3f2d84a_0.conda - sha256: aefed29499bdbe5d0c65ca44ef596929cf34cc3014f0ae225cdd45a0e66f2660 - md5: 3ad8eacbf716ddbca1b5292a3668c821 + size: 388621 + timestamp: 1746194374721 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart-static_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 47f9c7f8c946b9e6e2c7c616d9c59acf59ea96cf64f1e0a5c090f63b456ab1fc + md5: bc0e5f61bfea338148d265fe9bbbacae depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 762328 - timestamp: 1727810443982 -- kind: conda - name: cuda-cudart_linux-64 - version: 12.6.77 - build: h3f2d84a_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.6.77-h3f2d84a_0.conda - sha256: cf8433afa236108dba2a94ea5d4f605c50f0e297ee54eb6cb37175fd84ced907 - md5: 314908ad05e2c4833475a7d93f4149ca + size: 1148263 + timestamp: 1746194340428 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.37-h3f2d84a_0.conda + sha256: 5d3da5b258785cb7aa593363518d11e7b5580373d612faba43a72c9c9db941f9 + md5: 05c9f71dede6cfae29dfc1141128e717 depends: - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 188616 - timestamp: 1727810451690 -- kind: conda - name: cuda-cupti - version: 12.6.80 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.6.80-hbd13f7d_0.conda - sha256: 41cef2d389f5e467de25446aa0d856d9f3bb358d9671db3d4a06ecdb5802a317 - md5: 85e9354a9e32f7526d2451ed2bb93347 + size: 197833 + timestamp: 1746194349673 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cuobjdump-12.9.26-hbd13f7d_0.conda + sha256: 873d7f722904b104cbc31402380c0749cecf83e8ee270e4277e97975c4170793 + md5: 9f83ac9b3dcc0401bb19a546af50bd47 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-nvdisasm + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 1999085 - timestamp: 1727807734169 -- kind: conda - name: cuda-cupti-dev - version: 12.6.80 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.6.80-h5888daf_0.conda - sha256: f06ea656216d331c333889f1c020b385ada748f2dd5b0a36326cc8935a7b8d8c - md5: ed37a8cad974fed39334d096f3b18d81 + size: 244544 + timestamp: 1746193903455 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-12.9.19-h9ab20c4_0.conda + sha256: 19ca76b00200608775c97579ac0be54e767a86dd6b614d0b001d1bad8007f1fb + md5: 2ccc05e957d8f6a9e3d5d35b0847f0b2 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-cupti 12.6.80 hbd13f7d_0 - - cuda-version >=12.6,<12.7.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libgcc >=13 + - libstdcxx >=13 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 1844732 + timestamp: 1746192697291 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-cupti-dev-12.9.19-h9ab20c4_0.conda + sha256: 611ec4743bfc27cf21d5529611a384a6621a9600a8d036299fab198625465b51 + md5: 359a97d37351c1f1795155508a5337fc + depends: + - __glibc >=2.28,<3.0.a0 + - cuda-cupti 12.9.19 h9ab20c4_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 constrains: - - cuda-cupti-static >=12.6.80 + - cuda-cupti-static >=12.9.19 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 3533128 - timestamp: 1727807797633 -- kind: conda - name: cuda-nvcc-tools - version: 12.6.85 - build: he02047a_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.6.85-he02047a_0.conda - sha256: 0f8cc474130f9654cacc6e5ff4b62b731da28019c5e28ca318a3e38a84e3b1a8 - md5: 30b272fa555944cb44f8d4dc9244abb5 + size: 4614575 + timestamp: 1746192761574 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvcc-tools-12.9.41-he02047a_0.conda + sha256: e8784400792235d24e1e743a2678885ca631ec81dbf392a7c56511abe5efceec + md5: a53cbad5c98447d550b1740d0001cdc4 depends: - __glibc >=2.17,<3.0.a0 - - cuda-crt-tools 12.6.85 ha770c72_0 - - cuda-nvvm-tools 12.6.85 he02047a_0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-crt-tools 12.9.41 ha770c72_0 + - cuda-nvvm-tools 12.9.41 he02047a_0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=12 - libstdcxx >=12 constrains: - - gcc_impl_linux-64 >=6,<14.0a0 + - gcc_impl_linux-64 >=6,<15.0a0 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 27425139 + timestamp: 1746198424385 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvdisasm-12.9.19-hbd13f7d_0.conda + sha256: d3846331680396c3adf9adee7f0db9fbfb39b20c06c4235fc687489cced8b9b7 + md5: 8138274dcbaab5489b3e43b33d7825e9 + depends: + - __glibc >=2.17,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libgcc >=13 + - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 24082529 - timestamp: 1732132231855 -- kind: conda - name: cuda-nvrtc - version: 12.6.85 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.6.85-hbd13f7d_0.conda - sha256: 3ddec2c3b68cea5edba728ffc61a2257300d401d428b9d60aca7363c0c0d4ad5 - md5: 9d9909844a0133153d54b6f07283da8c + size: 5517513 + timestamp: 1746189877059 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvrtc-12.9.41-h5888daf_0.conda + sha256: 67d17fe3ca19ad30d3f5c885da1b509c2372ba865e6ace4074ddd3a4d89ff525 + md5: 57ea71a617e163f0b36512a5c9edd0bc depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 18138390 - timestamp: 1732133174552 -- kind: conda - name: cuda-nvtx - version: 12.6.77 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.6.77-hbd13f7d_0.conda - sha256: 98bdf2e5017069691e8b807e0ceba4327d427b57147249ca0a505b8ad6844148 - md5: 3fe3afe309918465f82f984b3a1a85e9 + size: 67173643 + timestamp: 1746190515836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvtx-12.9.19-h5888daf_0.conda + sha256: cccfc520ef222303de0fc94dd951b6c356d25f46eee450b17d853078afb6956c + md5: eeba52bd19d561f6b0be3bfcf4e292af depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 31364 - timestamp: 1727816542389 -- kind: conda - name: cuda-nvvm-tools - version: 12.6.85 - build: he02047a_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.6.85-he02047a_0.conda - sha256: 5c7ab2b1367cefaa15a8d8880e9985ed2753a990765d047df23fa8ddb2ba9e7a - md5: 0919bdf9454da5eb974e98dd79bf38fe + size: 29222 + timestamp: 1746195676216 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cuda-nvvm-tools-12.9.41-he02047a_0.conda + sha256: c0da297dc963cd4d1d333815189c4a60360a7bcb8d3905fb37c208326bda1dc4 + md5: e3310ca76e355bdb2b9589edc8fd6083 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=12 - libstdcxx >=12 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 10880815 - timestamp: 1732132210850 -- kind: conda - name: cuda-version - version: '12.6' - build: h7480c83_3 - build_number: 3 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.6-h7480c83_3.conda - sha256: fd9104d73199040285b6a6ad56322b38af04828fabbac1f5a268a83509358425 - md5: 1c8b99e65a4423b1e4ac2e4c76fb0978 + size: 24248207 + timestamp: 1746198369570 +- conda: https://conda.anaconda.org/conda-forge/noarch/cuda-version-12.9-h4f385c5_3.conda + sha256: 5f5f428031933f117ff9f7fcc650e6ea1b3fef5936cf84aa24af79167513b656 + md5: b6d5d7f1c171cbd228ea06b556cfa859 constrains: - - cudatoolkit 12.6|12.6.* + - cudatoolkit 12.9|12.9.* - __cuda >=12 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 20940 - timestamp: 1722603990914 -- kind: conda - name: cudnn - version: 9.3.0.75 - build: h62a6f1c_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.3.0.75-h62a6f1c_2.conda - sha256: e723324f64a9e3b10c91893aa1594e94427f54d2489ff0edf3b9296b5d6c5733 - md5: eca29a76544ab11bb6d78e4d836df7b4 + size: 21578 + timestamp: 1746134436166 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cudnn-9.8.0.87-h81d5506_1.conda + sha256: 88fd0bd4ad77f126d8b4d89a9d1a661f8be322c8a1ae9da28a89fb7373b5d4ca + md5: c87536f2e5d0740f4193625eb00fab7e depends: - __glibc >=2.28,<3.0.a0 - cuda-nvrtc - cuda-version >=12,<13.0a0 - libcublas - - libgcc >=12 - - libstdcxx >=12 + - libgcc >=13 + - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: LicenseRef-cuDNN-Software-License-Agreement - size: 401805073 - timestamp: 1735784276169 -- kind: conda - name: cupy - version: 13.3.0 - build: py312h7d319b9_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.3.0-py312h7d319b9_2.conda - sha256: 9e7a612a4b7f1bf58176816e50e30d3112724d318d884f3453c0edb44b4570ce - md5: 009ef049020fef7d1541183d52fab5a9 + size: 490227234 + timestamp: 1743628408368 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-13.4.1-py312h78400a1_0.conda + sha256: 4b5a53348865f90c743b68131ced2a361274fdeafc2e38d28ffd13c39dd9f907 + md5: 7e38588cf4ce6207bccd51dbfe647024 depends: - cuda-cudart-dev_linux-64 - cuda-nvrtc - - cuda-version >=12.0,<13.0a0 - - cupy-core 13.3.0 py312h1acd1a8_2 + - cuda-version >=12,<13.0a0 + - cupy-core 13.4.1 py312h007fbcc_0 - libcublas - libcufft - libcurand @@ -2101,69 +1893,51 @@ packages: - python_abi 3.12.* *_cp312 license: MIT license_family: MIT - size: 355525 - timestamp: 1729280147659 -- kind: conda - name: cupy-core - version: 13.3.0 - build: py312h1acd1a8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.3.0-py312h1acd1a8_2.conda - sha256: 7a7354a58863bef6bb11f77de42620f5b0965a0d11576fe0673f6b02dc034b6d - md5: 15e9530e87664584a6b409ecdf5c9264 + size: 357651 + timestamp: 1742853581416 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cupy-core-13.4.1-py312h007fbcc_0.conda + sha256: 7f2e3de74eb716156f35c74f7f380d23d9218f1f0b100b6d957847155bf7ad2a + md5: 724129d9d097c42880f3e3b8f2cfbebb depends: - __glibc >=2.17,<3.0.a0 - - fastrlock >=0.8.2,<0.9.0a0 - - libgcc >=12 - - libstdcxx >=12 + - fastrlock >=0.8.3,<0.9.0a0 + - libgcc >=13 + - libstdcxx >=13 - numpy >=1.22,<3.0.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 constrains: - - scipy ~=1.7 - - optuna ~=3.0 - - __cuda >=12.0 - - cutensor >=2.0.2.5,<3.0a0 - - libcurand >=10,<11.0a0 - - cuda-version >=12.0,<13 - - cupy >=13.3.0,<13.4.0a0 - cuda-nvrtc >=12,<13.0a0 - - nccl >=2.23.4.1,<3.0a0 - - libcublas >=12,<13.0a0 - - libcusparse >=12,<13.0a0 - libcufft >=11,<12.0a0 + - __cuda >=12.0 - libcusolver >=11,<12.0a0 + - libcusparse >=12,<13.0a0 + - cutensor >=2.2.0.0,<3.0a0 + - libcublas >=12,<13.0a0 + - optuna ~=3.0 + - scipy ~=1.7 + - cuda-version >=12,<13.0a0 + - nccl >=2.26.2.1,<3.0a0 + - libcurand >=10,<11.0a0 + - cupy >=13.4.1,<13.5.0a0 license: MIT license_family: MIT - size: 41249386 - timestamp: 1729280040168 -- kind: conda - name: cxx-compiler - version: 1.8.0 - build: h1a2810e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.8.0-h1a2810e_1.conda - sha256: cca0450bbc0d19044107d0f90fa36126a11b007fbfb62bd2a1949b2bb59a21a4 - md5: 3bb4907086d7187bf01c8bec397ffa5e - depends: - - c-compiler 1.8.0 h2b85faf_1 + size: 49529735 + timestamp: 1742853454294 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cxx-compiler-1.9.0-h1a2810e_0.conda + sha256: 5efc51b8e7d87fc5380f00ace9f9c758142eade520a63d3631d2616d1c1b25f9 + md5: 1ce8b218d359d9ed0ab481f2a3f3c512 + depends: + - c-compiler 1.9.0 h2b85faf_0 - gxx - gxx_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6059 - timestamp: 1728985302835 -- kind: conda - name: cython - version: 3.0.11 - build: py312h8fd2918_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.11-py312h8fd2918_3.conda - sha256: 7a888ddda463a3146949540229c70625fbefb05bcb1352cbff990f205b8392b0 - md5: 21e433caf1bb1e4c95832f8bb731d64c + size: 6168 + timestamp: 1736437002465 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cython-3.0.12-py312h2614dfc_0.conda + sha256: de815476da537b911e2ceeb7f76b445d0c76b3d5fad35600ed28bc8d19302127 + md5: e5d2a28866ee990a340bde1eabde587a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -2172,32 +1946,18 @@ packages: - python_abi 3.12.* *_cp312 license: Apache-2.0 license_family: APACHE - size: 3752086 - timestamp: 1727456382070 -- kind: conda - name: decorator - version: 5.1.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/decorator-5.1.1-pyhd8ed1ab_1.conda - sha256: 84e5120c97502a3785e8c3241c3bf51f64b4d445f13b4d2445db00d9816fe479 - md5: d622d8d7ee8868870f9cbe259f381181 + size: 3766553 + timestamp: 1739228870146 +- conda: https://conda.anaconda.org/conda-forge/noarch/decorator-5.2.1-pyhd8ed1ab_0.conda + sha256: c17c6b9937c08ad63cb20a26f403a3234088e57d4455600974a0ce865cb14017 + md5: 9ce473d1d1be1cc3810856a48b3fab32 depends: - python >=3.9 license: BSD-2-Clause license_family: BSD - size: 14068 - timestamp: 1733236549190 -- kind: conda - name: doit - version: 0.36.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda + size: 14129 + timestamp: 1740385067843 +- conda: https://conda.anaconda.org/conda-forge/noarch/doit-0.36.0-pyhd8ed1ab_1.conda sha256: 8925dc378a2d5533905b478c69fd7ea7c72c664aa4b37075a2711079bc9222e3 md5: 18d4243b3d30352f9dea8e522f6ff4d1 depends: @@ -2208,14 +1968,7 @@ packages: license_family: MIT size: 73790 timestamp: 1734618648157 -- kind: conda - name: exceptiongroup - version: 1.2.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_1.conda sha256: cbde2c64ec317118fc06b223c5fd87c8a680255e7348dd60e7b292d2e103e701 md5: a16662747cdeb9abbac74d0057cc976e depends: @@ -2223,14 +1976,7 @@ packages: license: MIT and PSF-2.0 size: 20486 timestamp: 1733208916977 -- kind: conda - name: execnet - version: 2.1.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 md5: a71efeae2c160f6789900ba2631a2c90 depends: @@ -2239,13 +1985,7 @@ packages: license_family: MIT size: 38835 timestamp: 1733231086305 -- kind: conda - name: fastrlock - version: 0.8.3 - build: py312h6edf5ed_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/fastrlock-0.8.3-py312h6edf5ed_1.conda sha256: 260589d271cfdd4bf04d084084123be3e49e9017da159f27bea5dc8617eaada6 md5: 2e401040f77cf54d8d5e1f0417dcf0b2 depends: @@ -2259,61 +1999,36 @@ packages: license_family: MIT size: 41705 timestamp: 1734873425804 -- kind: conda - name: filelock - version: 3.16.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/filelock-3.16.1-pyhd8ed1ab_1.conda - sha256: 18dca6e2194732df7ebf824abaefe999e4765ebe8e8a061269406ab88fc418b9 - md5: d692e9ba6f92dc51484bf3477e36ce7c +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.18.0-pyhd8ed1ab_0.conda + sha256: de7b6d4c4f865609ae88db6fa03c8b7544c2452a1aa5451eb7700aad16824570 + md5: 4547b39256e296bb758166893e909a7c depends: - python >=3.9 license: Unlicense - size: 17441 - timestamp: 1733240909987 -- kind: conda - name: fortran-compiler - version: 1.8.0 - build: h36df796_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.8.0-h36df796_1.conda - sha256: a713ede383b34fb46e73e00fc6b556a7446eae43f9d312c104678658ea463ea4 - md5: 6b57750841d53ade8d3b47eafe53dd9f + size: 17887 + timestamp: 1741969612334 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fortran-compiler-1.9.0-h36df796_0.conda + sha256: ca857e7b91eee0d33aa3f6cdebac36a8699ab3f37efbb717df409ae9b8decb34 + md5: cc0cf942201f9d3b0e9654ea02e12486 depends: - binutils - - c-compiler 1.8.0 h2b85faf_1 + - c-compiler 1.9.0 h2b85faf_0 - gfortran - gfortran_linux-64 13.* license: BSD-3-Clause license_family: BSD - size: 6095 - timestamp: 1728985303064 -- kind: conda - name: fsspec - version: 2024.12.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/fsspec-2024.12.0-pyhd8ed1ab_0.conda - sha256: 3320970c4604989eadf908397a9475f9e6a96a773c185915111399cbfbe47817 - md5: e041ad4c43ab5e10c74587f95378ebc7 + size: 6184 + timestamp: 1736437002625 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2025.3.2-pyhd8ed1ab_0.conda + sha256: 2040d4640708bd6ab9ed6cb9901267441798c44974bc63c9b6c1cb4c1891d825 + md5: 9c40692c3d24c7aaf335f673ac09d308 depends: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 137756 - timestamp: 1734650349242 -- kind: conda - name: gast - version: 0.5.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda + size: 142117 + timestamp: 1743437355974 +- conda: https://conda.anaconda.org/conda-forge/noarch/gast-0.5.5-pyhd8ed1ab_0.conda sha256: b0527039bb19aeb5636ecb1512378e4109b945bc99f409977bda3022485c526f md5: ebc1dc871c48673a0a922023a2e1eee2 depends: @@ -2324,85 +2039,55 @@ packages: license_family: BSD size: 24016 timestamp: 1719403213917 -- kind: conda - name: gcc - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_1.conda - sha256: d0161362430183cbdbc3db9cf95f9a1af1793027f3ab8755b3d3586deb28bf84 - md5: 606924335b5bcdf90e9aed9a2f5d22ed +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-13.3.0-h9576a4e_2.conda + sha256: 300f077029e7626d69cc250a69acd6018c1fced3f5bf76adf37854f3370d2c45 + md5: d92e51bf4b6bdbfe45e5884fb0755afe depends: - gcc_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53864 - timestamp: 1724801360210 -- kind: conda - name: gcc_impl_linux-64 - version: 13.3.0 - build: hfea6d02_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-hfea6d02_1.conda - sha256: 998ade1d487e93fc8a7a16b90e2af69ebb227355bf4646488661f7ae5887873c - md5: 0d043dbc126b64f79d915a0e96d3a1d5 + size: 55246 + timestamp: 1740240578937 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-13.3.0-h1e990d8_2.conda + sha256: c3e9f243ea8292eecad78bb200d8f5b590e0f82bf7e7452a3a7c8df4eea6f774 + md5: f46cf0acdcb6019397d37df1e407ab91 depends: - binutils_impl_linux-64 >=2.40 - libgcc >=13.3.0 - - libgcc-devel_linux-64 13.3.0 h84ea5a7_101 + - libgcc-devel_linux-64 13.3.0 hc03c837_102 - libgomp >=13.3.0 - - libsanitizer 13.3.0 heb74ff8_1 + - libsanitizer 13.3.0 he8ea267_2 - libstdcxx >=13.3.0 - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 67464415 - timestamp: 1724801227937 -- kind: conda - name: gcc_linux-64 - version: 13.3.0 - build: hc28eda2_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_7.conda - sha256: 1e5ac50580a68fdc7d2f5722abcf1a87898c24b1ab6eb5ecd322634742d93645 - md5: ac23afbf5805389eb771e2ad3b476f75 + size: 66770653 + timestamp: 1740240400031 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_linux-64-13.3.0-hc28eda2_10.conda + sha256: 2526f358e0abab84d6f93b2bae932e32712025a3547400393a1cfa6240257323 + md5: d151142bbafe5e68ec7fc065c5e6f80c depends: - binutils_linux-64 - gcc_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 32005 - timestamp: 1731939593317 -- kind: conda - name: gfortran - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_1.conda - sha256: fc711e4a5803c4052b3b9d29788f5256f5565f4609f7688268e89cbdae969f9b - md5: 5e5e3b592d5174eb49607a973c77825b + size: 32570 + timestamp: 1745040775220 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran-13.3.0-h9576a4e_2.conda + sha256: 9425741d57bbcf918fe98cb375508f31de0daa655f3cebe100a43cf7cb46ec39 + md5: 19e6d3c9cde10a0a9a170a684082588e depends: - gcc 13.3.0.* - gcc_impl_linux-64 13.3.0.* - gfortran_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53341 - timestamp: 1724801488689 -- kind: conda - name: gfortran_impl_linux-64 - version: 13.3.0 - build: h10434e7_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h10434e7_1.conda - sha256: 9439e1f01d328d4cbdfbb2c8579b83619a694ad114ddf671fb9971ebf088d267 - md5: 6709e113709b6ba67cc0f4b0de58ef7f + size: 54740 + timestamp: 1740240701423 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_impl_linux-64-13.3.0-h84c1745_2.conda + sha256: 03a45f58b41909d4189fb81ec7f971b60aaccf95a3953049d93ae7d06eb19000 + md5: 4e21ed177b76537067736f20f54fee0a depends: - gcc_impl_linux-64 >=13.3.0 - libgcc >=13.3.0 @@ -2411,33 +2096,21 @@ packages: - sysroot_linux-64 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 15894110 - timestamp: 1724801415339 -- kind: conda - name: gfortran_linux-64 - version: 13.3.0 - build: hb919d3a_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_7.conda - sha256: 73ba4c14b6b372385b0cb8e06c45a7df5ffc0ca688bd10180c0a3459ab71390d - md5: 0b8e7413559c4c892a37c35de4559969 + size: 15923784 + timestamp: 1740240635243 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gfortran_linux-64-13.3.0-hb919d3a_10.conda + sha256: e94192917326a726046e9e83f72d091e550224e7908af82dff2498f98886ebb6 + md5: 7ce070e3329cd10bf79dbed562a21bd4 depends: - binutils_linux-64 - - gcc_linux-64 13.3.0 hc28eda2_7 + - gcc_linux-64 13.3.0 hc28eda2_10 - gfortran_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 30355 - timestamp: 1731939610282 -- kind: conda - name: gmp - version: 6.3.0 - build: hac33072_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda + size: 30847 + timestamp: 1745040792044 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hac33072_2.conda sha256: 309cf4f04fec0c31b6771a5809a1909b4b3154a2208f52351e1ada006f4c750c md5: c94a5994ef49749880a8139cf9afcbe1 depends: @@ -2446,15 +2119,9 @@ packages: license: GPL-2.0-or-later OR LGPL-3.0-or-later size: 460055 timestamp: 1718980856608 -- kind: conda - name: gmpy2 - version: 2.1.5 - build: py312h7201bc8_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.1.5-py312h7201bc8_3.conda - sha256: addd0bc226ca86c11f1223ab322d12b67501c2b3d93749bdab2068ccaedd8ef0 - md5: 673ef4d6611f5b4ca7b5c1f8c65a38dc +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.2.1-py312h7201bc8_0.conda + sha256: 92cd104e06fafabc5a0da93ad16a18a7e33651208901bdb0ecd89d10c846e43a + md5: c539cba0be444c6cefcb853987187d9e depends: - __glibc >=2.17,<3.0.a0 - gmp >=6.3.0,<7.0a0 @@ -2465,119 +2132,74 @@ packages: - python_abi 3.12.* *_cp312 license: LGPL-3.0-or-later license_family: LGPL - size: 209631 - timestamp: 1733462668219 -- kind: conda - name: gxx - version: 13.3.0 - build: h9576a4e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_1.conda - sha256: 5446f5d1d609d996579f706d2020e83ef48e086d943bfeef7ab807ea246888a0 - md5: 209182ca6b20aeff62f442e843961d81 + size: 213405 + timestamp: 1745509508879 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-13.3.0-h9576a4e_2.conda + sha256: fa9d0171c17e4c4203a4199fcc35571a25c1f16c0ad992080d4f0ced53bf5aa5 + md5: 07e8df00b7cd3084ad3ef598ce32a71c depends: - gcc 13.3.0.* - gxx_impl_linux-64 13.3.0.* license: BSD-3-Clause license_family: BSD - size: 53338 - timestamp: 1724801498389 -- kind: conda - name: gxx_impl_linux-64 - version: 13.3.0 - build: hdbfa832_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hdbfa832_1.conda - sha256: 746dff24bb1efc89ab0ec108838d0711683054e3bbbcb94d042943410a98eca1 - md5: 806367e23a0a6ad21e51875b34c57d7e - depends: - - gcc_impl_linux-64 13.3.0 hfea6d02_1 - - libstdcxx-devel_linux-64 13.3.0 h84ea5a7_101 + size: 54718 + timestamp: 1740240712365 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-13.3.0-hae580e1_2.conda + sha256: 7cb36526a5c3e75ae07452aee5c9b6219f62fad9f85cc6d1dab5b21d1c4cc996 + md5: b55f02540605c322a47719029f8404cc + depends: + - gcc_impl_linux-64 13.3.0 h1e990d8_2 + - libstdcxx-devel_linux-64 13.3.0 hc03c837_102 - sysroot_linux-64 - tzdata license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 13337720 - timestamp: 1724801455825 -- kind: conda - name: gxx_linux-64 - version: 13.3.0 - build: h6834431_7 - build_number: 7 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_7.conda - sha256: a9b1ffea76f2cc5aedeead4793fcded7a687cce9d5e3f4fe93629f1b1d5043a6 - md5: 7c82ca9bda609b6f72f670e4219d3787 + size: 13362974 + timestamp: 1740240672045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_linux-64-13.3.0-h6834431_10.conda + sha256: 03de108ca10b693a1b03e7d5cf9173837281d15bc5da7743ffba114fa9389476 + md5: 9a8ebde471cec5cc9c48f8682f434f92 depends: - binutils_linux-64 - - gcc_linux-64 13.3.0 hc28eda2_7 + - gcc_linux-64 13.3.0 hc28eda2_10 - gxx_impl_linux-64 13.3.0.* - sysroot_linux-64 license: BSD-3-Clause license_family: BSD - size: 30356 - timestamp: 1731939612705 -- kind: conda - name: h2 - version: 4.1.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.1.0-pyhd8ed1ab_1.conda - sha256: 843ddad410c370672a8250470697027618f104153612439076d4d7b91eeb7b5c - md5: 825927dc7b0f287ef8d4d0011bb113b1 - depends: - - hpack >=4.0,<5 - - hyperframe >=6.0,<7 + size: 30904 + timestamp: 1745040794452 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: b4754fb1bdcb70c8fd54f918301582c6 + depends: + - hpack >=4.1,<5 + - hyperframe >=6.1,<7 - python >=3.9 license: MIT license_family: MIT - size: 52000 - timestamp: 1733298867359 -- kind: conda - name: hpack - version: 4.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hpack-4.0.0-pyhd8ed1ab_1.conda - sha256: ec89b7e5b8aa2f0219f666084446e1fb7b54545861e9caa892acb24d125761b5 - md5: 2aa5ff7fa34a81b9196532c84c10d865 + size: 53888 + timestamp: 1738578623567 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda + sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba + md5: 0a802cb9888dd14eeefc611f05c40b6e depends: - python >=3.9 license: MIT license_family: MIT - size: 29412 - timestamp: 1733299296857 -- kind: conda - name: hyperframe - version: 6.0.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.0.1-pyhd8ed1ab_1.conda - sha256: e91c6ef09d076e1d9a02819cd00fa7ee18ecf30cdd667605c853980216584d1b - md5: 566e75c90c1d0c8c459eb0ad9833dc7a + size: 30731 + timestamp: 1737618390337 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac depends: - python >=3.9 license: MIT license_family: MIT - size: 17239 - timestamp: 1733298862681 -- kind: conda - name: hypothesis - version: 6.123.7 - build: pyha770c72_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.123.7-pyha770c72_0.conda - sha256: 72a4bef11cecacb9dd4a460569724c2a7a026db3220c2532d85e564341ea3735 - md5: a1d0e1fa77d48bacb60d916d886ec63a + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/hypothesis-6.131.15-pyha770c72_0.conda + sha256: 38b97ec0b35070ac915a6732b920076d7bad376f9746375045ce31313c758fed + md5: 42127e8b4b57e92f097c74a396ae6511 depends: - attrs >=22.2.0 - click >=7.0 @@ -2586,16 +2208,20 @@ packages: - setuptools - sortedcontainers >=2.1.0,<3.0.0 license: MPL-2.0 - size: 344302 - timestamp: 1736235054115 -- kind: conda - name: idna - version: '3.10' - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda + size: 357607 + timestamp: 1746683820692 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + sha256: 71e750d509f5fa3421087ba88ef9a7b9be11c53174af3aa4d06aff4c18b38e8e + md5: 8b189310083baabfb622af68fd9d3ae3 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: MIT + license_family: MIT + size: 12129203 + timestamp: 1720853576813 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.10-pyhd8ed1ab_1.conda sha256: d7a472c9fd479e2e8dcb83fb8d433fce971ea369d704ece380e876f9c3494e87 md5: 39a4f67be3286c86d696df570b1201b7 depends: @@ -2604,31 +2230,17 @@ packages: license_family: BSD size: 49765 timestamp: 1733211921194 -- kind: conda - name: importlib-metadata - version: 8.5.0 - build: pyha770c72_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.5.0-pyha770c72_1.conda - sha256: 13766b88fc5b23581530d3a0287c0c58ad82f60401afefab283bf158d2be55a9 - md5: 315607a3030ad5d5227e76e0733798ff +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.6.1-pyha770c72_0.conda + sha256: 598951ebdb23e25e4cec4bbff0ae369cec65ead80b50bc08b441d8e54de5cf03 + md5: f4b39bf00c69f56ac01e020ebfac066c depends: - python >=3.9 - zipp >=0.5 license: Apache-2.0 license_family: APACHE - size: 28623 - timestamp: 1733223207185 -- kind: conda - name: iniconfig - version: 2.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda + size: 29141 + timestamp: 1737420302391 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_1.conda sha256: 0ec8f4d02053cd03b0f3e63168316530949484f80e16f5e2fb199a1d117a89ca md5: 6837f3eff7dcea42ecd714ce1ac2b108 depends: @@ -2637,67 +2249,49 @@ packages: license_family: MIT size: 11474 timestamp: 1733223232820 -- kind: conda - name: jax - version: 0.4.35 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/jax-0.4.35-pyhd8ed1ab_1.conda - sha256: 665e96d8a8144f33ea9733746ee3a9c913dd5fa460fb2095592f935cab0753a8 - md5: 8fe7d2b5328189557c539e8a82af00e9 +- conda: https://conda.anaconda.org/conda-forge/noarch/jax-0.5.2-pyhd8ed1ab_0.conda + sha256: be7644c955cd4be330a13a8f64c0b73d520f8b3ab6bb64b8b1d3a17945345684 + md5: f19f3d281603af8e67d533dbeac279ce depends: - importlib-metadata >=4.6 - - jaxlib >=0.4.34,<=0.4.35 + - jaxlib >=0.5.1,<=0.5.2 - ml_dtypes >=0.4.0 - - numpy >=1.24 - - opt-einsum + - numpy >=1.25 + - opt_einsum - python >=3.10 - - scipy >=1.10 + - scipy >=1.11.1 constrains: - - cudnn >=9.2.1.18 + - cudnn >=9.2.1.18,<10.0 license: Apache-2.0 license_family: APACHE - size: 1430482 - timestamp: 1733731330348 -- kind: conda - name: jaxlib - version: 0.4.35 - build: cpu_py312h7d5f655_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cpu_py312h7d5f655_0.conda - sha256: 4832163194f53de12d44446dc15226295fed77fbce5e5b8f1bbe22d8f4c1600f - md5: 8e8963097493140ce218084632be7424 + size: 1556886 + timestamp: 1741182198677 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cpu_py312h860c521_1.conda + sha256: 1fcc1bf0bef2ff4a072744d57e4f9cb5b7a4c75191d2a18767b4fcfbac76fc8c + md5: 338663f410794bf924e6264060071cfb depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libgcc >=13 - - libgrpc >=1.67.1,<1.68.0a0 + - libgrpc >=1.71.0,<1.72.0a0 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - ml_dtypes >=0.2.0 - numpy >=1.19,<3 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.4.1,<4.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - scipy >=1.9 constrains: - - jax >=0.4.35 + - jax >=0.5.2 license: Apache-2.0 license_family: APACHE - size: 58146105 - timestamp: 1733957097919 -- kind: conda - name: jaxlib - version: 0.4.35 - build: cuda126py312hd27b167_200 - build_number: 200 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.4.35-cuda126py312hd27b167_200.conda - sha256: 789319c6c97420714cc00b464eeec3f7feb3bdd5481efc607c2d42dcfe3a2574 - md5: e0fd05b260c335750c151466b645254d + size: 69281416 + timestamp: 1741977510646 +- conda: https://conda.anaconda.org/conda-forge/linux-64/jaxlib-0.5.2-cuda126py312hbc630d6_201.conda + sha256: 783146a85c17ebca17e05397fd8a739344d6bdb7b322ce206a72e4a737ef2df7 + md5: a914a0aa5e18831a85722d8fc8fa731d depends: - __cuda - __glibc >=2.17,<3.0.a0 @@ -2707,9 +2301,9 @@ packages: - cuda-nvcc-tools - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libcublas >=12.6.4.1,<13.0a0 - libcublas-dev - libcufft >=11.3.0.4,<12.0a0 @@ -2720,47 +2314,34 @@ packages: - libcusolver-dev - libcusparse >=12.5.4.2,<13.0a0 - libcusparse-dev - - libgcc >=12 - - libgrpc >=1.67.1,<1.68.0a0 - - libstdcxx >=12 + - libgcc >=13 + - libgrpc >=1.71.0,<1.72.0a0 + - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - ml_dtypes >=0.2.0 - - nccl >=2.23.4.1,<3.0a0 + - nccl >=2.25.1.1,<3.0a0 - numpy >=1.19,<3 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.4.1,<4.0a0 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - scipy >=1.9 constrains: - - jax >=0.4.35 + - jax >=0.5.2 license: Apache-2.0 license_family: APACHE - size: 135857260 - timestamp: 1733960818430 -- kind: conda - name: jinja2 - version: 3.1.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.5-pyhd8ed1ab_0.conda - sha256: 98977694b9ecaa3218662f843425f39501f81973c450f995eec68f1803ed71c3 - md5: 2752a6ed44105bfb18c9bef1177d9dcd + size: 150896132 + timestamp: 1741987809555 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhd8ed1ab_0.conda + sha256: f1ac18b11637ddadc05642e8185a851c7fab5998c6f5470d716812fae943b2af + md5: 446bd6c8cb26050d528881df495ce646 depends: - markupsafe >=2.0 - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 112561 - timestamp: 1734824044952 -- kind: conda - name: kernel-headers_linux-64 - version: 3.10.0 - build: he073ed8_18 - build_number: 18 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda + size: 112714 + timestamp: 1741263433881 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-3.10.0-he073ed8_18.conda sha256: a922841ad80bd7b222502e65c07ecb67e4176c4fa5b03678a005f39fcc98be4b md5: ad8527bf134a90e1c9ed35fa0b64318c constrains: @@ -2769,432 +2350,319 @@ packages: license_family: GPL size: 943486 timestamp: 1729794504440 -- kind: conda - name: ld_impl_linux-64 - version: '2.43' - build: h712a8e2_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_2.conda - sha256: 7c91cea91b13f4314d125d1bedb9d03a29ebbd5080ccdea70260363424646dbe - md5: 048b02e3962f066da18efe3a21b77672 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.43-h712a8e2_4.conda + sha256: db73f38155d901a610b2320525b9dd3b31e4949215c870685fd92ea61b5ce472 + md5: 01f8d123c96816249efd255a31ad7712 depends: - __glibc >=2.17,<3.0.a0 constrains: - binutils_impl_linux-64 2.43 license: GPL-3.0-only license_family: GPL - size: 669211 - timestamp: 1729655358674 -- kind: conda - name: libabseil - version: '20240722.0' - build: cxx17_hbbce691_4 - build_number: 4 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20240722.0-cxx17_hbbce691_4.conda - sha256: 143a586aa67d50622ef703de57b9d43f44945836d6568e0e7aa174bd8c45e0d4 - md5: 488f260ccda0afaf08acb286db439c2f + size: 671240 + timestamp: 1740155456116 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20250127.1-cxx17_hbbce691_0.conda + sha256: 65d5ca837c3ee67b9d769125c21dc857194d7f6181bb0e7bd98ae58597b457d0 + md5: 00290e549c5c8a32cc271020acc9ec6b depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 constrains: - - libabseil-static =20240722.0=cxx17* - - abseil-cpp =20240722.0 + - abseil-cpp =20250127.1 + - libabseil-static =20250127.1=cxx17* license: Apache-2.0 license_family: Apache - size: 1311599 - timestamp: 1736008414161 -- kind: conda - name: libblas - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_mkl.conda - sha256: 11cc33993e1865e6caa3e05f117effb3f7cbacc632e5adc572ffd36b4fa47241 - md5: 60463d3ec26e0860bfc7fc1547e005ef + size: 1325007 + timestamp: 1742369558286 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_h59b9bed_openblas.conda + build_number: 31 + sha256: 9839fc4ac0cbb0aa3b9eea520adfb57311838959222654804e58f6f2d1771db5 + md5: 728dbebd0f7a20337218beacffd37916 + depends: + - libopenblas >=0.3.29,<0.3.30.0a0 + - libopenblas >=0.3.29,<1.0a0 + constrains: + - liblapacke =3.9.0=31*_openblas + - liblapack =3.9.0=31*_openblas + - blas =2.131=openblas + - mkl <2025 + - libcblas =3.9.0=31*_openblas + license: BSD-3-Clause + license_family: BSD + size: 16859 + timestamp: 1740087969120 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-31_hfdb39a5_mkl.conda + build_number: 31 + sha256: 862289f2cfb84bb6001d0e3569e908b8c42d66b881bd5b03f730a3924628b978 + md5: bdf4a57254e8248222cb631db4393ff1 depends: - mkl >=2024.2.2,<2025.0a0 constrains: - - liblapack 3.9.0 26_linux64_mkl - - blas * mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl + - liblapack =3.9.0=31*_mkl + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl + - libcblas =3.9.0=31*_mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16766 - timestamp: 1734432542498 -- kind: conda - name: libblas - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-26_linux64_openblas.conda - sha256: 30bd658682b124243f8e52d8edf8a19e7be1bc31e4fe4baec30a64002dc8cd0c - md5: ac52800af2e0c0e7dac770b435ce768a - depends: - - libopenblas >=0.3.28,<0.3.29.0a0 - - libopenblas >=0.3.28,<1.0a0 - constrains: - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas - license: BSD-3-Clause - license_family: BSD - size: 16393 - timestamp: 1734432564346 -- kind: conda - name: libcap - version: '2.71' - build: h39aace5_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.71-h39aace5_0.conda - sha256: 2bbefac94f4ab8ff7c64dc843238b6c8edcc9ff1f2b5a0a48407a904dc7ccfb2 - md5: dd19e4e3043f6948bd7454b946ee0983 + size: 17259 + timestamp: 1740087718283 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcap-2.75-h39aace5_0.conda + sha256: 9c84448305e7c9cc44ccec7757cf5afcb5a021f4579aa750a1fa6ea398783950 + md5: c44c16d6976d2aebbd65894d7741e67e depends: - __glibc >=2.17,<3.0.a0 - attr >=2.5.1,<2.6.0a0 - libgcc >=13 license: BSD-3-Clause license_family: BSD - size: 102268 - timestamp: 1729940917945 -- kind: conda - name: libcblas - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_mkl.conda - sha256: 23866eb509e5896b8fcf647e9cef8f0923d5bb378c0dd14b44b94abe1b24c4d7 - md5: 760c109bfe25518d6f9af51d7af8b9f3 - depends: - - libblas 3.9.0 26_linux64_mkl + size: 120375 + timestamp: 1741176638215 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_h372d94f_mkl.conda + build_number: 31 + sha256: 2ee3ab2b6eeb59f2d3c6f933fa0db28f1b56f0bc543ed2c0f6ec04060e4b6ec0 + md5: 2a06a6c16b45bd3d10002927ca204b67 + depends: + - libblas 3.9.0 31_hfdb39a5_mkl constrains: - - liblapack 3.9.0 26_linux64_mkl - - blas * mkl - - liblapacke 3.9.0 26_linux64_mkl + - liblapack =3.9.0=31*_mkl + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16269 - timestamp: 1734432548754 -- kind: conda - name: libcblas - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-26_linux64_openblas.conda - sha256: 9c74e536c9bc868e356ffd43f81c2cb398aec84b40fcadc312315b164a5500ee - md5: ebcc5f37a435aa3c19640533c82f8d76 - depends: - - libblas 3.9.0 26_linux64_openblas + size: 16724 + timestamp: 1740087727554 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-31_he106b2a_openblas.conda + build_number: 31 + sha256: ede8545011f5b208b151fe3e883eb4e31d495ab925ab7b9ce394edca846e0c0d + md5: abb32c727da370c481a1c206f5159ce9 + depends: + - libblas 3.9.0 31_h59b9bed_openblas constrains: - - liblapack 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas + - liblapacke =3.9.0=31*_openblas + - liblapack =3.9.0=31*_openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16336 - timestamp: 1734432570482 -- kind: conda - name: libcublas - version: 12.6.4.1 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.6.4.1-hbd13f7d_0.conda - sha256: 99ac5f733effaabf30db0f9bf69f8969597834251cbe2ecff4b682806c0ad97b - md5: c7124adbde472a7052dc42e3fc8310db + size: 16796 + timestamp: 1740087984429 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-12.9.0.13-h9ab20c4_0.conda + sha256: 18dc7b16b5ab5f397222566b20c450ade1a16f1f2639991cbfe91eef6960ad62 + md5: 9c1477b1793b43fd128dffd240286e98 depends: - - __glibc >=2.17,<3.0.a0 + - __glibc >=2.28,<3.0.a0 - cuda-nvrtc - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 267981139 - timestamp: 1732133541796 -- kind: conda - name: libcublas-dev - version: 12.6.4.1 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.6.4.1-h5888daf_0.conda - sha256: 764f69865e71721be8b1f9fe641aa743bef256e67a2d91f3297c3da6bfdb500e - md5: 4f9c150a55906bb20d02010b2011bb87 + size: 467452297 + timestamp: 1746202246998 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcublas-dev-12.9.0.13-h9ab20c4_0.conda + sha256: 2ace6dd4b60212b3870dfefc63010c77cb486da06aadc46a4426ab340f032689 + md5: fdf825f59f01293b8e335e536296478e depends: - - __glibc >=2.17,<3.0.a0 + - __glibc >=2.28,<3.0.a0 - cuda-crt-dev_linux-64 - cuda-cudart-dev_linux-64 - - cuda-version >=12.6,<12.7.0a0 - - libcublas 12.6.4.1 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcublas 12.9.0.13 h9ab20c4_0 + - libgcc >=13 + - libstdcxx >=13 + constrains: + - libcublas-static >=12.9.0.13 + license: LicenseRef-NVIDIA-End-User-License-Agreement + size: 91998 + timestamp: 1746203009003 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcudss-0.5.0.16-h14340ca_1.conda + sha256: 0fb14ae71efe11429c24b2fa7d82e718fb52f4cf9cad9379dd7c0302e4294373 + md5: 290a26e7caf9bcbdde629db6612e212e + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - cuda-version >=12,<13.0a0 + - libcublas - libgcc >=13 - libstdcxx >=13 constrains: - - libcublas-static >=12.6.4.1 + - libcudss-commlayer-nccl 0.5.0.16 hb92ee24_1 + - libcudss-commlayer-mpi 0.5.0.16 h2f16e9f_1 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 89823 - timestamp: 1732134221381 -- kind: conda - name: libcufft - version: 11.3.0.4 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.3.0.4-hbd13f7d_0.conda - sha256: fc64a2611a15db7baef61efee2059f090b8f866d06b8f65808c8d2ee191cf7db - md5: a296940fa2e0448d066d03bf6b586772 + size: 32293521 + timestamp: 1739909124258 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-11.4.0.6-h5888daf_0.conda + sha256: 09689f760978a77d18bc393ce749b539e1fcc870c0e41f666993be26b0296314 + md5: 498af0c40a20ee97db04d51269f2fd87 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 163772747 - timestamp: 1727808246058 -- kind: conda - name: libcufft-dev - version: 11.3.0.4 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.3.0.4-h5888daf_0.conda - sha256: 6e102281119d38eef0fee707eaa51254db7e9a76c4a9cec6c4b3a6260a4929fa - md5: e51d70f74e9e5241a0bf33fb866e2476 + size: 161845949 + timestamp: 1746193474688 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufft-dev-11.4.0.6-h5888daf_0.conda + sha256: 4966ea4478e602583f8af1ee68e549abd77e9c014302f3ccc11e0cf6b6174275 + md5: 67dc1b5160e2fd24446b8355f3a0f175 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcufft 11.3.0.4 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcufft 11.4.0.6 h5888daf_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcufft-static >=11.3.0.4 + - libcufft-static >=11.4.0.6 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 33554 - timestamp: 1727808683502 -- kind: conda - name: libcufile - version: 1.11.1.6 - build: h12f29b5_4 - build_number: 4 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.11.1.6-h12f29b5_4.conda - sha256: 9ecee7787519cb3591188f3ac02b65f61775e7c790ca11690f3f35b4e1f89721 - md5: 44fd967c18c41e4e5822f339621a47b4 + size: 34188 + timestamp: 1746193845048 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcufile-1.14.0.30-h628e99a_0.conda + sha256: 59807deae0844774301acc8d03d78dbaae8718ab69faca7d203dc689be06d952 + md5: 248bb7bf66da6f601ee99fd24892380c depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 - rdma-core >=55.0 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 921236 - timestamp: 1734164180458 -- kind: conda - name: libcurand - version: 10.3.7.77 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.7.77-hbd13f7d_0.conda - sha256: 58ee962804a9df475638e0e83f1116bfbf00a5e4681ed180eb872990d49d7902 - md5: d8b8a1e6e6205447289cd09212c914ac + size: 971139 + timestamp: 1746193260621 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-10.3.10.19-h9ab20c4_0.conda + sha256: c4576976b8b5ceb060b32d24fc08db5253606256c3c99b42ace343e9be2229db + md5: c745bc0dd1f066e6752c8b2909216b62 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 41790488 - timestamp: 1727807993172 -- kind: conda - name: libcurand-dev - version: 10.3.7.77 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.7.77-h5888daf_0.conda - sha256: 409d598d56536bb23b944dff81508496835ff9f04858cc3c608ba3e34bffb3af - md5: 83a87ce38925eb22b509a8aba3ba3aaf + size: 46161381 + timestamp: 1746193213392 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcurand-dev-10.3.10.19-h9ab20c4_0.conda + sha256: 1d59e844f3a79c19040efc1f15f23e33bb6b13df19bb63714e9b34515fc9d8fc + md5: 9a7e41b2c3cf57f6a3a1aeac35ebebc0 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcurand 10.3.7.77 hbd13f7d_0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcurand 10.3.10.19 h9ab20c4_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcurand-static >=10.3.7.77 + - libcurand-static >=10.3.10.19 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 268460 - timestamp: 1727808054226 -- kind: conda - name: libcusolver - version: 11.7.1.2 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.1.2-hbd13f7d_0.conda - sha256: 3de5457807dd30f9509863324cfbe9d74d20f477dfeb5ed7de68bbb3da4064bd - md5: 035db50d5e949de81e015df72a834e79 + size: 253530 + timestamp: 1746193336357 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-11.7.4.40-h9ab20c4_0.conda + sha256: 4148415e990c51e5e396ea24869415de3996527f92b0e4dc625aa6bcccd50f87 + md5: 9b693f50985ce248765108972099fe55 depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcublas >=12.6.3.3,<12.7.0a0 - - libcusparse >=12.5.4.2,<12.6.0a0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcublas >=12.9.0.13,<12.10.0a0 + - libcusparse >=12.5.9.5,<12.6.0a0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 100482680 - timestamp: 1727816156921 -- kind: conda - name: libcusolver-dev - version: 11.7.1.2 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.1.2-h5888daf_0.conda - sha256: 91270bb03306d89aef2be679c0743c9b2ec6bcbc79dcce2df3f5267aafaeb247 - md5: 9e972a58dc8fc72fb39a0d8e7fc151d6 + size: 201753979 + timestamp: 1746205898951 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusolver-dev-11.7.4.40-h9ab20c4_0.conda + sha256: d6811f35727a6cedc4f6dec20584bcd775fe1cdb367b8cf3e7fd01d2c4439313 + md5: 416a81027b133a2cff0585e31d9dcafe depends: - - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcusolver 11.7.1.2 hbd13f7d_0 + - __glibc >=2.28,<3.0.a0 + - cuda-version >=12.9,<12.10.0a0 + - libcusolver 11.7.4.40 h9ab20c4_0 - libgcc >=13 - libstdcxx >=13 constrains: - - libcusolver-static >=11.7.1.2 + - libcusolver-static >=11.7.4.40 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 60630 - timestamp: 1727816304318 -- kind: conda - name: libcusparse - version: 12.5.4.2 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.4.2-hbd13f7d_0.conda - sha256: 9db5d983d102c20f2cecc494ea22d84c44df37d373982815fc2eb669bf0bd376 - md5: 8186e9de34f321aa34965c1cb72c0c26 + size: 60998 + timestamp: 1746206190695 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-12.5.9.5-h5888daf_0.conda + sha256: 2ae08171a1d207af2046951177f09f771a4ca76e757b8ce4020fa559524800d2 + md5: 2b89788a46b00abd59ffab688868c321 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 + - cuda-version >=12.9,<12.10.0a0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 124403455 - timestamp: 1727811455861 -- kind: conda - name: libcusparse-dev - version: 12.5.4.2 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.4.2-h5888daf_0.conda - sha256: 9db5e524f101b005c0ada807df1109055285f564e78b19aad87e1db46cb13c9f - md5: 48de133da2c0d116b3e7053b8c8dff89 + size: 208851709 + timestamp: 1746195989263 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcusparse-dev-12.5.9.5-h5888daf_0.conda + sha256: 82aef570f27ec0770477b841e16e70db352db7253425818c60d91dddf34f16f2 + md5: 7580baba0294656dda948344452e51c0 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.6,<12.7.0a0 - - libcusparse 12.5.4.2 hbd13f7d_0 + - cuda-version >=12.9,<12.10.0a0 + - libcusparse 12.5.9.5 h5888daf_0 - libgcc >=13 - - libnvjitlink >=12.6.77,<12.7.0a0 + - libnvjitlink >=12.9.41,<12.10.0a0 - libstdcxx >=13 constrains: - - libcusparse-static >=12.5.4.2 + - libcusparse-static >=12.5.9.5 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 51848 - timestamp: 1727811705461 -- kind: conda - name: libexpat - version: 2.6.4 - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.6.4-h5888daf_0.conda - sha256: 56541b98447b58e52d824bd59d6382d609e11de1f8adf20b23143e353d2b8d26 - md5: db833e03127376d461e1e13e76f09b6c + size: 52753 + timestamp: 1746196334627 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.0-h5888daf_0.conda + sha256: 33ab03438aee65d6aa667cf7d90c91e5e7d734c19a67aa4c7040742c0a13d505 + md5: db0bfbe7dd197b68ad5f30333bae6ce0 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 constrains: - - expat 2.6.4.* + - expat 2.7.0.* license: MIT license_family: MIT - size: 73304 - timestamp: 1730967041968 -- kind: conda - name: libffi - version: 3.4.2 - build: h7f98852_5 - build_number: 5 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2 - sha256: ab6e9856c21709b7b517e940ae7028ae0737546122f83c2aa5d692860c3b149e - md5: d645c6d2ac96843a2bfaccd2d62b3ac3 + size: 74427 + timestamp: 1743431794976 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.6-h2dba641_1.conda + sha256: 764432d32db45466e87f10621db5b74363a9f847d2b8b1f9743746cd160f06ab + md5: ede4673863426c0883c0063d853bbd85 depends: - - libgcc-ng >=9.4.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: MIT license_family: MIT - size: 58292 - timestamp: 1636488182923 -- kind: conda - name: libgcc - version: 14.2.0 - build: h77fa898_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.2.0-h77fa898_1.conda - sha256: 53eb8a79365e58849e7b1a068d31f4f9e718dc938d6f2c03e960345739a03569 - md5: 3cb76c3f10d3bc7f1105b2fc9db984df + size: 57433 + timestamp: 1743434498161 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.1.0-h767d61c_2.conda + sha256: 0024f9ab34c09629621aefd8603ef77bf9d708129b0dd79029e502c39ffc2195 + md5: ea8ac52380885ed41c1baa8f1d6d2b93 depends: - - _libgcc_mutex 0.1 conda_forge + - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 constrains: - - libgomp 14.2.0 h77fa898_1 - - libgcc-ng ==14.2.0=*_1 + - libgcc-ng ==15.1.0=*_2 + - libgomp 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 848745 - timestamp: 1729027721139 -- kind: conda - name: libgcc-devel_linux-64 - version: 13.3.0 - build: h84ea5a7_101 - build_number: 101 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-h84ea5a7_101.conda - sha256: 027cfb011328a108bc44f512a2dec6d954db85709e0b79b748c3392f85de0c64 - md5: 0ce69d40c142915ac9734bc6134e514a + size: 829108 + timestamp: 1746642191935 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-13.3.0-hc03c837_102.conda + sha256: 538544a2e0651bfeb0348ca6469b6b608606f6080a0b5a531af3a3852fec0215 + md5: 4c1d6961a6a54f602ae510d9bf31fa60 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 2598313 - timestamp: 1724801050802 -- kind: conda - name: libgcc-ng - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.2.0-h69a702a_1.conda - sha256: 3a76969c80e9af8b6e7a55090088bc41da4cffcde9e2c71b17f44d37b7cb87f7 - md5: e39480b9ca41323497b05492a63bc35b - depends: - - libgcc 14.2.0 h77fa898_1 + size: 2597400 + timestamp: 1740240211859 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.1.0-h69a702a_2.conda + sha256: 0ab5421a89f090f3aa33841036bb3af4ed85e1f91315b528a9d75fab9aad51ae + md5: ddca86c7040dd0e73b2b69bd7833d225 + depends: + - libgcc 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54142 - timestamp: 1729027726517 -- kind: conda - name: libgcrypt-lib - version: 1.11.0 - build: hb9d3cd8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda + size: 34586 + timestamp: 1746642200749 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-lib-1.11.0-hb9d3cd8_2.conda sha256: ffc3602f9298da248786f46b00d0594d26a18feeb1b07ce88f3d7d61075e39e6 md5: e55712ff40a054134d51b89afca57dbc depends: @@ -3204,146 +2672,76 @@ packages: license: LGPL-2.1-or-later size: 586185 timestamp: 1732523190369 -- kind: conda - name: libgfortran - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-14.2.0-h69a702a_1.conda - sha256: fc9e7f22a17faf74da904ebfc4d88699013d2992e55505e4aa0eb01770290977 - md5: f1fd30127802683586f768875127a987 - depends: - - libgfortran5 14.2.0 hd5240d6_1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.1.0-h69a702a_2.conda + sha256: 914daa4f632b786827ea71b5e07cd00d25fc6e67789db2f830dc481eec660342 + md5: f92e6e0a3c0c0c85561ef61aa59d555d + depends: + - libgfortran5 15.1.0 hcea5267_2 constrains: - - libgfortran-ng ==14.2.0=*_1 + - libgfortran-ng ==15.1.0=*_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 53997 - timestamp: 1729027752995 -- kind: conda - name: libgfortran-ng - version: 14.2.0 - build: h69a702a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-14.2.0-h69a702a_1.conda - sha256: 423f1e2403f0c665748e42d335e421e53fd03c08d457cfb6f360d329d9459851 - md5: 0a7f4cd238267c88e5d69f7826a407eb - depends: - - libgfortran 14.2.0 h69a702a_1 + size: 34541 + timestamp: 1746642233221 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-15.1.0-h69a702a_2.conda + sha256: 0665170a98c8ec586352929d45a9c833c0dcdbead38b0b8f3af7a0deee2af755 + md5: a483a87b71e974bb75d1b9413d4436dd + depends: + - libgfortran 15.1.0 h69a702a_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54106 - timestamp: 1729027945817 -- kind: conda - name: libgfortran5 - version: 14.2.0 - build: hd5240d6_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-14.2.0-hd5240d6_1.conda - sha256: d149a37ca73611e425041f33b9d8dbed6e52ec506fe8cc1fc0ee054bddeb6d5d - md5: 9822b874ea29af082e5d36098d25427d - depends: - - libgcc >=14.2.0 + size: 34616 + timestamp: 1746642441079 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.1.0-hcea5267_2.conda + sha256: be23750f3ca1a5cb3ada858c4f633effe777487d1ea35fddca04c0965c073350 + md5: 01de444988ed960031dbe84cf4f9b1fc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15.1.0 constrains: - - libgfortran 14.2.0 + - libgfortran 15.1.0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 1462645 - timestamp: 1729027735353 -- kind: conda - name: libgomp - version: 14.2.0 - build: h77fa898_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.2.0-h77fa898_1.conda - sha256: 1911c29975ec99b6b906904040c855772ccb265a1c79d5d75c8ceec4ed89cd63 - md5: cc3573974587f12dda90d96e3e55a702 + size: 1569986 + timestamp: 1746642212331 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.1.0-h767d61c_2.conda + sha256: 05fff3dc7e80579bc28de13b511baec281c4343d703c406aefd54389959154fb + md5: fbe7d535ff9d3a168c148e07358cd5b1 depends: - - _libgcc_mutex 0.1 conda_forge + - __glibc >=2.17,<3.0.a0 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 460992 - timestamp: 1729027639220 -- kind: conda - name: libgpg-error - version: '1.51' - build: hbd13f7d_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.51-hbd13f7d_1.conda - sha256: 9e0c09c1faf2151ade3ccb64e52d3c1f2dde85c00e37c6a3e6a8bced2aba68be - md5: 168cc19c031482f83b23c4eebbb94e26 + size: 452635 + timestamp: 1746642113092 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.55-h3f2d84a_0.conda + sha256: 697334de4786a1067ea86853e520c64dd72b11a05137f5b318d8a444007b5e60 + md5: 2bd47db5807daade8500ed7ca4c512a4 depends: - - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - libstdcxx >=13 - license: LGPL-2.1-only - license_family: GPL - size: 268740 - timestamp: 1731920927644 -- kind: conda - name: libgrpc - version: 1.67.1 - build: h25350d4_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-h25350d4_1.conda - sha256: 014627485b3cf0ea18e04c0bab07be7fb98722a3aeeb58477acc7e1c3d2f911e - md5: 0c6497a760b99a926c7c12b74951a39c - depends: + - libgcc >=13 - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.4,<2.0a0 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 - - libre2-11 >=2024.7.2 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 - - openssl >=3.4.0,<4.0a0 - - re2 - constrains: - - grpc-cpp =1.67.1 - license: Apache-2.0 - license_family: APACHE - size: 7792251 - timestamp: 1735584856826 -- kind: conda - name: libgrpc - version: 1.67.1 - build: hc2c308b_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.67.1-hc2c308b_0.conda - sha256: 870550c1faf524e9a695262cd4c31441b18ad542f16893bd3c5dbc93106705f7 - md5: 4606a4647bfe857e3cfe21ca12ac3afb + license: LGPL-2.1-only + size: 312184 + timestamp: 1745575272035 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgrpc-1.71.0-h8e591d7_1.conda + sha256: 37267300b25f292a6024d7fd9331085fe4943897940263c3a41d6493283b2a18 + md5: c3cfd72cbb14113abee7bbd86f44ad69 depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.32.3,<2.0a0 + - c-ares >=1.34.5,<2.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 - libgcc >=13 - - libprotobuf >=5.28.2,<5.28.3.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libre2-11 >=2024.7.2 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.3.2,<4.0a0 + - openssl >=3.5.0,<4.0a0 - re2 constrains: - - grpc-cpp =1.67.1 + - grpc-cpp =1.71.0 license: Apache-2.0 license_family: APACHE - size: 7362336 - timestamp: 1730236333879 -- kind: conda - name: libhiredis - version: 1.0.2 - build: h2cc385e_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 + size: 7920187 + timestamp: 1745229332239 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhiredis-1.0.2-h2cc385e_0.tar.bz2 sha256: ee39c69df4fb39cfe1139ac4f7405bb066eba773e11ba3ab7c33835be00c2e48 md5: b34907d3a81a3cd8095ee83d174c074a depends: @@ -3355,142 +2753,102 @@ packages: license_family: BSD size: 147325 timestamp: 1633982069195 -- kind: conda - name: libhwloc - version: 2.11.2 - build: default_h0d58e46_1001 - build_number: 1001 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.11.2-default_h0d58e46_1001.conda sha256: d14c016482e1409ae1c50109a9ff933460a50940d2682e745ab1c172b5282a69 md5: 804ca9e91bcaea0824a341d55b1684f2 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libstdcxx >=13 - - libxml2 >=2.13.4,<3.0a0 + - libxml2 >=2.13.4,<2.14.0a0 license: BSD-3-Clause license_family: BSD size: 2423200 timestamp: 1731374922090 -- kind: conda - name: libiconv - version: '1.17' - build: hd590300_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-hd590300_2.conda - sha256: 8ac2f6a9f186e76539439e50505d98581472fedb347a20e7d1f36429849f05c9 - md5: d66573916ffcf376178462f1b61c941e +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h4ce23a2_1.conda + sha256: 18a4afe14f731bfb9cf388659994263904d20111e42f841e9eea1bb6f91f4ab4 + md5: e796ff8ddc598affdf7c173d6145f087 depends: - - libgcc-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 license: LGPL-2.1-only - size: 705775 - timestamp: 1702682170569 -- kind: conda - name: liblapack - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_mkl.conda - sha256: 4ab8f00c325e1aacb6edc881b39c7c294adafc9d485cdde82979d1617fcd1e6f - md5: 84112111a50db59ca64153e0054fa73e - depends: - - libblas 3.9.0 26_linux64_mkl + size: 713084 + timestamp: 1740128065462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_h7ac8fdf_openblas.conda + build_number: 31 + sha256: f583661921456e798aba10972a8abbd9d33571c655c1f66eff450edc9cbefcf3 + md5: 452b98eafe050ecff932f0ec832dd03f + depends: + - libblas 3.9.0 31_h59b9bed_openblas constrains: - - blas * mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapacke 3.9.0 26_linux64_mkl - track_features: - - blas_mkl + - libcblas =3.9.0=31*_openblas + - liblapacke =3.9.0=31*_openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16302 - timestamp: 1734432554916 -- kind: conda - name: liblapack - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-26_linux64_openblas.conda - sha256: b76458c36331376911e0f98fa68109e02f4d5e5ebfffa79587ac69cef748bba1 - md5: 3792604c43695d6a273bc5faaac47d48 - depends: - - libblas 3.9.0 26_linux64_openblas + size: 16790 + timestamp: 1740087997375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-31_hc41d3b0_mkl.conda + build_number: 31 + sha256: a2d20845d916ac8fba09376cd791136a9b4547afb2131bc315178adfc87bb4ca + md5: 10d012ddd7cc1c7ff9093d4974a34e53 + depends: + - libblas 3.9.0 31_hfdb39a5_mkl constrains: - - libcblas 3.9.0 26_linux64_openblas - - liblapacke 3.9.0 26_linux64_openblas - - blas * openblas + - liblapacke =3.9.0=31*_mkl + - blas =2.131=mkl + - libcblas =3.9.0=31*_mkl + track_features: + - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16338 - timestamp: 1734432576650 -- kind: conda - name: liblapacke - version: 3.9.0 - build: 26_linux64_mkl - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_mkl.conda - sha256: 53b722b298222d1f3f1e3ad06c6fb4f0adb81b06e7ec2bd858627b2c79b0aea5 - md5: ffd5d8a606a1bd0e914f276dc44b42ee - depends: - - libblas 3.9.0 26_linux64_mkl - - libcblas 3.9.0 26_linux64_mkl - - liblapack 3.9.0 26_linux64_mkl + size: 16760 + timestamp: 1740087736615 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_hbc6e62b_mkl.conda + build_number: 31 + sha256: 3be711aadec095377094f861574d9327d98a6ffabb54ef48bb6669f63b128c61 + md5: 562026e418363dc346ad5a9e18cce73c + depends: + - libblas 3.9.0 31_hfdb39a5_mkl + - libcblas 3.9.0 31_h372d94f_mkl + - liblapack 3.9.0 31_hc41d3b0_mkl constrains: - - blas * mkl + - blas =2.131=mkl track_features: - blas_mkl license: BSD-3-Clause license_family: BSD - size: 16281 - timestamp: 1734432561155 -- kind: conda - name: liblapacke - version: 3.9.0 - build: 26_linux64_openblas - build_number: 26 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-26_linux64_openblas.conda - sha256: 90807199c20500d959cc37ca666f74286b20637d9d1de53cb0730802ed459c9a - md5: 7b8b7732fb4786c00cf9b67d1d69445c - depends: - - libblas 3.9.0 26_linux64_openblas - - libcblas 3.9.0 26_linux64_openblas - - liblapack 3.9.0 26_linux64_openblas + size: 16780 + timestamp: 1740087745326 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.9.0-31_he2f377e_openblas.conda + build_number: 31 + sha256: 510bfe8717ab6e7a19e2b0985c27629ddf89270dbd38def8c821f7f683a369a3 + md5: 7e5fff7d0db69be3a266f7e79a3bb0e2 + depends: + - libblas 3.9.0 31_h59b9bed_openblas + - libcblas 3.9.0 31_he106b2a_openblas + - liblapack 3.9.0 31_h7ac8fdf_openblas constrains: - - blas * openblas + - blas =2.131=openblas license: BSD-3-Clause license_family: BSD - size: 16332 - timestamp: 1734432582792 -- kind: conda - name: liblzma - version: 5.6.3 - build: hb9d3cd8_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.6.3-hb9d3cd8_1.conda - sha256: e6e425252f3839e2756e4af1ea2074dffd3396c161bf460629f9dfd6a65f15c6 - md5: 2ecf2f1c7e4e21fcfe6423a51a992d84 + size: 16819 + timestamp: 1740088012246 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.1-hb9d3cd8_1.conda + sha256: eeff241bddc8f1b87567dd6507c9f441f7f472c27f0860a07628260c000ef27c + md5: a76fd702c93cd2dfd89eff30a5fd45a8 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 + constrains: + - xz 5.8.1.* + - xz ==5.8.1=*_1 license: 0BSD - size: 111132 - timestamp: 1733407410083 -- kind: conda - name: libmagma - version: 2.8.0 - build: h566cb83_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.8.0-h566cb83_2.conda - sha256: b8999f6dfdcdd3d0531271bd6f45e4842561d44018c9e34f24d31d6d0c73c4d2 - md5: b6818d8ad575df8faace47ee560e0630 + size: 112845 + timestamp: 1746531470399 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmagma-2.9.0-h19665d7_1.conda + sha256: 13d50a4f7da02e6acce4b5b6df82072c0f447a2c5ba1f4a3190dfec3a9174965 + md5: 38b3447782263c96b0c0a7b92c97575e depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 @@ -3504,39 +2862,9 @@ packages: - libstdcxx >=13 license: BSD-3-Clause license_family: BSD - size: 296058740 - timestamp: 1734990709538 -- kind: conda - name: libmagma_sparse - version: 2.8.0 - build: h0af6554_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libmagma_sparse-2.8.0-h0af6554_0.conda - sha256: f20a4cc53548c2cf8a4cc36502f294aa5e37c4ec3d2930ebd80a7e51d0c851b7 - md5: f506a12b434490e2368a9f2b70b10053 - depends: - - __glibc >=2.17,<3.0.a0 - - _openmp_mutex >=4.5 - - cuda-cudart >=12.0.107,<13.0a0 - - cuda-version >=12.0,<13 - - libblas >=3.9.0,<4.0a0 - - libcublas >=12.0.1.189,<13.0a0 - - libcusparse >=12.0.0.76,<13.0a0 - - libgcc-ng >=12 - - liblapack >=3.9.0,<4.0a0 - - libmagma 2.8.0.* - - libmagma >=2.8.0,<2.8.1.0a0 - - libstdcxx-ng >=12 - license: BSD-3-Clause - license_family: BSD - size: 6731088 - timestamp: 1721861326572 -- kind: conda - name: libnl - version: 3.11.0 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda + size: 371275523 + timestamp: 1739994057566 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnl-3.11.0-hb9d3cd8_0.conda sha256: ba7c5d294e3d80f08ac5a39564217702d1a752e352e486210faff794ac5001b4 md5: db63358239cbe1ff86242406d440e44a depends: @@ -3546,12 +2874,7 @@ packages: license_family: LGPL size: 741323 timestamp: 1731846827427 -- kind: conda - name: libnsl - version: 2.0.1 - build: hd590300_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hd590300_0.conda sha256: 26d77a3bb4dceeedc2a41bd688564fe71bf2d149fdcf117049970bc02ff1add6 md5: 30fd6e37fe21f86f4bd26d6ee73eeec7 depends: @@ -3560,294 +2883,201 @@ packages: license_family: GPL size: 33408 timestamp: 1697359010159 -- kind: conda - name: libnvjitlink - version: 12.6.85 - build: hbd13f7d_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.6.85-hbd13f7d_0.conda - sha256: f8af058f7ba2e436f6bbeaabe273a6e88d6193028572769c8402bbee2bdfa95d - md5: dca2d62b3812922e6976f76c0a401918 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnvjitlink-12.9.41-h5888daf_0.conda + sha256: 363335da59cb71e6576087c98b13e7e13289b8c05b140b09de2e5e9bd06e675b + md5: fa47324d7e1e78492c2f17f0ce67e906 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12,<12.7.0a0 + - cuda-version >=12,<12.10.0a0 - libgcc >=13 - libstdcxx >=13 license: LicenseRef-NVIDIA-End-User-License-Agreement - size: 15590703 - timestamp: 1732133239776 -- kind: conda - name: libopenblas - version: 0.3.28 - build: pthreads_h94d23a6_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.28-pthreads_h94d23a6_1.conda - sha256: 99ba271d8a80a1af2723f2e124ffd91d850074c0389c067e6d96d72a2dbfeabe - md5: 62857b389e42b36b686331bec0922050 + size: 30491008 + timestamp: 1746190924588 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.29-pthreads_h94d23a6_0.conda + sha256: cc5389ea254f111ef17a53df75e8e5209ef2ea6117e3f8aced88b5a8e51f11c4 + md5: 0a4d0252248ef9a0f88f2ba8b8a08e12 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.2.0 constrains: - - openblas >=0.3.28,<0.3.29.0a0 - license: BSD-3-Clause - license_family: BSD - size: 5578513 - timestamp: 1730772671118 -- kind: conda - name: libprotobuf - version: 5.28.2 - build: h5b01275_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.2-h5b01275_0.conda - sha256: 5e8fd4aa00193c85602ce6101dd28fe31306dff85c9725048f6dc828dfa7c421 - md5: ab0bff36363bec94720275a681af8b83 - depends: - - __glibc >=2.17,<3.0.a0 - - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 - - libgcc >=13 - - libstdcxx >=13 - - libzlib >=1.3.1,<2.0a0 + - openblas >=0.3.29,<0.3.30.0a0 license: BSD-3-Clause license_family: BSD - size: 2945348 - timestamp: 1728565355702 -- kind: conda - name: libprotobuf - version: 5.28.3 - build: h6128344_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.28.3-h6128344_1.conda - sha256: 51125ebb8b7152e4a4e69fd2398489c4ec8473195c27cde3cbdf1cb6d18c5493 - md5: d8703f1ffe5a06356f06467f1d0b9464 + size: 5919288 + timestamp: 1739825731827 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-5.29.3-h501fc15_1.conda + sha256: 691af28446345674c6b3fb864d0e1a1574b6cc2f788e0f036d73a6b05dcf81cf + md5: edb86556cf4a0c133e7932a1597ff236 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 - libgcc >=13 - libstdcxx >=13 - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - size: 2960815 - timestamp: 1735577210663 -- kind: conda - name: libre2-11 - version: 2024.07.02 - build: hbbce691_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hbbce691_2.conda - sha256: 4420f8362c71251892ba1eeb957c5e445e4e1596c0c651c28d0d8b415fe120c7 - md5: b2fede24428726dd867611664fb372e8 + size: 3358788 + timestamp: 1745159546868 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libre2-11-2024.07.02-hba17884_3.conda + sha256: 392ec1e49370eb03270ffd4cc8d727f8e03e1e3a92b12f10c53f396ae4554668 + md5: 545e93a513c10603327c76c15485e946 depends: - __glibc >=2.17,<3.0.a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.0,<20250128.0a0 - libgcc >=13 - libstdcxx >=13 constrains: - re2 2024.07.02.* license: BSD-3-Clause license_family: BSD - size: 209793 - timestamp: 1735541054068 -- kind: conda - name: libsanitizer - version: 13.3.0 - build: heb74ff8_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-heb74ff8_1.conda - sha256: c86d130f0a3099e46ff51aa7ffaab73cb44fc420d27a96076aab3b9a326fc137 - md5: c4cb22f270f501f5c59a122dc2adf20a + size: 210073 + timestamp: 1741121121238 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-13.3.0-he8ea267_2.conda + sha256: 27c4c8bf8e2dd60182d47274389be7c70446df6ed5344206266321ee749158b4 + md5: 2b6cdf7bb95d3d10ef4e38ce0bc95dba depends: + - __glibc >=2.17,<3.0.a0 - libgcc >=13.3.0 - libstdcxx >=13.3.0 license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 4133922 - timestamp: 1724801171589 -- kind: conda - name: libsqlite - version: 3.47.2 - build: hee588c1_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.47.2-hee588c1_0.conda - sha256: 48af21ebc2cbf358976f1e0f4a0ab9e91dfc83d0ef337cf3837c6f5bc22fb352 - md5: b58da17db24b6e08bcbf8fed2fb8c915 + size: 4155341 + timestamp: 1740240344242 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.49.2-hee588c1_0.conda + sha256: 525d4a0e24843f90b3ff1ed733f0a2e408aa6dd18b9d4f15465595e078e104a2 + md5: 93048463501053a00739215ea3f36324 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libzlib >=1.3.1,<2.0a0 license: Unlicense - size: 873551 - timestamp: 1733761824646 -- kind: conda - name: libstdcxx - version: 14.2.0 - build: hc0a3c3a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda - sha256: 4661af0eb9bdcbb5fb33e5d0023b001ad4be828fccdcc56500059d56f9869462 - md5: 234a5554c53625688d51062645337328 - depends: - - libgcc 14.2.0 h77fa898_1 + size: 916313 + timestamp: 1746637007836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.1.0-h8f9b012_2.conda + sha256: 6ae3d153e78f6069d503d9309f2cac6de5b93d067fc6433160a4c05226a5dad4 + md5: 1cb1c67961f6dd257eae9e9691b341aa + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 15.1.0 h767d61c_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 3893695 - timestamp: 1729027746910 -- kind: conda - name: libstdcxx-devel_linux-64 - version: 13.3.0 - build: h84ea5a7_101 - build_number: 101 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-h84ea5a7_101.conda - sha256: 0a9226c1b994f996229ffb54fa40d608cd4e4b48e8dc73a66134bea8ce949412 - md5: 29b5a4ed4613fa81a07c21045e3f5bf6 + size: 3902355 + timestamp: 1746642227493 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-13.3.0-hc03c837_102.conda + sha256: abc89056d4ca7debe938504b3b6d9ccc6d7a0f0b528fe3409230636a21e81002 + md5: aa38de2738c5f4a72a880e3d31ffe8b4 depends: - __unix license: GPL-3.0-only WITH GCC-exception-3.1 license_family: GPL - size: 14074676 - timestamp: 1724801075448 -- kind: conda - name: libstdcxx-ng - version: 14.2.0 - build: h4852527_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-14.2.0-h4852527_1.conda - sha256: 25bb30b827d4f6d6f0522cc0579e431695503822f144043b93c50237017fffd8 - md5: 8371ac6457591af2cf6159439c1fd051 - depends: - - libstdcxx 14.2.0 hc0a3c3a_1 + size: 12873130 + timestamp: 1740240239655 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.1.0-h4852527_2.conda + sha256: 11bea86e11de7d6bce87589197a383344df3fa0a3552dab7e931785ff1159a5b + md5: 9d2072af184b5caa29492bf2344597bb + depends: + - libstdcxx 15.1.0 h8f9b012_2 license: GPL-3.0-only WITH GCC-exception-3.1 - license_family: GPL - size: 54105 - timestamp: 1729027780628 -- kind: conda - name: libsystemd0 - version: '256.9' - build: h0b6a36f_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-256.9-h0b6a36f_2.conda - sha256: 28e1a3c4bd242e7eb3bd0bcd35e558680d186e7a1d61482d371dde2a0f1bfb42 - md5: 135bbeb376345b6847c065115be4221a + size: 34647 + timestamp: 1746642266826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-257.4-h4e0b6ca_1.conda + sha256: 5aa2ba63747ad3b6e717f025c9d2ab4bb32c0d366e1ef81669ffa73b1d9af4a2 + md5: 04bcf3055e51f8dde6fab9672fb9fca0 depends: - __glibc >=2.17,<3.0.a0 - - libcap >=2.71,<2.72.0a0 + - libcap >=2.75,<2.76.0a0 - libgcc >=13 - libgcrypt-lib >=1.11.0,<2.0a0 - - liblzma >=5.6.3,<6.0a0 + - liblzma >=5.6.4,<6.0a0 - lz4-c >=1.10.0,<1.11.0a0 - - zstd >=1.5.6,<1.6.0a0 + - zstd >=1.5.7,<1.6.0a0 license: LGPL-2.1-or-later - size: 410566 - timestamp: 1733679350245 -- kind: conda - name: libtorch - version: 2.5.1 - build: cpu_mkl_he8ec5d7_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cpu_mkl_he8ec5d7_108.conda - sha256: 96e04252aa1a64c8a50fcccb6e36a0f53f54b7eb9a61b2e1930191b67cce655c - md5: a070bb62918bea542fbb092c2abd7004 + size: 488733 + timestamp: 1741629468703 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cpu_mkl_hf6ddc5a_100.conda + sha256: 7b6178464b02d65c4af92086c71b79e5c2b7fc1500c1547334a4755e6e92d8a9 + md5: 6bdda0b10852c6d03b030bab7ec251f0 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libstdcxx >=13 - - libuv >=1.49.2,<2.0a0 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - sleef >=3.7,<4.0a0 + - sleef >=3.8,<4.0a0 constrains: - - pytorch-cpu ==2.5.1 - - pytorch 2.5.1 cpu_mkl_*_108 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch 2.7.0 cpu_mkl_*_100 + - pytorch-cpu 2.7.0 license: BSD-3-Clause license_family: BSD - size: 53384470 - timestamp: 1736088424107 -- kind: conda - name: libtorch - version: 2.5.1 - build: cuda126_hebb32c0_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.5.1-cuda126_hebb32c0_306.conda - sha256: 8f358f3d4c019c63e091c29060608119076a21d997b8d89e57238d271737416a - md5: 70a1cc9baa999d6e4465ca4626e093ab + size: 55565925 + timestamp: 1746256872466 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.7.0-cuda126_mkl_h99b69db_300.conda + sha256: b4e8c062ddc343be1ff84346ef4f90b258a87d67e747e50a3644a81d1978eb40 + md5: 67d004faec95b8fff704681eae9ccf40 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - cuda-cudart >=12.6.77,<13.0a0 + - cuda-cupti >=12.6.80,<13.0a0 - cuda-nvrtc >=12.6.85,<13.0a0 - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libcublas >=12.6.4.1,<13.0a0 + - libcudss >=0.5.0.16,<0.5.1.0a0 - libcufft >=11.3.0.4,<12.0a0 - libcufile >=1.11.1.6,<2.0a0 - libcurand >=10.3.7.77,<11.0a0 - libcusolver >=11.7.1.2,<12.0a0 - libcusparse >=12.5.4.2,<13.0a0 - - libgcc >=12 - - libmagma >=2.8.0,<2.8.1.0a0 - - libmagma_sparse >=2.8.0,<2.8.1.0a0 - - libprotobuf >=5.28.2,<5.28.3.0a0 - - libstdcxx >=12 - - libuv >=1.49.2,<2.0a0 + - libgcc >=13 + - libmagma >=2.9.0,<2.9.1.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 + - libstdcxx >=13 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - nccl >=2.23.4.1,<3.0a0 - - sleef >=3.7,<4.0a0 + - nccl >=2.26.5.1,<3.0a0 + - sleef >=3.8,<4.0a0 constrains: - - pytorch-gpu ==2.5.1 - - pytorch-cpu ==99999999 - - sysroot_linux-64 >=2.17 - - pytorch 2.5.1 cuda126_*_306 + - pytorch-gpu 2.7.0 + - pytorch-cpu <0.0a0 + - pytorch 2.7.0 cuda126_mkl_*_300 license: BSD-3-Clause license_family: BSD - size: 514038936 - timestamp: 1733651457474 -- kind: conda - name: libudev1 - version: '256.9' - build: h9a4d06a_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libudev1-256.9-h9a4d06a_2.conda - sha256: aa38d000c5963f22f34ba4a73b5311a9d36da452ae34825ca6a4243138d5aab2 - md5: ae7750de534f1a10832bb08ade6f66dd + size: 594396124 + timestamp: 1746283375271 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libudev1-257.4-hbe16f8c_1.conda + sha256: 56e55a7e7380a980b418c282cb0240b3ac55ab9308800823ff031a9529e2f013 + md5: d6716795cd81476ac2f5465f1b1cde75 depends: - __glibc >=2.17,<3.0.a0 - - libcap >=2.71,<2.72.0a0 + - libcap >=2.75,<2.76.0a0 - libgcc >=13 license: LGPL-2.1-or-later - size: 141055 - timestamp: 1733679357863 -- kind: conda - name: libuuid - version: 2.38.1 - build: h0b41bf4_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda + size: 144039 + timestamp: 1741629479455 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.38.1-h0b41bf4_0.conda sha256: 787eb542f055a2b3de553614b25f09eefb0a0931b0c87dbcce6efdfd92f04f18 md5: 40b61aab5c7ba9ff276c41cfffe6b80b depends: @@ -3856,28 +3086,17 @@ packages: license_family: BSD size: 33601 timestamp: 1680112270483 -- kind: conda - name: libuv - version: 1.49.2 - build: hb9d3cd8_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.49.2-hb9d3cd8_0.conda - sha256: a35cd81cd1a9add11024097da83cc06b0aae83186fe4124b77710876f37d8f31 - md5: 070e3c9ddab77e38799d5c30b109c633 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.50.0-hb9d3cd8_0.conda + sha256: b4a8890023902aef9f1f33e3e35603ad9c2f16c21fdb58e968fa6c1bd3e94c0b + md5: 771ee65e13bc599b0b62af5359d80169 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 license: MIT license_family: MIT - size: 884647 - timestamp: 1729322566955 -- kind: conda - name: libxcrypt - version: 4.4.36 - build: hd590300_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda + size: 891272 + timestamp: 1737016632446 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda sha256: 6ae68e0b86423ef188196fff6207ed0c8195dd84273cb5623b85aa08033a410c md5: 5aa797f8787fe7a17d1b0821485b5adc depends: @@ -3885,34 +3104,21 @@ packages: license: LGPL-2.1-or-later size: 100393 timestamp: 1702724383534 -- kind: conda - name: libxml2 - version: 2.13.5 - build: h0d44e9d_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.5-h0d44e9d_1.conda - sha256: 306e18aa647d8208ad2cd0e62d84933222b2fbe93d2d53cd5283d2256b1d54de - md5: f5b05674697ae7d2c5932766695945e1 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.13.8-h4bc477f_0.conda + sha256: b0b3a96791fa8bb4ec030295e8c8bf2d3278f33c0f9ad540e73b5e538e6268e7 + md5: 14dbe05b929e329dbaa6f2d0aa19466d depends: - __glibc >=2.17,<3.0.a0 + - icu >=75.1,<76.0a0 - libgcc >=13 - - libiconv >=1.17,<2.0a0 - - liblzma >=5.6.3,<6.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.1,<6.0a0 - libzlib >=1.3.1,<2.0a0 - constrains: - - icu <0.0a0 license: MIT license_family: MIT - size: 689993 - timestamp: 1733443678322 -- kind: conda - name: libzlib - version: 1.3.1 - build: hb9d3cd8_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + size: 690864 + timestamp: 1746634244154 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 md5: edb0dca6bc32e4f4789199455a1dbeb8 depends: @@ -3924,29 +3130,18 @@ packages: license_family: Other size: 60963 timestamp: 1727963148474 -- kind: conda - name: llvm-openmp - version: 19.1.6 - build: h024ca30_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-19.1.6-h024ca30_0.conda - sha256: 9e385c2a8169d951cf153221fb7fbb3dc8f1e5ac77371edee7329f8721dbe1ae - md5: 96e42ccbd3c067c1713ff5f2d2169247 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-20.1.4-h024ca30_0.conda + sha256: 5b39cdde3457e41b133d6f1fe53095c7fd3951bbdab46580098ccbf5ee9c99f7 + md5: 4fc395cda27912a7d904b86b5dbf3a4d depends: - __glibc >=2.17,<3.0.a0 constrains: - - openmp 19.1.6|19.1.6.* + - openmp 20.1.4|20.1.4.* license: Apache-2.0 WITH LLVM-exception license_family: APACHE - size: 3201572 - timestamp: 1734520399290 -- kind: conda - name: lz4-c - version: 1.10.0 - build: h5888daf_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda + size: 3322195 + timestamp: 1746134424442 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346 md5: 9de5350a85c4a20c685259b889aa6393 depends: @@ -3957,14 +3152,7 @@ packages: license_family: BSD size: 167055 timestamp: 1733741040117 -- kind: conda - name: markdown-it-py - version: 3.0.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_1.conda sha256: 0fbacdfb31e55964152b24d5567e9a9996e1e7902fb08eb7d91b5fd6ce60803a md5: fee3164ac23dfca50cfcc8b85ddefb81 depends: @@ -3974,13 +3162,7 @@ packages: license_family: MIT size: 64430 timestamp: 1733250550053 -- kind: conda - name: markupsafe - version: 3.0.2 - build: py312h178313f_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py312h178313f_1.conda sha256: 4a6bf68d2a2b669fecc9a4a009abd1cf8e72c2289522ff00d81b5a6e51ae78f5 md5: eb227c3e0bf58f5bd69c0532b157975b depends: @@ -3994,14 +3176,7 @@ packages: license_family: BSD size: 24604 timestamp: 1733219911494 -- kind: conda - name: mdurl - version: 0.1.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 md5: 592132998493b3ff25fd7479396e8351 depends: @@ -4010,51 +3185,32 @@ packages: license_family: MIT size: 14465 timestamp: 1733255681319 -- kind: conda - name: meson - version: 1.6.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/meson-1.6.1-pyhd8ed1ab_0.conda - sha256: 879bb593f409858158fd246e17d02c5a126fab46c9b9d4daa0d455f8516c6d7a - md5: 0062fb0a7f5da474705d0ce626de12f4 +- conda: https://conda.anaconda.org/conda-forge/noarch/meson-1.8.0-pyh29332c3_0.conda + sha256: 702c8b92e7d33463b2c3ed226c1d7c3e745d9dedb3d35d0fdca6a47ef32d7e78 + md5: 8e25221b702272394b86b0f4d7217f77 depends: - - ninja >=1.8.2 - python >=3.9 - - setuptools + - ninja >=1.8.2 + - python license: Apache-2.0 license_family: APACHE - size: 657135 - timestamp: 1734395829616 -- kind: conda - name: meson-python - version: 0.17.1 - build: pyh70fd9c4_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.17.1-pyh70fd9c4_1.conda - sha256: 819692fa23d1cfdc05a4106789b413c83de2d0506df2e872c0a705b0df42bc43 - md5: 7a02679229c6c2092571b4c025055440 - depends: - - meson >=0.63.3 + size: 726539 + timestamp: 1745942023589 +- conda: https://conda.anaconda.org/conda-forge/noarch/meson-python-0.18.0-pyh70fd9c4_0.conda + sha256: e4866b9d6609cc69ac01822ae92caee8ec6533a1b770baadc26157f24e363de3 + md5: 576c04b9d9f8e45285fb4d9452c26133 + depends: + - meson >=1.2.3 - ninja - - packaging >=19.0 - - pyproject-metadata >=0.7.1 + - packaging >=23.2 + - pyproject-metadata >=0.9.0 - python >=3.9 - tomli >=1.0.0 license: MIT license_family: MIT - size: 74270 - timestamp: 1733419510995 -- kind: conda - name: mkl - version: 2024.2.2 - build: ha957f24_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda + size: 81997 + timestamp: 1746449677114 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2024.2.2-ha957f24_16.conda sha256: 77906b0acead8f86b489da46f53916e624897338770dbf70b04b8f673c9273c1 md5: 1459379c79dda834673426504d52b319 depends: @@ -4066,13 +3222,7 @@ packages: license_family: Proprietary size: 124718448 timestamp: 1730231808335 -- kind: conda - name: mkl-devel - version: 2024.2.2 - build: ha770c72_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2024.2.2-ha770c72_16.conda sha256: d2c6fb42c36f1ec48fd34192827dee619791b2a4ee73798cbf3cd0e6195ceff4 md5: 140891ea14285fc634353b31e9e40a95 depends: @@ -4082,27 +3232,16 @@ packages: license_family: Proprietary size: 35857 timestamp: 1730232581563 -- kind: conda - name: mkl-include - version: 2024.2.2 - build: ha957f24_16 - build_number: 16 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2024.2.2-ha957f24_16.conda sha256: 4b72b3acd46c69a8fb56d9f5d8240da811820a18be40765df6e2bd8ea859fbc7 md5: 42b0d14354b5910a9f41e29289914f6b license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary size: 716756 timestamp: 1730232137421 -- kind: conda - name: ml_dtypes - version: 0.5.0 - build: py312hf9745cd_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.0-py312hf9745cd_0.conda - sha256: 559c14640ce8e3f2270da6130ba50ae624f3db56176fad29a5436b2dec3fc3b2 - md5: 8ca779f3f30b00181aeee820fe8b22d5 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ml_dtypes-0.5.1-py312hf9745cd_0.conda + sha256: 87928a36d350c470455a322c4c2b82266b88322d0fd5187ae8cc6fb5e3aad61f + md5: c45ac8395a27736c27b2e50b53ffe62c depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 @@ -4111,15 +3250,9 @@ packages: - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 license: MPL-2.0 AND Apache-2.0 - size: 290054 - timestamp: 1726376440408 -- kind: conda - name: mpc - version: 1.3.1 - build: h24ddda3_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda + size: 290991 + timestamp: 1736538940686 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.3.1-h24ddda3_1.conda sha256: 1bf794ddf2c8b3a3e14ae182577c624fa92dea975537accff4bc7e5fea085212 md5: aa14b9a5196a6d8dd364164b7ce56acf depends: @@ -4131,13 +3264,7 @@ packages: license_family: LGPL size: 116777 timestamp: 1725629179524 -- kind: conda - name: mpfr - version: 4.2.1 - build: h90cbb55_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.1-h90cbb55_3.conda sha256: f25d2474dd557ca66c6231c8f5ace5af312efde1ba8290a6ea5e1732a4e669c0 md5: 2eeb50cab6652538eee8fc0bc3340c81 depends: @@ -4148,14 +3275,7 @@ packages: license_family: LGPL size: 634751 timestamp: 1725746740014 -- kind: conda - name: mpmath - version: 1.3.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.3.0-pyhd8ed1ab_1.conda sha256: 7d7aa3fcd6f42b76bd711182f3776a02bef09a68c5f117d66b712a6d81368692 md5: 3585aa87c43ab15b167b574cd73b057b depends: @@ -4164,47 +3284,28 @@ packages: license_family: BSD size: 439705 timestamp: 1733302781386 -- kind: conda - name: nccl - version: 2.23.4.1 - build: h2b5d15b_3 - build_number: 3 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.23.4.1-h2b5d15b_3.conda - sha256: fb327a4623d8d7b967a49104a1a4b111efbfdff6d4a1619d93aa9f3244395b73 - md5: da69647cf84be91a201e2c4138e676ae +- conda: https://conda.anaconda.org/conda-forge/linux-64/nccl-2.26.5.1-ha44e49d_0.conda + sha256: 3a715dab311d045ecd5811b06012ebc7a1b8ce9c899d40952d834bd713fe9ac9 + md5: 45823c363ce0803d29c4a444e4309634 depends: - __glibc >=2.17,<3.0.a0 - - cuda-version >=12.0,<13.0a0 - - libgcc >=12 - - libstdcxx >=12 + - cuda-version >=12,<13.0a0 + - libgcc >=13 + - libstdcxx >=13 license: BSD-3-Clause license_family: BSD - size: 124592707 - timestamp: 1732655186186 -- kind: conda - name: ncurses - version: '6.5' - build: he02047a_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - sha256: 6a1d5d8634c1a07913f1c525db6455918cbc589d745fac46d9d6e30340c8731a - md5: 70caf8bb6cf39a0b6b7efc885f51c0fe + size: 180506014 + timestamp: 1746010496065 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda + sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586 + md5: 47e340acb35de30501a76c7c799c41d7 depends: - __glibc >=2.17,<3.0.a0 - - libgcc-ng >=12 + - libgcc >=13 license: X11 AND BSD-3-Clause - size: 889086 - timestamp: 1724658547447 -- kind: conda - name: networkx - version: 3.4.2 - build: pyh267e887_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda + size: 891641 + timestamp: 1738195959188 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.4.2-pyh267e887_2.conda sha256: 39625cd0c9747fa5c46a9a90683b8997d8b9649881b3dc88336b13b7bdd60117 md5: fd40bf7f7f4bc4b647dc8512053d9873 depends: @@ -4219,29 +3320,20 @@ packages: license_family: BSD size: 1265008 timestamp: 1731521053408 -- kind: conda - name: ninja - version: 1.12.1 - build: h297d8ca_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-h297d8ca_0.conda - sha256: 40f7b76b07067935f8a5886aab0164067b7aa71eb5ad20b7278618c0c2c98e06 - md5: 3aa1c7e292afeff25a0091ddd7c69b72 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ninja-1.12.1-hff21bea_1.conda + sha256: 1f2f7e26084971e87bfbb733f17d824ff3323ee9618fb713ae9932386da76aed + md5: 2322531904f27501ee19847b87ba7c64 depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=13 + - libgcc >=13 license: Apache-2.0 - license_family: Apache - size: 2198858 - timestamp: 1715440571685 -- kind: conda - name: numpy - version: 2.2.1 - build: py312h7e784f5_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.1-py312h7e784f5_0.conda - sha256: d8ccc46c3d2149a1479b660f5c04acad45766ed04ef961ef022d71122ec2bb91 - md5: 6159cab400b61f38579a7692be5e630a + license_family: APACHE + size: 161883 + timestamp: 1745526264371 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.2.5-py312h72c5963_0.conda + sha256: af293ba6f715983f71543ed0111e6bb77423d409c1d13062474601257c2eebca + md5: 505bcfc142b97010c162863c38d90016 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 @@ -4255,64 +3347,29 @@ packages: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD - size: 8483009 - timestamp: 1734904730019 -- kind: conda - name: openblas - version: 0.3.28 - build: pthreads_h6ec200e_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.28-pthreads_h6ec200e_1.conda - sha256: c558f49a262f43b0c5b6f9feb75b631d0b1eeba53579fd2bbce0df37f1884ef0 - md5: 8fe5d50db07e92519cc639cb0aef9b1b - depends: - - libopenblas 0.3.28 pthreads_h94d23a6_1 + size: 8543883 + timestamp: 1745119461819 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openblas-0.3.29-pthreads_h6ec200e_0.conda + sha256: 49814ee7c946fc583ea9217dfaad23d6bb75868988062fdd921cedc79affd07a + md5: 7e4d48870b3258bea920d51b7f495a81 + depends: + - libopenblas 0.3.29 pthreads_h94d23a6_0 license: BSD-3-Clause license_family: BSD - size: 5727592 - timestamp: 1730772687576 -- kind: conda - name: openssl - version: 3.4.0 - build: h7b32b05_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.4.0-h7b32b05_1.conda - sha256: f62f6bca4a33ca5109b6d571b052a394d836956d21b25b7ffd03376abf7a481f - md5: 4ce6875f75469b2757a65e10a5d05e31 + size: 6054007 + timestamp: 1739825745796 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.5.0-h7b32b05_1.conda + sha256: b4491077c494dbf0b5eaa6d87738c22f2154e9277e5293175ec187634bd808a0 + md5: de356753cfdbffcde5bb1e86e3aa6cd0 depends: - __glibc >=2.17,<3.0.a0 - ca-certificates - libgcc >=13 license: Apache-2.0 license_family: Apache - size: 2937158 - timestamp: 1736086387286 -- kind: conda - name: opt-einsum - version: 3.4.0 - build: hd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/opt-einsum-3.4.0-hd8ed1ab_1.conda - sha256: 8db3d841c72f184de69e1237b900a2d79c742e30e8378973814543bf987b6bc6 - md5: b94f689d8b1ce7dd212946e0331037ad - depends: - - opt_einsum >=3.4.0,<3.4.1.0a0 - license: MIT - license_family: MIT - size: 6558 - timestamp: 1733688054327 -- kind: conda - name: opt_einsum - version: 3.4.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda + size: 3117410 + timestamp: 1746223723843 +- conda: https://conda.anaconda.org/conda-forge/noarch/opt_einsum-3.4.0-pyhd8ed1ab_1.conda sha256: af71aabb2bfa4b2c89b7b06403e5cec23b418452cae9f9772bd7ac3f9ea1ff44 md5: 52919815cd35c4e1a0298af658ccda04 depends: @@ -4321,47 +3378,42 @@ packages: license_family: MIT size: 62479 timestamp: 1733688053334 -- kind: conda - name: packaging - version: '24.2' - build: pyhd8ed1ab_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/packaging-24.2-pyhd8ed1ab_2.conda - sha256: da157b19bcd398b9804c5c52fc000fcb8ab0525bdb9c70f95beaa0bb42f85af1 - md5: 3bfed7e6228ebf2f7b9eaa47f1b4e2aa +- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.15.0-py312h68727a3_0.conda + sha256: b21dd4f339084a1db068b89cbc894954a3eb12f170ad646f3e8e3567438f4585 + md5: 9664a035da52d38121b1f75b27f2c471 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - python >=3.12,<3.13.0a0 + - python_abi 3.12.* *_cp312 + - typing-extensions >=4.5 + license: Apache-2.0 + license_family: Apache + size: 413963 + timestamp: 1744034409842 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-25.0-pyh29332c3_1.conda + sha256: 289861ed0c13a15d7bbb408796af4de72c2fe67e2bcb0de98f4c3fce259d7991 + md5: 58335b26c38bf4a20f399384c33cbcf9 depends: - python >=3.8 + - python license: Apache-2.0 license_family: APACHE - size: 60164 - timestamp: 1733203368787 -- kind: conda - name: pip - version: 24.3.1 - build: pyh8b19718_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda - sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb - md5: 04e691b9fadd93a8a9fad87a81d4fd8f + size: 62477 + timestamp: 1745345660407 +- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.1.1-pyh8b19718_0.conda + sha256: ebfa591d39092b111b9ebb3210eb42251be6da89e26c823ee03e5e838655a43e + md5: 32d0781ace05105cc99af55d36cbec7c depends: - python >=3.9,<3.13.0a0 - setuptools - wheel license: MIT license_family: MIT - size: 1245116 - timestamp: 1734466348103 -- kind: conda - name: pkg-config - version: 0.29.2 - build: h4bc722e_1009 - build_number: 1009 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda + size: 1242995 + timestamp: 1746249983238 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pkg-config-0.29.2-h4bc722e_1009.conda sha256: c9601efb1af5391317e04eca77c6fe4d716bf1ca1ad8da2a05d15cb7c28d7d4e md5: 1bee70681f504ea424fb07cdb090c001 depends: @@ -4371,30 +3423,17 @@ packages: license_family: GPL size: 115175 timestamp: 1720805894943 -- kind: conda - name: platformdirs - version: 4.3.6 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.6-pyhd8ed1ab_1.conda - sha256: bb50f6499e8bc1d1a26f17716c97984671121608dc0c3ecd34858112bce59a27 - md5: 577852c7e53901ddccc7e6a9959ddebe +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.3.7-pyh29332c3_0.conda + sha256: ae7d3e58224d53d6b59e1f5ac5809803bb1972f0ac4fb10cd9b8c87d4122d3e0 + md5: e57da6fe54bb3a5556cf36d199ff07d8 depends: - python >=3.9 + - python license: MIT license_family: MIT - size: 20448 - timestamp: 1733232756001 -- kind: conda - name: pluggy - version: 1.5.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda + size: 23291 + timestamp: 1742485085457 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.5.0-pyhd8ed1ab_1.conda sha256: 122433fc5318816b8c69283aaf267c73d87aa2d09ce39f64c9805c9a3b264819 md5: e9dcbce5f45f9ee500e728ae58b605b6 depends: @@ -4403,14 +3442,7 @@ packages: license_family: MIT size: 23595 timestamp: 1733222855563 -- kind: conda - name: ply - version: '3.11' - build: pyhd8ed1ab_3 - build_number: 3 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/ply-3.11-pyhd8ed1ab_3.conda sha256: bae453e5cecf19cab23c2e8929c6e30f4866d996a8058be16c797ed4b935461f md5: fd5062942bfa1b0bd5e0d2a4397b099e depends: @@ -4419,14 +3451,7 @@ packages: license_family: BSD size: 49052 timestamp: 1733239818090 -- kind: conda - name: pooch - version: 1.8.2 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pooch-1.8.2-pyhd8ed1ab_1.conda sha256: bedda6b36e8e42b0255179446699a0cf08051e6d9d358dd0dd0e787254a3620e md5: b3e783e8e8ed7577cf0b6dee37d1fbac depends: @@ -4438,14 +3463,7 @@ packages: license_family: BSD size: 54116 timestamp: 1733421432357 -- kind: conda - name: pybind11 - version: 2.13.6 - build: pyh1ec8472_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-2.13.6-pyh1ec8472_2.conda sha256: 27f888492af3d5ab19553f263b0015bf3766a334668b5b3a79c7dc0416e603c1 md5: 8088a5e7b2888c780738c3130f2a969d depends: @@ -4457,14 +3475,7 @@ packages: license_family: BSD size: 186375 timestamp: 1730237816231 -- kind: conda - name: pybind11-global - version: 2.13.6 - build: pyh415d2e4_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-2.13.6-pyh415d2e4_2.conda sha256: 9ff0d61d86878f81779bdb7e47656a75feaab539893462cff29b8ec353026d81 md5: 120541563e520d12d8e39abd7de9092c depends: @@ -4476,14 +3487,7 @@ packages: license_family: BSD size: 179139 timestamp: 1730237481227 -- kind: conda - name: pycparser - version: '2.22' - build: pyh29332c3_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyh29332c3_1.conda sha256: 79db7928d13fab2d892592223d7570f5061c192f27b9febd1a418427b719acc6 md5: 12c566707c80111f9799308d9e265aef depends: @@ -4493,13 +3497,7 @@ packages: license_family: BSD size: 110100 timestamp: 1733195786147 -- kind: conda - name: pydevtool - version: 0.3.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydevtool-0.3.0-pyhd8ed1ab_0.tar.bz2 sha256: 4171325c030b75bba1f5f83465a2ed0f71bcfbcf90f779844a71a54d18bd407c md5: e357fb5ecdaae7474f523094e8339dc5 depends: @@ -4509,45 +3507,26 @@ packages: license_family: MIT size: 15067 timestamp: 1659947454808 -- kind: conda - name: pygments - version: 2.19.1 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.1-pyhd8ed1ab_0.conda sha256: 28a3e3161390a9d23bc02b4419448f8d27679d9e2c250e29849e37749c8de86b md5: 232fb4577b6687b2d503ef8e254270c9 depends: - python >=3.9 license: BSD-2-Clause + license_family: BSD size: 888600 timestamp: 1736243563082 -- kind: conda - name: pyproject-metadata - version: 0.9.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.0-pyhd8ed1ab_1.conda - sha256: 5429009e692778a3a67e4435df5266f1d7bdb9477091d2742a232c9697b25316 - md5: 1239146a53a383a84633800294120f17 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject-metadata-0.9.1-pyhd8ed1ab_0.conda + sha256: 7eea506a4296ff86ccd1f3f07dfd262b2ee1970886d53185b2b975abc6b506b5 + md5: 22ae7c6ea81e0c8661ef32168dda929b depends: - packaging >=19.0 - python >=3.9 license: MIT license_family: MIT - size: 21364 - timestamp: 1733316711254 -- kind: conda - name: pyproject_hooks - version: 1.2.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda + size: 21982 + timestamp: 1741654784592 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyproject_hooks-1.2.0-pyhd8ed1ab_1.conda sha256: 065ac44591da9abf1ff740feb25929554b920b00d09287a551fcced2c9791092 md5: d4582021af437c931d7d77ec39007845 depends: @@ -4557,14 +3536,7 @@ packages: license_family: MIT size: 15528 timestamp: 1733710122949 -- kind: conda - name: pysocks - version: 1.7.1 - build: pyha55dd90_7 - build_number: 7 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 md5: 461219d1a5bd61342293efa2c0c90eac depends: @@ -4574,16 +3546,9 @@ packages: license_family: BSD size: 21085 timestamp: 1733217331982 -- kind: conda - name: pytest - version: 8.3.4 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.4-pyhd8ed1ab_1.conda - sha256: 75245ca9d0cbd6d38bb45ec02430189a9d4c21c055c5259739d738a2298d61b3 - md5: 799ed216dc6af62520f32aa39bc1c2bb +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.5-pyhd8ed1ab_0.conda + sha256: 963524de7340c56615583ba7b97a6beb20d5c56a59defb59724dc2a3105169c9 + md5: c3c9316209dec74a705a36797970c6be depends: - colorama - exceptiongroup >=1.0.0rc8 @@ -4596,16 +3561,9 @@ packages: - pytest-faulthandler >=2 license: MIT license_family: MIT - size: 259195 - timestamp: 1733217599806 -- kind: conda - name: pytest-xdist - version: 3.6.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda + size: 259816 + timestamp: 1740946648058 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.6.1-pyhd8ed1ab_1.conda sha256: fb35da93084d653b86918c200abb2f0b88aceb3b0526c6aaa21b844f565ae237 md5: 59aad4fb37cabc0bacc73cf344612ddd depends: @@ -4618,46 +3576,33 @@ packages: license_family: MIT size: 38147 timestamp: 1733240891538 -- kind: conda - name: python - version: 3.12.8 - build: h9e4cc4f_1_cpython - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.8-h9e4cc4f_1_cpython.conda - sha256: 3f0e0518c992d8ccfe62b189125721309836fe48a010dc424240583e157f9ff0 - md5: 7fd2fd79436d9b473812f14e86746844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.10-h9e4cc4f_0_cpython.conda + sha256: 4dc1da115805bd353bded6ab20ff642b6a15fcc72ac2f3de0e1d014ff3612221 + md5: a41d26cd4d47092d683915d058380dec depends: - __glibc >=2.17,<3.0.a0 - bzip2 >=1.0.8,<2.0a0 - ld_impl_linux-64 >=2.36.1 - - libexpat >=2.6.4,<3.0a0 - - libffi >=3.4,<4.0a0 + - libexpat >=2.7.0,<3.0a0 + - libffi >=3.4.6,<3.5.0a0 - libgcc >=13 - - liblzma >=5.6.3,<6.0a0 + - liblzma >=5.8.1,<6.0a0 - libnsl >=2.0.1,<2.1.0a0 - - libsqlite >=3.47.0,<4.0a0 + - libsqlite >=3.49.1,<4.0a0 - libuuid >=2.38.1,<3.0a0 - libxcrypt >=4.4.36 - libzlib >=1.3.1,<2.0a0 - ncurses >=6.5,<7.0a0 - - openssl >=3.4.0,<4.0a0 + - openssl >=3.5.0,<4.0a0 - readline >=8.2,<9.0a0 - tk >=8.6.13,<8.7.0a0 - tzdata constrains: - python_abi 3.12.* *_cp312 license: Python-2.0 - size: 31565686 - timestamp: 1733410597922 -- kind: conda - name: python-build - version: 1.2.2.post1 - build: pyhff2d567_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda + size: 31279179 + timestamp: 1744325164633 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-build-1.2.2.post1-pyhff2d567_1.conda sha256: da40ab7413029351852268ca479e5cc642011c72317bd02dba28235c5c5ec955 md5: 0903621fe8a9f37286596529528f4f74 depends: @@ -4673,29 +3618,17 @@ packages: license_family: MIT size: 25108 timestamp: 1733230700715 -- kind: conda - name: python_abi - version: '3.12' - build: 5_cp312 - build_number: 5 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.12-5_cp312.conda - sha256: d10e93d759931ffb6372b45d65ff34d95c6000c61a07e298d162a3bc2accebb0 - md5: 0424ae29b104430108f5218a66db7260 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-7_cp312.conda + build_number: 7 + sha256: a1bbced35e0df66cc713105344263570e835625c28d1bdee8f748f482b2d7793 + md5: 0dfcdc155cf23812a0c9deada86fb723 constrains: - python 3.12.* *_cpython license: BSD-3-Clause license_family: BSD - size: 6238 - timestamp: 1723823388266 -- kind: conda - name: pythran - version: 0.17.0 - build: pyh47d16e9_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda + size: 6971 + timestamp: 1745258861359 +- conda: https://conda.anaconda.org/conda-forge/noarch/pythran-0.17.0-pyh47d16e9_1.conda sha256: 80abbf25ed5a7f3a45e62b174d02cd9c72566161bacc385796a58e511eb3a6ee md5: 97a9ef430424566db8115934440a99d0 depends: @@ -4712,189 +3645,155 @@ packages: license_family: BSD size: 1948748 timestamp: 1732322355621 -- kind: conda - name: pytorch - version: 2.5.1 - build: cpu_mkl_py312_heeca0f5_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cpu_mkl_py312_heeca0f5_108.conda - sha256: 47d6733d7d23e8d719636a901f08362f08cb7d39ca435fa9762dae29b8daa0f8 - md5: d2dc4c7e49475cb141cb14e8329bb005 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cpu_mkl_py312_h6a7998d_100.conda + sha256: 5c4a340f7a729bcdc19c530b25ed71ed5239f5ad0e907c49f03d88efd5b3be75 + md5: c67501107a48c049f18e8cb7c7e800b2 depends: - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - filelock - fsspec - jinja2 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libgcc >=13 - - libprotobuf >=5.28.3,<5.28.4.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 - libstdcxx >=13 - - libtorch 2.5.1.* - - libuv >=1.49.2,<2.0a0 + - libtorch 2.7.0 cpu_mkl_hf6ddc5a_100 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - networkx - numpy >=1.19,<3 + - optree >=0.13.0 + - pybind11 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.7,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions + - setuptools <76 + - sleef >=3.8,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.10.0 constrains: - - pytorch-cpu ==2.5.1 - - pytorch-gpu ==99999999 + - pytorch-gpu <0.0a0 + - pytorch-cpu 2.7.0 license: BSD-3-Clause license_family: BSD - size: 37116444 - timestamp: 1736091774147 -- kind: conda - name: pytorch - version: 2.5.1 - build: cuda126_py312h1763f6d_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.5.1-cuda126_py312h1763f6d_306.conda - sha256: b614a2929a40191b0b333510401d11a3e78d7cccd9a4dafc223f4c45510d7d8e - md5: 48e79c8ee23c4527cbbea4c9ee8665c3 + size: 28982129 + timestamp: 1746260259104 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.7.0-cuda126_mkl_py312_h30b5a27_300.conda + sha256: f47c03eed5ead66344b80e71a8d87902c36ad32f2c8b19b793cc39995d1180f8 + md5: f2d5af2065419f03f2e393d640096efb depends: - __cuda - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm - _openmp_mutex >=4.5 - cuda-cudart >=12.6.77,<13.0a0 + - cuda-cupti >=12.6.80,<13.0a0 - cuda-nvrtc >=12.6.85,<13.0a0 - cuda-nvtx >=12.6.77,<13.0a0 - cuda-version >=12.6,<13 - - cudnn >=9.3.0.75,<10.0a0 + - cudnn >=9.8.0.87,<10.0a0 - filelock - fsspec - jinja2 - libabseil * cxx17* - - libabseil >=20240722.0,<20240723.0a0 + - libabseil >=20250127.1,<20250128.0a0 + - libblas * *mkl - libcblas >=3.9.0,<4.0a0 - libcublas >=12.6.4.1,<13.0a0 + - libcudss >=0.5.0.16,<0.5.1.0a0 - libcufft >=11.3.0.4,<12.0a0 - libcufile >=1.11.1.6,<2.0a0 - libcurand >=10.3.7.77,<11.0a0 - libcusolver >=11.7.1.2,<12.0a0 - libcusparse >=12.5.4.2,<13.0a0 - - libgcc >=12 - - libmagma >=2.8.0,<2.8.1.0a0 - - libmagma_sparse >=2.8.0,<2.8.1.0a0 - - libprotobuf >=5.28.2,<5.28.3.0a0 - - libstdcxx >=12 - - libtorch 2.5.1.* - - libuv >=1.49.2,<2.0a0 + - libgcc >=13 + - libmagma >=2.9.0,<2.9.1.0a0 + - libprotobuf >=5.29.3,<5.29.4.0a0 + - libstdcxx >=13 + - libtorch 2.7.0 cuda126_mkl_h99b69db_300 + - libuv >=1.50.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - llvm-openmp >=20.1.4 - mkl >=2024.2.2,<2025.0a0 - - nccl >=2.23.4.1,<3.0a0 + - nccl >=2.26.5.1,<3.0a0 - networkx - numpy >=1.19,<3 + - optree >=0.13.0 + - pybind11 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - setuptools - - sleef >=3.7,<4.0a0 - - sympy >=1.13.1,!=1.13.2 - - typing_extensions + - setuptools <76 + - sleef >=3.8,<4.0a0 + - sympy >=1.13.3 + - triton 3.3.0.* + - typing_extensions >=4.10.0 constrains: - - pytorch-gpu ==2.5.1 - - pytorch-cpu ==99999999 + - pytorch-gpu 2.7.0 + - pytorch-cpu <0.0a0 license: BSD-3-Clause license_family: BSD - size: 36996719 - timestamp: 1733652371588 -- kind: conda - name: pytorch-cpu - version: 2.5.1 - build: cpu_mkl_hc60beec_108 - build_number: 108 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.5.1-cpu_mkl_hc60beec_108.conda - sha256: bbb8a46767dc12fb35423aa561e02389aef87656721fbd3f87969d40f03e4664 - md5: 8135dc47e3dcbd4b3d83ad5b48e50ecb - depends: - - pytorch 2.5.1 cpu_mkl*108 + size: 29145540 + timestamp: 1746284384314 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.7.0-cpu_mkl_hc60beec_100.conda + sha256: f972760cda01fff159c56925036b8e6e2c39a8a8414b973ab5303912b3ff3f3a + md5: 20b3051f55ad823a27818dfa46a41c8f + depends: + - pytorch 2.7.0 cpu_mkl*100 track_features: - pytorch-cpu license: BSD-3-Clause license_family: BSD - size: 31886 - timestamp: 1736092702692 -- kind: conda - name: pytorch-gpu - version: 2.5.1 - build: cuda126ha999a5f_306 - build_number: 306 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.5.1-cuda126ha999a5f_306.conda - sha256: 080567e694289a7ecb7d62507e077c5db136bc7dc76bf083c1860eab2e1e4b41 - md5: 7690f1240c476f529f8e3f9ffc79ca8e - depends: - - pytorch 2.5.1 cuda*306 + size: 47101 + timestamp: 1746261172719 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-gpu-2.7.0-cuda126_mkl_ha999a5f_300.conda + sha256: e1162a51e77491abae15f6b651ba8f064870181d57d40f9168747652d0f70cb0 + md5: 84ecafc34c6f8933c2c9b00204832e38 + depends: + - pytorch 2.7.0 cuda*_mkl*300 license: BSD-3-Clause license_family: BSD - size: 24610 - timestamp: 1733656490429 -- kind: conda - name: rdma-core - version: '55.0' - build: h5888daf_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-55.0-h5888daf_0.conda - sha256: 3715a51f1ea6e3765f19b6db90a7edb77a3b5aa201a4f09cbd51a678e8609a88 - md5: fd94951ea305bdfe6fb3939db3fb7ce2 + size: 47219 + timestamp: 1746288556375 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-57.0-h5888daf_0.conda + sha256: fbb4599ba969c49d2280c84af196c514c49a3ad1529c693f4b6ac6c705998ec8 + md5: e5be997517f19a365b8b111b888be426 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=13 - libnl >=3.11.0,<4.0a0 - libstdcxx >=13 - - libsystemd0 >=256.9 - - libudev1 >=256.9 + - libsystemd0 >=257.4 + - libudev1 >=257.4 license: Linux-OpenIB license_family: BSD - size: 1223940 - timestamp: 1734115241096 -- kind: conda - name: re2 - version: 2024.07.02 - build: h9925aae_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_2.conda - sha256: d213c44958d49ce7e0d4d5b81afec23640cce5016685dbb2d23571a99caa4474 - md5: e84ddf12bde691e8ec894b00ea829ddf - depends: - - libre2-11 2024.07.02 hbbce691_2 + size: 1238038 + timestamp: 1745325325058 +- conda: https://conda.anaconda.org/conda-forge/linux-64/re2-2024.07.02-h9925aae_3.conda + sha256: 66d34e3b4881f856486d11914392c585713100ca547ccfc0947f3a4765c2c486 + md5: 6f445fb139c356f903746b2b91bbe786 + depends: + - libre2-11 2024.07.02 hba17884_3 license: BSD-3-Clause license_family: BSD - size: 26786 - timestamp: 1735541074034 -- kind: conda - name: readline - version: '8.2' - build: h8228510_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8228510_1.conda - sha256: 5435cf39d039387fbdc977b0a762357ea909a7694d9528ab40f005e9208744d7 - md5: 47d31b792659ce70f470b5c82fdfb7a4 + size: 26811 + timestamp: 1741121137599 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.2-h8c095d6_2.conda + sha256: 2d6d0c026902561ed77cd646b5021aef2d4db22e57a5b0178dfc669231e06d2c + md5: 283b96675859b20a825f8fa30f311446 depends: - - libgcc-ng >=12 - - ncurses >=6.3,<7.0a0 + - libgcc >=13 + - ncurses >=6.5,<7.0a0 license: GPL-3.0-only license_family: GPL - size: 281456 - timestamp: 1679532220005 -- kind: conda - name: requests - version: 2.32.3 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda + size: 282480 + timestamp: 1740379431762 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.32.3-pyhd8ed1ab_1.conda sha256: d701ca1136197aa121bbbe0e8c18db6b5c94acbd041c2b43c70e5ae104e1d8ad md5: a9b9368f3701a417eac9edbcae7cb737 depends: @@ -4909,50 +3808,33 @@ packages: license_family: APACHE size: 58723 timestamp: 1733217126197 -- kind: conda - name: rich - version: 13.9.4 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/rich-13.9.4-pyhd8ed1ab_1.conda - sha256: 06a760c5ae572e72e865d5a87e9fe3cc171e1a9c996e63daf3db52ff1a0b4457 - md5: 7aed65d4ff222bfb7335997aa40b7da5 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-14.0.0-pyh29332c3_0.conda + sha256: d10e2b66a557ec6296844e04686db87818b0df87d73c06388f2332fda3f7d2d5 + md5: 202f08242192ce3ed8bdb439ba40c0fe depends: - markdown-it-py >=2.2.0 - pygments >=2.13.0,<3.0.0 - python >=3.9 - typing_extensions >=4.0.0,<5.0.0 + - python license: MIT license_family: MIT - size: 185646 - timestamp: 1733342347277 -- kind: conda - name: rich-click - version: 1.8.5 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.5-pyhd8ed1ab_0.conda - sha256: 2650d0d3423637fefe4b4b21a061ef6c78d62a571f92c6e51f5c665b499a8012 - md5: 6fe1844fe7a1f819966d86b415e2e721 + size: 200323 + timestamp: 1743371105291 +- conda: https://conda.anaconda.org/conda-forge/noarch/rich-click-1.8.8-pyhd8ed1ab_0.conda + sha256: 9f3f08cb40d67e35199509ef8ff0045edc81c749e0c162c0da7390e974a3b7fc + md5: 9ade2651d83e7be4fa663adacaad984c depends: - click >=7,<9 - python >=3.9 - rich >=10.7 license: MIT license_family: MIT - size: 33968 - timestamp: 1734498970328 -- kind: conda - name: scipy - version: 1.15.0 - build: py312h180e4f1_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.0-py312h180e4f1_0.conda - sha256: 1b15a1ca749d5d6d9ff5e2f380f3a72c23edc316abdaa094b1578e4275146636 - md5: 66004839e9394a241b483436a9742845 + size: 34369 + timestamp: 1741676747646 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.15.2-py312ha707e6e_0.conda + sha256: b9faaa024b77a3678a988c5a490f02c4029c0d5903998b585100e05bc7d4ff36 + md5: 00b999c5f9d01fb633db819d79186bd4 depends: - __glibc >=2.17,<3.0.a0 - libblas >=3.9.0,<4.0a0 @@ -4962,73 +3844,56 @@ packages: - libgfortran5 >=13.3.0 - liblapack >=3.9.0,<4.0a0 - libstdcxx >=13 - - numpy <2.3 + - numpy <2.5 - numpy >=1.19,<3 - numpy >=1.23.5 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 license: BSD-3-Clause license_family: BSD - size: 18664052 - timestamp: 1736010660548 -- kind: conda - name: setuptools - version: 75.6.0 - build: pyhff2d567_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.6.0-pyhff2d567_1.conda - sha256: abb12e1dd515b13660aacb5d0fd43835bc2186cab472df25b7716cd65e095111 - md5: fc80f7995e396cbaeabd23cf46c413dc + size: 17064784 + timestamp: 1739791925628 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.2-pyhff2d567_0.conda + sha256: 91d664ace7c22e787775069418daa9f232ee8bafdd0a6a080a5ed2395a6fa6b2 + md5: 9bddfdbf4e061821a1a443f93223be61 + depends: + - python >=3.9 + license: MIT + license_family: MIT + size: 777736 + timestamp: 1740654030775 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-80.1.0-pyhff2d567_0.conda + sha256: 777d34ed359cedd5a5004c930077c101bb3b70e5fbb04d29da5058d75b0ba487 + md5: f6f72d0837c79eaec77661be43e8a691 depends: - python >=3.9 license: MIT license_family: MIT - size: 774252 - timestamp: 1732632769210 -- kind: conda - name: sleef - version: '3.7' - build: h1b44611_2 - build_number: 2 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.7-h1b44611_2.conda - sha256: 38ad951d30052522693d21b247105744c7c6fb7cefcf41edca36f0688322e76d - md5: 4792f3259c6fdc0b730563a85b211dc0 + size: 778484 + timestamp: 1746085063737 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.8-h1b44611_0.conda + sha256: c998d5a29848ce9ff1c53ba506e7d01bbd520c39bbe72e2fb7cdf5a53bad012f + md5: aec4dba5d4c2924730088753f6fa164b depends: - __glibc >=2.17,<3.0.a0 - _openmp_mutex >=4.5 - libgcc >=13 - libstdcxx >=13 license: BSL-1.0 - size: 1919287 - timestamp: 1731180933533 -- kind: conda - name: sortedcontainers - version: 2.4.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - sha256: 0cea408397d50c2afb2d25e987ebac4546ae11e549d65b1403d80dc368dfaaa6 - md5: 6d6552722448103793743dabfbda532d - depends: - - python >=2.7 + size: 1920152 + timestamp: 1738089391074 +- conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_1.conda + sha256: d1e3e06b5cf26093047e63c8cc77b70d970411c5cbc0cb1fad461a8a8df599f7 + md5: 0401a17ae845fa72c7210e206ec5647d + depends: + - python >=3.9 license: Apache-2.0 license_family: APACHE - size: 26314 - timestamp: 1621217159824 -- kind: conda - name: sympy - version: 1.13.3 - build: pyh2585a3b_105 - build_number: 105 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/sympy-1.13.3-pyh2585a3b_105.conda - sha256: 929d939c5a8bcdc10a17501890918da68cf14a5883b36fddf77b8f0fbf040be2 - md5: 254cd5083ffa04d96e3173397a3d30f4 + size: 28657 + timestamp: 1738440459037 +- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_105.conda + sha256: 09d3b6ac51d437bc996ad006d9f749ca5c645c1900a854a6c8f193cbd13f03a8 + md5: 8c09fac3785696e1c477156192d64b91 depends: - __unix - cpython @@ -5037,16 +3902,9 @@ packages: - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 4523617 - timestamp: 1736248315124 -- kind: conda - name: sysroot_linux-64 - version: '2.17' - build: h0157908_18 - build_number: 18 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda + size: 4616621 + timestamp: 1745946173026 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.17-h0157908_18.conda sha256: 69ab5804bdd2e8e493d5709eebff382a72fab3e9af6adf93a237ccf8f7dbd624 md5: 460eba7851277ec1fd80a1a24080787a depends: @@ -5056,13 +3914,7 @@ packages: license_family: GPL size: 15166921 timestamp: 1735290488259 -- kind: conda - name: tbb - version: 2021.13.0 - build: hceb3a55_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2021.13.0-hceb3a55_1.conda sha256: 65463732129899770d54b1fbf30e1bb82fdebda9d7553caf08d23db4590cd691 md5: ba7726b8df7b9d34ea80e82b097a4893 depends: @@ -5074,28 +3926,16 @@ packages: license_family: APACHE size: 175954 timestamp: 1732982638805 -- kind: conda - name: threadpoolctl - version: 3.5.0 - build: pyhc1e730c_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.5.0-pyhc1e730c_0.conda - sha256: 45e402941f6bed094022c5726a2ca494e6224b85180d2367fb6ddd9aea68079d - md5: df68d78237980a159bd7149f33c0e8fd +- conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda + sha256: 6016672e0e72c4cf23c0cf7b1986283bd86a9c17e8d319212d78d8e9ae42fdfd + md5: 9d64911b31d57ca443e9f1e36b04385f depends: - - python >=3.8 + - python >=3.9 license: BSD-3-Clause license_family: BSD - size: 23548 - timestamp: 1714400228771 -- kind: conda - name: tk - version: 8.6.13 - build: noxft_h4845f30_101 - build_number: 101 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda + size: 23869 + timestamp: 1741878358548 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h4845f30_101.conda sha256: e0569c9caa68bf476bead1bed3d79650bb080b532c64a4af7d8ca286c08dea4e md5: d453b98d9c83e71da0741bb0ff4d76bc depends: @@ -5105,14 +3945,7 @@ packages: license_family: BSD size: 3318875 timestamp: 1699202167581 -- kind: conda - name: tomli - version: 2.2.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.2.1-pyhd8ed1ab_1.conda sha256: 18636339a79656962723077df9a56c0ac7b8a864329eb8f847ee3d38495b863e md5: ac944244f1fed2eb49bae07193ae8215 depends: @@ -5121,43 +3954,56 @@ packages: license_family: MIT size: 19167 timestamp: 1733256819729 -- kind: conda - name: typing_extensions - version: 4.12.2 - build: pyha770c72_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.12.2-pyha770c72_1.conda - sha256: 337be7af5af8b2817f115b3b68870208b30c31d3439bec07bfb2d8f4823e3568 - md5: d17f13df8b65464ca316cbc000a3cb64 +- conda: https://conda.anaconda.org/conda-forge/linux-64/triton-3.3.0-cuda126py312hebffaa9_1.conda + sha256: 7089c27a38fc3ec199af4d51fcbba33720281f3098e984c49a9f010805d2de84 + md5: a05b9a73fe6a9be82a2fc4af2b01e95f + depends: + - python + - setuptools + - cuda-nvcc-tools + - cuda-cuobjdump + - cuda-cudart + - cuda-cupti + - cuda-version >=12.6,<13 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=13 + - libgcc >=13 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.12.* *_cp312 + - libzlib >=1.3.1,<2.0a0 + - cuda-cupti >=12.6.80,<13.0a0 + license: MIT + license_family: MIT + size: 163144991 + timestamp: 1746164460128 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.13.2-h0e9735f_0.conda + sha256: 4865fce0897d3cb0ffc8998219157a8325f6011c136e6fd740a9a6b169419296 + md5: 568ed1300869dca0ba09fb750cda5dbb + depends: + - typing_extensions ==4.13.2 pyh29332c3_0 + license: PSF-2.0 + license_family: PSF + size: 89900 + timestamp: 1744302253997 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.13.2-pyh29332c3_0.conda + sha256: a8aaf351e6461de0d5d47e4911257e25eec2fa409d71f3b643bb2f748bde1c08 + md5: 83fc6ae00127671e301c9f44254c31b8 depends: - python >=3.9 + - python license: PSF-2.0 license_family: PSF - size: 39637 - timestamp: 1733188758212 -- kind: conda - name: tzdata - version: 2024b - build: hc8b5060_0 - subdir: noarch - noarch: generic - url: https://conda.anaconda.org/conda-forge/noarch/tzdata-2024b-hc8b5060_0.conda - sha256: 4fde5c3008bf5d2db82f2b50204464314cc3c91c1d953652f7bd01d9e52aefdf - md5: 8ac3367aafb1cc0a068483c580af8015 + size: 52189 + timestamp: 1744302253997 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda + sha256: 5aaa366385d716557e365f0a4e9c3fca43ba196872abbbe3d56bb610d131e192 + md5: 4222072737ccff51314b5ece9c7d6f5a license: LicenseRef-Public-Domain - size: 122354 - timestamp: 1728047496079 -- kind: conda - name: urllib3 - version: 2.3.0 - build: pyhd8ed1ab_0 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.3.0-pyhd8ed1ab_0.conda - sha256: 114919ffa80c328127dab9c8e7a38f9d563c617691fb81fccb11c1e86763727e - md5: 32674f8dbfb7b26410ed580dd3c10a29 + size: 122968 + timestamp: 1742727099393 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.4.0-pyhd8ed1ab_0.conda + sha256: a25403b76f7f03ca1a906e1ef0f88521edded991b9897e7fed56a3e334b3db8c + md5: c1e349028e0052c4eea844e94f773065 depends: - brotli-python >=1.0.9 - h2 >=4,<5 @@ -5166,16 +4012,9 @@ packages: - zstandard >=0.18.0 license: MIT license_family: MIT - size: 100102 - timestamp: 1734859520452 -- kind: conda - name: wheel - version: 0.45.1 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda + size: 100791 + timestamp: 1744323705540 +- conda: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce md5: 75cb7132eb58d97896e173ef12ac9986 depends: @@ -5184,14 +4023,7 @@ packages: license_family: MIT size: 62931 timestamp: 1733130309598 -- kind: conda - name: zipp - version: 3.21.0 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.21.0-pyhd8ed1ab_1.conda sha256: 567c04f124525c97a096b65769834b7acb047db24b15a56888a322bf3966c3e1 md5: 0c3cc595284c5e8f0f9900a9b228a332 depends: @@ -5200,40 +4032,28 @@ packages: license_family: MIT size: 21809 timestamp: 1732827613585 -- kind: conda - name: zstandard - version: 0.23.0 - build: py312hef9b889_1 - build_number: 1 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312hef9b889_1.conda - sha256: b97015e146437283f2213ff0e95abdc8e2480150634d81fbae6b96ee09f5e50b - md5: 8b7069e9792ee4e5b4919a7a306d2e67 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstandard-0.23.0-py312h66e93f0_2.conda + sha256: ff62d2e1ed98a3ec18de7e5cf26c0634fd338cb87304cf03ad8cbafe6fe674ba + md5: 630db208bc7bbb96725ce9832c7423bb depends: - __glibc >=2.17,<3.0.a0 - cffi >=1.11 - libgcc >=13 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 - - zstd >=1.5.6,<1.5.7.0a0 - - zstd >=1.5.6,<1.6.0a0 license: BSD-3-Clause license_family: BSD - size: 419552 - timestamp: 1725305670210 -- kind: conda - name: zstd - version: 1.5.6 - build: ha6fb4c9_0 - subdir: linux-64 - url: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.6-ha6fb4c9_0.conda - sha256: c558b9cc01d9c1444031bd1ce4b9cff86f9085765f17627a6cd85fc623c8a02b - md5: 4d056880988120e29d75bfff282e0f45 + size: 732224 + timestamp: 1745869780524 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda + sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb + md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 depends: - - libgcc-ng >=12 - - libstdcxx-ng >=12 - - libzlib >=1.2.13,<2.0.0a0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libstdcxx >=13 + - libzlib >=1.3.1,<2.0a0 license: BSD-3-Clause license_family: BSD - size: 554846 - timestamp: 1714722996770 + size: 567578 + timestamp: 1742433379869 diff --git a/environment.yml b/environment.yml index b4984d3dd407..59d632d59608 100644 --- a/environment.yml +++ b/environment.yml @@ -30,7 +30,7 @@ dependencies: - asv >=0.6 - conda-build - hypothesis - - array-api-strict<2.1.1 + - array-api-strict>=2.3.1 # For type annotations - mypy - typing_extensions From adb78ded1d755df0a8268baaf4bf452fc5f16047 Mon Sep 17 00:00:00 2001 From: Christian Veenhuis <124370897+ChVeen@users.noreply.github.com> Date: Thu, 8 May 2025 17:28:09 +0200 Subject: [PATCH 158/251] added test to test_zeros.py --- scipy/optimize/tests/test_zeros.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scipy/optimize/tests/test_zeros.py b/scipy/optimize/tests/test_zeros.py index 35b34fa4b8ad..9902d6d63002 100644 --- a/scipy/optimize/tests/test_zeros.py +++ b/scipy/optimize/tests/test_zeros.py @@ -225,6 +225,10 @@ def test_lru_cached_individual(self, method): assert r.converged assert_allclose(root, 0) + def test_gh_22934(self): + with pytest.raises(ValueError, match="maxiter must be >= 0"): + zeros.brentq(lambda x: x**2 - 1, -2, 0, maxiter=-1) + class TestNewton(TestScalarRootFinders): def test_newton_collections(self): From 32acac429642984ec4f126d0f1fe3243f30406f5 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Thu, 8 May 2025 11:07:53 -0600 Subject: [PATCH 159/251] MAINT, DOC: forward port 1.15.3 relnotes * Update the version switcher in the docs. * Forward port the `1.15.3` release notes/`.mailmap`. [docs only] --- .mailmap | 3 + doc/source/_static/version_switcher.json | 9 +- doc/source/release.rst | 1 + doc/source/release/1.15.3-notes.rst | 108 +++++++++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 doc/source/release/1.15.3-notes.rst diff --git a/.mailmap b/.mailmap index 69f516715896..2761f330315b 100644 --- a/.mailmap +++ b/.mailmap @@ -300,6 +300,7 @@ Jonathan Sutton Jonathan Sutton Jonathan Tammo Siebert jotasi Jonathan Taylor jonathan.taylor Jordão Bragantini Jordão Bragantini +Joren Hammudoglu jorenham Joris Vankerschaver Joris Vankerschaver Joscha Reimer jor Josef Perktold josef-pktd @@ -456,6 +457,8 @@ Raphael Wettinger raphaelw Reidar Kind <53039431+reidarkind@users.noreply.github.com> reidarkind <53039431+reidarkind@users.noreply.github.com> Renee Otten reneeotten Reshama Shaikh reshamas +Richard Strong Bowen rsbowen +Richard Strong Bowen Richard Strong Bowen Richard Gowers richardjgowers Rick Paris rparis Rob Falck rob.falck diff --git a/doc/source/_static/version_switcher.json b/doc/source/_static/version_switcher.json index 657a982d236e..730cd0dba021 100644 --- a/doc/source/_static/version_switcher.json +++ b/doc/source/_static/version_switcher.json @@ -5,9 +5,14 @@ "url": "https://scipy.github.io/devdocs/" }, { - "name": "1.15.2 (stable)", - "version":"1.15.2", + "name": "1.15.3 (stable)", + "version":"1.15.3", "preferred": true, + "url": "https://docs.scipy.org/doc/scipy-1.15.3/" + }, + { + "name": "1.15.2", + "version":"1.15.2", "url": "https://docs.scipy.org/doc/scipy-1.15.2/" }, { diff --git a/doc/source/release.rst b/doc/source/release.rst index 7313e38c5f3a..ba2fd4db2ed2 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -9,6 +9,7 @@ see the `commit logs `_. :maxdepth: 1 release/1.16.0-notes + release/1.15.3-notes release/1.15.2-notes release/1.15.1-notes release/1.15.0-notes diff --git a/doc/source/release/1.15.3-notes.rst b/doc/source/release/1.15.3-notes.rst new file mode 100644 index 000000000000..b7ff4c70343d --- /dev/null +++ b/doc/source/release/1.15.3-notes.rst @@ -0,0 +1,108 @@ +========================== +SciPy 1.15.3 Release Notes +========================== + +.. contents:: + +SciPy 1.15.3 is a bug-fix release with no new features +compared to 1.15.2. + + + +Authors +======= +* Name (commits) +* aiudirog (1) + +* Nickolai Belakovski (1) +* Florian Bourgey (1) + +* Richard Strong Bowen (2) + +* Jake Bowhay (1) +* Dietrich Brunn (2) +* Evgeni Burovski (1) +* Lucas Colley (1) +* Ralf Gommers (1) +* Saarthak Gupta (1) + +* Matt Haberland (4) +* Chengyu Han (1) + +* Lukas Huber (1) + +* Nick ODell (2) +* Ilhan Polat (4) +* Tyler Reddy (52) +* Neil Schemenauer (1) + +* Dan Schult (1) +* sildater (1) + +* Gagandeep Singh (4) +* Albert Steppi (2) +* Matthias Urlichs (1) + +* David Varela (1) + +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (3) + +A total of 24 people contributed to this release. +People with a "+" by their names contributed a patch for the first time. +This list of names is automatically generated, and may not be fully complete. + + +Issues closed for 1.15.3 +------------------------ + +* `#10634 `__: BUG: optimize: ``least_squares`` with ``'trf'`` and ``'trf_sover=lsmr'``... +* `#18146 `__: BUG: scipy.sparse.linalg.expm_multiply fails with sparse matrices +* `#19418 `__: BUG: integrate.solve_ivp fails for some step sizes if dense_output=True... +* `#19865 `__: BUG: HalfspaceIntersection.add_halfspaces() does not seem to... +* `#20988 `__: BUG: special.hyp2f1: wrong result for extreme inputs +* `#22236 `__: BUG: scipy v1.15 breaking for pytest when assert-rewrite is on +* `#22400 `__: BUG: stats.genextreme.stats: Spurious warning from ``genextreme.stats(0.0,``... +* `#22451 `__: BUG: interpolative svd broken for non-square linear operators +* `#22515 `__: CI: Some GitHub workflows failing due to check on ``actions/cache``... +* `#22547 `__: BUG: _lib: Data race reported by TSAN in ``ccallback`` mechanism +* `#22558 `__: BUG: linalg.expm: bug on Windows / conda +* `#22574 `__: CI: benchmark job on CircleCI is failing on ``io.mmread`` memory... +* `#22586 `__: BUG: ndimage.median_filter: additional hard crashes +* `#22589 `__: BUG: spatial: ``Rotation`` no longer supports zero-length collections +* `#22599 `__: DOC: sparse.linalg.ArpackError: entire default ``infodict`` displayed +* `#22615 `__: CI: oneAPI job: ``Not enough disk space.`` +* `#22637 `__: BUG: Transposed LinearOperator fails on vector multiplication +* `#22655 `__: BUG: optimize.linprog: 40x slower in v1.15 compared to v1.14 +* `#22681 `__: DOC: integrate.tanhsinh: documentation refers to non-existent... +* `#22684 `__: BUG: signal.resample_poly: dtype not preserved +* `#22720 `__: MAINT, CI: floating point exceptions activated in NumPy +* `#22868 `__: BUG: re-importing ``scipy`` fails +* `#22903 `__: BUG: special.logsumexp: nan in 1.15 + + +Pull requests for 1.15.3 +------------------------ + +* `#20035 `__: BUG: spatial.HalfspaceIntersection: raise on non-feasible half... +* `#22502 `__: BUG: special: Fix typo in specfun::chgu +* `#22517 `__: CI: Use actions/cache 4.2.0 +* `#22532 `__: BUG: Remove warning for genextreme.stats(0.0, moments='mvsk') +* `#22543 `__: REL, MAINT: prep for 1.15.3 +* `#22555 `__: BUG: ``scipy.sparse.linalg``\ : Fix ``expm_multiply`` if both... +* `#22561 `__: BUG: _lib: Fix data race found by TSAN, use SCIPY_TLS. +* `#22567 `__: BUG: optimize: Fix ``bracket_root`` termination check and default... +* `#22582 `__: BUG: ``integrate.solve_ivp``\ : Avoid duplicate time stamps in... +* `#22587 `__: BUG: Pin jupyterlite-sphinx to >= 0.19.1 +* `#22588 `__: BUG/BLD: xsf: force defining the mdspan parenthesis operator... +* `#22590 `__: BENCH: remove triple run of mmread/mmwrite benchmark, limit sizes +* `#22600 `__: BUG: Fix ArpackError default argument +* `#22608 `__: BUG: ndimage.median_filter: fix segfault when using ``mode='mirror'`` +* `#22617 `__: CI: minimise disk space usage for oneAPI jobs +* `#22642 `__: BUG: sparse: sparse sum/mean out parameter shape not enforced... +* `#22643 `__: BUG: spatial.transform.Rotation: support 0-length rotations +* `#22660 `__: BUG: optimize: avoid expensive access of ``basis.col_status``... +* `#22689 `__: BUG: signal.resample_poly: fix dtype preservation +* `#22690 `__: MAINT/DOC: integrate.tanhsinh: lightly refactor error estimate... +* `#22693 `__: BUG: spatial.HalfspaceIntersection: fix ``add_halfspaces`` batch... +* `#22726 `__: MAINT: compensate for dot exceptions +* `#22763 `__: BUG: sparse: Remove reference cycle to improve memory use +* `#22772 `__: BUG: sparse.linalg: Transposed ``LinearOperator`` multiplication... +* `#22784 `__: BUG: signal._short_time_fft: incorrect index computation in ``upper_border_begin``... +* `#22792 `__: BUG: signal.ShortTimeFFT.upper_border_begin: Document parameter... +* `#22801 `__: BUG: ``signal.windows._windows.kaiser_bessel_derived``\ : use... +* `#22810 `__: BUG: special.hyp2f1: fix for extreme inputs +* `#22822 `__: BUG: linalg.expm: Fix noncompliant compiler branch typos in C... +* `#22828 `__: BUG: add workaround for pytest assertion rewriting overreach +* `#22834 `__: BUG: linalg: Fix shape mismatch in interpolative.svd +* `#22869 `__: BUG: optimize._highspy: don't import from inside a C module +* `#22910 `__: MAINT: special.logsumexp: improvement when weight of largest... From 7bffa469bcb8545e504d89b8581cd5f432cf3a4a Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 8 May 2025 18:44:12 +0100 Subject: [PATCH 160/251] MAINT: fix regressions in array-api-strict after disabling np.float64 (#22944) * MAINT: fix regressions in array-api-strict after disabling np.float64 * fix * fix pytorch float32 --- scipy/fft/tests/test_fftlog.py | 16 ++++++++++++++-- scipy/signal/windows/_windows.py | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/scipy/fft/tests/test_fftlog.py b/scipy/fft/tests/test_fftlog.py index 76c5d6630934..474decb59759 100644 --- a/scipy/fft/tests/test_fftlog.py +++ b/scipy/fft/tests/test_fftlog.py @@ -23,7 +23,7 @@ def f(r, mu): r = np.logspace(-4, 4, 16) - dln = np.log(r[1]/r[0]) + dln = math.log(r[1]/r[0]) mu = 0.3 offset = 0.0 bias = 0.0 @@ -60,6 +60,10 @@ def f(r, mu): # test 3: positive bias bias = 0.8 offset = fhtoffset(dln, mu, bias=bias) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) + ours = fht(a, dln, mu, offset=offset, bias=bias) theirs = [-7.3436673558316850E+00, +0.1710271207817100E+00, +0.1065374386206564E+00, -0.5121739602708132E-01, @@ -75,6 +79,8 @@ def f(r, mu): # test 4: negative bias bias = -0.8 offset = fhtoffset(dln, mu, bias=bias) + offset = float(offset) + ours = fht(a, dln, mu, offset=offset, bias=bias) theirs = [+0.8985777068568745E-05, +0.4074898209936099E-04, +0.2123969254700955E-03, +0.1009558244834628E-02, @@ -101,6 +107,9 @@ def test_fht_identity(n, bias, offset, optimal, xp): if optimal: offset = fhtoffset(dln, mu, initial=offset, bias=bias) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) A = fht(a, dln, mu, offset=offset, bias=bias) a_ = ifht(A, dln, mu, offset=offset, bias=bias) @@ -161,9 +170,12 @@ def test_fht_exact(n, xp): r = np.logspace(-2, 2, n) a = xp.asarray(r**gamma) - dln = np.log(r[1]/r[0]) + dln = math.log(r[1]/r[0]) offset = fhtoffset(dln, mu, initial=0.0, bias=gamma) + # offset is a np.float64, which array-api-strict disallows + # even if it's technically a subclass of float + offset = float(offset) A = fht(a, dln, mu, offset=offset, bias=gamma) diff --git a/scipy/signal/windows/_windows.py b/scipy/signal/windows/_windows.py index d7ddde67a2fc..21aba9f789b6 100644 --- a/scipy/signal/windows/_windows.py +++ b/scipy/signal/windows/_windows.py @@ -1299,7 +1299,7 @@ def kaiser(M, beta, sym=True, *, xp=None, device=None): n = xp.arange(0, M, dtype=xp.float64, device=device) alpha = (M - 1) / 2.0 w = (special.i0(beta * xp.sqrt(1 - ((n - alpha) / alpha) ** 2.0)) / - special.i0(beta)) + special.i0(xp.asarray(beta, dtype=xp.float64))) return _truncate(w, needs_trunc) From 4e2e7779e0476ca56069bda5edad63a556a22592 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 8 May 2025 18:50:44 +0100 Subject: [PATCH 161/251] MAINT: mass rename `make_skip_xp_backends` to `make_xp_test_case` (#22949) * MAINT: mass rename `make_skip_xp_backends` to `make_xp_test_case` * kick CI --- scipy/_lib/_array_api.py | 17 +++--- scipy/constants/tests/test_constants.py | 8 +-- scipy/optimize/tests/test_optimize.py | 8 +-- scipy/special/tests/test_logsumexp.py | 8 +-- scipy/stats/tests/test_stats.py | 74 ++++++++++++------------- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 3ff8d17d260e..39360a6bf26b 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -758,7 +758,7 @@ def xp_capabilities( Array API compatible backends. This decorator has two effects: - 1. It allows tagging tests with ``@make_skip_xp_backends`` or + 1. It allows tagging tests with ``@make_xp_test_case`` or ``make_xp_pytest_param`` (see below) to automatically generate SKIP/XFAIL markers and perform additional backend-specific testing, such as extra validation for Dask and JAX; @@ -767,7 +767,7 @@ def xp_capabilities( See Also -------- - make_skip_xp_backends + make_xp_test_case make_xp_pytest_param array_api_extra.testing.lazy_xp_function """ @@ -835,7 +835,7 @@ def _make_xp_pytest_marks(*funcs, capabilities_table=None): return marks -def make_skip_xp_backends(*funcs, capabilities_table=None): +def make_xp_test_case(*funcs, capabilities_table=None): capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) """Generate pytest decorator for a test function that tests functionality @@ -860,18 +860,19 @@ def make_skip_xp_backends(*funcs, capabilities_table=None): def make_xp_pytest_param(func, capabilities_table=None): - """Variant of ``make_skip_xp_backends`` that returns a pytest.param for a function, + """Variant of ``make_xp_test_case`` that returns a pytest.param for a function, with all necessary skip_xp_backends and xfail_xp_backends marks applied:: @pytest.mark.parametrize( - func, [make_xp_pytest_param(f1), make_xp_pytest_param(f2)] + "func", [make_xp_pytest_param(f1), make_xp_pytest_param(f2)] ) def test(func, xp): ... - The above is equivalent to: + The above is equivalent to:: + @pytest.mark.parametrize( - func, [ + "func", [ pytest.param(f1, marks=[ pytest.mark.skip_xp_backends(...), pytest.mark.xfail_xp_backends(...), ...]), @@ -885,7 +886,7 @@ def test(func, xp): See Also -------- xp_capabilities - make_skip_xp_backends + make_xp_test_case array_api_extra.testing.lazy_xp_function """ import pytest diff --git a/scipy/constants/tests/test_constants.py b/scipy/constants/tests/test_constants.py index e1628ce31081..672fac18884d 100644 --- a/scipy/constants/tests/test_constants.py +++ b/scipy/constants/tests/test_constants.py @@ -2,12 +2,12 @@ import scipy.constants as sc from scipy._lib._array_api_no_0d import xp_assert_equal, xp_assert_close -from scipy._lib._array_api import make_skip_xp_backends +from scipy._lib._array_api import make_xp_test_case lazy_xp_modules = [sc] -@make_skip_xp_backends(sc.convert_temperature) +@make_xp_test_case(sc.convert_temperature) class TestConvertTemperature: def test_convert_temperature(self, xp): xp_assert_equal(sc.convert_temperature(xp.asarray(32.), 'f', 'Celsius'), @@ -62,7 +62,7 @@ def test_convert_temperature_errors(self): sc.convert_temperature(1, old_scale="kelvin", new_scale="brie") -@make_skip_xp_backends(sc.lambda2nu) +@make_xp_test_case(sc.lambda2nu) class TestLambdaToNu: def test_lambda_to_nu(self, xp): xp_assert_equal(sc.lambda2nu(xp.asarray([sc.speed_of_light, 1])), @@ -73,7 +73,7 @@ def test_lambda_to_nu_array_like(self): xp_assert_close(sc.lambda2nu([sc.speed_of_light, 1]), [1, sc.speed_of_light]) -@make_skip_xp_backends(sc.nu2lambda) +@make_xp_test_case(sc.nu2lambda) class TestNuToLambda: def test_nu_to_lambda(self, xp): xp_assert_equal(sc.nu2lambda(xp.asarray([sc.speed_of_light, 1])), diff --git a/scipy/optimize/tests/test_optimize.py b/scipy/optimize/tests/test_optimize.py index ca56aeb9d63c..0a51dc290c41 100644 --- a/scipy/optimize/tests/test_optimize.py +++ b/scipy/optimize/tests/test_optimize.py @@ -35,7 +35,7 @@ from scipy.sparse import (coo_matrix, csc_matrix, csr_matrix, coo_array, csr_array, csc_array) from scipy._lib._array_api_no_0d import xp_assert_equal -from scipy._lib._array_api import make_skip_xp_backends +from scipy._lib._array_api import make_xp_test_case from scipy._lib._util import MapWrapper lazy_xp_modules = [optimize] @@ -2475,20 +2475,20 @@ def test_powell_output(): class TestRosen: - @make_skip_xp_backends(optimize.rosen) + @make_xp_test_case(optimize.rosen) def test_rosen(self, xp): # integer input should be promoted to the default floating type x = xp.asarray([1, 1, 1]) xp_assert_equal(optimize.rosen(x), xp.asarray(0.)) - @make_skip_xp_backends(optimize.rosen_der) + @make_xp_test_case(optimize.rosen_der) def test_rosen_der(self, xp): x = xp.asarray([1, 1, 1, 1]) xp_assert_equal(optimize.rosen_der(x), xp.zeros_like(x, dtype=xp.asarray(1.).dtype)) - @make_skip_xp_backends(optimize.rosen_hess, optimize.rosen_hess_prod) + @make_xp_test_case(optimize.rosen_hess, optimize.rosen_hess_prod) def test_hess_prod(self, xp): one = xp.asarray(1.) diff --git a/scipy/special/tests/test_logsumexp.py b/scipy/special/tests/test_logsumexp.py index c587a9c4b26d..5c897aa7ec7e 100644 --- a/scipy/special/tests/test_logsumexp.py +++ b/scipy/special/tests/test_logsumexp.py @@ -4,7 +4,7 @@ import numpy as np -from scipy._lib._array_api import (is_array_api_strict, make_skip_xp_backends, +from scipy._lib._array_api import (is_array_api_strict, make_xp_test_case, xp_default_dtype, xp_device) from scipy._lib._array_api_no_0d import (xp_assert_equal, xp_assert_close, xp_assert_less) @@ -32,7 +32,7 @@ def test_wrap_radians(xp): @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning") @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning") @pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning") -@make_skip_xp_backends(logsumexp) +@make_xp_test_case(logsumexp) class TestLogSumExp: def test_logsumexp(self, xp): # Test with zero-size array @@ -316,7 +316,7 @@ def test_gh22903(self, xp): xp_assert_close(logsumexp(a, b=b), xp.asarray(xp.nan)) -@make_skip_xp_backends(softmax) +@make_xp_test_case(softmax) class TestSoftmax: def test_softmax_fixtures(self, xp): xp_assert_close(softmax(xp.asarray([1000., 0., 0., 0.])), @@ -385,7 +385,7 @@ def test_softmax_array_like(self): np.asarray([1., 0., 0., 0.]), rtol=1e-13) -@make_skip_xp_backends(log_softmax) +@make_xp_test_case(log_softmax) class TestLogSoftmax: def test_log_softmax_basic(self, xp): xp_assert_close(log_softmax(xp.asarray([1000., 1.])), diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index ac8f7ac0810a..78d993a06c2f 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -42,7 +42,7 @@ from scipy.conftest import skip_xp_invalid_arg from scipy._lib._array_api import (array_namespace, eager_warns, is_lazy_array, is_numpy, is_torch, xp_default_dtype, xp_size, - SCIPY_ARRAY_API, make_skip_xp_backends) + SCIPY_ARRAY_API, make_xp_test_case) from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal import scipy._lib.array_api_extra as xpx @@ -79,7 +79,7 @@ class TestTrimmedStats: # TODO: write these tests to handle missing values properly dprec = np.finfo(np.float64).precision - @make_skip_xp_backends(stats.tmean) + @make_xp_test_case(stats.tmean) def test_tmean(self, xp): default_dtype = xp_default_dtype(xp) x = xp.asarray(X, dtype=default_dtype) @@ -126,7 +126,7 @@ def test_tmean(self, xp): y_true = [4.5, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan] xp_assert_close(y, xp.asarray(y_true)) - @make_skip_xp_backends(stats.tvar) + @make_xp_test_case(stats.tvar) @pytest.mark.filterwarnings( "ignore:invalid value encountered in divide:RuntimeWarning:dask" ) @@ -158,7 +158,7 @@ def test_tvar(self, xp): xp_assert_close(y[0], xp.asarray(4.666666666666667)) xp_assert_equal(y[1], xp.asarray(xp.nan)) - @make_skip_xp_backends(stats.tstd) + @make_xp_test_case(stats.tstd) def test_tstd(self, xp): x = xp.asarray(X.tolist()) # use default dtype of xp @@ -168,7 +168,7 @@ def test_tstd(self, xp): y = stats.tstd(x, limits=None) xp_assert_close(y, xp.std(x, correction=1)) - @make_skip_xp_backends(stats.tmin) + @make_xp_test_case(stats.tmin) def test_tmin(self, xp): x = xp.arange(10.) xp_assert_equal(stats.tmin(x), xp.asarray(0.)) @@ -207,7 +207,7 @@ def test_tmin_scalar_and_nanpolicy(self, xp): with assert_raises(ValueError, match=msg): stats.tmin(x, nan_policy='foobar') - @make_skip_xp_backends(stats.tmax) + @make_xp_test_case(stats.tmax) def test_tmax(self, xp): x = xp.arange(10.) xp_assert_equal(stats.tmax(x), xp.asarray(9.)) @@ -248,7 +248,7 @@ def test_tmax_scalar_and_nanpolicy(self, xp): with assert_raises(ValueError, match=msg): stats.tmax(x, nan_policy='foobar') - @make_skip_xp_backends(stats.tmin, stats.tmax) + @make_xp_test_case(stats.tmin, stats.tmax) def test_tmin_tmax_int_dtype(self, xp): x = xp.reshape(xp.arange(10, dtype=xp.int16), (2, 5)).T @@ -264,14 +264,14 @@ def test_tmin_tmax_int_dtype(self, xp): xp_assert_equal(stats.tmax(x, upperlimit=3), xp.asarray([3., xp.nan])) @skip_xp_backends(eager_only=True, reason="Only with data-dependent output dtype") - @make_skip_xp_backends(stats.tmin, stats.tmax) + @make_xp_test_case(stats.tmin, stats.tmax) def test_gh_22626(self, xp): # Test that `tmin`/`tmax` returns exact result with outrageously large integers x = xp.arange(2**62, 2**62+10) xp_assert_equal(stats.tmin(x[None, :]), x) xp_assert_equal(stats.tmax(x[None, :]), x) - @make_skip_xp_backends(stats.tsem) + @make_xp_test_case(stats.tsem) def test_tsem(self, xp): x = xp.asarray(X.tolist()) # use default dtype of xp @@ -396,7 +396,7 @@ def test_pROUNDROUND(self): assert_approx_equal(r,1.0) -@make_skip_xp_backends(stats.pearsonr) +@make_xp_test_case(stats.pearsonr) class TestPearsonr: def test_pearsonr_result_attributes(self): res = stats.pearsonr(X, X) @@ -2817,7 +2817,7 @@ def __array__(self, dtype=None, copy=None): stats.mode(np.arange(3, dtype=object)) -@make_skip_xp_backends(stats.sem) +@make_xp_test_case(stats.sem) class TestSEM: testcase = [1., 2., 3., 4.] @@ -2864,7 +2864,7 @@ def test_sem_nan_policy(self, xp): assert_raises(ValueError, stats.sem, x, nan_policy='foobar') -@make_skip_xp_backends(stats.zmap) +@make_xp_test_case(stats.zmap) class TestZmap: @pytest.mark.parametrize( @@ -2974,7 +2974,7 @@ def test_complex_gh22404(self, xp): xp_assert_close(res, ref) -@make_skip_xp_backends(stats.zscore) +@make_xp_test_case(stats.zscore) class TestZscore: def test_zscore(self, xp): # not in R, so tested by using: @@ -3144,7 +3144,7 @@ def test_zscore_masked_element_0_gh19039(self, xp): assert_equal(res[1:], np.nan) -@make_skip_xp_backends(stats.gzscore) +@make_xp_test_case(stats.gzscore) class TestGZscore: def test_gzscore_normal_array(self, xp): x = np.asarray([1, 2, 3, 4]) @@ -3473,7 +3473,7 @@ def test_scale(self): assert_raises(ValueError, stats.iqr, x, scale='foobar') -@make_skip_xp_backends(stats.moment) +@make_xp_test_case(stats.moment) class TestMoments: """ Comparison numbers are found using R v.1.5.1 @@ -3661,7 +3661,7 @@ def test_empty_1d(self, xp): xp_assert_equal(res, xp.asarray(xp.nan)) -@make_skip_xp_backends(stats.skew) +@make_xp_test_case(stats.skew) class TestSkew(SkewKurtosisTest): def stat_fun(self, x): return stats.skew(x) @@ -3760,7 +3760,7 @@ def skewness(a, axis, bias): xp_assert_close(res, ref) -@make_skip_xp_backends(stats.kurtosis) +@make_xp_test_case(stats.kurtosis) class TestKurtosis(SkewKurtosisTest): def stat_fun(self, x): return stats.kurtosis(x) @@ -3891,7 +3891,7 @@ def ttest_data_axis_strategy(draw): return data, axis -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) class TestStudentTest: # Preserving original test cases. # Recomputed statistics and p-values with R t.test, e.g. @@ -4211,7 +4211,7 @@ def test_nd(self, shape): ] -@make_skip_xp_backends(stats.power_divergence) +@make_xp_test_case(stats.power_divergence) class TestPowerDivergence: def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_, @@ -4408,7 +4408,7 @@ def test_power_divergence_against_cressie_read_data(self, xp): xp_assert_close(stat, expected_stat, rtol=5e-3) -@make_skip_xp_backends(stats.chisquare) +@make_xp_test_case(stats.chisquare) class TestChisquare: def test_chisquare_12282a(self, xp): # Currently `chisquare` is implemented via power_divergence @@ -5170,7 +5170,7 @@ def _stats(x, axis=0): return _stats(x1, axis) + _stats(x2, axis) -@make_skip_xp_backends(stats.ttest_ind, stats.ttest_ind_from_stats) +@make_xp_test_case(stats.ttest_ind, stats.ttest_ind_from_stats) def test_ttest_ind(xp): # regression test tr = xp.asarray(1.0912746897927283) @@ -5882,7 +5882,7 @@ def test_trim_bounds_error(self, trim): stats.ttest_ind([1, 2], [2, 1], trim=trim) -@make_skip_xp_backends(stats.ttest_ind) +@make_xp_test_case(stats.ttest_ind) class Test_ttest_CI: # indices in order [alternative={two-sided, less, greater}, # equal_var={False, True}, trim={0, 0.2}] @@ -5977,9 +5977,9 @@ def test__broadcast_concatenate(): assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n] -@make_skip_xp_backends(stats.ttest_ind) +@make_xp_test_case(stats.ttest_ind) class TestTTestInd: - @make_skip_xp_backends(stats.ttest_ind_from_stats) + @make_xp_test_case(stats.ttest_ind_from_stats) def test_ttest_ind_with_uneq_var(self, xp): # check vs. R `t.test`, e.g. # options(digits=20) @@ -6166,7 +6166,7 @@ def test_ttest_ind_nonaxis_size_zero_different_lengths(self, xp): assert res.pvalue.shape == (5, 0) -@make_skip_xp_backends(stats.ttest_ind_from_stats) +@make_xp_test_case(stats.ttest_ind_from_stats) class TestTTestIndFromStats: @pytest.mark.skip_xp_backends(np_only=True, reason="Other backends don't like integers") @@ -6231,7 +6231,7 @@ def _convert_pvalue_alternative(t, p, alt, xp): @pytest.mark.slow @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) def test_ttest_1samp_new(xp): n1, n2, n3 = (10, 15, 20) rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3)) @@ -6315,7 +6315,7 @@ def test_ttest_1samp_new_omit(xp): xp_assert_close(t, tr) -@make_skip_xp_backends(stats.ttest_1samp) +@make_xp_test_case(stats.ttest_1samp) @pytest.mark.skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.') def test_ttest_1samp_popmean_array(xp): # when popmean.shape[axis] != 1, raise an error @@ -6346,7 +6346,7 @@ def test_ttest_1samp_popmean_array(xp): xp_assert_close(res.pvalue, ref) -@make_skip_xp_backends(stats.describe) +@make_xp_test_case(stats.describe) class TestDescribe: @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") def test_describe_scalar(self, xp): @@ -6537,7 +6537,7 @@ def test_nan(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.skewtest) +@make_xp_test_case(stats.skewtest) class TestSkewTest(NormalityTests): test_name = 'skewtest' case_ref = (1.98078826090875881, 0.04761502382843208) # statistic, pvalue @@ -6565,7 +6565,7 @@ def test_skewtest_too_few_observations(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.kurtosistest) +@make_xp_test_case(stats.kurtosistest) class TestKurtosisTest(NormalityTests): test_name = 'kurtosistest' case_ref = (-0.01403734404759738, 0.98880018772590561) # statistic, pvalue @@ -6599,7 +6599,7 @@ def test_kurtosistest_too_few_observations(self, xp): xp_assert_equal(res.pvalue, NaN) -@make_skip_xp_backends(stats.normaltest) +@make_xp_test_case(stats.normaltest) class TestNormalTest(NormalityTests): test_name = 'normaltest' case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue @@ -6639,7 +6639,7 @@ def test_input_validation(self): stats.ranksums(self.x, self.y, alternative='foobar') -@make_skip_xp_backends(stats.jarque_bera) +@make_xp_test_case(stats.jarque_bera) class TestJarqueBera: def test_jarque_bera_against_R(self, xp): # library(tseries) @@ -6911,7 +6911,7 @@ def check_equal_pmean(*args, **kwargs): return check_equal_xmean(*args, mean_fun=stats.pmean, **kwargs) -@make_skip_xp_backends(stats.hmean) +@make_xp_test_case(stats.hmean) class TestHMean: @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") def test_0(self, xp): @@ -7028,7 +7028,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) -@make_skip_xp_backends(stats.gmean) +@make_xp_test_case(stats.gmean) class TestGMean: @pytest.mark.filterwarnings( "ignore:divide by zero encountered in log:RuntimeWarning:dask" @@ -7142,7 +7142,7 @@ def test_weights_masked_1d_array(self, xp): dtype=np.float64, xp=xp) -@make_skip_xp_backends(stats.pmean) +@make_xp_test_case(stats.pmean) class TestPMean: def pmean_reference(a, p): @@ -7262,7 +7262,7 @@ def fun(a, axis, weights): check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5, xp=xp) -@make_skip_xp_backends(stats.gstd) +@make_xp_test_case(stats.gstd) class TestGSTD: # must add 1 as `gstd` is only defined for positive values array_1d = (np.arange(2 * 3 * 4) + 1).tolist() @@ -8174,7 +8174,7 @@ def test_no_args_gh20661(self): -@make_skip_xp_backends(stats.combine_pvalues) +@make_xp_test_case(stats.combine_pvalues) class TestCombinePvalues: # Reference values computed using the following R code: # options(digits=16) From cbaeaa2c0fda81dcc76bafccb2620bca5ca9f076 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Wed, 7 May 2025 18:38:20 +0100 Subject: [PATCH 162/251] ENH: `special`: add xp_capabilities --- scipy/_lib/_array_api.py | 38 ++- scipy/special/__init__.py | 5 +- .../special/_support_alternative_backends.py | 264 +++++++++++------- .../test_support_alternative_backends.py | 144 ++++++---- 4 files changed, 270 insertions(+), 181 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 39360a6bf26b..53de15b1ca3d 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -789,17 +789,21 @@ def xp_capabilities( sphinx_capabilities = _make_sphinx_capabilities(**capabilities) def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - return f(*args, **kwargs) - - capabilities_table[wrapper] = capabilities + # Don't use a wrapper, as in some cases @xp_capabilities is + # applied to a ufunc + capabilities_table[f] = capabilities note = _make_capabilities_note(f.__name__, sphinx_capabilities) - doc = FunctionDoc(wrapper) + doc = FunctionDoc(f) doc['Notes'].append(note) - wrapper.__doc__ = str(doc).split("\n", 1)[1] # remove signature + doc = str(doc).split("\n", 1)[1] # remove signature + try: + f.__doc__ = doc + except AttributeError: + # Can't update __doc__ on Cython ufuncs if SciPy + # was compiled against NumPy 1.x + pass - return wrapper + return f return decorator @@ -859,7 +863,7 @@ def make_xp_test_case(*funcs, capabilities_table=None): return lambda func: functools.reduce(lambda f, g: g(f), marks, func) -def make_xp_pytest_param(func, capabilities_table=None): +def make_xp_pytest_param(func, *args, capabilities_table=None): """Variant of ``make_xp_test_case`` that returns a pytest.param for a function, with all necessary skip_xp_backends and xfail_xp_backends marks applied:: @@ -883,6 +887,20 @@ def test(func, xp): def test(func, xp): ... + Parameters + ---------- + func : Callable + Function to be tested. It must be decorated with ``@xp_capabilities``. + *args : Any, optional + Extra pytest parameters for the use case, e.g.:: + + @pytest.mark.parametrize("func,verb", [ + make_xp_pytest_param(f1, "hello"), + make_xp_pytest_param(f2, "world")]) + def test(func, verb, xp): + # iterates on (func=f1, verb="hello") + # and (func=f2, verb="world") + See Also -------- xp_capabilities @@ -892,7 +910,7 @@ def test(func, xp): import pytest marks = _make_xp_pytest_marks(func, capabilities_table=capabilities_table) - return pytest.param(func, marks=marks) + return pytest.param(func, *args, marks=marks, id=func.__name__) # Is it OK to have a dictionary that is mutated (once upon import) in many places? diff --git a/scipy/special/__init__.py b/scipy/special/__init__.py index 0c2fe8809279..2572e27b48d8 100644 --- a/scipy/special/__init__.py +++ b/scipy/special/__init__.py @@ -785,10 +785,7 @@ from ._ufuncs import * # Replace some function definitions from _ufuncs to add Array API support -from ._support_alternative_backends import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, - chdtr, chdtrc, betainc, betaincc, stdtr, stdtrit) +from ._support_alternative_backends import * from . import _basic from ._basic import * diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 74a1c4f32418..5bc89043c443 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -1,66 +1,138 @@ import functools import operator +from collections.abc import Callable +from dataclasses import dataclass +from types import ModuleType import numpy as np from scipy._lib._array_api import ( array_namespace, scipy_namespace_for, is_numpy, is_dask, is_marray, - xp_promote, SCIPY_ARRAY_API + xp_promote, xp_capabilities, SCIPY_ARRAY_API ) import scipy._lib.array_api_extra as xpx from . import _ufuncs -# These don't really need to be imported, but otherwise IDEs might not realize -# that these are defined in this file / report an error in __init__.py -from ._ufuncs import ( - log_ndtr, ndtr, ndtri, erf, erfc, i0, i0e, i1, i1e, gammaln, # noqa: F401 - gammainc, gammaincc, logit, expit, entr, rel_entr, xlogy, # noqa: F401 - chdtr, chdtrc, betainc, betaincc, stdtr, stdtrit # noqa: F401 -) - -array_api_compat_prefix = "scipy._lib.array_api_compat" -def get_array_special_func(f_name, xp): - if is_numpy(xp): - return getattr(_ufuncs, f_name) - - spx = scipy_namespace_for(xp) - if spx is not None: - f = getattr(spx.special, f_name, None) +@dataclass +class _FuncInfo: + # NumPy-only function. IT MUST BE ELEMENTWISE. + func: Callable + # Number of arguments, not counting out= + # This is for testing purposes only, due to the fact that + # inspect.signature() just returns *args for Cython ufuncs. + n_args: int + # @xp_capabilities decorator, for the purpose of + # documentation and unit testing. Omit to indicate + # full support for all backends. + xp_capabilities: Callable[[Callable], Callable] | None = None + # Generic implementation to fall back on if there is no native dispatch + # available. This is a function that accepts (main namespace, scipy namespace) + # and returns the final callable, or None if not available. + generic_impl: Callable[ + [ModuleType, ModuleType | None], Callable | None + ] | None = None + + @property + def name(self): + return self.func.__name__ + + # These are needed by @lru_cache below + def __hash__(self): + return hash(self.func) + + def __eq__(self, other): + return isinstance(other, _FuncInfo) and self.func == other.func + + @property + def wrapper(self): + if self.name in globals(): + # Already initialised. We are likely in a unit test. + # Return function potentially overridden by xpx.testing.lazy_xp_function. + import scipy.special + return getattr(scipy.special, self.name) + + if SCIPY_ARRAY_API: + @functools.wraps(self.func) + def wrapped(*args, **kwargs): + xp = array_namespace(*args) + return self._wrapper_for(xp)(*args, **kwargs) + + # Allow pickling the function. Normally this is done by @wraps, + # but in this case it doesn't work because self.func is a ufunc. + wrapped.__module__ = "scipy.special" + wrapped.__qualname__ = self.name + func = wrapped + else: + func = self.func + + capabilities = self.xp_capabilities or xp_capabilities() + # In order to retain a naked Cython ufunc when SCIPY_ARRAY_API is + # disabled, xp_capabilities must apply its changes in place. + cap_func = capabilities(func) + assert cap_func is func + return func + + @functools.lru_cache(1000) + def _wrapper_for(self, xp): + if is_numpy(xp): + return self.func + + # If a native implementation is available, use that + spx = scipy_namespace_for(xp) + f = _get_native_func(spx, self.name) if f is not None: return f - # if generic array-API implementation is available, use that; - # otherwise, fall back to NumPy/SciPy - if f_name in _generic_implementations: - f = _generic_implementations[f_name](xp=xp, spx=spx) - if f is not None: - return f + # If generic Array API implementation is available, use that + if self.generic_impl is not None: + f = self.generic_impl(xp, spx) + if f is not None: + return f - def f(*args, **kwargs): if is_marray(xp): - _f = globals()[f_name] # Allow nested wrapping - data_args = [arg.data for arg in args] - out = _f(*data_args, **kwargs) - mask = functools.reduce(operator.or_, (arg.mask for arg in args)) - return xp.asarray(out, mask=mask) - - elif is_dask(xp): - # IMPORTANT: map_blocks works only because all ufuncs in this module + # Unwrap the array, apply the function on the wrapped namespace, + # and then re-wrap it. + # IMPORTANT: this only works because all functions in this module + # are elementwise. Otherwise, we would not be able to define a + # general rule for mask propagation. + + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + data_args = [arg.data for arg in args] + out = _f(*data_args, **kwargs) + mask = functools.reduce(operator.or_, (arg.mask for arg in args)) + return xp.asarray(out, mask=mask) + + return f + + if is_dask(xp): + # Apply the function to each block of the Dask array. + # IMPORTANT: map_blocks works only because all functions in this module # are elementwise. It would be a grave mistake to apply this to gufuncs # or any other function with reductions, as they would change their # output depending on chunking! - _f = globals()[f_name] # Allow nested wrapping - # Hide dtype kwarg from map_blocks - return xp.map_blocks(functools.partial(_f, **kwargs), *args) + _f = globals()[self.name] # Allow nested wrapping + def f(*args, _f=_f, xp=xp, **kwargs): + # Hide dtype kwarg from map_blocks + return xp.map_blocks(functools.partial(_f, **kwargs), *args) - else: - _f = getattr(_ufuncs, f_name) + return f + + # As a final resort, use the NumPy/SciPy implementation + _f = self.func + def f(*args, _f=_f, xp=xp, **kwargs): + # TODO use xpx.lazy_apply to add jax.jit support + # (but dtype propagation can be non-trivial) args = [np.asarray(arg) for arg in args] out = _f(*args, **kwargs) return xp.asarray(out) - return f + return f + + +def _get_native_func(spx, f_name): + return getattr(spx.special, f_name, None) if spx else None def _rel_entr(xp, spx): @@ -93,19 +165,13 @@ def __xlogy(x, y, *, xp=xp): return __xlogy -def _get_native_func(xp, spx, f_name): - f = getattr(spx.special, f_name, None) if spx else None - if f is None and hasattr(xp, 'special'): - f = getattr(xp.special, f_name, None) - return f - def _chdtr(xp, spx): # The difference between this and just using `gammainc` # defined by `get_array_special_func` is that if `gammainc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtr`. - gammainc = _get_native_func(xp, spx, 'gammainc') # noqa: F811 + gammainc = _get_native_func(spx, 'gammainc') if gammainc is None: return None @@ -124,7 +190,7 @@ def _chdtrc(xp, spx): # defined by `get_array_special_func` is that if `gammaincc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtrc`. - gammaincc = _get_native_func(xp, spx, 'gammaincc') # noqa: F811 + gammaincc = _get_native_func(spx, 'gammaincc') if gammaincc is None: return None @@ -137,7 +203,7 @@ def __chdtrc(v, x): def _betaincc(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + betainc = _get_native_func(spx, 'betainc') if betainc is None: return None @@ -148,7 +214,7 @@ def __betaincc(a, b, x): def _stdtr(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + betainc = _get_native_func(spx, 'betainc') if betainc is None: return None @@ -161,11 +227,12 @@ def __stdtr(df, t): def _stdtrit(xp, spx): - betainc = _get_native_func(xp, spx, 'betainc') # noqa: F811 + # Need either native stdtr or native betainc + stdtr = _get_native_func(spx, 'stdtr') or _stdtr(xp, spx) # If betainc is not defined, the root-finding would be done with `xp` # despite `stdtr` being evaluated with SciPy/NumPy `stdtr`. Save the # conversions: in this case, just evaluate `stdtrit` with SciPy/NumPy. - if betainc is None: + if stdtr is None: return None from scipy.optimize.elementwise import bracket_root, find_root @@ -179,62 +246,45 @@ def fun(t, df, p): return stdtr(df, t) - p return __stdtrit -_generic_implementations = {'rel_entr': _rel_entr, - 'xlogy': _xlogy, - 'chdtr': _chdtr, - 'chdtrc': _chdtrc, - 'betaincc': _betaincc, - 'stdtr': _stdtr, - 'stdtrit': _stdtrit, - } - - -# functools.wraps doesn't work because: -# 'numpy.ufunc' object has no attribute '__module__' -def support_alternative_backends(f_name): - func = getattr(_ufuncs, f_name) - - @functools.wraps(func) - def wrapped(*args, **kwargs): - xp = array_namespace(*args) - f = get_array_special_func(f_name, xp) - return f(*args, **kwargs) - - return wrapped - - -# function name: number of args (for testing purposes) -array_special_func_map = { - 'log_ndtr': 1, - 'ndtr': 1, - 'ndtri': 1, - 'erf': 1, - 'erfc': 1, - 'i0': 1, - 'i0e': 1, - 'i1': 1, - 'i1e': 1, - 'gammaln': 1, - 'gammainc': 2, - 'gammaincc': 2, - 'logit': 1, - 'expit': 1, - 'entr': 1, - 'rel_entr': 2, - 'xlogy': 2, - 'chdtr': 2, - 'chdtrc': 2, - 'betainc': 3, - 'betaincc': 3, - 'stdtr': 2, - 'stdtrit': 2, -} - -globals().update( - {f_name: support_alternative_backends(f_name) - if SCIPY_ARRAY_API - else getattr(_ufuncs, f_name) - for f_name in array_special_func_map} +# Inventory of automatically dispatched functions +# IMPORTANT: these must all be **elementwise** functions! + +# PyTorch doesn't implement `betainc`. +# On torch CPU we can fall back to Cython, but on GPU it won't work. +_needs_betainc = xp_capabilities(cpu_only=True, exceptions=['jax.numpy', 'cupy']) + +_special_funcs = ( + _FuncInfo(_ufuncs.betainc, 3, _needs_betainc), + _FuncInfo(_ufuncs.betaincc, 3, _needs_betainc, generic_impl=_betaincc), + _FuncInfo(_ufuncs.chdtr, 2, generic_impl=_chdtr), + _FuncInfo(_ufuncs.chdtrc, 2, generic_impl=_chdtrc), + _FuncInfo(_ufuncs.erf, 1), + _FuncInfo(_ufuncs.erfc, 1), + _FuncInfo(_ufuncs.entr, 1), + _FuncInfo(_ufuncs.expit, 1), + _FuncInfo(_ufuncs.i0, 1), + _FuncInfo(_ufuncs.i0e, 1), + _FuncInfo(_ufuncs.i1, 1), + _FuncInfo(_ufuncs.i1e, 1), + _FuncInfo(_ufuncs.log_ndtr, 1), + _FuncInfo(_ufuncs.logit, 1), + _FuncInfo(_ufuncs.gammaln, 1), + _FuncInfo(_ufuncs.gammainc, 2), + _FuncInfo(_ufuncs.gammaincc, 2), + _FuncInfo(_ufuncs.ndtr, 1), + _FuncInfo(_ufuncs.ndtri, 1), + _FuncInfo(_ufuncs.rel_entr, 2, generic_impl=_rel_entr), + _FuncInfo(_ufuncs.stdtr, 2, _needs_betainc, generic_impl=_stdtr), + _FuncInfo(_ufuncs.stdtrit, 2, + xp_capabilities( + cpu_only=True, exceptions=['cupy'], # needs betainc + skip_backends=[("jax.numpy", "no scipy.optimize support")]), + generic_impl=_stdtrit), + _FuncInfo(_ufuncs.xlogy, 2, generic_impl=_xlogy), ) -__all__ = list(array_special_func_map) +# Override ufuncs. +# When SCIPY_ARRAY_API is disabled, this exclusively updates the docstrings in place +# and populates the xp_capabilities table, while retaining the original Cython ufuncs. +globals().update({nfo.func.__name__: nfo.wrapper for nfo in _special_funcs}) +__all__ = [nfo.func.__name__ for nfo in _special_funcs] diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 78e20046df9d..0e26d029b8ad 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -1,35 +1,23 @@ from functools import partial -from types import ModuleType +import pickle + import pytest from hypothesis import given, strategies import hypothesis.extra.numpy as npst from scipy import special -from scipy.special._support_alternative_backends import (get_array_special_func, - array_special_func_map) +from scipy.special._support_alternative_backends import _special_funcs from scipy._lib._array_api_no_0d import xp_assert_close from scipy._lib._array_api import (is_cupy, is_dask, is_jax, is_torch, - xp_default_dtype, SCIPY_ARRAY_API, SCIPY_DEVICE) + make_xp_pytest_param, make_xp_test_case, + xp_default_dtype) from scipy._lib.array_api_compat import numpy as np -from scipy._lib.array_api_extra.testing import lazy_xp_function - - -special_wrapped = ModuleType("special_wrapped") -lazy_xp_modules = [special_wrapped] -for f_name in array_special_func_map: - f = getattr(special, f_name) - setattr(special_wrapped, f_name, f) - lazy_xp_function(f) +# Run all tests in this module in the Array API CI, including those without +# the xp fixture +pytestmark = pytest.mark.array_api_backends -@pytest.mark.skipif(not SCIPY_ARRAY_API, reason="Alternative backends must be enabled.") -def test_dispatch_to_unrecognized_library(): - xp = pytest.importorskip("array_api_strict") - f = get_array_special_func('ndtr', xp=xp) - x = [1, 2, 3] - res = f(xp.asarray(x)) - ref = xp.asarray(special.ndtr(np.asarray(x))) - xp_assert_close(res, ref) +lazy_xp_modules = [special] def _skip_or_tweak_alternative_backends(xp, f_name, dtypes): @@ -44,15 +32,6 @@ def _skip_or_tweak_alternative_backends(xp, f_name, dtypes): dtypes_np_ref : list[str] The dtypes to use for the reference NumPy arrays. """ - if (SCIPY_DEVICE != 'cpu' - and is_torch(xp) - and f_name in {'stdtr', 'stdtrit', 'betaincc', 'betainc'} - ): - pytest.skip(f"`{f_name}` does not have an array-agnostic implementation " - "and cannot delegate to PyTorch.") - if is_jax(xp) and f_name == "stdtrit": - pytest.skip(f"`{f_name}` requires scipy.optimize support for immutable arrays") - if ((is_jax(xp) and f_name == 'gammaincc') # google/jax#20699 # gh-20972 or ((is_cupy(xp) or is_jax(xp) or is_torch(xp)) and f_name == 'chdtrc')): @@ -93,20 +72,19 @@ def _skip_or_tweak_alternative_backends(xp, f_name, dtypes): return positive_only, dtypes_np_ref -@pytest.mark.parametrize('f_name,n_args', array_special_func_map.items()) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) @pytest.mark.parametrize('shapes', [[(0,)]*4, [tuple()]*4, [(10,)]*4, [(10,), (11, 1), (12, 1, 1), (13, 1, 1, 1)]]) -def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): +@pytest.mark.parametrize('dtype', ['float32', 'float64', 'int64']) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs]) +def test_support_alternative_backends(xp, func, nfo, dtype, shapes): positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( - xp, f_name, [dtype]) - f = getattr(special, f_name) # Unwrapped - fw = getattr(special_wrapped, f_name) # Wrapped by lazy_xp_function + xp, nfo.name, [dtype]) dtype_np = getattr(np, dtype) dtype_xp = getattr(xp, dtype) - shapes = shapes[:n_args] + shapes = shapes[:nfo.n_args] rng = np.random.default_rng(984254252920492019) if 'int' in dtype: iinfo = np.iinfo(dtype_np) @@ -127,27 +105,24 @@ def test_support_alternative_backends(xp, f_name, n_args, dtype, shapes): # Try to trigger bugs related to having multiple chunks. args_xp = [arg.rechunk(5) for arg in args_xp] - res = fw(*args_xp) - ref = f(*args_np) + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped Cython ufunc # When dtype_np is integer, the output dtype can be float atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps xp_assert_close(res, xp.asarray(ref), atol=atol) -@pytest.mark.parametrize('f_name,n_args', - [(f_name, n_args) - for f_name, n_args in array_special_func_map.items() - if n_args >= 2]) +@pytest.mark.parametrize( + 'func, nfo', + [make_xp_pytest_param(i.wrapper, i) for i in _special_funcs if i.n_args >= 2]) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") -def test_support_alternative_backends_mismatched_dtypes(xp, f_name, n_args): +def test_support_alternative_backends_mismatched_dtypes(xp, func, nfo): """Test mix-n-match of int and float arguments""" - assert n_args <= 3 - dtypes = ['int64', 'float32', 'float64'][:n_args] - dtypes_xp = [xp.int64, xp.float32, xp.float64][:n_args] + dtypes = ['int64', 'float32', 'float64'][:nfo.n_args] + dtypes_xp = [xp.int64, xp.float32, xp.float64][:nfo.n_args] positive_only, dtypes_np_ref = _skip_or_tweak_alternative_backends( - xp, f_name, dtypes) - f = getattr(special, f_name) + xp, nfo.name, dtypes) rng = np.random.default_rng(984254252920492019) iinfo = np.iinfo(np.int64) @@ -156,7 +131,7 @@ def test_support_alternative_backends_mismatched_dtypes(xp, f_name, n_args): randint(size=1, dtype=np.int64), rng.standard_normal(size=1, dtype=np.float32), rng.standard_normal(size=1, dtype=np.float64), - ][:n_args] + ][:nfo.n_args] if positive_only: args_np = [np.abs(arg) for arg in args_np] @@ -165,8 +140,8 @@ def test_support_alternative_backends_mismatched_dtypes(xp, f_name, n_args): args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg, dtype_np_ref in zip(args_np, dtypes_np_ref)] - res = f(*args_xp) - ref = f(*args_np) + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped Cython ufunc atol = 10 * np.finfo(ref.dtype).eps xp_assert_close(res, xp.asarray(ref), atol=atol) @@ -175,18 +150,18 @@ def test_support_alternative_backends_mismatched_dtypes(xp, f_name, n_args): @pytest.mark.xslow @given(data=strategies.data()) @pytest.mark.fail_slow(5) -# `reversed` is for developer convenience: test new function first = less waiting -@pytest.mark.parametrize('f_name,n_args', reversed(array_special_func_map.items())) +@pytest.mark.parametrize( + 'func,nfo', [make_xp_pytest_param(nfo.wrapper, nfo) for nfo in _special_funcs]) @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask") +@pytest.mark.filterwarnings("ignore:overflow encountered:RuntimeWarning:dask") @pytest.mark.filterwarnings( "ignore:overflow encountered:RuntimeWarning:array_api_strict" ) -def test_support_alternative_backends_hypothesis(xp, f_name, n_args, data): +def test_support_alternative_backends_hypothesis(xp, func, nfo, data): dtype = data.draw(strategies.sampled_from(['float32', 'float64', 'int64'])) positive_only, [dtype_np_ref] = _skip_or_tweak_alternative_backends( - xp, f_name, [dtype]) - f = getattr(special, f_name) + xp, nfo.name, [dtype]) dtype_np = getattr(np, dtype) dtype_xp = getattr(xp, dtype) @@ -197,21 +172,70 @@ def test_support_alternative_backends_hypothesis(xp, f_name, n_args, data): if positive_only: elements['min_value'] = 0 - shapes, _ = data.draw(npst.mutually_broadcastable_shapes(num_shapes=n_args)) + shapes, _ = data.draw( + npst.mutually_broadcastable_shapes(num_shapes=nfo.n_args)) args_np = [data.draw(npst.arrays(dtype_np, shape, elements=elements)) for shape in shapes] args_xp = [xp.asarray(arg, dtype=dtype_xp) for arg in args_np] args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] - res = f(*args_xp) - ref = f(*args_np) + res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function + ref = nfo.func(*args_np) # Unwrapped Cython ufunc # When dtype_np is integer, the output dtype can be float atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps xp_assert_close(res, xp.asarray(ref), atol=atol) +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_pickle(func): + roundtrip = pickle.loads(pickle.dumps(func)) + assert roundtrip is func + + +@pytest.mark.parametrize("func", [nfo.wrapper for nfo in _special_funcs]) +def test_repr(func): + assert func.__name__ in repr(func) + assert "locals" not in repr(func) + + +@pytest.mark.skipif( + np.__version__ < "2", + reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy 1.x") +@pytest.mark.parametrize('func', [nfo.wrapper for nfo in _special_funcs]) +def test_doc(func): + """xp_capabilities updates the docstring in place. + Make sure it does so exactly once, including when SCIPY_ARRAY_API is not set. + """ + match = "has experimental support for Python Array API" + assert func.__doc__.count(match) == 1 + + +@pytest.mark.parametrize('func,n_args', + [(nfo.wrapper, nfo.n_args) for nfo in _special_funcs]) +def test_ufunc_kwargs(func, n_args): + """Test that numpy-specific out= and dtype= keyword arguments + of ufuncs still work when SCIPY_ARRAY_API is set. + """ + # out= + args = [np.asarray([.1, .2])] * n_args + out = np.empty(2) + y = func(*args, out=out) + xp_assert_close(y, out) + + # out= with out.dtype != args.dtype + out = np.empty(2, dtype=np.float32) + y = func(*args, out=out) + xp_assert_close(y, out) + + # dtype= + y = func(*args, dtype=np.float32) + assert y.dtype == np.float32 + + + +@make_xp_test_case(special.chdtr) def test_chdtr_gh21311(xp): # the edge case behavior of generic chdtr was not right; see gh-21311 # be sure to test at least these cases From 694791a95ed0303b4a2d3cc4be63d302cf47032c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 8 May 2025 22:17:16 +0200 Subject: [PATCH 163/251] MAINT: avoid nested `asarray` calls (#22947) * MAINT: avoid nesting asarray calls (array-api-strict disapproves * Apply suggestions from code review --------- Co-authored-by: Lucas Colley --- scipy/integrate/tests/test_cubature.py | 4 +++- scipy/ndimage/tests/test_filters.py | 4 ++-- scipy/stats/tests/test_morestats.py | 8 ++++---- scipy/stats/tests/test_stats.py | 14 +++++++------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/scipy/integrate/tests/test_cubature.py b/scipy/integrate/tests/test_cubature.py index 691eb213c591..1c3ee9790c5b 100644 --- a/scipy/integrate/tests/test_cubature.py +++ b/scipy/integrate/tests/test_cubature.py @@ -222,7 +222,9 @@ def _eval_indefinite_integral(F, a, b, xp): out = 0 for ind in itertools.product(range(2), repeat=ndim): - selected_points = xp.asarray([points[i, j] for i, j in zip(ind, range(ndim))]) + selected_points = xp.asarray( + [float(points[i, j]) for i, j in zip(ind, range(ndim))] + ) out += pow(-1, sum(ind) + ndim) * F(selected_points) return out diff --git a/scipy/ndimage/tests/test_filters.py b/scipy/ndimage/tests/test_filters.py index 3e98639914dd..ad6448fcae4a 100644 --- a/scipy/ndimage/tests/test_filters.py +++ b/scipy/ndimage/tests/test_filters.py @@ -2860,12 +2860,12 @@ def test_dtype_batch_memory(self, dtype, batch_memory, use_footprint, xp): if batch_memory == 1 else contextlib.nullcontext()) with context: res = ndimage.vectorized_filter(input, xp.sum, **kwargs) - xp_assert_close(res, xp.asarray(ref, dtype=sum_dtype)) + xp_assert_close(res, xp.astype(xp.stack(ref), sum_dtype)) assert res.dtype == sum_dtype output = xp.empty_like(input) res = ndimage.vectorized_filter(input, xp.sum, output=output, **kwargs) - xp_assert_close(res, xp.asarray(ref, dtype=dtype)) + xp_assert_close(res, xp.astype(xp.stack(ref), dtype)) assert res.dtype == dtype def test_mode_valid(self, xp): diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index 628ca9a039dc..f3348d677ab5 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -1804,7 +1804,7 @@ class TestKstat: def test_moments_normal_distribution(self, xp): rng = np.random.RandomState(32149) data = xp.asarray(rng.randn(12345), dtype=xp.float64) - moments = xp.asarray([stats.kstat(data, n) for n in [1, 2, 3, 4]]) + moments = xp.stack([stats.kstat(data, n) for n in [1, 2, 3, 4]]) expected = xp.asarray([0.011315, 1.017931, 0.05811052, 0.0754134], dtype=data.dtype) @@ -1814,7 +1814,7 @@ def test_moments_normal_distribution(self, xp): m1 = stats.moment(data, order=1) m2 = stats.moment(data, order=2) m3 = stats.moment(data, order=3) - xp_assert_close(xp.asarray((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) + xp_assert_close(xp.stack((m1, m2, m3)), expected[:-1], atol=0.02, rtol=1e-2) @pytest.mark.filterwarnings("ignore:invalid value encountered in scalar divide") def test_empty_input(self, xp): @@ -3247,11 +3247,11 @@ def test_axis(self, case, xp): x = xp.asarray(rng.random((6, 7))) res = fun(x, **kwargs, axis=0) - ref = xp.asarray([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) + ref = xp.stack([fun(x[:, i], **kwargs) for i in range(x.shape[1])]) xp_assert_close(res, ref) res = fun(x, **kwargs, axis=1) - ref = xp.asarray([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) + ref = xp.stack([fun(x[i, :], **kwargs) for i in range(x.shape[0])]) xp_assert_close(res, ref) res = fun(x, **kwargs, axis=None) diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index 78d993a06c2f..e33b8ffae5ea 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -5175,8 +5175,8 @@ def test_ttest_ind(xp): # regression test tr = xp.asarray(1.0912746897927283) pr = xp.asarray(0.27647818616351882) - tr_2D = xp.asarray([tr, -tr]) - pr_2D = xp.asarray([pr, pr]) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) rvs1 = xp.linspace(5, 105, 100) rvs2 = xp.linspace(1, 100, 100) @@ -6017,8 +6017,8 @@ def test_ttest_ind_with_uneq_var(self, xp): tr_uneq_n = xp.asarray(0.66745638708050492) pr = xp.asarray(0.27647831993021388) pr_uneq_n = xp.asarray(0.50873585065616544) - tr_2D = xp.asarray([tr, -tr]) - pr_2D = xp.asarray([pr, pr]) + tr_2D = xp.stack([tr, -tr]) + pr_2D = xp.stack([pr, pr]) rvs3 = xp.linspace(1, 100, 25) rvs2 = xp.linspace(1, 100, 100) @@ -6689,8 +6689,8 @@ def test_axis(self, xp): res = stats.jarque_bera(x, axis=1) s0, p0 = stats.jarque_bera(x[0, :]) s1, p1 = stats.jarque_bera(x[1, :]) - xp_assert_close(res.statistic, xp.asarray([s0, s1])) - xp_assert_close(res.pvalue, xp.asarray([p0, p1])) + xp_assert_close(res.statistic, xp.stack([s0, s1])) + xp_assert_close(res.pvalue, xp.stack([p0, p1])) resT = stats.jarque_bera(x.T, axis=0) xp_assert_close(res.statistic, resT.statistic) @@ -8237,7 +8237,7 @@ def test_monotonicity(self, variant, method, xp): pvaluess = xp.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0) combined_pvalues = xp.asarray([ - stats.combine_pvalues(pvaluess[i, :], method=method)[1] + float(stats.combine_pvalues(pvaluess[i, :], method=method)[1]) for i in range(pvaluess.shape[0]) ]) assert xp.all(combined_pvalues[1:] - combined_pvalues[:-1] >= 0) From c70982d3739d56792ec3229c75c666cde5e20a42 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 8 May 2025 23:02:32 +0200 Subject: [PATCH 164/251] MAINT: signal: use xp_promote Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/signal/_filter_design.py | 10 ++-------- scipy/signal/tests/test_filter_design.py | 5 ----- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index ee98f11a7d09..8a6cbd666b3f 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -1217,13 +1217,7 @@ def tf2zpk(b, a): xp = array_namespace(b, a) b, a = normalize(b, a) - if xp.isdtype(b.dtype, 'integral'): - b = xp.astype(b, xp.float64) - if xp.isdtype(a.dtype, 'integral'): - a = xp.astype(a, xp.float64) - - b = b / a[0] - a = a / a[0] + a, b = xp_promote(a, b, xp=xp, force_floating=True) k = b[0] b = b / b[0] @@ -1662,7 +1656,7 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): k = xp.asarray(k) if xp.isdtype(k.dtype, 'complex floating'): if xp.imag(k) != 0: - raise ValueError('k must be real') + raise ValueError('k must be real') k = float(xp.real(k)) else: k = float(k) diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index c9efc2840fd2..c21c6816c607 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -525,11 +525,8 @@ def test_basic(self, xp): # first example thetas = xp.asarray([22.5, 45, 77.5]) mags = xp.asarray([0.8, 0.6, 0.9]) - # z = xp.asarray([xp.exp(theta * deg2rad * 1j) for theta in thetas]) z = xp.exp(1j * deg2rad * thetas) z = xp.concat((z, xp.conj(z))) -# p = xp.asarray([mag * np.exp(theta * deg2rad * 1j) -# for theta, mag in zip(thetas, mags)]) p = xp.exp(1j * deg2rad * thetas) * mags p = xp.concat((p, xp.conj(p))) sos = zpk2sos(z, p, k) @@ -544,8 +541,6 @@ def test_basic(self, xp): assert_array_almost_equal(sos, sos2, decimal=4) # second example -# z = xp.asarray([xp.exp(theta * deg2rad * 1j) -# for theta in (85., 10.)]) thetas = xp.asarray([85., 10.]) z = xp.exp(1j * deg2rad * thetas) z = xp.concat((z, xp.conj(z), xp.asarray([1.0, -1.0]))) From ba134075bd61599604e4d1918538a93907146c6a Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Fri, 9 May 2025 08:19:38 +1000 Subject: [PATCH 165/251] MAINT: wheel downloader --- tools/download-wheels.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/download-wheels.py b/tools/download-wheels.py index 3d5a117e2ba2..47ee49862424 100644 --- a/tools/download-wheels.py +++ b/tools/download-wheels.py @@ -16,7 +16,7 @@ __version__ = '0.1' # Edit these for other projects. -STAGING_URL = 'https://anaconda.org/multibuild-wheels-staging/scipy' +STAGING_URL = "https://pypi.anaconda.org/multibuild-wheels-staging/simple/scipy/" PREFIX = 'scipy' def http_manager(): @@ -47,7 +47,7 @@ def get_wheel_names(version): """ http = http_manager() tmpl = re.compile(rf"^.*{PREFIX}-{version}-.*\.whl$") - index_url = f"{STAGING_URL}/files" + index_url = f"{STAGING_URL}" index_html = http.request('GET', index_url) soup = BeautifulSoup(index_html.data, 'html.parser') return soup.findAll(string=tmpl) From 842cdbdec90d4a6033b2753b3fb046e95e6befe4 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Fri, 9 May 2025 12:38:01 +1000 Subject: [PATCH 166/251] MAINT: use extra URL --- tools/download-wheels.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/download-wheels.py b/tools/download-wheels.py index 47ee49862424..5ff3e1578be2 100644 --- a/tools/download-wheels.py +++ b/tools/download-wheels.py @@ -16,7 +16,8 @@ __version__ = '0.1' # Edit these for other projects. -STAGING_URL = "https://pypi.anaconda.org/multibuild-wheels-staging/simple/scipy/" +STAGING_FILE_URL = "https://pypi.anaconda.org/multibuild-wheels-staging/simple/scipy/" +STAGING_URL = 'https://anaconda.org/multibuild-wheels-staging/scipy' PREFIX = 'scipy' def http_manager(): @@ -47,7 +48,7 @@ def get_wheel_names(version): """ http = http_manager() tmpl = re.compile(rf"^.*{PREFIX}-{version}-.*\.whl$") - index_url = f"{STAGING_URL}" + index_url = f"{STAGING_FILE_URL}" index_html = http.request('GET', index_url) soup = BeautifulSoup(index_html.data, 'html.parser') return soup.findAll(string=tmpl) From 6103e55cf6192fe70896f53b02925dde78db393d Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 9 May 2025 12:21:52 +0200 Subject: [PATCH 167/251] MAINT: signal: do not pre-emptively check taht k is real in zpk2sos --- scipy/signal/_filter_design.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/scipy/signal/_filter_design.py b/scipy/signal/_filter_design.py index 8a6cbd666b3f..54b4fd5ed0df 100644 --- a/scipy/signal/_filter_design.py +++ b/scipy/signal/_filter_design.py @@ -1652,14 +1652,7 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): # convert to numpy, convert back on exit XXX z, p = map(np.asarray, (z, p)) - - k = xp.asarray(k) - if xp.isdtype(k.dtype, 'complex floating'): - if xp.imag(k) != 0: - raise ValueError('k must be real') - k = float(xp.real(k)) - else: - k = float(k) + k = np.asarray(k) if pairing is None: pairing = 'minimal' if analog else 'nearest' @@ -1674,9 +1667,9 @@ def zpk2sos(z, p, k, pairing=None, *, analog=False): if len(z) == len(p) == 0: if not analog: - return xp.asarray([[k, 0., 0., 1., 0., 0.]]) + return xp.asarray(np.asarray([[k, 0., 0., 1., 0., 0.]])) else: - return xp.asarray([[0., 0., k, 0., 0., 1.]]) + return xp.asarray(np.asarray([[0., 0., k, 0., 0., 1.]])) if pairing != 'minimal': # ensure we have the same number of poles and zeros, and make copies From e5b8f9d4203920c15896b6c636922509ef013772 Mon Sep 17 00:00:00 2001 From: Martin Schuck <57562633+amacati@users.noreply.github.com> Date: Sat, 10 May 2025 09:22:14 +0200 Subject: [PATCH 168/251] TST: spatial.transform: Add array API standard support for testing (#22939) --- .../transform/tests/test_rigid_transform.py | 966 ++++---- .../spatial/transform/tests/test_rotation.py | 2026 ++++++++++------- .../transform/tests/test_rotation_spline.py | 37 +- 3 files changed, 1791 insertions(+), 1238 deletions(-) diff --git a/scipy/spatial/transform/tests/test_rigid_transform.py b/scipy/spatial/transform/tests/test_rigid_transform.py index 9bcf5bbd22ee..183db247f665 100644 --- a/scipy/spatial/transform/tests/test_rigid_transform.py +++ b/scipy/spatial/transform/tests/test_rigid_transform.py @@ -1,13 +1,30 @@ import pytest import numpy as np -from numpy.testing import assert_allclose from scipy.spatial.transform import Rotation, RigidTransform from scipy.spatial.transform._rigid_transform import normalize_dual_quaternion +from scipy._lib._array_api import ( + is_lazy_array, + xp_vector_norm, + is_numpy, + xp_assert_close, +) +import scipy._lib.array_api_extra as xpx -def test_repr(): - actual = repr(RigidTransform.identity()) +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + +def rotation_to_xp(r: Rotation, xp): + return Rotation.from_quat(xp.asarray(r.as_quat())) + + +def rigid_transform_to_xp(r: RigidTransform, xp): + return RigidTransform.from_matrix(xp.asarray(r.as_matrix())) + + +def test_repr(xp): + actual = repr(RigidTransform.from_matrix(xp.eye(4))) expected = """\ RigidTransform.from_matrix(array([[1., 0., 0., 0.], [0., 1., 0., 0.], @@ -15,7 +32,8 @@ def test_repr(): [0., 0., 0., 1.]]))""" assert actual == expected - actual = repr(RigidTransform.identity(2)) + tf = RigidTransform.from_matrix(xp.asarray(RigidTransform.identity(2).as_matrix())) + actual = repr(tf) expected = """\ RigidTransform.from_matrix(array([[[1., 0., 0., 0.], [0., 1., 0., 0.], @@ -29,176 +47,240 @@ def test_repr(): assert actual == expected -def test_from_rotation(): +def test_from_rotation(xp): atol = 1e-12 # Test single rotation - r = Rotation.identity() + r = Rotation.from_matrix(xp.eye(3)) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) assert tf.single - r = Rotation.from_euler('z', 90, degrees=True) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix()[:3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf.as_matrix()[:3, 3], [0, 0, 0], atol=atol) - assert_allclose(tf.as_matrix()[3], [0, 0, 0, 1], atol=atol) + xp_assert_close(tf.as_matrix()[:3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf.as_matrix()[:3, 3], xp.asarray([0.0, 0, 0]), atol=atol) + xp_assert_close(tf.as_matrix()[3, :], xp.asarray([0.0, 0, 0, 1]), atol=atol) assert tf.single # Test multiple rotations - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) tf = RigidTransform.from_rotation(r) - assert_allclose(tf.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf.as_matrix()[:, :3, 3], [[0, 0, 0], [0, 0, 0]], atol=atol) - assert_allclose(tf.as_matrix()[:, 3], [[0, 0, 0, 1], [0, 0, 0, 1]], atol=atol) + xp_assert_close(tf.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf.as_matrix()[:, :3, 3], xp.asarray([[0.0, 0, 0], [0, 0, 0]]), + atol=atol) + xp_assert_close(tf.as_matrix()[:, 3, :], xp.asarray([[0.0, 0, 0, 1], [0, 0, 0, 1]]), + atol=atol) assert not tf.single -def test_from_translation(): +def test_from_translation(xp): # Test single translation - t = np.array([1, 2, 3]) + t = xp.asarray([1, 2, 3]) tf = RigidTransform.from_translation(t) - expected = np.eye(4) - expected[:3, 3] = t - assert_allclose(tf.as_matrix(), expected) + expected = xp.eye(4) + expected = xpx.at(expected)[..., :3, 3].set(t) + xp_assert_close(tf.as_matrix(), expected) assert tf.single # Test multiple translations - t = np.array([[1, 2, 3], [4, 5, 6]]) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_translation(t) + for i in range(t.shape[0]): + expected = xp.eye(4) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + xp_assert_close(tf.as_matrix()[i, ...], expected) + assert not tf.single + + +def test_from_translation_array_like(): + # Test single translation + t = [1, 2, 3] + tf = RigidTransform.from_translation(t) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) + assert tf.single + + # Test multiple translations + t = [[1, 2, 3], [4, 5, 6]] tf = RigidTransform.from_translation(t) - for i in range(len(t)): - expected = np.eye(4) - expected[:3, 3] = t[i] - assert_allclose(tf.as_matrix()[i], expected) + tf_expected = RigidTransform.from_translation(np.array(t)) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix()) assert not tf.single -def test_from_matrix(): +def test_from_matrix(xp): atol = 1e-12 # Test single transform matrix - matrix = np.eye(4) - matrix[:3, 3] = [1, 2, 3] + matrix = xp.eye(4) + matrix = xpx.at(matrix)[..., :3, 3].set(xp.asarray([1, 2, 3])) tf = RigidTransform.from_matrix(matrix) - assert_allclose(tf.as_matrix(), matrix, atol=atol) + xp_assert_close(tf.as_matrix(), matrix, atol=atol) assert tf.single # Test multiple transform matrices - matrices = np.array([np.eye(4)]*2) - matrices[0, :3, 3] = [1, 2, 3] - matrices[1, :3, 3] = [4, 5, 6] + matrices = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + matrices = xpx.at(matrices)[0, :3, 3].set(xp.asarray([1, 2, 3])) + matrices = xpx.at(matrices)[1, :3, 3].set(xp.asarray([4, 5, 6])) tf = RigidTransform.from_matrix(matrices) - assert_allclose(tf.as_matrix(), matrices, atol=atol) + xp_assert_close(tf.as_matrix(), matrices, atol=atol) assert not tf.single # Test non-1 determinant - matrix = np.diag([2, 2, 2, 1]) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[..., :3, :3].set(xp.eye(3) * 2.0) tf = RigidTransform.from_matrix(matrix) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) # Test non-orthogonal rotation matrix - matrix = np.array([[1, 1, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1]]) - tf = RigidTransform.from_matrix(matrix) - expected = np.array([[0.894427, 0.447214, 0, 0], - [-0.447214, 0.894427, 0, 0], + matrix = xp.asarray([[1, 1, 0, 0], + [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) - assert_allclose(tf.as_matrix(), expected, atol=1e-6) + tf = RigidTransform.from_matrix(matrix) + expected = xp.asarray([[0.894427, 0.447214, 0, 0], + [-0.447214, 0.894427, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]]) + xp_assert_close(tf.as_matrix(), expected, atol=1e-6) # Test invalid matrix - with pytest.raises(ValueError): - invalid = np.eye(4) - invalid[3, 3] = 2 # Invalid last row - RigidTransform.from_matrix(invalid) + invalid = xp.eye(4) + invalid = xpx.at(invalid)[..., 3, 3].set(2) # Invalid last row + if is_lazy_array(invalid): + tf = RigidTransform.from_matrix(invalid) + assert xp.all(xp.isnan(tf.as_matrix())) + else: + with pytest.raises(ValueError): + RigidTransform.from_matrix(invalid) -def test_from_components(): +def test_from_matrix_array_like(): + # Test single transform matrix + matrix = [[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1]] + expected = np.eye(4) + tf = RigidTransform.from_matrix(matrix) + xp_assert_close(tf.as_matrix(), expected) + assert tf.single + + # Test multiple transform matrices + matrices = [matrix, matrix] + tf = RigidTransform.from_matrix(matrices) + for i in range(len(matrices)): + xp_assert_close(tf.as_matrix()[i, ...], expected) + assert not tf.single + + +def test_from_components(xp): atol = 1e-12 # Test single rotation and translation - t = np.array([1, 2, 3]) - r = Rotation.from_euler('zyx', [90, 0, 0], degrees=True) + t = xp.asarray([1, 2, 3]) + r = Rotation.from_euler("zyx", xp.asarray([90, 0, 0]), degrees=True) tf = RigidTransform.from_components(t, r) - expected = np.zeros((4, 4)) - expected[:3, :3] = r.as_matrix() - expected[:3, 3] = t - expected[3, 3] = 1 - assert_allclose(tf.as_matrix(), expected, atol=atol) + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()) + expected = xpx.at(expected)[..., :3, 3].set(t) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix(), expected, atol=atol) assert tf.single # Test single rotation and multiple translations - t = np.array([[1, 2, 3], [4, 5, 6]]) - r = Rotation.from_euler('z', 90, degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_components(t, r) assert not tf.single - for i in range(len(t)): - expected = np.zeros((4, 4)) - expected[:3, :3] = r.as_matrix() - expected[:3, 3] = t[i] - expected[3, 3] = 1 - assert_allclose(tf.as_matrix()[i], expected, atol=atol) + for i in range(t.shape[0]): + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol) # Test multiple rotations and translations - t = np.array([[1, 2, 3], [4, 5, 6]]) - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) tf = RigidTransform.from_components(t, r) assert not tf.single - for i in range(len(t)): - expected = np.zeros((4, 4)) - expected[:3, :3] = r[i].as_matrix() - expected[:3, 3] = t[i] - expected[3, 3] = 1 - assert_allclose(tf.as_matrix()[i], expected, atol=atol) + for i in range(t.shape[0]): + expected = xp.zeros((4, 4)) + expected = xpx.at(expected)[..., :3, :3].set(r.as_matrix()[i, ...]) + expected = xpx.at(expected)[..., :3, 3].set(t[i, ...]) + expected = xpx.at(expected)[..., 3, 3].set(1) + xp_assert_close(tf.as_matrix()[i, ...], expected, atol=atol) -def test_as_components(): +def test_from_components_array_like(): + rng = np.random.default_rng(123) + # Test single rotation and translation + t = [1, 2, 3] + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert tf.single + + # Test multiple rotations and translations + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + tf_expected = RigidTransform.from_components(np.array(t), r) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + assert not tf.single + + + +def test_as_components(xp): atol = 1e-12 n = 10 rng = np.random.default_rng(123) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf = RigidTransform.from_components(t, r) new_t, new_r = tf.as_components() assert all(new_r.approx_equal(r, atol=atol)) - assert_allclose(new_t, t, atol=atol) + xp_assert_close(new_t, t, atol=atol) -def test_from_exp_coords(): +def test_from_exp_coords(xp): # example from 3.3 of # https://hades.mech.northwestern.edu/images/2/25/MR-v2.pdf angle1 = np.deg2rad(30.0) - tf1 = RigidTransform.from_matrix([ + mat = xp.asarray([ [np.cos(angle1), -np.sin(angle1), 0.0, 1.0], [np.sin(angle1), np.cos(angle1), 0.0, 2.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0] ]) + tf1 = RigidTransform.from_matrix(mat) angle2 = np.deg2rad(60.0) - tf2 = RigidTransform.from_matrix([ + mat = xp.asarray([ [np.cos(angle2), -np.sin(angle2), 0.0, 2.0], [np.sin(angle2), np.cos(angle2), 0.0, 1.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0] ]) + tf2 = RigidTransform.from_matrix(mat) expected = tf2 * tf1.inv() actual = RigidTransform.from_exp_coords( - np.deg2rad(30.0) * np.array([0.0, 0.0, 1.0, 3.37, -3.37, 0.0])) - assert_allclose(actual.as_matrix(), expected.as_matrix(), atol=1e-2) + np.deg2rad(30.0) * xp.asarray([0.0, 0.0, 1.0, 3.37, -3.37, 0.0])) + xp_assert_close(actual.as_matrix(), expected.as_matrix(), atol=1e-2) # test cases generated by comparison to pytransform3d - exp_coords = [ + exp_coords = xp.asarray([ [-2.01041204, -0.52983629, 0.65773501, 0.10386614, 0.05855009, 0.54959179], [-0.22537438, -0.24132627, -2.4747121, -0.09158594, 1.88075832, -0.03197204] - ] - expected_matrix = [ + ]) + expected_matrix = xp.asarray([ [[0.76406621, 0.10504613, -0.63652819, -0.10209961], [0.59956454, -0.47987325, 0.64050295, 0.40158789], [-0.2381705, -0.87102639, -0.42963687, 0.19637636], @@ -207,18 +289,18 @@ def test_from_exp_coords(): [-0.58017785, -0.78232107, 0.22664378, 0.52660831], [0.21909052, 0.11810973, 0.96852952, -0.02968529], [0., 0., 0., 1.]] - ] - assert_allclose( + ]) + xp_assert_close( RigidTransform.from_exp_coords(exp_coords).as_matrix(), expected_matrix, atol=1e-8) # identity - assert_allclose( - RigidTransform.from_exp_coords(np.zeros(6)).as_matrix(), - np.eye(4), atol=1e-12) + xp_assert_close( + RigidTransform.from_exp_coords(xp.zeros(6)).as_matrix(), + xp.eye(4), atol=1e-12) # only translation - expected_matrix = np.array([ + expected_matrix = xp.asarray([ [[1.0, 0.0, 0.0, 3.0], [0.0, 1.0, 0.0, -5.4], [0.0, 0.0, 1.0, 100.2], @@ -228,103 +310,130 @@ def test_from_exp_coords(): [0.0, 0.0, 1.0, 1.3], [0.0, 0.0, 0.0, 1.0]] ]) - actual = RigidTransform.from_exp_coords([ + actual = RigidTransform.from_exp_coords(xp.asarray([ [0.0, 0.0, 0.0, 3.0, -5.4, 100.2], [0.0, 0.0, 0.0, -3.0, 13.3, 1.3], - ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + ])) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # only rotation rot = Rotation.from_euler( 'zyx', - [[34, -12, 0.5], - [-102, -55, 30]], + xp.asarray([[34, -12, 0.5], + [-102, -55, 30]]), degrees=True) rotvec = rot.as_rotvec() - expected_matrix = np.array([np.eye(4), np.eye(4)]) - expected_matrix[:, :3, :3] = rot.as_matrix() + expected_matrix = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(rot.as_matrix()) actual = RigidTransform.from_exp_coords( - np.hstack((rotvec, np.zeros((2, 3))))) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp.concat((rotvec, xp.zeros((2, 3))), axis=-1)) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) + + +def test_from_exp_coords_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + exp_coords = tf_expected.as_exp_coords().tolist() + assert isinstance(exp_coords, list) + tf = RigidTransform.from_exp_coords(exp_coords) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) -def test_as_exp_coords(): +def test_as_exp_coords(xp): # identity - expected = np.zeros(6) + expected = xp.zeros(6) actual = RigidTransform.from_exp_coords(expected).as_exp_coords() - assert_allclose(actual, expected, atol=1e-12) + xp_assert_close(actual, expected, atol=1e-12) rng = np.random.default_rng(10) # pure rotation - rot_vec = rng.normal(scale=0.1, size=(1000, 3)) + rot_vec = xp.asarray(rng.normal(scale=0.1, size=(1000, 3))) tf = RigidTransform.from_rotation(Rotation.from_rotvec(rot_vec)) exp_coords = tf.as_exp_coords() - assert_allclose(exp_coords[:, :3], rot_vec, rtol=1e-13) - assert_allclose(exp_coords[:, 3:], 0.0, atol=1e-16) + xp_assert_close(exp_coords[:, :3], rot_vec, rtol=1e-13) + expected = xp.zeros_like(rot_vec) + xp_assert_close(exp_coords[:, 3:], expected, atol=1e-16) # pure translation - translation = rng.normal(scale=100.0, size=(1000, 3)) + translation = xp.asarray(rng.normal(scale=100.0, size=(1000, 3))) tf = RigidTransform.from_translation(translation) exp_coords = tf.as_exp_coords() - assert_allclose(exp_coords[:, :3], 0.0, atol=1e-16) - assert_allclose(exp_coords[:, 3:], translation, rtol=1e-15) + xp_assert_close(exp_coords[:, :3], expected, atol=1e-16) + xp_assert_close(exp_coords[:, 3:], translation, rtol=1e-15) -def test_from_dual_quat(): +def test_from_dual_quat(xp): # identity - assert_allclose( + xp_assert_close( RigidTransform.from_dual_quat( - np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])).as_matrix(), - np.eye(4), atol=1e-12) - assert_allclose( + xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0])).as_matrix(), + xp.eye(4), atol=1e-12) + xp_assert_close( RigidTransform.from_dual_quat( - np.array([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), + xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]), scalar_first=True).as_matrix(), - np.eye(4), atol=1e-12) + xp.eye(4), atol=1e-12) # only translation actual = RigidTransform.from_dual_quat( - [0, 0, 0, 1, 0.25, 0.15, -0.7, 0]) - expected_matrix = np.array([ + xp.asarray([0, 0, 0, 1, 0.25, 0.15, -0.7, 0])) + expected_matrix = xp.asarray([ [1, 0, 0, 0.5], [0, 1, 0, 0.3], [0, 0, 1, -1.4], [0, 0, 0, 1] ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) actual = RigidTransform.from_dual_quat( - [1, 0, 0, 0, 0, 0.25, 0.15, -0.7], scalar_first=True) - expected_matrix = np.array([ + xp.asarray([1, 0, 0, 0, 0, 0.25, 0.15, -0.7]), scalar_first=True) + expected_matrix = xp.asarray([ [1, 0, 0, 0.5], [0, 1, 0, 0.3], [0, 0, 1, -1.4], [0, 0, 0, 1] ]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # only rotation - actual_rot = Rotation.from_euler("xyz", [65, -13, 90], degrees=True) + actual_rot = Rotation.from_euler("xyz", xp.asarray([65, -13, 90]), degrees=True) actual = RigidTransform.from_dual_quat( - np.hstack((actual_rot.as_quat(), np.zeros(4)))) - expected_matrix = np.eye(4) - expected_matrix[:3, :3] = actual_rot.as_matrix() - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp.concat((actual_rot.as_quat(), xp.zeros(4)), axis=-1)) + expected_matrix = xp.eye(4) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) actual = RigidTransform.from_dual_quat( - np.hstack((actual_rot.as_quat(scalar_first=True), np.zeros(4))), + xp.concat((actual_rot.as_quat(scalar_first=True), xp.zeros(4)), axis=-1), scalar_first=True) - expected_matrix = np.eye(4) - expected_matrix[:3, :3] = actual_rot.as_matrix() - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + expected_matrix = xp.eye(4) + expected_matrix = xpx.at(expected_matrix)[..., :3, :3].set(actual_rot.as_matrix()) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12) # rotation and translation + # rtol is set to 1e-7 because xp_assert_close deviates from + # np.testing.assert_allclose in that it does not automatically default to 1e-7 for + # floating point inputs. + # See https://numpy.org/doc/2.2/reference/generated/numpy.testing.assert_allclose.html actual = RigidTransform.from_dual_quat( + xp.asarray( [[0.0617101, -0.06483886, 0.31432811, 0.94508498, 0.04985168, -0.26119618, 0.1691491, -0.07743254], [0.19507259, 0.49404931, -0.06091285, 0.8450749, - 0.65049656, -0.30782513, 0.16566752, 0.04174544]]) - expected_matrix = np.array( + 0.65049656, -0.30782513, 0.16566752, 0.04174544]])) + expected_matrix = xp.asarray( [[[0.79398752, -0.60213598, -0.08376202, 0.24605262], [0.58613113, 0.79477941, -0.15740392, -0.4932833], [0.16135089, 0.07588122, 0.98397557, 0.34262676], @@ -333,276 +442,319 @@ def test_from_dual_quat(): [0.08979911, 0.91647262, -0.3898898, -0.70540077], [-0.8587822, 0.26951399, 0.43572393, -0.47776265], [0., 0., 0., 1.]]]) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7) actual = RigidTransform.from_dual_quat( + xp.asarray( [[0.94508498, 0.0617101, -0.06483886, 0.31432811, -0.07743254, 0.04985168, -0.26119618, 0.1691491], [0.8450749, 0.19507259, 0.49404931, -0.06091285, - 0.04174544, 0.65049656, -0.30782513, 0.16566752]], + 0.04174544, 0.65049656, -0.30782513, 0.16566752]]), scalar_first=True) - assert_allclose(actual.as_matrix(), expected_matrix, atol=1e-12) + xp_assert_close(actual.as_matrix(), expected_matrix, atol=1e-12, rtol=1e-7) # unnormalized dual quaternions # invalid real quaternion with norm 0 - actual = RigidTransform.from_dual_quat(np.zeros(8)) - assert_allclose(actual.as_matrix(), np.eye(4), atol=1e-12) + actual = RigidTransform.from_dual_quat(xp.zeros(8)) + xp_assert_close(actual.as_matrix(), xp.eye(4), atol=1e-12) # real quaternion with norm != 1 - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [-0.2547655, 1.23506123, 0.20230088, 0.24247194, # norm 1.3 0.38559628, 0.08184063, 0.1755943, -0.1582222] # orthogonal ) - assert pytest.approx(np.linalg.norm(unnormalized_dual_quat[:4])) == 1.3 - assert pytest.approx(np.dot(unnormalized_dual_quat[:4], - unnormalized_dual_quat[4:]), abs=8) == 0.0 + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.3)[()], + atol=1e-12) + xp_assert_close(xp.vecdot(unnormalized_dual_quat[:4], + unnormalized_dual_quat[4:])[()], + xp.asarray(0.0)[()], atol=1e-8) + dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert pytest.approx(np.linalg.norm(dual_quat[:4])) == 1.0 - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # real and dual quaternion are not orthogonal - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [0.20824458, 0.75098079, 0.54542913, -0.30849493, # unit norm -0.16051025, 0.10742978, 0.21277201, 0.20596935] # not orthogonal ) - assert pytest.approx(np.linalg.norm(unnormalized_dual_quat[:4])) == 1.0 - assert np.dot(unnormalized_dual_quat[:4], - unnormalized_dual_quat[4:]) != 0.0 + xp_assert_close(xp_vector_norm(unnormalized_dual_quat[:4]), xp.asarray(1.0)[()], + atol=1e-12) + assert xp.vecdot(unnormalized_dual_quat[:4], unnormalized_dual_quat[4:]) != 0.0 dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert pytest.approx(np.linalg.norm(dual_quat[:4])) == 1.0 - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(xp_vector_norm(dual_quat[:4]), xp.asarray(1.0)[()], atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # invalid real quaternion with norm 0, non-orthogonal dual quaternion - unnormalized_dual_quat = np.array( + unnormalized_dual_quat = xp.asarray( [0.0, 0.0, 0.0, 0.0, -0.16051025, 0.10742978, 0.21277201, 0.20596935]) - assert np.dot(np.array([0.0, 0.0, 0.0, 1.0]), - unnormalized_dual_quat[4:]) != 0.0 + assert xp.vecdot(xp.asarray([0.0, 0, 0, 1]), unnormalized_dual_quat[4:]) != 0.0 dual_quat = RigidTransform.from_dual_quat( unnormalized_dual_quat).as_dual_quat() - assert_allclose(dual_quat[:4], np.array([0, 0, 0, 1]), atol=1e-12) - assert pytest.approx(np.dot(dual_quat[:4], dual_quat[4:])) == 0.0 + xp_assert_close(dual_quat[:4], xp.asarray([0.0, 0, 0, 1]), atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[:4], dual_quat[4:])[()], xp.asarray(0.0)[()], + atol=1e-12) # compensation for precision loss in real quaternion rng = np.random.default_rng(1000) - t = rng.normal(size=(3,)) - r = Rotation.random(10, rng=rng) + t = xp.asarray(rng.normal(size=(3,))) + r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp) random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() # ensure that random quaternions are not normalized - random_dual_quats[:, :4] = random_dual_quats[:, :4].round(2) - assert not np.any(np.isclose( - np.linalg.norm(random_dual_quats[:, :4], axis=1), 1.0, atol=0.0001)) + random_dual_quats = xpx.at(random_dual_quats)[:, :4].add(0.01) + assert not xp.any(xpx.isclose(xp_vector_norm(random_dual_quats[:, :4], axis=1), + 1.0, atol=0.0001)) dual_quat_norm = RigidTransform.from_dual_quat( random_dual_quats).as_dual_quat() - assert_allclose( - np.linalg.norm(dual_quat_norm[:, :4], axis=1), 1.0, atol=1e-12) + expected = xp.ones(dual_quat_norm.shape[0]) + xp_assert_close(xp_vector_norm(dual_quat_norm[:, :4], axis=1), expected, atol=1e-12) # compensation for precision loss in dual quaternion, results in violation # of orthogonality constraint - t = rng.normal(size=(10, 3)) - r = Rotation.random(10, rng=rng) + t = xp.asarray(rng.normal(size=(10, 3))) + r = rotation_to_xp(Rotation.random(10, rng=rng), xp=xp) random_dual_quats = RigidTransform.from_components(t, r).as_dual_quat() # ensure that random quaternions are not normalized - random_dual_quats[:, 4:] = random_dual_quats[:, 4:].round(2) - assert not np.any(np.isclose( - np.einsum("ij,ij->i", - random_dual_quats[:, :4], - random_dual_quats[:, 4:]), - 0.0, atol=0.0001)) + random_dual_quats = xpx.at(random_dual_quats)[:, 4:].add(0.01) + q_norm = xp.vecdot(random_dual_quats[:, :4], random_dual_quats[:, 4:]) + assert not xp.any(xpx.isclose(q_norm, 0.0, atol=0.0001)) dual_quat_norm = RigidTransform.from_dual_quat( random_dual_quats).as_dual_quat() - assert_allclose( - np.einsum("ij,ij->i", dual_quat_norm[:, :4], dual_quat_norm[:, 4:]), - 0.0, atol=1e-12) - assert_allclose( - random_dual_quats[:, :4], dual_quat_norm[:, :4], atol=1e-12) + expected = xp.zeros(dual_quat_norm.shape[0]) + xp_assert_close(xp.vecdot(dual_quat_norm[:, :4], dual_quat_norm[:, 4:]), expected, + atol=1e-12) + xp_assert_close(random_dual_quats[:, :4], dual_quat_norm[:, :4], atol=1e-12) + + +def test_from_dual_quat_array_like(): + rng = np.random.default_rng(123) + # Test single transform + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) + + # Test multiple transforms + t = [[1, 2, 3], [4, 5, 6]] + r = Rotation.random(len(t), rng=rng) + tf_expected = RigidTransform.from_components(t, r) + dual_quat = tf_expected.as_dual_quat().tolist() + assert isinstance(dual_quat, list) + tf = RigidTransform.from_dual_quat(dual_quat) + xp_assert_close(tf.as_matrix(), tf_expected.as_matrix(), atol=1e-12) -def test_as_dual_quat(): +def test_as_dual_quat(xp): # identity - expected = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) - actual = RigidTransform.identity().as_dual_quat() - assert_allclose(actual, expected, atol=1e-12) + expected = xp.asarray([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0]) + actual = xp.asarray(RigidTransform.identity().as_dual_quat()) + xp_assert_close(actual, expected, atol=1e-12) - expected = np.array([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) - actual = RigidTransform.identity().as_dual_quat(scalar_first=True) - assert_allclose(actual, expected, atol=1e-12) + expected = xp.asarray([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) + actual = xp.asarray(RigidTransform.identity().as_dual_quat(scalar_first=True)) + xp_assert_close(actual, expected, atol=1e-12) rng = np.random.default_rng(10) # only rotation for _ in range(10): - real_part = Rotation.random(rng=rng).as_quat() - dual_part = np.zeros(4) - expected = np.hstack((real_part, dual_part)) + real_part = xp.asarray(Rotation.random(rng=rng).as_quat()) + dual_part = xp.zeros(4) + expected = xp.concat((real_part, dual_part), axis=-1) actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) # only translation for _ in range(10): - tf = rng.normal(size=3) - expected = np.hstack(([0, 0, 0, 1], 0.5 * tf, [0])) + tf = 0.5 * rng.normal(size=3) + expected = xp.asarray([0.0, 0, 0, 1, *tf.tolist(), 0]) actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) # rotation and translation for _ in range(10): - t = rng.normal(size=3) - r = Rotation.random(rng=rng) + t = xp.asarray(rng.normal(size=3)) + r = rotation_to_xp(Rotation.random(rng=rng), xp=xp) expected = RigidTransform.from_components(t, r).as_dual_quat() actual = RigidTransform.from_dual_quat(expected).as_dual_quat() # because of double cover: - if np.sign(expected[0]) != np.sign(actual[0]): - actual *= -1.0 - assert_allclose(actual, expected, atol=1e-12) + if xp.sign(expected[0]) != xp.sign(actual[0]): + actual = -actual + xp_assert_close(actual, expected, atol=1e-12) -def test_from_as_internal_consistency(): +def test_from_as_internal_consistency(xp): atol = 1e-12 n = 1000 rng = np.random.default_rng(10) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf0 = RigidTransform.from_components(t, r) tf1 = RigidTransform.from_components(*tf0.as_components()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_components(tf0.translation, tf0.rotation) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_matrix(tf0.as_matrix()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) tf1 = RigidTransform.from_dual_quat(tf0.as_dual_quat()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) # exp_coords small rotation tf0 = RigidTransform.from_components( - rng.normal(scale=1000.0, size=(1000, 3)), - Rotation.from_rotvec(rng.normal(scale=1e-10, size=(1000, 3)))) + xp.asarray(rng.normal(scale=1000.0, size=(1000, 3))), + Rotation.from_rotvec(xp.asarray(rng.normal(scale=1e-10, size=(1000, 3))))) tf1 = RigidTransform.from_exp_coords(tf0.as_exp_coords()) - assert_allclose(tf0.as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(tf0.as_matrix(), tf1.as_matrix(), atol=atol) def test_identity(): + # We do not use xp here because identity always returns numpy arrays atol = 1e-12 # Test single identity tf = RigidTransform.identity() - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(tf.as_matrix(), np.eye(4), atol=atol) # Test multiple identities tf = RigidTransform.identity(5) - assert_allclose(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol) + xp_assert_close(tf.as_matrix(), np.array([np.eye(4)] * 5), atol=atol) -def test_apply(): +def test_apply(xp): atol = 1e-12 ## Single transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([2, 3, 4]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([2, 3, 4]) tf = RigidTransform.from_components(t, r) # Single vector, single transform - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = t + r.apply(vec) res = tf.apply(vec) - assert_allclose(res, expected, atol=atol) + xp_assert_close(res, expected, atol=atol) # Multiple vectors, single transform - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = t + r.apply(vecs) - assert_allclose(tf.apply(vecs), expected, atol=atol) + xp_assert_close(tf.apply(vecs), expected, atol=atol) ## Multiple transforms - r = Rotation.from_euler('z', [90, 0], degrees=True) - t = np.array([[2, 3, 4], [5, 6, 7]]) + r = Rotation.from_euler('z', xp.asarray([90, 0]), degrees=True) + t = xp.asarray([[2, 3, 4], [5, 6, 7]]) tf = RigidTransform.from_components(t, r) # Single vector, multiple transforms - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = t + r.apply(vec) - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) # Multiple vectors, multiple transforms - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = t + r.apply(vecs) - assert_allclose(tf.apply(vecs), expected, atol=atol) + xp_assert_close(tf.apply(vecs), expected, atol=atol) + + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + t = np.array([1, 2, 3]) + r = Rotation.random(rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [1, 0, 0] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) + # Multiple vectors + t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.random(len(t), rng=rng) + tf = RigidTransform.from_components(t, r) + vec = [[1, 0, 0], [0, 1, 0]] + expected = t + r.apply(vec) + xp_assert_close(tf.apply(vec), expected, atol=1e-12) -def test_inverse_apply(): + +def test_inverse_apply(xp): atol = 1e-12 # Test applying inverse transform - t = np.array([1, 2, 3]) - r = Rotation.from_euler('z', 90, degrees=True) + t = xp.asarray([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_components(t, r) # Test single vector - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = tf.inv().apply(vec) - assert_allclose(tf.apply(vec, inverse=True), expected, atol=atol) + xp_assert_close(tf.apply(vec, inverse=True), expected, atol=atol) # Test multiple vectors - vecs = np.array([[1, 0, 0], [0, 1, 0]]) + vecs = xp.asarray([[1, 0, 0], [0, 1, 0]]) expected = tf.inv().apply(vecs) - assert_allclose(tf.apply(vecs, inverse=True), expected, atol=atol) + xp_assert_close(tf.apply(vecs, inverse=True), expected, atol=atol) -def test_rotation_alone(): +def test_rotation_alone(xp): atol = 1e-12 - r = Rotation.from_euler('z', 90, degrees=True) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf = RigidTransform.from_rotation(r) - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = r.apply(vec) - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) -def test_translation_alone(): +def test_translation_alone(xp): atol = 1e-12 - t = np.array([1, 2, 3]) + t = xp.asarray([1.0, 2, 3]) tf = RigidTransform.from_translation(t) - vec = np.array([5, 6, 7]) + vec = xp.asarray([5.0, 6, 7]) expected = t + vec - assert_allclose(tf.apply(vec), expected, atol=atol) + xp_assert_close(tf.apply(vec), expected, atol=atol) -def test_composition(): +def test_composition(xp): atol = 1e-12 # Test composing single transforms - t1 = np.array([1, 0, 0]) - r1 = Rotation.from_euler('z', 90, degrees=True) + t1 = xp.asarray([1.0, 0, 0]) + r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf1 = RigidTransform.from_components(t1, r1) - t2 = np.array([0, 1, 0]) - r2 = Rotation.from_euler('x', 90, degrees=True) + t2 = xp.asarray([0, 1, 0]) + r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True) tf2 = RigidTransform.from_components(t2, r2) composed = tf2 * tf1 - vec = np.array([1, 0, 0]) + vec = xp.asarray([1, 0, 0]) expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) # Multiple transforms with single transform t2 = np.array([[1, 2, 3], [4, 5, 6]]) @@ -610,243 +762,275 @@ def test_composition(): composed = tf2 * tf1 expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert not composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) # Multiple transforms with multiple transforms - t1 = np.array([[1, 0, 0], [0, -1, 1]]) + t1 = xp.asarray([[1, 0, 0], [0, -1, 1]]) tf1 = RigidTransform.from_components(t1, r1) composed = tf2 * tf1 expected = tf2.apply(tf1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) assert not composed.single expected = t2 + r2.apply(t1 + r1.apply(vec)) - assert_allclose(composed.apply(vec), expected, atol=atol) + xp_assert_close(composed.apply(vec), expected, atol=atol) -def test_pow(): +def test_pow(xp): atol = 1e-12 num = 10 rng = np.random.default_rng(100) - t = rng.normal(size=(num, 3)) - r = Rotation.random(num, rng=rng) + t = xp.asarray(rng.normal(size=(num, 3))) + r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp) p = RigidTransform.from_components(t, r) p_inv = p.inv() # Test the short-cuts and other integers for n in [-5, -2, -1, 0, 1, 2, 5]: q = p**n - r = RigidTransform.identity(num) + r = rigid_transform_to_xp(RigidTransform.identity(num), xp=xp) for _ in range(abs(n)): if n > 0: r = r * p else: r = r * p_inv - assert_allclose(q.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(q.as_matrix(), r.as_matrix(), atol=atol) # Test shape preservation - r = RigidTransform.from_rotation(Rotation.from_quat([0, 0, 0, 1])) + r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([0, 0, 0, 1]))) assert (r**n).as_matrix().shape == (4, 4) - r = RigidTransform.from_rotation(Rotation.from_quat([[0, 0, 0, 1]])) + r = RigidTransform.from_rotation(Rotation.from_quat(xp.asarray([[0, 0, 0, 1]]))) assert (r**n).as_matrix().shape == (1, 4, 4) # Test fractional powers q = p**0.5 - assert_allclose((q * q).as_matrix(), p.as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), p.as_matrix(), atol=atol) q = p**-0.5 - assert_allclose((q * q).as_matrix(), p.inv().as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), p.inv().as_matrix(), atol=atol) q = p** 1.5 - assert_allclose((q * q).as_matrix(), (p**3).as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), (p**3).as_matrix(), atol=atol) q = p** -1.5 - assert_allclose((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol) + xp_assert_close((q * q).as_matrix(), (p**-3).as_matrix(), atol=atol) # pow function - tf = pow(RigidTransform.identity(), 2) - assert_allclose(tf.as_matrix(), np.eye(4), atol=atol) + tf = pow(RigidTransform.from_matrix(xp.eye(4)), 2) + xp_assert_close(tf.as_matrix(), xp.eye(4), atol=atol) -def test_pow_equivalence_with_rotation(): +def test_pow_equivalence_with_rotation(xp): atol = 1e-12 num = 10 rng = np.random.default_rng(100) - r = Rotation.random(num, rng=rng) + r = rotation_to_xp(Rotation.random(num, rng=rng), xp=xp) p = RigidTransform.from_rotation(r) for n in [-5, -2, -1.5, -1, -0.5, 0.0, 0.5, 1, 1.5, 2, 5]: - assert_allclose((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol) + xp_assert_close((p**n).rotation.as_matrix(), (r**n).as_matrix(), atol=atol) -def test_inverse(): +def test_inverse(xp): atol = 1e-12 # Test inverse transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([1, 2, 3]) tf = RigidTransform.from_components(t, r) # Test that tf * tf.inv() equals identity tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.eye(4), atol=atol) + xp_assert_close(composed.as_matrix(), xp.eye(4), atol=atol) n = 10 rng = np.random.default_rng(1000) - t = rng.normal(size=(n, 3)) - r = Rotation.random(n, rng=rng) + t = xp.asarray(rng.normal(size=(n, 3))) + r = rotation_to_xp(Rotation.random(n, rng=rng), xp=xp) tf = RigidTransform.from_components(t, r) tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.array([np.eye(4)] * n), atol=atol) + expected = xp.repeat(xp.eye(4)[None, ...], n, axis=0) + xp_assert_close(composed.as_matrix(), expected, atol=atol) # Test multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) tf_inv = tf.inv() composed = tf * tf_inv - assert_allclose(composed.as_matrix(), np.array([np.eye(4)] * 2), atol=atol) + expected = xp.repeat(xp.eye(4)[None, ...], 2, axis=0) + xp_assert_close(composed.as_matrix(), expected, atol=atol) -def test_properties(): +def test_properties(xp): atol = 1e-12 # Test rotation and translation properties for single transform - r = Rotation.from_euler('z', 90, degrees=True) - t = np.array([1, 2, 3]) + r = Rotation.from_euler('z', xp.asarray(90), degrees=True) + t = xp.asarray([1.0, 2, 3]) tf = RigidTransform.from_components(t, r) - assert_allclose(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) assert tf.rotation.approx_equal(r) - assert_allclose(tf.translation, t, atol=atol) + xp_assert_close(tf.translation, t, atol=atol) # Test rotation and translation properties for multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1.0, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) - assert_allclose(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) + xp_assert_close(tf.rotation.as_matrix(), r.as_matrix(), atol=atol) assert all(tf.rotation.approx_equal(r)) - assert_allclose(tf.translation, t, atol=atol) + xp_assert_close(tf.translation, t, atol=atol) -def test_indexing(): +def test_indexing(xp): atol = 1e-12 # Test indexing for multiple transforms - r = Rotation.from_euler('zyx', [[90, 0, 0], [0, 90, 0]], degrees=True) - t = np.array([[1, 2, 3], [4, 5, 6]]) + r = Rotation.from_euler('zyx', xp.asarray([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = xp.asarray([[1.0, 2, 3], [4, 5, 6]]) tf = RigidTransform.from_components(t, r) # Test single index - assert_allclose(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol) - assert_allclose(tf[0].as_matrix()[:3, 3], t[0], atol=atol) + xp_assert_close(tf[0].as_matrix()[:3, :3], r[0].as_matrix(), atol=atol) + xp_assert_close(tf[0].as_matrix()[:3, 3], t[0, ...], atol=atol) # Test slice tf_slice = tf[0:2] - assert_allclose(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol) - assert_allclose(tf_slice.as_matrix()[:, :3, 3], t[0:2], atol=atol) + xp_assert_close(tf_slice.as_matrix()[:, :3, :3], r[0:2].as_matrix(), atol=atol) + xp_assert_close(tf_slice.as_matrix()[:, :3, 3], t[0:2, ...], atol=atol) # Test boolean indexing - tf_masked = tf[[True, True]] - assert_allclose(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) - assert_allclose(tf_masked.as_matrix()[:, :3, 3], t, atol=atol) + tf_masked = tf[xp.asarray([True, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r.as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t, atol=atol) + + tf_masked = tf[xp.asarray([False, True])] + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], + r[xp.asarray([False, True])].as_matrix(), atol=atol) + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[xp.asarray([False, True])], + atol=atol) + + tf_masked = tf[xp.asarray([False, False])] + assert len(tf_masked) == 0 + + +def test_indexing_array_like(): + atol = 1e-12 + + r = Rotation.from_euler('zyx', np.array([[90, 0, 0], [0, 90, 0]]), degrees=True) + t = np.array([[1.0, 2, 3], [4, 5, 6]]) + tf = RigidTransform.from_components(t, r) tf_masked = tf[[False, True]] - assert_allclose(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(), + xp_assert_close(tf_masked.as_matrix()[:, :3, :3], r[[False, True]].as_matrix(), atol=atol) - assert_allclose(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol) - + xp_assert_close(tf_masked.as_matrix()[:, :3, 3], t[[False, True]], atol=atol) tf_masked = tf[[False, False]] assert len(tf_masked) == 0 -def test_concatenate(): +def test_concatenate(xp): atol = 1e-12 # Test concatenation of transforms - t1 = np.array([1, 0, 0]) - r1 = Rotation.from_euler('z', 90, degrees=True) + t1 = xp.asarray([1, 0, 0]) + r1 = Rotation.from_euler('z', xp.asarray(90), degrees=True) tf1 = RigidTransform.from_components(t1, r1) - t2 = np.array([0, 1, 0]) - r2 = Rotation.from_euler('x', 90, degrees=True) + t2 = xp.asarray([0, 1, 0]) + r2 = Rotation.from_euler('x', xp.asarray(90), degrees=True) tf2 = RigidTransform.from_components(t2, r2) # Concatenate single transforms concatenated1 = RigidTransform.concatenate([tf1, tf2]) - assert_allclose(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol) + xp_assert_close(concatenated1[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated1[1].as_matrix(), tf2.as_matrix(), atol=atol) # Concatenate multiple transforms concatenated2 = RigidTransform.concatenate([tf1, concatenated1]) - assert_allclose(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol) - assert_allclose(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol) + xp_assert_close(concatenated2[0].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[1].as_matrix(), tf1.as_matrix(), atol=atol) + xp_assert_close(concatenated2[2].as_matrix(), tf2.as_matrix(), atol=atol) -def test_input_validation(): +def test_input_validation(xp): # Test invalid matrix shapes - inputs = [np.eye(3), np.zeros((4, 3)), [], np.zeros((1, 1, 4, 4))] + inputs = [xp.eye(3), xp.zeros((4, 3)), [], xp.zeros((1, 1, 4, 4))] for input in inputs: with pytest.raises(ValueError, match="Expected `matrix` to have shape"): RigidTransform.from_matrix(input) # Test invalid last row - with pytest.raises(ValueError, match="last row of transformation matrix 0"): - matrix = np.eye(4) - matrix[3, :] = [1, 0, 0, 1] - RigidTransform.from_matrix(matrix) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[3, :].set(xp.asarray([1, 0, 0, 1])) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix)) + else: + with pytest.raises(ValueError, match="last row of transformation matrix 0"): + RigidTransform.from_matrix(matrix) # Test invalid last row for multiple transforms - with pytest.raises(ValueError, match="last row of transformation matrix 1"): - matrix = np.array([np.eye(4)] * 2) - matrix[1, 3, :] = [1, 0, 0, 1] - RigidTransform.from_matrix(matrix) + matrix = xp.zeros((2, 4, 4)) + matrix = xpx.at(matrix)[...].set(xp.eye(4)) + matrix = xpx.at(matrix)[1, 3, :].set(xp.asarray([1, 0, 0, 1])) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert not xp.any(xp.isnan(matrix[0, ...])) + assert xp.all(xp.isnan(matrix[1, ...])) + else: + with pytest.raises(ValueError, match="last row of transformation matrix 1"): + RigidTransform.from_matrix(matrix) # Test left handed rotation matrix - with pytest.raises(ValueError, match="Non-positive determinant"): - matrix = np.eye(4) - matrix[0, 0] = -1 - RigidTransform(matrix, normalize=True) + matrix = xp.eye(4) + matrix = xpx.at(matrix)[0, 0].set(-1) + if is_lazy_array(matrix): + matrix = RigidTransform.from_matrix(matrix).as_matrix() + assert xp.all(xp.isnan(matrix[..., :3, :3])) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + RigidTransform(matrix, normalize=True) # Test non-Rotation input with pytest.raises(ValueError, match="Expected `rotation` to be a `Rotation` instance"): - RigidTransform.from_rotation(np.eye(3)) + RigidTransform.from_rotation(xp.eye(3)) -def test_translation_validation(): +def test_translation_validation(xp): # Test invalid translation shapes with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation([1, 2]) + RigidTransform.from_translation(xp.asarray([1, 2])) with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation(np.zeros((2, 2))) + RigidTransform.from_translation(xp.zeros((2, 2))) with pytest.raises(ValueError, match="Expected `translation` to have shape"): - RigidTransform.from_translation(np.zeros((1, 1, 3))) + RigidTransform.from_translation(xp.zeros((1, 1, 3))) -def test_vector_validation(): - tf = RigidTransform.identity(2) +def test_vector_validation(xp): + tf = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) # Test invalid vector shapes with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply([1, 2]) + tf.apply(xp.asarray([1, 2])) with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply(np.zeros((2, 2))) + tf.apply(xp.zeros((2, 2))) with pytest.raises(ValueError, match="Expected vector to have shape"): - tf.apply(np.zeros((1, 1, 3))) + tf.apply(xp.zeros((1, 1, 3))) -def test_indexing_validation(): - tf = RigidTransform.identity() +def test_indexing_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) # Test indexing on single transform with pytest.raises(TypeError, match="Single transform is not subscriptable"): @@ -860,27 +1044,27 @@ def test_indexing_validation(): len(tf) -def test_composition_validation(): - tf2 = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6]]) - tf3 = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) +def test_composition_validation(xp): + tf2 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + tf3 = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6], [7, 8, 9]])) # Test incompatible shapes with pytest.raises(ValueError, match="Expected equal number of transforms"): tf2 * tf3 -def test_concatenate_validation(): - tf = RigidTransform.identity() +def test_concatenate_validation(xp): + tf = RigidTransform.from_matrix(xp.eye(4)) # Test invalid inputs with pytest.raises(TypeError, match="input must contain RigidTransform objects"): - RigidTransform.concatenate([tf, np.eye(4)]) + RigidTransform.concatenate([tf, xp.eye(4)]) -def test_setitem_validation(): - tf = RigidTransform.from_translation([[1, 2, 3], [4, 5, 6]]) - single = RigidTransform.identity() +def test_setitem_validation(xp): + tf = RigidTransform.from_translation(xp.asarray([[1, 2, 3], [4, 5, 6]])) + single = RigidTransform.from_matrix(xp.eye(4)) # Test setting item on single transform with pytest.raises(TypeError, match="Single transform is not subscriptable"): @@ -888,58 +1072,64 @@ def test_setitem_validation(): # Test invalid value type with pytest.raises(TypeError, match="value must be a RigidTransform"): - tf[0] = np.eye(4) + tf[0] = xp.eye(4) -def test_copy_flag(): +@pytest.mark.skip_xp_backends("jax.numpy", + reason="JAX does not support memory sharing") +def test_copy_flag(xp): # Test that copy=True creates new memory - matrix = np.eye(4) + matrix = xp.eye(4) tf = RigidTransform(matrix, normalize=False, copy=True) matrix[0, 0] = 2 assert tf.as_matrix()[0, 0] == 1 # Test that copy=False shares memory - matrix = np.eye(4) + matrix = xp.eye(4) tf = RigidTransform(matrix, normalize=False, copy=False) matrix[0, 0] = 2 assert tf.as_matrix()[0, 0] == 2 -def test_normalize_dual_quaternion(): - dual_quat = normalize_dual_quaternion(np.zeros((1, 8))) - assert_allclose(np.linalg.norm(dual_quat[0, :4]), 1.0, atol=1e-12) - assert_allclose(dual_quat[0, :4] @ dual_quat[0, 4:], 0.0, atol=1e-12) +def test_normalize_dual_quaternion(xp): + dual_quat = normalize_dual_quaternion(xp.zeros((1, 8))) + xp_assert_close(xp_vector_norm(dual_quat[0, :4], axis=-1), xp.asarray(1.0)[()], + atol=1e-12) + xp_assert_close(xp.vecdot(dual_quat[0, :4], dual_quat[0, 4:])[()], + xp.asarray(0.0)[()], atol=1e-12) rng = np.random.default_rng(103213650) - dual_quat = rng.normal(size=(1000, 8)) + dual_quat = xp.asarray(rng.normal(size=(1000, 8))) dual_quat = normalize_dual_quaternion(dual_quat) - assert_allclose(np.linalg.norm(dual_quat[:, :4], axis=1), 1.0, atol=1e-12) - assert_allclose(np.einsum("ij,ij->i", dual_quat[:, :4], dual_quat[:, 4:]), - 0.0, atol=1e-12) + expected = xp.ones(dual_quat.shape[0]) + xp_assert_close(xp_vector_norm(dual_quat[:, :4], axis=-1), expected, atol=1e-12) + expected = xp.zeros(dual_quat.shape[0]) + xp_assert_close(xp.vecdot(dual_quat[:, :4], dual_quat[:, 4:]), expected, atol=1e-12) -def test_empty_transform_construction(): - tf = RigidTransform.from_matrix(np.empty((0, 4, 4))) +def test_empty_transform_construction(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf) == 0 assert not tf.single - - tf = RigidTransform.from_rotation(Rotation.random(0)) + + tf = RigidTransform.from_rotation(Rotation.from_quat(xp.zeros((0, 4)))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_translation(np.empty((0, 3))) + tf = RigidTransform.from_translation(xp.empty((0, 3))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_components(np.empty((0, 3)), Rotation.random(0)) + empty_rot = Rotation.from_quat(xp.zeros((0, 4))) + tf = RigidTransform.from_components(xp.empty((0, 3)), empty_rot) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_exp_coords(np.empty((0, 6))) + tf = RigidTransform.from_exp_coords(xp.empty((0, 6))) assert len(tf) == 0 assert not tf.single - tf = RigidTransform.from_dual_quat(np.empty((0, 8))) + tf = RigidTransform.from_dual_quat(xp.empty((0, 8))) assert len(tf) == 0 assert not tf.single @@ -948,8 +1138,8 @@ def test_empty_transform_construction(): assert not tf.single -def test_empty_transform_representation(): - tf = RigidTransform.identity(0) +def test_empty_transform_representation(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf.rotation) == 0 assert tf.translation.shape == (0, 3) @@ -963,20 +1153,20 @@ def test_empty_transform_representation(): assert tf.as_dual_quat().shape == (0, 8) -def test_empty_transform_application(): - tf = RigidTransform.identity(0) +def test_empty_transform_application(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) - assert tf.apply(np.zeros((3,))).shape == (0, 3) - assert tf.apply(np.empty((0, 3))).shape == (0, 3) + assert tf.apply(xp.zeros((3,))).shape == (0, 3) + assert tf.apply(xp.empty((0, 3))).shape == (0, 3) with pytest.raises(ValueError, match="operands could not be broadcast together"): - tf.apply(np.zeros((2, 3))) + tf.apply(xp.zeros((2, 3))) -def test_empty_transform_composition(): - tf_empty = RigidTransform.identity(0) - tf_single = RigidTransform.identity() - tf_many = RigidTransform.identity(3) +def test_empty_transform_composition(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) assert len(tf_empty * tf_empty) == 0 assert len(tf_empty * tf_single) == 0 @@ -989,10 +1179,10 @@ def test_empty_transform_composition(): tf_empty * tf_many -def test_empty_transform_concatenation(): - tf_empty = RigidTransform.identity(0) - tf_single = RigidTransform.identity() - tf_many = RigidTransform.identity(2) +def test_empty_transform_concatenation(xp): + tf_empty = RigidTransform.from_matrix(xp.empty((0, 4, 4))) + tf_single = RigidTransform.from_matrix(xp.eye(4)) + tf_many = rigid_transform_to_xp(RigidTransform.identity(2), xp=xp) assert len(RigidTransform.concatenate([tf_empty, tf_empty])) == 0 assert len(RigidTransform.concatenate([tf_empty, tf_single])) == 1 @@ -1002,8 +1192,8 @@ def test_empty_transform_concatenation(): assert len(RigidTransform.concatenate([tf_many, tf_empty, tf_single])) == 3 -def test_empty_transform_inv_and_pow(): - tf = RigidTransform.identity(0) +def test_empty_transform_inv_and_pow(xp): + tf = RigidTransform.from_matrix(xp.empty((0, 4, 4))) assert len(tf.inv()) == 0 assert len(tf ** 0) == 0 assert len(tf ** 1) == 0 @@ -1011,19 +1201,21 @@ def test_empty_transform_inv_and_pow(): assert len(tf ** 0.5) == 0 -def test_empty_transform_indexing(): - tf_many = RigidTransform.identity(3) - tf_zero = tf_many[[]] +def test_empty_transform_indexing(xp): + tf_many = rigid_transform_to_xp(RigidTransform.identity(3), xp=xp) + tf_zero = tf_many[xp.asarray([], dtype=xp.int64)] assert len(tf_zero) == 0 - assert len(tf_zero[[]]) == 0 - assert len(tf_zero[:5]) == 0 # Slices can go out of bounds. + assert len(tf_zero[xp.asarray([], dtype=xp.int64)]) == 0 + # Array API does not specify out-of-bounds indexing. Only check for numpy. + if is_numpy(xp): + assert len(tf_zero[:5]) == 0 # Slices can go out of bounds. with pytest.raises(IndexError): tf_zero[0] with pytest.raises(IndexError): - tf_zero[[0, 2]] + tf_zero[xp.asarray([0, 2])] with pytest.raises(IndexError): - tf_zero[[False, True]] + tf_zero[xp.asarray([False, True])] diff --git a/scipy/spatial/transform/tests/test_rotation.py b/scipy/spatial/transform/tests/test_rotation.py index 2c91018f7a2b..5dd8356266f5 100644 --- a/scipy/spatial/transform/tests/test_rotation.py +++ b/scipy/spatial/transform/tests/test_rotation.py @@ -1,15 +1,30 @@ +import math + import pytest import numpy as np -from numpy.testing import assert_equal, assert_array_almost_equal -from numpy.testing import assert_allclose +from numpy.testing import assert_equal from scipy.spatial.transform import Rotation, Slerp from scipy.stats import special_ortho_group from itertools import permutations, product +from scipy._lib._array_api import ( + xp_assert_equal, + is_numpy, + is_lazy_array, + xp_vector_norm, + xp_assert_close, + eager_warns, + xp_default_dtype +) +import scipy._lib.array_api_extra as xpx import pickle import copy + +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + def basis_vec(axis): if axis == 'x': return [1, 0, 0] @@ -18,81 +33,114 @@ def basis_vec(axis): elif axis == 'z': return [0, 0, 1] -def test_generic_quat_matrix(): - x = np.array([[3, 4, 0, 0], [5, 12, 0, 0]]) + +def rotation_to_xp(r: Rotation, xp): + return Rotation.from_quat(xp.asarray(r.as_quat())) + + +def test_init_non_array(): + Rotation((0, 0, 0, 1)) + Rotation([0, 0, 0, 1]) + + +def test_generic_quat_matrix(xp): + x = xp.asarray([[3.0, 4, 0, 0], [5, 12, 0, 0]]) r = Rotation.from_quat(x) - expected_quat = x / np.array([[5], [13]]) - assert_array_almost_equal(r.as_quat(), expected_quat) + expected_quat = x / xp.asarray([[5.0], [13.0]]) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_single_1d_quaternion(): - x = np.array([3, 4, 0, 0]) +def test_from_single_1d_quaternion(xp): + x = xp.asarray([3.0, 4, 0, 0]) r = Rotation.from_quat(x) expected_quat = x / 5 - assert_array_almost_equal(r.as_quat(), expected_quat) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_single_2d_quaternion(): - x = np.array([[3, 4, 0, 0]]) +def test_from_single_2d_quaternion(xp): + x = xp.asarray([[3.0, 4, 0, 0]]) r = Rotation.from_quat(x) expected_quat = x / 5 - assert_array_almost_equal(r.as_quat(), expected_quat) + xp_assert_close(r.as_quat(), expected_quat) -def test_from_quat_scalar_first(): +def test_from_quat_scalar_first(xp): rng = np.random.RandomState(0) - r = Rotation.from_quat([1, 0, 0, 0], scalar_first=True) - assert_allclose(r.as_matrix(), np.eye(3), rtol=1e-15, atol=1e-16) + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0]), scalar_first=True) + xp_assert_close(r.as_matrix(), xp.eye(3), rtol=1e-15, atol=1e-16) - r = Rotation.from_quat(np.tile([1, 0, 0, 0], (10, 1)), scalar_first=True) - assert_allclose(r.as_matrix(), np.tile(np.eye(3), (10, 1, 1)), - rtol=1e-15, atol=1e-16) + q = xp.tile(xp.asarray([1, 0, 0, 0]), (10, 1)) + r = Rotation.from_quat(q, scalar_first=True) + xp_assert_close( + r.as_matrix(), xp.tile(xp.eye(3), (10, 1, 1)), rtol=1e-15, atol=1e-16 + ) - q = rng.randn(100, 4) - q /= np.linalg.norm(q, axis=1)[:, None] - for qi in q: + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] r = Rotation.from_quat(qi, scalar_first=True) - assert_allclose(np.roll(r.as_quat(), 1), qi, rtol=1e-15) + xp_assert_close(xp.roll(r.as_quat(), 1), qi, rtol=1e-15) r = Rotation.from_quat(q, scalar_first=True) - assert_allclose(np.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15) + xp_assert_close(xp.roll(r.as_quat(), 1, axis=1), q, rtol=1e-15) + + +def test_from_quat_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_quat(r_expected.as_quat().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_quat_int_dtype(xp): + r = Rotation.from_quat(xp.asarray([1, 0, 0, 0])) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_as_quat_scalar_first(): +def test_as_quat_scalar_first(xp): rng = np.random.RandomState(0) - r = Rotation.from_euler('xyz', np.zeros(3)) - assert_allclose(r.as_quat(scalar_first=True), [1, 0, 0, 0], + r = Rotation.from_euler('xyz', xp.zeros(3)) + xp_assert_close(r.as_quat(scalar_first=True), xp.asarray([1.0, 0, 0, 0]), rtol=1e-15, atol=1e-16) - r = Rotation.from_euler('xyz', np.zeros((10, 3))) - assert_allclose(r.as_quat(scalar_first=True), - np.tile([1, 0, 0, 0], (10, 1)), rtol=1e-15, atol=1e-16) + r = Rotation.from_euler('xyz', xp.zeros((10, 3))) + xp_assert_close(r.as_quat(scalar_first=True), + xp.tile(xp.asarray([1.0, 0, 0, 0]), (10, 1)), + rtol=1e-15, atol=1e-16) - q = rng.randn(100, 4) - q /= np.linalg.norm(q, axis=1)[:, None] - for qi in q: + q = xp.asarray(rng.randn(100, 4)) + q /= xp_vector_norm(q, axis=1)[:, None] + for i in range(q.shape[0]): # Array API conforming loop + qi = q[i, ...] r = Rotation.from_quat(qi) - assert_allclose(r.as_quat(scalar_first=True), np.roll(qi, 1), + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(qi, 1), rtol=1e-15) - assert_allclose(r.as_quat(canonical=True, scalar_first=True), - np.roll(r.as_quat(canonical=True), 1), + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1), rtol=1e-15) r = Rotation.from_quat(q) - assert_allclose(r.as_quat(scalar_first=True), np.roll(q, 1, axis=1), + xp_assert_close(r.as_quat(scalar_first=True), xp.roll(q, 1, axis=1), rtol=1e-15) - assert_allclose(r.as_quat(canonical=True, scalar_first=True), - np.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15) + xp_assert_close(r.as_quat(canonical=True, scalar_first=True), + xp.roll(r.as_quat(canonical=True), 1, axis=1), rtol=1e-15) -def test_from_square_quat_matrix(): +def test_from_square_quat_matrix(xp): # Ensure proper norm array broadcasting - x = np.array([ - [3, 0, 0, 4], + x = xp.asarray([ + [3.0, 0, 0, 4], [5, 0, 12, 0], [0, 0, 0, 1], [-1, -1, -1, 1], @@ -100,643 +148,724 @@ def test_from_square_quat_matrix(): [-1, -1, -1, -1] # Check double cover ]) r = Rotation.from_quat(x) - expected_quat = x / np.array([[5], [13], [1], [2], [1], [2]]) - assert_array_almost_equal(r.as_quat(), expected_quat) + expected_quat = x / xp.asarray([[5.0], [13], [1], [2], [1], [2]]) + xp_assert_close(r.as_quat(), expected_quat) -def test_quat_double_to_canonical_single_cover(): - x = np.array([ - [-1, 0, 0, 0], +def test_quat_double_to_canonical_single_cover(xp): + x = xp.asarray([ + [-1.0, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, -1], [-1, -1, -1, -1] ]) r = Rotation.from_quat(x) - expected_quat = np.abs(x) / np.linalg.norm(x, axis=1)[:, None] - assert_allclose(r.as_quat(canonical=True), expected_quat) + expected_quat = xp.abs(x) / xp_vector_norm(x, axis=1)[:, None] + xp_assert_close(r.as_quat(canonical=True), expected_quat) -def test_quat_double_cover(): +def test_quat_double_cover(xp): # See the Rotation.from_quat() docstring for scope of the quaternion # double cover property. # Check from_quat and as_quat(canonical=False) - q = np.array([0, 0, 0, -1]) + q = xp.asarray([0.0, 0, 0, -1]) r = Rotation.from_quat(q) - assert_equal(q, r.as_quat(canonical=False)) - + xp_assert_equal(q, r.as_quat(canonical=False)) # Check composition and inverse - q = np.array([1, 0, 0, 1])/np.sqrt(2) # 90 deg rotation about x + q = xp.asarray([1.0, 0, 0, 1])/math.sqrt(2) # 90 deg rotation about x r = Rotation.from_quat(q) r3 = r*r*r - assert_allclose(r.as_quat(canonical=False)*np.sqrt(2), - [1, 0, 0, 1]) - assert_allclose(r.inv().as_quat(canonical=False)*np.sqrt(2), - [-1, 0, 0, 1]) - assert_allclose(r3.as_quat(canonical=False)*np.sqrt(2), - [1, 0, 0, -1]) - assert_allclose(r3.inv().as_quat(canonical=False)*np.sqrt(2), - [-1, 0, 0, -1]) + xp_assert_close(r.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, 1])) + xp_assert_close(r.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, 1])) + xp_assert_close(r3.as_quat(canonical=False)*math.sqrt(2), + xp.asarray([1.0, 0, 0, -1])) + xp_assert_close(r3.inv().as_quat(canonical=False)*math.sqrt(2), + xp.asarray([-1.0, 0, 0, -1])) # More sanity checks - assert_allclose((r*r.inv()).as_quat(canonical=False), - [0, 0, 0, 1], atol=2e-16) - assert_allclose((r3*r3.inv()).as_quat(canonical=False), - [0, 0, 0, 1], atol=2e-16) - assert_allclose((r*r3).as_quat(canonical=False), - [0, 0, 0, -1], atol=2e-16) - assert_allclose((r.inv()*r3.inv()).as_quat(canonical=False), - [0, 0, 0, -1], atol=2e-16) + xp_assert_close((r*r.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r3*r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, 1]), atol=2e-16) + xp_assert_close((r*r3).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) + xp_assert_close((r.inv() * r3.inv()).as_quat(canonical=False), + xp.asarray([0.0, 0, 0, -1]), atol=2e-16) -def test_from_quat_wrong_shape(): +def test_from_quat_wrong_shape(xp): # Wrong shape 1d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([1, 2, 3])) + Rotation.from_quat(xp.asarray([1, 2, 3])) # Wrong shape 2d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([ + Rotation.from_quat(xp.asarray([ [1, 2, 3, 4, 5], [4, 5, 6, 7, 8] ])) # 3d array with pytest.raises(ValueError, match='Expected `quat` to have shape'): - Rotation.from_quat(np.array([ + Rotation.from_quat(xp.asarray([ [[1, 2, 3, 4]], [[4, 5, 6, 7]] ])) -def test_zero_norms_from_quat(): - x = np.array([ +def test_zero_norms_from_quat(xp): + x = xp.asarray([ [3, 4, 0, 0], [0, 0, 0, 0], [5, 0, 12, 0] ]) - with pytest.raises(ValueError): - Rotation.from_quat(x) + if is_lazy_array(x): + assert xp.all(xp.isnan(Rotation.from_quat(x).as_quat()[1, ...])) + else: + with pytest.raises(ValueError): + Rotation.from_quat(x) -def test_as_matrix_single_1d_quaternion(): - quat = [0, 0, 0, 1] +def test_as_matrix_single_1d_quaternion(xp): + quat = xp.asarray([0, 0, 0, 1]) mat = Rotation.from_quat(quat).as_matrix() # mat.shape == (3,3) due to 1d input - assert_array_almost_equal(mat, np.eye(3)) + xp_assert_close(mat, xp.eye(3)) -def test_as_matrix_single_2d_quaternion(): - quat = [[0, 0, 1, 1]] +def test_as_matrix_single_2d_quaternion(xp): + quat = xp.asarray([[0, 0, 1, 1]]) mat = Rotation.from_quat(quat).as_matrix() assert_equal(mat.shape, (1, 3, 3)) - expected_mat = np.array([ - [0, -1, 0], + expected_mat = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected_mat) + xp_assert_close(mat[0, ...], expected_mat) -def test_as_matrix_from_square_input(): - quats = [ +def test_as_matrix_from_square_input(xp): + quats = xp.asarray([ [0, 0, 1, 1], [0, 1, 0, 1], [0, 0, 0, 1], [0, 0, 0, -1] - ] + ]) mat = Rotation.from_quat(quats).as_matrix() assert_equal(mat.shape, (4, 3, 3)) - expected0 = np.array([ - [0, -1, 0], + expected0 = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected0) + xp_assert_close(mat[0, ...], expected0) - expected1 = np.array([ - [0, 0, 1], + expected1 = xp.asarray([ + [0.0, 0, 1], [0, 1, 0], [-1, 0, 0] ]) - assert_array_almost_equal(mat[1], expected1) + xp_assert_close(mat[1, ...], expected1) - assert_array_almost_equal(mat[2], np.eye(3)) - assert_array_almost_equal(mat[3], np.eye(3)) + xp_assert_close(mat[2, ...], xp.eye(3)) + xp_assert_close(mat[3, ...], xp.eye(3)) -def test_as_matrix_from_generic_input(): - quats = [ +def test_as_matrix_from_generic_input(xp): + quats = xp.asarray([ [0, 0, 1, 1], [0, 1, 0, 1], [1, 2, 3, 4] - ] + ]) mat = Rotation.from_quat(quats).as_matrix() assert_equal(mat.shape, (3, 3, 3)) - expected0 = np.array([ - [0, -1, 0], + expected0 = xp.asarray([ + [0.0, -1, 0], [1, 0, 0], [0, 0, 1] ]) - assert_array_almost_equal(mat[0], expected0) + xp_assert_close(mat[0, ...], expected0) - expected1 = np.array([ - [0, 0, 1], + expected1 = xp.asarray([ + [0.0, 0, 1], [0, 1, 0], [-1, 0, 0] ]) - assert_array_almost_equal(mat[1], expected1) + xp_assert_close(mat[1, ...], expected1) - expected2 = np.array([ + expected2 = xp.asarray([ [0.4, -2, 2.2], [2.8, 1, 0.4], [-1, 2, 2] ]) / 3 - assert_array_almost_equal(mat[2], expected2) + xp_assert_close(mat[2, ...], expected2) -def test_from_single_2d_matrix(): - mat = [ +def test_from_single_2d_matrix(xp): + mat = xp.asarray([ [0, 0, 1], [1, 0, 0], [0, 1, 0] - ] - expected_quat = [0.5, 0.5, 0.5, 0.5] - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) + ]) + expected_quat = xp.asarray([0.5, 0.5, 0.5, 0.5]) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat) -def test_from_single_3d_matrix(): - mat = np.array([ +def test_from_single_3d_matrix(xp): + mat = xp.asarray([[ [0, 0, 1], [1, 0, 0], - [0, 1, 0] - ]).reshape((1, 3, 3)) - expected_quat = np.array([0.5, 0.5, 0.5, 0.5]).reshape((1, 4)) - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) + [0, 1, 0], + ]]) + expected_quat = xp.asarray([[0.5, 0.5, 0.5, 0.5]]) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat) -def test_from_matrix_calculation(): - expected_quat = np.array([1, 1, 6, 1]) / np.sqrt(39) - mat = np.array([ +def test_from_matrix_calculation(xp): + atol = 1e-8 + expected_quat = xp.asarray([1.0, 1, 6, 1]) / math.sqrt(39) + mat = xp.asarray([ [-0.8974359, -0.2564103, 0.3589744], [0.3589744, -0.8974359, 0.2564103], [0.2564103, 0.3589744, 0.8974359] ]) - assert_array_almost_equal( - Rotation.from_matrix(mat).as_quat(), - expected_quat) - assert_array_almost_equal( - Rotation.from_matrix(mat.reshape((1, 3, 3))).as_quat(), - expected_quat.reshape((1, 4))) + xp_assert_close(Rotation.from_matrix(mat).as_quat(), expected_quat, atol=atol) + xp_assert_close(Rotation.from_matrix(xp.reshape(mat, (1, 3, 3))).as_quat(), + xp.reshape(expected_quat, (1, 4)), + atol=atol) -def test_matrix_calculation_pipeline(): - mat = special_ortho_group.rvs(3, size=10, random_state=0) - assert_array_almost_equal(Rotation.from_matrix(mat).as_matrix(), mat) +def test_matrix_calculation_pipeline(xp): + mat = xp.asarray(special_ortho_group.rvs(3, size=10, random_state=0)) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), mat) -def test_from_matrix_ortho_output(): +def test_from_matrix_ortho_output(xp): + atol = 1e-12 rnd = np.random.RandomState(0) - mat = rnd.random_sample((100, 3, 3)) - dets = np.linalg.det(mat) - for i in range(len(dets)): + mat = xp.asarray(rnd.random_sample((100, 3, 3))) + dets = xp.linalg.det(mat) + for i in range(dets.shape[0]): # Make sure we have a right-handed rotation matrix if dets[i] < 0: - mat[i] = -mat[i] + mat = xpx.at(mat)[i, ...].set(-mat[i, ...]) ortho_mat = Rotation.from_matrix(mat).as_matrix() - mult_result = np.einsum('...ij,...jk->...ik', ortho_mat, - ortho_mat.transpose((0, 2, 1))) + mult_result = xp.matmul(ortho_mat, xp.matrix_transpose(ortho_mat)) - eye3d = np.zeros((100, 3, 3)) - for i in range(3): - eye3d[:, i, i] = 1.0 + eye3d = xp.zeros((100, 3, 3)) + xp.eye(3) + xp_assert_close(mult_result, eye3d, atol=atol) - assert_array_almost_equal(mult_result, eye3d) - -def test_from_matrix_normalize(): - mat = np.array([ +def test_from_matrix_normalize(xp): + mat = xp.asarray([ [1, 1, 0], [0, 1, 0], [0, 0, 1]]) - expected = np.array([[ 0.894427, 0.447214, 0.0], - [-0.447214, 0.894427, 0.0], - [ 0.0, 0.0, 1.0]]) - assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + expected = xp.asarray([[ 0.894427, 0.447214, 0.0], + [-0.447214, 0.894427, 0.0], + [ 0.0, 0.0, 1.0]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) - mat = np.array([ + mat = xp.asarray([ [0, -0.5, 0 ], [0.5, 0 , 0 ], [0, 0 , 0.5]]) - expected = np.array([[ 0, -1, 0], - [ 1, 0, 0], - [ 0, 0, 1]]) - assert_allclose(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) - - -def test_from_matrix_non_positive_determinant(): - mat = np.eye(3) - mat[0, 0] = 0 - with pytest.raises(ValueError, match='Non-positive determinant'): - Rotation.from_matrix(mat) - - mat[0, 0] = -1 - with pytest.raises(ValueError, match='Non-positive determinant'): - Rotation.from_matrix(mat) + expected = xp.asarray([[0.0, -1, 0], + [ 1, 0, 0], + [ 0, 0, 1]]) + xp_assert_close(Rotation.from_matrix(mat).as_matrix(), expected, atol=1e-6) + + +def test_from_matrix_non_positive_determinant(xp): + mat = xp.eye(3) + mat = xpx.at(mat)[0, 0].set(0) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + mat = xpx.at(mat)[0, 0].set(-1) + if is_lazy_array(mat): + assert xp.all(xp.isnan(Rotation.from_matrix(mat).as_matrix())) + else: + with pytest.raises(ValueError, match="Non-positive determinant"): + Rotation.from_matrix(mat) + + +def test_from_matrix_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_matrix(r_expected.as_matrix().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_matrix_int_dtype(xp): + mat = xp.asarray([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + r = Rotation.from_matrix(mat) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_from_1d_single_rotvec(): - rotvec = [1, 0, 0] - expected_quat = np.array([0.4794255, 0, 0, 0.8775826]) +def test_from_1d_single_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([1, 0, 0]) + expected_quat = xp.asarray([0.4794255, 0, 0, 0.8775826]) result = Rotation.from_rotvec(rotvec) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=atol) -def test_from_2d_single_rotvec(): - rotvec = [[1, 0, 0]] - expected_quat = np.array([[0.4794255, 0, 0, 0.8775826]]) +def test_from_2d_single_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([[1, 0, 0]]) + expected_quat = xp.asarray([[0.4794255, 0, 0, 0.8775826]]) result = Rotation.from_rotvec(rotvec) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=atol) -def test_from_generic_rotvec(): - rotvec = [ +def test_from_generic_rotvec(xp): + atol = 1e-7 + rotvec = xp.asarray([ [1, 2, 2], [1, -1, 0.5], - [0, 0, 0] - ] - expected_quat = np.array([ + [0, 0, 0]]) + expected_quat = xp.asarray([ [0.3324983, 0.6649967, 0.6649967, 0.0707372], [0.4544258, -0.4544258, 0.2272129, 0.7316889], [0, 0, 0, 1] ]) - assert_array_almost_equal( - Rotation.from_rotvec(rotvec).as_quat(), - expected_quat) + xp_assert_close(Rotation.from_rotvec(rotvec).as_quat(), expected_quat, atol=atol) -def test_from_rotvec_small_angle(): - rotvec = np.array([ - [5e-4 / np.sqrt(3), -5e-4 / np.sqrt(3), 5e-4 / np.sqrt(3)], +def test_from_rotvec_small_angle(xp): + rotvec = xp.asarray([ + [5e-4 / math.sqrt(3), -5e-4 / math.sqrt(3), 5e-4 / math.sqrt(3)], [0.2, 0.3, 0.4], [0, 0, 0] ]) quat = Rotation.from_rotvec(rotvec).as_quat() # cos(theta/2) ~~ 1 for small theta - assert_allclose(quat[0, 3], 1) + xp_assert_close(quat[0, 3], xp.asarray(1.0)[()]) # sin(theta/2) / theta ~~ 0.5 for small theta - assert_allclose(quat[0, :3], rotvec[0] * 0.5) + xp_assert_close(quat[0, :3], rotvec[0, ...] * 0.5) - assert_allclose(quat[1, 3], 0.9639685) - assert_allclose( - quat[1, :3], - np.array([ + xp_assert_close(quat[1, 3], xp.asarray(0.9639685)[()]) + xp_assert_close(quat[1, :3], + xp.asarray([ 0.09879603932153465, 0.14819405898230198, - 0.19759207864306931 - ])) + 0.19759207864306931])) + + xp_assert_equal(quat[2, ...], xp.asarray([0.0, 0, 0, 1])) + + +def test_from_rotvec_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_rotvec(r_expected.as_rotvec().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) - assert_equal(quat[2], np.array([0, 0, 0, 1])) +def test_from_rotvec_int_dtype(xp): + rotvec = xp.asarray([1, 0, 0]) + r = Rotation.from_rotvec(rotvec) + assert r.as_quat().dtype == xp_default_dtype(xp) -def test_degrees_from_rotvec(): - rotvec1 = [1.0 / np.cbrt(3), 1.0 / np.cbrt(3), 1.0 / np.cbrt(3)] + +def test_degrees_from_rotvec(xp): + rotvec1 = xp.asarray([1 / 3 ** (1/3)] * 3) rot1 = Rotation.from_rotvec(rotvec1, degrees=True) quat1 = rot1.as_quat() - rotvec2 = np.deg2rad(rotvec1) + # deg2rad is not implemented in Array API -> / 180 * xp.pi + rotvec2 = xp.asarray(rotvec1 / 180 * xp.pi) rot2 = Rotation.from_rotvec(rotvec2) quat2 = rot2.as_quat() - assert_allclose(quat1, quat2) + xp_assert_close(quat1, quat2) -def test_malformed_1d_from_rotvec(): +def test_malformed_1d_from_rotvec(xp): with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): - Rotation.from_rotvec([1, 2]) + Rotation.from_rotvec(xp.asarray([1, 2])) -def test_malformed_2d_from_rotvec(): +def test_malformed_2d_from_rotvec(xp): with pytest.raises(ValueError, match='Expected `rot_vec` to have shape'): - Rotation.from_rotvec([ + Rotation.from_rotvec(xp.asarray([ [1, 2, 3, 4], [5, 6, 7, 8] - ]) + ])) -def test_as_generic_rotvec(): - quat = np.array([ +def test_as_generic_rotvec(xp): + quat = xp.asarray([ [1, 2, -1, 0.5], [1, -1, 1, 0.0003], [0, 0, 0, 1] ]) - quat /= np.linalg.norm(quat, axis=1)[:, None] + quat /= xp_vector_norm(quat, axis=-1, keepdims=True) rotvec = Rotation.from_quat(quat).as_rotvec() - angle = np.linalg.norm(rotvec, axis=1) + angle = xp_vector_norm(rotvec, axis=-1) - assert_allclose(quat[:, 3], np.cos(angle/2)) - assert_allclose(np.cross(rotvec, quat[:, :3]), np.zeros((3, 3))) + xp_assert_close(quat[:, 3], xp.cos(angle / 2)) + xp_assert_close(xp.linalg.cross(rotvec, quat[:, :3]), xp.zeros((3, 3)), atol=1e-15) -def test_as_rotvec_single_1d_input(): - quat = np.array([1, 2, -3, 2]) - expected_rotvec = np.array([0.5772381, 1.1544763, -1.7317144]) +def test_as_rotvec_single_1d_input(xp): + quat = xp.asarray([1, 2, -3, 2]) + expected_rotvec = xp.asarray([0.5772381, 1.1544763, -1.7317144]) actual_rotvec = Rotation.from_quat(quat).as_rotvec() assert_equal(actual_rotvec.shape, (3,)) - assert_allclose(actual_rotvec, expected_rotvec) + xp_assert_close(actual_rotvec, expected_rotvec) -def test_as_rotvec_single_2d_input(): - quat = np.array([[1, 2, -3, 2]]) - expected_rotvec = np.array([[0.5772381, 1.1544763, -1.7317144]]) +def test_as_rotvec_single_2d_input(xp): + quat = xp.asarray([[1, 2, -3, 2]]) + expected_rotvec = xp.asarray([[0.5772381, 1.1544763, -1.7317144]]) actual_rotvec = Rotation.from_quat(quat).as_rotvec() assert_equal(actual_rotvec.shape, (1, 3)) - assert_allclose(actual_rotvec, expected_rotvec) + xp_assert_close(actual_rotvec, expected_rotvec) -def test_as_rotvec_degrees(): +def test_as_rotvec_degrees(xp): # x->y, y->z, z->x - mat = [[0, 0, 1], [1, 0, 0], [0, 1, 0]] + mat = xp.asarray([[0, 0, 1], [1, 0, 0], [0, 1, 0]]) rot = Rotation.from_matrix(mat) rotvec = rot.as_rotvec(degrees=True) - angle = np.linalg.norm(rotvec) - assert_allclose(angle, 120.0) - assert_allclose(rotvec[0], rotvec[1]) - assert_allclose(rotvec[1], rotvec[2]) + angle = xp_vector_norm(rotvec, axis=-1) + xp_assert_close(angle, xp.asarray(120.0)[()]) + xp_assert_close(rotvec[0], rotvec[1]) + xp_assert_close(rotvec[1], rotvec[2]) -def test_rotvec_calc_pipeline(): +def test_rotvec_calc_pipeline(xp): # Include small angles - rotvec = np.array([ + rotvec = xp.asarray([ [0, 0, 0], [1, -1, 2], [-3e-4, 3.5e-4, 7.5e-5] ]) - assert_allclose(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec) - assert_allclose(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), + xp_assert_close(Rotation.from_rotvec(rotvec).as_rotvec(), rotvec) + xp_assert_close(Rotation.from_rotvec(rotvec, degrees=True).as_rotvec(degrees=True), rotvec) -def test_from_1d_single_mrp(): - mrp = [0, 0, 1.0] - expected_quat = np.array([0, 0, 1, 0]) +def test_from_1d_single_mrp(xp): + mrp = xp.asarray([0, 0, 1.0]) + expected_quat = xp.asarray([0.0, 0, 1, 0]) result = Rotation.from_mrp(mrp) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat, atol=1e-12) -def test_from_2d_single_mrp(): - mrp = [[0, 0, 1.0]] - expected_quat = np.array([[0, 0, 1, 0]]) +def test_from_2d_single_mrp(xp): + mrp = xp.asarray([[0, 0, 1.0]]) + expected_quat = xp.asarray([[0.0, 0, 1, 0]]) result = Rotation.from_mrp(mrp) - assert_array_almost_equal(result.as_quat(), expected_quat) + xp_assert_close(result.as_quat(), expected_quat) + +def test_from_mrp_array_like(): + rng = np.random.default_rng(123) + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert r_expected.approx_equal(r, atol=1e-12) -def test_from_generic_mrp(): - mrp = np.array([ + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_mrp(r_expected.as_mrp().tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_mrp_int_dtype(xp): + mrp = xp.asarray([0, 0, 1]) + r = Rotation.from_mrp(mrp) + assert r.as_quat().dtype == xp_default_dtype(xp) + + +def test_from_generic_mrp(xp): + mrp = xp.asarray([ [1, 2, 2], [1, -1, 0.5], [0, 0, 0]]) - expected_quat = np.array([ + expected_quat = xp.asarray([ [0.2, 0.4, 0.4, -0.8], [0.61538462, -0.61538462, 0.30769231, -0.38461538], [0, 0, 0, 1]]) - assert_array_almost_equal(Rotation.from_mrp(mrp).as_quat(), expected_quat) + xp_assert_close(Rotation.from_mrp(mrp).as_quat(), expected_quat) -def test_malformed_1d_from_mrp(): +def test_malformed_1d_from_mrp(xp): with pytest.raises(ValueError, match='Expected `mrp` to have shape'): - Rotation.from_mrp([1, 2]) + Rotation.from_mrp(xp.asarray([1, 2])) -def test_malformed_2d_from_mrp(): +def test_malformed_2d_from_mrp(xp): with pytest.raises(ValueError, match='Expected `mrp` to have shape'): - Rotation.from_mrp([ + Rotation.from_mrp(xp.asarray([ [1, 2, 3, 4], [5, 6, 7, 8] - ]) + ])) -def test_as_generic_mrp(): - quat = np.array([ +def test_as_generic_mrp(xp): + quat = xp.asarray([ [1, 2, -1, 0.5], [1, -1, 1, 0.0003], [0, 0, 0, 1]]) - quat /= np.linalg.norm(quat, axis=1)[:, None] + quat /= xp_vector_norm(quat, axis=1)[:, None] - expected_mrp = np.array([ + expected_mrp = xp.asarray([ [0.33333333, 0.66666667, -0.33333333], [0.57725028, -0.57725028, 0.57725028], [0, 0, 0]]) - assert_array_almost_equal(Rotation.from_quat(quat).as_mrp(), expected_mrp) + xp_assert_close(Rotation.from_quat(quat).as_mrp(), expected_mrp) -def test_past_180_degree_rotation(): + +def test_past_180_degree_rotation(xp): # ensure that a > 180 degree rotation is returned as a <180 rotation in MRPs # in this case 270 should be returned as -90 - expected_mrp = np.array([-np.tan(np.pi/2/4), 0.0, 0]) - assert_array_almost_equal( - Rotation.from_euler('xyz', [270, 0, 0], degrees=True).as_mrp(), - expected_mrp + expected_mrp = xp.asarray([-math.tan(xp.pi / 2 / 4), 0.0, 0]) + xp_assert_close( + Rotation.from_euler('xyz', xp.asarray([270, 0, 0]), degrees=True).as_mrp(), + expected_mrp, ) -def test_as_mrp_single_1d_input(): - quat = np.array([1, 2, -3, 2]) - expected_mrp = np.array([0.16018862, 0.32037724, -0.48056586]) +def test_as_mrp_single_1d_input(xp): + quat = xp.asarray([1, 2, -3, 2]) + expected_mrp = xp.asarray([0.16018862, 0.32037724, -0.48056586]) actual_mrp = Rotation.from_quat(quat).as_mrp() assert_equal(actual_mrp.shape, (3,)) - assert_allclose(actual_mrp, expected_mrp) + xp_assert_close(actual_mrp, expected_mrp) -def test_as_mrp_single_2d_input(): - quat = np.array([[1, 2, -3, 2]]) - expected_mrp = np.array([[0.16018862, 0.32037724, -0.48056586]]) +def test_as_mrp_single_2d_input(xp): + quat = xp.asarray([[1, 2, -3, 2]]) + expected_mrp = xp.asarray([[0.16018862, 0.32037724, -0.48056586]]) actual_mrp = Rotation.from_quat(quat).as_mrp() assert_equal(actual_mrp.shape, (1, 3)) - assert_allclose(actual_mrp, expected_mrp) + xp_assert_close(actual_mrp, expected_mrp) -def test_mrp_calc_pipeline(): - actual_mrp = np.array([ +def test_mrp_calc_pipeline(xp): + actual_mrp = xp.asarray([ [0, 0, 0], [1, -1, 2], [0.41421356, 0, 0], [0.1, 0.2, 0.1]]) - expected_mrp = np.array([ + expected_mrp = xp.asarray([ [0, 0, 0], [-0.16666667, 0.16666667, -0.33333333], [0.41421356, 0, 0], [0.1, 0.2, 0.1]]) - assert_allclose(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp) + xp_assert_close(Rotation.from_mrp(actual_mrp).as_mrp(), expected_mrp) -def test_from_euler_single_rotation(): - quat = Rotation.from_euler('z', 90, degrees=True).as_quat() - expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2) - assert_allclose(quat, expected_quat) +def test_from_euler_single_rotation(xp): + quat = Rotation.from_euler("z", xp.asarray(90), degrees=True).as_quat() + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) -def test_single_intrinsic_extrinsic_rotation(): - extrinsic = Rotation.from_euler('z', 90, degrees=True).as_matrix() - intrinsic = Rotation.from_euler('Z', 90, degrees=True).as_matrix() - assert_allclose(extrinsic, intrinsic) +def test_single_intrinsic_extrinsic_rotation(xp): + extrinsic = Rotation.from_euler('z', xp.asarray(90), degrees=True).as_matrix() + intrinsic = Rotation.from_euler('Z', xp.asarray(90), degrees=True).as_matrix() + xp_assert_close(extrinsic, intrinsic) -def test_from_euler_rotation_order(): +def test_from_euler_rotation_order(xp): # Intrinsic rotation is same as extrinsic with order reversed rnd = np.random.RandomState(0) - a = rnd.randint(low=0, high=180, size=(6, 3)) - b = a[:, ::-1] + a = xp.asarray(rnd.randint(low=0, high=180, size=(6, 3))) + b = xp.flip(a, axis=-1) x = Rotation.from_euler('xyz', a, degrees=True).as_quat() y = Rotation.from_euler('ZYX', b, degrees=True).as_quat() - assert_allclose(x, y) + xp_assert_close(x, y) -def test_from_euler_elementary_extrinsic_rotation(): +def test_from_euler_elementary_extrinsic_rotation(xp): + atol = 1e-12 # Simple test to check if extrinsic rotations are implemented correctly - mat = Rotation.from_euler('zx', [90, 90], degrees=True).as_matrix() - expected_mat = np.array([ - [0, -1, 0], + mat = Rotation.from_euler('zx', xp.asarray([90, 90]), degrees=True).as_matrix() + expected_mat = xp.asarray([ + [0.0, -1, 0], [0, 0, -1], [1, 0, 0] ]) - assert_array_almost_equal(mat, expected_mat) + xp_assert_close(mat, expected_mat, atol=atol) -def test_from_euler_intrinsic_rotation_312(): - angles = [ +def test_from_euler_intrinsic_rotation_312(xp): + atol = 1e-7 + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('ZXY', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.3061862, -0.2500000, 0.9185587], [0.8838835, 0.4330127, -0.1767767], [-0.3535534, 0.8660254, 0.3535534] - ])) + ]), atol=atol) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.5334936, -0.2500000, 0.8080127], [0.8080127, 0.4330127, -0.3995191], [-0.2500000, 0.8660254, 0.4330127] - ])) + ]), atol=atol) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [0.0473672, -0.6123725, 0.7891491], [0.6597396, 0.6123725, 0.4355958], [-0.7500000, 0.5000000, 0.4330127] - ])) + ]), atol=atol) -def test_from_euler_intrinsic_rotation_313(): - angles = [ +def test_from_euler_intrinsic_rotation_313(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('ZXZ', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.43559574, -0.78914913, 0.4330127], [0.65973961, -0.04736717, -0.750000], [0.61237244, 0.61237244, 0.500000] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.6250000, -0.64951905, 0.4330127], [0.64951905, 0.1250000, -0.750000], [0.4330127, 0.750000, 0.500000] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [-0.1767767, -0.91855865, 0.35355339], [0.88388348, -0.30618622, -0.35355339], [0.4330127, 0.25000000, 0.8660254] ])) -def test_from_euler_extrinsic_rotation_312(): - angles = [ +def test_from_euler_extrinsic_rotation_312(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('zxy', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.91855865, 0.1767767, 0.35355339], [0.25000000, 0.4330127, -0.8660254], [-0.30618622, 0.88388348, 0.35355339] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.96650635, -0.0580127, 0.2500000], [0.25000000, 0.4330127, -0.8660254], [-0.0580127, 0.89951905, 0.4330127] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [0.65973961, -0.04736717, 0.7500000], [0.61237244, 0.61237244, -0.5000000], [-0.43559574, 0.78914913, 0.4330127] ])) -def test_from_euler_extrinsic_rotation_313(): - angles = [ +def test_from_euler_extrinsic_rotation_313(xp): + angles = xp.asarray([ [30, 60, 45], [30, 60, 30], [45, 30, 60] - ] + ]) mat = Rotation.from_euler('zxz', angles, degrees=True).as_matrix() - assert_array_almost_equal(mat[0], np.array([ + xp_assert_close(mat[0, ...], xp.asarray([ [0.43559574, -0.65973961, 0.61237244], [0.78914913, -0.04736717, -0.61237244], [0.4330127, 0.75000000, 0.500000] ])) - assert_array_almost_equal(mat[1], np.array([ + xp_assert_close(mat[1, ...], xp.asarray([ [0.62500000, -0.64951905, 0.4330127], [0.64951905, 0.12500000, -0.750000], [0.4330127, 0.75000000, 0.500000] ])) - assert_array_almost_equal(mat[2], np.array([ + xp_assert_close(mat[2, ...], xp.asarray([ [-0.1767767, -0.88388348, 0.4330127], [0.91855865, -0.30618622, -0.250000], [0.35355339, 0.35355339, 0.8660254] ])) +def test_from_euler_array_like(): + rng = np.random.default_rng(123) + order = "xyz" + # Single rotation + r_expected = Rotation.random(rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(3, rng=rng) + r = Rotation.from_euler(order, r_expected.as_euler(order).tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_from_euler_scalar(): + rng = np.random.default_rng(123) + deg = rng.uniform(low=-180, high=180) + r_expected = Rotation.from_euler("x", deg, degrees=True) + r = Rotation.from_euler("x", float(deg), degrees=True) + assert r_expected.approx_equal(r, atol=1e-12) + + @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_asymmetric_axes(seq_tuple, intrinsic): +def test_as_euler_asymmetric_axes(xp, seq_tuple, intrinsic): # helper function for mean error tests def test_stats(error, mean_max, rms_max): - mean = np.mean(error, axis=0) - std = np.std(error, axis=0) - rms = np.hypot(mean, std) - assert np.all(np.abs(mean) < mean_max) - assert np.all(rms < rms_max) + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) rnd = np.random.RandomState(0) n = 1000 @@ -744,6 +873,7 @@ def test_stats(error, mean_max, rms_max): angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) angles[:, 1] = rnd.uniform(low=-np.pi / 2, high=np.pi / 2, size=(n,)) angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) seq = "".join(seq_tuple) if intrinsic: @@ -752,9 +882,11 @@ def test_stats(error, mean_max, rms_max): seq = seq.upper() rotation = Rotation.from_euler(seq, angles) angles_quat = rotation.as_euler(seq) + # TODO: Why are we using _as_euler_from_matrix here? As a sanity check? It is not + # part of the public API and should not be used anywhere else angles_mat = rotation._as_euler_from_matrix(seq) - assert_allclose(angles, angles_quat, atol=0, rtol=1e-12) - assert_allclose(angles, angles_mat, atol=0, rtol=1e-12) + xp_assert_close(angles, angles_quat, atol=0, rtol=1e-12) + xp_assert_close(angles, angles_mat, atol=0, rtol=1e-12) test_stats(angles_quat - angles, 1e-15, 1e-14) test_stats(angles_mat - angles, 1e-15, 1e-14) @@ -762,14 +894,14 @@ def test_stats(error, mean_max, rms_max): @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_symmetric_axes(seq_tuple, intrinsic): +def test_as_euler_symmetric_axes(xp, seq_tuple, intrinsic): # helper function for mean error tests def test_stats(error, mean_max, rms_max): - mean = np.mean(error, axis=0) - std = np.std(error, axis=0) - rms = np.hypot(mean, std) - assert np.all(np.abs(mean) < mean_max) - assert np.all(rms < rms_max) + mean = xp.mean(error, axis=0) + std = xp.std(error, axis=0) + rms = xp.hypot(mean, std) + assert xp.all(xp.abs(mean) < mean_max) + assert xp.all(rms < rms_max) rnd = np.random.RandomState(0) n = 1000 @@ -777,6 +909,7 @@ def test_stats(error, mean_max, rms_max): angles[:, 0] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) angles[:, 1] = rnd.uniform(low=0, high=np.pi, size=(n,)) angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) + angles = xp.asarray(angles) # Rotation of the form A/B/A are rotation around symmetric axes seq = "".join([seq_tuple[0], seq_tuple[1], seq_tuple[0]]) @@ -784,9 +917,10 @@ def test_stats(error, mean_max, rms_max): seq = seq.upper() rotation = Rotation.from_euler(seq, angles) angles_quat = rotation.as_euler(seq) + # TODO: Same as before: Remove _as_euler_from_matrix? angles_mat = rotation._as_euler_from_matrix(seq) - assert_allclose(angles, angles_quat, atol=0, rtol=1e-13) - assert_allclose(angles, angles_mat, atol=0, rtol=1e-9) + xp_assert_close(angles, angles_quat, atol=0, rtol=1e-13) + xp_assert_close(angles, angles_mat, atol=0, rtol=1e-9) test_stats(angles_quat - angles, 1e-16, 1e-14) test_stats(angles_mat - angles, 1e-15, 1e-13) @@ -794,10 +928,11 @@ def test_stats(error, mean_max, rms_max): @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic): +def test_as_euler_degenerate_asymmetric_axes(xp, seq_tuple, intrinsic): + atol = 1e-12 # Since we cannot check for angle equality, we check for rotation matrix # equality - angles = np.array([ + angles = xp.asarray([ [45, 90, 35], [35, -90, 20], [35, 90, 25], @@ -811,20 +946,23 @@ def test_as_euler_degenerate_asymmetric_axes(seq_tuple, intrinsic): rotation = Rotation.from_euler(seq, angles, degrees=True) mat_expected = rotation.as_matrix() - with pytest.warns(UserWarning, match="Gimbal lock"): + # We can only warn on non-lazy backends because we'd need to condition on traced + # booleans + with eager_warns(mat_expected, UserWarning, match="Gimbal lock"): angle_estimates = rotation.as_euler(seq, degrees=True) mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + xp_assert_close(mat_expected, mat_estimated, atol=atol) @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic): +def test_as_euler_degenerate_symmetric_axes(xp, seq_tuple, intrinsic): + atol = 1e-12 # Since we cannot check for angle equality, we check for rotation matrix # equality - angles = np.array([ + angles = xp.asarray([ [15, 0, 60], [35, 0, 75], [60, 180, 35], @@ -839,22 +977,23 @@ def test_as_euler_degenerate_symmetric_axes(seq_tuple, intrinsic): rotation = Rotation.from_euler(seq, angles, degrees=True) mat_expected = rotation.as_matrix() - with pytest.warns(UserWarning, match="Gimbal lock"): + # We can only warn on non-lazy backends + with eager_warns(mat_expected, UserWarning, match="Gimbal lock"): angle_estimates = rotation.as_euler(seq, degrees=True) mat_estimated = Rotation.from_euler(seq, angle_estimates, degrees=True).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + xp_assert_close(mat_expected, mat_estimated, atol=atol) @pytest.mark.thread_unsafe @pytest.mark.parametrize("seq_tuple", permutations("xyz")) @pytest.mark.parametrize("intrinsic", (False, True)) -def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): +def test_as_euler_degenerate_compare_algorithms(xp, seq_tuple, intrinsic): # this test makes sure that both algorithms are doing the same choices # in degenerate cases # asymmetric axes - angles = np.array([ + angles = xp.asarray([ [45, 90, 35], [35, -90, 20], [35, 90, 25], @@ -867,21 +1006,20 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): seq = seq.upper() rot = Rotation.from_euler(seq, angles, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): estimates_quat = rot.as_euler(seq, degrees=True) - assert_allclose( + xp_assert_close( estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12 ) - assert_allclose(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7) + xp_assert_close(estimates_matrix[:, 1], estimates_quat[:, 1], atol=0, rtol=1e-7) # symmetric axes # Absolute error tolerance must be looser to directly compare the results # from both algorithms, because of numerical loss of precision for the # method _as_euler_from_matrix near a zero angle value - angles = np.array([ + angles = xp.asarray([ [15, 0, 60], [35, 0, 75], [60, 180, 35], @@ -897,45 +1035,49 @@ def test_as_euler_degenerate_compare_algorithms(seq_tuple, intrinsic): seq = seq.upper() rot = Rotation.from_euler(seq, angles, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_matrix = rot._as_euler_from_matrix(seq, degrees=True) - with pytest.warns(UserWarning, match="Gimbal lock"): + with eager_warns(rot, UserWarning, match="Gimbal lock"): estimates_quat = rot.as_euler(seq, degrees=True) - assert_allclose( + xp_assert_close( estimates_matrix[:, [0, 2]], estimates_quat[:, [0, 2]], atol=0, rtol=1e-12 ) - assert_allclose( + xp_assert_close( estimates_matrix[~idx, 1], estimates_quat[~idx, 1], atol=0, rtol=1e-7 ) - assert_allclose( + xp_assert_close( estimates_matrix[idx, 1], estimates_quat[idx, 1], atol=1e-6 ) # problematic, angles[1] = 0 -def test_inv(): +def test_inv(xp): + atol = 1e-12 rnd = np.random.RandomState(0) n = 10 # preserve use of old random_state during SPEC 7 transition p = Rotation.random(num=n, random_state=rnd) + p = Rotation.from_quat(xp.asarray(p.as_quat())) q = p.inv() p_mat = p.as_matrix() q_mat = q.as_matrix() - result1 = np.einsum('...ij,...jk->...ik', p_mat, q_mat) - result2 = np.einsum('...ij,...jk->...ik', q_mat, p_mat) + result1 = xp.asarray(np.einsum("...ij,...jk->...ik", p_mat, q_mat)) + result2 = xp.asarray(np.einsum("...ij,...jk->...ik", q_mat, p_mat)) - eye3d = np.empty((n, 3, 3)) - eye3d[:] = np.eye(3) + eye3d = xp.empty((n, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) - assert_array_almost_equal(result1, eye3d) - assert_array_almost_equal(result2, eye3d) + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) -def test_inv_single_rotation(): +def test_inv_single_rotation(xp): + atol = 1e-12 rng = np.random.default_rng(146972845698875399755764481408308808739) p = Rotation.random(rng=rng) + p = Rotation.from_quat(xp.asarray(p.as_quat())) q = p.inv() p_mat = p.as_matrix() @@ -943,93 +1085,105 @@ def test_inv_single_rotation(): res1 = np.dot(p_mat, q_mat) res2 = np.dot(q_mat, p_mat) - eye = np.eye(3) + eye = xp.eye(3) - assert_array_almost_equal(res1, eye) - assert_array_almost_equal(res2, eye) + xp_assert_close(res1, eye, atol=atol) + xp_assert_close(res2, eye, atol=atol) x = Rotation.random(num=1, rng=rng) + x = Rotation.from_quat(xp.asarray(x.as_quat())) y = x.inv() x_matrix = x.as_matrix() y_matrix = y.as_matrix() - result1 = np.einsum('...ij,...jk->...ik', x_matrix, y_matrix) - result2 = np.einsum('...ij,...jk->...ik', y_matrix, x_matrix) + result1 = xp.linalg.matmul(x_matrix, y_matrix) + result2 = xp.linalg.matmul(y_matrix, x_matrix) - eye3d = np.empty((1, 3, 3)) - eye3d[:] = np.eye(3) + eye3d = xp.empty((1, 3, 3)) + eye3d = xpx.at(eye3d)[..., :3, :3].set(xp.eye(3)) - assert_array_almost_equal(result1, eye3d) - assert_array_almost_equal(result2, eye3d) + xp_assert_close(result1, eye3d, atol=atol) + xp_assert_close(result2, eye3d, atol=atol) -def test_identity_magnitude(): +def test_identity_magnitude(xp): n = 10 - assert_allclose(Rotation.identity(n).magnitude(), 0) - assert_allclose(Rotation.identity(n).inv().magnitude(), 0) + r = Rotation.identity(n) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + expected = xp.zeros(n) + xp_assert_close(r.magnitude(), expected) + xp_assert_close(r.inv().magnitude(), expected) -def test_single_identity_magnitude(): - assert Rotation.identity().magnitude() == 0 - assert Rotation.identity().inv().magnitude() == 0 +def test_single_identity_magnitude(xp): + r = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + assert r.magnitude() == 0 + assert r.inv().magnitude() == 0 -def test_identity_invariance(): +def test_identity_invariance(xp): + atol = 1e-12 n = 10 p = Rotation.random(n, rng=0) - - result = p * Rotation.identity(n) - assert_array_almost_equal(p.as_quat(), result.as_quat()) + p = Rotation.from_quat(xp.asarray(p.as_quat())) + q = Rotation.from_quat(xp.asarray(Rotation.identity(n).as_quat())) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) result = result * p.inv() - assert_array_almost_equal(result.magnitude(), np.zeros(n)) + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) -def test_single_identity_invariance(): +def test_single_identity_invariance(xp): + atol = 1e-12 n = 10 p = Rotation.random(n, rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) - result = p * Rotation.identity() - assert_array_almost_equal(p.as_quat(), result.as_quat()) + q = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + result = p * q + xp_assert_close(p.as_quat(), result.as_quat()) result = result * p.inv() - assert_array_almost_equal(result.magnitude(), np.zeros(n)) + xp_assert_close(result.magnitude(), xp.zeros(n), atol=atol) -def test_magnitude(): - r = Rotation.from_quat(np.eye(4)) +def test_magnitude(xp): + r = Rotation.from_quat(xp.eye(4)) result = r.magnitude() - assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0]) + xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0])) - r = Rotation.from_quat(-np.eye(4)) + r = Rotation.from_quat(-xp.eye(4)) result = r.magnitude() - assert_array_almost_equal(result, [np.pi, np.pi, np.pi, 0]) + xp_assert_close(result, xp.asarray([xp.pi, xp.pi, xp.pi, 0])) -def test_magnitude_single_rotation(): - r = Rotation.from_quat(np.eye(4)) +def test_magnitude_single_rotation(xp): + r = Rotation.from_quat(xp.eye(4)) result1 = r[0].magnitude() - assert_allclose(result1, np.pi) + xp_assert_close(result1, xp.pi) result2 = r[3].magnitude() - assert_allclose(result2, 0) + xp_assert_close(result2, 0.0) -def test_approx_equal(): +def test_approx_equal(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) p = Rotation.random(10, rng=rng) q = Rotation.random(10, rng=rng) + p = Rotation.from_quat(xp.asarray(p.as_quat())) + q = Rotation.from_quat(xp.asarray(q.as_quat())) r = p * q.inv() r_mag = r.magnitude() - atol = np.median(r_mag) # ensure we get mix of Trues and Falses - assert_equal(p.approx_equal(q, atol), (r_mag < atol)) + atol = xp.asarray(np.median(r_mag)) # ensure we get mix of Trues and Falses + xp_assert_equal(p.approx_equal(q, atol), (r_mag < atol)) @pytest.mark.thread_unsafe -def test_approx_equal_single_rotation(): +def test_approx_equal_single_rotation(xp): # also tests passing single argument to approx_equal - p = Rotation.from_rotvec([0, 0, 1e-9]) # less than default atol of 1e-8 - q = Rotation.from_quat(np.eye(4)) + p = Rotation.from_rotvec(xp.asarray([0, 0, 1e-9])) # less than default atol of 1e-8 + q = Rotation.from_quat(xp.eye(4)) assert p.approx_equal(q[3]) assert not p.approx_equal(q[0]) @@ -1040,40 +1194,47 @@ def test_approx_equal_single_rotation(): assert p.approx_equal(q[3], degrees=True) -def test_mean(): +def test_mean(xp): + axes = xp.concat((-xp.eye(3), xp.eye(3))) axes = np.concatenate((-np.eye(3), np.eye(3))) - thetas = np.linspace(0, np.pi / 2, 100) + thetas = xp.linspace(0, xp.pi / 2, 100) for t in thetas: r = Rotation.from_rotvec(t * axes) - assert_allclose(r.mean().magnitude(), 0, atol=1E-10) + xp_assert_close(r.mean().magnitude(), 0.0, atol=1e-10) -def test_weighted_mean(): +def test_weighted_mean(xp): # test that doubling a weight is equivalent to including a rotation twice. - axes = np.array([[0, 0, 0], [1, 0, 0], [1, 0, 0]]) - thetas = np.linspace(0, np.pi / 2, 100) + axes = xp.asarray([[0.0, 0, 0], [1, 0, 0], [1, 0, 0]]) + thetas = xp.linspace(0, xp.pi / 2, 100) for t in thetas: - rw = Rotation.from_rotvec(t * axes[:2]) + rw = Rotation.from_rotvec(t * axes[:2, ...]) mw = rw.mean(weights=[1, 2]) r = Rotation.from_rotvec(t * axes) m = r.mean() - assert_allclose((m * mw.inv()).magnitude(), 0, atol=1E-10) + xp_assert_close((m * mw.inv()).magnitude(), 0.0, atol=1e-10) -def test_mean_invalid_weights(): - with pytest.raises(ValueError, match="non-negative"): - r = Rotation.from_quat(np.eye(4)) - r.mean(weights=-np.ones(4)) +def test_mean_invalid_weights(xp): + r = Rotation.from_quat(xp.eye(4)) + if is_lazy_array(r.as_quat()): + m = r.mean(weights=-xp.ones(4)) + assert all(xp.isnan(m._quat)) + else: + with pytest.raises(ValueError, match="non-negative"): + r.mean(weights=-xp.ones(4)) -def test_reduction_no_indices(): - result = Rotation.identity().reduce(return_indices=False) +def test_reduction_no_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=False) assert isinstance(result, Rotation) -def test_reduction_none_indices(): - result = Rotation.identity().reduce(return_indices=True) +def test_reduction_none_indices(xp): + r = Rotation.from_quat(xp.asarray([0.0, 0.0, 0.0, 1.0])) + result = r.reduce(return_indices=True) assert type(result) is tuple assert len(result) == 3 @@ -1082,11 +1243,12 @@ def test_reduction_none_indices(): assert right_best is None -def test_reduction_scalar_calculation(): +def test_reduction_scalar_calculation(xp): + atol = 1e-12 rng = np.random.default_rng(146972845698875399755764481408308808739) - l = Rotation.random(5, rng=rng) - r = Rotation.random(10, rng=rng) - p = Rotation.random(7, rng=rng) + l = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat())) + r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + p = Rotation.from_quat(xp.asarray(Rotation.random(7, rng=rng).as_quat())) reduced, left_best, right_best = p.reduce(l, r, return_indices=True) # Loop implementation of the vectorized calculation in Rotation.reduce @@ -1098,66 +1260,66 @@ def test_reduction_scalar_calculation(): scalars = np.reshape(np.moveaxis(scalars, 1, 0), (scalars.shape[1], -1)) max_ind = np.argmax(np.reshape(scalars, (len(p), -1)), axis=1) - left_best_check = max_ind // len(r) - right_best_check = max_ind % len(r) - assert (left_best == left_best_check).all() - assert (right_best == right_best_check).all() + left_best_check = xp.asarray(max_ind // len(r)) + right_best_check = xp.asarray(max_ind % len(r)) + assert xp.all(left_best == left_best_check) + assert xp.all(right_best == right_best_check) reduced_check = l[left_best_check] * p * r[right_best_check] mag = (reduced.inv() * reduced_check).magnitude() - assert_array_almost_equal(mag, np.zeros(len(p))) + xp_assert_close(mag, xp.zeros(len(p)), atol=atol) -def test_apply_single_rotation_single_point(): - mat = np.array([ +def test_apply_single_rotation_single_point(xp): + mat = xp.asarray([ [0, -1, 0], [1, 0, 0], [0, 0, 1] ]) r_1d = Rotation.from_matrix(mat) - r_2d = Rotation.from_matrix(np.expand_dims(mat, axis=0)) + r_2d = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) - v_1d = np.array([1, 2, 3]) - v_2d = np.expand_dims(v_1d, axis=0) - v1d_rotated = np.array([-2, 1, 3]) - v2d_rotated = np.expand_dims(v1d_rotated, axis=0) + v_1d = xp.asarray([1.0, 2, 3]) + v_2d = xp.expand_dims(v_1d, axis=0) + v1d_rotated = xp.asarray([-2.0, 1, 3]) + v2d_rotated = xp.expand_dims(v1d_rotated, axis=0) - assert_allclose(r_1d.apply(v_1d), v1d_rotated) - assert_allclose(r_1d.apply(v_2d), v2d_rotated) - assert_allclose(r_2d.apply(v_1d), v2d_rotated) - assert_allclose(r_2d.apply(v_2d), v2d_rotated) + xp_assert_close(r_1d.apply(v_1d), v1d_rotated) + xp_assert_close(r_1d.apply(v_2d), v2d_rotated) + xp_assert_close(r_2d.apply(v_1d), v2d_rotated) + xp_assert_close(r_2d.apply(v_2d), v2d_rotated) - v1d_inverse = np.array([2, -1, 3]) - v2d_inverse = np.expand_dims(v1d_inverse, axis=0) + v1d_inverse = xp.asarray([2.0, -1, 3]) + v2d_inverse = xp.expand_dims(v1d_inverse, axis=0) - assert_allclose(r_1d.apply(v_1d, inverse=True), v1d_inverse) - assert_allclose(r_1d.apply(v_2d, inverse=True), v2d_inverse) - assert_allclose(r_2d.apply(v_1d, inverse=True), v2d_inverse) - assert_allclose(r_2d.apply(v_2d, inverse=True), v2d_inverse) + xp_assert_close(r_1d.apply(v_1d, inverse=True), v1d_inverse) + xp_assert_close(r_1d.apply(v_2d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_1d, inverse=True), v2d_inverse) + xp_assert_close(r_2d.apply(v_2d, inverse=True), v2d_inverse) -def test_apply_single_rotation_multiple_points(): - mat = np.array([ +def test_apply_single_rotation_multiple_points(xp): + mat = xp.asarray([ [0, -1, 0], [1, 0, 0], [0, 0, 1] ]) r1 = Rotation.from_matrix(mat) - r2 = Rotation.from_matrix(np.expand_dims(mat, axis=0)) + r2 = Rotation.from_matrix(xp.expand_dims(mat, axis=0)) - v = np.array([[1, 2, 3], [4, 5, 6]]) - v_rotated = np.array([[-2, 1, 3], [-5, 4, 6]]) + v = xp.asarray([[1, 2, 3], [4, 5, 6]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [-5, 4, 6]]) - assert_allclose(r1.apply(v), v_rotated) - assert_allclose(r2.apply(v), v_rotated) + xp_assert_close(r1.apply(v), v_rotated) + xp_assert_close(r2.apply(v), v_rotated) - v_inverse = np.array([[2, -1, 3], [5, -4, 6]]) + v_inverse = xp.asarray([[2.0, -1, 3], [5, -4, 6]]) - assert_allclose(r1.apply(v, inverse=True), v_inverse) - assert_allclose(r2.apply(v, inverse=True), v_inverse) + xp_assert_close(r1.apply(v, inverse=True), v_inverse) + xp_assert_close(r2.apply(v, inverse=True), v_inverse) -def test_apply_multiple_rotations_single_point(): +def test_apply_multiple_rotations_single_point(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1169,23 +1331,24 @@ def test_apply_multiple_rotations_single_point(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - v1 = np.array([1, 2, 3]) - v2 = np.expand_dims(v1, axis=0) + v1 = xp.asarray([1, 2, 3]) + v2 = xp.expand_dims(v1, axis=0) - v_rotated = np.array([[-2, 1, 3], [1, -3, 2]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [1, -3, 2]]) - assert_allclose(r.apply(v1), v_rotated) - assert_allclose(r.apply(v2), v_rotated) + xp_assert_close(r.apply(v1), v_rotated) + xp_assert_close(r.apply(v2), v_rotated) - v_inverse = np.array([[2, -1, 3], [1, 3, -2]]) + v_inverse = xp.asarray([[2.0, -1, 3], [1, 3, -2]]) - assert_allclose(r.apply(v1, inverse=True), v_inverse) - assert_allclose(r.apply(v2, inverse=True), v_inverse) + xp_assert_close(r.apply(v1, inverse=True), v_inverse) + xp_assert_close(r.apply(v2, inverse=True), v_inverse) -def test_apply_multiple_rotations_multiple_points(): +def test_apply_multiple_rotations_multiple_points(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1197,22 +1360,24 @@ def test_apply_multiple_rotations_multiple_points(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - v = np.array([[1, 2, 3], [4, 5, 6]]) - v_rotated = np.array([[-2, 1, 3], [4, -6, 5]]) - assert_allclose(r.apply(v), v_rotated) + v = xp.asarray([[1, 2, 3], [4, 5, 6]]) + v_rotated = xp.asarray([[-2.0, 1, 3], [4, -6, 5]]) + xp_assert_close(r.apply(v), v_rotated) - v_inverse = np.array([[2, -1, 3], [4, 6, -5]]) - assert_allclose(r.apply(v, inverse=True), v_inverse) + v_inverse = xp.asarray([[2.0, -1, 3], [4, 6, -5]]) + xp_assert_close(r.apply(v, inverse=True), v_inverse) -def test_apply_shapes(): - vector0 = np.array([1.0, 2.0, 3.0]) - vector1 = np.array([vector0]) - vector2 = np.array([vector0, vector0]) - matrix0 = np.identity(3) - matrix1 = np.array([matrix0]) - matrix2 = np.array([matrix0, matrix0]) + +def test_apply_shapes(xp): + vector0 = xp.asarray([1.0, 2.0, 3.0]) + vector1 = xp.asarray([vector0]) + vector2 = xp.asarray([vector0, vector0]) + matrix0 = xp.eye(3) + matrix1 = xp.asarray([matrix0]) + matrix2 = xp.asarray([matrix0, matrix0]) for m, v in product([matrix0, matrix1, matrix2], [vector0, vector1, vector2]): r = Rotation.from_matrix(m) @@ -1224,7 +1389,23 @@ def test_apply_shapes(): x = r.apply(v, inverse=True) assert x.shape == shape -def test_getitem(): + +def test_apply_array_like(): + rng = np.random.default_rng(123) + # Single vector + r = Rotation.random(rng=rng) + t = rng.uniform(-100, 100, size=(3,)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + # Multiple vectors + t = rng.uniform(-100, 100, size=(2, 3)) + v = r.apply(t.tolist()) + v_expected = r.apply(t) + xp_assert_close(v, v_expected, atol=1e-12) + + +def test_getitem(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1236,47 +1417,60 @@ def test_getitem(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) - assert_allclose(r[0].as_matrix(), mat[0], atol=1e-15) - assert_allclose(r[1].as_matrix(), mat[1], atol=1e-15) - assert_allclose(r[:-1].as_matrix(), np.expand_dims(mat[0], axis=0), atol=1e-15) + xp_assert_close(r[0].as_matrix(), mat[0], atol=1e-15) + xp_assert_close(r[1].as_matrix(), mat[1, ...], atol=1e-15) + xp_assert_close(r[:-1].as_matrix(), xp.expand_dims(mat[0, ...], axis=0), atol=1e-15) -def test_getitem_single(): +def test_getitem_single(xp): with pytest.raises(TypeError, match='not subscriptable'): - Rotation.identity()[0] + Rotation.from_quat(xp.asarray([0, 0, 0, 1]))[0] -def test_setitem_single(): - r = Rotation.identity() +def test_getitem_array_like(): + mat = np.array([[[0.0, -1, 0], + [1, 0, 0], + [0, 0, 1]], + [[1, 0, 0], + [0, 0, -1], + [0, 1, 0]]]) + r = Rotation.from_matrix(mat) + xp_assert_close(r[[0]].as_matrix(), mat[[0]], atol=1e-15) + xp_assert_close(r[[0, 1]].as_matrix(), mat[[0, 1]], atol=1e-15) + + +def test_setitem_single(xp): + r = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) with pytest.raises(TypeError, match='not subscriptable'): - r[0] = Rotation.identity() + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) -def test_setitem_slice(): +def test_setitem_slice(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) - r1 = Rotation.random(10, rng=rng) - r2 = Rotation.random(5, rng=rng) + r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + r2 = Rotation.from_quat(xp.asarray(Rotation.random(5, rng=rng).as_quat())) r1[1:6] = r2 - assert_equal(r1[1:6].as_quat(), r2.as_quat()) + xp_assert_equal(r1[1:6].as_quat(), r2.as_quat()) -def test_setitem_integer(): +def test_setitem_integer(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) - r1 = Rotation.random(10, rng=rng) - r2 = Rotation.random(rng=rng) + r1 = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=rng).as_quat())) + r2 = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat())) r1[1] = r2 - assert_equal(r1[1].as_quat(), r2.as_quat()) + xp_assert_equal(r1[1].as_quat(), r2.as_quat()) -def test_setitem_wrong_type(): - r = Rotation.random(10, rng=0) +def test_setitem_wrong_type(xp): + r = Rotation.from_quat(xp.asarray(Rotation.random(10, rng=0).as_quat())) with pytest.raises(TypeError, match='Rotation object'): r[0] = 1 -def test_n_rotations(): +def test_n_rotations(xp): mat = np.empty((2, 3, 3)) mat[0] = np.array([ [0, -1, 0], @@ -1288,6 +1482,7 @@ def test_n_rotations(): [0, 0, -1], [0, 1, 0] ]) + mat = xp.asarray(mat) r = Rotation.from_matrix(mat) assert_equal(len(r), 2) @@ -1295,6 +1490,7 @@ def test_n_rotations(): def test_random_rotation_shape(): + # No xp testing since random rotations are always using NumPy rng = np.random.default_rng(146972845698875399755764481408308808739) assert_equal(Rotation.random(rng=rng).as_quat().shape, (4,)) assert_equal(Rotation.random(None, rng=rng).as_quat().shape, (4,)) @@ -1303,80 +1499,80 @@ def test_random_rotation_shape(): assert_equal(Rotation.random(5, rng=rng).as_quat().shape, (5, 4)) -def test_align_vectors_no_rotation(): - x = np.array([[1, 2, 3], [4, 5, 6]]) - y = x.copy() +def test_align_vectors_no_rotation(xp): + x = xp.asarray([[1, 2, 3], [4, 5, 6]]) + y = xp.asarray(x, copy=True) r, rssd = Rotation.align_vectors(x, y) - assert_array_almost_equal(r.as_matrix(), np.eye(3)) - assert_allclose(rssd, 0, atol=1e-6) + xp_assert_close(r.as_matrix(), xp.eye(3), atol=1e-12) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-6) -def test_align_vectors_no_noise(): +def test_align_vectors_no_noise(xp): rng = np.random.default_rng(14697284569885399755764481408308808739) - c = Rotation.random(rng=rng) - b = rng.normal(size=(5, 3)) + c = Rotation.from_quat(xp.asarray(Rotation.random(rng=rng).as_quat())) + b = xp.asarray(rng.normal(size=(5, 3))) a = c.apply(b) est, rssd = Rotation.align_vectors(a, b) - assert_allclose(c.as_quat(), est.as_quat()) - assert_allclose(rssd, 0, atol=1e-7) + xp_assert_close(c.as_quat(), est.as_quat()) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7) -def test_align_vectors_improper_rotation(): +def test_align_vectors_improper_rotation(xp): # Tests correct logic for issue #10444 - x = np.array([[0.89299824, -0.44372674, 0.0752378], - [0.60221789, -0.47564102, -0.6411702]]) - y = np.array([[0.02386536, -0.82176463, 0.5693271], - [-0.27654929, -0.95191427, -0.1318321]]) + x = xp.asarray([[0.89299824, -0.44372674, 0.0752378], + [0.60221789, -0.47564102, -0.6411702]]) + y = xp.asarray([[0.02386536, -0.82176463, 0.5693271], + [-0.27654929, -0.95191427, -0.1318321]]) est, rssd = Rotation.align_vectors(x, y) - assert_allclose(x, est.apply(y), atol=1e-6) - assert_allclose(rssd, 0, atol=1e-7) + xp_assert_close(x, est.apply(y), atol=1e-6) + xp_assert_close(rssd, xp.asarray(0.0)[()], check_shape=False, atol=1e-7) -def test_align_vectors_rssd_sensitivity(): - rssd_expected = 0.141421356237308 - sens_expected = np.array([[0.2, 0. , 0.], - [0. , 1.5, 1.], - [0. , 1. , 1.]]) +def test_align_vectors_rssd_sensitivity(xp): + rssd_expected = xp.asarray(0.141421356237308)[()] + sens_expected = xp.asarray([[0.2, 0. , 0.], + [0. , 1.5, 1.], + [0. , 1. , 1.]]) atol = 1e-6 - a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]] - b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]] + a = xp.asarray([[0, 1, 0], [0, 1, 1], [0, 1, 1]]) + b = xp.asarray([[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]) rot, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) - assert np.isclose(rssd, rssd_expected, atol=atol) - assert np.allclose(sens, sens_expected, atol=atol) + xp_assert_close(rssd, rssd_expected, atol=atol) + xp_assert_close(sens, sens_expected, atol=atol) -def test_align_vectors_scaled_weights(): +def test_align_vectors_scaled_weights(xp): n = 10 - a = Rotation.random(n, rng=0).apply([1, 0, 0]) - b = Rotation.random(n, rng=1).apply([1, 0, 0]) + a = xp.asarray(Rotation.random(n, rng=0).apply([1, 0, 0])) + b = xp.asarray(Rotation.random(n, rng=1).apply([1, 0, 0])) scale = 2 - est1, rssd1, cov1 = Rotation.align_vectors(a, b, np.ones(n), True) - est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * np.ones(n), True) + est1, rssd1, cov1 = Rotation.align_vectors(a, b, xp.ones(n), True) + est2, rssd2, cov2 = Rotation.align_vectors(a, b, scale * xp.ones(n), True) - assert_allclose(est1.as_matrix(), est2.as_matrix()) - assert_allclose(np.sqrt(scale) * rssd1, rssd2, atol=1e-6) - assert_allclose(cov1, cov2) + xp_assert_close(est1.as_matrix(), est2.as_matrix()) + xp_assert_close(math.sqrt(scale) * rssd1, rssd2, atol=1e-6) + xp_assert_close(cov1, cov2) -def test_align_vectors_noise(): +def test_align_vectors_noise(xp): rng = np.random.default_rng(146972845698875399755764481408308808739) n_vectors = 100 - rot = Rotation.random(rng=rng) - vectors = rng.normal(size=(n_vectors, 3)) + rot = rotation_to_xp(Rotation.random(rng=rng), xp) + vectors = xp.asarray(rng.normal(size=(n_vectors, 3))) result = rot.apply(vectors) # The paper adds noise as independently distributed angular errors sigma = np.deg2rad(1) tolerance = 1.5 * sigma noise = Rotation.from_rotvec( - rng.normal( + xp.asarray(rng.normal( size=(n_vectors, 3), scale=sigma - ) + )) ) # Attitude errors must preserve norm. Hence apply individual random @@ -1388,99 +1584,134 @@ def test_align_vectors_noise(): # Use rotation compositions to find out closeness error_vector = (rot * est.inv()).as_rotvec() - assert_allclose(error_vector[0], 0, atol=tolerance) - assert_allclose(error_vector[1], 0, atol=tolerance) - assert_allclose(error_vector[2], 0, atol=tolerance) + xp_assert_close(error_vector[0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(error_vector[2], xp.asarray(0.0)[()], atol=tolerance) # Check error bounds using covariance matrix cov *= sigma - assert_allclose(cov[0, 0], 0, atol=tolerance) - assert_allclose(cov[1, 1], 0, atol=tolerance) - assert_allclose(cov[2, 2], 0, atol=tolerance) + xp_assert_close(cov[0, 0], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[1, 1], xp.asarray(0.0)[()], atol=tolerance) + xp_assert_close(cov[2, 2], xp.asarray(0.0)[()], atol=tolerance) - assert_allclose(rssd, np.sum((noisy_result - est.apply(vectors))**2)**0.5) + rssd_check = xp.sum((noisy_result - est.apply(vectors)) ** 2) ** 0.5 + xp_assert_close(rssd, rssd_check, check_shape=False) -def test_align_vectors_invalid_input(): +def test_align_vectors_invalid_input(xp): with pytest.raises(ValueError, match="Expected input `a` to have shape"): - Rotation.align_vectors([1, 2, 3, 4], [1, 2, 3]) + a, b = xp.asarray([1, 2, 3, 4]), xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected input `b` to have shape"): - Rotation.align_vectors([1, 2, 3], [1, 2, 3, 4]) + a, b = xp.asarray([1, 2, 3]), xp.asarray([1, 2, 3, 4]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected inputs `a` and `b` " "to have same shapes"): - Rotation.align_vectors([[1, 2, 3],[4, 5, 6]], [[1, 2, 3]]) + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3]]) + Rotation.align_vectors(a, b) with pytest.raises(ValueError, match="Expected `weights` to be 1 dimensional"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[[1]]) + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([[1]]) + Rotation.align_vectors(a, b, weights) with pytest.raises(ValueError, match="Expected `weights` to have number of values"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - weights=[1, 2, 3]) - - with pytest.raises(ValueError, - match="`weights` may not contain negative values"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], weights=[-1]) - - with pytest.raises(ValueError, - match="Only one infinite weight is allowed"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - weights=[np.inf, np.inf]) - - with pytest.raises(ValueError, - match="Cannot align zero length primary vectors"): - Rotation.align_vectors([[0, 0, 0]], [[1, 2, 3]]) - - with pytest.raises(ValueError, - match="Cannot return sensitivity matrix"): - Rotation.align_vectors([[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]], - return_sensitivity=True, weights=[np.inf, 1]) - - with pytest.raises(ValueError, - match="Cannot return sensitivity matrix"): - Rotation.align_vectors([[1, 2, 3]], [[1, 2, 3]], - return_sensitivity=True) - - -def test_align_vectors_align_constrain(): + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([1, 2, 3]) + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + weights = xp.asarray([-1]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="`weights` may not contain negative values"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, xp.inf]) + if is_lazy_array(weights): + r, rssd = Rotation.align_vectors(a, b, weights) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Only one infinite weight is allowed"): + Rotation.align_vectors(a, b, weights) + + a, b = xp.asarray([[0, 0, 0]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd = Rotation.align_vectors(a, b) + assert xp.all(xp.isnan(r.as_quat())), "Quaternion should be nan" + assert xp.isnan(rssd), "RSSD should be nan" + else: + with pytest.raises(ValueError, + match="Cannot align zero length primary vectors"): + Rotation.align_vectors(a, b) + + a, b = xp.asarray([[1, 2, 3], [4, 5, 6]]), xp.asarray([[1, 2, 3], [4, 5, 6]]) + weights = xp.asarray([xp.inf, 1]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, weights, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, weights, return_sensitivity=True) + + a, b = xp.asarray([[1, 2, 3]]), xp.asarray([[1, 2, 3]]) + if is_lazy_array(a): + r, rssd, sens = Rotation.align_vectors(a, b, return_sensitivity=True) + assert xp.all(xp.isnan(sens)), "Sensitivity matrix should be nan" + else: + with pytest.raises(ValueError, + match="Cannot return sensitivity matrix"): + Rotation.align_vectors(a, b, return_sensitivity=True) + + +def test_align_vectors_align_constrain(xp): # Align the primary +X B axis with the primary +Y A axis, and rotate about # it such that the +Y B axis (residual of the [1, 1, 0] secondary b vector) # is aligned with the +Z A axis (residual of the [0, 1, 1] secondary a # vector) atol = 1e-12 - b = [[1, 0, 0], [1, 1, 0]] - a = [[0, 1, 0], [0, 1, 1]] - m_expected = np.array([[0, 0, 1], - [1, 0, 0], - [0, 1, 0]]) - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b), a, atol=atol) # Pri and sec align exactly - assert np.isclose(rssd, 0, atol=atol) + b = xp.asarray([[1, 0, 0], [1, 1, 0]]) + a = xp.asarray([[0.0, 1, 0], [0, 1, 1]]) + m_expected = xp.asarray([[0.0, 0, 1], + [1, 0, 0], + [0, 1, 0]]) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b), a, atol=atol) # Pri and sec align exactly + assert xpx.isclose(rssd, 0.0, atol=atol, xp=xp) # Do the same but with an inexact secondary rotation - b = [[1, 0, 0], [1, 2, 0]] + b = xp.asarray([[1, 0, 0], [1, 2, 0]]) rssd_expected = 1.0 - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly - assert np.isclose(rssd, rssd_expected, atol=atol) - a_expected = [[0, 1, 0], [0, 1, 2]] - assert_allclose(R.apply(b), a_expected, atol=atol) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) + a_expected = xp.asarray([[0.0, 1, 0], [0, 1, 2]]) + xp_assert_close(R.apply(b), a_expected, atol=atol) # Check random vectors - b = [[1, 2, 3], [-2, 3, -1]] - a = [[-1, 3, 2], [1, -1, 2]] + b = xp.asarray([[1, 2, 3], [-2, 3, -1]]) + a = xp.asarray([[-1.0, 3, 2], [1, -1, 2]]) rssd_expected = 1.3101595297515016 - R, rssd = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.apply(b)[0], a[0], atol=atol) # Only pri aligns exactly - assert np.isclose(rssd, rssd_expected, atol=atol) + R, rssd = Rotation.align_vectors(a, b, weights=xp.asarray([xp.inf, 1])) + xp_assert_close(R.apply(b)[0, ...], a[0, ...], atol=atol) # Only pri aligns exactly + assert xpx.isclose(rssd, rssd_expected, atol=atol, xp=xp) -def test_align_vectors_near_inf(): +def test_align_vectors_near_inf(xp): # align_vectors should return near the same result for high weights as for # infinite weights. rssd will be different with floating point error on the # exactly aligned vector being multiplied by a large non-infinite weight @@ -1491,58 +1722,60 @@ def test_align_vectors_near_inf(): for i in range(n): # Get random pairs of 3-element vectors - a = [1*mats[0][i][0], 2*mats[1][i][0]] - b = [3*mats[2][i][0], 4*mats[3][i][0]] + a = xp.asarray([1 * mats[0][i][0], 2 * mats[1][i][0]]) + b = xp.asarray([3 * mats[2][i][0], 4 * mats[3][i][0]]) R, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) - R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) for i in range(n): # Get random triplets of 3-element vectors - a = [1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]] - b = [4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]] + a = xp.asarray([1*mats[0][i][0], 2*mats[1][i][0], 3*mats[2][i][0]]) + b = xp.asarray([4*mats[3][i][0], 5*mats[4][i][0], 6*mats[5][i][0]]) R, _ = Rotation.align_vectors(a, b, weights=[1e10, 2, 1]) - R2, _ = Rotation.align_vectors(a, b, weights=[np.inf, 2, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=1e-4) + R2, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 2, 1]) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=1e-4) -def test_align_vectors_parallel(): +def test_align_vectors_parallel(xp): atol = 1e-12 - a = [[1, 0, 0], [0, 1, 0]] - b = [[0, 1, 0], [0, 1, 0]] - m_expected = np.array([[0, 1, 0], - [-1, 0, 0], - [0, 0, 1]]) - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - R, _ = Rotation.align_vectors(a[0], b[0]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) - - b = [[1, 0, 0], [1, 0, 0]] - m_expected = np.array([[1, 0, 0], - [0, 1, 0], - [0, 0, 1]]) - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - R, _ = Rotation.align_vectors(a[0], b[0]) - assert_allclose(R.as_matrix(), m_expected, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) - - -def test_align_vectors_antiparallel(): + a = xp.asarray([[1.0, 0, 0], [0, 1, 0]]) + b = xp.asarray([[0.0, 1, 0], [0, 1, 0]]) + m_expected = xp.asarray([[0.0, 1, 0], + [-1, 0, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + b = xp.asarray([[1, 0, 0], [1, 0, 0]]) + m_expected = xp.asarray([[1.0, 0, 0], + [0, 1, 0], + [0, 0, 1]]) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + R, _ = Rotation.align_vectors(a[0, ...], b[0, ...]) + xp_assert_close(R.as_matrix(), m_expected, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) + + +def test_align_vectors_antiparallel(xp): # Test exact 180 deg rotation atol = 1e-12 - as_to_test = np.array([[[1, 0, 0], [0, 1, 0]], + as_to_test = np.array([[[1.0, 0, 0], [0, 1, 0]], [[0, 1, 0], [1, 0, 0]], [[0, 0, 1], [0, 1, 0]]]) + bs_to_test = [[-a[0], a[1]] for a in as_to_test] for a, b in zip(as_to_test, bs_to_test): - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) - assert_allclose(R.magnitude(), np.pi, atol=atol) - assert_allclose(R.apply(b[0]), a[0], atol=atol) + a, b = xp.asarray(a), xp.asarray(b) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) + xp_assert_close(R.magnitude(), xp.pi, atol=atol) + xp_assert_close(R.apply(b[0, ...]), a[0, ...], atol=atol) # Test exact rotations near 180 deg Rs = Rotation.random(100, rng=0) @@ -1551,42 +1784,60 @@ def test_align_vectors_antiparallel(): b = [[-1, 0, 0], [0, 1, 0]] as_to_test = [] for dR in dRs: - as_to_test.append([dR.apply(a[0]), a[1]]) + as_to_test.append(np.array([dR.apply(a[0]), a[1]])) for a in as_to_test: - R, _ = Rotation.align_vectors(a, b, weights=[np.inf, 1]) + a, b = xp.asarray(a), xp.asarray(b) + R, _ = Rotation.align_vectors(a, b, weights=[xp.inf, 1]) R2, _ = Rotation.align_vectors(a, b, weights=[1e10, 1]) - assert_allclose(R.as_matrix(), R2.as_matrix(), atol=atol) + xp_assert_close(R.as_matrix(), R2.as_matrix(), atol=atol) -def test_align_vectors_primary_only(): +def test_align_vectors_primary_only(xp): atol = 1e-12 mats_a = Rotation.random(100, rng=0).as_matrix() mats_b = Rotation.random(100, rng=1).as_matrix() + for mat_a, mat_b in zip(mats_a, mats_b): # Get random 3-element unit vectors - a = mat_a[0] - b = mat_b[0] + a = xp.asarray(mat_a[0]) + b = xp.asarray(mat_b[0]) # Compare to align_vectors with primary only R, rssd = Rotation.align_vectors(a, b) - assert_allclose(R.apply(b), a, atol=atol) + xp_assert_close(R.apply(b), a, atol=atol) assert np.isclose(rssd, 0, atol=atol) -def test_repr_single_rotation(): - q = np.array([0, 0, 0, 1]) +def test_align_vectors_array_like(): + rng = np.random.default_rng(123) + c = Rotation.random(rng=rng) + b = rng.normal(size=(5, 3)) + a = c.apply(b) + + est_expected, rssd_expected = Rotation.align_vectors(a, b) + est, rssd = Rotation.align_vectors(a.tolist(), b.tolist()) + xp_assert_close(est_expected.as_quat(), est.as_quat()) + xp_assert_close(rssd, rssd_expected) + + +def test_repr_single_rotation(xp): + q = xp.asarray([0, 0, 0, 1]) actual = repr(Rotation.from_quat(q)) - expected = """\ + if is_numpy(xp): + expected = """\ Rotation.from_matrix(array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]]))""" - assert actual == expected + assert actual == expected + else: + assert actual.startswith("Rotation.from_matrix(") -def test_repr_rotation_sequence(): - q = np.array([[0, 1, 0, 1], [0, 0, 1, 1]]) / np.sqrt(2) +def test_repr_rotation_sequence(xp): + q = xp.asarray([[0.0, 1, 0, 1], [0, 0, 1, 1]]) / math.sqrt(2) actual = f"{Rotation.from_quat(q)!r}" - expected = """\ + if is_numpy(xp): + expected = """\ Rotation.from_matrix(array([[[ 0., 0., 1.], [ 0., 1., 0.], [-1., 0., 0.]], @@ -1594,204 +1845,242 @@ def test_repr_rotation_sequence(): [[ 0., -1., 0.], [ 1., 0., 0.], [ 0., 0., 1.]]]))""" - assert actual == expected + assert actual == expected + else: + assert actual.startswith("Rotation.from_matrix(") -def test_slerp(): +def test_slerp(xp): rnd = np.random.RandomState(0) - key_rots = Rotation.from_quat(rnd.uniform(size=(5, 4))) + key_rots = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) key_quats = key_rots.as_quat() key_times = [0, 1, 2, 3, 4] interpolator = Slerp(key_times, key_rots) + assert isinstance(interpolator.times, type(xp.asarray(0))) times = [0, 0.5, 0.25, 1, 1.5, 2, 2.75, 3, 3.25, 3.60, 4] interp_rots = interpolator(times) interp_quats = interp_rots.as_quat() # Dot products are affected by sign of quaternions - interp_quats[interp_quats[:, -1] < 0] *= -1 + mask = (interp_quats[:, -1] < 0)[:, None] + interp_quats = xp.where(mask, -interp_quats, interp_quats) # Checking for quaternion equality, perform same operation - key_quats[key_quats[:, -1] < 0] *= -1 + mask = (key_quats[:, -1] < 0)[:, None] + key_quats = xp.where(mask, -key_quats, key_quats) # Equality at keyframes, including both endpoints - assert_allclose(interp_quats[0], key_quats[0]) - assert_allclose(interp_quats[3], key_quats[1]) - assert_allclose(interp_quats[5], key_quats[2]) - assert_allclose(interp_quats[7], key_quats[3]) - assert_allclose(interp_quats[10], key_quats[4]) + xp_assert_close(interp_quats[0, ...], key_quats[0, ...]) + xp_assert_close(interp_quats[3, ...], key_quats[1, ...]) + xp_assert_close(interp_quats[5, ...], key_quats[2, ...]) + xp_assert_close(interp_quats[7, ...], key_quats[3, ...]) + xp_assert_close(interp_quats[10, ...], key_quats[4, ...]) # Constant angular velocity between keyframes. Check by equating # cos(theta) between quaternion pairs with equal time difference. - cos_theta1 = np.sum(interp_quats[0] * interp_quats[2]) - cos_theta2 = np.sum(interp_quats[2] * interp_quats[1]) - assert_allclose(cos_theta1, cos_theta2) + cos_theta1 = xp.sum(interp_quats[0, ...] * interp_quats[2, ...]) + cos_theta2 = xp.sum(interp_quats[2, ...] * interp_quats[1, ...]) + xp_assert_close(cos_theta1, cos_theta2) - cos_theta4 = np.sum(interp_quats[3] * interp_quats[4]) - cos_theta5 = np.sum(interp_quats[4] * interp_quats[5]) - assert_allclose(cos_theta4, cos_theta5) + cos_theta4 = xp.sum(interp_quats[3, ...] * interp_quats[4, ...]) + cos_theta5 = xp.sum(interp_quats[4, ...] * interp_quats[5, ...]) + xp_assert_close(cos_theta4, cos_theta5) # theta1: 0 -> 0.25, theta3 : 0.5 -> 1 # Use double angle formula for double the time difference - cos_theta3 = np.sum(interp_quats[1] * interp_quats[3]) - assert_allclose(cos_theta3, 2 * (cos_theta1**2) - 1) + cos_theta3 = xp.sum(interp_quats[1, ...] * interp_quats[3, ...]) + xp_assert_close(cos_theta3, 2 * (cos_theta1**2) - 1) # Miscellaneous checks assert_equal(len(interp_rots), len(times)) -def test_slerp_rot_is_rotation(): +def test_slerp_rot_is_rotation(xp): with pytest.raises(TypeError, match="must be a `Rotation` instance"): - r = np.array([[1,2,3,4], - [0,0,0,1]]) - t = np.array([0, 1]) + r = xp.asarray([[1,2,3,4], + [0,0,0,1]]) + t = xp.asarray([0, 1]) Slerp(t, r) + SLERP_EXCEPTION_MESSAGE = "must be a sequence of at least 2 rotations" -def test_slerp_single_rot(): - r = Rotation.from_quat([1, 2, 3, 4]) + +def test_slerp_single_rot(xp): + r = Rotation.from_quat(xp.asarray([[1.0, 2, 3, 4]])) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([1], r) -def test_slerp_rot_len0(): +def test_slerp_rot_len0(xp): r = Rotation.random() + r = Rotation.from_quat(xp.asarray(r.as_quat())) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([], r) -def test_slerp_rot_len1(): +def test_slerp_rot_len1(xp): r = Rotation.random(1) + r = Rotation.from_quat(xp.asarray(r.as_quat())) with pytest.raises(ValueError, match=SLERP_EXCEPTION_MESSAGE): Slerp([1], r) -def test_slerp_time_dim_mismatch(): +def test_slerp_time_dim_mismatch(xp): with pytest.raises(ValueError, match="times to be specified in a 1 dimensional array"): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(2, 4))) - t = np.array([[1], - [2]]) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(2, 4)))) + t = xp.asarray([[1], + [2]]) Slerp(t, r) -def test_slerp_num_rotations_mismatch(): +def test_slerp_num_rotations_mismatch(xp): with pytest.raises(ValueError, match="number of rotations to be equal to " "number of timestamps"): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(7) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(7) Slerp(t, r) -def test_slerp_equal_times(): - with pytest.raises(ValueError, match="strictly increasing order"): - rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = [0, 1, 2, 2, 4] - Slerp(t, r) +def test_slerp_equal_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 2, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) -def test_slerp_decreasing_times(): - with pytest.raises(ValueError, match="strictly increasing order"): - rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = [0, 1, 3, 2, 4] - Slerp(t, r) +def test_slerp_decreasing_times(xp): + rnd = np.random.RandomState(0) + q = xp.asarray(rnd.uniform(size=(5, 4))) + r = Rotation.from_quat(q) + t = [0, 1, 3, 2, 4] + if is_lazy_array(q): + s = Slerp(t, r) + assert xp.all(xp.isnan(s.times)) + else: + with pytest.raises(ValueError, match="strictly increasing order"): + Slerp(t, r) -def test_slerp_call_time_dim_mismatch(): +def test_slerp_call_time_dim_mismatch(xp): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(5) + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) s = Slerp(t, r) with pytest.raises(ValueError, match="`times` must be at most 1-dimensional."): - interp_times = np.array([[3.5], - [4.2]]) + interp_times = xp.asarray([[3.5], + [4.2]]) s(interp_times) -def test_slerp_call_time_out_of_range(): +def test_slerp_call_time_out_of_range(xp): rnd = np.random.RandomState(0) - r = Rotation.from_quat(rnd.uniform(size=(5, 4))) - t = np.arange(5) + 1 + r = Rotation.from_quat(xp.asarray(rnd.uniform(size=(5, 4)))) + t = xp.arange(5) + 1 s = Slerp(t, r) - with pytest.raises(ValueError, match="times must be within the range"): - s([0, 1, 2]) - with pytest.raises(ValueError, match="times must be within the range"): - s([1, 2, 6]) - - -def test_slerp_call_scalar_time(): - r = Rotation.from_euler('X', [0, 80], degrees=True) + times_low = xp.asarray([0, 1, 2]) + times_high = xp.asarray([1, 2, 6]) + if is_lazy_array(times_low): + q = s(times_low).as_quat() + in_range = xp.logical_and(times_low >= xp.min(t), times_low <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + q = s(times_high).as_quat() + in_range = xp.logical_and(times_high >= xp.min(t), times_high <= xp.max(t)) + assert xp.all(xp.isnan(q[~in_range, ...])) + assert xp.all(~xp.isnan(q[in_range, ...])) + else: + with pytest.raises(ValueError, match="times must be within the range"): + s(times_low) + with pytest.raises(ValueError, match="times must be within the range"): + s(times_high) + + +def test_slerp_call_scalar_time(xp): + r = Rotation.from_euler('X', xp.asarray([0, 80]), degrees=True) s = Slerp([0, 1], r) r_interpolated = s(0.25) - r_interpolated_expected = Rotation.from_euler('X', 20, degrees=True) + r_interpolated_expected = Rotation.from_euler('X', xp.asarray(20), degrees=True) delta = r_interpolated * r_interpolated_expected.inv() - assert_allclose(delta.magnitude(), 0, atol=1e-16) + assert xp.allclose(delta.magnitude(), 0, atol=1e-16) -def test_multiplication_stability(): +def test_multiplication_stability(xp): qs = Rotation.random(50, rng=0) + qs = Rotation.from_quat(xp.asarray(qs.as_quat())) rs = Rotation.random(1000, rng=1) + rs = Rotation.from_quat(xp.asarray(rs.as_quat())) + expected = xp.ones(len(rs)) for q in qs: rs *= q * rs - assert_allclose(np.linalg.norm(rs.as_quat(), axis=1), 1) + xp_assert_close(xp_vector_norm(rs.as_quat(), axis=1), expected) -def test_pow(): +def test_pow(xp): atol = 1e-14 p = Rotation.random(10, rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) p_inv = p.inv() # Test the short-cuts and other integers for n in [-5, -2, -1, 0, 1, 2, 5]: # Test accuracy q = p ** n r = Rotation.identity(10) + r = Rotation.from_quat(xp.asarray(r.as_quat())) for _ in range(abs(n)): if n > 0: r = r * p else: r = r * p_inv ang = (q * r.inv()).magnitude() - assert np.all(ang < atol) + assert xp.all(ang < atol) # Test shape preservation - r = Rotation.from_quat([0, 0, 0, 1]) + r = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) assert (r**n).as_quat().shape == (4,) - r = Rotation.from_quat([[0, 0, 0, 1]]) + r = Rotation.from_quat(xp.asarray([[0, 0, 0, 1]])) assert (r**n).as_quat().shape == (1, 4) # Large angle fractional for n in [-1.5, -0.5, -0.0, 0.0, 0.5, 1.5]: q = p ** n r = Rotation.from_rotvec(n * p.as_rotvec()) - assert_allclose(q.as_quat(), r.as_quat(), atol=atol) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) # Small angle - p = Rotation.from_rotvec([1e-12, 0, 0]) + p = Rotation.from_rotvec(xp.asarray([1e-12, 0, 0])) n = 3 q = p ** n r = Rotation.from_rotvec(n * p.as_rotvec()) - assert_allclose(q.as_quat(), r.as_quat(), atol=atol) + xp_assert_close(q.as_quat(), r.as_quat(), atol=atol) -def test_pow_errors(): +def test_pow_errors(xp): p = Rotation.random(rng=0) + p = Rotation.from_quat(xp.asarray(p.as_quat())) with pytest.raises(NotImplementedError, match='modulus not supported'): pow(p, 1, 1) def test_rotation_within_numpy_array(): + # TODO: Do we want to support this for all Array API frameworks? single = Rotation.random(rng=0) multiple = Rotation.random(2, rng=1) @@ -1800,8 +2089,8 @@ def test_rotation_within_numpy_array(): array = np.array(multiple) assert_equal(array.shape, (2,)) - assert_allclose(array[0].as_matrix(), multiple[0].as_matrix()) - assert_allclose(array[1].as_matrix(), multiple[1].as_matrix()) + xp_assert_close(array[0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[1].as_matrix(), multiple[1].as_matrix()) array = np.array([single]) assert_equal(array.shape, (1,)) @@ -1809,8 +2098,8 @@ def test_rotation_within_numpy_array(): array = np.array([multiple]) assert_equal(array.shape, (1, 2)) - assert_allclose(array[0, 0].as_matrix(), multiple[0].as_matrix()) - assert_allclose(array[0, 1].as_matrix(), multiple[1].as_matrix()) + xp_assert_close(array[0, 0].as_matrix(), multiple[0].as_matrix()) + xp_assert_close(array[0, 1].as_matrix(), multiple[1].as_matrix()) array = np.array([single, multiple], dtype=object) assert_equal(array.shape, (2,)) @@ -1821,20 +2110,25 @@ def test_rotation_within_numpy_array(): assert_equal(array.shape, (3, 2)) -def test_pickling(): - r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)]) +def test_pickling(xp): + # Note: Array API makes no provision for arrays to be pickleable, so + # it's OK to skip this test for the backends that don't support it + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) pkl = pickle.dumps(r) unpickled = pickle.loads(pkl) - assert_allclose(r.as_matrix(), unpickled.as_matrix(), atol=1e-15) + xp_assert_close(r.as_matrix(), unpickled.as_matrix(), atol=1e-15) -def test_deepcopy(): - r = Rotation.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)]) +def test_deepcopy(xp): + # Note: Array API makes no provision for arrays to support the `__copy__` + # protocol, so it's OK to skip this test for the backends that don't + r = Rotation.from_quat(xp.asarray([0, 0, math.sin(np.pi/4), math.cos(np.pi/4)])) r1 = copy.deepcopy(r) - assert_allclose(r.as_matrix(), r1.as_matrix(), atol=1e-15) + xp_assert_close(r.as_matrix(), r1.as_matrix(), atol=1e-15) def test_as_euler_contiguous(): + # The Array API does not specify contiguous arrays, so we can only check for NumPy r = Rotation.from_quat([0, 0, 0, 1]) e1 = r.as_euler('xyz') # extrinsic euler rotation e2 = r.as_euler('XYZ') # intrinsic @@ -1844,36 +2138,39 @@ def test_as_euler_contiguous(): assert all(i >= 0 for i in e2.strides) -def test_concatenate(): +def test_concatenate(xp): rotation = Rotation.random(10, rng=0) + rotation = Rotation.from_quat(xp.asarray(rotation.as_quat())) sizes = [1, 2, 3, 1, 3] starts = [0] + list(np.cumsum(sizes)) split = [rotation[i:i + n] for i, n in zip(starts, sizes)] result = Rotation.concatenate(split) - assert_equal(rotation.as_quat(), result.as_quat()) + xp_assert_equal(rotation.as_quat(), result.as_quat()) # Test Rotation input for multiple rotations result = Rotation.concatenate(rotation) - assert_equal(rotation.as_quat(), result.as_quat()) + xp_assert_equal(rotation.as_quat(), result.as_quat()) # Test that a copy is returned assert rotation is not result # Test Rotation input for single rotations - result = Rotation.concatenate(Rotation.identity()) - assert_equal(Rotation.identity().as_quat(), result.as_quat()) + rot = Rotation.from_quat(xp.asarray(Rotation.identity().as_quat())) + result = Rotation.concatenate(rot) + xp_assert_equal(rot.as_quat(), result.as_quat()) -def test_concatenate_wrong_type(): +def test_concatenate_wrong_type(xp): with pytest.raises(TypeError, match='Rotation objects only'): - Rotation.concatenate([Rotation.identity(), 1, None]) + rot = Rotation(xp.asarray(Rotation.identity().as_quat())) + Rotation.concatenate([rot, 1, None]) # Regression test for gh-16663 -def test_len_and_bool(): - rotation_multi_one = Rotation([[0, 0, 0, 1]]) - rotation_multi = Rotation([[0, 0, 0, 1], [0, 0, 0, 1]]) - rotation_single = Rotation([0, 0, 0, 1]) +def test_len_and_bool(xp): + rotation_multi_one = Rotation(xp.asarray([[0, 0, 0, 1]])) + rotation_multi = Rotation(xp.asarray([[0, 0, 0, 1], [0, 0, 0, 1]])) + rotation_single = Rotation(xp.asarray([0, 0, 0, 1])) assert len(rotation_multi_one) == 1 assert len(rotation_multi) == 2 @@ -1886,61 +2183,93 @@ def test_len_and_bool(): assert rotation_single -def test_from_davenport_single_rotation(): - axis = [0, 0, 1] +def test_from_davenport_single_rotation(xp): + axis = xp.asarray([0, 0, 1]) quat = Rotation.from_davenport(axis, 'extrinsic', 90, degrees=True).as_quat() - expected_quat = np.array([0, 0, 1, 1]) / np.sqrt(2) - assert_allclose(quat, expected_quat) + expected_quat = xp.asarray([0.0, 0, 1, 1]) / math.sqrt(2) + xp_assert_close(quat, expected_quat) -def test_from_davenport_one_or_two_axes(): - ez = [0, 0, 1] - ey = [0, 1, 0] +def test_from_davenport_one_or_two_axes(xp): + ez = xp.asarray([0.0, 0, 1]) + ey = xp.asarray([0.0, 1, 0]) # Single rotation, single axis, axes.shape == (3, ) - rot = Rotation.from_rotvec(np.array(ez) * np.pi/4) - rot_dav = Rotation.from_davenport(ez, 'e', np.pi/4) - assert_allclose(rot.as_quat(canonical=True), + rot = Rotation.from_rotvec(ez * xp.pi/4) + rot_dav = Rotation.from_davenport(ez, 'e', xp.pi/4) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Single rotation, single axis, axes.shape == (1, 3) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/4]) - rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes = xp.reshape(ez, (1, 3)) # Torch can't create tensors from xp.asarray([ez]) + rot = Rotation.from_rotvec(axes * xp.pi/4) + rot_dav = Rotation.from_davenport(axes, 'e', [xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Single rotation, two axes, axes.shape == (2, 3) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/4, - np.array(ey) * np.pi/6]) + axes = xp.stack([ez, ey], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/4], [xp.pi/6]])) rot = rot[0] * rot[1] - rot_dav = Rotation.from_davenport([ey, ez], 'e', [np.pi/6, np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes_dav = xp.stack([ey, ez], axis=0) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) # Two rotations, single axis, axes.shape == (3, ) - rot = Rotation.from_rotvec([np.array(ez) * np.pi/6, - np.array(ez) * np.pi/4]) - rot_dav = Rotation.from_davenport([ez], 'e', [np.pi/6, np.pi/4]) - assert_allclose(rot.as_quat(canonical=True), + axes = xp.stack([ez, ez], axis=0) + rot = Rotation.from_rotvec(axes * xp.asarray([[xp.pi/6], [xp.pi/4]])) + axes_dav = xp.reshape(ez, (1, 3)) + rot_dav = Rotation.from_davenport(axes_dav, 'e', [xp.pi/6, xp.pi/4]) + xp_assert_close(rot.as_quat(canonical=True), rot_dav.as_quat(canonical=True)) -def test_from_davenport_invalid_input(): +def test_from_davenport_invalid_input(xp): ez = [0, 0, 1] ey = [0, 1, 0] ezy = [0, 1, 1] - with pytest.raises(ValueError, match="must be orthogonal"): - Rotation.from_davenport([ez, ezy], 'e', [0, 0]) - with pytest.raises(ValueError, match="must be orthogonal"): - Rotation.from_davenport([ez, ey, ezy], 'e', [0, 0, 0]) + # We can only raise in non-lazy frameworks. + axes = xp.asarray([ez, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0]) + axes = xp.asarray([ez, ey, ezy]) + if is_lazy_array(axes): + q = Rotation.from_davenport(axes, 'e', [0, 0, 0]).as_quat() + assert xp.all(xp.isnan(q)) + else: + with pytest.raises(ValueError, match="must be orthogonal"): + Rotation.from_davenport(axes, 'e', [0, 0, 0]) with pytest.raises(ValueError, match="order should be"): - Rotation.from_davenport([ez], 'xyz', [0]) + Rotation.from_davenport(xp.asarray([ez]), 'xyz', [0]) with pytest.raises(ValueError, match="Expected `angles`"): - Rotation.from_davenport([ez, ey, ez], 'e', [0, 1, 2, 3]) + Rotation.from_davenport(xp.asarray([ez, ey, ez]), 'e', [0, 1, 2, 3]) -def test_as_davenport(): +def test_from_davenport_array_like(): + rng = np.random.default_rng(123) + # Single rotation + e1 = np.array([1, 0, 0]) + e2 = np.array([0, 1, 0]) + e3 = np.array([0, 0, 1]) + r_expected = Rotation.random(rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert r_expected.approx_equal(r, atol=1e-12) + + # Multiple rotations + r_expected = Rotation.random(2, rng=rng) + angles = r_expected.as_davenport([e1, e2, e3], 'e') + r = Rotation.from_davenport([e1, e2, e3], 'e', angles.tolist()) + assert np.all(r_expected.approx_equal(r, atol=1e-12)) + + +def test_as_davenport(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -1949,21 +2278,22 @@ def test_as_davenport(): angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) lambdas = rnd.uniform(low=0, high=np.pi, size=(20,)) - e1 = np.array([1, 0, 0]) - e2 = np.array([0, 1, 0]) + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) for lamb in lambdas: - ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)] + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) angles[:, 1] = angles_middle - lamb for order in ['extrinsic', 'intrinsic']: - ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1] - rot = Rotation.from_davenport(ax, order, angles) - angles_dav = rot.as_davenport(ax, order) - assert_allclose(angles_dav, angles) + ax = ax_lamb if order == "intrinsic" else xp.flip(ax_lamb, axis=0) + rot = Rotation.from_davenport(xp.asarray(ax), order, angles) + angles_dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(angles_dav, xp.asarray(angles)) @pytest.mark.thread_unsafe -def test_as_davenport_degenerate(): +def test_as_davenport_degenerate(xp): # Since we cannot check for angle equality, we check for rotation matrix # equality rnd = np.random.RandomState(0) @@ -1976,23 +2306,25 @@ def test_as_davenport_degenerate(): angles[:, 2] = rnd.uniform(low=-np.pi, high=np.pi, size=(n,)) lambdas = rnd.uniform(low=0, high=np.pi, size=(5,)) - e1 = np.array([1, 0, 0]) - e2 = np.array([0, 1, 0]) + e1 = xp.asarray([1.0, 0, 0]) + e2 = xp.asarray([0.0, 1, 0]) for lamb in lambdas: - ax_lamb = [e1, e2, Rotation.from_rotvec(lamb*e2).apply(e1)] + e3 = xp.asarray(Rotation.from_rotvec(lamb*e2).apply(e1)) + ax_lamb = xp.stack([e1, e2, e3], axis=0) angles[:, 1] = angles_middle - lamb for order in ['extrinsic', 'intrinsic']: ax = ax_lamb if order == 'intrinsic' else ax_lamb[::-1] - rot = Rotation.from_davenport(ax, order, angles) - with pytest.warns(UserWarning, match="Gimbal lock"): - angles_dav = rot.as_davenport(ax, order) + rot = Rotation.from_davenport(xp.asarray(ax), order, angles) + with eager_warns(rot, UserWarning, match="Gimbal lock"): + angles_dav = rot.as_davenport(xp.asarray(ax), order) mat_expected = rot.as_matrix() - mat_estimated = Rotation.from_davenport(ax, order, angles_dav).as_matrix() - assert_array_almost_equal(mat_expected, mat_estimated) + rot_estimated = Rotation.from_davenport(xp.asarray(ax), order, angles_dav) + mat_estimated = rot_estimated.as_matrix() + xp_assert_close(mat_expected, mat_estimated, atol=1e-12) -def test_compare_from_davenport_from_euler(): +def test_compare_from_davenport_from_euler(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -2007,9 +2339,9 @@ def test_compare_from_davenport_from_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - eul = Rotation.from_euler(seq, angles) - dav = Rotation.from_davenport(ax, order, angles) - assert_allclose(eul.as_quat(canonical=True), dav.as_quat(canonical=True), + eul = Rotation.from_euler(seq, xp.asarray(angles)) + dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles)) + xp_assert_close(eul.as_quat(canonical=True), dav.as_quat(canonical=True), rtol=1e-12) # asymmetric sequences @@ -2020,12 +2352,12 @@ def test_compare_from_davenport_from_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - eul = Rotation.from_euler(seq, angles) - dav = Rotation.from_davenport(ax, order, angles) - assert_allclose(eul.as_quat(), dav.as_quat(), rtol=1e-12) + eul = Rotation.from_euler(seq, xp.asarray(angles)) + dav = Rotation.from_davenport(xp.asarray(ax), order, xp.asarray(angles)) + xp_assert_close(eul.as_quat(), dav.as_quat(), rtol=1e-12) -def test_compare_as_davenport_as_euler(): +def test_compare_as_davenport_as_euler(xp): rnd = np.random.RandomState(0) n = 100 angles = np.empty((n, 3)) @@ -2040,10 +2372,10 @@ def test_compare_as_davenport_as_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - rot = Rotation.from_euler(seq, angles) + rot = Rotation.from_euler(seq, xp.asarray(angles)) eul = rot.as_euler(seq) - dav = rot.as_davenport(ax, order) - assert_allclose(eul, dav, rtol=1e-12) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) # asymmetric sequences angles[:, 1] -= np.pi / 2 @@ -2053,13 +2385,13 @@ def test_compare_as_davenport_as_euler(): ax = [basis_vec(i) for i in seq] if order == 'intrinsic': seq = seq.upper() - rot = Rotation.from_euler(seq, angles) + rot = Rotation.from_euler(seq, xp.asarray(angles)) eul = rot.as_euler(seq) - dav = rot.as_davenport(ax, order) - assert_allclose(eul, dav, rtol=1e-12) + dav = rot.as_davenport(xp.asarray(ax), order) + xp_assert_close(eul, dav, rtol=1e-12) -def test_zero_rotation_construction(): +def test_zero_rotation_construction(xp): r = Rotation.random(num=0) assert len(r) == 0 @@ -2069,68 +2401,69 @@ def test_zero_rotation_construction(): r_get = Rotation.random(num=3)[[]] assert len(r_get) == 0 - r_quat = Rotation.from_quat(np.zeros((0, 4))) + r_quat = Rotation.from_quat(xp.zeros((0, 4))) assert len(r_quat) == 0 - r_matrix = Rotation.from_matrix(np.zeros((0, 3, 3))) + r_matrix = Rotation.from_matrix(xp.zeros((0, 3, 3))) assert len(r_matrix) == 0 - r_euler = Rotation.from_euler("xyz", np.zeros((0, 3))) + r_euler = Rotation.from_euler("xyz", xp.zeros((0, 3))) assert len(r_euler) == 0 - r_vec = Rotation.from_rotvec(np.zeros((0, 3))) + r_vec = Rotation.from_rotvec(xp.zeros((0, 3))) assert len(r_vec) == 0 - r_dav = Rotation.from_davenport(np.eye(3), "extrinsic", np.zeros((0, 3))) + r_dav = Rotation.from_davenport(xp.eye(3), "extrinsic", xp.zeros((0, 3))) assert len(r_dav) == 0 - r_mrp = Rotation.from_mrp(np.zeros((0, 3))) + r_mrp = Rotation.from_mrp(xp.zeros((0, 3))) assert len(r_mrp) == 0 -def test_zero_rotation_representation(): - r = Rotation.random(num=0) +def test_zero_rotation_representation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) assert r.as_quat().shape == (0, 4) assert r.as_matrix().shape == (0, 3, 3) assert r.as_euler("xyz").shape == (0, 3) assert r.as_rotvec().shape == (0, 3) assert r.as_mrp().shape == (0, 3) - assert r.as_davenport(np.eye(3), "extrinsic").shape == (0, 3) + assert r.as_davenport(xp.eye(3), "extrinsic").shape == (0, 3) -def test_zero_rotation_array_rotation(): - r = Rotation.random(num=0) +def test_zero_rotation_array_rotation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - v = np.array([1, 2, 3]) + v = xp.asarray([1, 2, 3]) v_rotated = r.apply(v) assert v_rotated.shape == (0, 3) - v0 = np.zeros((0, 3)) + v0 = xp.zeros((0, 3)) v0_rot = r.apply(v0) assert v0_rot.shape == (0, 3) - v2 = np.ones((2, 3)) + v2 = xp.ones((2, 3)) with pytest.raises( ValueError, match="Expected equal numbers of rotations and vectors"): r.apply(v2) -def test_zero_rotation_multiplication(): - r = Rotation.random(num=0) +def test_zero_rotation_multiplication(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - r_single = Rotation.random() + r_single = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) r_mult_left = r * r_single assert len(r_mult_left) == 0 r_mult_right = r_single * r assert len(r_mult_right) == 0 - r0 = Rotation.random(0) + r0 = Rotation.from_quat(xp.zeros((0, 4))) r_mult = r * r0 assert len(r_mult) == 0 msg_rotation_error = "Expected equal number of rotations" r2 = Rotation.random(2) + r2 = Rotation.from_quat(xp.asarray(r2.as_quat())) with pytest.raises(ValueError, match=msg_rotation_error): r0 * r2 @@ -2138,55 +2471,62 @@ def test_zero_rotation_multiplication(): r2 * r0 -def test_zero_rotation_concatentation(): - r = Rotation.random(num=0) +def test_zero_rotation_concatentation(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) r0 = Rotation.concatenate([r, r]) assert len(r0) == 0 - r1 = r.concatenate([Rotation.random(), r]) + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + r1 = r.concatenate([r1, r]) assert len(r1) == 1 - r3 = r.concatenate([Rotation.random(3), r]) + r3 = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat())) + r3 = r.concatenate([r3, r]) assert len(r3) == 3 - r4 = r.concatenate([r, Rotation.random(4)]) + r4 = Rotation.from_quat(xp.asarray(Rotation.random(4).as_quat())) + r4 = r.concatenate([r, r4]) + r4 = r.concatenate([r, r4]) assert len(r4) == 4 -def test_zero_rotation_power(): - r = Rotation.random(num=0) +def test_zero_rotation_power(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) for pp in [-1.5, -1, 0, 1, 1.5]: pow0 = r**pp assert len(pow0) == 0 -def test_zero_rotation_inverse(): - r = Rotation.random(num=0) +def test_zero_rotation_inverse(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) r_inv = r.inv() assert len(r_inv) == 0 -def test_zero_rotation_magnitude(): - r = Rotation.random(num=0) +def test_zero_rotation_magnitude(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) magnitude = r.magnitude() assert magnitude.shape == (0,) -def test_zero_rotation_mean(): - r = Rotation.random(num=0) +def test_zero_rotation_mean(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) with pytest.raises(ValueError, match="Mean of an empty rotation set is undefined."): r.mean() -def test_zero_rotation_approx_equal(): - r = Rotation.random(0) - assert r.approx_equal(Rotation.random(0)).shape == (0,) - assert r.approx_equal(Rotation.random()).shape == (0,) - assert Rotation.random().approx_equal(r).shape == (0,) +def test_zero_rotation_approx_equal(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) + r0 = Rotation.from_quat(xp.zeros((0, 4))) + assert r.approx_equal(r0).shape == (0,) + r1 = Rotation.from_quat(xp.asarray([0.0, 0, 0, 1])) + assert r.approx_equal(r1).shape == (0,) + r2 = Rotation.from_quat(xp.asarray(Rotation.random().as_quat())) + assert r2.approx_equal(r).shape == (0,) approx_msg = "Expected equal number of rotations" - r3 = Rotation.random(2) + r3 = Rotation.from_quat(xp.asarray(Rotation.random(2).as_quat())) with pytest.raises(ValueError, match=approx_msg): r.approx_equal(r3) @@ -2194,36 +2534,36 @@ def test_zero_rotation_approx_equal(): r3.approx_equal(r) -def test_zero_rotation_get_set(): - r = Rotation.random(0) +def test_zero_rotation_get_set(xp): + r = Rotation.from_quat(xp.zeros((0, 4))) - r_get = r[[]] + r_get = r[xp.asarray([], dtype=xp.bool)] assert len(r_get) == 0 r_slice = r[:0] assert len(r_slice) == 0 with pytest.raises(IndexError): - r[[0]] + r[xp.asarray([0])] with pytest.raises(IndexError): - r[[True]] + r[xp.asarray([True])] with pytest.raises(IndexError): - r[0] = Rotation.random() + r[0] = Rotation.from_quat(xp.asarray([0, 0, 0, 1])) -def test_boolean_indexes(): - r = Rotation.random(3) +def test_boolean_indexes(xp): + r = Rotation.from_quat(xp.asarray(Rotation.random(3).as_quat())) - r0 = r[[False, False, False]] + r0 = r[xp.asarray([False, False, False])] assert len(r0) == 0 - r1 = r[[False, True, False]] + r1 = r[xp.asarray([False, True, False])] assert len(r1) == 1 - r3 = r[[True, True, True]] + r3 = r[xp.asarray([True, True, True])] assert len(r3) == 3 with pytest.raises(IndexError): - r[[True, True]] + r[xp.asarray([True, True])] diff --git a/scipy/spatial/transform/tests/test_rotation_spline.py b/scipy/spatial/transform/tests/test_rotation_spline.py index 6441431f2fb5..316121b8f06b 100644 --- a/scipy/spatial/transform/tests/test_rotation_spline.py +++ b/scipy/spatial/transform/tests/test_rotation_spline.py @@ -1,7 +1,7 @@ from itertools import product import numpy as np from numpy.testing import assert_allclose -from pytest import raises +import pytest from scipy.spatial.transform import Rotation, RotationSpline from scipy.spatial.transform._rotation_spline import ( _angular_rate_to_rotvec_dot_matrix, @@ -11,6 +11,9 @@ _create_block_3_diagonal_matrix) +pytestmark = pytest.mark.skip_xp_backends(np_only=True) + + def test_angular_rate_to_rotvec_conversions(): np.random.seed(0) rv = np.random.randn(4, 3) @@ -141,22 +144,40 @@ def test_spline_properties(): def test_error_handling(): - raises(ValueError, RotationSpline, [1.0], Rotation.random()) + with pytest.raises(ValueError): + RotationSpline([1.0], Rotation.random()) r = Rotation.random(10) t = np.arange(10).reshape(5, 2) - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(9) - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(10) t[5] = 0 - raises(ValueError, RotationSpline, t, r) + with pytest.raises(ValueError): + RotationSpline(t, r) t = np.arange(10) s = RotationSpline(t, r) - raises(ValueError, s, 10, -1) - - raises(ValueError, s, np.arange(10).reshape(5, 2)) + with pytest.raises(ValueError): + s(10, -1) + + with pytest.raises(ValueError): + s(np.arange(10).reshape(5, 2)) + + +@pytest.mark.skip_xp_backends("numpy") +def test_xp_errors(xp): + times = xp.asarray([0, 10]) + r = Rotation.random(2) + r = Rotation.from_quat(xp.asarray(r.as_quat())) + s = RotationSpline(times, r) + t = xp.asarray([0.5, 1.5]) + # RotationSpline does not have native Array API support, so we check that it + # converts any array to NumPy and outputs NumPy arrays. + assert isinstance(s(t).as_quat(), np.ndarray) From df944bedb81d42152885b59492e94b6207c62ac6 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 10 May 2025 18:27:53 +1000 Subject: [PATCH 169/251] DOC: outline that not all attributes of OptimizeResult may be present --- scipy/optimize/_minimize.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 6e4b2495643d..8005cf5bc1d9 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -218,10 +218,10 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, where ``intermediate_result`` is a keyword parameter containing an `OptimizeResult` with attributes ``x`` and ``fun``, the present values - of the parameter vector and objective function. Note that the name - of the parameter must be ``intermediate_result`` for the callback - to be passed an `OptimizeResult`. These methods will also terminate if - the callback raises ``StopIteration``. + of the parameter vector and objective function. Not all attributes within + `OptimizeResult` may be present. The name of the parameter must be + ``intermediate_result`` for the callback to be passed an `OptimizeResult`. + These methods will also terminate if the callback raises ``StopIteration``. All methods except trust-constr (also) support a signature like:: From 23c356822cdbf1da5c471fcaac930c4a43e39c18 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 10 May 2025 19:02:41 +1000 Subject: [PATCH 170/251] Update scipy/optimize/_minimize.py --- scipy/optimize/_minimize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 8005cf5bc1d9..58e765f97b82 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -218,7 +218,7 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, where ``intermediate_result`` is a keyword parameter containing an `OptimizeResult` with attributes ``x`` and ``fun``, the present values - of the parameter vector and objective function. Not all attributes within + of the parameter vector and objective function. Not all attributes of `OptimizeResult` may be present. The name of the parameter must be ``intermediate_result`` for the callback to be passed an `OptimizeResult`. These methods will also terminate if the callback raises ``StopIteration``. From af3f28bbaf4820456cb53f7f2ad548a3901ae523 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Sat, 10 May 2025 20:03:34 -0600 Subject: [PATCH 171/251] MAINT: stats.DiscreteDistribution: fix inversion methods --- scipy/stats/_distribution_infrastructure.py | 39 +++++++++++++++------ scipy/stats/tests/test_continuous.py | 21 ++++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 13b8380c5a95..ec889571c838 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -2141,7 +2141,7 @@ def f2(x, _p, **kwargs): # named `_p` to avoid conflict with shape `p` xrtol = None if _isnull(self.tol) else self.tol xatol = None if xatol is None else xatol - tolerances = dict(xrtol=xrtol, xatol=xatol) + tolerances = dict(xrtol=xrtol, xatol=xatol, fatol=0, frtol=0) return _chandrupatla(f3, a=res.xl, b=res.xr, args=args, **tolerances) ## Other @@ -2889,7 +2889,7 @@ def _ilogcdf_complement(self, x, **params): return self._ilogccdf_dispatch(_log1mexp(x), **params) def _ilogcdf_inversion(self, x, **params): - return self._solve_bounded(self._logcdf_dispatch, x, params=params).x + return self._solve_bounded_continuous(self._logcdf_dispatch, x, params=params) @_set_invalid_nan def icdf(self, p, /, *, method=None): @@ -2924,7 +2924,7 @@ def _icdf_complement_safe(self, x, **params): return out[()] def _icdf_inversion(self, x, **params): - return self._solve_bounded(self._cdf_dispatch, x, params=params).x + return self._solve_bounded_continuous(self._cdf_dispatch, x, params=params) @_set_invalid_nan def ilogccdf(self, logp, /, *, method=None): @@ -2947,7 +2947,7 @@ def _ilogccdf_complement(self, x, **params): return self._ilogcdf_dispatch(_log1mexp(x), **params) def _ilogccdf_inversion(self, x, **params): - return self._solve_bounded(self._logccdf_dispatch, x, params=params).x + return self._solve_bounded_continuous(self._logccdf_dispatch, x, params=params) @_set_invalid_nan def iccdf(self, p, /, *, method=None): @@ -2982,7 +2982,7 @@ def _iccdf_complement_safe(self, x, **params): return out[()] def _iccdf_inversion(self, x, **params): - return self._solve_bounded(self._ccdf_dispatch, x, params=params).x + return self._solve_bounded_continuous(self._ccdf_dispatch, x, params=params) ### Sampling Functions # The following functions for drawing samples from the distribution are @@ -3614,6 +3614,9 @@ def _pxf_dispatch(self, x, *, method=None, **params): def _logpxf_dispatch(self, x, *, method=None, **params): return self._logpdf_dispatch(x, method=method, **params) + def _solve_bounded_continuous(self, func, p, params, xatol=None): + return self._solve_bounded(func, p, params=params, xatol=xatol).x + class DiscreteDistribution(UnivariateDistribution): def _overrides(self, method_name): @@ -3675,25 +3678,41 @@ def _logccdf2(self, x, y, *, method): "Two argument cdf functions are currently only supported for " "continuous distributions.") + def _solve_bounded_discrete(self, func, p, params, comp): + res = self._solve_bounded(func, p, params=params, xatol=0.9) + x = np.asarray(np.floor(res.xr)) + + # if _chandrupatla finds exact inverse, the bracket may not have been reduced + # enough for `np.floor(res.x)` to be the appropriate value of `x`. + mask = res.fun == 0 + x[mask] = np.floor(res.x[mask]) + + xmin, xmax = self._support(**params) + p, xmin, xmax = np.broadcast_arrays(p, xmin, xmax) + mask = comp(func(xmin, **params), p) + x[mask] = xmin[mask] + + return x + def _base_discrete_inversion(self, p, func, comp, /, **params): # For discrete distributions, icdf(p) is defined as the minimum n # such that cdf(n) >= p. iccdf(p) is defined as the minimum n such # that ccdf(n) <= p, or equivalently as iccdf(p) = icdf(1 - p). - res = self._solve_bounded(func, p, params=params, xatol=0.9) # First try to find where cdf(x) == p for the continuous extension of the # cdf. res.xl and res.xr will be a bracket for this root. The parameter # xatol in solve_bounded controls the bracket width. We thus know that # know cdf(res.xr) >= p, cdf(res.xl) <= p, and |res.xr - res.xl| <= 0.9. # This means the minimum integer n such that cdf(n) >= p is either floor(x) # or floor(x) + 1. - res, fr = np.asarray(np.floor(res.xr)), res.fr + x = self._solve_bounded_discrete(func, p, params=params, comp=comp) + # comp should be <= for ccdf, >= for cdf. + f = func(x, **params) + res = np.where(comp(f, p), x, x + 1.0) # xr is a bracket endpoint, and will usually be a finite value even when # the computed result should be nan. We need to explicitly handle this # case. - res[np.isnan(fr)] = np.nan - # comp should be <= for ccdf, >= for cdf. - res = np.where(comp(func(res, **params), p), res, res + 1.0) + res[np.isnan(f) | np.isnan(p)] = np.nan return res[()] def _icdf_inversion(self, x, **params): diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 5132d54eebf8..29c453930ff7 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1140,7 +1140,6 @@ def test_rv_generic(self, i, distdata): 3: {'pareto'}, # stats.pareto is just wrong 4: {'invgamma'}} # tolerance issue skip_standardized = {'exponpow', 'ksone'} # tolerances - skip_median = {'nhypergeom', 'yulesimon'} # nan mismatch dist = getattr(stats, distname) params = dict(zip(dist.shapes.split(', '), distdata[1])) if dist.shapes else {} @@ -1162,8 +1161,7 @@ def test_rv_generic(self, i, distdata): # some continuous distributions have trouble with `logentropy` because # it uses complex numbers assert_allclose(np.exp(X.logentropy()), Y.entropy(), rtol=rtol) - if distname not in skip_median: - assert_allclose(X.median(), Y.median(), rtol=rtol) + assert_allclose(X.median(), Y.median(), rtol=rtol) assert_allclose(X.mean(), m, rtol=rtol, atol=atol) assert_allclose(X.variance(), v, rtol=rtol, atol=atol) if distname not in skip_skewness: @@ -1182,11 +1180,18 @@ def test_rv_generic(self, i, distdata): if distname not in skip_logccdf: assert_allclose(X.logccdf(x), Y.logsf(x), rtol=rtol) assert_allclose(X.ccdf(x), Y.sf(x), rtol=rtol) - if isinstance(dist, stats.rv_continuous): - # For discrete distributions, these won't agree at the far left end - # of the support, and the new infrastructure is slow there (for now). - assert_allclose(X.icdf(p), Y.ppf(p), rtol=rtol) - assert_allclose(X.iccdf(p), Y.isf(p), rtol=rtol) + + # old infrastructure convention for ppf(p=0) and isf(p=1) is different than + # new infrastructure. Adjust reference values accordingly. + a, _ = Y.support() + ref_ppf = Y.ppf(p) + ref_ppf[p == 0] = a + ref_isf = Y.isf(p) + ref_isf[p == 1] = a + + assert_allclose(X.icdf(p), ref_ppf, rtol=rtol) + assert_allclose(X.iccdf(p), ref_isf, rtol=rtol) + for order in range(5): if distname not in skip_raw.get(order, {}): assert_allclose(X.moment(order, kind='raw'), From 9320e0dfe2b81d79626883f8a93131e8ac1e2a49 Mon Sep 17 00:00:00 2001 From: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Date: Mon, 12 May 2025 15:30:21 +0200 Subject: [PATCH 172/251] MAINT: stats.wrapcauchy.rvs: wrap between 0 and 2*pi (#22963) --- scipy/stats/_continuous_distns.py | 4 ++++ scipy/stats/tests/test_distributions.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index b9c10e2abf61..e7c6307b8f90 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -11381,6 +11381,10 @@ def _fitstart(self, data): data = data._uncensor() return 0.5, np.min(data), np.ptp(data)/(2*np.pi) + @inherit_docstring_from(rv_continuous) + def rvs(self, *args, **kwds): + rvs = super().rvs(*args, **kwds) + return np.mod(rvs, 2*np.pi) wrapcauchy = wrapcauchy_gen(a=0.0, b=2*np.pi, name='wrapcauchy') diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py index f5673437b8ec..e2bf87b6b8a1 100644 --- a/scipy/stats/tests/test_distributions.py +++ b/scipy/stats/tests/test_distributions.py @@ -9951,6 +9951,15 @@ def test_cdf(self): assert_allclose(p[0], np.arctan(cr*np.tan(x1/2))/np.pi) assert_allclose(p[1], 1 - np.arctan(cr*np.tan(np.pi - x2/2))/np.pi) + @pytest.mark.parametrize('c', [1e-10, 1e-1]) + @pytest.mark.parametrize('loc', [-100, -2*np.pi, -np.pi, 0, np.pi, 2*np.pi, 100]) + @pytest.mark.parametrize('scale', [1e-10, 1, 1e10]) + def test_rvs_lie_on_circle(self, c, loc, scale): + # Check that the random variates lie in range [0, 2*pi] + x = stats.wrapcauchy.rvs(c=c, loc=loc, scale=scale, size=1000) + assert np.all(x >= 0) + assert np.all(x <= 2 * np.pi) + def test_rvs_no_size_error(): # _rvs methods must have parameter `size`; see gh-11394 From 0e111ff11a56d294402ff0c1211e46a55bbc6209 Mon Sep 17 00:00:00 2001 From: Julia Tatz Date: Tue, 13 May 2025 00:48:31 -0400 Subject: [PATCH 173/251] ENH: stats: Implement _munp for gennorm (#22976) --- scipy/stats/_continuous_distns.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py index e7c6307b8f90..52208164a2ff 100644 --- a/scipy/stats/_continuous_distns.py +++ b/scipy/stats/_continuous_distns.py @@ -11457,6 +11457,15 @@ def _sf(self, x, beta): def _isf(self, x, beta): return -self._ppf(x, beta) + def _munp(self, n, beta): + if n == 0: + return 1. + if n % 2 == 0: + c1, cn = sc.gammaln([1.0/beta, (n + 1.0)/beta]) + return np.exp(cn - c1) + else: + return 0. + def _stats(self, beta): c1, c3, c5 = sc.gammaln([1.0/beta, 3.0/beta, 5.0/beta]) return 0., np.exp(c3 - c1), 0., np.exp(c5 + c1 - 2.0*c3) - 3. From e9d03b00e546cde17040a43f6bac6bb5949857b1 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 13 May 2025 05:23:19 -0700 Subject: [PATCH 174/251] ENH: stats.rankdata: add array API standard support (#20639) * ENH: stats.rankdata: draft array API support * TST: stats.rankdata: add array API support * TST: stats: fix failing tests * MAINT: stats.rankdata: modernizations * MAINT: stats.rankdata: updates per self-review / CI results * MAINT: stats.rankdata: revisions per review * MAINT: stats.rankdata: run tests with jax_jit=False * Update scipy/stats/_stats_py.py Co-authored-by: Matt Haberland --------- Co-authored-by: Lucas Colley --- scipy/_lib/_array_api.py | 9 ++ scipy/stats/_stats_py.py | 83 +++++++----- scipy/stats/tests/test_rank.py | 231 +++++++++++++++++---------------- 3 files changed, 179 insertions(+), 144 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 39360a6bf26b..8641b7ded8c9 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -490,6 +490,15 @@ def xp_ravel(x: Array, /, *, xp: ModuleType | None = None) -> Array: return xp.reshape(x, (-1,)) +def xp_swapaxes(a, axis1, axis2, xp=None): + # Equivalent of np.swapaxes written in terms of array API + xp = array_namespace(a) if xp is None else xp + axes = list(range(a.ndim)) + axes[axis1], axes[axis2] = axes[axis2], axes[axis1] + a = xp.permute_dims(a, axes) + return a + + # utility to find common dtype with option to force floating def xp_result_type(*args, force_floating=False, xp): """ diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index a8a6ee6ae6e6..c773e96ac779 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -76,11 +76,14 @@ is_lazy_array, is_numpy, is_marray, + is_cupy, xp_size, xp_vector_norm, xp_promote, xp_capabilities, xp_ravel, + xp_swapaxes, + xp_default_dtype, ) import scipy._lib.array_api_extra as xpx @@ -348,7 +351,7 @@ def hmean(a, axis=0, dtype=None, *, weights=None): return 1.0 / _xp_mean(1.0 / a, axis=axis, weights=weights) -@xp_capabilities(static_argnames=("axis", "dtype"), +@xp_capabilities(static_argnames=("axis", "dtype"), jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, @@ -869,7 +872,7 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): # Possible loss of precision for int types res = xp_promote(res, force_floating=True, xp=xp) res = xp.where(invalid, xp.nan, res) - + return res[()] if res.ndim == 0 else res @@ -10191,6 +10194,11 @@ def _square_of_sums(a, axis=0): return float(s) * s +@xp_capabilities(skip_backends=[("torch", "no `repeat`"), + ("cupy", "`repeat` can't handle array second arg"), + ("dask.array", "no `take_along_axis`")], + static_argnames=("method", "axis", "nan_policy"), jax_jit=False, + allow_dask_compute=True) def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): """Assign ranks to data, dealing with ties appropriately. @@ -10282,61 +10290,71 @@ def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): if method not in methods: raise ValueError(f'unknown method "{method}"') - x = np.asarray(a) + xp = array_namespace(a) + x = xp.asarray(a) if axis is None: - x = x.ravel() + x = xp_ravel(x) axis = -1 - if x.size == 0: - dtype = float if method == 'average' else np.dtype("long") - return np.empty(x.shape, dtype=dtype) + if xp_size(x) == 0: + dtype = xp.asarray(1.).dtype if method == 'average' else xp.asarray(1).dtype + return xp.empty(x.shape, dtype=dtype) contains_nan = _contains_nan(x, nan_policy) - x = np.swapaxes(x, axis, -1) - ranks = _rankdata(x, method) + x = xp_swapaxes(x, axis, -1, xp=xp) + ranks = _rankdata(x, method, xp=xp) if contains_nan: - i_nan = (np.isnan(x) if nan_policy == 'omit' - else np.isnan(x).any(axis=-1)) - ranks = ranks.astype(float, copy=False) - ranks[i_nan] = np.nan + default_float = xp_default_dtype(xp) + i_nan = (xp.isnan(x) if nan_policy == 'omit' + else xp.any(xp.isnan(x), axis=-1)) + ranks = xp.asarray(ranks, dtype=default_float) # copy=False when implemented + ranks[i_nan] = xp.nan - ranks = np.swapaxes(ranks, axis, -1) + ranks = xp_swapaxes(ranks, axis, -1, xp=xp) return ranks -def _order_ranks(ranks, j): +def _order_ranks(ranks, j, *, xp): # Reorder ascending order `ranks` according to `j` - ordered_ranks = np.empty(j.shape, dtype=ranks.dtype) - np.put_along_axis(ordered_ranks, j, ranks, axis=-1) + xp = array_namespace(ranks) if xp is None else xp + if is_numpy(xp) or is_cupy(xp): + ordered_ranks = xp.empty(j.shape, dtype=ranks.dtype) + xp.put_along_axis(ordered_ranks, j, ranks, axis=-1) + else: + # `put_along_axis` not in array API (data-apis/array-api#177) + # so argsort the argsort and take_along_axis... + j_inv = xp.argsort(j, axis=-1) + ordered_ranks = xp.take_along_axis(ranks, j_inv, axis=-1) return ordered_ranks -def _rankdata(x, method, return_ties=False): +def _rankdata(x, method, return_ties=False, xp=None): # Rank data `x` by desired `method`; `return_ties` if desired + xp = array_namespace(x) if xp is None else xp shape = x.shape + dtype = xp.asarray(1.).dtype if method == 'average' else xp.asarray(1).dtype # Get sort order - kind = 'mergesort' if method == 'ordinal' else 'quicksort' - j = np.argsort(x, axis=-1, kind=kind) - ordinal_ranks = np.broadcast_to(np.arange(1, shape[-1]+1, dtype=int), shape) + j = xp.argsort(x, axis=-1) + ordinal_ranks = xp.broadcast_to(xp.arange(1, shape[-1]+1, dtype=dtype), shape) # Ordinal ranks is very easy because ties don't matter. We're done. if method == 'ordinal': - return _order_ranks(ordinal_ranks, j) # never return ties + return _order_ranks(ordinal_ranks, j, xp=xp) # never return ties # Sort array - y = np.take_along_axis(x, j, axis=-1) + y = xp.take_along_axis(x, j, axis=-1) # Logical indices of unique elements - i = np.concatenate([np.ones(shape[:-1] + (1,), dtype=np.bool_), - y[..., :-1] != y[..., 1:]], axis=-1) + i = xp.concat([xp.ones(shape[:-1] + (1,), dtype=xp.bool), + y[..., :-1] != y[..., 1:]], axis=-1) # Integer indices of unique elements - indices = np.arange(y.size)[i.ravel()] + indices = xp.arange(xp_size(y))[xp.reshape(i, (-1,))] # i gets raveled # Counts of unique elements - counts = np.diff(indices, append=y.size) + counts = xp.diff(indices, append=xp.asarray([xp_size(y)], dtype=indices.dtype)) # Compute `'min'`, `'max'`, and `'mid'` ranks of unique elements if method == 'min': @@ -10344,12 +10362,13 @@ def _rankdata(x, method, return_ties=False): elif method == 'max': ranks = ordinal_ranks[i] + counts - 1 elif method == 'average': - ranks = ordinal_ranks[i] + (counts - 1)/2 + # array API doesn't promote integers to floats + ranks = ordinal_ranks[i] + (xp.asarray(counts, dtype=dtype) - 1)/2 elif method == 'dense': - ranks = np.cumsum(i, axis=-1)[i] + ranks = xp.cumulative_sum(xp.astype(i, dtype, copy=False), axis=-1)[i] - ranks = np.repeat(ranks, counts).reshape(shape) - ranks = _order_ranks(ranks, j) + ranks = xp.reshape(xp.repeat(ranks, counts), shape) + ranks = _order_ranks(ranks, j, xp=xp) if return_ties: # Tie information is returned in a format that is useful to functions that @@ -10368,7 +10387,7 @@ def _rankdata(x, method, return_ties=False): # sorted order, so this does not unnecessarily reorder them. # - One exception is `wilcoxon`, which needs the number of zeros. Zeros always # have the lowest rank, so it is easy to find them at the zeroth index. - t = np.zeros(shape, dtype=float) + t = xp.zeros(shape, dtype=xp.float64) t[i] = counts return ranks, t return ranks diff --git a/scipy/stats/tests/test_rank.py b/scipy/stats/tests/test_rank.py index 0df57aa7e875..cf154866eec4 100644 --- a/scipy/stats/tests/test_rank.py +++ b/scipy/stats/tests/test_rank.py @@ -2,10 +2,10 @@ from numpy.testing import assert_equal, assert_array_equal import pytest +from scipy import stats from scipy.conftest import skip_xp_invalid_arg from scipy.stats import rankdata, tiecorrect -from scipy._lib._util import np_long - +from scipy._lib._array_api import xp_assert_equal, make_xp_test_case class TestTieCorrect: @@ -73,63 +73,69 @@ def test_overflow(self): assert_equal(out, 1.0 - k * (ntie**3 - ntie) / float(n**3 - n)) +@make_xp_test_case(stats.rankdata) class TestRankData: - def test_empty(self): - """stats.rankdata([]) should return an empty array.""" - a = np.array([], dtype=int) + def desired_dtype(self, method='average', has_nans=False, *, xp): + if has_nans: + return xp.asarray(1.).dtype + return xp.asarray(1.).dtype if method=='average' else xp.asarray(1).dtype + + def test_empty(self, xp): + """stats.rankdata of empty array should return an empty array.""" + a = xp.asarray([], dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, np.array([], dtype=np.float64)) + xp_assert_equal(r, xp.asarray([], dtype=self.desired_dtype(xp=xp))) + + def test_list(self): + # test that NumPy still accepts lists r = rankdata([]) - assert_array_equal(r, np.array([], dtype=np.float64)) + assert_array_equal(r, np.array([])) + + r = rankdata([40, 10, 30, 10, 50]) + assert_equal(r, [4.0, 1.5, 3.0, 1.5, 5.0]) @pytest.mark.parametrize("shape", [(0, 1, 2)]) @pytest.mark.parametrize("axis", [None, *range(3)]) - def test_empty_multidim(self, shape, axis): - a = np.empty(shape, dtype=int) + def test_empty_multidim(self, shape, axis, xp): + a = xp.empty(shape, dtype=xp.int64) r = rankdata(a, axis=axis) expected_shape = (0,) if axis is None else shape - assert_equal(r.shape, expected_shape) - assert_equal(r.dtype, np.float64) + xp_assert_equal(r, xp.empty(expected_shape, dtype=self.desired_dtype(xp=xp))) - def test_one(self): + def test_one(self, xp): """Check stats.rankdata with an array of length 1.""" data = [100] - a = np.array(data, dtype=int) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, np.array([1.0], dtype=np.float64)) - r = rankdata(data) - assert_array_equal(r, np.array([1.0], dtype=np.float64)) + xp_assert_equal(r, xp.asarray([1.0], dtype=self.desired_dtype(xp=xp))) - def test_basic(self): + def test_basic(self, xp): """Basic tests of stats.rankdata.""" + desired_dtype = self.desired_dtype(xp=xp) + data = [100, 10, 50] - expected = np.array([3.0, 1.0, 2.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([3.0, 1.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) data = [40, 10, 30, 10, 50] - expected = np.array([4.0, 1.5, 3.0, 1.5, 5.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([4.0, 1.5, 3.0, 1.5, 5.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) data = [20, 20, 20, 10, 10, 10] - expected = np.array([5.0, 5.0, 5.0, 2.0, 2.0, 2.0], dtype=np.float64) - a = np.array(data, dtype=int) + expected = xp.asarray([5.0, 5.0, 5.0, 2.0, 2.0, 2.0], dtype=desired_dtype) + a = xp.asarray(data, dtype=xp.int64) r = rankdata(a) - assert_array_equal(r, expected) - r = rankdata(data) - assert_array_equal(r, expected) - # The docstring states explicitly that the argument is flattened. - a2d = a.reshape(2, 3) + xp_assert_equal(r, expected) + + # # The docstring states explicitly that the argument is flattened. + a2d = xp.reshape(a, (2, 3)) r = rankdata(a2d) - assert_array_equal(r, expected) + xp_assert_equal(r, expected) @skip_xp_invalid_arg def test_rankdata_object_string(self): @@ -165,50 +171,51 @@ def check_ranks(a): val = np.array([0, 1, 2, 2.718, 3, 3.141], dtype='object') check_ranks(np.random.choice(val, 200).astype('object')) - def test_large_int(self): - data = np.array([2**60, 2**60+1], dtype=np.uint64) + def test_large_int(self, xp): + if hasattr(xp, 'uint64'): + data = xp.asarray([2**60, 2**60+1], dtype=xp.uint64) + r = rankdata(data) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=self.desired_dtype(xp=xp))) + + data = xp.asarray([2**60, 2**60+1], dtype=xp.int64) r = rankdata(data) - assert_array_equal(r, [1.0, 2.0]) + xp_assert_equal(r, xp.asarray([1.0, 2.0], dtype=self.desired_dtype(xp=xp))) - data = np.array([2**60, 2**60+1], dtype=np.int64) + data = xp.asarray([2**60, -2**60+1], dtype=xp.int64) r = rankdata(data) - assert_array_equal(r, [1.0, 2.0]) + xp_assert_equal(r, xp.asarray([2.0, 1.0], dtype=self.desired_dtype(xp=xp))) - data = np.array([2**60, -2**60+1], dtype=np.int64) + @pytest.mark.parametrize('n', [10000, 100000, 1000000]) + def test_big_tie(self, n, xp): + data = xp.ones(n) r = rankdata(data) - assert_array_equal(r, [2.0, 1.0]) + expected_rank = 0.5 * (n + 1) + ref = xp.asarray(expected_rank * data, dtype=self.desired_dtype(xp=xp)) + xp_assert_equal(r, ref) - def test_big_tie(self): - for n in [10000, 100000, 1000000]: - data = np.ones(n, dtype=int) - r = rankdata(data) - expected_rank = 0.5 * (n + 1) - assert_array_equal(r, expected_rank * data, - err_msg=f"test failed with n={n}") - - def test_axis(self): - data = [[0, 2, 1], - [4, 2, 2]] - expected0 = [[1., 1.5, 1.], - [2., 1.5, 2.]] + def test_axis(self, xp): + data = xp.asarray([[0, 2, 1], [4, 2, 2]]) + + expected0 = xp.asarray([[1., 1.5, 1.], [2., 1.5, 2.]]) r0 = rankdata(data, axis=0) - assert_array_equal(r0, expected0) - expected1 = [[1., 3., 2.], - [3., 1.5, 1.5]] + xp_assert_equal(r0, expected0) + + expected1 = xp.asarray([[1., 3., 2.], [3., 1.5, 1.5]]) r1 = rankdata(data, axis=1) - assert_array_equal(r1, expected1) + xp_assert_equal(r1, expected1) - methods = ["average", "min", "max", "dense", "ordinal"] - dtypes = [np.float64] + [np_long]*4 + methods= ["average", "min", "max", "dense", "ordinal"] @pytest.mark.parametrize("axis", [0, 1]) - @pytest.mark.parametrize("method, dtype", zip(methods, dtypes)) - def test_size_0_axis(self, axis, method, dtype): + @pytest.mark.parametrize("method", methods) + def test_size_0_axis(self, axis, method, xp): shape = (3, 0) - data = np.zeros(shape) + desired_dtype = self.desired_dtype(method, xp=xp) + data = xp.zeros(shape) r = rankdata(data, method=method, axis=axis) assert_equal(r.shape, shape) - assert_equal(r.dtype, dtype) + assert_equal(r.dtype, desired_dtype) + xp_assert_equal(r, xp.empty(shape, dtype=desired_dtype)) @pytest.mark.parametrize('axis', range(3)) @pytest.mark.parametrize('method', methods) @@ -289,50 +296,50 @@ def test_nan_policy_propagate(self): [np.nan, np.nan, np.nan], [1, 2.5, 2.5]]) - -_cases = ( - # values, method, expected - ([], 'average', []), - ([], 'min', []), - ([], 'max', []), - ([], 'dense', []), - ([], 'ordinal', []), - # - ([100], 'average', [1.0]), - ([100], 'min', [1.0]), - ([100], 'max', [1.0]), - ([100], 'dense', [1.0]), - ([100], 'ordinal', [1.0]), - # - ([100, 100, 100], 'average', [2.0, 2.0, 2.0]), - ([100, 100, 100], 'min', [1.0, 1.0, 1.0]), - ([100, 100, 100], 'max', [3.0, 3.0, 3.0]), - ([100, 100, 100], 'dense', [1.0, 1.0, 1.0]), - ([100, 100, 100], 'ordinal', [1.0, 2.0, 3.0]), - # - ([100, 300, 200], 'average', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'min', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'max', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'dense', [1.0, 3.0, 2.0]), - ([100, 300, 200], 'ordinal', [1.0, 3.0, 2.0]), - # - ([100, 200, 300, 200], 'average', [1.0, 2.5, 4.0, 2.5]), - ([100, 200, 300, 200], 'min', [1.0, 2.0, 4.0, 2.0]), - ([100, 200, 300, 200], 'max', [1.0, 3.0, 4.0, 3.0]), - ([100, 200, 300, 200], 'dense', [1.0, 2.0, 3.0, 2.0]), - ([100, 200, 300, 200], 'ordinal', [1.0, 2.0, 4.0, 3.0]), - # - ([100, 200, 300, 200, 100], 'average', [1.5, 3.5, 5.0, 3.5, 1.5]), - ([100, 200, 300, 200, 100], 'min', [1.0, 3.0, 5.0, 3.0, 1.0]), - ([100, 200, 300, 200, 100], 'max', [2.0, 4.0, 5.0, 4.0, 2.0]), - ([100, 200, 300, 200, 100], 'dense', [1.0, 2.0, 3.0, 2.0, 1.0]), - ([100, 200, 300, 200, 100], 'ordinal', [1.0, 3.0, 5.0, 4.0, 2.0]), - # - ([10] * 30, 'ordinal', np.arange(1.0, 31.0)), -) - - -def test_cases(): - for values, method, expected in _cases: - r = rankdata(values, method=method) - assert_array_equal(r, expected) + _rankdata_cases = ( + # values, method, expected + ([], 'average', []), + ([], 'min', []), + ([], 'max', []), + ([], 'dense', []), + ([], 'ordinal', []), + # + ([100], 'average', [1.0]), + ([100], 'min', [1.0]), + ([100], 'max', [1.0]), + ([100], 'dense', [1.0]), + ([100], 'ordinal', [1.0]), + # + ([100, 100, 100], 'average', [2.0, 2.0, 2.0]), + ([100, 100, 100], 'min', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'max', [3.0, 3.0, 3.0]), + ([100, 100, 100], 'dense', [1.0, 1.0, 1.0]), + ([100, 100, 100], 'ordinal', [1.0, 2.0, 3.0]), + # + ([100, 300, 200], 'average', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'min', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'max', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'dense', [1.0, 3.0, 2.0]), + ([100, 300, 200], 'ordinal', [1.0, 3.0, 2.0]), + # + ([100, 200, 300, 200], 'average', [1.0, 2.5, 4.0, 2.5]), + ([100, 200, 300, 200], 'min', [1.0, 2.0, 4.0, 2.0]), + ([100, 200, 300, 200], 'max', [1.0, 3.0, 4.0, 3.0]), + ([100, 200, 300, 200], 'dense', [1.0, 2.0, 3.0, 2.0]), + ([100, 200, 300, 200], 'ordinal', [1.0, 2.0, 4.0, 3.0]), + # + ([100, 200, 300, 200, 100], 'average', [1.5, 3.5, 5.0, 3.5, 1.5]), + ([100, 200, 300, 200, 100], 'min', [1.0, 3.0, 5.0, 3.0, 1.0]), + ([100, 200, 300, 200, 100], 'max', [2.0, 4.0, 5.0, 4.0, 2.0]), + ([100, 200, 300, 200, 100], 'dense', [1.0, 2.0, 3.0, 2.0, 1.0]), + ([100, 200, 300, 200, 100], 'ordinal', [1.0, 3.0, 5.0, 4.0, 2.0]), + # + ([10] * 30, 'ordinal', np.arange(1.0, 31.0)), + ) + + @pytest.mark.parametrize('case', _rankdata_cases) + def test_cases(self, case, xp): + values, method, expected = case + r = rankdata(xp.asarray(values), method=method) + ref = xp.asarray(expected, dtype=self.desired_dtype(method, xp=xp)) + xp_assert_equal(r, ref) From a723f335b4f57ef1536c21f927111b4e976ff556 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Tue, 13 May 2025 16:22:38 +0200 Subject: [PATCH 175/251] BUG: signal: fix incorrect vendoring of npp_polyval --- scipy/signal/_polyutils.py | 8 ++++++-- scipy/signal/tests/test_filter_design.py | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_polyutils.py b/scipy/signal/_polyutils.py index 6090563d9bae..04d6686924bd 100644 --- a/scipy/signal/_polyutils.py +++ b/scipy/signal/_polyutils.py @@ -6,6 +6,7 @@ To distinguish the two sets, the "new-style" routine names start with `npp_` """ import scipy._lib.array_api_extra as xpx +from scipy._lib._array_api import xp_promote, xp_default_dtype def _sort_cmplx(arr, xp): @@ -138,15 +139,18 @@ def polymul(a1, a2, *, xp): # ### New-style routines ### -# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L845-L894 +# https://github.com/numpy/numpy/blob/v2.2.0/numpy/polynomial/polynomial.py#L663 def npp_polyval(x, c, *, xp, tensor=True): + if xp.isdtype(c.dtype, 'integral'): + c = xp.astype(c, xp_default_dtype(xp)) + c = xpx.atleast_nd(c, ndim=1, xp=xp) if isinstance(x, tuple | list): x = xp.asarray(x) if tensor: c = xp.reshape(c, (c.shape + (1,)*x.ndim)) - c0 = c[-1, ...] + c0, _ = xp_promote(c[-1, ...], x, broadcast=True, xp=xp) for i in range(2, c.shape[0] + 1): c0 = c[-i, ...] + c0*x return c0 diff --git a/scipy/signal/tests/test_filter_design.py b/scipy/signal/tests/test_filter_design.py index c21c6816c607..c9d7dd9f41cc 100644 --- a/scipy/signal/tests/test_filter_design.py +++ b/scipy/signal/tests/test_filter_design.py @@ -749,6 +749,11 @@ def test_ticket1441(self, xp): w, h = freqz(xp.asarray([1.0]), worN=N) assert w.shape == (N,) + def test_gh_22886(self, xp): + w, h = freqz(xp.asarray([1.]), worN=xp.asarray([0., 0.1])) + xp_assert_equal(w, xp.asarray([0. , 0.1])) + xp_assert_equal(h, xp.asarray([1.+0.j, 1.+0.j])) + def test_basic(self, xp): w, h = freqz(xp.asarray([1.0]), worN=8) assert_array_almost_equal(w, xp.pi * xp.arange(8, dtype=w.dtype) / 8.) From 4c5e1ea1952f83208e0ca7843b23a47867f60202 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Tue, 13 May 2025 11:31:41 -0700 Subject: [PATCH 176/251] MAINT: stats.make_distribution: fix most remaining discrete distribution specific issues (#22969) * MAINT: stats.make_distribution: fix some discrete distributions * TST: stats.make_distribution: fix tests for some discrete distributions * TST: stats.make_distribution: clarify reason for test failure * MAINT: stats.make_distribution: fix noncentral hypergeometric failures --- scipy/stats/_discrete_distns.py | 17 +++++++---- scipy/stats/_distribution_infrastructure.py | 6 ++-- scipy/stats/tests/test_continuous.py | 31 +++++++++++---------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/scipy/stats/_discrete_distns.py b/scipy/stats/_discrete_distns.py index 1e3340506e37..ac12ef17aa3d 100644 --- a/scipy/stats/_discrete_distns.py +++ b/scipy/stats/_discrete_distns.py @@ -1371,12 +1371,13 @@ def _gen_harmonic_leq1(n, a): """Generalized harmonic number, a <= 1""" if not np.size(n): return n - n_max = np.max(n) # loop starts at maximum of all n + n_max = np.nanmax(n) # loop starts at maximum of all n out = np.zeros_like(a, dtype=float) # add terms of harmonic series; starting from smallest to avoid roundoff for i in np.arange(n_max, 0, -1, dtype=float): mask = i <= n # don't add terms after nth out[mask] += 1/i**a[mask] + out[np.isnan(n)] = np.nan return out @@ -1871,9 +1872,9 @@ def _get_support(self, M, n, N, odds): def _argcheck(self, M, n, N, odds): M, n = np.asarray(M), np.asarray(n), N, odds = np.asarray(N), np.asarray(odds) - cond1 = (M.astype(int) == M) & (M >= 0) - cond2 = (n.astype(int) == n) & (n >= 0) - cond3 = (N.astype(int) == N) & (N >= 0) + cond1 = (~np.isnan(M)) & (M.astype(int) == M) & (M >= 0) + cond2 = (~np.isnan(n)) & (n.astype(int) == n) & (n >= 0) + cond3 = (~np.isnan(N)) & (N.astype(int) == N) & (N >= 0) cond4 = odds > 0 cond5 = N <= M cond6 = n <= M @@ -1883,6 +1884,8 @@ def _rvs(self, M, n, N, odds, size=None, random_state=None): @_vectorize_rvs_over_shapes def _rvs1(M, n, N, odds, size, random_state): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.full(size, np.nan) length = np.prod(size) urn = _PyStochasticLib3() rv_gen = getattr(urn, self.rvs_name) @@ -1900,15 +1903,19 @@ def _pmf(self, x, M, n, N, odds): @np.vectorize def _pmf1(x, M, n, N, odds): + if np.isnan(x) | np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan urn = self.dist(N, n, M, odds, 1e-12) return urn.probability(x) return _pmf1(x, M, n, N, odds) - def _stats(self, M, n, N, odds, moments): + def _stats(self, M, n, N, odds, moments='mv'): @np.vectorize def _moments1(M, n, N, odds): + if np.isnan(M) | np.isnan(n) | np.isnan(N): + return np.nan, np.nan urn = self.dist(N, n, M, odds, 1e-12) return urn.moments() diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 13b8380c5a95..94b2243d177b 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3856,8 +3856,7 @@ def make_distribution(dist): `make_distribution` does not work perfectly with all instances of `rv_continuous`. Known failures include `levy_stable`, `vonmises`, - `hypergeom`, `nchypergeom_fisher`, `nchypergeom_wallenius`, - `poisson_binom`; and some methods of some distributions + `hypergeom`, `poisson_binom`, and 'skellam'. Some methods of some distributions will not support array shape parameters. Parameters @@ -4066,8 +4065,7 @@ class or its methods for more information. """ if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, - stats.nchypergeom_fisher, stats.nchypergeom_wallenius, - stats.poisson_binom}: + stats.poisson_binom, stats.skellam}: raise NotImplementedError(f"`{dist.name}` is not supported.") if isinstance(dist, stats.rv_continuous | stats.rv_discrete): diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 5132d54eebf8..2be327145c2e 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1113,7 +1113,7 @@ def test_rv_generic(self, i, distdata): 'johnsonsb', 'kappa4', 'ksone', 'kstwo', 'kstwobign', 'norminvgauss', 'powerlognorm', 'powernorm', 'recipinvgauss', 'studentized_range', 'vonmises_line', # continuous - 'betanbinom', 'zipf', 'logser', 'skellam'} # discrete + 'skellam'} # discrete if not int(os.environ.get('SCIPY_XSLOW', '0')) and distname in slow: pytest.skip('Skipping as XSLOW') @@ -1122,25 +1122,24 @@ def test_rv_generic(self, i, distdata): 'vonmises', # circular distribution; shouldn't work 'poisson_binom', # vector shape parameter 'hypergeom', # distribution functions need interpolation - 'nchypergeom_fisher', # distribution functions don't accept NaN - 'nchypergeom_wallenius', # distribution functions don't accept NaN - 'skellam', # during `entropy`, Fatal Python error: Aborted! - 'zipfian', # during init, value error due to unexpected nans + 'skellam', # gh-22956 (_ncx2_pdf crashes with extreme input) }: return # skip single test, mostly due to slight disagreement custom_tolerances = {'ksone': 1e-5, 'kstwo': 1e-5} # discontinuous PDF skip_entropy = {'kstwobign', 'pearson3'} # tolerance issue - skip_skewness = {'exponpow', 'ksone'} # tolerance issue - skip_kurtosis = {'chi', 'exponpow', 'invgamma', # tolerance issue - 'johnsonsb', 'ksone', 'kstwo'} # tolerance issue + skip_skewness = {'exponpow', 'ksone', 'nchypergeom_wallenius'} # tolerance + skip_kurtosis = {'chi', 'exponpow', 'invgamma', # tolerance + 'johnsonsb', 'ksone', 'kstwo', # tolerance + 'nchypergeom_wallenius'} # tolerance skip_logccdf = {'arcsine', 'skewcauchy', 'trapezoid', 'triang'} # tolerance skip_raw = {2: {'alpha', 'foldcauchy', 'halfcauchy', 'levy', 'levy_l'}, 3: {'pareto'}, # stats.pareto is just wrong 4: {'invgamma'}} # tolerance issue - skip_standardized = {'exponpow', 'ksone'} # tolerances - skip_median = {'nhypergeom', 'yulesimon'} # nan mismatch + skip_standardized = {'exponpow', 'ksone', 'nchypergeom_wallenius'} # tolerances + skip_median = {'nhypergeom', 'yulesimon', # nan mismatch + 'betanbinom', 'zipf', 'logser'} # median 0th element dist = getattr(stats, distname) params = dict(zip(dist.shapes.split(', '), distdata[1])) if dist.shapes else {} @@ -1195,10 +1194,14 @@ def test_rv_generic(self, i, distdata): if distname not in skip_standardized: assert_allclose(X.moment(order, kind='standardized'), Y.stats('mvsk'[order-1]), rtol=rtol, atol=atol) - seed = 845298245687345 - assert_allclose(X.sample(shape=10, rng=seed), - Y.rvs(size=10, random_state=np.random.default_rng(seed)), - rtol=rtol) + if isinstance(dist, stats.rv_continuous): + # For discrete distributions, these won't agree at the far left end + # of the support, and the new infrastructure is slow there (for now). + seed = 845298245687345 + assert_allclose(X.sample(shape=10, rng=seed), + Y.rvs(size=10, + random_state=np.random.default_rng(seed)), + rtol=rtol) def test_custom(self): rng = np.random.default_rng(7548723590230982) From 21e25535048a3b34e057d93c8b951c7e23a5a9b2 Mon Sep 17 00:00:00 2001 From: Daniel Schmitz <40656107+dschmitz89@users.noreply.github.com> Date: Tue, 13 May 2025 21:34:34 +0200 Subject: [PATCH 177/251] MAINT: fix skellam distribution tests (#22971) * TST: add test case extreme input for ncx2 pdf * TST: remove skellam from makedistribution skip list * MAINT: catch errors in ncx2 pdf * TST: Remove skellam from slow list --------- Co-authored-by: Matt Haberland --- scipy/special/boost_special_functions.h | 16 +++++++++++++++- scipy/special/tests/test_boost_ufuncs.py | 3 +++ scipy/stats/_distribution_infrastructure.py | 4 ++-- scipy/stats/tests/test_continuous.py | 1 - 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scipy/special/boost_special_functions.h b/scipy/special/boost_special_functions.h index 4d865f8e5ce6..9f281a183ea4 100644 --- a/scipy/special/boost_special_functions.h +++ b/scipy/special/boost_special_functions.h @@ -699,8 +699,22 @@ Real ncx2_pdf_wrap(const Real x, const Real k, const Real l) { if (std::isfinite(x)) { + try { return boost::math::pdf( - boost::math::non_central_chi_squared_distribution(k, l), x); + boost::math::non_central_chi_squared_distribution(k, l), x); + } + catch (const std::overflow_error& e) { + sf_error("_ncx2_pdf", SF_ERROR_OVERFLOW, NULL); + return INFINITY; + } + catch (const std::underflow_error& e) { + sf_error("_ncx2_pdf", SF_ERROR_UNDERFLOW, NULL); + return 0; + } + catch (...) { + sf_error("_ncx2_pdf", SF_ERROR_OTHER, NULL); + return NAN; + } } return NAN; // inf or -inf returns NAN } diff --git a/scipy/special/tests/test_boost_ufuncs.py b/scipy/special/tests/test_boost_ufuncs.py index 132fb9ab11ec..43da7337d0d2 100644 --- a/scipy/special/tests/test_boost_ufuncs.py +++ b/scipy/special/tests/test_boost_ufuncs.py @@ -59,3 +59,6 @@ def test_landau(): assert_allclose(ppf, x) isf = scu._landau_isf(sf, *args) assert_allclose(isf, x, rtol=1e-6) + +def test_gh22956(): + _ = scu._ncx2_pdf(30, 1e307, 16) diff --git a/scipy/stats/_distribution_infrastructure.py b/scipy/stats/_distribution_infrastructure.py index 94b2243d177b..74b1beae89ac 100644 --- a/scipy/stats/_distribution_infrastructure.py +++ b/scipy/stats/_distribution_infrastructure.py @@ -3856,7 +3856,7 @@ def make_distribution(dist): `make_distribution` does not work perfectly with all instances of `rv_continuous`. Known failures include `levy_stable`, `vonmises`, - `hypergeom`, `poisson_binom`, and 'skellam'. Some methods of some distributions + `hypergeom`, and `poisson_binom`. Some methods of some distributions will not support array shape parameters. Parameters @@ -4065,7 +4065,7 @@ class or its methods for more information. """ if dist in {stats.levy_stable, stats.vonmises, stats.hypergeom, - stats.poisson_binom, stats.skellam}: + stats.poisson_binom}: raise NotImplementedError(f"`{dist.name}` is not supported.") if isinstance(dist, stats.rv_continuous | stats.rv_discrete): diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 2be327145c2e..1b6c66a229a6 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1122,7 +1122,6 @@ def test_rv_generic(self, i, distdata): 'vonmises', # circular distribution; shouldn't work 'poisson_binom', # vector shape parameter 'hypergeom', # distribution functions need interpolation - 'skellam', # gh-22956 (_ncx2_pdf crashes with extreme input) }: return From b55123d153069750c43c870fb35c7816386af07b Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Tue, 13 May 2025 23:59:48 +0200 Subject: [PATCH 178/251] TST: `cluster`: reduce test reliance from linkage (#22961) --- scipy/cluster/tests/test_hierarchy.py | 424 +++++++++++--------------- 1 file changed, 177 insertions(+), 247 deletions(-) diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 29f848ace459..4e1a620abd52 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -32,12 +32,10 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import numpy as np -from numpy.testing import (assert_allclose, assert_equal, assert_array_equal, assert_, - assert_warns) +from numpy.testing import assert_allclose, assert_equal, assert_array_equal, assert_ import pytest from pytest import raises as assert_raises -import scipy.cluster.hierarchy from scipy.cluster.hierarchy import ( ClusterWarning, linkage, from_mlab_linkage, to_mlab_linkage, num_obs_linkage, inconsistent, cophenet, fclusterdata, fcluster, @@ -46,9 +44,9 @@ is_valid_linkage, is_valid_im, to_tree, leaves_list, dendrogram, set_link_color_palette, cut_tree, optimal_leaf_ordering, _order_cluster_tree, _hierarchy, _EUCLIDEAN_METHODS, _LINKAGE_METHODS) -from scipy.spatial.distance import pdist from scipy.cluster._hierarchy import Heap -from scipy._lib._array_api import xp_assert_close, xp_assert_equal +from scipy.spatial.distance import pdist +from scipy._lib._array_api import eager_warns, xp_assert_close, xp_assert_equal import scipy._lib.array_api_extra as xpx from scipy._lib.array_api_extra.testing import lazy_xp_function @@ -56,6 +54,12 @@ from . import hierarchy_test_data +class eager: + # Bypass xpx.testing.lazy_xp_function when calling + # these functions from this namespace + is_valid_im = is_valid_im + is_valid_linkage = is_valid_linkage + # Matplotlib is not a scipy dependency but is optionally used in dendrogram, so # check if it's available @@ -71,8 +75,6 @@ skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends -use_linkage = skip_xp_backends(cpu_only=True, exceptions=["jax.numpy"], - reason="linkage() invokes Cython code") lazy_xp_function(single) lazy_xp_function(ward) @@ -110,7 +112,7 @@ lazy_xp_function(leaders, jax_jit=False) -@use_linkage +@skip_xp_backends(cpu_only=True, reason="linkage() invokes Cython code") class TestLinkage: @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") @@ -166,37 +168,51 @@ def test_optimal_leaf_ordering(self, xp): expectedZ = getattr(hierarchy_test_data, 'linkage_ytdist_single_olo') xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-10) - -@use_linkage -class TestLinkageTies: - - _expectations = { - 'single': np.array([[0, 1, 1.41421356, 2], - [2, 3, 1.41421356, 3]]), - 'complete': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.82842712, 3]]), - 'average': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'weighted': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'centroid': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'median': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.12132034, 3]]), - 'ward': np.array([[0, 1, 1.41421356, 2], - [2, 3, 2.44948974, 3]]), - } - - def test_linkage_ties(self, xp): - for method in ['single', 'complete', 'average', 'weighted', - 'centroid', 'median', 'ward']: - self.check_linkage_ties(method, xp) - - def check_linkage_ties(self, method, xp): + @pytest.mark.parametrize("method,expect", [ + ('single', [[0, 1, 1.41421356, 2], + [2, 3, 1.41421356, 3]]), + ('complete', [[0, 1, 1.41421356, 2], + [2, 3, 2.82842712, 3]]), + ('average', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('weighted', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('centroid', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('median', [[0, 1, 1.41421356, 2], + [2, 3, 2.12132034, 3]]), + ('ward', [[0, 1, 1.41421356, 2], + [2, 3, 2.44948974, 3]]), + ]) + def test_linkage_ties(self, method, expect, xp): X = xp.asarray([[-1, -1], [0, 0], [1, 1]]) Z = linkage(X, method=method) - expectedZ = self._expectations[method] - xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-06) + expect = xp.asarray(expect, dtype=xp.float64) + xp_assert_close(Z, expect, atol=1e-06) + + def test_unsupported_uncondensed_distance_matrix_linkage_warning(self, xp): + X = xp.asarray([[0, 1], [1, 0]]) + with eager_warns(X, ClusterWarning): + linkage(X) + + @pytest.mark.parametrize("method", _EUCLIDEAN_METHODS) + def test_euclidean_linkage_value_error(self, method, xp): + X = xp.asarray([[1, 1], [1, 1]]) + with pytest.raises(ValueError): + linkage(X, method=method, metric='cityblock') + + def test_2x2_linkage(self, xp): + Z1 = linkage(xp.asarray([1]), method='single', metric='euclidean') + Z2 = linkage(xp.asarray([[0, 1], [0, 0]]), method='single', metric='euclidean') + xp_assert_close(Z1, Z2, rtol=1e-15) + + @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") + def test_centroid_neg_distance(self, xp): + # gh-21011 + values = xp.asarray([0, 0, -1]) + with pytest.raises(ValueError): + # This is just checking that this doesn't crash + linkage(values, method='centroid') @skip_xp_backends(cpu_only=True) @@ -288,51 +304,45 @@ def test_mlab_linkage_conversion_multiple_rows(self, xp): @skip_xp_backends(cpu_only=True) -class TestFcluster: +class TestFclusterData: - def test_fclusterdata(self, xp): - for t in hierarchy_test_data.fcluster_inconsistent: - self.check_fclusterdata(t, 'inconsistent', xp) - for t in hierarchy_test_data.fcluster_distance: - self.check_fclusterdata(t, 'distance', xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fclusterdata(t, 'maxclust', xp) - - def check_fclusterdata(self, t, criterion, xp): + @pytest.mark.parametrize("criterion,t", + [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + + [("distance", t) for t in hierarchy_test_data.fcluster_distance] + + [("maxclust", t) for t in hierarchy_test_data.fcluster_maxclust] + ) + def test_fclusterdata(self, t, criterion, xp): # Tests fclusterdata(X, criterion=criterion, t=t) on a random 3-cluster data set expectedT = xp.asarray(getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]) X = xp.asarray(hierarchy_test_data.Q_X) T = fclusterdata(X, criterion=criterion, t=t) - assert_(is_isomorphic(T, expectedT)) + assert is_isomorphic(T, expectedT) + - def test_fcluster(self, xp): - for t in hierarchy_test_data.fcluster_inconsistent: - self.check_fcluster(t, 'inconsistent', xp) - for t in hierarchy_test_data.fcluster_distance: - self.check_fcluster(t, 'distance', xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fcluster(t, 'maxclust', xp) +@skip_xp_backends(cpu_only=True) +class TestFcluster: - def check_fcluster(self, t, criterion, xp): + @pytest.mark.parametrize("criterion,t", + [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + + [("distance", t) for t in hierarchy_test_data.fcluster_distance] + + [("maxclust", t) for t in hierarchy_test_data.fcluster_maxclust] + ) + def test_fcluster(self, t, criterion, xp): # Tests fcluster(Z, criterion=criterion, t=t) on a random 3-cluster data set. expectedT = xp.asarray(getattr(hierarchy_test_data, 'fcluster_' + criterion)[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, criterion=criterion, t=t) assert_(is_isomorphic(T, expectedT)) - def test_fcluster_monocrit(self, xp): - for t in hierarchy_test_data.fcluster_distance: - self.check_fcluster_monocrit(t, xp) - for t in hierarchy_test_data.fcluster_maxclust: - self.check_fcluster_maxclust_monocrit(t, xp) - - def check_fcluster_monocrit(self, t, xp): + @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_distance) + def test_fcluster_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_distance[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, t, criterion='monocrit', monocrit=maxdists(Z)) assert_(is_isomorphic(T, expectedT)) - def check_fcluster_maxclust_monocrit(self, t, xp): + @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_maxclust) + def test_fcluster_maxclust_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_maxclust[t]) Z = single(xp.asarray(hierarchy_test_data.Q_X)) T = fcluster(Z, t, criterion='maxclust_monocrit', monocrit=maxdists(Z)) @@ -356,21 +366,22 @@ class TestLeaders: def test_leaders_single(self, xp): # Tests leaders using a flat clustering generated by single linkage. - X = xp.asarray(hierarchy_test_data.Q_X) + X = hierarchy_test_data.Q_X Y = pdist(X) Z = linkage(Y) T = fcluster(Z, criterion='maxclust', t=3) - Lright = (xp.asarray([53, 55, 56]), xp.asarray([2, 3, 1])) + Z = xp.asarray(Z) T = xp.asarray(T, dtype=xp.int32) L = leaders(Z, T) - assert_allclose(np.concatenate(L), np.concatenate(Lright), rtol=1e-15) + expect = xp.asarray([53, 55, 56, 2, 3, 1], dtype=xp.int32) + xp_assert_close(xp.concat(L), expect, rtol=1e-15) @skip_xp_backends(np_only=True, reason='`is_isomorphic` only supports NumPy backend') class TestIsIsomorphic: - def test_array_like(self, xp): + def test_array_like(self): assert is_isomorphic([1, 1, 1], [2, 2, 2]) assert is_isomorphic([], []) @@ -418,18 +429,18 @@ def test_is_isomorphic_4C(self, xp): assert is_isomorphic(a, b) assert is_isomorphic(b, a) - def test_is_isomorphic_5(self, xp): + @pytest.mark.parametrize("nclusters", [2, 3, 5]) + def test_is_isomorphic_5(self, nclusters, xp): # Tests is_isomorphic on test case #5 (1000 observations, 2/3/5 random # clusters, random permutation of the labeling). - for nc in [2, 3, 5]: - self.help_is_isomorphic_randperm(1000, nc, xp=xp) + self.is_isomorphic_randperm(1000, nclusters, xp=xp) - def test_is_isomorphic_6(self, xp): + @pytest.mark.parametrize("nclusters", [2, 3, 5]) + def test_is_isomorphic_6(self, nclusters, xp): # Tests is_isomorphic on test case #5A (1000 observations, 2/3/5 random # clusters, random permutation of the labeling, slightly # nonisomorphic.) - for nc in [2, 3, 5]: - self.help_is_isomorphic_randperm(1000, nc, True, 5, xp=xp) + self.is_isomorphic_randperm(1000, nclusters, True, 5, xp=xp) def test_is_isomorphic_7(self, xp): # Regression test for gh-6271 @@ -437,9 +448,8 @@ def test_is_isomorphic_7(self, xp): b = xp.asarray([1, 1, 1]) assert not is_isomorphic(a, b) - def help_is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, - *, xp): - for k in range(3): + def is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, *, xp): + for _ in range(3): a = (np.random.rand(nobs) * nclusters).astype(int) b = np.zeros(a.size, dtype=int) P = np.random.permutation(nclusters) @@ -449,6 +459,8 @@ def help_is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, Q = np.random.permutation(nobs) b[Q[0:nerrors]] += 1 b[Q[0:nerrors]] %= nclusters + a = xp.asarray(a) + b = xp.asarray(b) assert is_isomorphic(a, b) == (not noniso) assert is_isomorphic(b, a) == (not noniso) @@ -479,70 +491,62 @@ def test_is_valid_linkage_empty(self, xp): xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) assert_raises(ValueError, is_valid_linkage, Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) xp_assert_equal(is_valid_linkage(Z), True, check_namespace=False) - @use_linkage def test_is_valid_linkage_4_and_up_neg_index_left(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (left). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 0].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) - # Use fully-qualified function name to bypass lazy_xp_function(), - # because `is_valid_*` materializes. with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up_neg_index_right(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative indices (right). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 1].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) - - @use_linkage def test_is_valid_linkage_4_and_up_neg_dist(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative distances. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 2].set(-0.5) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) - @use_linkage def test_is_valid_linkage_4_and_up_neg_counts(self, xp): # Tests is_valid_linkage(Z) on linkage on observation sets between # sizes 4 and 15 (step size 3) with negative counts. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) Z = xpx.at(Z)[i//2, 3].set(-2) xp_assert_equal(is_valid_linkage(Z), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_linkage(Z, throw=True) + eager.is_valid_linkage(Z, throw=True) class TestIsValidInconsistent: @@ -571,60 +575,54 @@ def test_is_valid_im_empty(self, xp): xp_assert_equal(is_valid_im(R), False, check_namespace=False) assert_raises(ValueError, is_valid_im, R, throw=True) - @use_linkage def test_is_valid_im_4_and_up(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), True, check_namespace=False) - @use_linkage def test_is_valid_im_4_and_up_neg_index_left(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height means. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2 , 0].set(-2.0) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) - # Use fully-qualified function name to bypass lazy_xp_function(), - # because `is_valid_*`materializes. with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) - @use_linkage def test_is_valid_im_4_and_up_neg_index_right(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link height standard deviations. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2 , 1].set(-2.0) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) - @use_linkage def test_is_valid_im_4_and_up_neg_dist(self, xp): # Tests is_valid_im(R) on im on observation sets between sizes 4 and 15 # (step size 3) with negative link counts. for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) Z = linkage(y) R = inconsistent(Z) R = xpx.at(R)[i//2, 2].set(-0.5) + R = xp.asarray(R) xp_assert_equal(is_valid_im(R), False, check_namespace=False) with pytest.raises(ValueError): - scipy.cluster.hierarchy.is_valid_im(R, throw=True) + eager.is_valid_im(R, throw=True) class TestNumObsLinkage: @@ -645,23 +643,20 @@ def test_num_obs_linkage_2x4(self, xp): [3, 2, 4.0, 3]], dtype=xp.float64) assert num_obs_linkage(Z) == 3 - @use_linkage def test_num_obs_linkage_4_and_up(self, xp): # Tests num_obs_linkage(Z) on linkage on observation sets between sizes # 4 and 15 (step size 3). for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) - y = xp.asarray(y) - Z = linkage(y) + Z = xp.asarray(linkage(y)) assert num_obs_linkage(Z) == i - @use_linkage def test_num_obs_linkage_multi_matrix(self, xp): # Tests num_obs_linkage with observation matrices of multiple sizes. for n in range(2, 10): - X = xp.asarray(np.random.rand(n, 4)) + X = np.random.rand(n, 4) Y = pdist(X) - Z = linkage(Y) + Z = xp.asarray(linkage(Y)) assert num_obs_linkage(Z) == n @@ -681,25 +676,22 @@ def test_leaves_list_2x4(self, xp): to_tree(Z) assert_allclose(leaves_list(Z), [0, 1, 2], rtol=1e-15) - def test_leaves_list_Q(self, xp): - for method in ['single', 'complete', 'average', 'weighted', 'centroid', - 'median', 'ward']: - self.check_leaves_list_Q(method, xp) - - def check_leaves_list_Q(self, method, xp): + @pytest.mark.parametrize("method", + ['single', 'complete', 'average', 'weighted', 'centroid', 'median', 'ward']) + def test_leaves_list_Q(self, method, xp): # Tests leaves_list(Z) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, method) + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, method)) node = to_tree(Z) assert_allclose(node.pre_order(), leaves_list(Z), rtol=1e-15) def test_Q_subtree_pre_order(self, xp): # Tests that pre_order() works when called on sub-trees. - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, 'single') + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, 'single')) node = to_tree(Z) - assert_allclose(node.pre_order(), (node.get_left().pre_order() - + node.get_right().pre_order()), + assert_allclose(node.pre_order(), + (node.get_left().pre_order() + node.get_right().pre_order()), rtol=1e-15) @@ -711,22 +703,20 @@ def test_correspond_empty(self, xp): Z = xp.zeros((0,4), dtype=xp.float64) assert_raises(ValueError, correspond, Z, y) - @use_linkage def test_correspond_2_and_up(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. for i in range(2, 4): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) assert_(correspond(Z, y)) for i in range(4, 15, 3): y = np.random.rand(i*(i-1)//2) + Z = xp.asarray(linkage(y)) y = xp.asarray(y) - Z = linkage(y) assert_(correspond(Z, y)) - @use_linkage def test_correspond_4_and_up(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. Correspondence should be false. @@ -734,14 +724,13 @@ def test_correspond_4_and_up(self, xp): list(zip(list(range(3, 5)), list(range(2, 4))))): y = np.random.rand(i*(i-1)//2) y2 = np.random.rand(j*(j-1)//2) + Z = xp.asarray(linkage(y)) + Z2 = xp.asarray(linkage(y2)) y = xp.asarray(y) y2 = xp.asarray(y2) - Z = linkage(y) - Z2 = linkage(y2) assert not correspond(Z, y2) assert not correspond(Z2, y) - @use_linkage def test_correspond_4_and_up_2(self, xp): # Tests correspond(Z, y) on linkage and CDMs over observation sets of # different sizes. Correspondence should be false. @@ -749,10 +738,10 @@ def test_correspond_4_and_up_2(self, xp): list(zip(list(range(2, 7)), list(range(16, 21))))): y = np.random.rand(i*(i-1)//2) y2 = np.random.rand(j*(j-1)//2) + Z = xp.asarray(linkage(y)) + Z2 = xp.asarray(linkage(y2)) y = xp.asarray(y) y2 = xp.asarray(y2) - Z = linkage(y) - Z2 = linkage(y2) assert not correspond(Z, y2) assert not correspond(Z2, y) @@ -809,27 +798,24 @@ def test_is_monotonic_3x4_F3(self, xp): [4, 5, 0.2, 4]], dtype=xp.float64) assert not is_monotonic(Z) - @use_linkage def test_is_monotonic_tdist_linkage1(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # tdist data set. Expecting True. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) assert is_monotonic(Z) - @use_linkage def test_is_monotonic_tdist_linkage2(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # tdist data set. Perturbing. Expecting False. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) Z = xpx.at(Z)[2, 2].set(0.0) assert not is_monotonic(Z) - @use_linkage def test_is_monotonic_Q_linkage(self, xp): # Tests is_monotonic(Z) on clustering generated by single linkage on # Q data set. Expecting True. - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, 'single') + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, 'single')) assert is_monotonic(Z) @@ -848,22 +834,20 @@ def test_maxdists_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_distances(Z, xp) xp_assert_close(MD, expectedMD, atol=1e-15) - def test_maxdists_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - self.check_maxdists_Q_linkage(method, xp) - - def check_maxdists_Q_linkage(self, method, xp): + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxdists_Q_linkage(self, method, xp): # Tests maxdists(Z) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) - Z = linkage(X, method) + X = hierarchy_test_data.Q_X + Z = xp.asarray(linkage(X, method)) MD = maxdists(Z) expectedMD = calculate_maximum_distances(Z, xp) xp_assert_close(MD, expectedMD, atol=1e-15) +@skip_xp_backends(cpu_only=True) class TestMaxInconsts: - @skip_xp_backends(cpu_only=True) def test_maxinconsts_empty_linkage(self, xp): # Tests maxinconsts(Z, R) on empty linkage. Expecting exception. Z = xp.zeros((0, 4), dtype=xp.float64) @@ -878,7 +862,6 @@ def test_maxinconsts_difrow_linkage(self, xp): R = xp.asarray(R) assert_raises(ValueError, maxinconsts, Z, R) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") def test_maxinconsts_one_cluster_linkage(self, xp): # Tests maxinconsts(Z, R) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -887,52 +870,42 @@ def test_maxinconsts_one_cluster_linkage(self, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, xp=xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") - def test_maxinconsts_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - self.check_maxinconsts_Q_linkage(method, xp) - - def check_maxinconsts_Q_linkage(self, method, xp): + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxinconsts_Q_linkage(self, method, xp): # Tests maxinconsts(Z, R) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) + X = hierarchy_test_data.Q_X Z = linkage(X, method) - R = inconsistent(Z) + R = xp.asarray(inconsistent(Z)) + Z = xp.asarray(Z) MD = maxinconsts(Z, R) expectedMD = calculate_maximum_inconsistencies(Z, R, xp=xp) xp_assert_close(MD, expectedMD, atol=1e-15) +@skip_xp_backends(cpu_only=True) class TestMaxRStat: def test_maxRstat_invalid_index(self, xp): - for i in [3.3, -1, 4]: - self.check_maxRstat_invalid_index(i, xp) - - def check_maxRstat_invalid_index(self, i, xp): # Tests maxRstat(Z, R, i). Expecting exception. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) R = xp.asarray([[0, 0, 0, 0.3]], dtype=xp.float64) - if isinstance(i, int): - assert_raises(ValueError, maxRstat, Z, R, i) - else: - assert_raises(TypeError, maxRstat, Z, R, i) - - @skip_xp_backends(cpu_only=True) - def test_maxRstat_empty_linkage(self, xp): - for i in range(4): - self.check_maxRstat_empty_linkage(i, xp) - - def check_maxRstat_empty_linkage(self, i, xp): + with pytest.raises(TypeError): + maxRstat(Z, R, 3.3) + with pytest.raises(ValueError): + maxRstat(Z, R, -1) + with pytest.raises(ValueError): + maxRstat(Z, R, 4) + + @pytest.mark.parametrize("i", range(4)) + def test_maxRstat_empty_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on empty linkage. Expecting exception. Z = xp.zeros((0, 4), dtype=xp.float64) R = xp.zeros((0, 4), dtype=xp.float64) assert_raises(ValueError, maxRstat, Z, R, i) - def test_maxRstat_difrow_linkage(self, xp): - for i in range(4): - self.check_maxRstat_difrow_linkage(i, xp) - - def check_maxRstat_difrow_linkage(self, i, xp): + @pytest.mark.parametrize("i", range(4)) + def test_maxRstat_difrow_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on linkage and inconsistency matrices with # different numbers of clusters. Expecting exception. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) @@ -940,12 +913,7 @@ def check_maxRstat_difrow_linkage(self, i, xp): R = xp.asarray(R) assert_raises(ValueError, maxRstat, Z, R, i) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") def test_maxRstat_one_cluster_linkage(self, xp): - for i in range(4): - self.check_maxRstat_one_cluster_linkage(i, xp) - - def check_maxRstat_one_cluster_linkage(self, i, xp): # Tests maxRstat(Z, R, i) on linkage with one cluster. Z = xp.asarray([[0, 1, 0.3, 4]], dtype=xp.float64) R = xp.asarray([[0, 0, 0, 0.3]], dtype=xp.float64) @@ -953,17 +921,14 @@ def check_maxRstat_one_cluster_linkage(self, i, xp): expectedMD = calculate_maximum_inconsistencies(Z, R, 1, xp) xp_assert_close(MD, expectedMD, atol=1e-15) - @skip_xp_backends(cpu_only=True, reason="implicit device->host transfer") - def test_maxRstat_Q_linkage(self, xp): - for method in ['single', 'complete', 'ward', 'centroid', 'median']: - for i in range(4): - self.check_maxRstat_Q_linkage(method, i, xp) - - def check_maxRstat_Q_linkage(self, method, i, xp): - # Tests maxRstat(Z, R, i) on the Q data set - X = xp.asarray(hierarchy_test_data.Q_X) + @pytest.mark.parametrize( + "method", ['single', 'complete', 'ward', 'centroid', 'median']) + def test_maxRstat_Q_linkage(self, method, xp): + # Tests maxRstat(Z, R, 1) on the Q data set + X = hierarchy_test_data.Q_X Z = linkage(X, method) - R = inconsistent(Z) + R = xp.asarray(inconsistent(Z)) + Z = xp.asarray(Z) MD = maxRstat(Z, R, 1) expectedMD = calculate_maximum_inconsistencies(Z, R, 1, xp) xp_assert_close(MD, expectedMD, atol=1e-15) @@ -974,18 +939,18 @@ class TestDendrogram: def test_dendrogram_single_linkage_tdist(self, xp): # Tests dendrogram calculation on single linkage of the tdist data set. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) R = dendrogram(Z, no_plot=True) leaves = R["leaves"] assert_equal(leaves, [2, 5, 1, 0, 3, 4]) def test_valid_orientation(self, xp): - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) assert_raises(ValueError, dendrogram, Z, orientation="foo") def test_labels_as_array_or_list(self, xp): # test for gh-12418 - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) labels = [1, 3, 2, 6, 4, 5] result1 = dendrogram(Z, labels=xp.asarray(labels), no_plot=True) result2 = dendrogram(Z, labels=labels, no_plot=True) @@ -1019,13 +984,10 @@ def test_valid_label_size(self, xp): reason='dask.array has bad interaction with matplotlib' ) @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") - def test_dendrogram_plot(self, xp): - for orientation in ['top', 'bottom', 'left', 'right']: - self.check_dendrogram_plot(orientation, xp) - - def check_dendrogram_plot(self, orientation, xp): + @pytest.mark.parametrize("orientation", ['top', 'bottom', 'left', 'right']) + def test_dendrogram_plot(self, orientation, xp): # Tests dendrogram plotting. - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) expected = {'color_list': ['C1', 'C0', 'C0', 'C0', 'C0'], 'dcoord': [[0.0, 138.0, 138.0, 0.0], [0.0, 219.0, 219.0, 0.0], @@ -1094,7 +1056,7 @@ def check_dendrogram_plot(self, orientation, xp): ) @pytest.mark.skipif(not have_matplotlib, reason="no matplotlib") def test_dendrogram_truncate_mode(self, xp): - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) R = dendrogram(Z, 2, 'lastp', show_contracted=True) plt.close() @@ -1130,13 +1092,13 @@ def dendrogram_lock(self): def test_dendrogram_colors(self, xp, dendrogram_lock): # Tests dendrogram plots with alternate colors - Z = linkage(xp.asarray(hierarchy_test_data.ytdist), 'single') + Z = xp.asarray(linkage(hierarchy_test_data.ytdist, 'single')) with dendrogram_lock: # Global color palette might be changed concurrently set_link_color_palette(['c', 'm', 'y', 'k']) R = dendrogram(Z, no_plot=True, - above_threshold_color='g', color_threshold=250) + above_threshold_color='g', color_threshold=250) set_link_color_palette(['g', 'r', 'c', 'm', 'y', 'k']) color_list = R['color_list'] @@ -1148,14 +1110,14 @@ def test_dendrogram_colors(self, xp, dendrogram_lock): def test_dendrogram_leaf_colors_zero_dist(self, xp): # tests that the colors of leafs are correct for tree # with two identical points - x = xp.asarray([[1, 0, 0], + X = np.asarray([[1, 0, 0], [0, 0, 1], [0, 2, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0]]) - z = linkage(x, "single") - d = dendrogram(z, no_plot=True) + Z = xp.asarray(linkage(X, "single")) + d = dendrogram(Z, no_plot=True) exp_colors = ['C0', 'C1', 'C1', 'C0', 'C2', 'C2'] colors = d["leaves_color_list"] assert_equal(colors, exp_colors) @@ -1163,14 +1125,14 @@ def test_dendrogram_leaf_colors_zero_dist(self, xp): def test_dendrogram_leaf_colors(self, xp): # tests that the colors are correct for a tree # with two near points ((0, 0, 1.1) and (0, 0, 1)) - x = xp.asarray([[1, 0, 0], + X = np.asarray([[1, 0, 0], [0, 0, 1.1], [0, 2, 0], [0, 0, 1], [0, 1, 0], [0, 1, 0]]) - z = linkage(x, "single") - d = dendrogram(z, no_plot=True) + Z = xp.asarray(linkage(X, "single")) + d = dendrogram(Z, no_plot=True) exp_colors = ['C0', 'C1', 'C1', 'C0', 'C2', 'C2'] colors = d["leaves_color_list"] assert_equal(colors, exp_colors) @@ -1215,33 +1177,12 @@ def calculate_maximum_inconsistencies(Z, R, k=3, xp=np): return B -@pytest.mark.thread_unsafe -@use_linkage -@skip_xp_backends(eager_only=True) -def test_unsupported_uncondensed_distance_matrix_linkage_warning(xp): - assert_warns(ClusterWarning, linkage, xp.asarray([[0, 1], [1, 0]])) - - -def test_euclidean_linkage_value_error(xp): - for method in _EUCLIDEAN_METHODS: - assert_raises(ValueError, linkage, xp.asarray([[1, 1], [1, 1]]), - method=method, metric='cityblock') - - -@use_linkage -def test_2x2_linkage(xp): - Z1 = linkage(xp.asarray([1]), method='single', metric='euclidean') - Z2 = linkage(xp.asarray([[0, 1], [0, 0]]), method='single', metric='euclidean') - xp_assert_close(Z1, Z2, rtol=1e-15) - - @skip_xp_backends(cpu_only=True) def test_node_compare(xp): np.random.seed(23) nobs = 50 X = np.random.randn(nobs, 4) - X = xp.asarray(X) - Z = ward(X) + Z = xp.asarray(ward(X)) tree = to_tree(Z) assert_(tree > tree.get_left()) assert_(tree.get_right() > tree.get_left()) @@ -1254,8 +1195,7 @@ def test_cut_tree(xp): np.random.seed(23) nobs = 50 X = np.random.randn(nobs, 4) - X = xp.asarray(X) - Z = ward(X) + Z = xp.asarray(ward(X)) cutree = cut_tree(Z) # cutree.dtype varies between int32 and int64 over platforms @@ -1281,13 +1221,13 @@ def test_cut_tree(xp): @skip_xp_backends(cpu_only=True) def test_optimal_leaf_ordering(xp): # test with the distance vector y - Z = optimal_leaf_ordering(linkage(xp.asarray(hierarchy_test_data.ytdist)), + Z = optimal_leaf_ordering(xp.asarray(linkage(hierarchy_test_data.ytdist)), xp.asarray(hierarchy_test_data.ytdist)) expectedZ = hierarchy_test_data.linkage_ytdist_single_olo xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-10) # test with the observation matrix X - Z = optimal_leaf_ordering(linkage(xp.asarray(hierarchy_test_data.X), 'ward'), + Z = optimal_leaf_ordering(xp.asarray(linkage(hierarchy_test_data.X, 'ward')), xp.asarray(hierarchy_test_data.X)) expectedZ = hierarchy_test_data.linkage_X_ward_olo xp_assert_close(Z, xp.asarray(expectedZ), atol=1e-06) @@ -1324,13 +1264,3 @@ def test_Heap(xp): pair = heap.get_min() assert_equal(pair['key'], 1) assert_equal(pair['value'], 10) - - -@use_linkage -@skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") -def test_centroid_neg_distance(xp): - # gh-21011 - values = xp.asarray([0, 0, -1]) - with pytest.raises(ValueError): - # This is just checking that this doesn't crash - linkage(values, method='centroid') From f350e02b2b47051978a041520f2c9b7df9d7e070 Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:00:55 -0600 Subject: [PATCH 179/251] TST: DIA + DIA format and cases not covered by common add tests --- scipy/sparse/tests/test_base.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index d89bdf586b8e..be6f13a3cc63 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -4985,6 +4985,21 @@ def test_tocoo_tocsr_tocsc_gh19245(self): csc = dia.tocsc() assert csc.indices.dtype == np.int32 + def test_add_sparse(self): + # test format and cases not covered by common add tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + + Csp = Asp + Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), A + B) + + Csp = Bsp + Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), B + A) + def test_mul_scalar(self): # repro for gh-20434 m = self.dia_container([[1, 2], [0, 4]]) From 96a8d00124491e7430186d48f5966e5746e6134d Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:10:46 -0600 Subject: [PATCH 180/251] ENH: implement DIA - DIA by generalizing _add_sparse --- scipy/sparse/_dia.py | 29 +++++++++++++++++++++++------ scipy/sparse/tests/test_base.py | 19 +++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index e5508f9c5c8e..aecef0cce02c 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -175,14 +175,15 @@ def sum(self, axis=None, dtype=None, out=None): sum.__doc__ = _spbase.sum.__doc__ - def _add_sparse(self, other): + def _add_sparse(self, other, sub=False): # If other is not DIA format, let them handle us instead. if not isinstance(other, _dia_base): return other._add_sparse(self) # Fast path for exact equality of the sparsity structure. if np.array_equal(self.offsets, other.offsets): - return self._with_data(self.data + other.data) + return self._with_data(self.data - other.data if sub else + self.data + other.data) # Find the union of the offsets (which will be sorted and unique). new_offsets = np.union1d(self.offsets, other.offsets) @@ -195,23 +196,39 @@ def _add_sparse(self, other): # permutation of the existing offsets and the diagonal lengths match. if self_d == other_d and len(new_offsets) == len(self.offsets): new_data = self.data[_invert_index(self_idx)] - new_data[other_idx, :] += other.data + if sub: + new_data[other_idx, :] -= other.data + else: + new_data[other_idx, :] += other.data elif self_d == other_d and len(new_offsets) == len(other.offsets): - new_data = other.data[_invert_index(other_idx)] + if sub: + new_data = -other.data[_invert_index(other_idx)] + else: + new_data = other.data[_invert_index(other_idx)] new_data[self_idx, :] += self.data else: # Maximum diagonal length of the result. d = min(self.shape[0] + new_offsets[-1], self.shape[1]) - # Add all diagonals to a freshly-allocated data array. + # Add all diagonals to a freshly allocated data array. new_data = np.zeros( (len(new_offsets), d), dtype=np.result_type(self.data, other.data), ) new_data[self_idx, :self_d] += self.data[:, :d] - new_data[other_idx, :other_d] += other.data[:, :d] + if sub: + new_data[other_idx, :other_d] -= other.data[:, :d] + else: + new_data[other_idx, :other_d] += other.data[:, :d] return self._dia_container((new_data, new_offsets), shape=self.shape) + def _sub_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._sub_sparse(other) + + return self._add_sparse(other, sub=True) + def _mul_scalar(self, other): return self._with_data(self.data * other) diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index be6f13a3cc63..ca17c2dcbd9b 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -5000,6 +5000,25 @@ def test_add_sparse(self): assert isinstance(Csp, dia_matrix) assert_array_equal(Csp.toarray(), B + A) + def test_sub_sparse(self): + # test format and cases not covered by common sub tests + A = diag([1, 2]) + B = A + diag([3], 1) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + + Csp = Asp - Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), A - B) + + Csp = Bsp - Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), B - A) + + Bsp = Bsp.asformat('csr') + assert_array_equal((Asp - Bsp).toarray(), A - B) + assert_array_equal((Bsp - Asp).toarray(), B - A) + def test_mul_scalar(self): # repro for gh-20434 m = self.dia_container([[1, 2], [0, 4]]) From 03c8dc14787f18939efee987fd82e5a725c61522 Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:23:11 -0600 Subject: [PATCH 181/251] ENH: implement DIA @ DIA (matrix multiplication) --- scipy/sparse/_dia.py | 18 +++- scipy/sparse/_generate_sparsetools.py | 1 + scipy/sparse/sparsetools/dia.h | 114 ++++++++++++++++++++++++++ scipy/sparse/tests/test_base.py | 44 ++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index aecef0cce02c..6755247aece8 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -13,7 +13,7 @@ from ._sputils import ( isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape ) -from ._sparsetools import dia_matvec +from ._sparsetools import dia_matmat, dia_matvec class _dia_base(_data_matrix): @@ -247,6 +247,22 @@ def _matmul_vector(self, other): return y + def _matmul_sparse(self, other): + # If other is not DIA format, use default handler. + if not isinstance(other, _dia_base): + return super()._matmul_sparse(other) + + # If any dimension is zero, return empty array immediately. + if 0 in self.shape or 0 in other.shape: + return self._dia_container((self.shape[0], other.shape[1])) + + offsets, data = dia_matmat(*self.shape, *self.data.shape, + self.offsets, self.data, + other.shape[1], *other.data.shape, + other.offsets, other.data) + return self._dia_container((data.reshape(len(offsets), -1), offsets), + (self.shape[0], other.shape[1])) + def _setdiag(self, values, k=0): M, N = self.shape diff --git a/scipy/sparse/_generate_sparsetools.py b/scipy/sparse/_generate_sparsetools.py index 4300d77bf552..1e2e2a35cb8e 100644 --- a/scipy/sparse/_generate_sparsetools.py +++ b/scipy/sparse/_generate_sparsetools.py @@ -107,6 +107,7 @@ coo_todense v iilIIT*Ti coo_todense_nd v IllIT*Ti coo_matvec v lIITT*T +dia_matmat v iiiiITiiiIT*V*W coo_matvec_nd v llIITT*T coo_matmat_dense v llIITT*T coo_matmat_dense_nd v lllIIITT*T diff --git a/scipy/sparse/sparsetools/dia.h b/scipy/sparse/sparsetools/dia.h index 9700ec3c1849..9e4c5503e382 100644 --- a/scipy/sparse/sparsetools/dia.h +++ b/scipy/sparse/sparsetools/dia.h @@ -2,6 +2,120 @@ #define __DIA_H__ #include +#include + +using std::min, std::max; + +template +T min_el(const T vec[], I len) { + return *std::min_element(vec, vec + len); +} + +template +T max_el(const T vec[], I len) { + return *std::max_element(vec, vec + len); +} + + +/* + * Compute DIA matrix output = A * B for DIA matrices A, B + * + * + * Input Arguments: + * I A_rows - number of rows in A + * I A_cols - number of columns in A + * I A_diags - number of diagonals in A + * I A_L - length of each diagonal in A + * I A_offsets[A_diags] - diagonal offsets in A + * T A_data[A_diags,A_L] - diagonals data of A + * I B_cols - number of columns in B + * I B_diags - number of diagonals in B + * I B_L - length of each diagonal in B + * I B_offsets[B_diags] - diagonal offsets in B + * T B_data[B_diags,B_L] - diagonals data of B + * + * Output Arguments: + * V offsets - diagonal offsets + * W data - diagonals data + * + * Note: + * Number of diagonals in output (diags) is the length of output offsets; + * length of each diagonal (L) equals B_L; data dimensions are [diags,L] + * Negative offsets correspond to lower diagonals + * Positive offsets correspond to upper diagonals + * + */ +template +void dia_matmat(const I A_rows, + const I A_cols, + const I A_diags, + const I A_L, + const I A_offsets[], + const T A_data[], + const I B_cols, + const I B_diags, + const I B_L, + const I B_offsets[], + const T B_data[], + std::vector* offsets, + std::vector* data) +{ + const I B_rows = A_cols, + rows = A_rows, cols = B_cols, + L = min(cols, B_L); + + // range of combinations of input offsets + const I min_map_ofs = min_el(A_offsets, A_diags) + min_el(B_offsets, B_diags), + max_map_ofs = max_el(A_offsets, A_diags) + max_el(B_offsets, B_diags); + // limits for output offsets + const I min_ofs = max(min_map_ofs, 1 - rows), + max_ofs = min(max_map_ofs, L - 1); + // mark output offsets + std::vector buf(max_map_ofs - min_map_ofs + 1); + // (alias to buf indexable from [min_map_ofs] to [max_map_ofs]; + // only [min_ofs] to [max_ofs] will be used later) + I* ofs_map = buf.data() - min_map_ofs; + for (I i = 0; i < A_diags; ++i) + for (I j = 0; j < B_diags; ++j) + ofs_map[A_offsets[i] + B_offsets[j]] = I(true); + // enumerate marks + offsets->resize(max_ofs - min_ofs + 1); + I N_ofs = 0; + for (I ofs = min_ofs; ofs <= max_ofs; ++ofs) + if (ofs_map[ofs]) { + (*offsets)[N_ofs] = ofs; + ofs_map[ofs] = N_ofs; + ++N_ofs; + } + offsets->resize(N_ofs); + + // allocate output diagonals, filled with zeros + data->resize(N_ofs * L); + // loop over diagonals in B + for (I B_i = 0; B_i < B_diags; ++B_i) { + const I B_ofs = B_offsets[B_i]; + const T* B_diag_r = B_data + npy_intp(B_L) * B_i + B_ofs; // row-indexed + // span of data within current (row-indexed) B diagonal + const I B_j_beg = max(-B_ofs, I(0)), + B_j_end = min(min(B_cols, B_L) - B_ofs, B_rows); + // loop over diagonals in A + for (I A_i = 0; A_i < A_diags; ++A_i) { + const I A_ofs = A_offsets[A_i]; + const T* A_diag_c = A_data + npy_intp(A_L) * A_i; // column-indexed + // output offset and corresponding diagonal + const I ofs = A_ofs + B_ofs; + if (ofs < min_ofs or ofs > max_ofs) + continue; + T* diag_r = data->data() + npy_intp(L) * ofs_map[ofs] + B_ofs; // row-indexed + // overlapping span of data within current B and A diagonals + const I j_beg = max(B_j_beg, A_ofs), + j_end = min({B_j_end, min(A_cols, A_L), A_rows + A_ofs}); + // add partial product to output + for (I j = j_beg; j < j_end; ++j) + diag_r[j] += A_diag_c[j] * B_diag_r[j]; + } + } +} /* diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index ca17c2dcbd9b..8886055bac97 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -5030,6 +5030,50 @@ def test_mul_scalar(self): assert isinstance(res2, m.__class__) assert_array_equal(res2.toarray(), [[3, 6], [0, 12]]) + def test_matmul_dia(self): + # test DIA structure of DIA @ DIA: + + # that all and only needed elements are used and produced + A = array([[1, 2, 3], + [4, 5, 6]]) + B = array([[11, 12], + [13, 14], + [15, 16]]) + Asp = dia_matrix(A) + Bsp = dia_matrix(B) + Asp.data[Asp.data == 0] = -1 # poison outside elements + Bsp.data[Bsp.data == 0] = -1 + assert_array_equal(Asp.toarray(), A) + assert_array_equal(Bsp.toarray(), B) + + C = A @ B + Csp = Asp @ Bsp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-1, 0, 1]) + assert_array_equal(Csp.data, dia_matrix(C).data) + + C = B @ A + Csp = Bsp @ Asp + assert isinstance(Csp, dia_matrix) + assert_array_equal(Csp.toarray(), C) + assert_array_equal(Csp.offsets, [-2, -1, 0, 1, 2]) + assert_array_equal(Csp.data, dia_matrix(C).data) + + # short data and that order of input offsets doesn't matter + Asp = dia_matrix(([[0., 1., 2.], [3., 4., 5.]], [1, -2]), (5, 5)) + Bsp = dia_matrix(([[6., 7., 8.], [0., 0., 9.]], [-1, 2]), (5, 5)) + + Csp = Asp @ Bsp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 35., 0.], + [6., 14., 27.]]) + + Csp = Bsp @ Asp + assert_array_equal(Csp.offsets, array([-3, 0])) + assert_array_equal(Csp.data, [[24., 0., 0.], + [27., 6., 14.]]) + class TestDIAMatrix(_MatrixMixin, TestDIA): spcreator = dia_matrix From ffbefbb11be12060e7e0998eb16bbe6e6e188aa6 Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:31:56 -0600 Subject: [PATCH 182/251] ENH: implement DIA * DIA and DIA * dense (element-wise multiplication) --- scipy/sparse/_dia.py | 52 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 6755247aece8..10d606e46b99 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -11,7 +11,8 @@ from ._base import issparse, _formats, _spbase, sparray from ._data import _data_matrix from ._sputils import ( - isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape + isdense, isscalarlike, isshape, upcast_char, getdtype, get_sum_dtype, + validateaxis, check_shape ) from ._sparsetools import dia_matmat, dia_matvec @@ -232,6 +233,55 @@ def _sub_sparse(self, other): def _mul_scalar(self, other): return self._with_data(self.data * other) + def multiply(self, other): + if isscalarlike(other): + return self._mul_scalar(other) + + if isdense(other): + if other.ndim > 2: + return self.toarray() * other + + # Use default handler for pathological cases. + if 0 in self.shape or 1 in self.shape or 0 in other.shape: + return super().multiply(other) + + other = np.atleast_2d(other) + other_rows, other_cols = other.shape + rows, cols = self.shape + L = min(self.data.shape[1], cols) + data = self.data[:, :L].astype(np.result_type(self.data, other)) + if other_rows == 1: + data *= other[0, :L] + elif other_rows != rows: + raise ValueError('inconsistent shapes') + else: + j = np.arange(L) + if L > rows: + i = (j - self.offsets[:, None]) % rows + else: # can use faster method + i = j - self.offsets[:, None] % rows + if other_cols == 1: + j = 0 + elif other_cols != cols: + raise ValueError('inconsistent shapes') + data *= other[i, j] + return self._with_data(data) + + # If other is not DIA format or needs broadcasting (unreasonable + # use case for DIA anyway), use default handler. + if not isinstance(other, _dia_base) or other.shape != self.shape: + return super().multiply(other) + + # Find common offsets (unique diagonals don't contribute) + # and indices corresponding to them in multiplicand and multiplier. + offsets, self_idx, other_idx = \ + np.intersect1d(self.offsets, other.offsets, + assume_unique=True, return_indices=True) + # Only overlapping length of diagonals can have non-zero products. + L = min(self.data.shape[1], other.data.shape[1]) + data = self.data[self_idx, :L] * other.data[other_idx, :L] + return self._dia_container((data, offsets), shape=self.shape) + def _matmul_vector(self, other): x = other From abf8a6c3913883539b11a333dabc914aff65096f Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:40:43 -0600 Subject: [PATCH 183/251] ENH: implement DIA @ dense (matrix multiplication, _matmul_multivector) --- scipy/sparse/_dia.py | 9 ++++- scipy/sparse/_generate_sparsetools.py | 1 + scipy/sparse/sparsetools/dia.h | 57 +++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index 10d606e46b99..30fbdcbee7f3 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -14,7 +14,7 @@ isdense, isscalarlike, isshape, upcast_char, getdtype, get_sum_dtype, validateaxis, check_shape ) -from ._sparsetools import dia_matmat, dia_matvec +from ._sparsetools import dia_matmat, dia_matvec, dia_matvecs class _dia_base(_data_matrix): @@ -297,6 +297,13 @@ def _matmul_vector(self, other): return y + def _matmul_multivector(self, other): + res = np.zeros((self.shape[0], other.shape[1]), + dtype=np.result_type(self.data, other)) + dia_matvecs(*self.shape, *self.data.shape, self.offsets, self.data, + other.shape[1], other, res) + return res + def _matmul_sparse(self, other): # If other is not DIA format, use default handler. if not isinstance(other, _dia_base): diff --git a/scipy/sparse/_generate_sparsetools.py b/scipy/sparse/_generate_sparsetools.py index 1e2e2a35cb8e..423a1c2dc5e5 100644 --- a/scipy/sparse/_generate_sparsetools.py +++ b/scipy/sparse/_generate_sparsetools.py @@ -112,6 +112,7 @@ coo_matmat_dense v llIITT*T coo_matmat_dense_nd v lllIIITT*T dia_matvec v iiiiITT*T +dia_matvecs v iiiiITiT*T cs_graph_components i iII*I """ diff --git a/scipy/sparse/sparsetools/dia.h b/scipy/sparse/sparsetools/dia.h index 9e4c5503e382..8a2ceccb4f1a 100644 --- a/scipy/sparse/sparsetools/dia.h +++ b/scipy/sparse/sparsetools/dia.h @@ -170,4 +170,61 @@ void dia_matvec(const I n_row, } +/* + * Compute output += A * B for DIA matrix A and dense matrices B, output + * + * + * Input Arguments: + * I A_rows - number of rows in A + * I A_cols - number of columns in A + * I A_diags - number of diagonals in A + * I A_L - length of each diagonal in A + * I offsets[A_diags] - diagonal offsets in A + * T A_data[A_diags,A_L] - diagonals data of A + * I B_cols - number of columns in B + * T B_data[A_rows,B_cols] - data of B (in C order) + * + * Output Arguments: + * T data[A_rows,B_cols] - output data (in C order) + * + * Note: + * Output array data must be preallocated + * Number of rows in B must be equal A_cols + * Negative offsets correspond to lower diagonals + * Positive offsets correspond to upper diagonals + * + */ +template +void dia_matvecs(const I A_rows, + const I A_cols, + const I A_diags, + const I A_L, + const I A_offsets[], + const T A_data[], + const I B_cols, + const T B_data[], + T data[]) +{ + const I rows = A_rows, cols = B_cols, + k_end = min(A_cols, A_L); // for index along A columns and B rows + // loop over output rows + for (I i = 0; i < rows; ++i) { + T* row = data + npy_intp(cols) * i; + // loop over diagonals in A + for (I n = 0; n < A_diags; ++n) { + const I k = i + A_offsets[n]; + if (k < 0 or k >= k_end) + continue; + // element at i-th row, k-th column in A + const T a = (A_data + npy_intp(A_L) * n)[k]; + // k-th row in B + const T* B_row = B_data + npy_intp(B_cols) * k; + // loop over columns in current output row + for (I j = 0; j < cols; ++j) + row[j] += a * B_row[j]; + } + } +} + + #endif From 81b6f5b6cc0016be1c7e68731ee5208121678d1d Mon Sep 17 00:00:00 2001 From: MikhailRyazanov Date: Sat, 13 Jul 2024 21:47:17 -0600 Subject: [PATCH 184/251] MAINT: code readability Avoid trailing spaces, mixed tab/spaces indentation, unspaced operators. --- scipy/sparse/sparsetools/dia.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/scipy/sparse/sparsetools/dia.h b/scipy/sparse/sparsetools/dia.h index 8a2ceccb4f1a..75b951dbb47c 100644 --- a/scipy/sparse/sparsetools/dia.h +++ b/scipy/sparse/sparsetools/dia.h @@ -119,7 +119,7 @@ void dia_matmat(const I A_rows, /* - * Compute Y += A*X for DIA matrix A and dense vectors X,Y + * Compute Y += A * X for DIA matrix A and dense vectors X, Y * * * Input Arguments: @@ -145,25 +145,25 @@ void dia_matvec(const I n_row, const I n_col, const I n_diags, const I L, - const I offsets[], - const T diags[], - const T Xx[], - T Yx[]) + const I offsets[], + const T diags[], + const T Xx[], + T Yx[]) { - for(I i = 0; i < n_diags; i++){ + for (I i = 0; i < n_diags; i++){ const I k = offsets[i]; //diagonal offset - const I i_start = std::max(0,-k); - const I j_start = std::max(0, k); - const I j_end = std::min(std::min(n_row + k, n_col),L); + const I i_start = max(0, -k); + const I j_start = max(0, k); + const I j_end = min(min(n_row + k, n_col), L); const I N = j_end - j_start; //number of elements to process - const T * diag = diags + (npy_intp)i*L + j_start; + const T * diag = diags + (npy_intp)i * L + j_start; const T * x = Xx + j_start; T * y = Yx + i_start; - for(I n = 0; n < N; n++){ + for (I n = 0; n < N; n++) { y[n] += diag[n] * x[n]; } } From bd0388c382042f03f5c750c60bea277745c61240 Mon Sep 17 00:00:00 2001 From: Swastik Mishra <4453983+swstkm@users.noreply.github.com> Date: Wed, 14 May 2025 02:25:36 +0200 Subject: [PATCH 185/251] ENH: `optimize.root`: add warning for invalid inner parameters in `newton_krylov` method (#22809) --------- Co-authored-by: Lucas Colley Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/optimize/_nonlin.py | 34 ++++++++++++++++++++++++++- scipy/optimize/tests/test_nonlin.py | 36 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/scipy/optimize/_nonlin.py b/scipy/optimize/_nonlin.py index 7d873765098e..0d7ea805fc70 100644 --- a/scipy/optimize/_nonlin.py +++ b/scipy/optimize/_nonlin.py @@ -15,6 +15,8 @@ from scipy._lib._util import copy_if_needed from scipy._lib._util import getfullargspec_no_self as _getfullargspec from ._linesearch import scalar_search_wolfe1, scalar_search_armijo +from inspect import signature +from difflib import get_close_matches __all__ = [ @@ -1491,9 +1493,39 @@ def __init__(self, rdiff=None, method='lgmres', inner_maxiter=20, self.method_kw.setdefault('store_outer_Av', False) self.method_kw.setdefault('atol', 0) + # Retrieve the signature of the method to find the valid parameters + valid_inner_params = [ + k for k in signature(self.method).parameters + if k not in ('self', 'args', 'kwargs') + ] + for key, value in kw.items(): - if not key.startswith('inner_'): + if not key.startswith("inner_"): raise ValueError(f"Unknown parameter {key}") + if key[6:] not in valid_inner_params: + # Use difflib to find close matches to the invalid key + inner_param_suggestions = get_close_matches(key[6:], + valid_inner_params, + n=1) + if inner_param_suggestions: + suggestion_msg = (f" Did you mean '" + f"{inner_param_suggestions[0]}'?") + else: + suggestion_msg = "" + + # warn user that the parameter is not valid for the inner method + warnings.warn( + f"Option '{key}' is invalid for the inner method: {method}." + " It will be ignored." + "Please check inner method documentation for valid options." + + suggestion_msg, + stacklevel=3, + category=UserWarning, + # using `skip_file_prefixes` would be a good idea + # and should be added once we drop support for Python 3.11 + ) + # ignore this parameter and continue + continue self.method_kw[key[6:]] = value def _update_diff_step(self): diff --git a/scipy/optimize/tests/test_nonlin.py b/scipy/optimize/tests/test_nonlin.py index e5eb094c1590..cffb9d104801 100644 --- a/scipy/optimize/tests/test_nonlin.py +++ b/scipy/optimize/tests/test_nonlin.py @@ -12,6 +12,7 @@ from numpy.linalg import inv import numpy as np import scipy +from scipy.sparse.linalg import minres from .test_minpack import pressure_network @@ -217,6 +218,41 @@ def wont_converge(x): with pytest.raises(scipy.optimize.NoConvergence): nonlin.newton_krylov(wont_converge, xin=[0], maxiter=1) + def test_warnings_invalid_inner_param(self): + """ + Test for ENH #21986, for behavior of `nonlin.newton_krylov` + Test the following scenarios: + 1. Raise warning for invalid inner param + 2. No warning for valid inner param + 3. No warning for user-provided callable method + """ + # This should raise exactly one warning + # (`inner_atol` is not valid for `minres`) + with pytest.warns(UserWarning, + match="Please check inner method documentation"): + nonlin.newton_krylov(F, F.xin, method="minres", inner_atol=1e-5) + + # This should not raise a warning (`minres` without `inner_atol`, + # but with `inner_maxiter` which is valid) + nonlin.newton_krylov(F, F.xin, method="minres", inner_maxiter=100, + inner_callback= lambda _ : ...) + + # Test newton_krylov with a user-provided callable method + def user_provided_callable_method_enh_21986(op, rhs, **kwargs): + """A dummy user-provided callable method for testing.""" + # Return a dummy result (mimicking minres) + return minres(op, rhs, **kwargs) + # This should not raise any warnings + nonlin.newton_krylov(F, F.xin, + method=user_provided_callable_method_enh_21986) + + def test_non_inner_prefix(self): + with pytest.raises(ValueError, + match="Unknown parameter" + ): + # Pass a parameter without 'inner_' prefix + nonlin.newton_krylov(F, F.xin, method="minres", invalid_param=1e-5) + class TestSecant: """Check that some Jacobian approximations satisfy the secant condition""" From e61cc469363571fef4c090418893554521c5f3dc Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Wed, 14 May 2025 02:18:54 +0100 Subject: [PATCH 186/251] MAINT: special: remove test_compiles_in_cupy (#22984) --- scipy/special/tests/meson.build | 1 - scipy/special/tests/test_xsf_cuda.py | 114 --------------------------- 2 files changed, 115 deletions(-) delete mode 100644 scipy/special/tests/test_xsf_cuda.py diff --git a/scipy/special/tests/meson.build b/scipy/special/tests/meson.build index 512db3022a81..65e0ca673779 100644 --- a/scipy/special/tests/meson.build +++ b/scipy/special/tests/meson.build @@ -56,7 +56,6 @@ python_sources = [ 'test_wrightomega.py', 'test_zeta.py', 'test_boost_ufuncs.py', - 'test_xsf_cuda.py', ] diff --git a/scipy/special/tests/test_xsf_cuda.py b/scipy/special/tests/test_xsf_cuda.py deleted file mode 100644 index dc39c2eeaf43..000000000000 --- a/scipy/special/tests/test_xsf_cuda.py +++ /dev/null @@ -1,114 +0,0 @@ -import os -import pytest -import scipy.special as sc -import shutil -import tempfile - -from uuid import uuid4 - -from scipy.special._testutils import check_version -from scipy.special._testutils import MissingModule - -try: - import cupy # type: ignore -except (ImportError, AttributeError): - cupy = MissingModule('cupy') - - -def get_test_cases(): - cases_source = [ - (sc.beta, "cephes/beta.h", "out0 = xsf::cephes::beta(in0, in1)"), - (sc.binom, "binom.h", "out0 = xsf::binom(in0, in1)"), - (sc.digamma, "digamma.h", "xsf::digamma(in0)"), - (sc.expn, "cephes/expn.h", "out0 = xsf::cephes::expn(in0, in1)"), - (sc.hyp2f1, "hyp2f1.h", "out0 = xsf::hyp2f1(in0, in1, in2, in3)"), - (sc._ufuncs._lambertw, "lambertw.h", "out0 = xsf::lambertw(in0, in1, in2)"), - (sc.ellipkinc, "cephes/ellik.h", "out0 = xsf::cephes::ellik(in0, in1)"), - (sc.ellipeinc, "cephes/ellie.h", "out0 = xsf::cephes::ellie(in0, in1)"), - (sc.gdtrib, "cdflib.h", "out0 = xsf::gdtrib(in0, in1, in2)"), - (sc.sici, "sici.h", "xsf::sici(in0, &out0, &out1)"), - (sc.shichi, "sici.h", "xsf::shichi(in0, &out0, &out1)"), - ] - - cases = [] - for ufunc, header, routine in cases_source: - preamble = f"#include " - for signature in ufunc.types: - cases.append((signature, preamble, routine)) - return cases - - -dtype_map = { - "f": "float32", - "d": "float64", - "F": "complex64", - "D": "complex128", - "i": "int32", - "l": "int64", -} - - -def get_params(signature): - in_, out = signature.split("->") - in_params = [] - out_params = [] - for i, typecode in enumerate(in_): - in_params.append(f"{dtype_map[typecode]} in{i}") - for i, typecode in enumerate(out): - out_params.append(f"{dtype_map[typecode]} out{i}") - in_params = ", ".join(in_params) - out_params = ", ".join(out_params) - return in_params, out_params - - -def get_sample_input(signature, xp): - dtype_map = { - "f": xp.float32, - "d": xp.float64, - "F": xp.complex64, - "D": xp.complex128, - "i": xp.int32, - "l": xp.int64, - } - - in_, _ = signature.split("->") - args = [] - for typecode in in_: - args.append(xp.zeros(2, dtype=dtype_map[typecode])) - return args - - -@pytest.fixture(scope="module", autouse=True) -def manage_cupy_cache(): - # Temporarily change cupy kernel cache location so kernel cache will not be polluted - # by these tests. Remove temporary cache in teardown. - temp_cache_dir = tempfile.mkdtemp() - original_cache_dir = os.environ.get('CUPY_CACHE_DIR', None) - os.environ['CUPY_CACHE_DIR'] = temp_cache_dir - - yield - - if original_cache_dir is not None: - os.environ['CUPY_CACHE_DIR'] = original_cache_dir - else: - del os.environ['CUPY_CACHE_DIR'] - shutil.rmtree(temp_cache_dir) - - -@check_version(cupy, "13.0.0") -@pytest.mark.parametrize("signature,preamble,routine", get_test_cases()) -@pytest.mark.xslow -def test_compiles_in_cupy(signature, preamble, routine, manage_cupy_cache): - name = f"x{uuid4().hex}" - in_params, out_params = get_params(signature) - - func = cupy.ElementwiseKernel( - in_params, - out_params, - routine, - name, - preamble=preamble, - options=(f"--include-path={sc._get_include()}", "-std=c++17") - ) - - _ = func(*get_sample_input(signature, cupy)) From 8e7578828d1a3ef29dbd67b5ee7c26731d6d24ba Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 14 May 2025 15:31:44 -0700 Subject: [PATCH 187/251] DOC: sparse: sparray migration guide updates (#22987) * 1.16 updates to sparray migration guide [docs only] * fix the needed change from summing to min [docs only] --- .../reference/sparse.migration_to_sparray.rst | 185 ++++++++++++++---- 1 file changed, 143 insertions(+), 42 deletions(-) diff --git a/doc/source/reference/sparse.migration_to_sparray.rst b/doc/source/reference/sparse.migration_to_sparray.rst index f169a23f4a9a..7093cf3ab200 100644 --- a/doc/source/reference/sparse.migration_to_sparray.rst +++ b/doc/source/reference/sparse.migration_to_sparray.rst @@ -43,7 +43,7 @@ Overview and big picture ``scipy.sparse.linalg.matrix_power(A, n)``. - When index arrays are provided to the constructor functions, spmatrix selects a dtype based on dtype and values of the incoming arrays, while - sparray only bases on the dtype of the incoming arrays. For example, + sparray only considers the dtype of the incoming arrays. For example, ``M=csr_matrix((data, indices, indptr))`` results in ``int32`` dtype for ``M.indices`` so long as the values in ``indices`` and ``indptr`` are small, even if the ``dtype`` of the incoming arrays are ``int64``. In contrast, @@ -162,15 +162,15 @@ Their signatures are:: def random_array(shape, density=0.01, format='coo', dtype=None, rng=None, data_sampler=None): The ``random_array`` function has a ``shape`` (2-tuple) arg rather than -two integers. And the ``random_state`` arg defaults to NumPy's new ``default_rng()``. +two integers. And the ``rng`` arg defaults to NumPy's new ``default_rng()``. This differs from the spmatrix ``rand`` and ``random`` which default to the global RandomState instance. If you don't care much about these things, leaving it as the default should work fine. If you care about seeding your -random numbers, you should probably add a ``random_state=...`` keyword argument +random numbers, you should probably add a ``rng=...`` keyword argument to this call when you switch functions. In summary, to migrate to ``random_array`` change the function name, switch the shape argument to a single tuple argument, leave any other parameters as before, and think about what -sort of ``random_state=`` argument should be used, if any. +sort of ``rng=`` argument should be used, if any. The `diags_array` function uses keyword-only rules for arguments. So you have to type the `offsets=` in front of the offsets arguments. That seems like a pain @@ -239,8 +239,8 @@ Details: shape changes and reductions - Reduction operations along an axis reduce the shape: - - ``M.sum(axis=1)`` returns a 2D row matrix by summing along axis 1. - - ``A.sum(axis=1)`` returns a 1D ``coo_array`` summing along axis 1. + - ``M.min(axis=1)`` returns a 2D row matrix of the min along axis 1. + - ``A.min(axis=1)`` returns a 1D ``coo_array`` of the min along axis 1. Some reductions return dense arrays/matrices instead of sparse ones: ============ ========= @@ -261,7 +261,7 @@ Details: shape changes and reductions - Some reductions return a scalar. Those should behave as they did before and shouldn’t need to be considered during migration. E.g. - ``A.sum()`` + ``A.min()`` .. _sparse-migration-removed-methods: @@ -341,17 +341,28 @@ Use tests to find * and ** spots you change when you shouldn't have. So the test suite with this monkey-patch checks the corrections too. - Add the following code to your ``conftest.py`` file near the top. + Add the following code to your ``conftest.py`` file. Then run your tests locally. If there are many matrix expressions, you might want to test one section of your codebase at a time. A quick read of the code shows that it raises a ``ValueError`` whenever ``*`` is used between two matrix-like objects (sparse or dense), - and whenever ``**`` is used for matrix power. + and whenever ``**`` is used for matrix power. It also produces a warning + whenever sum/mean/min/max/argmin/argmax are used with an axis so the + output will be 2D with spmatrix and 1D with sparray. That means you + check that the code will handle either 1D or 2D output via + ``flatten``/``ravel``, ``np.atleast_2d`` or indexing. .. code-block:: python + #================== Added to check spmatrix usage ======================== import scipy + from warnings import warn + def flag_this_call(*args, **kwds): + raise ValueError("Old spmatrix function names for rand/spdiags called") + + scipy.sparse._construct.rand = flag_this_call + scipy.sparse._construct.spdiags = flag_this_call class _strict_mul_mixin: def __mul__(self, other): @@ -372,53 +383,143 @@ Use tests to find * and ** spots def __pow__(self, *args, **kwargs): raise ValueError('spmatrix ** found! Use linalg.matrix_power?') - class _strict_coo_matrix(_strict_mul_mixin, scipy.sparse.coo_matrix): + @property + def A(self): + raise TypeError('spmatrix A property found! Use .toarray()') + + @property + def H(self): + raise TypeError('spmatrix H property found! Use .conjugate().T') + + def asfptype(self): + raise TypeError('spmatrix asfptype found! rewrite needed') + + def get_shape(self): + raise TypeError('spmatrix get_shape found! Use .shape') + + def getformat(self): + raise TypeError('spmatrix getformat found! Use .format') + + def getmaxprint(self): + raise TypeError('spmatrix getmaxprint found! Use .shape') + + def getnnz(self): + raise TypeError('spmatrix getnnz found! Use .nnz') + + def getH(self): + raise TypeError('spmatrix getH found! Use .conjugate().T') + + def getrow(self): + raise TypeError('spmatrix getrow found! Use .row') + + def getcol(self): + raise TypeError('spmatrix getcol found! Use .col') + + def sum(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix sum found using axis={axis}. " + "\nsparray with a single axis will produce 1D output. " + "\nCheck nearby to ensure 1D output is handled OK in this spot.\n") + print(f"{args=} {axis=} {kwds=}") + return super().sum(*args, **kwds) + + def mean(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix mean found using axis={axis}." + "\nsparray with a single axis will produce 1D output.\n" + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().mean(*args, **kwds) + + def min(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix min found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().min(*args, **kwds) + + def max(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix max found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().max(*args, **kwds) + + def argmin(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix argmin found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().argmin(*args, **kwds) + + def argmax(self, *args, **kwds): + axis = args[0] if len(args)==1 else args if args else kwds.get("axis", None) + if axis is not None: + warn(f"\nMIGRATION WARNING: spmatrix argmax found using axis={axis}." + "\nsparray with a single axis will produce 1D output. " + "Check nearby to ensure 1D output is handled OK in this spot.\n") + return super().argmax(*args, **kwds) + + + class coo_matrix_strict(_strict_mul_mixin, scipy.sparse.coo_matrix): pass - class _strict_bsr_matrix(_strict_mul_mixin, scipy.sparse.bsr_matrix): + class bsr_matrix_strict(_strict_mul_mixin, scipy.sparse.bsr_matrix): pass - class _strict_csr_matrix(_strict_mul_mixin, scipy.sparse.csr_matrix): + class csr_matrix_strict(_strict_mul_mixin, scipy.sparse.csr_matrix): pass - class _strict_csc_matrix(_strict_mul_mixin, scipy.sparse.csc_matrix): + class csc_matrix_strict(_strict_mul_mixin, scipy.sparse.csc_matrix): pass - class _strict_dok_matrix(_strict_mul_mixin, scipy.sparse.dok_matrix): + class dok_matrix_strict(_strict_mul_mixin, scipy.sparse.dok_matrix): pass - class _strict_lil_matrix(_strict_mul_mixin, scipy.sparse.lil_matrix): + class lil_matrix_strict(_strict_mul_mixin, scipy.sparse.lil_matrix): pass - class _strict_dia_matrix(_strict_mul_mixin, scipy.sparse.dia_matrix): + class dia_matrix_strict(_strict_mul_mixin, scipy.sparse.dia_matrix): pass - scipy.sparse.coo_matrix = scipy.sparse._coo.coo_matrix = _strict_coo_matrix - scipy.sparse.bsr_matrix = scipy.sparse._bsr.bsr_matrix = _strict_bsr_matrix - scipy.sparse.csr_matrix = scipy.sparse._csr.csr_matrix = _strict_csr_matrix - scipy.sparse.csc_matrix = scipy.sparse._csc.csc_matrix = _strict_csc_matrix - scipy.sparse.dok_matrix = scipy.sparse._dok.dok_matrix = _strict_dok_matrix - scipy.sparse.lil_matrix = scipy.sparse._lil.lil_matrix = _strict_lil_matrix - scipy.sparse.dia_matrix = scipy.sparse._dia.dia_matrix = _strict_dia_matrix - - scipy.sparse._compressed.csr_matrix = _strict_csr_matrix - - scipy.sparse._construct.bsr_matrix = _strict_bsr_matrix - scipy.sparse._construct.coo_matrix = _strict_coo_matrix - scipy.sparse._construct.csc_matrix = _strict_csc_matrix - scipy.sparse._construct.csr_matrix = _strict_csr_matrix - scipy.sparse._construct.dia_matrix = _strict_dia_matrix - - scipy.sparse._extract.coo_matrix = _strict_coo_matrix - - scipy.sparse._matrix.bsr_matrix = _strict_bsr_matrix - scipy.sparse._matrix.coo_matrix = _strict_coo_matrix - scipy.sparse._matrix.csc_matrix = _strict_csc_matrix - scipy.sparse._matrix.csr_matrix = _strict_csr_matrix - scipy.sparse._matrix.dia_matrix = _strict_dia_matrix - scipy.sparse._matrix.dok_matrix = _strict_dok_matrix - scipy.sparse._matrix.lil_matrix = _strict_lil_matrix - + scipy.sparse.coo_matrix = scipy.sparse._coo.coo_matrix = coo_matrix_strict + scipy.sparse.bsr_matrix = scipy.sparse._bsr.bsr_matrix = bsr_matrix_strict + scipy.sparse.csr_matrix = scipy.sparse._csr.csr_matrix = csr_matrix_strict + scipy.sparse.csc_matrix = scipy.sparse._csc.csc_matrix = csc_matrix_strict + scipy.sparse.dok_matrix = scipy.sparse._dok.dok_matrix = dok_matrix_strict + scipy.sparse.lil_matrix = scipy.sparse._lil.lil_matrix = lil_matrix_strict + scipy.sparse.dia_matrix = scipy.sparse._dia.dia_matrix = dia_matrix_strict + + scipy.sparse._compressed.csr_matrix = csr_matrix_strict + + scipy.sparse._construct.bsr_matrix = bsr_matrix_strict + scipy.sparse._construct.coo_matrix = coo_matrix_strict + scipy.sparse._construct.csc_matrix = csc_matrix_strict + scipy.sparse._construct.csr_matrix = csr_matrix_strict + scipy.sparse._construct.dia_matrix = dia_matrix_strict + + scipy.sparse._extract.coo_matrix = coo_matrix_strict + + scipy.sparse._matrix.bsr_matrix = bsr_matrix_strict + scipy.sparse._matrix.coo_matrix = coo_matrix_strict + scipy.sparse._matrix.csc_matrix = csc_matrix_strict + scipy.sparse._matrix.csr_matrix = csr_matrix_strict + scipy.sparse._matrix.dia_matrix = dia_matrix_strict + scipy.sparse._matrix.dok_matrix = dok_matrix_strict + scipy.sparse._matrix.lil_matrix = lil_matrix_strict + + del coo_matrix_strict + del bsr_matrix_strict + del csr_matrix_strict + del csc_matrix_strict + del dok_matrix_strict + del lil_matrix_strict + del dia_matrix_strict + #========================================== .. _sparse-migration-index-array-dtypes: From c1e2c2c8983217d156494bb861f33c0e7635fa47 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 14 May 2025 16:32:45 -0700 Subject: [PATCH 188/251] ENH: sparse: Support nD sum/mean/min/max/argmin for sparse arrays (#22873) * Add nd sum/minmax and rewrite dot Add nD support for sum, mean, min, max, argmmin, argmax Adjust validateaxis() to return tuple or None. Not int. Replace _ravel_non_reduced_axes() with _convert_to_2d() cuz we are always doing that anyway. Test power() All has been building on the base submitted by Anushka Suyal. This PR simplifies min/max and sum with one method (like _min_or_max_axis_nd()) with all the nd code: ravels nd to 2d, applies operator, unravels. Also rewrites the ravel helper function to construct the 2d array. The `dot` series of methods also can use this simplified interface. Rewrites there simplify a bunch too. Co-authored-by: Anushka Suyal <126159239+anushkasuyal@users.noreply.github.com> * linting unused import * replace np.permute_dims with the older transpose * separate out array from exp calulation and test it * update return type annotation --------- Co-authored-by: Anushka Suyal <126159239+anushkasuyal@users.noreply.github.com> --- scipy/sparse/_base.py | 65 +++--- scipy/sparse/_coo.py | 292 +++++++++--------------- scipy/sparse/_data.py | 74 +++--- scipy/sparse/_dia.py | 4 +- scipy/sparse/_sputils.py | 4 +- scipy/sparse/tests/test_arithmetic1d.py | 2 +- scipy/sparse/tests/test_base.py | 20 +- scipy/sparse/tests/test_common1d.py | 2 +- scipy/sparse/tests/test_coo.py | 142 +++++++++++- scipy/sparse/tests/test_sputils.py | 13 +- 10 files changed, 360 insertions(+), 258 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 9e0374e1010b..30e61ccd8f72 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -4,8 +4,8 @@ import numpy as np from ._sputils import (asmatrix, check_reshape_kwargs, check_shape, - get_sum_dtype, isdense, isscalarlike, - matrix, validateaxis, getdtype, isintlike) + get_sum_dtype, isdense, isscalarlike, _todata, + matrix, validateaxis, getdtype) from scipy._lib._sparse import SparseABC, issparse from ._matrix import spmatrix @@ -1148,25 +1148,39 @@ def sum(self, axis=None, dtype=None, out=None): # Mimic numpy's casting. res_dtype = get_sum_dtype(self.dtype) - # Note: all valid 1D axis values are canonically `None` so use this code. + # Note: all valid 1D axis values are canonically `None`. if axis is None: - ones = self._ascontainer(np.ones((self.shape[-1], 1), dtype=res_dtype)) - return (self @ ones).sum(dtype=dtype, out=out) + if self.nnz == 0: + return np.sum(self._ascontainer([0]), dtype=dtype or res_dtype, out=out) + return np.sum(self._ascontainer(_todata(self)), dtype=dtype, out=out) + elif isspmatrix(self): + # Ensure spmatrix sums stay 2D + new_shape = (1, self.shape[1]) if axis == (0,) else (self.shape[0], 1) + else: + new_shape = tuple(self.shape[i] for i in range(self.ndim) if i not in axis) + + if out is None: + # create out array with desired dtype + out = np.zeros(new_shape, dtype=dtype or res_dtype) + else: + if out.shape != new_shape: + raise ValueError("out dimensions do not match shape") + + if self.ndim > 2: + return self._sum_nd(axis, res_dtype, out) - # We use multiplication by a matrix of ones to achieve this. + # We use multiplication by a matrix of ones to sum. # For some sparse array formats more efficient methods are # possible -- these should override this function. - if axis == 0: - # sum over columns + if axis == (0,): ones = self._ascontainer(np.ones((1, self.shape[0]), dtype=res_dtype)) - ret = ones @ self - else: # axis == 1: - # sum over rows + # sets dtype while loading into out + out[...] = (ones @ self).reshape(new_shape) + else: # axis == (1,) ones = self._ascontainer(np.ones((self.shape[1], 1), dtype=res_dtype)) - ret = self @ ones - - # This doesn't sum anything. It handles dtype and out. - return ret.sum(axis=axis, dtype=dtype, out=out) + # sets dtype while loading into out + out[...] = (self @ ones).reshape(new_shape) + return out def mean(self, axis=None, dtype=None, out=None): """ @@ -1207,32 +1221,21 @@ def mean(self, axis=None, dtype=None, out=None): """ axis = validateaxis(axis, ndim=self.ndim) - res_dtype = self.dtype.type integral = (np.issubdtype(self.dtype, np.integer) or np.issubdtype(self.dtype, np.bool_)) - # output dtype - if dtype is None: - if integral: - res_dtype = np.float64 - else: - res_dtype = np.dtype(dtype).type - # intermediate dtype for summation - inter_dtype = np.float64 if integral else res_dtype + inter_dtype = np.float64 if integral else self.dtype inter_self = self.astype(inter_dtype) if axis is None: denom = math.prod(self.shape) - elif isintlike(axis): - denom = self.shape[axis] else: denom = math.prod(self.shape[ax] for ax in axis) - res = (inter_self * (1.0 / denom)).sum(axis=axis, dtype=inter_dtype) - if out is None: - return res.sum(axis=(), dtype=dtype) - # out is handled differently by matrix and ndarray so use as_container - return self._ascontainer(res).sum(axis=(), dtype=dtype, out=out) + res = (inter_self * (1.0 / denom)).sum(axis=axis, dtype=inter_dtype, out=out) + if dtype is not None and out is None: + return res.astype(dtype, copy=False) + return res def diagonal(self, k=0): diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index 834f29397b35..a7a4994055ee 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -298,7 +298,7 @@ def toarray(self, order=None, out=None): M, N = self.shape coo_todense(M, N, self.nnz, self.row, self.col, self.data, B.ravel('A'), fortran) - else: + else: # dim>2 if fortran: strides = np.append(1, np.cumprod(self.shape[:-1])) else: @@ -603,7 +603,6 @@ def _add_sparse(self, other): A = self.__class__((new_data, new_coords), shape=self.shape) return A - def _sub_sparse(self, other): if self.ndim < 3: return self.tocsr()._sub_sparse(other) @@ -616,7 +615,6 @@ def _sub_sparse(self, other): A = coo_array((new_data, new_coords), shape=self.shape) return A - def _matmul_vector(self, other): if self.ndim > 2: result = np.zeros(math.prod(self.shape[:-1]), @@ -650,7 +648,6 @@ def _matmul_vector(self, other): return result[0] return result - def _rmatmul_dispatch(self, other): if isscalarlike(other): return self._mul_scalar(other) @@ -676,7 +673,6 @@ def _rmatmul_dispatch(self, other): perm = tuple(range(ret.ndim)[:-2]) + tuple(range(ret.ndim)[-2:][::-1]) return ret.transpose(perm) - def _matmul_dispatch(self, other): if isscalarlike(other): return self.multiply(other) @@ -726,7 +722,6 @@ def _matmul_dispatch(self, other): f"{err_prefix} (n,..,k={N}),(k={other.shape[-2]},..,m)->(n,..,m)" ) - if isscalarlike(other): # scalar value return self._mul_scalar(other) @@ -771,7 +766,6 @@ def _matmul_dispatch(self, other): result = result.reshape(result.shape[:-1]) return result - def _matmul_multivector(self, other): result_dtype = upcast_char(self.dtype.char, other.dtype.char) if self.ndim >= 3 or other.ndim >= 3: @@ -807,7 +801,6 @@ def _matmul_multivector(self, other): self.data, other.ravel('C'), result) return result.view(type=type(other)) - def dot(self, other): """Return the dot product of two arrays. @@ -861,6 +854,7 @@ def dot(self, other): >>> A.dot(B).shape (3, 4, 5, 5, 4, 3) """ + # handle non-array input: lists, ints, etc if not (issparse(other) or isdense(other) or isscalarlike(other)): # If it's a list or whatever, treat it like an array o_array = np.asanyarray(other) @@ -872,118 +866,68 @@ def dot(self, other): except AttributeError: other = o_array - if self.ndim < 3 and (np.isscalar(other) or other.ndim<3): - return _spbase.dot(self, other) # Handle scalar multiplication - if np.isscalar(other): + if isscalarlike(other): return self * other + + # other.shape[-2:][0] gets last index of 1d, next to last index of >1d + if self.shape[-1] != other.shape[-2:][0]: + raise ValueError(f"shapes {self.shape} and {other.shape}" + " are not aligned for n-D dot") + + if self.ndim < 3 and other.ndim < 3: + return self @ other if isdense(other): return self._dense_dot(other) - elif other.format != "coo": - raise TypeError("input must be a COO matrix/array") - elif self.ndim == 1 and other.ndim == 1: - # Handle inner product of vectors (1-D arrays) - if self.shape[0] != other.shape[0]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for inner product") - return self @ other - elif self.ndim == 2 and other.ndim == 2: - # Handle matrix multiplication (2-D arrays) - if self.shape[1] != other.shape[0]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for matmul") - return self @ other - else: - return self._sparse_dot(other) - + return self._sparse_dot(other.tocoo()) def _sparse_dot(self, other): - self_is_1d = self.ndim == 1 - other_is_1d = other.ndim == 1 - - # reshape to 2-D if self or other is 1-D - if self_is_1d: - self = self.reshape(self._shape_as_2d) # prepend 1 to shape - if other_is_1d: - other = other.reshape((other.shape[0], 1)) # append 1 to shape - - if self.shape[-1] != other.shape[-2]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for n-D dot") - - # Prepare the tensors for dot operation + # already checked: at least one is >2d, neither scalar, both are coo # Ravel non-reduced axes coordinates - self_raveled_coords = _ravel_non_reduced_axes(self.coords, - self.shape, [self.ndim-1]) - other_raveled_coords = _ravel_non_reduced_axes(other.coords, - other.shape, [other.ndim-2]) - - # Get the shape of the non-reduced axes - self_nonreduced_shape = self.shape[:-1] - other_nonreduced_shape = other.shape[:-2] + other.shape[-1:] + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) + other_2d, o_new_shape = _convert_to_2d(other, [max(0, other.ndim - 2)]) - # Create 2D coords arrays - ravel_coords_shape_self = (math.prod(self_nonreduced_shape), self.shape[-1]) - ravel_coords_shape_other = (other.shape[-2], math.prod(other_nonreduced_shape)) + prod = self_2d @ other_2d.T # routes via 2-D CSR + prod = prod.tocoo() - self_2d_coords = (self_raveled_coords, self.coords[-1]) - other_2d_coords = (other.coords[-2], other_raveled_coords) - - self_2d = coo_array((self.data, self_2d_coords), ravel_coords_shape_self) - other_2d = coo_array((other.data, other_2d_coords), ravel_coords_shape_other) - - prod = (self_2d @ other_2d).tocoo() # routes via 2-D CSR - - # Combine the shapes of the non-reduced axes - combined_shape = self_nonreduced_shape + other_nonreduced_shape + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape # Unravel the 2D coordinates to get multi-dimensional coordinates - shapes = (self_nonreduced_shape, other_nonreduced_shape) - prod_coords = [] - for c, s in zip(prod.coords, shapes): - prod_coords.extend(np.unravel_index(c, s)) - - prod_arr = coo_array((prod.data, prod_coords), combined_shape) - - # reshape back if a or b were originally 1-D - # TODO: Move this logic before computation of prod_coords for efficiency - if self_is_1d: - prod_arr = prod_arr.reshape(combined_shape[1:]) - if other_is_1d: - prod_arr = prod_arr.reshape(combined_shape[:-1]) + coords = [] + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): + coords.extend(np.unravel_index(c, s)) - return prod_arr + # Construct the resulting COO array with coords and shape + return coo_array((prod.data, coords), shape=combined_shape) def _dense_dot(self, other): - self_is_1d = self.ndim == 1 - other_is_1d = other.ndim == 1 - - # reshape to 2-D if self or other is 1-D - if self_is_1d: - self = self.reshape(self._shape_as_2d) # prepend 1 to shape - if other_is_1d: - other = other.reshape((other.shape[0], 1)) # append 1 to shape - - if self.shape[-1] != other.shape[-2]: - raise ValueError(f"shapes {self.shape} and {other.shape}" - " are not aligned for n-D dot") + # already checked: self is >0d, other is dense and >0d + # Ravel non-reduced axes coordinates + s_ndim = self.ndim + if s_ndim <= 2: + s_new_shape = () if s_ndim == 1 else (self.shape[0],) + self_2d = self + else: + self_2d, s_new_shape = _convert_to_2d(self, [self.ndim - 1]) - new_shape_self = ( - self.shape[:-1] + (1,) * (len(other.shape) - 1) + self.shape[-1:] - ) - new_shape_other = (1,) * (len(self.shape) - 1) + other.shape + o_ndim = other.ndim + if o_ndim <= 2: + o_new_shape = () if o_ndim == 1 else (other.shape[-1],) + other_2d = other + else: + o_new_shape = other.shape[:-2] + other.shape[-1:] + reorder_dims = (o_ndim - 2, *range(o_ndim - 2), o_ndim - 1) + o_reorg = np.transpose(other, reorder_dims) + other_2d = o_reorg.reshape((other.shape[-2], math.prod(o_new_shape))) - result_shape = self.shape[:-1] + other.shape[:-2] + other.shape[-1:] - result = self.reshape(new_shape_self) @ other.reshape(new_shape_other) - prod_arr = result.reshape(result_shape) + prod = self_2d @ other_2d # routes via 2-D CSR - # reshape back if a or b were originally 1-D - if self_is_1d: - prod_arr = prod_arr.reshape(result_shape[1:]) - if other_is_1d: - prod_arr = prod_arr.reshape(result_shape[:-1]) + # Combine the shapes of the non-contracted axes + combined_shape = s_new_shape + o_new_shape + return prod.reshape(combined_shape) - return prod_arr def tensordot(self, other, axes=2): """Return the tensordot product with another array along the given axes. @@ -1097,85 +1041,52 @@ def tensordot(self, other, axes=2): else: return self._sparse_tensordot(other, axes_self, axes_other) - - def _sparse_tensordot(self, other, axes_self, axes_other): - ndim_self = len(self.shape) - ndim_other = len(other.shape) - + def _sparse_tensordot(self, other, s_axes, o_axes): # Prepare the tensors for tensordot operation # Ravel non-reduced axes coordinates - self_non_red_coords = _ravel_non_reduced_axes(self.coords, self.shape, - axes_self) - self_reduced_coords = np.ravel_multi_index( - [self.coords[ax] for ax in axes_self], [self.shape[ax] for ax in axes_self]) - other_non_red_coords = _ravel_non_reduced_axes(other.coords, other.shape, - axes_other) - other_reduced_coords = np.ravel_multi_index( - [other.coords[a] for a in axes_other], [other.shape[a] for a in axes_other] - ) - # Get the shape of the non-reduced axes - self_nonreduced_shape = tuple(self.shape[ax] for ax in range(ndim_self) - if ax not in axes_self) - other_nonreduced_shape = tuple(other.shape[ax] for ax in range(ndim_other) - if ax not in axes_other) - - # Create 2D coords arrays - ravel_coords_shape_self = (math.prod(self_nonreduced_shape), - math.prod([self.shape[ax] for ax in axes_self])) - ravel_coords_shape_other = (math.prod([other.shape[ax] for ax in axes_other]), - math.prod(other_nonreduced_shape)) - - self_2d_coords = (self_non_red_coords, self_reduced_coords) - other_2d_coords = (other_reduced_coords, other_non_red_coords) - - self_2d = coo_array((self.data, self_2d_coords), ravel_coords_shape_self) - other_2d = coo_array((other.data, other_2d_coords), ravel_coords_shape_other) + self_2d, s_new_shape = _convert_to_2d(self, s_axes) + other_2d, o_new_shape = _convert_to_2d(other, o_axes) # Perform matrix multiplication (routed via 2-D CSR) - prod = (self_2d @ other_2d).tocoo() + prod = self_2d @ other_2d.T + # handle case of scalar result (axis includes all axes for both) + if not issparse(prod): + return prod + prod = prod.tocoo() # Combine the shapes of the non-contracted axes - combined_shape = self_nonreduced_shape + other_nonreduced_shape + combined_shape = s_new_shape + o_new_shape # Unravel the 2D coordinates to get multi-dimensional coordinates coords = [] - for c, s in zip(prod.coords, (self_nonreduced_shape, other_nonreduced_shape)): + new_shapes = (s_new_shape, o_new_shape) if s_new_shape else (o_new_shape,) + for c, s in zip(prod.coords, new_shapes): if s: coords.extend(np.unravel_index(c, s)) - if coords == []: # if result is scalar - return sum(prod.data) - - # Construct the resulting COO array with combined coordinates and shape + # Construct the resulting COO array with coords and shape return coo_array((prod.data, coords), shape=combined_shape) + def _dense_tensordot(self, other, s_axes, o_axes): + s_ndim = len(self.shape) + o_ndim = len(other.shape) - def _dense_tensordot(self, other, axes_self, axes_other): - ndim_self = len(self.shape) - ndim_other = len(other.shape) - - non_reduced_axes_self = [ax for ax in range(ndim_self) if ax not in axes_self] - reduced_shape_self = [self.shape[s] for s in axes_self] - non_reduced_shape_self = [self.shape[s] for s in non_reduced_axes_self] + s_non_axes = [i for i in range(s_ndim) if i not in s_axes] + s_axes_shape = [self.shape[i] for i in s_axes] + s_non_axes_shape = [self.shape[i] for i in s_non_axes] - non_reduced_axes_other = [ax for ax in range(ndim_other) - if ax not in axes_other] - reduced_shape_other = [other.shape[s] for s in axes_other] - non_reduced_shape_other = [other.shape[s] for s in non_reduced_axes_other] - - permute_self = non_reduced_axes_self + axes_self - permute_other = ( - non_reduced_axes_other[:-1] + axes_other + non_reduced_axes_other[-1:] - ) - self = self.transpose(permute_self) - other = np.transpose(other, permute_other) + o_non_axes = [i for i in range(o_ndim) if i not in o_axes] + o_axes_shape = [other.shape[i] for i in o_axes] + o_non_axes_shape = [other.shape[i] for i in o_non_axes] - reshape_self = (*non_reduced_shape_self, math.prod(reduced_shape_self)) - reshape_other = (*non_reduced_shape_other[:-1], math.prod(reduced_shape_other), - *non_reduced_shape_other[-1:]) + left = self.transpose(s_non_axes + s_axes) + right = np.transpose(other, o_non_axes[:-1] + o_axes + o_non_axes[-1:]) - return self.reshape(reshape_self).dot(other.reshape(reshape_other)) + reshape_left = (*s_non_axes_shape, math.prod(s_axes_shape)) + reshape_right = (*o_non_axes_shape[:-1], math.prod(o_axes_shape), + *o_non_axes_shape[-1:]) + return left.reshape(reshape_left).dot(right.reshape(reshape_right)) def _matmul_sparse(self, other): """ @@ -1219,7 +1130,6 @@ def _matmul_sparse(self, other): shape=(*broadcast_shape, self.shape[-2], other.shape[-1]), ) - def _broadcast_to(self, new_shape, copy=False): if self.shape == new_shape: return self.copy() if copy else self @@ -1275,6 +1185,26 @@ def _broadcast_to(self, new_shape, copy=False): return coo_array((new_data, new_coords), new_shape) + def _sum_nd(self, axis, res_dtype, out): + # axis and out are preprocessed. out.shape is new_shape + A2d, new_shape = _convert_to_2d(self, axis) + ones = np.ones((A2d.shape[1], 1), dtype=res_dtype) + # sets dtype while loading into out + out[...] = (A2d @ ones).reshape(new_shape) + return out + + def _min_or_max_axis_nd(self, axis, min_or_max, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res = A2d._min_or_max_axis(1, min_or_max, explicit) + unraveled_coords = np.unravel_index(res.coords[0], new_shape) + + return coo_array((res.data, unraveled_coords), new_shape) + + def _argminmax_axis_nd(self, axis, argminmax, compare, explicit): + A2d, new_shape = _convert_to_2d(self, axis) + res_flat = A2d._argminmax_axis(1, argminmax, compare, explicit) + return res_flat.reshape(new_shape) + def _block_diag(self): """ @@ -1349,22 +1279,26 @@ def _process_axes(ndim_a, ndim_b, axes): return axes_a, axes_b -def _ravel_non_reduced_axes(coords, shape, axes): - ndim = len(shape) - non_reduced_axes = [ax for ax in range(ndim) if ax not in axes] - - if not non_reduced_axes: - # Return an array with one row - return np.zeros_like(coords[0]) - - # Extract the shape of the non-reduced axes - non_reduced_shape = [shape[ax] for ax in non_reduced_axes] - - # Extract the coordinates of the non-reduced axes - non_reduced_coords = tuple(coords[idx] for idx in non_reduced_axes) - - # Ravel the coordinates into 1D - return np.ravel_multi_index(non_reduced_coords, non_reduced_shape) +def _convert_to_2d(coo, axis): + axis_coords = tuple(coo.coords[i] for i in axis) + axis_shape = tuple(coo.shape[i] for i in axis) + axis_ravel = _ravel_coords(axis_coords, axis_shape) + + ndim = len(coo.coords) + non_axis = tuple(i for i in range(ndim) if i not in axis) + if non_axis: + non_axis_coords = tuple(coo.coords[i] for i in non_axis) + non_axis_shape = tuple(coo.shape[i] for i in non_axis) + non_axis_ravel = _ravel_coords(non_axis_coords, non_axis_shape) + coords_2d = (non_axis_ravel, axis_ravel) + shape_2d = (math.prod(non_axis_shape), math.prod(axis_shape)) + else: # all axes included in axis so result will have 1 element + coords_2d = (axis_ravel,) + shape_2d = (math.prod(axis_shape),) + non_axis_shape = () + + new_coo = coo_array((coo.data, coords_2d), shape=shape_2d) + return new_coo, non_axis_shape def _ravel_coords(coords, shape, order='C'): diff --git a/scipy/sparse/_data.py b/scipy/sparse/_data.py index 420f44c44bcd..e6f876b69b7f 100644 --- a/scipy/sparse/_data.py +++ b/scipy/sparse/_data.py @@ -124,7 +124,7 @@ def power(self, n, dtype=None): data = self._deduped_data() if dtype is not None: - data = data.astype(dtype) + data = data.astype(dtype, copy=False) return self._with_data(data ** n) ########################### @@ -172,9 +172,8 @@ class _minmax_mixin: """ def _min_or_max_axis(self, axis, min_or_max, explicit): + # already checked that self.shape[axis] is not zero N = self.shape[axis] - if N == 0: - raise ValueError("zero-size array to reduction operation") M = self.shape[1 - axis] idx_dtype = self._get_index_dtype(maxval=M) @@ -208,7 +207,7 @@ def _min_or_max_axis(self, axis, min_or_max, explicit): def _min_or_max(self, axis, out, min_or_max, explicit): if out is not None: - raise ValueError("Sparse arrays do not support an 'out' parameter.") + raise ValueError("Sparse min/max does not support an 'out' parameter.") axis = validateaxis(axis, ndim=self.ndim) @@ -224,12 +223,15 @@ def _min_or_max(self, axis, out, min_or_max, explicit): m = min_or_max(zero, m) return m - return self._min_or_max_axis(axis, min_or_max, explicit) + if any(self.shape[d] == 0 for d in axis): + raise ValueError("zero-size array to reduction operation") - def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): - if self.shape[axis] == 0: - raise ValueError("Cannot apply the operation along a zero-sized dimension.") + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._min_or_max_axis(axis[0], min_or_max, explicit) + return self._min_or_max_axis_nd(axis, min_or_max, explicit) + def _argminmax_axis(self, axis, argminmax, compare, explicit): zero = self.dtype.type(0) mat = self.tocsc() if axis == 0 else self.tocsr() @@ -243,7 +245,7 @@ def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): p, q = mat.indptr[i:i + 2] data = mat.data[p:q] indices = mat.indices[p:q] - extreme_index = argmin_or_argmax(data) + extreme_index = argminmax(data) extreme_value = data[extreme_index] if explicit: if q - p > 0: @@ -266,21 +268,31 @@ def _arg_min_or_max_axis(self, axis, argmin_or_argmax, compare, explicit): return self._ascontainer(ret) - def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare, explicit): + def _argminmax(self, axis, out, argminmax, compare, explicit): if out is not None: - raise ValueError("Sparse types do not support an 'out' parameter.") + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Sparse {minmax} does not support an 'out' parameter.") axis = validateaxis(axis, ndim=self.ndim) if axis is not None: - return self._arg_min_or_max_axis(axis, argmin_or_argmax, compare, explicit) + if any(self.shape[i] == 0 for i in axis): + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} along a zero-sized dimension.") + + if self.ndim == 2: + # note: 2D ensures that len(axis)==1 so we pass in the int axis[0] + return self._argminmax_axis(axis[0], argminmax, compare, explicit) + return self._argminmax_axis_nd(axis, argminmax, compare, explicit) if 0 in self.shape: - raise ValueError("Cannot apply the operation to an empty matrix.") + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to an empty matrix.") if self.nnz == 0: if explicit: - raise ValueError("Cannot apply the operation to zero matrix " + minmax = "argmin" if argminmax == np.argmin else "argmax" + raise ValueError(f"Cannot apply {minmax} to zero matrix " "when explicit=True.") return 0 @@ -288,28 +300,34 @@ def _arg_min_or_max(self, axis, out, argmin_or_argmax, compare, explicit): mat = self.tocoo() # Convert to canonical form: no duplicates, sorted indices. mat.sum_duplicates() - extreme_index = argmin_or_argmax(mat.data) + extreme_index = argminmax(mat.data) if explicit: return extreme_index extreme_value = mat.data[extreme_index] - num_col = mat.shape[-1] + if mat.ndim > 2: + mat = mat.reshape(-1) # If the min value is less than zero, or max is greater than zero, # then we do not need to worry about implicit zeros. - if compare(extreme_value, zero): + # And we use a "cheap test" for the rare case of no implicit zeros. + maxnnz = math.prod(self.shape) + if compare(extreme_value, zero) or mat.nnz == maxnnz: # cast to Python int to avoid overflow and RuntimeError - return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) - - # Cheap test for the rare case where we have no implicit zeros. - size = math.prod(self.shape) - if size == mat.nnz: + if mat.ndim == 1: # includes nD case that was reshaped above + return int(mat.col[extreme_index]) + # ndim == 2 + num_col = mat.shape[-1] return int(mat.row[extreme_index]) * num_col + int(mat.col[extreme_index]) # At this stage, any implicit zero could be the min or max value. # After sum_duplicates(), the `row` and `col` arrays are guaranteed to # be sorted in C-order, which means the linearized indices are sorted. - linear_indices = mat.row * num_col + mat.col - first_implicit_zero_index = _find_missing_index(linear_indices, size) + if mat.ndim == 1: # includes nD case that was reshaped above + linear_indices = mat.coords[-1] + else: # ndim == 2 + num_col = mat.shape[-1] + linear_indices = mat.row * num_col + mat.col + first_implicit_zero_index = _find_missing_index(linear_indices, maxnnz) if extreme_value == zero: return min(first_implicit_zero_index, extreme_index) return first_implicit_zero_index @@ -506,7 +524,7 @@ def argmax(self, axis=None, out=None, *, explicit=False): explicit : {False, True} optional (default: False) When set to True, only explicitly stored elements will be considered. - If axis is not None and a row/column has no stored elements, argmax + If axis is not None and an axis has no stored elements, argmax is undefined, so the index ``0`` is returned for that row/column. .. versionadded:: 1.15.0 @@ -516,7 +534,7 @@ def argmax(self, axis=None, out=None, *, explicit=False): ind : numpy.matrix or int Indices of maximum elements. If matrix, its size along `axis` is 1. """ - return self._arg_min_or_max(axis, out, np.argmax, np.greater, explicit) + return self._argminmax(axis, out, np.argmax, np.greater, explicit) def argmin(self, axis=None, out=None, *, explicit=False): """Return indices of minimum elements along an axis. @@ -538,7 +556,7 @@ def argmin(self, axis=None, out=None, *, explicit=False): explicit : {False, True} optional (default: False) When set to True, only explicitly stored elements will be considered. - If axis is not None and a row/column has no stored elements, argmin + If axis is not None and an axis has no stored elements, argmin is undefined, so the index ``0`` is returned for that row/column. .. versionadded:: 1.15.0 @@ -548,4 +566,4 @@ def argmin(self, axis=None, out=None, *, explicit=False): ind : numpy.matrix or int Indices of minimum elements. If matrix, its size along `axis` is 1. """ - return self._arg_min_or_max(axis, out, np.argmin, np.less, explicit) + return self._argminmax(axis, out, np.argmin, np.less, explicit) diff --git a/scipy/sparse/_dia.py b/scipy/sparse/_dia.py index e5508f9c5c8e..bbf22f3f7c36 100644 --- a/scipy/sparse/_dia.py +++ b/scipy/sparse/_dia.py @@ -148,7 +148,7 @@ def sum(self, axis=None, dtype=None, out=None): num_rows, num_cols = self.shape ret = None - if axis == 0: + if axis == (0,): mask = self._data_mask() x = (self.data * mask).sum(axis=0) if x.shape[0] == num_cols: @@ -158,7 +158,7 @@ def sum(self, axis=None, dtype=None, out=None): res[:x.shape[0]] = x ret = self._ascontainer(res, dtype=res_dtype) - else: + else: # axis is None or (1,) row_sums = np.zeros((num_rows, 1), dtype=res_dtype) one = np.ones(num_cols, dtype=res_dtype) dia_matvec(num_rows, num_cols, len(self.offsets), diff --git a/scipy/sparse/_sputils.py b/scipy/sparse/_sputils.py index a200724e951e..326c68bab785 100644 --- a/scipy/sparse/_sputils.py +++ b/scipy/sparse/_sputils.py @@ -403,7 +403,7 @@ def isdense(x) -> bool: return isinstance(x, np.ndarray) -def validateaxis(axis, *, ndim=2) -> int | tuple[int, ...]: +def validateaxis(axis, *, ndim=2) -> tuple[int, ...] | None: if axis is None: return None @@ -437,8 +437,6 @@ def validateaxis(axis, *, ndim=2) -> int | tuple[int, ...]: raise ValueError("axis tuple has too many elements") elif len_axis == ndim: return None - elif len_axis == 1: - return canon_axis[0] else: return tuple(canon_axis) diff --git a/scipy/sparse/tests/test_arithmetic1d.py b/scipy/sparse/tests/test_arithmetic1d.py index 3d5d2ee2f1bc..4ad8679508b2 100644 --- a/scipy/sparse/tests/test_arithmetic1d.py +++ b/scipy/sparse/tests/test_arithmetic1d.py @@ -320,7 +320,7 @@ def test_size_zero_matrix_arithmetic(self, spcreator): assert_equal(asp.dot(asp), np.dot(a, a)) # bad matrix products - with pytest.raises(ValueError, match='dimension mismatch'): + with pytest.raises(ValueError, match='dimension mismatch|shapes.*not aligned'): asp.dot(f) # elemente-wise multiplication diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index d89bdf586b8e..d331720ae522 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -1053,7 +1053,9 @@ def test_sum_invalid_params(self): datsp.sum(axis=(0, 3)) with assert_raises(TypeError, match="axis must be an integer"): datsp.sum(axis=1.5) - assert_raises(ValueError, datsp.sum, axis=1, out=out) + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): + datsp.mean(axis=1, out=out) def test_sum_dtype(self): dat = array([[0, 1, 2], @@ -1151,7 +1153,7 @@ def check(dtype): for dtype in self.checked_dtypes: check(dtype) - def test_mean_invalid_params(self): + def test_mean_invalid_param(self): out = self.asdense(np.zeros((1, 3))) dat = array([[0, 1, 2], [3, -4, 5], @@ -1164,7 +1166,8 @@ def test_mean_invalid_params(self): datsp.mean(axis=(0, 3)) with assert_raises(TypeError, match="axis must be an integer"): datsp.mean(axis=1.5) - with assert_raises(ValueError, match="doesn't match.*shape|wrong.*dimensions"): + # error msg varies by sparray (1d result) or spmatrix (2d result) + with assert_raises(ValueError, match="do.*n.t match.*shape|wrong.*dimensions"): datsp.mean(axis=1, out=out) def test_mean_dtype(self): @@ -3722,9 +3725,11 @@ def test_minmax(self): X = self.spcreator(np.arange(1, 10).reshape(3, 3)) assert_equal(X.min(), 1) assert_equal(X.min().dtype, X.dtype) + assert_equal(X.min(explicit=True), 1) X = -X assert_equal(X.max(), -1) + assert_equal(X.max(explicit=True), -1) # and a fully sparse matrix Z = self.spcreator(np.zeros((1, 1))) @@ -3886,10 +3891,12 @@ def test_minmax_invalid_params(self): datsp = self.spcreator(dat) for fname in ('min', 'max'): + datfunc = getattr(dat, fname) func = getattr(datsp, fname) assert_raises(ValueError, func, axis=3) assert_raises(TypeError, func, axis=1.5) assert_raises(ValueError, func, axis=1, out=1) + assert_equal(func(axis=(0, 1)), datfunc(axis=(0, 1))) def test_numpy_minmax(self): # See gh-5987 @@ -3939,6 +3946,13 @@ def test_argmax(self): assert_equal(mat.argmax(axis=1), np.argmax(D, axis=1)) assert_equal(mat.argmin(axis=1), np.argmin(D, axis=1)) + # full matrix with explicit=True + mat = self.spcreator(self.asdense(D5)) + assert_equal(mat.argmax(explicit=True), 5) + assert_equal((-mat).argmax(explicit=True), 2) + assert_equal(mat.argmin(explicit=True), 2) + assert_equal((-mat).argmin(explicit=True), 5) + # zero-size matrices D6 = self.spcreator(np.empty((0, 5))) D7 = self.spcreator(np.empty((5, 0))) diff --git a/scipy/sparse/tests/test_common1d.py b/scipy/sparse/tests/test_common1d.py index 6f36008caa6c..377088500149 100644 --- a/scipy/sparse/tests/test_common1d.py +++ b/scipy/sparse/tests/test_common1d.py @@ -180,7 +180,7 @@ def test_mean_invalid_params(self, spcreator): datsp.mean(axis=(0, 3)) with pytest.raises(TypeError, match='axis must be an integer'): datsp.mean(axis=1.5) - with pytest.raises(ValueError, match='output parameter.*wrong.*dimension'): + with pytest.raises(ValueError, match='out.*not match shape'): datsp.mean(axis=1, out=out) def test_sum_dtype(self, spcreator): diff --git a/scipy/sparse/tests/test_coo.py b/scipy/sparse/tests/test_coo.py index e9281748e6fc..db8c3082b704 100644 --- a/scipy/sparse/tests/test_coo.py +++ b/scipy/sparse/tests/test_coo.py @@ -1,5 +1,5 @@ import numpy as np -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_allclose import pytest from scipy.linalg import block_diag from scipy.sparse import coo_array, random_array @@ -563,8 +563,7 @@ def test_nd_add_sparse_with_inconsistent_shapes(a_shape, b_shape): arr_a = random_array((a_shape), density=0.6, rng=rng, dtype=int) arr_b = random_array((b_shape), density=0.6, rng=rng, dtype=int) - with pytest.raises(ValueError, - match="(Incompatible|inconsistent) shapes|cannot be broadcast"): + with pytest.raises(ValueError, match="inconsistent shapes"): arr_a + arr_b @@ -849,3 +848,140 @@ def test_extract_block_diag(shape): res = _extract_block_diag(_block_diag(sp_x), shape) assert_equal(res.toarray(), sp_x.toarray()) + + +add_sub_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)) +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_add_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a + b + exp = np.add(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + res = a + b.toarray() + assert_equal(res, exp) + +@pytest.mark.parametrize(('a_shape', 'b_shape'), add_sub_shapes) +def test_sub_no_broadcasting(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + + res = a - b + exp = np.subtract(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + res = a - b.toarray() + assert_equal(res, exp) + +argmax_argmin_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((2,0), 0), ((3,0,0,2), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((3,2,4,7), -2), ((4,5,7,8,2), 4), ((4,5,7,8,2), -3), +] +@pytest.mark.parametrize(('shape', 'axis'), argmax_argmin_shapes_axis) +def test_argmax_argmin(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.argmax(axis=axis) + exp = np.argmax(a.toarray(), axis=axis) + assert_equal(res, exp) + + res = a.argmin(axis=axis) + exp = np.argmin(a.toarray(), axis=axis) + assert_equal(res, exp) + + +max_min_shapes_axis = [ + ((3,), None), ((3,), 0), + ((4,6), 1), ((7,3), 0), ((3,5), None), + ((2,8,7), 2), ((2,8,7), 0), + ((3,2,4,7), None), ((3,2,4,7), 1), ((3,2,4,7), 0), ((3,2,4,7), 2), + ((4,5,7,8,2), 4), ((4,5,8,1), 3), ((4,6), (0,)), ((4,6), (0,1)), + ((3,0,2), 2), ((3,0,2), (0,2)), ((3,0), 0), + ((3,7,8,5), (0,1)), ((3,7,8,5), (2,1)), ((3,7,8,5), (2,0)), + ((3,7,8,5), (0,-2)), ((3,7,8,5), (-1,2)), ((3,7,8,5), (3)), + ((3,7,8,5), (0,1,2)), ((3,7,8,5), (0,1,2,3)), +] +@pytest.mark.parametrize(('shape', 'axis'), max_min_shapes_axis) +def test_min_max(shape, axis): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res_min = a.min(axis=axis) + exp_min = np.min(a.toarray(), axis=axis) + res_max = a.max(axis=axis) + exp_max = np.max(a.toarray(), axis=axis) + res_nanmin = a.nanmin(axis=axis) + exp_nanmin = np.nanmin(a.toarray(), axis=axis) + res_nanmax = a.nanmax(axis=axis) + exp_nanmax = np.nanmax(a.toarray(), axis=axis) + + for res, exp in [(res_min, exp_min), (res_max, exp_max), + (res_nanmin, exp_nanmin), (res_nanmax, exp_nanmax)]: + if np.issubdtype(type(res), np.number): + assert_equal(res, exp) + else: + assert_equal(res.toarray(), exp) + + +def test_min_max_full(): + for a in (coo_array([[[1, 2, 3, 4]]]), coo_array([[1, 2, 3, 4]])): + assert a.min() == 1 + assert (-a).max() == -1 + + +sum_mean_params = [ + ((3,), None, None), ((3,), 0, None), + ((4,6), 1, None), ((7,3), 0, None), ((3,5), None, None), + ((2,8,7), 2, None), ((2,8,7), 0, np.zeros((8,7))), + ((3,2,4,7), None, None), ((3,2,4,7), 1, np.zeros((3,4,7))), + ((3,2,4,7), 0, None), ((4,5,7,8,2), 4, None), + ((4,5,8,1), 3, None), ((4,6), (0,), None), ((4,6), (0,1), None), + ((3,0,2), 2, None), ((3,0,2), (0,2), None), ((3,0), 0, None), + ((3,7,8,5), (0,1), np.zeros((8,5))), ((3,7,8,5), (2,1), None), + ((3,7,8,5), (0,-2), None), ((3,7,8,5), (-1,2), np.zeros((3,7))), + ((3,7,8,5), (3), None), ((3,7,8,5), (0,1,2), np.zeros((5,))), + ((3,7,8,5), (0,1,2,3), None), +] +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_sum(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.sum(axis=axis, out=out) + exp = np.sum(a.toarray(), axis=axis) + assert_equal(res, exp) + if out is not None: + assert_equal(out, exp) + assert id(res) == id(out) + + +@pytest.mark.parametrize(('shape', 'axis', 'out'), sum_mean_params) +def test_mean(shape, axis, out): + rng = np.random.default_rng(23409823) + a = random_array(shape, density=0.6, random_state=rng, dtype=int) + + res = a.mean(axis=axis, out=out) + exp = np.mean(a.toarray(), axis=axis) + assert_allclose(res, exp) + if out is not None: + assert id(res) == id(out) + assert_allclose(out, exp) + + +def test_pow_abs_round(): + rng = np.random.default_rng(23409823) + a = random_array((3,6,5,2,4), density=0.6, random_state=rng, dtype=int) + assert_allclose((a**3).toarray(), np.power(a.toarray(), 3)) + assert_allclose((a**7).toarray(), np.power(a.toarray(), 7)) + assert_allclose(round(a).toarray(), np.round(a.toarray())) + assert_allclose(abs(a).toarray(), np.abs(a.toarray())) diff --git a/scipy/sparse/tests/test_sputils.py b/scipy/sparse/tests/test_sputils.py index 7a34a56ad335..345f89f8585c 100644 --- a/scipy/sparse/tests/test_sputils.py +++ b/scipy/sparse/tests/test_sputils.py @@ -131,20 +131,19 @@ def test_validateaxis(self): for ax in [5, -5, (0, 5), (-5, 0)]: with assert_raises(ValueError, match="out of range"): sputils.validateaxis(ax, ndim=2) - for axis in (0, 1, None): + for axis in ((0,), (1,), None): assert sputils.validateaxis(axis, ndim=2) == axis - convert_axis_2d = {-2: 0, -1: 1, (0, 1): None, (0, -1): None} - for axis, canonical_axis in convert_axis_2d.items(): + axis_2d = {-2: (0,), -1: (1,), 0: (0,), 1: (1,), (0, 1): None, (0, -1): None} + for axis, canonical_axis in axis_2d.items(): assert sputils.validateaxis(axis, ndim=2) == canonical_axis # ndim 4 - for axis in (2, 3, (2, 3), (2, 1), (0, 3)): + for axis in ((2,), (3,), (2, 3), (2, 1), (0, 3)): assert sputils.validateaxis(axis, ndim=4) == axis - convert_axis_4d = {-4: 0, -3: 1, (3, -4): (3, 0)} - for axis, canonical_axis in convert_axis_4d.items(): + axis_4d = {-4: (0,), -3: (1,), 2: (2,), 3: (3,), (3, -4): (3, 0)} + for axis, canonical_axis in axis_4d.items(): sputils.validateaxis(axis, ndim=4) == canonical_axis - @pytest.mark.parametrize("container", [csr_array, bsr_array]) def test_safely_cast_index_compressed(self, container): # This is slow to test completely as nnz > imax is big From 9d3217ffdff8f7a2d38d4d6fb67f5435594e260a Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Wed, 14 May 2025 21:13:04 -0700 Subject: [PATCH 189/251] ENH: sparse: ND binary operations support (#22897) * support nD COO boolean comparison. Refactor _inequality to _base.py * add nD COO support for multiply, divide, and tests * Support nD maximum/minimum. Requires moving _maximum_minimum to base.py * fix pydata for eq and ne, add support for the other comparisons, add tests import fixes * fix typo and fstrings * change varnames: e.g. csr_self * make csr.multiply private, move scalar-check to _base * improve shape messages and add comments about custom spclass support * add helpful comments * add comments questioning status quo --- scipy/sparse/_base.py | 216 ++++++++++++++++-- scipy/sparse/_compressed.py | 195 +--------------- scipy/sparse/_coo.py | 2 +- .../sparse/linalg/tests/test_pydata_sparse.py | 14 ++ scipy/sparse/tests/test_arithmetic1d.py | 5 +- scipy/sparse/tests/test_base.py | 9 +- scipy/sparse/tests/test_coo.py | 136 ++++++++++- 7 files changed, 369 insertions(+), 208 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index 30e61ccd8f72..a8060fa4860a 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -1,11 +1,13 @@ """Base class for sparse matrices""" +from warnings import warn import math import numpy as np +import operator from ._sputils import (asmatrix, check_reshape_kwargs, check_shape, get_sum_dtype, isdense, isscalarlike, _todata, - matrix, validateaxis, getdtype) + matrix, validateaxis, getdtype, is_pydata_spmatrix) from scipy._lib._sparse import SparseABC, issparse from ._matrix import spmatrix @@ -64,6 +66,18 @@ class SparseEfficiencyWarning(SparseWarning): MAXPRINT = 50 +# helper dicts to manipulate comparison operators +# We negate operators (with warning) when all implicit values would be True +op_neg = {operator.eq: operator.ne, operator.ne: operator.eq, + operator.lt: operator.ge, operator.ge: operator.lt, + operator.gt: operator.le, operator.le: operator.gt} + + +# We use symbolic version of operators in warning messages. +op_sym = {operator.eq: '==', operator.ge: '>=', operator.le: '<=', + operator.ne: '!=', operator.gt: '>', operator.lt: '<'} + + # `_spbase` is a subclass of `SparseABC`. # This allows other submodules to check for instances of sparse subclasses # via `scipy._lib._sparse.issparse`, without introducing @@ -143,7 +157,7 @@ def reshape(self, *args, **kwargs): Parameters ---------- - shape : length-2 tuple of ints + shape : tuple of ints The new shape should be compatible with the original shape. order : {'C', 'F'}, optional Read the elements using this index order. 'C' means to read and @@ -475,18 +489,94 @@ def asformat(self, format, copy=False): #################################################################### def multiply(self, other): - """Point-wise multiplication by another array/matrix.""" + """Element-wise multiplication by another array/matrix.""" if isscalarlike(other): return self._mul_scalar(other) - return self.tocsr().multiply(other) + + if self.ndim < 3: + return self.tocsr()._multiply_2d_with_broadcasting(other) + + if not (issparse(other) or isdense(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if self.shape != other.shape: + raise ValueError("inconsistent shapes: >2D multiply() does not yet " + "support broadcasting") + + # self is >2D so must be COO + if isdense(other): + data = np.multiply(self.data, other[self.coords]) + result = self.copy() + result.data = data.view(np.ndarray).ravel() + return result + + elif issparse(other): + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + return csr_self._binopt(csr_other, '_elmul_').reshape(self.shape) + + else: + # Not scalar, dense or sparse. Return NotImplemented so that + # other's __rmul__ can kick in if that's implemented. + return NotImplemented + + def _maximum_minimum(self, other, np_op): + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # We don't know how to handle it either. + raise NotImplementedError('maximum or minimum with an unrecognized ' + 'array type is not supported') + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if np_op(0, other): + pos_neg = 'positive' if np_op == np.maximum else 'negative' + warn(f"Taking {np_op.__name__} with a {pos_neg} number results in a" + " dense matrix.", SparseEfficiencyWarning, stacklevel=3) + return self.__class__(np_op(self.toarray(), other)) + else: + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + result = csr_self._scalar_binopt(other, np_op) + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + elif isdense(other): + return np_op(self.todense(), other) + elif issparse(other): + if self.shape != other.shape: + raise ValueError(f"inconsistent shapes {self.shape=} {other.shape=}") + if self.ndim < 3: # shape is same so other.ndim < 3 + return self.tocsr()._binopt(other, f'_{np_op.__name__}_') + csr_self = self.reshape(1, -1).tocsr() + csr_other = other.reshape(1, -1).tocsr() + result = csr_self._binopt(csr_other, f'_{np_op.__name__}_') + return result.tocoo().reshape(self.shape) + else: + raise ValueError("Operands not compatible.") def maximum(self, other): """Element-wise maximum between this and another array/matrix.""" - return self.tocsr().maximum(other) + return self._maximum_minimum(other, np.maximum) def minimum(self, other): """Element-wise minimum between this and another array/matrix.""" - return self.tocsr().minimum(other) + return self._maximum_minimum(other, np.minimum) def dot(self, other): """Ordinary dot product @@ -516,23 +606,96 @@ def _broadcast_to(self, shape, copy=False): else: return self.tocsr()._broadcast_to(shape, copy) + def _comparison(self, other, op): + # We convert to CSR format and use methods _binopt or _scalar_binopt + # If ndim>2 we reshape to 2D, compare and then reshape back to nD + if not (issparse(other) or isdense(other) or isscalarlike(other)): + if is_pydata_spmatrix(other): + # cannot compare with pydata other, but it might compare with us. + return NotImplemented + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so + # other's dunder methods can kick in if implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + + if isscalarlike(other): + if not op(0, other): + if np.isnan(other): # op is not `ne`, so results are all False. + return self.__class__(self.shape, dtype=np.bool_) + else: + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + res = csr_self._scalar_binopt(other, op) + return res if self.ndim < 3 else res.tocoo().reshape(self.shape) + else: + warn(f"Comparing a sparse matrix with {other} using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + if np.isnan(other): + # op is `ne` cuz op(0, other) and isnan(other). Return all True. + return self.__class__(np.ones(self.shape, dtype=np.bool_)) + + # op is eq, le, or ge. Use negated op and then negate. + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + inv = csr_self._scalar_binopt(other, op_neg[op]) + all_true = csr_self.__class__(np.ones(csr_self.shape, dtype=np.bool_)) + result = all_true - inv + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + + elif isdense(other): + return op(self.todense(), other) + + elif issparse(other): + # TODO sparse broadcasting + if self.shape != other.shape: + # eq and ne return True or False instead of an array when the shapes + # don't match. Numpy doesn't do this. Is this what we want? + if op in (operator.eq, operator.ne): + return op == operator.eq + raise ValueError("inconsistent shape") + + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + csr_other = (other if other.ndim < 3 else other.reshape(1, -1)).tocsr() + if not op(0, 0): + result = csr_self._binopt(csr_other, f'_{op.__name__}_') + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # result will not be sparse. Use negated op and then negate. + warn(f"Comparing two sparse matrices using {op_sym[op]} " + f"is inefficient. Try using {op_sym[op_neg[op]]} instead.", + SparseEfficiencyWarning, stacklevel=3) + inv = csr_self._binopt(csr_other, f'_{op_neg[op].__name__}_') + all_true = csr_self.__class__(np.ones(csr_self.shape, dtype=np.bool_)) + result = all_true - inv + return result if self.ndim < 3 else result.tocoo().reshape(self.shape) + else: + # cannot compare with other, but it might compare with us. + return NotImplemented + def __eq__(self, other): - return self.tocsr().__eq__(other) + return self._comparison(other, operator.eq) def __ne__(self, other): - return self.tocsr().__ne__(other) + return self._comparison(other, operator.ne) def __lt__(self, other): - return self.tocsr().__lt__(other) + return self._comparison(other, operator.lt) def __gt__(self, other): - return self.tocsr().__gt__(other) + return self._comparison(other, operator.gt) def __le__(self, other): - return self.tocsr().__le__(other) + return self._comparison(other, operator.le) def __ge__(self, other): - return self.tocsr().__ge__(other) + return self._comparison(other, operator.ge) def __abs__(self): return abs(self.tocsr()) @@ -647,12 +810,12 @@ def _matmul_dispatch(self, other): # If it's a list or whatever, treat it like an array other_a = np.asanyarray(other) - if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. # Not interpretable as an array; return NotImplemented so that # other's __rmatmul__ can kick in if that's implemented. return NotImplemented - + # Allow custom sparse class indicated by attr sparse gh-6520 try: other.shape except AttributeError: @@ -752,6 +915,21 @@ def __rmatmul__(self, other): #################### def _divide(self, other, true_divide=False, rdivide=False): + # Do we need to continue to support true_divide and divide? + if not (issparse(other) or isdense(other) or isscalarlike(other)): + # If it's a list or whatever, treat it like an array + other_a = np.asanyarray(other) + if other_a.ndim == 0 and other_a.dtype == np.object_: + # numpy creates a 0d object array if all else fails. + # Not interpretable as an array; return NotImplemented so that + # other's __rdiv__ can kick in if that's implemented. + return NotImplemented + # Allow custom sparse class indicated by attr sparse gh-6520 + try: + other.shape + except AttributeError: + other = other_a + if isscalarlike(other): if rdivide: if true_divide: @@ -787,12 +965,16 @@ def _divide(self, other, true_divide=False, rdivide=False): if rdivide: return other._divide(self, true_divide, rdivide=False) - self_csr = self.tocsr() + csr_self = (self if self.ndim < 3 else self.reshape(1, -1)).tocsr() + csr_other = (other if self.ndim < 3 else other.reshape(1, -1)).tocsr() if true_divide and np.can_cast(self.dtype, np.float64): - return self_csr.astype(np.float64)._divide_sparse(other) + result = csr_self.astype(np.float64)._divide_sparse(csr_other) else: - return self_csr._divide_sparse(other) + result = csr_self._divide_sparse(csr_other) + return result if self.ndim < 3 else result.reshape(self.shape) else: + # not scalar, dense or sparse. Return NotImplemented so + # other's __rdiv__ can kick in if that's implemented. return NotImplemented def __truediv__(self, other): diff --git a/scipy/sparse/_compressed.py b/scipy/sparse/_compressed.py index d6910bd8fb8e..37ae345f829d 100644 --- a/scipy/sparse/_compressed.py +++ b/scipy/sparse/_compressed.py @@ -3,7 +3,6 @@ from warnings import warn import itertools -import operator import numpy as np from scipy._lib._util import _prune_array, copy_if_needed @@ -18,10 +17,9 @@ csr_sum_duplicates, csr_has_sorted_indices, csr_sort_indices, csr_matmat_maxnnz, csr_matmat) from ._index import IndexMixin -from ._sputils import (upcast, upcast_char, to_native, isdense, isshape, - getdtype, isscalarlike, isintlike, downcast_intp_index, - get_sum_dtype, check_shape, get_index_dtype, broadcast_shapes, - is_pydata_spmatrix) +from ._sputils import (upcast, upcast_char, to_native, isshape, + getdtype, isintlike, downcast_intp_index, + get_sum_dtype, check_shape, get_index_dtype, broadcast_shapes) class _cs_matrix(_data_matrix, _minmax_mixin, IndexMixin): @@ -241,135 +239,6 @@ def _scalar_binopt(self, other, op): res.eliminate_zeros() return res - def __eq__(self, other): - # Scalar other. - if isscalarlike(other): - if np.isnan(other): - return self.__class__(self.shape, dtype=np.bool_) - - if other == 0: - warn("Comparing a sparse matrix with 0 using == is inefficient" - ", try using != instead.", SparseEfficiencyWarning, - stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - inv = self._scalar_binopt(other, operator.ne) - return all_true - inv - else: - return self._scalar_binopt(other, operator.eq) - # Dense other. - elif isdense(other): - return self.todense() == other - # Pydata sparse other. - elif is_pydata_spmatrix(other): - return NotImplemented - # Sparse other. - elif issparse(other): - warn("Comparing sparse matrices using == is inefficient, try using" - " != instead.", SparseEfficiencyWarning, stacklevel=3) - # TODO sparse broadcasting - if self.shape != other.shape: - return False - elif self.format != other.format: - other = other.asformat(self.format) - res = self._binopt(other, '_ne_') - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - return all_true - res - else: - return NotImplemented - - def __ne__(self, other): - # Scalar other. - if isscalarlike(other): - if np.isnan(other): - warn("Comparing a sparse matrix with nan using != is" - " inefficient", SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - return all_true - elif other != 0: - warn("Comparing a sparse matrix with a nonzero scalar using !=" - " is inefficient, try using == instead.", - SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape), dtype=np.bool_) - inv = self._scalar_binopt(other, operator.eq) - return all_true - inv - else: - return self._scalar_binopt(other, operator.ne) - # Dense other. - elif isdense(other): - return self.todense() != other - # Pydata sparse other. - elif is_pydata_spmatrix(other): - return NotImplemented - # Sparse other. - elif issparse(other): - # TODO sparse broadcasting - if self.shape != other.shape: - return True - elif self.format != other.format: - other = other.asformat(self.format) - return self._binopt(other, '_ne_') - else: - return NotImplemented - - def _inequality(self, other, op, op_name, bad_scalar_msg): - # Scalar other. - if isscalarlike(other): - if 0 == other and op_name in ('_le_', '_ge_'): - raise NotImplementedError(" >= and <= don't work with 0.") - elif op(0, other): - warn(bad_scalar_msg, SparseEfficiencyWarning, stacklevel=3) - other_arr = np.empty(self.shape, dtype=np.result_type(other)) - other_arr.fill(other) - other_arr = self.__class__(other_arr) - return self._binopt(other_arr, op_name) - else: - return self._scalar_binopt(other, op) - # Dense other. - elif isdense(other): - return op(self.todense(), other) - # Sparse other. - elif issparse(other): - # TODO sparse broadcasting - if self.shape != other.shape: - raise ValueError("inconsistent shapes") - elif self.format != other.format: - other = other.asformat(self.format) - if op_name not in ('_ge_', '_le_'): - return self._binopt(other, op_name) - - warn("Comparing sparse matrices using >= and <= is inefficient, " - "using <, >, or !=, instead.", - SparseEfficiencyWarning, stacklevel=3) - all_true = self.__class__(np.ones(self.shape, dtype=np.bool_)) - res = self._binopt(other, '_gt_' if op_name == '_le_' else '_lt_') - return all_true - res - else: - return NotImplemented - - def __lt__(self, other): - return self._inequality(other, operator.lt, '_lt_', - "Comparing a sparse matrix with a scalar " - "greater than zero using < is inefficient, " - "try using >= instead.") - - def __gt__(self, other): - return self._inequality(other, operator.gt, '_gt_', - "Comparing a sparse matrix with a scalar " - "less than zero using > is inefficient, " - "try using <= instead.") - - def __le__(self, other): - return self._inequality(other, operator.le, '_le_', - "Comparing a sparse matrix with a scalar " - "greater than zero using <= is inefficient, " - "try using > instead.") - - def __ge__(self, other): - return self._inequality(other, operator.ge, '_ge_', - "Comparing a sparse matrix with a scalar " - "less than zero using >= is inefficient, " - "try using < instead.") - ################################# # Arithmetic operator overrides # ################################# @@ -391,12 +260,9 @@ def _add_sparse(self, other): def _sub_sparse(self, other): return self._binopt(other, '_minus_') - def multiply(self, other): - """Point-wise multiplication by array/matrix, vector, or scalar.""" - # Scalar multiplication. - if isscalarlike(other): - return self._mul_scalar(other) - # Sparse matrix or vector. + def _multiply_2d_with_broadcasting(self, other): + """Element-wise multiplication by array/matrix, vector, or scalar.""" + # Called after checking that other is not scalarlike and self.ndim <=2 if issparse(other): if self.shape == other.shape: other = self.__class__(other) @@ -440,7 +306,7 @@ def multiply(self, other): if sN == 1 and sM == oM: new_self = _make_diagonal_csr(self.toarray().ravel(), is_array) return new_self._matmul_sparse(other) - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") # Assume other is a dense matrix/array, which produces a single-item # object array if other isn't convertible to ndarray. @@ -476,7 +342,7 @@ def multiply(self, other): elif other2d.shape[1] == self.shape[-1]: # Dense 2d matrix. data = np.multiply(ret.data, other2d[:, ret.col]) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") idx_dtype = self._get_index_dtype(ret.col, maxval=ret.nnz * other2d.shape[0]) row = np.repeat(np.arange(other2d.shape[0], dtype=idx_dtype), ret.nnz) @@ -493,7 +359,7 @@ def multiply(self, other): elif other2d.shape[0] == self.shape[0]: # Dense 2d array. data = np.multiply(ret.data[:, None], other2d[ret.row]) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") idx_dtype = self._get_index_dtype(ret.row, maxval=ret.nnz * other2d.shape[1]) row = np.repeat(ret.row.astype(idx_dtype, copy=False), other2d.shape[1]) @@ -510,7 +376,7 @@ def multiply(self, other): elif other2d.shape[1] == 1 and self.shape[0] == other2d.shape[0]: data = np.multiply(ret.data, other2d[ret.row].ravel()) else: - raise ValueError("inconsistent shapes") + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") ret.data = data.view(np.ndarray).ravel() return ret @@ -619,45 +485,6 @@ def diagonal(self, k=0): diagonal.__doc__ = _spbase.diagonal.__doc__ - ##################### - # Other binary ops # - ##################### - - def _maximum_minimum(self, other, npop, op_name, dense_check): - if isscalarlike(other): - if dense_check(other): - warn("Taking maximum (minimum) with > 0 (< 0) number results" - " to a dense matrix.", SparseEfficiencyWarning, - stacklevel=3) - other_arr = np.empty(self.shape, dtype=np.asarray(other).dtype) - other_arr.fill(other) - other_arr = self.__class__(other_arr) - return self._binopt(other_arr, op_name) - else: - self.sum_duplicates() - new_data = npop(self.data, np.asarray(other)) - mat = self.__class__((new_data, self.indices, self.indptr), - dtype=new_data.dtype, shape=self.shape) - return mat - elif isdense(other): - return npop(self.todense(), other) - elif issparse(other): - return self._binopt(other, op_name) - else: - raise ValueError("Operands not compatible.") - - def maximum(self, other): - return self._maximum_minimum(other, np.maximum, - '_maximum_', lambda x: np.asarray(x) > 0) - - maximum.__doc__ = _spbase.maximum.__doc__ - - def minimum(self, other): - return self._maximum_minimum(other, np.minimum, - '_minimum_', lambda x: np.asarray(x) < 0) - - minimum.__doc__ = _spbase.minimum.__doc__ - ##################### # Reduce operations # ##################### @@ -1390,7 +1217,7 @@ def _divide_sparse(self, other): Divide this matrix by a second sparse matrix. """ if other.shape != self.shape: - raise ValueError('inconsistent shapes') + raise ValueError(f"inconsistent shapes {self.shape} and {other.shape}") r = self._binopt(other, '_eldiv_') diff --git a/scipy/sparse/_coo.py b/scipy/sparse/_coo.py index a7a4994055ee..3b85916ef508 100644 --- a/scipy/sparse/_coo.py +++ b/scipy/sparse/_coo.py @@ -685,7 +685,7 @@ def _matmul_dispatch(self, other): # Not interpretable as an array; return NotImplemented so that # other's __rmatmul__ can kick in if that's implemented. return NotImplemented - + # Allow custom sparse class indicated by attr sparse gh-6520 try: other.shape except AttributeError: diff --git a/scipy/sparse/linalg/tests/test_pydata_sparse.py b/scipy/sparse/linalg/tests/test_pydata_sparse.py index f6b855271063..0b6a304f4b7f 100644 --- a/scipy/sparse/linalg/tests/test_pydata_sparse.py +++ b/scipy/sparse/linalg/tests/test_pydata_sparse.py @@ -256,3 +256,17 @@ def test_ne(same_matrix): # temporary splint until pydata sparse support sparray equality sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) assert not (sp_sparse != pd_sparse).any() + + +def test_ge(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert (sp_sparse >= pd_sparse).all() + + +def test_gt(same_matrix): + sp_sparse, pd_sparse = same_matrix + # temporary splint until pydata sparse support sparray equality + sp_sparse = sp.coo_matrix(sp_sparse).asformat(sp_sparse.format) + assert not (sp_sparse > pd_sparse).any() diff --git a/scipy/sparse/tests/test_arithmetic1d.py b/scipy/sparse/tests/test_arithmetic1d.py index 4ad8679508b2..62b56f5fe2f6 100644 --- a/scipy/sparse/tests/test_arithmetic1d.py +++ b/scipy/sparse/tests/test_arithmetic1d.py @@ -194,7 +194,10 @@ def test_elementwise_multiply_broadcast(self, spcreator): with pytest.raises(ValueError, match=matchme): i.multiply(j) continue - sp_mult = i.multiply(j) + try: + sp_mult = i.multiply(j) + except ValueError: + continue assert_allclose(toarray(sp_mult), dense_mult) def test_elementwise_divide(self, spcreator, dat1d): diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index d331720ae522..85f8165c8362 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -1666,7 +1666,10 @@ def test_elementwise_multiply_broadcast(self): except ValueError: assert_raises(ValueError, i.multiply, j) continue - sp_mult = i.multiply(j) + try: + sp_mult = i.multiply(j) + except ValueError: + continue if issparse(sp_mult): assert_almost_equal(sp_mult.toarray(), dense_mult) else: @@ -2083,8 +2086,8 @@ def check(dtype, dtype2, btype): with suppress_warnings() as sup: sup.filter(SparseEfficiencyWarning, - "Taking maximum .minimum. with > 0 .< 0. number " - "results to a dense matrix") + "Taking (maximum|minimum) with a (positive|negative) number " + "results in a dense matrix") max_s = A.maximum(B) min_s = A.minimum(B) diff --git a/scipy/sparse/tests/test_coo.py b/scipy/sparse/tests/test_coo.py index db8c3082b704..42b538b31a48 100644 --- a/scipy/sparse/tests/test_coo.py +++ b/scipy/sparse/tests/test_coo.py @@ -1,8 +1,9 @@ +import math import numpy as np -from numpy.testing import assert_equal, assert_allclose +from numpy.testing import assert_equal, assert_allclose, suppress_warnings import pytest from scipy.linalg import block_diag -from scipy.sparse import coo_array, random_array +from scipy.sparse import coo_array, random_array, SparseEfficiencyWarning from .._coo import _block_diag, _extract_block_diag @@ -985,3 +986,134 @@ def test_pow_abs_round(): assert_allclose((a**7).toarray(), np.power(a.toarray(), 7)) assert_allclose(round(a).toarray(), np.round(a.toarray())) assert_allclose(abs(a).toarray(), np.abs(a.toarray())) + + +#bitwise_op_and_compare_broadcast_shapes = [ +# ((3,4), (3,4)), ((1,4), (2,1)), ((3,5), (1,)), ((1,), (7,8)), +# ((3,4,6), (3,4,6)), ((4,3), (2,1,3)), ((2,1,3), (4,3)), +# ((3,5,4), (1,)), ((1,), (7,8,4)), ((16,1,6), (2,6)), ((3,7,5), (3,7,5)), +# ((16,2,6), (1,2,6)), ((7,8), (5,7,8)), ((4,5,1), (5,1)), +# ((6,8,3), (4,1,1,3)), ((1,1,1), (3,4,2)), ((3,4,2), (1,1,1,1,1)), +bitwise_op_and_compare_shapes = [ + ((3,4), (3,4)), ((3,4,6), (3,4,6)), ((3,7,5), (3,7,5)), +] +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_boolean_comparisons(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + assert_equal((a==b).toarray(), a.toarray()==b.toarray()) + assert_equal((a!=b).toarray(), a.toarray()!=b.toarray()) + assert_equal((a>=b).toarray(), a.toarray()>=b.toarray()) + assert_equal((a<=b).toarray(), a.toarray()<=b.toarray()) + assert_equal((a>b).toarray(), a.toarray()>b.toarray()) + assert_equal((a=b).toarray(), np.bitwise_not((ab).toarray())) + + +def test_boolean_comparisons_with_scalar(): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array((5,4,8,7), density=0.6, random_state=rng, dtype=int) + with sup: + assert_equal((a==0).toarray(), a.toarray()==0) + assert_equal((a!=0).toarray(), a.toarray()!=0) + assert_equal((a>=1).toarray(), a.toarray()>=1) + assert_equal((a<=1).toarray(), a.toarray()<=1) + assert_equal((a>0).toarray(), a.toarray()>0) + assert_equal((a<0).toarray(), a.toarray()<0) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_multiply(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + res = a * b + exp = np.multiply(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_multiply_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a * 7 + exp = np.multiply(a.toarray(), 7) + assert_equal(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_divide(a_shape, b_shape): + rng = np.random.default_rng(23409823) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = np.arange(1, 1 + math.prod(b_shape)).reshape(b_shape) + res = a / b + exp = a.toarray() / b + assert_allclose(res.toarray(), exp) + + res = a / b + assert_allclose(res.toarray(), exp) + + +def test_divide_with_scalar(): + rng = np.random.default_rng(23409823) + a = random_array((3,5,4), density=0.6, random_state=rng, dtype=int) + res = a / 7 + exp = a.toarray() / 7 + assert_allclose(res.toarray(), exp) + + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_maximum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + res = a.maximum(b) + exp = np.maximum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + +@pytest.mark.parametrize(('a_shape', 'b_shape'), bitwise_op_and_compare_shapes) +def test_minimum(a_shape, b_shape): + rng = np.random.default_rng(23409823) + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = random_array(a_shape, density=0.6, random_state=rng, dtype=int) + b = random_array(b_shape, density=0.6, random_state=rng, dtype=int) + with sup: + res = a.minimum(b) + exp = np.minimum(a.toarray(), b.toarray()) + assert_equal(res.toarray(), exp) + + +def test_maximum_with_scalar(): + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with sup: + assert_equal(a.maximum(5).toarray(), np.maximum(a.toarray(), 5)) + assert_equal(b.maximum(9).toarray(), np.maximum(b.toarray(), 9)) + assert_equal(c.maximum(5).toarray(), np.maximum(c.toarray(), 5)) + +def test_minimum_with_scalar(): + sup = suppress_warnings() + sup.filter(SparseEfficiencyWarning) + a = coo_array([0,1,6]) + b = coo_array([[15, 0], [14, 5], [0, -12]]) + c = coo_array([[[[3,0], [2,4]], [[8,9], [-3,12]]], + [[[5,2], [3,0]], [[0,7], [0,-6]]]]) + with sup: + assert_equal(a.minimum(5).toarray(), np.minimum(a.toarray(), 5)) + assert_equal(b.minimum(9).toarray(), np.minimum(b.toarray(), 9)) + assert_equal(c.minimum(5).toarray(), np.minimum(c.toarray(), 5)) From a08e18ce3ec85d6d83872f713be317d6afffe36c Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Thu, 15 May 2025 01:54:16 -0700 Subject: [PATCH 190/251] TST: stats.make_distribution: mark as slow --- scipy/stats/tests/test_continuous.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/stats/tests/test_continuous.py b/scipy/stats/tests/test_continuous.py index 7d55aa55d148..c73561eb3586 100644 --- a/scipy/stats/tests/test_continuous.py +++ b/scipy/stats/tests/test_continuous.py @@ -1113,7 +1113,7 @@ def test_rv_generic(self, i, distdata): 'johnsonsb', 'kappa4', 'ksone', 'kstwo', 'kstwobign', 'norminvgauss', 'powerlognorm', 'powernorm', 'recipinvgauss', 'studentized_range', 'vonmises_line', # continuous - 'logser', 'skellam', 'zipf'} # discrete + 'betanbinom', 'logser', 'skellam', 'zipf'} # discrete if not int(os.environ.get('SCIPY_XSLOW', '0')) and distname in slow: pytest.skip('Skipping as XSLOW') From 761649453daf340d00363f000189e014e2cc09a2 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 13:30:22 +0100 Subject: [PATCH 191/251] ENH: `cluster`: more lazy functions (#22959) --- scipy/cluster/hierarchy.py | 301 +++++++++++++++----------- scipy/cluster/tests/test_hierarchy.py | 24 +- 2 files changed, 191 insertions(+), 134 deletions(-) diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index 56620ad9a8d9..d8cac08aee0a 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -134,7 +134,7 @@ import numpy as np from . import _hierarchy, _optimal_leaf_ordering import scipy.spatial.distance as distance -from scipy._lib._array_api import (_asarray, array_namespace, is_dask, is_jax, +from scipy._lib._array_api import (_asarray, array_namespace, is_dask, is_lazy_array, xp_copy) from scipy._lib._disjoint_set import DisjointSet import scipy._lib.array_api_extra as xpx @@ -165,7 +165,7 @@ def _warning(s): def int_floor(arr, xp): # array_api_strict is strict about not allowing `int()` on a float array. # That's typically not needed, here it is - so explicitly convert - return int(xp.astype(xp.asarray(arr), xp.int64)) + return int(xp.asarray(arr, dtype=xp.int64)) def single(y): @@ -1049,7 +1049,8 @@ def cy_linkage(y, validate): return _hierarchy.fast_linkage(y, n, method_code) result = xpx.lazy_apply(cy_linkage, y, validate=lazy, - shape=(n - 1, 4), dtype=xp.float64, as_numpy=True) + shape=(n - 1, 4), dtype=xp.float64, + as_numpy=True, xp=xp) if optimal_ordering: return optimal_leaf_ordering(result, y) @@ -1438,8 +1439,8 @@ def to_tree(Z, rd=False): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, order='C', xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) # Number of original objects is equal to the number of rows plus 1. n = Z.shape[0] + 1 @@ -1523,7 +1524,7 @@ def optimal_leaf_ordering(Z, y, metric='euclidean'): Z = _asarray(Z, order='C', xp=xp) y = _asarray(y, order='C', dtype=xp.float64, xp=xp) lazy = is_lazy_array(Z) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) if y.ndim == 1: distance.is_valid_y(y, throw=True, name='y') @@ -1544,16 +1545,16 @@ def optimal_leaf_ordering(Z, y, metric='euclidean'): # The function name is prominently visible on the user-facing Dask dashboard; # make sure it is meaningful. - def optimal_leaf_ordering_(Z, y, validate): + def cy_optimal_leaf_ordering(Z, y, validate): if validate: - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=np) if not np.all(np.isfinite(y)): raise ValueError("The condensed distance matrix must contain only " "finite values.") return _optimal_leaf_ordering.optimal_leaf_ordering(Z, y) - return xpx.lazy_apply(optimal_leaf_ordering_, Z, y, validate=lazy, - shape=Z.shape, dtype=Z.dtype, as_numpy=True) + return xpx.lazy_apply(cy_optimal_leaf_ordering, Z, y, validate=lazy, + shape=Z.shape, dtype=Z.dtype, as_numpy=True, xp=xp) def cophenet(Z, Y=None): @@ -1666,13 +1667,21 @@ def cophenet(Z, Y=None): xp = array_namespace(Z, Y) # Ensure float64 C-contiguous array. Cython code doesn't deal with striding. Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - n = Z.shape[0] + 1 - zz = np.zeros((n * (n-1)) // 2, dtype=np.float64) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - Z = np.asarray(Z) - _hierarchy.cophenetic_distances(Z, zz, int(n)) - zz = xp.asarray(zz) + def cy_cophenet(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + n = Z.shape[0] + 1 + zz = np.zeros((n * (n-1)) // 2, dtype=np.float64) + _hierarchy.cophenetic_distances(Z, zz, n) + return zz + + n = Z.shape[0] + 1 + zz = xpx.lazy_apply(cy_cophenet, Z, validate=is_lazy_array(Z), + shape=((n * (n-1)) // 2, ), dtype=xp.float64, + as_numpy=True, xp=xp) + if Y is None: return zz @@ -1747,19 +1756,23 @@ def inconsistent(Z, d=2): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - if (not d == np.floor(d)) or d < 0: + if d != np.floor(d) or d < 0: raise ValueError('The second argument d must be a nonnegative ' 'integer value.') - n = Z.shape[0] + 1 - R = np.zeros((n - 1, 4), dtype=np.float64) + def cy_inconsistent(Z, d, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + R = np.zeros((Z.shape[0], 4), dtype=np.float64) + n = Z.shape[0] + 1 + _hierarchy.inconsistent(Z, R, n, d) + return R - Z = np.asarray(Z) - _hierarchy.inconsistent(Z, R, int(n), int(d)) - R = xp.asarray(R) - return R + return xpx.lazy_apply(cy_inconsistent, Z, d=int(d), validate=is_lazy_array(Z), + shape=(Z.shape[0], 4), dtype=xp.float64, + as_numpy=True, xp=xp) def from_mlab_linkage(Z): @@ -1834,32 +1847,45 @@ def from_mlab_linkage(Z): """ xp = array_namespace(Z) Z = _asarray(Z, dtype=xp.float64, order='C', xp=xp) - Zs = Z.shape # If it's empty, return it. - if len(Zs) == 0 or (len(Zs) == 1 and Zs[0] == 0): + if Z.shape in ((), (0, )): return xp_copy(Z, xp=xp) - if len(Zs) != 2: + if Z.ndim != 2: raise ValueError("The linkage array must be rectangular.") # If it contains no rows, return it. - if Zs[0] == 0: + n = Z.shape[0] + if n == 0: return xp_copy(Z, xp=xp) - if xp.min(Z[:, 0:2]) != 1.0 and xp.max(Z[:, 0:2]) != 2 * Zs[0]: + lazy = is_lazy_array(Z) + + if not lazy and xp.min(Z[:, :2]) != 1.0 and xp.max(Z[:, :2]) != 2 * n: raise ValueError('The format of the indices is not 1..N') - Zpart = xp.concat((Z[:, 0:2] - 1.0, Z[:, 2:]), axis=1) - CS = np.zeros((Zs[0],), dtype=np.float64) - if is_jax(xp): - # calculate_cluster_sizes doesn't accept read-only arrays - Zpart = np.array(Zpart, copy=True) - else: - Zpart = np.asarray(Zpart) - _hierarchy.calculate_cluster_sizes(Zpart, CS, int(Zs[0]) + 1) - res = np.hstack([Zpart, CS.reshape(Zs[0], 1)]) - return xp.asarray(res) + res = xp.empty((Z.shape[0], Z.shape[1] + 1), dtype=Z.dtype) + res = xpx.at(res)[:, :2].set(Z[:, :2] - 1.0) + res = xpx.at(res)[:, 2:-1].set(Z[:, 2:]) + + def cy_from_mlab_linkage(Zpart, validate): + n = Zpart.shape[0] + if validate and np.min(Zpart[:, :2]) != 0.0 and np.max(Zpart[:, :2]) != 2 * n: + raise ValueError('The format of the indices is not 1..N') + + if not Zpart.flags.writeable: + Zpart = Zpart.copy() # xp=jax.numpy + + CS = np.zeros((n,)) + _hierarchy.calculate_cluster_sizes(Zpart, CS, n + 1) + return CS + + CS = xpx.lazy_apply(cy_from_mlab_linkage, res[:, :-1], validate=lazy, + shape=(res.shape[0],), dtype=xp.float64, + as_numpy=True, xp=xp) + + return xpx.at(res)[:, -1].set(CS) def to_mlab_linkage(Z): @@ -1938,10 +1964,10 @@ def to_mlab_linkage(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - if Z.ndim == 0 or (Z.ndim == 1 and Z.shape[0] == 0): + Z = _asarray(Z, dtype=xp.float64, xp=xp) + if Z.shape in ((), (0, )): return xp_copy(Z, xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) return xp.concat((Z[:, :2] + 1.0, Z[:, 2:3]), axis=1) @@ -2025,8 +2051,8 @@ def is_monotonic(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) # We expect the i'th value to be greater than its successor. return xp.all(Z[1:, 2] >= Z[:-1, 2]) @@ -2125,16 +2151,17 @@ def is_valid_im(R, warning=False, throw=False, name=None): False """ - return _is_valid_im(R, warning=warning, throw=throw, name=name, materialize=True) + xp = array_namespace(R) + R = _asarray(R, xp=xp) + return _is_valid_im(R, warning=warning, throw=throw, name=name, + materialize=True, xp=xp) -def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False): +def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False, *, xp): """Variant of `is_valid_im` to be called internally by other scipy functions, which by default does not materialize lazy input arrays (Dask, JAX, etc.) when warning=True or throw=True. """ - xp = array_namespace(R) - R = _asarray(R, xp=xp) name_str = f"{name!r} " if name else '' try: if R.dtype != xp.float64: @@ -2259,17 +2286,18 @@ def is_valid_linkage(Z, warning=False, throw=False, name=None): False """ + xp = array_namespace(Z) + Z = _asarray(Z, xp=xp) return _is_valid_linkage(Z, warning=warning, throw=throw, - name=name, materialize=True) + name=name, materialize=True, xp=xp) -def _is_valid_linkage(Z, warning=False, throw=False, name=None, materialize=False): +def _is_valid_linkage(Z, warning=False, throw=False, name=None, + materialize=False, *, xp): """Variant of `is_valid_linkage` to be called internally by other scipy functions, which by default does not materialize lazy input arrays (Dask, JAX, etc.) when warning=True or throw=True. """ - xp = array_namespace(Z) - Z = _asarray(Z, xp=xp) name_str = f"{name!r} " if name else '' try: if Z.dtype != xp.float64: @@ -2397,8 +2425,8 @@ def num_obs_linkage(Z): """ xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + Z = _asarray(Z, xp=xp) + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) return Z.shape[0] + 1 @@ -2451,11 +2479,11 @@ def correspond(Z, Y): True """ - _is_valid_linkage(Z, throw=True) - distance.is_valid_y(Y, throw=True) xp = array_namespace(Z, Y) - Z = _asarray(Z, order='c', xp=xp) - Y = _asarray(Y, order='c', xp=xp) + Z = _asarray(Z, xp=xp) + Y = _asarray(Y, xp=xp) + _is_valid_linkage(Z, throw=True, xp=xp) + distance.is_valid_y(Y, throw=True) return distance.num_obs_y(Y) == num_obs_linkage(Z) @@ -2612,7 +2640,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) n = Z.shape[0] + 1 T = np.zeros((n,), dtype='i') @@ -2627,7 +2655,7 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): R = inconsistent(Z, depth) else: R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_im(R, throw=True, name='R') + _is_valid_im(R, throw=True, name='R', materialize=True, xp=xp) # Since the C code does not support striding using strides. # The dimensions are used instead. R = np.asarray(R) @@ -2741,7 +2769,7 @@ def fclusterdata(X, t, criterion='inconsistent', if R is None: R = inconsistent(Z, d=depth) else: - R = _asarray(R, order='c', xp=xp) + R = _asarray(R, order='C', xp=xp) T = fcluster(Z, criterion=criterion, depth=depth, R=R, t=t) return T @@ -2796,12 +2824,20 @@ def leaves_list(Z): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + + def cy_leaves_list(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + n = Z.shape[0] + 1 + ML = np.zeros((n,), dtype=np.int32) + _hierarchy.prelist(Z, ML, n) + return ML + n = Z.shape[0] + 1 - ML = np.zeros((n,), dtype='i') - Z = np.asarray(Z) - _hierarchy.prelist(Z, ML, n) - return xp.asarray(ML) + return xpx.lazy_apply(cy_leaves_list, Z, validate=is_lazy_array(Z), + shape=(n, ), dtype=xp.int32, + as_numpy=True, xp=xp) # Maps number of leaves to text size. @@ -3335,7 +3371,7 @@ def llf(id): # None orders leaf nodes based on the order they appear in the # pre-order traversal. xp = array_namespace(Z) - Z = _asarray(Z, order='c', xp=xp) + Z = _asarray(Z, order='C', xp=xp) if orientation not in ["top", "left", "bottom", "right"]: raise ValueError("orientation must be one of 'top', 'left', " @@ -3349,7 +3385,7 @@ def llf(id): if Z.shape[0] + 1 != len_labels: raise ValueError("Dimensions of Z and labels must be consistent.") - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', materialize=True, xp=xp) Zs = Z.shape n = Zs[0] + 1 if isinstance(p, int | float): @@ -3759,6 +3795,11 @@ def is_isomorphic(T1, T2): Whether the flat cluster assignments `T1` and `T2` are equivalent. + Notes + ----- + *Array API support (experimental):* If the input is a lazy Array (e.g. Dask + or JAX), the return value will be a 0-dimensional bool Array. + See Also -------- linkage : for a description of what a linkage matrix is. @@ -3773,7 +3814,7 @@ def is_isomorphic(T1, T2): Two flat cluster assignments can be isomorphic if they represent the same cluster assignment, with different labels. - For example, we can use the `scipy.cluster.hierarchy.single`: method + For example, we can use the `scipy.cluster.hierarchy.single` method and flatten the output to four clusters: >>> X = [[0, 0], [0, 1], [1, 0], @@ -3803,34 +3844,37 @@ def is_isomorphic(T1, T2): True """ - T1 = np.asarray(T1, order='c') - T2 = np.asarray(T2, order='c') + xp = array_namespace(T1, T2) + T1 = _asarray(T1, xp=xp) + T2 = _asarray(T2, xp=xp) - T1S = T1.shape - T2S = T2.shape - - if len(T1S) != 1: + if T1.ndim != 1: raise ValueError('T1 must be one-dimensional.') - if len(T2S) != 1: + if T2.ndim != 1: raise ValueError('T2 must be one-dimensional.') - if T1S[0] != T2S[0]: + if T1.shape != T2.shape: raise ValueError('T1 and T2 must have the same number of elements.') - n = T1S[0] - d1 = {} - d2 = {} - for i in range(0, n): - if T1[i] in d1: - if T2[i] not in d2: - return False - if d1[T1[i]] != T2[i] or d2[T2[i]] != T1[i]: + + def py_is_isomorphic(T1, T2): + d1 = {} + d2 = {} + for t1, t2 in zip(T1, T2): + if t1 in d1: + if t2 not in d2: + return False + if d1[t1] != t2 or d2[t2] != t1: + return False + elif t2 in d2: return False - elif T2[i] in d2: - return False - else: - d1[T1[i]] = T2[i] - d2[T2[i]] = T1[i] - return True + else: + d1[t1] = t2 + d2[t2] = t1 + return True + res = xpx.lazy_apply(py_is_isomorphic, T1, T2, + shape=(), dtype=xp.bool, + as_numpy=True, xp=xp) + return res if is_lazy_array(res) else bool(res) def maxdists(Z): """ @@ -3907,14 +3951,18 @@ def maxdists(Z): """ xp = array_namespace(Z) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) - n = Z.shape[0] + 1 - MD = np.zeros((n - 1,)) - Z = np.asarray(Z) - _hierarchy.get_max_dist_for_each_cluster(Z, MD, int(n)) - MD = xp.asarray(MD) - return MD + def cy_maxdists(Z, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + MD = np.zeros((Z.shape[0],)) + _hierarchy.get_max_dist_for_each_cluster(Z, MD, Z.shape[0] + 1) + return MD + + return xpx.lazy_apply(cy_maxdists, Z, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) def maxinconsts(Z, R): @@ -3995,19 +4043,25 @@ def maxinconsts(Z, R): xp = array_namespace(Z, R) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - is_valid_im(R, throw=True, name='R') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + _is_valid_im(R, throw=True, name='R', xp=xp) - n = Z.shape[0] + 1 if Z.shape[0] != R.shape[0]: raise ValueError("The inconsistency matrix and linkage matrix each " "have a different number of rows.") - MI = np.zeros((n - 1,)) - Z = np.asarray(Z) - R = np.asarray(R) - _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, int(n), 3) - MI = xp.asarray(MI) - return MI + + def cy_maxinconsts(Z, R, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + _is_valid_im(R, throw=True, name='R', xp=np) + n = Z.shape[0] + 1 + MI = np.zeros((n - 1,)) + _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MI, n, 3) + return MI + + return xpx.lazy_apply(cy_maxinconsts, Z, R, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) def maxRstat(Z, R, i): @@ -4090,8 +4144,8 @@ def maxRstat(Z, R, i): xp = array_namespace(Z, R) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) R = _asarray(R, order='C', dtype=xp.float64, xp=xp) - is_valid_linkage(Z, throw=True, name='Z') - is_valid_im(R, throw=True, name='R') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) + _is_valid_im(R, throw=True, name='R', xp=xp) if not isinstance(i, int): raise TypeError('The third argument must be an integer.') @@ -4103,13 +4157,18 @@ def maxRstat(Z, R, i): raise ValueError("The inconsistency matrix and linkage matrix each " "have a different number of rows.") - n = Z.shape[0] + 1 - MR = np.zeros((n - 1,)) - Z = np.asarray(Z) - R = np.asarray(R) - _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, int(n), i) - MR = xp.asarray(MR) - return MR + def cy_maxRstat(Z, R, i, validate): + if validate: + _is_valid_linkage(Z, throw=True, name='Z', xp=np) + _is_valid_im(R, throw=True, name='R', xp=np) + MR = np.zeros((Z.shape[0],)) + n = Z.shape[0] + 1 + _hierarchy.get_max_Rfield_for_each_cluster(Z, R, MR, n, i) + return MR + + return xpx.lazy_apply(cy_maxRstat, Z, R, i=i, validate=is_lazy_array(Z), + shape=(Z.shape[0], ), dtype=xp.float64, + as_numpy=True, xp=xp) def leaders(Z, T): @@ -4222,7 +4281,7 @@ def leaders(Z, T): xp = array_namespace(Z, T) Z = _asarray(Z, order='C', dtype=xp.float64, xp=xp) T = _asarray(T, order='C', xp=xp) - _is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=xp) if T.dtype != xp.int32: raise TypeError('T must be a 1-D array of dtype int32.') @@ -4232,9 +4291,9 @@ def leaders(Z, T): n_obs = Z.shape[0] + 1 - def leaders_(Z, T, validate): + def cy_leaders(Z, T, validate): if validate: - is_valid_linkage(Z, throw=True, name='Z') + _is_valid_linkage(Z, throw=True, name='Z', xp=np) n_clusters = int(xpx.nunique(T)) L = np.zeros(n_clusters, dtype=np.int32) M = np.zeros(n_clusters, dtype=np.int32) @@ -4244,6 +4303,6 @@ def leaders_(Z, T, validate): f'when examining linkage node {s} (< 2n-1).') return L, M - return xpx.lazy_apply(leaders_, Z, T, validate=is_lazy_array(Z), + return xpx.lazy_apply(cy_leaders, Z, T, validate=is_lazy_array(Z), shape=((None,), (None, )), dtype=(xp.int32, xp.int32), - as_numpy=True) + as_numpy=True, xp=xp) diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 4e1a620abd52..1eb4c0d8e6ee 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -83,11 +83,10 @@ class eager: lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=True, static_argnames=('rd', )) lazy_xp_function(optimal_leaf_ordering, static_argnames=('metric',)) -lazy_xp_function(cophenet, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(inconsistent, jax_jit=False, allow_dask_compute=2, - static_argnames=('d',)) -lazy_xp_function(from_mlab_linkage, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(to_mlab_linkage, jax_jit=False, allow_dask_compute=1) +lazy_xp_function(cophenet) +lazy_xp_function(inconsistent, static_argnames=('d',)) +lazy_xp_function(from_mlab_linkage) +lazy_xp_function(to_mlab_linkage) lazy_xp_function(is_monotonic) # Note: these functions materialize lazy arrays when warning=True or throw=True @@ -100,13 +99,12 @@ class eager: static_argnames=('criterion', 'depth')) lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=True, static_argnames=('criterion', 'metric', 'depth', 'method')) -lazy_xp_function(leaves_list, jax_jit=False, allow_dask_compute=2) +lazy_xp_function(leaves_list) lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(is_isomorphic, jax_jit=False, allow_dask_compute=2) -lazy_xp_function(maxdists, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(maxinconsts, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(maxRstat, jax_jit=False, allow_dask_compute=True, - static_argnames=('i',)) +lazy_xp_function(is_isomorphic) +lazy_xp_function(maxdists) +lazy_xp_function(maxinconsts) +lazy_xp_function(maxRstat, static_argnames=('i',)) # Returns data-dependent shape lazy_xp_function(leaders, jax_jit=False) @@ -249,6 +247,7 @@ def test_linkage_cophenet_tdist_Z_Y(self, xp): xp_assert_close(c, expectedc, atol=1e-10) xp_assert_close(M, expectedM, atol=1e-10) + @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") def test_gh_22183(self, xp): # check for lack of segfault # (out of bounds memory access) @@ -377,8 +376,7 @@ def test_leaders_single(self, xp): xp_assert_close(xp.concat(L), expect, rtol=1e-15) -@skip_xp_backends(np_only=True, - reason='`is_isomorphic` only supports NumPy backend') +@skip_xp_backends(cpu_only=True, reason='pure-Python algorithm') class TestIsIsomorphic: def test_array_like(self): From 81ca46d7bb5382ba1d21fd6350360f6cc84becee Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 15:58:25 +0100 Subject: [PATCH 192/251] Update scipy/_lib/_array_api.py Co-authored-by: Albert Steppi <1953382+steppi@users.noreply.github.com> --- scipy/_lib/_array_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 672e2d0e3f1b..aa9d9c524af2 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -808,8 +808,8 @@ def decorator(f): try: f.__doc__ = doc except AttributeError: - # Can't update __doc__ on Cython ufuncs if SciPy - # was compiled against NumPy 1.x + # Can't update __doc__ on ufuncs if SciPy + # was compiled against NumPy < 2.2. pass return f From a0bc0fbdc7bd057bb6a36236fa040ab9f1c7d104 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 15:58:36 +0100 Subject: [PATCH 193/251] Update scipy/special/_support_alternative_backends.py Co-authored-by: Albert Steppi <1953382+steppi@users.noreply.github.com> --- scipy/special/_support_alternative_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 5bc89043c443..50b75ea5e0a4 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -19,7 +19,7 @@ class _FuncInfo: func: Callable # Number of arguments, not counting out= # This is for testing purposes only, due to the fact that - # inspect.signature() just returns *args for Cython ufuncs. + # inspect.signature() just returns *args for ufuncs. n_args: int # @xp_capabilities decorator, for the purpose of # documentation and unit testing. Omit to indicate From f50628a4d10a25feb86e5d54d0abafff0e9de0f7 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 15:58:46 +0100 Subject: [PATCH 194/251] Update scipy/special/_support_alternative_backends.py Co-authored-by: Albert Steppi <1953382+steppi@users.noreply.github.com> --- scipy/special/_support_alternative_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 50b75ea5e0a4..7d17884e4141 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -66,7 +66,7 @@ def wrapped(*args, **kwargs): func = self.func capabilities = self.xp_capabilities or xp_capabilities() - # In order to retain a naked Cython ufunc when SCIPY_ARRAY_API is + # In order to retain a naked ufunc when SCIPY_ARRAY_API is # disabled, xp_capabilities must apply its changes in place. cap_func = capabilities(func) assert cap_func is func From 38aaf6a49592a6e2ea7989827f7b3047a0cedb65 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 16:00:32 +0100 Subject: [PATCH 195/251] Apply suggestions from code review Co-authored-by: Albert Steppi <1953382+steppi@users.noreply.github.com> --- scipy/special/_support_alternative_backends.py | 4 ++-- scipy/special/tests/test_support_alternative_backends.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 7d17884e4141..86ca2ab3e6b6 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -250,7 +250,7 @@ def fun(t, df, p): return stdtr(df, t) - p # IMPORTANT: these must all be **elementwise** functions! # PyTorch doesn't implement `betainc`. -# On torch CPU we can fall back to Cython, but on GPU it won't work. +# On torch CPU we can fall back to NumPy, but on GPU it won't work. _needs_betainc = xp_capabilities(cpu_only=True, exceptions=['jax.numpy', 'cupy']) _special_funcs = ( @@ -285,6 +285,6 @@ def fun(t, df, p): return stdtr(df, t) - p # Override ufuncs. # When SCIPY_ARRAY_API is disabled, this exclusively updates the docstrings in place -# and populates the xp_capabilities table, while retaining the original Cython ufuncs. +# and populates the xp_capabilities table, while retaining the original ufuncs. globals().update({nfo.func.__name__: nfo.wrapper for nfo in _special_funcs}) __all__ = [nfo.func.__name__ for nfo in _special_funcs] diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 0e26d029b8ad..436aa470f707 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -201,7 +201,7 @@ def test_repr(func): @pytest.mark.skipif( - np.__version__ < "2", + version.parse(np.__version__) < version.parse("2.2"), reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy 1.x") @pytest.mark.parametrize('func', [nfo.wrapper for nfo in _special_funcs]) def test_doc(func): From 4af8b8255915ad48442b6eaee42da89faf68bc1a Mon Sep 17 00:00:00 2001 From: crusaderky Date: Thu, 15 May 2025 16:08:55 +0100 Subject: [PATCH 196/251] Revert `_get_native_func` --- .../special/_support_alternative_backends.py | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scipy/special/_support_alternative_backends.py b/scipy/special/_support_alternative_backends.py index 86ca2ab3e6b6..e9811fcfe495 100644 --- a/scipy/special/_support_alternative_backends.py +++ b/scipy/special/_support_alternative_backends.py @@ -79,7 +79,7 @@ def _wrapper_for(self, xp): # If a native implementation is available, use that spx = scipy_namespace_for(xp) - f = _get_native_func(spx, self.name) + f = _get_native_func(xp, spx, self.name) if f is not None: return f @@ -131,8 +131,13 @@ def f(*args, _f=_f, xp=xp, **kwargs): return f -def _get_native_func(spx, f_name): - return getattr(spx.special, f_name, None) if spx else None +def _get_native_func(xp, spx, f_name): + f = getattr(spx.special, f_name, None) if spx else None + if f is None and hasattr(xp, 'special'): + # Currently dead branch, in anticipation of 'special' Array API extension + # https://github.com/data-apis/array-api/issues/725 + f = getattr(xp.special, f_name, None) + return f def _rel_entr(xp, spx): @@ -171,7 +176,7 @@ def _chdtr(xp, spx): # defined by `get_array_special_func` is that if `gammainc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtr`. - gammainc = _get_native_func(spx, 'gammainc') + gammainc = _get_native_func(xp, spx, 'gammainc') if gammainc is None: return None @@ -190,7 +195,7 @@ def _chdtrc(xp, spx): # defined by `get_array_special_func` is that if `gammaincc` # isn't found, we don't want to use the SciPy version; we'll # return None here and use the SciPy version of `chdtrc`. - gammaincc = _get_native_func(spx, 'gammaincc') + gammaincc = _get_native_func(xp, spx, 'gammaincc') if gammaincc is None: return None @@ -203,7 +208,7 @@ def __chdtrc(v, x): def _betaincc(xp, spx): - betainc = _get_native_func(spx, 'betainc') + betainc = _get_native_func(xp, spx, 'betainc') if betainc is None: return None @@ -214,7 +219,7 @@ def __betaincc(a, b, x): def _stdtr(xp, spx): - betainc = _get_native_func(spx, 'betainc') + betainc = _get_native_func(xp, spx, 'betainc') if betainc is None: return None @@ -228,7 +233,7 @@ def __stdtr(df, t): def _stdtrit(xp, spx): # Need either native stdtr or native betainc - stdtr = _get_native_func(spx, 'stdtr') or _stdtr(xp, spx) + stdtr = _get_native_func(xp, spx, 'stdtr') or _stdtr(xp, spx) # If betainc is not defined, the root-finding would be done with `xp` # despite `stdtr` being evaluated with SciPy/NumPy `stdtr`. Save the # conversions: in this case, just evaluate `stdtrit` with SciPy/NumPy. From a6ee8f191faa6da266485acf6678c46f874a7878 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Thu, 15 May 2025 16:09:03 +0100 Subject: [PATCH 197/251] fix test --- scipy/special/tests/test_support_alternative_backends.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 436aa470f707..1d4291b79592 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -4,6 +4,7 @@ import pytest from hypothesis import given, strategies import hypothesis.extra.numpy as npst +from packaging import version from scipy import special from scipy.special._support_alternative_backends import _special_funcs From 2bbcaf8f20f6fbbfb16dcf2d43698c3c33486df8 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Thu, 15 May 2025 16:12:23 +0100 Subject: [PATCH 198/251] Apply suggestions from code review Co-authored-by: Albert Steppi <1953382+steppi@users.noreply.github.com> --- scipy/special/tests/test_support_alternative_backends.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/special/tests/test_support_alternative_backends.py b/scipy/special/tests/test_support_alternative_backends.py index 1d4291b79592..e98939fef4b1 100644 --- a/scipy/special/tests/test_support_alternative_backends.py +++ b/scipy/special/tests/test_support_alternative_backends.py @@ -107,7 +107,7 @@ def test_support_alternative_backends(xp, func, nfo, dtype, shapes): args_xp = [arg.rechunk(5) for arg in args_xp] res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function - ref = nfo.func(*args_np) # Unwrapped Cython ufunc + ref = nfo.func(*args_np) # Unwrapped ufunc # When dtype_np is integer, the output dtype can be float atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps @@ -142,7 +142,7 @@ def test_support_alternative_backends_mismatched_dtypes(xp, func, nfo): for arg, dtype_np_ref in zip(args_np, dtypes_np_ref)] res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function - ref = nfo.func(*args_np) # Unwrapped Cython ufunc + ref = nfo.func(*args_np) # Unwrapped ufunc atol = 10 * np.finfo(ref.dtype).eps xp_assert_close(res, xp.asarray(ref), atol=atol) @@ -182,7 +182,7 @@ def test_support_alternative_backends_hypothesis(xp, func, nfo, data): args_np = [np.asarray(arg, dtype=dtype_np_ref) for arg in args_np] res = nfo.wrapper(*args_xp) # Also wrapped by lazy_xp_function - ref = nfo.func(*args_np) # Unwrapped Cython ufunc + ref = nfo.func(*args_np) # Unwrapped ufunc # When dtype_np is integer, the output dtype can be float atol = 0 if ref.dtype.kind in 'iu' else 10 * np.finfo(ref.dtype).eps @@ -203,7 +203,7 @@ def test_repr(func): @pytest.mark.skipif( version.parse(np.__version__) < version.parse("2.2"), - reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy 1.x") + reason="Can't update ufunc __doc__ when SciPy is compiled vs. NumPy < 2.2") @pytest.mark.parametrize('func', [nfo.wrapper for nfo in _special_funcs]) def test_doc(func): """xp_capabilities updates the docstring in place. From 6d699dded4423e4ff9ac97eff9f4912ed24964fa Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Thu, 15 May 2025 17:20:13 +0100 Subject: [PATCH 199/251] MAINT: vendor qhull as subproject and add `-Duse-system-libraries` (#22893) Co-authored-by: Ralf Gommers --- .github/workflows/macos.yml | 6 +- .spin/cmds.py | 10 +- LICENSES_bundled.txt | 4 +- doc/source/dev/core-dev/vendored-code.rst.inc | 34 +- meson.build | 43 +- meson.options | 4 + scipy/spatial/_qhull.pyx | 14 +- scipy/spatial/meson.build | 31 +- scipy/spatial/qhull_misc.c | 7 +- scipy/spatial/qhull_misc.h | 2 +- .../README.scipy => qhull_misc_README.txt} | 12 +- scipy/spatial/qhull_src/README.txt | 720 ------------------ .../qhull_r/libqhull_r}/Announce.txt | 0 .../qhull_r/libqhull_r}/COPYING.txt | 2 +- .../qhull_r/libqhull_r}/geom2_r.c | 0 .../qhull_r/libqhull_r}/geom_r.c | 0 .../qhull_r/libqhull_r}/geom_r.h | 0 .../qhull_r/libqhull_r}/global_r.c | 0 .../qhull_r/libqhull_r}/io_r.c | 0 .../qhull_r/libqhull_r}/io_r.h | 0 .../qhull_r/libqhull_r}/libqhull_r.c | 0 .../qhull_r/libqhull_r}/libqhull_r.h | 0 .../qhull_r/libqhull_r}/mem_r.c | 0 .../qhull_r/libqhull_r}/mem_r.h | 0 .../qhull_r/libqhull_r}/merge_r.c | 0 .../qhull_r/libqhull_r}/merge_r.h | 0 .../qhull_r/libqhull_r}/poly2_r.c | 0 .../qhull_r/libqhull_r}/poly_r.c | 0 .../qhull_r/libqhull_r}/poly_r.h | 0 .../qhull_r/libqhull_r}/qhull_ra.h | 0 .../qhull_r/libqhull_r}/qset_r.c | 0 .../qhull_r/libqhull_r}/qset_r.h | 0 .../qhull_r/libqhull_r}/random_r.c | 0 .../qhull_r/libqhull_r}/random_r.h | 0 .../qhull_r/libqhull_r}/rboxlib_r.c | 0 .../qhull_r/libqhull_r}/stat_r.c | 0 .../qhull_r/libqhull_r}/stat_r.h | 0 .../qhull_r/libqhull_r}/user_r.c | 0 .../qhull_r/libqhull_r}/user_r.h | 0 .../qhull_r/libqhull_r}/usermem_r.c | 0 .../qhull_r/libqhull_r}/userprintf_r.c | 0 .../qhull_r/libqhull_r}/userprintf_rbox_r.c | 0 subprojects/qhull_r/meson.build | 43 ++ tools/vendor_qhull.sh | 30 + 44 files changed, 173 insertions(+), 789 deletions(-) rename scipy/spatial/{qhull_src/README.scipy => qhull_misc_README.txt} (81%) delete mode 100644 scipy/spatial/qhull_src/README.txt rename {scipy/spatial/qhull_src => subprojects/qhull_r/libqhull_r}/Announce.txt (100%) rename {scipy/spatial/qhull_src => subprojects/qhull_r/libqhull_r}/COPYING.txt (99%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/geom2_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/geom_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/geom_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/global_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/io_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/io_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/libqhull_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/libqhull_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/mem_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/mem_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/merge_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/merge_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/poly2_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/poly_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/poly_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/qhull_ra.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/qset_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/qset_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/random_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/random_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/rboxlib_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/stat_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/stat_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/user_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/user_r.h (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/usermem_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/userprintf_r.c (100%) rename {scipy/spatial/qhull_src/src => subprojects/qhull_r/libqhull_r}/userprintf_rbox_r.c (100%) create mode 100644 subprojects/qhull_r/meson.build create mode 100755 tools/vendor_qhull.sh diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 33d5da93faa1..4e7c78a8f832 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -121,7 +121,11 @@ jobs: export BOOST_LIBRARYDIR=${{ env.CONDA }}/envs/scipy-dev/lib rm -rf subprojects/boost_math # so will fail if system boost doesn't work - CC="ccache $CC" spin build + # configure system qhull + mamba install qhull=2020.2 + rm -rf subprojects/qhull_r # so will fail if system qhull doesn't work + + CC="ccache $CC" spin build --use-system-libraries - name: Test SciPy shell: bash -l {0} diff --git a/.spin/cmds.py b/.spin/cmds.py index 433cf2ecf652..0b3cc6942c50 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -53,6 +53,10 @@ help=("If set, use `Accelerate` as the BLAS/LAPACK to build against." " Takes precedence over -with-scipy-openblas (macOS only)") ) +@click.option( + '--use-system-libraries', default=False, is_flag=True, + help=("If set, use system libraries" + "if they are available for subprojects.")) @click.option( '--tags', default="runtime,python-runtime,tests,devel", show_default=True, help="Install tags to be used by meson." @@ -60,7 +64,8 @@ @spin.util.extend_command(spin.cmds.meson.build) def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, release, parallel, setup_args, show_build_log, - with_scipy_openblas, with_accelerate, tags, **kwargs): + with_scipy_openblas, with_accelerate, use_system_libraries, + tags, **kwargs): """🔧 Build package with Meson/ninja and install MESON_ARGS are passed through e.g.: @@ -126,6 +131,9 @@ def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, os.getcwd(), os.environ.get('PKG_CONFIG_PATH', '') ]) + + if use_system_libraries: + meson_args = meson_args + ("-Duse-system-libraries=auto",) if parallel is None: # Use number of physical cores rather than ninja's default of 2N+2, diff --git a/LICENSES_bundled.txt b/LICENSES_bundled.txt index 8c12a9590cc2..477033fcdaa3 100644 --- a/LICENSES_bundled.txt +++ b/LICENSES_bundled.txt @@ -87,9 +87,9 @@ License: 3-clause BSD For details, see scipy/sparse/linalg/eigen/arpack/ARPACK/COPYING Name: Qhull -Files: scipy/spatial/qhull/* +Files: subprojects/qhull_r/libqhull_r/* License: Qhull license (BSD-like) - For details, see scipy/spatial/qhull/COPYING.txt + For details, see subprojects/qhull_r/libqhull_r/COPYING.txt Name: xsf Files: scipy/subprojects/xsf/* diff --git a/doc/source/dev/core-dev/vendored-code.rst.inc b/doc/source/dev/core-dev/vendored-code.rst.inc index b2966e8b13c2..be0920079b65 100644 --- a/doc/source/dev/core-dev/vendored-code.rst.inc +++ b/doc/source/dev/core-dev/vendored-code.rst.inc @@ -3,29 +3,47 @@ Vendored Code ============= Many parts of the SciPy codebase are maintained elsewhere, and vendored in SciPy. -Some of these parts are vendored as git submodules, for example, ``boost_math``. +Some of these parts are vendored as git submodules, for example, ``xsf``, +or are placed under the ``subprojects`` directory (or both). -Other parts are not vendored as git submodules, despite having a maintained upstream. -This is mainly for historical reasons, and it is possible that some of these parts -will see patches contributed upstream and become git submodules in the future. +Other parts are not vendored as git submodules or under the ``subprojects`` directory, +despite having a maintained upstream. This is usually either because: + +1. a subset of the upstream repo is vendored with a script. + (It is possible that these parts will be moved under ``subprojects`` in the future.) + +2. Code has been copied into SciPy and modified since. + (It is possible that some of these parts will see patches contributed upstream and + become git submodules or be moved under ``subprojects`` in the future.) Maintainers should be careful to *not* accept contributions (especially trivial changes) into parts of SciPy where the code is actively maintained upstream. Instead, they should direct contributors to the upstream repo. -Currently, this includes the following parts of the codebase: + +Parts of the codebase which are vendored with a script include: + +- PRIMA_, at ``scipy/_lib/pyprima`` + +Parts of the codebase which contain code copied from an upstream include: - DIRECT_, at ``scipy/optimize/_direct`` - ARPACK_, at ``scipy/sparse/linalg/_eigen/arpack/ARPACK`` - SuperLU_, at ``scipy/sparse/linalg/_dsolve/SuperLU`` -- QHull_, at ``scipy/spatial/qhull_src`` - trlib_, at ``scipy/optimize/_trlib`` - UNU.RAN_, at ``scipy/stats/_unuran`` -- PRIMA_, at ``scipy/_lib/pyprima`` +- `Cython/Tempita`_, at ``scipy/_build_utils/tempita`` +- `fast_matrix_market`_, at ``scipy/io/_fast_matrix_market`` +- `numpydoc/docscrape`_, at ``scipy/_lib/_docscrape.py`` .. _ARPACK: https://github.com/opencollab/arpack-ng .. _SuperLU: https://github.com/xiaoyeli/superlu -.. _QHull: https://github.com/qhull/qhull .. _trlib: https://github.com/felixlen/trlib .. _UNU.RAN: https://statmath.wu.ac.at/unuran/ .. _DIRECT: https://github.com/stevengj/nlopt/tree/master/src/algs/direct .. _PRIMA: https://github.com/libprima/prima +.. _`Cython/Tempita`: https://github.com/cython/cython/tree/master/Cython/Tempita +.. _`fast_matrix_market`: https://github.com/alugowski/fast_matrix_market +.. _`numpydoc/docscrape`: https://github.com/numpy/numpydoc/ + +Please refer to https://github.com/scipy/scipy/issues/21232 for further details and +tracking of vendored code in the repository. diff --git a/meson.build b/meson.build index 19fdc85be865..0ddafd08003d 100644 --- a/meson.build +++ b/meson.build @@ -126,8 +126,8 @@ elif ff.get_id() in ['intel-cl', 'intel-llvm-cl'] '/assume:minus0' ) endif -add_project_arguments(_intel_cflags, language: ['c', 'cpp']) -add_project_arguments(_intel_fflags, language: 'fortran') +add_global_arguments(_intel_cflags, language: ['c', 'cpp']) +add_global_arguments(_intel_fflags, language: 'fortran') # Hide symbols when building on Linux with GCC. For Python extension modules, # we only need `PyInit_*` to be public, anything else may cause problems. So we @@ -163,11 +163,40 @@ endif xsf = subproject('xsf') xsf_dep = xsf.get_variable('xsf_dep') -boost_math_dep = dependency( - 'boost', - version : '1.88.0', - fallback : ['boost_math', 'boost_math_dep'], -) +use_system_libraries = get_option('use-system-libraries') +all_system_libraries = false +auto_system_libraries = false + +if use_system_libraries.contains('none') + use_system_libraries = ['none'] +elif use_system_libraries.contains('all') + all_system_libraries = true +elif use_system_libraries.contains('auto') + auto_system_libraries = true +endif + +if all_system_libraries or use_system_libraries.contains('boost.math') + boost_math_dep = dependency('boost', version : '1.88.0') +elif auto_system_libraries + boost_math_dep = dependency( + 'boost', version : '1.88.0', + fallback : ['boost_math', 'boost_math_dep'] + ) +else + boost_math = subproject('boost_math', version : '1.88.0') + boost_math_dep = boost_math.get_variable('boost_math_dep') +endif +if all_system_libraries or use_system_libraries.contains('qhull') + qhull_r_dep = dependency('qhull_r', version : '8.0.2') +elif auto_system_libraries + qhull_r_dep = dependency( + 'qhull_r', version : '8.0.2', + fallback : ['qhull_r', 'qhull_r_dep'] + ) +else + qhull_r = subproject('qhull_r', version : '8.0.2') + qhull_r_dep = qhull_r.get_variable('qhull_r_dep') +endif subdir('scipy') diff --git a/meson.options b/meson.options index 3257cb8a8ff5..65aac3557612 100644 --- a/meson.options +++ b/meson.options @@ -10,3 +10,7 @@ option('use-pythran', type: 'boolean', value: true, description: 'If set to false, disables using Pythran (it falls back ' + 'to either pure Python code or Cython code, depending on ' + 'the implementation).') +option('use-system-libraries', type: 'array', + choices : ['none', 'all', 'auto', 'boost.math', 'qhull'], value : ['none'], + description: 'Choose which system libraries for subprojects ' + + 'if they are available.') diff --git a/scipy/spatial/_qhull.pyx b/scipy/spatial/_qhull.pyx index 61a293f5239e..0622121529c4 100644 --- a/scipy/spatial/_qhull.pyx +++ b/scipy/spatial/_qhull.pyx @@ -54,11 +54,11 @@ cdef extern from "setjmp.h" nogil: void longjmp(jmp_buf STATE, int VALUE) nogil # Define the clockwise constant -cdef extern from "qhull_src/src/user_r.h": +cdef extern from "": cdef enum: qh_ORIENTclock -cdef extern from "qhull_src/src/qset_r.h": +cdef extern from "": ctypedef union setelemT: void *p int i @@ -70,7 +70,7 @@ cdef extern from "qhull_src/src/qset_r.h": int qh_setsize(qhT *, setT *set) nogil void qh_setappend(qhT *, setT **setp, void *elem) nogil -cdef extern from "qhull_src/src/libqhull_r.h": +cdef extern from "": ctypedef double realT ctypedef double coordT ctypedef double pointT @@ -182,7 +182,7 @@ cdef extern from "qhull_misc.h": boolT ismalloc, char* qhull_cmd, void *outfile, void *errfile, coordT* feaspoint) nogil -cdef extern from "qhull_src/src/io_r.h": +cdef extern from "": ctypedef enum qh_RIDGE: qh_RIDGEall qh_RIDGEinner @@ -197,14 +197,14 @@ cdef extern from "qhull_src/src/io_r.h": void qh_order_vertexneighbors(qhT *, vertexT *vertex) nogil int qh_compare_facetvisit(const void *p1, const void *p2) nogil -cdef extern from "qhull_src/src/geom_r.h": +cdef extern from "": pointT *qh_facetcenter(qhT *, setT *vertices) nogil double qh_getarea(qhT *, facetT *facetlist) nogil -cdef extern from "qhull_src/src/poly_r.h": +cdef extern from "": void qh_check_maxout(qhT *) nogil -cdef extern from "qhull_src/src/mem_r.h": +cdef extern from "": void qh_memfree(qhT *, void *object, int insize) from libc.stdlib cimport qsort diff --git a/scipy/spatial/meson.build b/scipy/spatial/meson.build index 1f46d1c7c53d..2718ddc59c4f 100644 --- a/scipy/spatial/meson.build +++ b/scipy/spatial/meson.build @@ -10,37 +10,16 @@ spt_cython_gen = generator(cython, output : '@BASENAME@.c', depends : [_cython_tree, _spatial_pxd, _lib_pxd, cython_lapack_pxd]) -qhull_src = [ - 'qhull_src/src/geom2_r.c', - 'qhull_src/src/geom_r.c', - 'qhull_src/src/global_r.c', - 'qhull_src/src/io_r.c', - 'qhull_src/src/libqhull_r.c', - 'qhull_src/src/mem_r.c', - 'qhull_src/src/merge_r.c', - 'qhull_src/src/poly2_r.c', - 'qhull_src/src/poly_r.c', - 'qhull_src/src/qset_r.c', - 'qhull_src/src/random_r.c', - 'qhull_src/src/rboxlib_r.c', - 'qhull_src/src/stat_r.c', - 'qhull_src/src/user_r.c', - 'qhull_src/src/usermem_r.c', - 'qhull_src/src/userprintf_r.c', - 'qhull_src/src/userprintf_rbox_r.c' -] - py3.extension_module('_qhull', [spt_cython_gen.process('_qhull.pyx'), - 'qhull_misc.h', 'qhull_misc.c'] + qhull_src, + 'qhull_misc.h', 'qhull_misc.c'], c_args: cython_c_args, include_directories: [ '../_lib', '../_build_utils/src', - 'qhull_src/src' ], link_args: version_link_args, - dependencies: [np_dep], + dependencies: [np_dep, qhull_r_dep], install: true, subdir: 'scipy/spatial' ) @@ -105,12 +84,6 @@ py3.extension_module('_hausdorff', subdir: 'scipy/spatial' ) -py3.install_sources([ - 'qhull_src/COPYING.txt' - ], - subdir: 'scipy/spatial/qhull_src' -) - py3.install_sources([ '_qhull.pyi', '_voronoi.pyi', diff --git a/scipy/spatial/qhull_misc.c b/scipy/spatial/qhull_misc.c index 9b8eafc7cfee..94696eb41eae 100644 --- a/scipy/spatial/qhull_misc.c +++ b/scipy/spatial/qhull_misc.c @@ -1,10 +1,9 @@ -#include "qhull_src/src/qhull_ra.h" +#include - -/* This is a patched version of qhull_src/src/user_r.c:qh_new_qhull, +/* This is a patched version of subprojects/qhull_r/libqhull_r/user_r.c:qh_new_qhull, with an additional "feaspoint" argument. - See qhull_src/README.scipy + See scipy/spatial/qhull_misc_README.txt */ int qh_new_qhull_scipy(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc, char *qhull_cmd, FILE *outfile, FILE *errfile, coordT* feaspoint) { diff --git a/scipy/spatial/qhull_misc.h b/scipy/spatial/qhull_misc.h index 041b2a22c1d7..4a0a4a76d91e 100644 --- a/scipy/spatial/qhull_misc.h +++ b/scipy/spatial/qhull_misc.h @@ -9,7 +9,7 @@ #define qhull_misc_lib_check() QHULL_LIB_CHECK -#include "qhull_src/src/libqhull_r.h" +#include int qh_new_qhull_scipy(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc, char *qhull_cmd, FILE *outfile, FILE *errfile, coordT* feaspoint); diff --git a/scipy/spatial/qhull_src/README.scipy b/scipy/spatial/qhull_misc_README.txt similarity index 81% rename from scipy/spatial/qhull_src/README.scipy rename to scipy/spatial/qhull_misc_README.txt index c2d270a3c2b4..aeb56ff39b18 100644 --- a/scipy/spatial/qhull_src/README.scipy +++ b/scipy/spatial/qhull_misc_README.txt @@ -1,12 +1,9 @@ -The directory ./qhull_src/src ships unmodified Qhull 2019.1 "_r" -version source code. - -The file scipy/spatial/qhull_misc.c additionally contains a function +The file scipy/spatial/qhull_misc.c contains a function "qh_new_qhull_scipy" derived from Qhull sources, via the following patch: ---- a/scipy/spatial/qhull_src/src/user_r.c -+++ b/scipy/spatial/qhull_src/src/user_r.c +--- a/subprojects/qhull_r/libqhull_r/user_r.c ++++ b/subprojects/qhull_r/libqhull_r/user_r.c @@ -122,7 +122,7 @@ An example of using qh_new_qhull is user_eg_r.c */ @@ -16,8 +13,7 @@ patch: /* gcc may issue a "might be clobbered" warning for dim, points, and ismalloc [-Wclobbered]. These parameters are not referenced after a longjmp() and hence not clobbered. See http://stackoverflow.com/questions/7721854/what-sense-do-these-clobbered-variable-warnings-make */ -@@ -158,7 +158,26 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc - /* points is an array of halfspaces, +@@ -159,6 +159,26 @@ int qh_new_qhull(qhT *qh, int dim, int numpoints, coordT *points, boolT ismalloc the last coordinate of each halfspace is its offset */ hulldim= dim-1; - qh_setfeasible(qh, hulldim); diff --git a/scipy/spatial/qhull_src/README.txt b/scipy/spatial/qhull_src/README.txt deleted file mode 100644 index 37cad1d99e80..000000000000 --- a/scipy/spatial/qhull_src/README.txt +++ /dev/null @@ -1,720 +0,0 @@ -Name - - qhull, rbox 2020.2 2020/08/31 (8.0.2) - -Convex hull, Delaunay triangulation, Voronoi diagrams, Halfspace intersection - - Documentation: - html/index.htm - - - Available from: - - - (git@github.com:qhull/qhull.git) - - News and a paper: - - - - Version 1 (simplicial only): - - -Purpose - - Qhull is a general dimension convex hull program that reads a set - of points from stdin, and outputs the smallest convex set that contains - the points to stdout. It also generates Delaunay triangulations, Voronoi - diagrams, furthest-site Voronoi diagrams, and halfspace intersections - about a point. - - Rbox is a useful tool in generating input for Qhull; it generates - hypercubes, diamonds, cones, circles, simplices, spirals, - lattices, and random points. - - Qhull produces graphical output for Geomview. This helps with - understanding the output. - -Environment requirements - - Qhull and rbox should run on all 32-bit and 64-bit computers. Use - an ANSI C or C++ compiler to compile the program. The software is - self-contained. It comes with examples and test scripts. - - Qhull's C++ interface uses the STL. The C++ test program uses QTestLib - from the Qt Framework. - - Qhull is copyrighted software. Please read COPYING.txt and REGISTER.txt - before using or distributing Qhull. - -To cite Qhull, please use - - Barber, C.B., Dobkin, D.P., and Huhdanpaa, H.T., "The Quickhull - algorithm for convex hulls," ACM Trans. on Mathematical Software, - 22(4):469-483, Dec 1996, http://www.qhull.org. - -To modify Qhull, particularly the C++ interface - - Qhull is on GitHub - (http://github.com/qhull/qhull/wiki, git@github.com:qhull/qhull.git) - - For internal documentation, see html/qh-code.htm - -To install Qhull - - Qhull is precompiled for Windows 32-bit, otherwise it needs compilation. - - Qhull includes Makefiles for gcc and other targets, CMakeLists.txt for CMake, - .sln/.vcproj/.vcxproj files for Microsoft Visual Studio, and .pro files - for Qt Creator. It compiles under Windows with mingw. - () - - Install and build instructions follow. - - See the end of this document for a list of distributed files. - ------------------- -Index - -Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT -Installing Qhull on Unix with gcc -Installing Qhull with CMake 2.6 or later -Installing Qhull with Qt -Working with Qhull's C++ interface -Calling Qhull from C programs -Compiling Qhull with Microsoft Visual C++ -Compiling Qhull with Qt Creator -Compiling Qhull with mingw/gcc on Windows -Compiling Qhull with cygwin on Windows -Compiling from Makfile without gcc -Compiling on other machines and compilers -Distributed files -Authors - ------------------- -Installing Qhull on Windows 10, 8, 7 (32- or 64-bit), Windows XP, and Windows NT - - The zip file contains rbox.exe, qhull.exe, qconvex.exe, qdelaunay.exe, - qhalf.exe, qvoronoi.exe, testqset.exe, user_eg*.exe, documentation files, - and source files. Qhull.exe and user-eg3.exe are compiled with the reentrant - library while the other executables use the non-reentrant library. - - To install Qhull: - - Unzip the files into a directory (e.g., named 'qhull') - - Click on QHULL-GO or open a command window into Qhull's bin directory. - - Test with 'rbox D4 | qhull' - - To uninstall Qhull - - Delete the qhull directory - - To learn about Qhull: - - Execute 'qconvex' for a synopsis and examples. - Or 'qconvex --help' or 'qconvex -?' - - Execute 'rbox 10 | qconvex' to compute the convex hull of 10 random points. - - Execute 'rbox 10 | qconvex i TO file' to write results to 'file'. - - Browse the documentation: qhull\html\index.htm - - If an error occurs, Windows sends the error to stdout instead of stderr. - Use 'TO xxx' to send normal output to xxx - - To improve the command window - - Double-click the window bar to increase the size of the window - - Right-click the window bar - - Select Properties - - Check QuickEdit Mode - Select text with right-click or Enter - Paste text with right-click - - Change Font to Lucinda Console - - Change Layout to Screen Buffer Height 999, Window Size Height 55 - - Change Colors to Screen Background White, Screen Text Black - - Click OK - - Select 'Modify shortcut that started this window', then OK - - If you regularly use qhull on a Windows host, install a bash shell such as - https://gitforwindows.org/ # based on MSYS2 - https://github.com/git-for-windows/git/wiki - http://www.msys2.org/ - https://github.com/msys2/msys2/wiki - [mar'19] Git for Windows v2.21 requires 'qhull --help' - Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' fails - www.cygwin.com - www.mingw.org/wiki/msys # for Windows XP - Road Bash (www.qhull.org/bash) # based on MSYS - ------------------- -Installing Qhull on Unix with gcc - - To build Qhull, static libraries, shared library, and C++ interface - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - make - - export LD_LIBRARY_PATH=$PWD/lib:$LD_LIBRARY_PATH - - make test - - 'make install' installs Qhull at '/usr/local/'. It installs pkg-config files - at '/usr/local/lib/pkgconfig'. Change the install directory with DESTDIR and PREFIX. - - To build 32-bit Qhull on a 64-bit host (uses 33% less memory in 4-d) - - make new M32=-m32 - - To build 32-bit Qhull without -fpic (may be faster, but shared library may fail) - - make new M32=-m32 FPIC= - - The Makefiles may be edited for other compilers. - If 'testqset' exits with an error, qhull is broken - - A simple Makefile for Qhull is in src/libqhull and src/libqhull_r. - To build the Qhull executables and libqhullstatic - - Extract Qhull from qhull...tgz or qhull...zip - - cd src/libqhull_r # cd src/libqhull - - make - - ------------------- -Installing Qhull with CMake 2.6 or later - - See CMakeLists.txt for examples and further build instructions - - To build Qhull, static libraries, shared library, and C++ interface - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - cd build - - cmake --help # List build generators - - cmake -G "" .. # e.g., for MINGW-w64 -- cmake -G "MSYS Makefiles" .. - - cmake .. - - make - - ctest - - make install # If MSYS or UNIX, default CMAKE_INSTALL_PREFIX is '/usr/local' - # otherwise if WINDOWS, installs to ../bin, ../include, and ../lib - - make uninstall # Delete the files in install_manifest.txt - - The ".." is important. It refers to the parent directory (i.e., qhull/) - - CMake installs lib/pkgconfig/qhull*.pc for use with pkg-config - - If CMAKE_INSTALL_PREFIX is C:/Program Files/qhull, you may need to give 'Users' "full control" - to qhull's sub-directories: bin, doc, include, lib, and man (folder > Properties > Security > Edit > Users). - - On Windows, CMake's 64-bit generators have a "Win64" tag. Qhull's data structures - are substantial larger as 64-bit code than as 32-bit code. This may slow down Qhull. - - If cmake fails with "No CMAKE_C_COMPILER could be found" - - cmake was not able to find the build environment specified by -G "..." - - If cmake's gcc smoketest fails after a Windows update - - Reinstall MINGW-w64 and delete CMakeCache.txt. A Windows update can break gcc process creation for cc1. - ------------------- -Installing Qhull with Qt - - To build Qhull, including its C++ test program (qhulltest) - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load src/qhull-all.pro into QtCreator - - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' - If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' - - Build - - - Build qhulltest with a C++11 or later compiler - - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied - into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin - - If qhulltest fails with exit status 127 and no error message, - check for missing Q5Core.dll and Qt5Test.dll - ------------------- -Working with Qhull's C++ interface - - See html/qh-code.htm#cpp for calling Qhull from C++ programs - - Class and method documentation is limited - - See html/qh-code.htm#reentrant for converting from Qhull-2012 - - Examples of using the C++ interface - user_eg3_r.cpp - qhulltest/*_test.cpp - - Qhull's C++ interface is likely to change. Stay current with GitHub. - - To clone Qhull's next branch from http://github.com/qhull/qhull/wiki - git init - git clone git@github.com:qhull/qhull.git - cd qhull - git checkout next - ... - git pull origin next - - Compile qhullcpp and libqhullstatic_r with the same compiler. Both libraries - use the C routines setjmp() and longjmp() for error handling. They must - be compiled with the same compiler. - - Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc - With back-ticks, you can compile your C++ program with the Qhull libraries: - g++ `pkg-config --cflags --libs qhullcpp qhullstatic_r` -o my_app my_app.cpp - or - g++ `pkg-config --cflags --libs qhullcpp qhull_r` -o my_app my_app.cpp - - qhullcpp must be linked before qhull_r, otherwise the linker reports - an error -- "QhullUser ... multiple definition of `qh_fprintf'" - ------------------- -Calling Qhull from C programs - - See html/qh-code.htm#library for calling Qhull from C programs - - Qhull provides pkg-config support with build/qhull.pc.in and lib/pkgconfig/qhull*.pc - With back-ticks, you can compile your C program with the Qhull library - gcc `pkg-config --cflags --libs qhull_r` -o my_app my_app.c - - See html/qh-code.htm#reentrant for converting from Qhull-2012 - - Warning: You will need to understand Qhull's data structures and read the - code. Most users will find it easier to call Qhull as an external command. - - The reentrant 'C' code (src/libqhull_r), passes a pointer to qhT - to most Qhull routines. This allows multiple instances of Qhull to run - at the same time. It simplifies the C++ interface. - - The non-reentrant 'C' code (src/libqhull) looks unusual. It refers to - Qhull's global data structure, qhT, through a 'qh' macro (e.g., 'qh ferr'). - This allows the same code to use static memory or heap memory. - If qh_QHpointer is defined, qh_qh is a pointer to an allocated qhT; - otherwise qh_qh is a global static data structure of type qhT. - ------------------- -Compiling Qhull with Microsoft Visual C++ - - To compile 32-bit Qhull with Microsoft Visual C++ 2010 and later - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull-32.sln - - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset - File > Save All - - Build target 'Win32' - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - - Copy Qt shared libraries, QtCore.dll and QtTest.dll, into the bin directory - - To compile 64-bit Qhull with Microsoft Visual C++ 2010 and later - - 64-bit Qhull has larger data structures due to 64-bit pointers. This may slow down Qhull. - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull-64.sln - - Right-click 'Retarget solution' from toolset v110 to your Platform Toolset - File > Save All - - Build target 'x64' - - If build as 32-bit fails, use solution build/qhull-32.sln - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/5.2.0/5.2.0/msvc2012_64) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - - If error -- MSB8020: The build tools for Visual Studio 2012 (Platform Toolset = 'v110') cannot be found. - - 'Project > Retarget solution' for both qhull-32.sln and qhull-64.sln - - 'File > Open' your preferred solution (qhull-32.sln or qhull-64.sln) - - 'Save All' both projects - - DevStudio may need a restart - - To compile Qhull with Microsoft Visual C++ 2005 (vcproj files) - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Load solution build/qhull.sln - - Build target 'win32' (not 'x64') - - Project qhulltest requires Qt for DevStudio (http://www.qt.io) - Set the QTDIR environment variable to your Qt directory (e.g., c:/qt/4.7.4) - If QTDIR is incorrect, precompile will fail with 'Can not locate the file specified' - ------------------- -Compiling Qhull with Qt Creator - - Qt (http://www.qt.io) is a C++ framework for Windows, Linux, and Macintosh - - Qhull uses QTestLib to test qhull's C++ interface (see src/qhulltest/) - - To compile Qhull with Qt Creator - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Download the Qt SDK - - Start Qt Creator - - Load src/qhull-all.pro - - Configure the project to use a Shadow build at the same level as 'src', 'bin', and 'lib' - If, instead, the shadow build is a subdirectory of 'build', Qt Creator will install Qhull in 'build/bin' and 'build/lib' - - Build - - - Build qhulltest with a C++11 or later compiler - - qhulltest depends on shared libraries QtCore.a and QtTest.a. They may need to be copied - into the bin directory. On Windows, copy Qt5Core.dll and Qt5Test.dll, e.g., /qt/5.11.2/msvc2017_64/bin - - If qhulltest fails with exit status 127 and no error message, - check for missing Q5Core.dll and Qt5Test.dll - ------------------- -Compiling Qhull with mingw/gcc on Windows - - To compile Qhull with MINGW - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Install GitForWindows (https://gitforwindows.org/) - or MSYS2 (http://www.msys2.org/) - Install in C:\Git\... # Not 'Program Files\...' otherwise './configure && make' will not work - - Install MINGW-w64 with gcc (https://mingw-w64.org/) - 1) Goto sourceforge -- https://sourceforge.net/projects/mingw-w64/files/ - 2) in folder -- mingw-w64 - 3) download installer -- MinGW-W64-install.exe - Run the installer - 1) Select i686/posix/dwarf - 2) Install in 'C:\mingw-w64' # Not 'Program Files\...' - Rename /c/mingw-w64/mingw32/bin/mingw32-make.exe to make.exe - Add the 'C:\mingw-w64\mingw32\bin' directory to your $PATH environment variable - Execute 'which make' to check that 'make' is mingw-w64's make - - Compile Qhull from the home directory - make help - make - - Notes - - Mingw is included with Qt SDK in qt/Tools/mingw53_32 - - If you use Windows XP - Install Road Bash (http://www.qhull.org/bash) or MSYS (http://www.mingw.org/wiki/msys) - Install MINGW (http://mingw.org/) - ------------------- -Compiling Qhull with cygwin on Windows - - To compile Qhull with cygwin - - Download and extract Qhull (either GitHub, .tgz file, or .zip file) - - Install cygwin (http://www.cygwin.com) - - Include packages for gcc, make, ar, and ln - - make - ------------------- -Compiling from Makfile without gcc - - The file, qhull-src.tgz, contains documentation and source files for - qhull and rbox. - - To unpack the tgz file - - tar zxf qhull-src.tgz - - cd qhull - - Use qhull/Makefile - Simpler Makefiles are qhull/src/libqhull/Makefile and qhull/src/libqhull_r/Makefile - - Compiling qhull and rbox with Makefile - - in Makefile, check the CC, CCOPTS1, PRINTMAN, and PRINTC defines - - the defaults are gcc and enscript - - CCOPTS1 should include the ANSI flag. It defines __STDC__ - - in user.h, check the definitions of qh_SECticks and qh_CPUclock. - - use '#define qh_CLOCKtype 2' for timing runs longer than 1 hour - - type: make - - this builds: qhull qconvex qdelaunay qhalf qvoronoi rbox libqhull.a libqhull_r.a - - type: make doc - - this prints the man page - - See also qhull/html/index.htm - - if your compiler reports many errors, it is probably not a ANSI C compiler - - you will need to set the -ansi switch or find another compiler - - if your compiler warns about missing prototypes for fprintf() etc. - - this is ok, your compiler should have these in stdio.h - - if your compiler warns about missing prototypes for memset() etc. - - include memory.h in qhull_a.h - - if your compiler reports "global.c: storage size of 'qh_qh' isn't known" - - delete the initializer "={0}" in global.c, stat.c and mem.c - - if your compiler warns about "stat.c: improper initializer" - - this is ok, the initializer is not used - - if you have trouble building libqhull.a with 'ar' - - try 'make -f Makefile.txt qhullx' - - if the code compiles, the qhull test case will automatically execute - - if an error occurs, there's an incompatibility between machines - - If you can, try a different compiler - - You can turn off the Qhull memory manager with qh_NOmem in mem.h - - You can turn off compiler optimization (-O2 in Makefile) - - If you find the source of the problem, please let us know - - to install the programs and their man pages: - - define MANDIR and BINDIR - - type 'make install' - - - if you have Geomview (www.geomview.org) - - try 'rbox 100 | qconvex G >a' and load 'a' into Geomview - - run 'q_eg' for Geomview examples of Qhull output (see qh-eg.htm) - ------------------- -Compiling on other machines and compilers - - Qhull may compile with Borland C++ 5.0 bcc32. A Makefile is included. - Execute 'cd src/libqhull; make -f Mborland'. If you use the Borland IDE, set - the ANSI option in Options:Project:Compiler:Source:Language-compliance. - - Qhull may compile with Borland C++ 4.02 for Win32 and DOS Power Pack. - Use 'cd src/libqhull; make -f Mborland -D_DPMI'. Qhull 1.0 compiles with - Borland C++ 4.02. For rbox 1.0, use "bcc32 -WX -w- -O2-e -erbox -lc rbox.c". - Use the same options for Qhull 1.0. [D. Zwick] - - If you have troubles with the memory manager, you can turn it off by - defining qh_NOmem in mem.h. - ------------------- -Distributed files - - README.txt // Instructions for installing Qhull - REGISTER.txt // Qhull registration - COPYING.txt // Copyright notice - QHULL-GO.lnk // Windows icon for eg/qhull-go.bat - Announce.txt // Announcement - CMakeLists.txt // CMake build file (2.6 or later) - File_id.diz // Package descriptor - index.htm // Home page - Makefile // Makefile for gcc and other compilers - qhull*.md5sum // md5sum for all files - - bin/* // Qhull executables and dll (.zip only) - build/CMakeModules/CheckLFS.cmake // enables Large File Support in CMake - build/config.cmake.in // extract target variables - build/qhull.pc.in // pkg-config template for creating lib/pkgconfig/qhull*.pc - build/qhull-32.sln // 32-bit DevStudio solution and project files (2010 and later) - build/*-32.vcxproj - build/qhull-64.sln // 64-bit DevStudio solution and project files (2010 and later) - build/*-64.vcxproj - build/qhull.sln // DevStudio solution and project files (2005 and 2009) - build/*.vcproj - build/qhulltest/ // DevStudio project files for qhulltest (C++ and Qt) - build/README-build.txt // Contents of build/ - eg/* // Test scripts and geomview files from q_eg - html/index.htm // Manual - html/qh-faq.htm // Frequently asked questions - html/qh-get.htm // Download page - html/qhull-cpp.xml // C++ style notes as a Road FAQ (www.qhull.org/road) - src/Changes.txt // Change history for Qhull and rbox - src/qhull-all.pro // Qt project - -eg/ - q_benchmark // shell script for precision and performance benchmark - q_benchmark-ok.txt // reviewed output from q_benchmark - q_eg // shell script for Geomview examples (eg.01.cube) - q_egtest // shell script for Geomview test examples - q_test // shell script to test qhull - q_test.bat // Windows batch test for QHULL-GO.bat - // cd bin; ..\eg\q_test.bat >q_test.x 2>&1 - q_test-ok.txt // reviewed output from q_test - qhulltest-ok.txt // reviewed output from qhulltest (Qt only) - make-qhull_qh.sh // shell script to create non-reentrant qhull_qh from reentrant Qhull - make-vcproj.sh // shell script to create vcproj and vcxprog files - qhull-zip.sh // shell script to create distribution files - qtest.sh // shell script for testing and logging qhull - -rbox consists of (bin, html): - rbox.exe // Win32 executable (.zip only) - rbox.htm // html manual - rbox.man // Unix man page - rbox.txt - -qhull consists of (bin, html): - qconvex.exe // Win32 executables and dlls (.zip download only) - qhull.exe // Built with the reentrant library (about 2% slower) - qdelaunay.exe - qhalf.exe - qvoronoi.exe - qhull_r.dll - qhull-go.bat // command window - qconvex.htm // html manual - qdelaun.htm - qdelau_f.htm - qhalf.htm - qvoronoi.htm - qvoron_f.htm - qh-eg.htm - qh-code.htm - qh-impre.htm - index.htm - qh-opt*.htm - qh-quick.htm - qh--*.gif // images for manual - normal_voronoi_knauss_oesterle.jpg - qh_findbestfacet-drielsma.pdf - qhull.man // Unix man page - qhull.txt - -bin/ - msvcr80.dll // Visual C++ redistributable file (.zip download only) - -src/ - qhull/unix.c // Qhull and rbox applications using non-reentrant libqhullstatic.a - rbox/rbox.c - qconvex/qconvex.c - qhalf/qhalf.c - qdelaunay/qdelaunay.c - qvoronoi/qvoronoi.c - - qhull/unix_r.c // Qhull and rbox applications using reentrant libqhullstatic_r.a - rbox/rbox_r.c - qconvex/qconvex_r.c // Qhull applications built with reentrant libqhull_r/Makefile - qhalf/qhalf_r.c - qdelaunay/qdelaun_r.c - qvoronoi/qvoronoi_r.c - - user_eg/user_eg_r.c // example of using qhull_r.dll from a user program - user_eg2/user_eg2_r.c // example of using libqhullstatic_r.a from a user program - user_eg3/user_eg3_r.cpp // example of Qhull's C++ interface libqhullcpp with libqhullstatic_r.a - qhulltest/qhulltest.cpp // Test of Qhull's C++ interface using Qt's QTestLib - qhull-*.pri // Include files for Qt projects - testqset_r/testqset_r.c // Test of reentrant qset_r.c and mem_r.c - testqset/testqset.c // Test of non-rentrant qset.c and mem.c - -src/libqhull - libqhull.pro // Qt project for non-rentrant, shared library (qhull.dll) - index.htm // design documentation for libqhull - qh-*.htm - qhull-exports.def // Export Definition files for Visual C++ - qhull-nomerge-exports.def - qhull_p-exports.def - qhull_p-nomerge-exports.def - Makefile // Simple gcc Makefile for qhull and libqhullstatic.a - Mborland // Makefile for Borland C++ 5.0 - - libqhull.h // header file for qhull - user.h // header file of user definable constants - libqhull.c // Quickhull algorithm with partitioning - user.c // user re-definable functions - usermem.c - userprintf.c - userprintf_rbox.c - - qhull_a.h // include files for libqhull/*.c - geom.c // geometric routines - geom2.c - geom.h - global.c // global variables - io.c // input-output routines - io.h - mem.c // memory routines, this is stand-alone code - mem.h - merge.c // merging of non-convex facets - merge.h - poly.c // polyhedron routines - poly2.c - poly.h - qset.c // set routines, this only depends on mem.c - qset.h - random.c // utilities w/ Park & Miller's random number generator - random.h - rboxlib.c // point set generator for rbox - stat.c // statistics - stat.h - -src/libqhull_r - libqhull_r.pro // Qt project for rentrant, shared library (qhull_r.dll) - index.htm // design documentation for libqhull_r - qh-*_r.htm - qhull_r-exports.def // Export Definition files for Visual C++ - qhull_r-nomerge-exports.def - Makefile // Simple gcc Makefile for qhull and libqhullstatic.a - - libqhull_r.h // header file for qhull - user_r.h // header file of user definable constants - libqhull_r.c // Quickhull algorithm wi_r.hpartitioning - user_r.c // user re-definable functions - usermem.c - userprintf.c - userprintf_rbox.c - qhull_ra.h // include files for libqhull/*_r.c - geom_r.c // geometric routines - geom2.c - geom_r.h - global_r.c // global variables - io_r.c // input-output routines - io_r.h - mem_r.c // memory routines, this is stand-alone code - mem.h - merge_r.c // merging of non-convex facets - merge.h - poly_r.c // polyhedron routines - poly2.c - poly_r.h - qset_r.c // set routines, this only depends on mem_r.c - qset.h - random_r.c // utilities w/ Park & Miller's random number generator - random.h - rboxlib_r.c // point set generator for rbox - stat_r.c // statistics - stat.h - -src/libqhullcpp/ - libqhullcpp.pro // Qt project for renentrant, static C++ library - Qhull.cpp // Calls libqhull_r.c from C++ - Qhull.h - qt-qhull.cpp // Supporting methods for Qt - - Coordinates.cpp // input classes - Coordinates.h - - PointCoordinates.cpp - PointCoordinates.h - RboxPoints.cpp // call rboxlib.c from C++ - RboxPoints.h - - QhullFacet.cpp // data structure classes - QhullFacet.h - QhullHyperplane.cpp - QhullHyperplane.h - QhullPoint.cpp - QhullPoint.h - QhullQh.cpp - QhullRidge.cpp - QhullRidge.h - QhullVertex.cpp - QhullVertex.h - - QhullFacetList.cpp // collection classes - QhullFacetList.h - QhullFacetSet.cpp - QhullFacetSet.h - QhullIterator.h - QhullLinkedList.h - QhullPoints.cpp - QhullPoints.h - QhullPointSet.cpp - QhullPointSet.h - QhullSet.cpp - QhullSet.h - QhullSets.h - QhullVertexSet.cpp - QhullVertexSet.h - - functionObjects.h // supporting classes - QhullError.cpp - QhullError.h - QhullQh.cpp - QhullQh.h - QhullStat.cpp - QhullStat.h - QhullUser.cpp - QhullUser.h - RoadError.cpp // Supporting base classes - RoadError.h - RoadLogEvent.cpp - RoadLogEvent.h - usermem_r-cpp.cpp // Optional override for qh_exit() to throw an error - -src/libqhullstatic/ - libqhullstatic.pro // Qt project for non-reentrant, static library - -src/libqhullstatic_r/ - libqhullstatic_r.pro // Qt project for reentrant, static library - -src/qhulltest/ - qhulltest.pro // Qt project for test of C++ interface - Coordinates_test.cpp // Test of each class - PointCoordinates_test.cpp - Qhull_test.cpp - QhullFacet_test.cpp - QhullFacetList_test.cpp - QhullFacetSet_test.cpp - QhullHyperplane_test.cpp - QhullLinkedList_test.cpp - QhullPoint_test.cpp - QhullPoints_test.cpp - QhullPointSet_test.cpp - QhullRidge_test.cpp - QhullSet_test.cpp - QhullVertex_test.cpp - QhullVertexSet_test.cpp - RboxPoints_test.cpp - RoadTest.cpp // Run multiple test files with QTestLib - RoadTest.h - ------------------- -Authors - - C. Bradford Barber Hannu Huhdanpaa (Version 1.0) - bradb@shore.net hannu@qhull.org - - Qhull 1.0 and 2.0 were developed under NSF grants NSF/DMS-8920161 - and NSF-CCR-91-15793 750-7504 at the Geometry Center and Harvard - University. If you find Qhull useful, please let us know. \ No newline at end of file diff --git a/scipy/spatial/qhull_src/Announce.txt b/subprojects/qhull_r/libqhull_r/Announce.txt similarity index 100% rename from scipy/spatial/qhull_src/Announce.txt rename to subprojects/qhull_r/libqhull_r/Announce.txt diff --git a/scipy/spatial/qhull_src/COPYING.txt b/subprojects/qhull_r/libqhull_r/COPYING.txt similarity index 99% rename from scipy/spatial/qhull_src/COPYING.txt rename to subprojects/qhull_r/libqhull_r/COPYING.txt index 14e122d71f77..122a00a4fa99 100644 --- a/scipy/spatial/qhull_src/COPYING.txt +++ b/subprojects/qhull_r/libqhull_r/COPYING.txt @@ -36,4 +36,4 @@ modified, and redistributed under the following conditions: 5. There is no warranty or other guarantee of fitness for Qhull, it is provided solely "as is". Bug reports or fixes may be sent to qhull_bug@qhull.org; the authors may or may not act on them as - they desire. \ No newline at end of file + they desire. diff --git a/scipy/spatial/qhull_src/src/geom2_r.c b/subprojects/qhull_r/libqhull_r/geom2_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/geom2_r.c rename to subprojects/qhull_r/libqhull_r/geom2_r.c diff --git a/scipy/spatial/qhull_src/src/geom_r.c b/subprojects/qhull_r/libqhull_r/geom_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/geom_r.c rename to subprojects/qhull_r/libqhull_r/geom_r.c diff --git a/scipy/spatial/qhull_src/src/geom_r.h b/subprojects/qhull_r/libqhull_r/geom_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/geom_r.h rename to subprojects/qhull_r/libqhull_r/geom_r.h diff --git a/scipy/spatial/qhull_src/src/global_r.c b/subprojects/qhull_r/libqhull_r/global_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/global_r.c rename to subprojects/qhull_r/libqhull_r/global_r.c diff --git a/scipy/spatial/qhull_src/src/io_r.c b/subprojects/qhull_r/libqhull_r/io_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/io_r.c rename to subprojects/qhull_r/libqhull_r/io_r.c diff --git a/scipy/spatial/qhull_src/src/io_r.h b/subprojects/qhull_r/libqhull_r/io_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/io_r.h rename to subprojects/qhull_r/libqhull_r/io_r.h diff --git a/scipy/spatial/qhull_src/src/libqhull_r.c b/subprojects/qhull_r/libqhull_r/libqhull_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/libqhull_r.c rename to subprojects/qhull_r/libqhull_r/libqhull_r.c diff --git a/scipy/spatial/qhull_src/src/libqhull_r.h b/subprojects/qhull_r/libqhull_r/libqhull_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/libqhull_r.h rename to subprojects/qhull_r/libqhull_r/libqhull_r.h diff --git a/scipy/spatial/qhull_src/src/mem_r.c b/subprojects/qhull_r/libqhull_r/mem_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/mem_r.c rename to subprojects/qhull_r/libqhull_r/mem_r.c diff --git a/scipy/spatial/qhull_src/src/mem_r.h b/subprojects/qhull_r/libqhull_r/mem_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/mem_r.h rename to subprojects/qhull_r/libqhull_r/mem_r.h diff --git a/scipy/spatial/qhull_src/src/merge_r.c b/subprojects/qhull_r/libqhull_r/merge_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/merge_r.c rename to subprojects/qhull_r/libqhull_r/merge_r.c diff --git a/scipy/spatial/qhull_src/src/merge_r.h b/subprojects/qhull_r/libqhull_r/merge_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/merge_r.h rename to subprojects/qhull_r/libqhull_r/merge_r.h diff --git a/scipy/spatial/qhull_src/src/poly2_r.c b/subprojects/qhull_r/libqhull_r/poly2_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/poly2_r.c rename to subprojects/qhull_r/libqhull_r/poly2_r.c diff --git a/scipy/spatial/qhull_src/src/poly_r.c b/subprojects/qhull_r/libqhull_r/poly_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/poly_r.c rename to subprojects/qhull_r/libqhull_r/poly_r.c diff --git a/scipy/spatial/qhull_src/src/poly_r.h b/subprojects/qhull_r/libqhull_r/poly_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/poly_r.h rename to subprojects/qhull_r/libqhull_r/poly_r.h diff --git a/scipy/spatial/qhull_src/src/qhull_ra.h b/subprojects/qhull_r/libqhull_r/qhull_ra.h similarity index 100% rename from scipy/spatial/qhull_src/src/qhull_ra.h rename to subprojects/qhull_r/libqhull_r/qhull_ra.h diff --git a/scipy/spatial/qhull_src/src/qset_r.c b/subprojects/qhull_r/libqhull_r/qset_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/qset_r.c rename to subprojects/qhull_r/libqhull_r/qset_r.c diff --git a/scipy/spatial/qhull_src/src/qset_r.h b/subprojects/qhull_r/libqhull_r/qset_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/qset_r.h rename to subprojects/qhull_r/libqhull_r/qset_r.h diff --git a/scipy/spatial/qhull_src/src/random_r.c b/subprojects/qhull_r/libqhull_r/random_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/random_r.c rename to subprojects/qhull_r/libqhull_r/random_r.c diff --git a/scipy/spatial/qhull_src/src/random_r.h b/subprojects/qhull_r/libqhull_r/random_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/random_r.h rename to subprojects/qhull_r/libqhull_r/random_r.h diff --git a/scipy/spatial/qhull_src/src/rboxlib_r.c b/subprojects/qhull_r/libqhull_r/rboxlib_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/rboxlib_r.c rename to subprojects/qhull_r/libqhull_r/rboxlib_r.c diff --git a/scipy/spatial/qhull_src/src/stat_r.c b/subprojects/qhull_r/libqhull_r/stat_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/stat_r.c rename to subprojects/qhull_r/libqhull_r/stat_r.c diff --git a/scipy/spatial/qhull_src/src/stat_r.h b/subprojects/qhull_r/libqhull_r/stat_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/stat_r.h rename to subprojects/qhull_r/libqhull_r/stat_r.h diff --git a/scipy/spatial/qhull_src/src/user_r.c b/subprojects/qhull_r/libqhull_r/user_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/user_r.c rename to subprojects/qhull_r/libqhull_r/user_r.c diff --git a/scipy/spatial/qhull_src/src/user_r.h b/subprojects/qhull_r/libqhull_r/user_r.h similarity index 100% rename from scipy/spatial/qhull_src/src/user_r.h rename to subprojects/qhull_r/libqhull_r/user_r.h diff --git a/scipy/spatial/qhull_src/src/usermem_r.c b/subprojects/qhull_r/libqhull_r/usermem_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/usermem_r.c rename to subprojects/qhull_r/libqhull_r/usermem_r.c diff --git a/scipy/spatial/qhull_src/src/userprintf_r.c b/subprojects/qhull_r/libqhull_r/userprintf_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/userprintf_r.c rename to subprojects/qhull_r/libqhull_r/userprintf_r.c diff --git a/scipy/spatial/qhull_src/src/userprintf_rbox_r.c b/subprojects/qhull_r/libqhull_r/userprintf_rbox_r.c similarity index 100% rename from scipy/spatial/qhull_src/src/userprintf_rbox_r.c rename to subprojects/qhull_r/libqhull_r/userprintf_rbox_r.c diff --git a/subprojects/qhull_r/meson.build b/subprojects/qhull_r/meson.build new file mode 100644 index 000000000000..7911c36f8f16 --- /dev/null +++ b/subprojects/qhull_r/meson.build @@ -0,0 +1,43 @@ +project('qhull_r', + 'c', + version : '8.0.2', + meson_version: '>= 1.5.0', +) + +cc = meson.get_compiler('c') + +libqhull_r_sources = [ + 'libqhull_r/geom2_r.c', + 'libqhull_r/geom_r.c', + 'libqhull_r/global_r.c', + 'libqhull_r/io_r.c', + 'libqhull_r/libqhull_r.c', + 'libqhull_r/mem_r.c', + 'libqhull_r/merge_r.c', + 'libqhull_r/poly2_r.c', + 'libqhull_r/poly_r.c', + 'libqhull_r/qset_r.c', + 'libqhull_r/random_r.c', + 'libqhull_r/rboxlib_r.c', + 'libqhull_r/stat_r.c', + 'libqhull_r/user_r.c', + 'libqhull_r/usermem_r.c', + 'libqhull_r/userprintf_r.c', + 'libqhull_r/userprintf_rbox_r.c' +] + +libqhull_r = static_library( + 'libqhull_r', + [libqhull_r_sources], + c_args: cc.get_supported_arguments('-Wno-unused-but-set-variable'), + include_directories: 'libqhull_r', + # Ensure that if we link a static library into a shared library, + # private symbols don't get re-exported. + gnu_symbol_visibility: 'inlineshidden', + install: false, +) + +qhull_r_dep = declare_dependency( + link_with: libqhull_r, + include_directories: '.', +) diff --git a/tools/vendor_qhull.sh b/tools/vendor_qhull.sh new file mode 100755 index 000000000000..93711a1e3e3a --- /dev/null +++ b/tools/vendor_qhull.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Vendors qhull from https://github.com/qhull/qhull + +set -o nounset +set -o errexit + +REPO_URL="https://github.com/qhull/qhull" +COMMIT_HASH="613debeaea72ee66626dace9ba1a2eff11b5d37d" + +# XXX: run this from the repo top level like `./tools/vendor_qhull.sh` +ROOT_DIR="subprojects/qhull_r/libqhull_r" + +rm -rf $ROOT_DIR +mkdir $ROOT_DIR +mkdir $ROOT_DIR/.tmp +git clone $REPO_URL $ROOT_DIR/.tmp +pushd $ROOT_DIR/.tmp +git checkout $COMMIT_HASH +pushd src/libqhull_r/ +rm *.htm +rm *.pro +rm *.def +rm Makefile +popd # $ROOT_DIR/.tmp +popd +mv -v $ROOT_DIR/.tmp/COPYING.txt $ROOT_DIR/ +mv -v $ROOT_DIR/.tmp/Announce.txt $ROOT_DIR/ +cp -v $ROOT_DIR/.tmp/src/libqhull_r/* $ROOT_DIR/ +rm -rf $ROOT_DIR/.tmp From 11c0c45be6439b5fa8d993c1847b1388973973b2 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Thu, 15 May 2025 18:21:54 +0200 Subject: [PATCH 200/251] ENH `signal.csd`: Incorporate review comments to docstr of `signal.csd`. [docs only] --- scipy/signal/_spectral_py.py | 46 ++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index 9e633ced38ff..fa7e0d3920b5 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -400,7 +400,7 @@ def periodogram(x, fs=1.0, window='boxcar', nfft=None, detrend='constant', Notes ----- - The ratio of the squared magnitude (``scaling='spectrum'``) divided the spectral + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral power density (``scaling='density'``) is the constant factor of ``sum(abs(window)**2)*fs / abs(sum(window))**2``. If `return_onesided` is ``True``, the values of the negative frequencies are added @@ -574,7 +574,7 @@ def welch(x, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, windows may require a larger overlap. If `noverlap` is 0, this method is equivalent to Bartlett's method [2]_. - The ratio of the squared magnitude (``scaling='spectrum'``) divided the spectral + The ratio of the squared magnitude (``scaling='spectrum'``) divided by the spectral power density (``scaling='density'``) is the constant factor of ``sum(abs(window)**2)*fs / abs(sum(window))**2``. If `return_onesided` is ``True``, the values of the negative frequencies are added @@ -765,6 +765,26 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, Consult the :ref:`tutorial_SpectralAnalysis` section of the :ref:`user_guide` for a discussion of the scalings of a spectral density and an (amplitude) spectrum. + Welch's method may be interpreted as taking the average over the time slices of a + (cross-) spectrogram. Internally, this function utilizes the `ShortTimeFFT` to + determine the required (cross-) spectrogram. An example below illustrates that it + is straightforward to calculate `Pxy` directly with the `ShortTimeFFT`. However, + there are some notable differences in the behavior of the `ShortTimeFFT`: + + * There is no direct `ShortTimeFFT` equivalent for the `csd()` parameter + combination ``return_onesided=True, scaling='density'``, since + ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd()` + returning the doubled squared magnitude in this case, which does not have a + sensible interpretation. + * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the + behavior of the utilized `~scipy.fft` module. Thus, those are the dtypes being + returned. The `csd` function casts the return values to `float32` / `complex64` + if the input is `float32` / `complex64` as well. + * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas + `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where + ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window + positioning is different (consult the code snippet above). + .. versionadded:: 0.16.0 References @@ -819,23 +839,9 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, >>> np.allclose(Pxy, Pxy1) # same result as with csd() True - Allthough this function uses the `ShortTimeFFT` internally, the results of using an - approach analog to the code snippet above and the `csd()` function may deviate due - to implementation details. Notable differences are: - - * There is no direct `ShortTimeFFT` equivalent for the `csd()` parameter - combination ``return_onesided=True, scaling='density'``, since - ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd()` - returning the doubled squared magnitude in this case, which does not have a - sensible interpretation. - * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the - behavior of the utilized `~scipy.fft` module. Thus, those are the dtypes being - returned. The `csd` function casts the return values to `float32` / `complex64` - if the input is `float32` / `complex64` as well. - * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas - `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where - ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window - positioning is different (consult the code snippet above). + As discussed in the Notes section, the results of using an approach analogous to + the code snippet above and the `csd()` function may deviate due to implementation + details. Note that the code snippet above can be easily adapted to determine other statistical properties than the mean value. @@ -2318,7 +2324,7 @@ def _fft_helper(x, win, detrend_func, nperseg, noverlap, nfft, sides): .. legacy:: function This function is solely used by the legacy `_spectral_helper` function, - which s located also in this file. + which is located also in this file. This is a helper function that does the main FFT calculation for `_spectral helper`. All input validation is performed there, and the From 9da93220ea674af84ddb6244f28e2823dfd30cd1 Mon Sep 17 00:00:00 2001 From: zitongzhoueric Date: Thu, 15 May 2025 15:37:58 -0700 Subject: [PATCH 201/251] guard empty arrays in signal.cspline1d_eval and signal.qspline1d_eval --- scipy/signal/_spline_filters.py | 6 ++++++ scipy/signal/tests/test_bsplines.py | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/scipy/signal/_spline_filters.py b/scipy/signal/_spline_filters.py index e3d080cc9cae..edb69f0d5a20 100644 --- a/scipy/signal/_spline_filters.py +++ b/scipy/signal/_spline_filters.py @@ -579,6 +579,9 @@ def cspline1d_eval(cj, newx, dx=1.0, x0=0): newx = (np.asarray(newx) - x0) / float(dx) cj = np.asarray(cj) + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + res = zeros_like(newx, dtype=cj.dtype) if res.size == 0: return xp.asarray(res) @@ -662,6 +665,9 @@ def qspline1d_eval(cj, newx, dx=1.0, x0=0): return xp.asarray(res) cj = np.asarray(cj) + if cj.size == 0: + raise ValueError("Spline coefficients 'cj' must not be empty.") + N = len(cj) cond1 = newx < 0 cond2 = newx > (N - 1) diff --git a/scipy/signal/tests/test_bsplines.py b/scipy/signal/tests/test_bsplines.py index 1c46eda24e2e..16d364bab373 100644 --- a/scipy/signal/tests/test_bsplines.py +++ b/scipy/signal/tests/test_bsplines.py @@ -182,6 +182,11 @@ def test_cspline1d_eval(self, xp): signal.cspline1d_eval(cj, xp.asarray(newx), dx=dx, x0=x[0]), newy ) + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.cspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + @skip_xp_backends(cpu_only=True) def test_qspline1d_eval(self, xp): xp_assert_close(signal.qspline1d_eval(xp.asarray([0., 0]), xp.asarray([0.])), @@ -211,6 +216,11 @@ def test_qspline1d_eval(self, xp): ) xp_assert_close(r, newy) + with pytest.raises(ValueError, + match="Spline coefficients 'cj' must not be empty."): + signal.qspline1d_eval(xp.asarray([], dtype=xp.float64), + xp.asarray([0.0], dtype=xp.float64)) + # i/o dtypes with scipy 1.9.1, likely fixed by backwards compat sepfir_dtype_map = {np.uint8: np.float32, int: np.float64, From b19af0c8ad2863f33a0466ca4d174a980d41656e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Fri, 16 May 2025 12:16:23 +0530 Subject: [PATCH 202/251] DEV: Use ENUM_CHECK_NAME for avoiding memory leaks (#22071) --- scipy/sparse/linalg/_dsolve/_superluobject.c | 50 ++++---------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/scipy/sparse/linalg/_dsolve/_superluobject.c b/scipy/sparse/linalg/_dsolve/_superluobject.c index 6d07473e7f19..9656ec18d204 100644 --- a/scipy/sparse/linalg/_dsolve/_superluobject.c +++ b/scipy/sparse/linalg/_dsolve/_superluobject.c @@ -915,20 +915,11 @@ static int trans_cvt(PyObject * input, trans_t * value) { ENUM_CHECK_INIT; ENUM_CHECK(NOTRANS); + ENUM_CHECK_NAME(NOTRANS, "N"); ENUM_CHECK(TRANS); + ENUM_CHECK_NAME(TRANS, "T"); ENUM_CHECK(CONJ); - if (my_strxcmp(s, "N") == 0) { - *value = NOTRANS; - return 1; - } - if (my_strxcmp(s, "T") == 0) { - *value = TRANS; - return 1; - } - if (my_strxcmp(s, "H") == 0) { - *value = CONJ; - return 1; - } + ENUM_CHECK_NAME(CONJ, "H"); ENUM_CHECK_FINISH("invalid value for 'Trans' parameter"); } @@ -967,34 +958,13 @@ static int milu_cvt(PyObject * input, milu_t * value) static int droprule_one_cvt(PyObject * input, int *value) { ENUM_CHECK_INIT; - if (my_strxcmp(s, "BASIC") == 0) { - *value = DROP_BASIC; - return 1; - } - if (my_strxcmp(s, "PROWS") == 0) { - *value = DROP_PROWS; - return 1; - } - if (my_strxcmp(s, "COLUMN") == 0) { - *value = DROP_COLUMN; - return 1; - } - if (my_strxcmp(s, "AREA") == 0) { - *value = DROP_AREA; - return 1; - } - if (my_strxcmp(s, "SECONDARY") == 0) { - *value = DROP_SECONDARY; - return 1; - } - if (my_strxcmp(s, "DYNAMIC") == 0) { - *value = DROP_DYNAMIC; - return 1; - } - if (my_strxcmp(s, "INTERP") == 0) { - *value = DROP_INTERP; - return 1; - } + ENUM_CHECK_NAME(DROP_BASIC, "BASIC"); + ENUM_CHECK_NAME(DROP_PROWS, "PROWS"); + ENUM_CHECK_NAME(DROP_COLUMN, "COLUMN"); + ENUM_CHECK_NAME(DROP_AREA, "AREA"); + ENUM_CHECK_NAME(DROP_SECONDARY, "SECONDARY"); + ENUM_CHECK_NAME(DROP_DYNAMIC, "DYNAMIC"); + ENUM_CHECK_NAME(DROP_INTERP, "INTERP"); ENUM_CHECK_FINISH("invalid value for 'ILU_DropRule' parameter"); } From 58fbfc8e5d0c02f62e5c9d7fb2dec903c451fa42 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Fri, 16 May 2025 09:31:33 +0200 Subject: [PATCH 203/251] DOC: signal.csd: Small fixes to docstr * Remove phrase "(consult the code snippet above)." * Use `csd` instead of `csd()` everywhere in docstr. * Follow up to PR #22460. [docs only] --- scipy/signal/_spectral_py.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scipy/signal/_spectral_py.py b/scipy/signal/_spectral_py.py index fa7e0d3920b5..aefb827aadc9 100644 --- a/scipy/signal/_spectral_py.py +++ b/scipy/signal/_spectral_py.py @@ -771,9 +771,9 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, is straightforward to calculate `Pxy` directly with the `ShortTimeFFT`. However, there are some notable differences in the behavior of the `ShortTimeFFT`: - * There is no direct `ShortTimeFFT` equivalent for the `csd()` parameter + * There is no direct `ShortTimeFFT` equivalent for the `csd` parameter combination ``return_onesided=True, scaling='density'``, since - ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd()` + ``fft_mode='onesided2X'`` requires ``'psd'`` scaling. The is due to `csd` returning the doubled squared magnitude in this case, which does not have a sensible interpretation. * `ShortTimeFFT` uses `float64` / `complex128` internally, which is due to the @@ -783,7 +783,7 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, * The `csd` function calculates ``np.conj(Sx[q,p]) * Sy[q,p]``, whereas `~ShortTimeFFT.spectrogram` calculates ``Sx[q,p] * np.conj(Sy[q,p])`` where ``Sx[q,p]``, ``Sy[q,p]`` are the STFTs of `x` and `y`. Also, the window - positioning is different (consult the code snippet above). + positioning is different. .. versionadded:: 0.16.0 @@ -840,7 +840,7 @@ def csd(x, y, fs=1.0, window='hann', nperseg=None, noverlap=None, nfft=None, True As discussed in the Notes section, the results of using an approach analogous to - the code snippet above and the `csd()` function may deviate due to implementation + the code snippet above and the `csd` function may deviate due to implementation details. Note that the code snippet above can be easily adapted to determine other From 4765c54c6cfff0ff6829bec51f95aed9ebf96756 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 16 May 2025 00:40:36 -0700 Subject: [PATCH 204/251] CI: pin marray version --- .github/workflows/array_api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index af4c488e838a..95239d1b640a 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -76,7 +76,7 @@ jobs: - name: Install MArray run: | - python -m pip install marray + python -m pip install marray==0.0.8 - name: Prepare compiler cache id: prep-ccache From 2fd11ab2ec08902e423415f09267286653adc8c9 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Fri, 16 May 2025 11:50:25 +0200 Subject: [PATCH 205/251] CI: temporarily disable free-threaded job with parallel-threads Reasons: 1. A segfault in `stats/_distribution_infrastructure.py` since ~1 week 2. Preferred not to have this not-so-stable job running on the 1.16.x branch Re-enable on `main` after 1.16.x has branched and (1) is investigated. [skip circle] --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 593830d5b12e..af4a57b9eac8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -456,7 +456,7 @@ jobs: needs: get_commit_message strategy: matrix: - parallel: ['0', '1'] + parallel: ['0'] # '1' temporarily disabled, `stats` segfault (see gh-22758) runs-on: ubuntu-latest if: > From 4bf6e4b32e4b0be404a88001b4f0dd6d7e9d9333 Mon Sep 17 00:00:00 2001 From: Ilhan Polat Date: Fri, 16 May 2025 14:06:37 +0200 Subject: [PATCH 206/251] DOC: optimize: Add the multiplier details to SLSQP funcs (#22787) * DOC: optimize: Add the multiplier details to SLSQP funcs [docs only] * DOC:optimize: Convert `minimize` tutorial code pieces to single statements [docs only] * DOC: optimize: Fix grammar and typos [docs only] Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> * DOC: optimize: Fix grammar and typos [docs only] Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> * DOC: optimize: Add Wiki ref for KKT multipliers [skip ci] * DOC: optimize: Add missing `mineq` detail to SLSQP docstring [docs only] * DOC: optimize: SLSQP convert options to parameters [docs only] --------- Co-authored-by: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> --- scipy/optimize/_minimize.py | 41 +++++++++++++++++++++++++++---- scipy/optimize/_slsqp_py.py | 48 +++++++++++++++++++++++++++++++++---- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/scipy/optimize/_minimize.py b/scipy/optimize/_minimize.py index 58e765f97b82..97859546c4b8 100644 --- a/scipy/optimize/_minimize.py +++ b/scipy/optimize/_minimize.py @@ -490,6 +490,8 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, .. [19] Zhang, Z. "PRIMA: Reference Implementation for Powell's Methods with Modernization and Amelioration", https://www.libprima.net, :doi:`10.5281/zenodo.8052654` + .. [20] Karush-Kuhn-Tucker conditions, + https://en.wikipedia.org/wiki/Karush%E2%80%93Kuhn%E2%80%93Tucker_conditions Examples -------- @@ -529,7 +531,6 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, [ 0.09495377, 0.18996269, 0.38165151, 0.7664427, 1.53713523] ]) - Next, consider a minimization problem with several constraints (namely Example 16.4 from [5]_). The objective function is: @@ -547,10 +548,42 @@ def minimize(fun, x0, args=(), method=None, jac=None, hess=None, The optimization problem is solved using the SLSQP method as: - >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, - ... constraints=cons) + >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, constraints=cons) + + It should converge to the theoretical solution ``[1.4 ,1.7]``. *SLSQP* also + returns the multipliers that are used in the solution of the problem. These + multipliers, when the problem constraints are linear, can be thought of as the + Karush-Kuhn-Tucker (KKT) multipliers, which are a generalization + of Lagrange multipliers to inequality-constrained optimization problems ([20]_). + + Notice that at the solution, the first constraint is active. Let's evaluate the + function at solution: + + >>> cons[0]['fun'](res.x) + np.float64(1.4901224698604665e-09) + + Also, notice that at optimality there is a non-zero multiplier: + + >>> res.multipliers + array([0.8, 0. , 0. ]) + + This can be understood as the local sensitivity of the optimal value of the + objective function with respect to changes in the first constraint. If we + tighten the constraint by a small amount ``eps``: + + >>> eps = 0.01 + >>> cons[0]['fun'] = lambda x: x[0] - 2 * x[1] + 2 - eps + + we expect the optimal value of the objective function to increase by + approximately ``eps * res.multipliers[0]``: - It should converge to the theoretical solution (1.4 ,1.7). + >>> eps * res.multipliers[0] # Expected change in f0 + np.float64(0.008000000027153205) + >>> f0 = res.fun # Keep track of the previous optimal value + >>> res = minimize(fun, (2, 0), method='SLSQP', bounds=bnds, constraints=cons) + >>> f1 = res.fun # New optimal value + >>> f1 - f0 + np.float64(0.008019998807885509) """ x0 = np.atleast_1d(np.asarray(x0)) diff --git a/scipy/optimize/_slsqp_py.py b/scipy/optimize/_slsqp_py.py index 42e9fa3fee2e..d02ff56a4d93 100644 --- a/scipy/optimize/_slsqp_py.py +++ b/scipy/optimize/_slsqp_py.py @@ -221,14 +221,14 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, Minimize a scalar function of one or more variables using Sequential Least Squares Programming (SLSQP). - Options - ------- + Parameters + ---------- ftol : float Precision target for the value of f in the stopping criterion. This value controls the final accuracy for checking various optimality conditions; gradient of the lagrangian and absolute sum of the constraint violations - should be lower than ``ftol``. Similarly, if computed step size and the - objective function chage are checked against this value. Default is 1e-6. + should be lower than ``ftol``. Similarly, computed step size and the + objective function changes are checked against this value. Default is 1e-6. eps : float Step size used for numerical approximation of the Jacobian. disp : bool @@ -250,6 +250,46 @@ def _minimize_slsqp(func, x0, args=(), jac=None, bounds=None, .. versionadded:: 1.16.0 + Returns + ------- + res : OptimizeResult + The optimization result represented as an `OptimizeResult` object. + In this dict-like object the following fields are of particular importance: + ``x`` the solution array, ``success`` a Boolean flag indicating if the + optimizer exited successfully, ``message`` which describes the reason for + termination, and ``multipliers`` which contains the Karush-Kuhn-Tucker + (KKT) multipliers for the QP approximation used in solving the original + nonlinear problem. See ``Notes`` below. See also `OptimizeResult` for a + description of other attributes. + + Notes + ----- + The KKT multipliers are returned in the ``OptimizeResult.multipliers`` + attribute as a NumPy array. Denoting the dimension of the equality constraints + with ``meq``, and of inequality constraints with ``mineq``, then the returned + array slice ``m[:meq]`` contains the multipliers for the equality constraints, + and the remaining ``m[meq:meq + mineq]`` contains the multipliers for the + inequality constraints. The multipliers corresponding to bound inequalities + are not returned. See [1]_ pp. 321 or [2]_ for an explanation of how to interpret + these multipliers. The internal QP problem is solved using the methods given + in [3]_ Chapter 25. + + Note that if new-style `NonlinearConstraint` or `LinearConstraint` were + used, then ``minimize`` converts them first to old-style constraint dicts. + It is possible for a single new-style constraint to simultaneously contain + both inequality and equality constraints. This means that if there is mixing + within a single constraint, then the returned list of multipliers will have + a different length than the original new-style constraints. + + References + ---------- + .. [1] Nocedal, J., and S J Wright, 2006, "Numerical Optimization", Springer, + New York. + .. [2] Kraft, D., "A software package for sequential quadratic programming", + 1988, Tech. Rep. DFVLR-FB 88-28, DLR German Aerospace Center, Germany. + .. [3] Lawson, C. L., and R. J. Hanson, 1995, "Solving Least Squares Problems", + SIAM, Philadelphia, PA. + """ _check_unknown_options(unknown_options) acc = ftol From 9b92c55c8dbf71ad01371d04c17a7c3cd5956613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Fri, 16 May 2025 22:46:51 +0530 Subject: [PATCH 207/251] BUG: interpolate.make_splrep: raise error when `residuals.sum()` becomes `NaN` (#22973) reviewed at https://github.com/scipy/scipy/pull/22973 --- scipy/interpolate/_fitpack_repro.py | 30 ++++++++++++++---------- scipy/interpolate/tests/test_bsplines.py | 16 ++++++++++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/scipy/interpolate/_fitpack_repro.py b/scipy/interpolate/_fitpack_repro.py index c352761786c8..76ae803c63ac 100644 --- a/scipy/interpolate/_fitpack_repro.py +++ b/scipy/interpolate/_fitpack_repro.py @@ -57,7 +57,11 @@ def _get_residuals(x, y, t, k, w): _, _, c = _lsq_solve_qr(x, y, t, k, w) c = np.ascontiguousarray(c) spl = BSpline(t, c, k) - return _compute_residuals(w2, spl(x), y) + residuals = _compute_residuals(w2, spl(x), y) + fp = residuals.sum() + if np.isnan(fp): + raise ValueError(_iermesg[1]) + return residuals, fp def _compute_residuals(w2, splx, y): @@ -258,8 +262,7 @@ def _generate_knots_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, nest=None) # construct the LSQ spline with this set of knots fpold = fp - residuals = _get_residuals(x, y, t, k, w=w) - fp = residuals.sum() + residuals, fp = _get_residuals(x, y, t, k, w=w) fpms = fp - s # c test whether the approximation sinf(x) is an acceptable solution. @@ -300,7 +303,7 @@ def _generate_knots_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, nest=None) # recompute if needed if j < nplus - 1: - residuals = _get_residuals(x, y, t, k, w=w) + residuals, _ = _get_residuals(x, y, t, k, w=w) # this should never be reached return @@ -536,11 +539,15 @@ def __init__(self, **kwargs): self.__dict__.update(**kwargs) -_iermesg = { -2: """error. a theoretically impossible result was found during +_iermesg1 = """error. a theoretically impossible result was found during the iteration process for finding a smoothing spline with fp = s. probably causes : s too small. -there is an approximation returned but the corresponding +""" + +_iermesg = { +1: _iermesg1 + """the weighted sum of squared residuals is becoming NaN +""", +2: _iermesg1 + """there is an approximation returned but the corresponding weighted sum of squared residuals does not satisfy the condition abs(fp-s)/s < tol. """, @@ -659,7 +666,7 @@ def _make_splrep_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, t=None, nest= nest = max(m + k + 1, 2*k + 3) else: if nest < 2*(k + 1): - raise ValueError(f"`nest` too small: {nest = } < 2*(k+1) = {2*(k+1)}.") + raise ValueError(f"`nest` too small: {nest = } < 2*(k+1) = {2*(k+1)}.") if t is not None: raise ValueError("Either supply `t` or `nest`.") @@ -685,13 +692,11 @@ def _make_splrep_impl(x, y, *, w=None, xb=None, xe=None, k=3, s=0, t=None, nest= # ### bespoke solver #### # initial conditions # f(p=inf) : LSQ spline with knots t (XXX: reuse R, c) - residuals = _get_residuals(x, y, t, k, w=w) - fp = residuals.sum() + _, fp = _get_residuals(x, y, t, k, w=w) fpinf = fp - s # f(p=0): LSQ spline without internal knots - residuals = _get_residuals(x, y, np.array([xb]*(k+1) + [xe]*(k+1)), k, w) - fp0 = residuals.sum() + _, fp0 = _get_residuals(x, y, np.array([xb]*(k+1) + [xe]*(k+1)), k, w) fp0 = fp0 - s # solve @@ -989,4 +994,3 @@ def make_splprep(x, *, w=None, u=None, ub=None, ue=None, k=3, s=0, t=None, nest= spl1 = BSpline(spl.t, cc, spl.k, axis=1) return spl1, u - diff --git a/scipy/interpolate/tests/test_bsplines.py b/scipy/interpolate/tests/test_bsplines.py index b6898752be28..33cc70a390c4 100644 --- a/scipy/interpolate/tests/test_bsplines.py +++ b/scipy/interpolate/tests/test_bsplines.py @@ -2167,7 +2167,7 @@ def test_compare_with_GCVSPL(self): # using an iterative algorithm for minimizing the GCV criteria. These # algorithms may vary, so the tolerance should be rather low. # Not checking dtypes as gcvspl.npz stores little endian arrays, which - # result in conflicting dtypes on big endian systems. + # result in conflicting dtypes on big endian systems. xp_assert_close(y_compr, y_GCVSPL, atol=1e-4, rtol=1e-4, check_dtype=False) def test_non_regularized_case(self): @@ -3533,6 +3533,20 @@ def test_s_too_small(self): xp_assert_close(np.r_[spl.c, [0]*(spl.k+1)], tck[1], atol=5e-13) + def test_issue_22704(self): + # Reference - https://github.com/scipy/scipy/issues/22704 + x = np.asarray([20.00, 153.81, 175.57, 202.47, 237.11, + 253.61, 258.56, 273.40, 284.54, 293.61, + 298.56, 301.86, 305.57, 307.22, 308.45, + 310.10, 310.10, 310.50], dtype=np.float64) + y = np.asarray([53.00, 49.50, 48.60, 46.80, 43.20, + 40.32, 39.60, 36.00, 32.40, 28.80, + 25.20, 21.60, 18.00, 14.40, 10.80, + 7.20, 3.60, 0.0], dtype=np.float64) + w = np.asarray([1.38723] * y.shape[0], dtype=np.float64) + with assert_raises(ValueError): + make_splrep(x, y, w=w, k=2, s=12) + def test_shape(self): # make sure coefficients have the right shape (not extra dims) n, k = 10, 3 From dcda6e2f2f384647523fdedee2b194c74aa26afe Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Fri, 16 May 2025 18:58:43 +0100 Subject: [PATCH 208/251] MAINT: bump up array-api-compat and array-api-extra (#22999) --- scipy/_lib/array_api_compat | 2 +- scipy/_lib/array_api_extra | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scipy/_lib/array_api_compat b/scipy/_lib/array_api_compat index e600449a645c..8005d6d02c0f 160000 --- a/scipy/_lib/array_api_compat +++ b/scipy/_lib/array_api_compat @@ -1 +1 @@ -Subproject commit e600449a645c2e6ce5a2276da0006491f097c096 +Subproject commit 8005d6d02c0f1717881de37a710871bb955eb5cd diff --git a/scipy/_lib/array_api_extra b/scipy/_lib/array_api_extra index bb6129b1bfe3..e58c0cbc6d47 160000 --- a/scipy/_lib/array_api_extra +++ b/scipy/_lib/array_api_extra @@ -1 +1 @@ -Subproject commit bb6129b1bfe344b9807a2f28451fe9211efe0b1b +Subproject commit e58c0cbc6d4793bab91f3efc266fad00e1d45f17 From b6b0f2d36b9817ffab521cd983a28342fbad673c Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Fri, 16 May 2025 22:11:58 +0100 Subject: [PATCH 209/251] DOC: `special`: update top-level docs to reflect `xp_capabilities` (#23001) --- doc/source/dev/api-dev/array_api.rst | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index c4d07a431ced..530778de897f 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -101,21 +101,13 @@ variable is set: - `scipy.fft` - `scipy.io` - `scipy.ndimage` +- `scipy.special` - `scipy.stats` Individual functions in the above modules provide a capability table in the documentation like the one below. If the table is absent, the function does not yet support backends other than NumPy. -Additionally, support is provided in `scipy.special` for the following functions, even -if they do not have a capability table in the documentation: -`scipy.special.log_ndtr`, `scipy.special.ndtr`, `scipy.special.ndtri`, -`scipy.special.erf`, `scipy.special.erfc`, `scipy.special.i0`, -`scipy.special.i0e`, `scipy.special.i1`, `scipy.special.i1e`, -`scipy.special.gammaln`, `scipy.special.gammainc`, `scipy.special.gammaincc`, -`scipy.special.logit`, `scipy.special.expit`, `scipy.special.entr`, -`scipy.special.rel_entr`, `scipy.special.rel_entr`, `scipy.special.xlogy`, -and `scipy.special.chdtrc`. Example capabilities table -------------------------- From 7e2504d82e5ae3388557be13cc479cb8373548fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Sat, 17 May 2025 02:47:41 +0530 Subject: [PATCH 210/251] DEV: Remove manual addition of tests in --pyargs (#22998) --- .spin/cmds.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index 0b3cc6942c50..1c09426c74fe 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -131,7 +131,7 @@ def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, os.getcwd(), os.environ.get('PKG_CONFIG_PATH', '') ]) - + if use_system_libraries: meson_args = meson_args + ("-Duse-system-libraries=auto",) @@ -267,9 +267,6 @@ def test(*, parent_callback, pytest_args, tests, coverage, if (n_jobs != 1) and ('-n' not in pytest_args): pytest_args = ('-n', str(n_jobs)) + pytest_args - if tests and '--pyargs' not in pytest_args: - pytest_args += ('--pyargs', tests) - if durations: pytest_args += ('--durations', durations) From e578dc0ee928091afede0ec8c46812da87d684bf Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Fri, 16 May 2025 22:49:14 +0100 Subject: [PATCH 211/251] TST: Use `jax_autojit` (#22909) --- scipy/_lib/_array_api.py | 8 +-- scipy/_lib/array_api_extra | 2 +- scipy/_lib/tests/test__util.py | 2 +- scipy/_lib/tests/test_array_api.py | 5 +- scipy/cluster/tests/test_hierarchy.py | 23 ++++---- scipy/constants/_constants.py | 2 +- scipy/special/_logsumexp.py | 6 +-- scipy/stats/_stats_py.py | 76 ++++++++++----------------- 8 files changed, 48 insertions(+), 76 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index aa9d9c524af2..faaeb11c2632 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -687,7 +687,7 @@ def _make_sphinx_capabilities( # xpx.lazy_xp_backends kwargs allow_dask_compute=False, jax_jit=True, # unused in documentation - reason=None, static_argnums=None, static_argnames=None, + reason=None, ): exceptions = set(exceptions) @@ -761,7 +761,6 @@ def xp_capabilities( # xpx.testing.lazy_xp_function kwargs. # Refer to array-api-extra documentation. allow_dask_compute=False, jax_jit=True, - static_argnums=None, static_argnames=None, ): """Decorator for a function that states its support among various Array API compatible backends. @@ -792,8 +791,6 @@ def xp_capabilities( exceptions=exceptions, allow_dask_compute=allow_dask_compute, jax_jit=jax_jit, - static_argnums=static_argnums, - static_argnames=static_argnames, ) sphinx_capabilities = _make_sphinx_capabilities(**capabilities) @@ -841,8 +838,7 @@ def _make_xp_pytest_marks(*funcs, capabilities_table=None): marks.append(pytest.mark.xfail_xp_backends(mod_name, reason=reason)) lazy_kwargs = {k: capabilities[k] - for k in ('allow_dask_compute', 'jax_jit', - 'static_argnums', 'static_argnames')} + for k in ('allow_dask_compute', 'jax_jit')} lazy_xp_function(func, **lazy_kwargs) return marks diff --git a/scipy/_lib/array_api_extra b/scipy/_lib/array_api_extra index e58c0cbc6d47..28a364d961df 160000 --- a/scipy/_lib/array_api_extra +++ b/scipy/_lib/array_api_extra @@ -1 +1 @@ -Subproject commit e58c0cbc6d4793bab91f3efc266fad00e1d45f17 +Subproject commit 28a364d961dfe506877bd111f9d4e503a9fafa16 diff --git a/scipy/_lib/tests/test__util.py b/scipy/_lib/tests/test__util.py index 4c4e61ce8c49..d0480907da67 100644 --- a/scipy/_lib/tests/test__util.py +++ b/scipy/_lib/tests/test__util.py @@ -21,7 +21,7 @@ from scipy import cluster, interpolate, linalg, optimize, sparse, spatial, stats -lazy_xp_function(_contains_nan, static_argnames=("nan_policy", "xp_omit_okay", "xp")) +lazy_xp_function(_contains_nan) @pytest.mark.slow diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index 663ee0ac0024..f7f323adaa47 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -12,9 +12,8 @@ from scipy._lib.array_api_extra.testing import lazy_xp_function -lazy_xp_function(_asarray, static_argnames=( - "dtype", "order", "copy", "xp", "check_finite", "subok")) -lazy_xp_function(xp_copy, static_argnames=("xp", )) +lazy_xp_function(_asarray) +lazy_xp_function(xp_copy) @pytest.mark.skipif(not _GLOBAL_CONFIG["SCIPY_ARRAY_API"], diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 1eb4c0d8e6ee..52463bfdd11e 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -78,33 +78,30 @@ class eager: lazy_xp_function(single) lazy_xp_function(ward) -lazy_xp_function(linkage, static_argnames=('method', 'metric', 'optimal_ordering')) -lazy_xp_function(cut_tree, static_argnames=('n_clusters', 'height')) -lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=True, - static_argnames=('rd', )) -lazy_xp_function(optimal_leaf_ordering, static_argnames=('metric',)) +lazy_xp_function(linkage) +lazy_xp_function(cut_tree) +lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=True) +lazy_xp_function(optimal_leaf_ordering) lazy_xp_function(cophenet) -lazy_xp_function(inconsistent, static_argnames=('d',)) +lazy_xp_function(inconsistent) lazy_xp_function(from_mlab_linkage) lazy_xp_function(to_mlab_linkage) lazy_xp_function(is_monotonic) # Note: these functions materialize lazy arrays when warning=True or throw=True -lazy_xp_function(is_valid_im, static_argnames=("warning", "throw", "name")) -lazy_xp_function(is_valid_linkage, static_argnames=("warning", "throw", "name")) +lazy_xp_function(is_valid_im) +lazy_xp_function(is_valid_linkage) lazy_xp_function(num_obs_linkage) lazy_xp_function(correspond) -lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=True, - static_argnames=('criterion', 'depth')) -lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=True, - static_argnames=('criterion', 'metric', 'depth', 'method')) +lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=True) +lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=True) lazy_xp_function(leaves_list) lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=True) lazy_xp_function(is_isomorphic) lazy_xp_function(maxdists) lazy_xp_function(maxinconsts) -lazy_xp_function(maxRstat, static_argnames=('i',)) +lazy_xp_function(maxRstat) # Returns data-dependent shape lazy_xp_function(leaders, jax_jit=False) diff --git a/scipy/constants/_constants.py b/scipy/constants/_constants.py index 2cbe5dac272a..fc4bdf83d60e 100644 --- a/scipy/constants/_constants.py +++ b/scipy/constants/_constants.py @@ -225,7 +225,7 @@ # functions for conversions that are not linear -@xp_capabilities(static_argnames=("old_scale", "new_scale")) +@xp_capabilities() def convert_temperature( val: "npt.ArrayLike", old_scale: str, diff --git a/scipy/special/_logsumexp.py b/scipy/special/_logsumexp.py index db0debafdcc8..b7338c3f881d 100644 --- a/scipy/special/_logsumexp.py +++ b/scipy/special/_logsumexp.py @@ -12,7 +12,7 @@ __all__ = ["logsumexp", "softmax", "log_softmax"] -@xp_capabilities(static_argnames=("axis", "keepdims", "return_sign")) +@xp_capabilities() def logsumexp(a, axis=None, b=None, keepdims=False, return_sign=False): """Compute the log of the sum of exponentials of input elements. @@ -254,7 +254,7 @@ def _logsumexp(a, b, *, axis, return_sign, xp): return out, sgn -@xp_capabilities(static_argnames="axis") +@xp_capabilities() def softmax(x, axis=None): r"""Compute the softmax function. @@ -353,7 +353,7 @@ def softmax(x, axis=None): return exp_x_shifted / xp.sum(exp_x_shifted, axis=axis, keepdims=True) -@xp_capabilities(static_argnames="axis") +@xp_capabilities() def log_softmax(x, axis=None): r"""Compute the logarithm of the softmax function. diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index c773e96ac779..90b6f45994e2 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -165,7 +165,7 @@ def _unpack_CorrelationResult(res): # note that `weights` are paired with `x` -@xp_capabilities(static_argnames=("axis", "dtype")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -249,8 +249,7 @@ def gmean(a, axis=0, dtype=None, weights=None): return xp.exp(_xp_mean(log_a, axis=axis, weights=weights)) -@xp_capabilities(static_argnames=("axis", "dtype"), - jax_jit=False, allow_dask_compute=1) +@xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -351,8 +350,7 @@ def hmean(a, axis=0, dtype=None, *, weights=None): return 1.0 / _xp_mean(1.0 / a, axis=axis, weights=weights) -@xp_capabilities(static_argnames=("axis", "dtype"), - jax_jit=False, allow_dask_compute=1) +@xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, result_to_tuple=lambda x: (x,), kwd_samples=['weights']) @@ -633,7 +631,7 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): return a, mask -@xp_capabilities(static_argnames=("inclusive", "axis")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, result_to_tuple=lambda x: (x,) @@ -689,7 +687,7 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): return mean[()] if mean.ndim == 0 else mean -@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -749,7 +747,7 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): return _xp_var(a, correction=ddof, axis=axis, nan_policy='omit', xp=xp) -@xp_capabilities(static_argnames=("inclusive", "axis")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -813,7 +811,7 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): return res[()] if res.ndim == 0 else res -@xp_capabilities(static_argnames=("inclusive", "axis")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -876,7 +874,7 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): return res[()] if res.ndim == 0 else res -@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -929,7 +927,7 @@ def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): return tvar(a, limits, inclusive, axis, ddof, _no_deco=True)**0.5 -@xp_capabilities(static_argnames=("inclusive", "axis", "ddof")) +@xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) ) @@ -1044,8 +1042,7 @@ def _moment_tuple(x, n_out): # empty, there is no distinction between the `moment` function being called # with parameter `order=1` and `order=[1]`; the latter *should* produce # the same as the former but with a singleton zeroth dimension. -@xp_capabilities(static_argnames=("axis", "nan_policy"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_rename_parameter('moment', 'order') @_axis_nan_policy_factory( # noqa: E302 _moment_result_object, n_samples=1, result_to_tuple=_moment_tuple, @@ -1266,8 +1263,7 @@ def _share_masks(*args, xp): return args[0] if len(args) == 1 else args -@xp_capabilities(static_argnames=("axis", "bias", "nan_policy"), - jax_jit=False, allow_dask_compute=2) +@xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 ) @@ -1368,8 +1364,7 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): return vals[()] if vals.ndim == 0 else vals -@xp_capabilities(static_argnames=("axis", "fisher", "bias", "nan_policy"), - jax_jit=False, allow_dask_compute=2) +@xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 ) @@ -1483,8 +1478,7 @@ def kurtosis(a, axis=0, fisher=True, bias=True, nan_policy='propagate'): 'kurtosis')) -@xp_capabilities(static_argnames=("axis", "ddof", "bias", "nan_policy"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) def describe(a, axis=0, ddof=1, bias=True, nan_policy='propagate'): """Compute several descriptive statistics of the passed array. @@ -1608,8 +1602,7 @@ def _get_pvalue(statistic, distribution, alternative, symmetric=True, xp=None): SkewtestResult = namedtuple('SkewtestResult', ('statistic', 'pvalue')) -@xp_capabilities(static_argnames=("axis", "nan_policy", "alternative"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SkewtestResult, n_samples=1, too_small=7) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1719,8 +1712,7 @@ def skewtest(a, axis=0, nan_policy='propagate', alternative='two-sided'): KurtosistestResult = namedtuple('KurtosistestResult', ('statistic', 'pvalue')) -@xp_capabilities(static_argnames=("axis", "nan_policy", "alternative"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(KurtosistestResult, n_samples=1, too_small=4) def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): r"""Test whether a dataset has normal kurtosis. @@ -1834,8 +1826,7 @@ def kurtosistest(a, axis=0, nan_policy='propagate', alternative='two-sided'): NormaltestResult = namedtuple('NormaltestResult', ('statistic', 'pvalue')) -@xp_capabilities(static_argnames=("axis", "nan_policy"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(NormaltestResult, n_samples=1, too_small=7) def normaltest(a, axis=0, nan_policy='propagate'): r"""Test whether a sample differs from a normal distribution. @@ -1913,7 +1904,7 @@ def normaltest(a, axis=0, nan_policy='propagate'): return NormaltestResult(statistic, pvalue) -@xp_capabilities(static_argnames="axis", jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, default_axis=None) def jarque_bera(x, *, axis=None): r"""Perform the Jarque-Bera goodness of fit test on sample data. @@ -2691,7 +2682,7 @@ def _isconst(x): return (y[0] == y).all(keepdims=True) -@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) +@xp_capabilities() def zscore(a, axis=0, ddof=0, nan_policy='propagate'): """ Compute the z score. @@ -2777,7 +2768,7 @@ def zscore(a, axis=0, ddof=0, nan_policy='propagate'): return zmap(a, a, axis=axis, ddof=ddof, nan_policy=nan_policy) -@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) +@xp_capabilities() def gzscore(a, *, axis=0, ddof=0, nan_policy='propagate'): """ Compute the geometric standard score. @@ -2872,7 +2863,7 @@ def gzscore(a, *, axis=0, ddof=0, nan_policy='propagate'): return zscore(log(a), axis=axis, ddof=ddof, nan_policy=nan_policy) -@xp_capabilities(static_argnames=('axis', 'ddof', 'nan_policy')) +@xp_capabilities() def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): """ Calculate the relative z-scores. @@ -2953,7 +2944,7 @@ def zmap(scores, compare, axis=0, ddof=0, nan_policy='propagate'): return z -@xp_capabilities(static_argnames=('axis', 'ddof', 'keepdims', 'nan_policy')) +@xp_capabilities() def gstd(a, axis=0, ddof=1, *, keepdims=False, nan_policy='propagate'): r""" Calculate the geometric standard deviation of an array. @@ -4397,7 +4388,6 @@ def confidence_interval(self, confidence_level=0.95, method=None): @xp_capabilities(cpu_only=True, exceptions=['cupy'], - static_argnames=("alternative", "method", "axis"), jax_jit=False, allow_dask_compute=True) def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): r""" @@ -6332,8 +6322,7 @@ def _equal_var_ttest_denom(v1, n1, v2, n2, xp=None): Ttest_indResult = namedtuple('Ttest_indResult', ('statistic', 'pvalue')) -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], - static_argnames=("equal_var", "alternative")) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, equal_var=True, alternative="two-sided"): r""" @@ -6479,13 +6468,7 @@ def ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2, _ttest_ind_dep_msg = "Use ``method`` to perform a permutation test." -@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"], - # FIXME this is a test artefact - # xpx.lazy_xp_function can't repack returned object TtestResult - # https://github.com/data-apis/array-api-extra/issues/270 - jax_jit=False, - static_argnames=("axis", "equal_var", "nan_policy", "permutations", - "random_state", "alternative", "trim", "method")) +@xp_capabilities(cpu_only=True, exceptions=["cupy", "jax.numpy"]) @_deprecate_positional_args(version='1.17.0', deprecated_args={'permutations', 'random_state'}, custom_message=_ttest_ind_dep_msg) @@ -7200,8 +7183,7 @@ def _pd_nsamples(kwargs): return 2 if kwargs.get('f_exp', None) is not None else 1 -@xp_capabilities(static_argnames=("ddof", "axis", "lambda_"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def power_divergence(f_obs, f_exp=None, ddof=0, axis=0, lambda_=None): @@ -7442,8 +7424,7 @@ def _power_divergence(f_obs, f_exp, ddof, axis, lambda_, sum_check=True): -@xp_capabilities(static_argnames=("ddof, axis", "sum_check"), - jax_jit=False, allow_dask_compute=True) +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(Power_divergenceResult, paired=True, n_samples=_pd_nsamples, too_small=-1) def chisquare(f_obs, f_exp=None, ddof=0, axis=0, *, sum_check=True): @@ -8932,8 +8913,8 @@ def brunnermunzel(x, y, alternative="two-sided", distribution="t", @xp_capabilities(cpu_only=True, exceptions=['cupy', 'jax.numpy'], - reason=('Delegation for `special.stdtr` only implemented for CuPy and JAX.'), - static_argnames=("method", "axis"), jax_jit=False, allow_dask_compute=True) + reason='Delegation for `special.stdtr` only implemented for CuPy and JAX.', + jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory(SignificanceResult, kwd_samples=['weights'], paired=True) def combine_pvalues(pvalues, method='fisher', weights=None, *, axis=0): """ @@ -10197,8 +10178,7 @@ def _square_of_sums(a, axis=0): @xp_capabilities(skip_backends=[("torch", "no `repeat`"), ("cupy", "`repeat` can't handle array second arg"), ("dask.array", "no `take_along_axis`")], - static_argnames=("method", "axis", "nan_policy"), jax_jit=False, - allow_dask_compute=True) + jax_jit=False, allow_dask_compute=True) def rankdata(a, method='average', *, axis=None, nan_policy='propagate'): """Assign ranks to data, dealing with ties appropriately. From 003d311f84187469abcb6a4b01e13debf501b288 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 9 May 2025 15:57:38 +0100 Subject: [PATCH 212/251] DOC/TST: `cluster.hierarchy`: use `xp_capabilities` --- scipy/_lib/_array_api.py | 19 +++++-- scipy/cluster/hierarchy.py | 42 +++++++++++++- scipy/cluster/tests/test_hierarchy.py | 81 ++++++++++----------------- 3 files changed, 84 insertions(+), 58 deletions(-) diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index faaeb11c2632..5e1170ac51ef 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -660,7 +660,7 @@ def is_marray(xp): class _XPSphinxCapability: cpu: bool | None # None if not applicable gpu: bool | None - warnings: str | None = None + warnings: list[str] = dataclasses.field(default_factory=list) def _render(self, value): if value is None: @@ -668,8 +668,8 @@ def _render(self, value): if not value: return "⛔" if self.warnings: - res = f"⚠️ {self.warnings}" - assert len(res) <= 20 + res = "⚠️ " + '; '.join(self.warnings) + assert len(res) <= 20, "Warnings too long" return res return "✅" @@ -686,6 +686,8 @@ def _make_sphinx_capabilities( cpu_only=False, np_only=False, exceptions=(), # xpx.lazy_xp_backends kwargs allow_dask_compute=False, jax_jit=True, + # list of tuples [(module name, reason), ...] + warnings = (), # unused in documentation reason=None, ): @@ -698,10 +700,10 @@ def _make_sphinx_capabilities( "cupy": _XPSphinxCapability(cpu=None, gpu=True), "torch": _XPSphinxCapability(cpu=True, gpu=True), "jax.numpy": _XPSphinxCapability(cpu=True, gpu=True, - warnings=None if jax_jit else "no JIT"), + warnings=[] if jax_jit else ["no JIT"]), # Note: Dask+CuPy is currently untested and unsupported "dask.array": _XPSphinxCapability(cpu=True, gpu=None, - warnings="computes graph" if allow_dask_compute else None), + warnings=["computes graph"] if allow_dask_compute else []), } # documentation doesn't display the reason @@ -721,6 +723,10 @@ def _make_sphinx_capabilities( elif cpu_only and module not in exceptions and backend.gpu is not None: backend.gpu = False + for module, warning in warnings: + backend = capabilities[module] + backend.warnings.append(warning) + return capabilities @@ -758,6 +764,8 @@ def xp_capabilities( # lists of tuples [(module name, reason), ...] skip_backends=(), xfail_backends=(), cpu_only=False, np_only=False, reason=None, exceptions=(), + # lists of tuples [(module name, reason), ...] + warnings=(), # xpx.testing.lazy_xp_function kwargs. # Refer to array-api-extra documentation. allow_dask_compute=False, jax_jit=True, @@ -791,6 +799,7 @@ def xp_capabilities( exceptions=exceptions, allow_dask_compute=allow_dask_compute, jax_jit=jax_jit, + warnings=warnings, ) sphinx_capabilities = _make_sphinx_capabilities(**capabilities) diff --git a/scipy/cluster/hierarchy.py b/scipy/cluster/hierarchy.py index d8cac08aee0a..1a7d212355db 100644 --- a/scipy/cluster/hierarchy.py +++ b/scipy/cluster/hierarchy.py @@ -135,7 +135,7 @@ from . import _hierarchy, _optimal_leaf_ordering import scipy.spatial.distance as distance from scipy._lib._array_api import (_asarray, array_namespace, is_dask, - is_lazy_array, xp_copy) + is_lazy_array, xp_capabilities, xp_copy) from scipy._lib._disjoint_set import DisjointSet import scipy._lib.array_api_extra as xpx @@ -168,6 +168,12 @@ def int_floor(arr, xp): return int(xp.asarray(arr, dtype=xp.int64)) +lazy_cython = xp_capabilities( + cpu_only=True, reason="Cython code", + warnings=[("dask.array", "merges chunks")]) + + +@lazy_cython def single(y): """ Perform single/min/nearest linkage on the condensed distance matrix ``y``. @@ -246,6 +252,7 @@ def single(y): return linkage(y, method='single', metric='euclidean') +@lazy_cython def complete(y): """ Perform complete/max/farthest point linkage on a condensed distance matrix. @@ -328,6 +335,7 @@ def complete(y): return linkage(y, method='complete', metric='euclidean') +@lazy_cython def average(y): """ Perform average/UPGMA linkage on a condensed distance matrix. @@ -410,6 +418,7 @@ def average(y): return linkage(y, method='average', metric='euclidean') +@lazy_cython def weighted(y): """ Perform weighted/WPGMA linkage on the condensed distance matrix. @@ -495,6 +504,7 @@ def weighted(y): return linkage(y, method='weighted', metric='euclidean') +@lazy_cython def centroid(y): """ Perform centroid/UPGMC linkage. @@ -597,6 +607,7 @@ def centroid(y): return linkage(y, method='centroid', metric='euclidean') +@lazy_cython def median(y): """ Perform median/WPGMC linkage. @@ -699,6 +710,7 @@ def median(y): return linkage(y, method='median', metric='euclidean') +@lazy_cython def ward(y): """ Perform Ward's linkage on a condensed distance matrix. @@ -798,6 +810,7 @@ def ward(y): return linkage(y, method='ward', metric='euclidean') +@lazy_cython def linkage(y, method='single', metric='euclidean', optimal_ordering=False): """ Perform hierarchical/agglomerative clustering. @@ -1291,6 +1304,7 @@ def _order_cluster_tree(Z): return nodes +@xp_capabilities(np_only=True, reason="non-standard indexing") def cut_tree(Z, n_clusters=None, height=None): """ Given a linkage matrix Z, return the cut tree. @@ -1378,6 +1392,7 @@ def cut_tree(Z, n_clusters=None, height=None): return groups.T +@xp_capabilities(jax_jit=False, allow_dask_compute=True) def to_tree(Z, rd=False): """ Convert a linkage matrix into an easy-to-use tree object. @@ -1481,6 +1496,7 @@ def to_tree(Z, rd=False): return nd +@lazy_cython def optimal_leaf_ordering(Z, y, metric='euclidean'): """ Given a linkage matrix Z and distance, reorder the cut tree. @@ -1557,6 +1573,7 @@ def cy_optimal_leaf_ordering(Z, y, validate): shape=Z.shape, dtype=Z.dtype, as_numpy=True, xp=xp) +@lazy_cython def cophenet(Z, Y=None): """ Calculate the cophenetic distances between each observation in @@ -1699,6 +1716,7 @@ def cy_cophenet(Z, validate): return (c, zz) +@lazy_cython def inconsistent(Z, d=2): r""" Calculate inconsistency statistics on a linkage matrix. @@ -1775,6 +1793,7 @@ def cy_inconsistent(Z, d, validate): as_numpy=True, xp=xp) +@lazy_cython def from_mlab_linkage(Z): """ Convert a linkage matrix generated by MATLAB(TM) to a new @@ -1888,6 +1907,7 @@ def cy_from_mlab_linkage(Zpart, validate): return xpx.at(res)[:, -1].set(CS) +@xp_capabilities() def to_mlab_linkage(Z): """ Convert a linkage matrix to a MATLAB(TM) compatible one. @@ -1972,6 +1992,7 @@ def to_mlab_linkage(Z): return xp.concat((Z[:, :2] + 1.0, Z[:, 2:3]), axis=1) +@xp_capabilities() def is_monotonic(Z): """ Return True if the linkage passed is monotonic. @@ -2058,6 +2079,7 @@ def is_monotonic(Z): return xp.all(Z[1:, 2] >= Z[:-1, 2]) +@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_valid_im(R, warning=False, throw=False, name=None): """Return True if the inconsistency matrix passed is valid. @@ -2195,6 +2217,7 @@ def _is_valid_im(R, warning=False, throw=False, name=None, materialize=False, *, ) +@xp_capabilities(warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_valid_linkage(Z, warning=False, throw=False, name=None): """ Check the validity of a linkage matrix. @@ -2391,6 +2414,7 @@ def _lazy_valid_checks(*args, throw=False, warning=False, materialize=False, xp) return not any(conds) +@xp_capabilities() def num_obs_linkage(Z): """ Return the number of original observations of the linkage matrix passed. @@ -2430,6 +2454,7 @@ def num_obs_linkage(Z): return Z.shape[0] + 1 +@xp_capabilities() def correspond(Z, Y): """ Check for correspondence between linkage and condensed distance matrices. @@ -2487,6 +2512,8 @@ def correspond(Z, Y): return distance.num_obs_y(Y) == num_obs_linkage(Z) +@xp_capabilities(cpu_only=True, reason="Cython code", + jax_jit=False, allow_dask_compute=True) def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): """ Form flat clusters from the hierarchical clustering defined by @@ -2673,6 +2700,8 @@ def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): return xp.asarray(T) +@xp_capabilities(cpu_only=True, reason="Cython code", + jax_jit=False, allow_dask_compute=True) def fclusterdata(X, t, criterion='inconsistent', metric='euclidean', depth=2, method='single', R=None): """ @@ -2774,6 +2803,7 @@ def fclusterdata(X, t, criterion='inconsistent', return T +@lazy_cython def leaves_list(Z): """ Return a list of leaf node ids. @@ -3095,6 +3125,7 @@ def set_link_color_palette(palette): _link_line_colors = palette +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) def dendrogram(Z, p=30, truncate_mode=None, color_threshold=None, get_leaves=True, orientation='top', labels=None, count_sort=False, distance_sort=False, show_leaf_counts=True, @@ -3778,6 +3809,8 @@ def _dendrogram_calculate_info(Z, p, truncate_mode, return (((uiva + uivb) / 2), uwa + uwb, h, max_dist) +@xp_capabilities(cpu_only=True, + warnings=[("dask.array", "see notes"), ("jax.numpy", "see notes")]) def is_isomorphic(T1, T2): """ Determine if two different cluster assignments are equivalent. @@ -3876,6 +3909,8 @@ def py_is_isomorphic(T1, T2): as_numpy=True, xp=xp) return res if is_lazy_array(res) else bool(res) + +@lazy_cython def maxdists(Z): """ Return the maximum distance between any non-singleton cluster. @@ -3965,6 +4000,7 @@ def cy_maxdists(Z, validate): as_numpy=True, xp=xp) +@lazy_cython def maxinconsts(Z, R): """ Return the maximum inconsistency coefficient for each @@ -4064,6 +4100,7 @@ def cy_maxinconsts(Z, R, validate): as_numpy=True, xp=xp) +@lazy_cython def maxRstat(Z, R, i): """ Return the maximum statistic for each non-singleton cluster and its @@ -4171,6 +4208,9 @@ def cy_maxRstat(Z, R, i, validate): as_numpy=True, xp=xp) +# Data-dependent output shape makes it impossible to use jax.jit +@xp_capabilities(cpu_only=True, reason="Cython code", jax_jit=False, + warnings=[("dask.array", "merges chunks")]) def leaders(Z, T): """ Return the root nodes in a hierarchical clustering. diff --git a/scipy/cluster/tests/test_hierarchy.py b/scipy/cluster/tests/test_hierarchy.py index 52463bfdd11e..7cab57ba2fd0 100644 --- a/scipy/cluster/tests/test_hierarchy.py +++ b/scipy/cluster/tests/test_hierarchy.py @@ -46,9 +46,9 @@ _order_cluster_tree, _hierarchy, _EUCLIDEAN_METHODS, _LINKAGE_METHODS) from scipy.cluster._hierarchy import Heap from scipy.spatial.distance import pdist -from scipy._lib._array_api import eager_warns, xp_assert_close, xp_assert_equal +from scipy._lib._array_api import (eager_warns, make_xp_test_case, + xp_assert_close, xp_assert_equal) import scipy._lib.array_api_extra as xpx -from scipy._lib.array_api_extra.testing import lazy_xp_function from threading import Lock @@ -74,40 +74,9 @@ class eager: have_matplotlib = False skip_xp_backends = pytest.mark.skip_xp_backends -xfail_xp_backends = pytest.mark.xfail_xp_backends - -lazy_xp_function(single) -lazy_xp_function(ward) -lazy_xp_function(linkage) -lazy_xp_function(cut_tree) -lazy_xp_function(to_tree, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(optimal_leaf_ordering) -lazy_xp_function(cophenet) -lazy_xp_function(inconsistent) -lazy_xp_function(from_mlab_linkage) -lazy_xp_function(to_mlab_linkage) -lazy_xp_function(is_monotonic) - -# Note: these functions materialize lazy arrays when warning=True or throw=True -lazy_xp_function(is_valid_im) -lazy_xp_function(is_valid_linkage) - -lazy_xp_function(num_obs_linkage) -lazy_xp_function(correspond) -lazy_xp_function(fcluster, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(fclusterdata, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(leaves_list) -lazy_xp_function(dendrogram, jax_jit=False, allow_dask_compute=True) -lazy_xp_function(is_isomorphic) -lazy_xp_function(maxdists) -lazy_xp_function(maxinconsts) -lazy_xp_function(maxRstat) - -# Returns data-dependent shape -lazy_xp_function(leaders, jax_jit=False) - - -@skip_xp_backends(cpu_only=True, reason="linkage() invokes Cython code") + + +@make_xp_test_case(linkage) class TestLinkage: @skip_xp_backends("jax.numpy", reason="Can't raise inside jax.pure_callback") @@ -210,7 +179,7 @@ def test_centroid_neg_distance(self, xp): linkage(values, method='centroid') -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(inconsistent) class TestInconsistent: def test_inconsistent_tdist(self, xp): @@ -223,7 +192,7 @@ def check_inconsistent_tdist(self, depth, xp): xp.asarray(hierarchy_test_data.inconsistent_ytdist[depth])) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(cophenet) class TestCopheneticDistance: def test_linkage_cophenet_tdist_Z(self, xp): @@ -265,6 +234,7 @@ def test_gh_22183(self, xp): cophenet(xp.asarray(arr)) +@make_xp_test_case(from_mlab_linkage, to_mlab_linkage) class TestMLabLinkageConversion: def test_mlab_linkage_conversion_empty(self, xp): @@ -273,7 +243,6 @@ def test_mlab_linkage_conversion_empty(self, xp): xp_assert_equal(from_mlab_linkage(X), X) xp_assert_equal(to_mlab_linkage(X), X) - @skip_xp_backends(cpu_only=True) def test_mlab_linkage_conversion_single_row(self, xp): # Tests from/to_mlab_linkage on linkage array with single row. Z = xp.asarray([[0., 1., 3., 2.]]) @@ -283,7 +252,6 @@ def test_mlab_linkage_conversion_single_row(self, xp): xp_assert_close(to_mlab_linkage(Z), xp.asarray(Zm, dtype=xp.float64), rtol=1e-15) - @skip_xp_backends(cpu_only=True) def test_mlab_linkage_conversion_multiple_rows(self, xp): # Tests from/to_mlab_linkage on linkage array with multiple rows. Zm = xp.asarray([[3, 6, 138], [4, 5, 219], @@ -299,9 +267,10 @@ def test_mlab_linkage_conversion_multiple_rows(self, xp): rtol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(fclusterdata) class TestFclusterData: + @make_xp_test_case(is_isomorphic) @pytest.mark.parametrize("criterion,t", [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + [("distance", t) for t in hierarchy_test_data.fcluster_distance] @@ -315,9 +284,10 @@ def test_fclusterdata(self, t, criterion, xp): assert is_isomorphic(T, expectedT) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(fcluster) class TestFcluster: + @make_xp_test_case(single, is_isomorphic) @pytest.mark.parametrize("criterion,t", [("inconsistent", t) for t in hierarchy_test_data.fcluster_inconsistent] + [("distance", t) for t in hierarchy_test_data.fcluster_distance] @@ -330,6 +300,7 @@ def test_fcluster(self, t, criterion, xp): T = fcluster(Z, criterion=criterion, t=t) assert_(is_isomorphic(T, expectedT)) + @make_xp_test_case(single, is_isomorphic, maxdists) @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_distance) def test_fcluster_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_distance[t]) @@ -337,6 +308,7 @@ def test_fcluster_monocrit(self, t, xp): T = fcluster(Z, t, criterion='monocrit', monocrit=maxdists(Z)) assert_(is_isomorphic(T, expectedT)) + @make_xp_test_case(single, is_isomorphic, maxdists) @pytest.mark.parametrize("t", hierarchy_test_data.fcluster_maxclust) def test_fcluster_maxclust_monocrit(self, t, xp): expectedT = xp.asarray(hierarchy_test_data.fcluster_maxclust[t]) @@ -344,6 +316,7 @@ def test_fcluster_maxclust_monocrit(self, t, xp): T = fcluster(Z, t, criterion='maxclust_monocrit', monocrit=maxdists(Z)) assert_(is_isomorphic(T, expectedT)) + @make_xp_test_case(single) def test_fcluster_maxclust_gh_12651(self, xp): y = xp.asarray([[1], [4], [5]]) Z = single(y) @@ -357,7 +330,7 @@ def test_fcluster_maxclust_gh_12651(self, xp): xp.asarray([1, 2, 3])) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(leaders) class TestLeaders: def test_leaders_single(self, xp): @@ -373,7 +346,7 @@ def test_leaders_single(self, xp): xp_assert_close(xp.concat(L), expect, rtol=1e-15) -@skip_xp_backends(cpu_only=True, reason='pure-Python algorithm') +@make_xp_test_case(is_isomorphic) class TestIsIsomorphic: def test_array_like(self): @@ -460,6 +433,7 @@ def is_isomorphic_randperm(self, nobs, nclusters, noniso=False, nerrors=0, *, xp assert is_isomorphic(b, a) == (not noniso) +@make_xp_test_case(is_valid_linkage) class TestIsValidLinkage: @pytest.mark.parametrize("nrow, ncol, valid", [(2, 5, False), (2, 3, False), @@ -544,6 +518,7 @@ def test_is_valid_linkage_4_and_up_neg_counts(self, xp): eager.is_valid_linkage(Z, throw=True) +@make_xp_test_case(is_valid_im) class TestIsValidInconsistent: def test_is_valid_im_int_type(self, xp): @@ -655,7 +630,7 @@ def test_num_obs_linkage_multi_matrix(self, xp): assert num_obs_linkage(Z) == n -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(leaves_list, to_tree) class TestLeavesList: def test_leaves_list_1x4(self, xp): @@ -690,6 +665,7 @@ def test_Q_subtree_pre_order(self, xp): rtol=1e-15) +@make_xp_test_case(correspond) class TestCorrespond: def test_correspond_empty(self, xp): @@ -741,6 +717,7 @@ def test_correspond_4_and_up_2(self, xp): assert not correspond(Z2, y) +@make_xp_test_case(is_monotonic) class TestIsMonotonic: def test_is_monotonic_empty(self, xp): @@ -814,7 +791,7 @@ def test_is_monotonic_Q_linkage(self, xp): assert is_monotonic(Z) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(maxdists) class TestMaxDists: def test_maxdists_empty_linkage(self, xp): @@ -840,7 +817,7 @@ def test_maxdists_Q_linkage(self, method, xp): xp_assert_close(MD, expectedMD, atol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(maxinconsts) class TestMaxInconsts: def test_maxinconsts_empty_linkage(self, xp): @@ -878,7 +855,7 @@ def test_maxinconsts_Q_linkage(self, method, xp): xp_assert_close(MD, expectedMD, atol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(maxRstat) class TestMaxRStat: def test_maxRstat_invalid_index(self, xp): @@ -929,7 +906,7 @@ def test_maxRstat_Q_linkage(self, method, xp): xp_assert_close(MD, expectedMD, atol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(dendrogram) class TestDendrogram: def test_dendrogram_single_linkage_tdist(self, xp): @@ -1172,7 +1149,7 @@ def calculate_maximum_inconsistencies(Z, R, k=3, xp=np): return B -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(to_tree) def test_node_compare(xp): np.random.seed(23) nobs = 50 @@ -1185,7 +1162,7 @@ def test_node_compare(xp): assert_(tree.get_right() != tree.get_left()) -@skip_xp_backends(np_only=True, reason='`cut_tree` uses non-standard indexing') +@make_xp_test_case(cut_tree) def test_cut_tree(xp): np.random.seed(23) nobs = 50 @@ -1213,7 +1190,7 @@ def test_cut_tree(xp): cut_tree(Z, height=[10, 5]), rtol=1e-15) -@skip_xp_backends(cpu_only=True) +@make_xp_test_case(optimal_leaf_ordering) def test_optimal_leaf_ordering(xp): # test with the distance vector y Z = optimal_leaf_ordering(xp.asarray(linkage(hierarchy_test_data.ytdist)), From ff67bc1ad9b5ab3ec0d6ad86aefcb48a13ac240c Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 16 May 2025 18:56:50 +0100 Subject: [PATCH 213/251] ENH/DOC/TST: `cluster.vq`: use `xp_capabilities` --- scipy/cluster/tests/test_vq.py | 85 ++++++++++++++++------------------ scipy/cluster/vq.py | 44 ++++++++++-------- 2 files changed, 64 insertions(+), 65 deletions(-) diff --git a/scipy/cluster/tests/test_vq.py b/scipy/cluster/tests/test_vq.py index ee5bc567fef1..c368aa367569 100644 --- a/scipy/cluster/tests/test_vq.py +++ b/scipy/cluster/tests/test_vq.py @@ -1,12 +1,10 @@ -import warnings +import math import sys from copy import deepcopy from threading import Lock import numpy as np -from numpy.testing import ( - assert_array_equal, assert_equal, assert_, suppress_warnings -) +from numpy.testing import assert_array_equal, suppress_warnings import pytest from pytest import raises as assert_raises @@ -17,7 +15,8 @@ from scipy._lib import array_api_extra as xpx from scipy._lib._array_api import ( - SCIPY_ARRAY_API, xp_copy, xp_assert_close, xp_assert_equal + SCIPY_ARRAY_API, eager_warns, is_lazy_array, make_xp_test_case, + xp_copy, xp_assert_close, xp_assert_equal ) xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -79,6 +78,7 @@ LABEL1 = np.array([0, 1, 2, 2, 2, 2, 1, 2, 1, 1, 1]) +@make_xp_test_case(whiten) class TestWhiten: def test_whiten(self, xp): @@ -95,11 +95,7 @@ def test_whiten(self, xp): [0.45067590, 0.45464607]]) xp_assert_close(whiten(obs), desired, rtol=1e-5) - @pytest.fixture - def whiten_lock(self): - return Lock() - - def test_whiten_zero_std(self, xp, whiten_lock): + def test_whiten_zero_std(self, xp): desired = xp.asarray([[0., 1.0, 2.86666544], [0., 1.0, 1.32460034], [0., 1.0, 3.74382172]]) @@ -108,27 +104,32 @@ def test_whiten_zero_std(self, xp, whiten_lock): [0., 1., 0.34243798], [0., 1., 0.96785929]]) - with whiten_lock: - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - - xp_assert_close(whiten(obs), desired, rtol=1e-5) + with eager_warns(obs, RuntimeWarning, match="standard deviation zero"): + actual = whiten(obs) + xp_assert_close(actual, desired, rtol=1e-5) - assert_equal(len(w), 1) - assert_(issubclass(w[-1].category, RuntimeWarning)) + @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask") + @pytest.mark.parametrize("bad_value", [math.nan, math.inf, -math.inf]) + def test_whiten_not_finite(self, bad_value, xp): + obs = xp.asarray([[0.98744510, bad_value], + [0.62093317, 0.19406729], + [0.87545741, 0.00735733], + [0.85124403, 0.26499712], + [0.45067590, 0.45464607]]) - def test_whiten_not_finite(self, xp): - for bad_value in xp.nan, xp.inf, -xp.inf: - obs = xp.asarray([[0.98744510, bad_value], - [0.62093317, 0.19406729], - [0.87545741, 0.00735733], - [0.85124403, 0.26499712], - [0.45067590, 0.45464607]]) + if is_lazy_array(obs): + desired = xp.asarray([[5.08738849, math.nan], + [3.19909255, math.nan], + [4.51041982, math.nan], + [4.38567074, math.nan], + [2.32191480, math.nan]]) + xp_assert_close(whiten(obs), desired, rtol=1e-5) + else: assert_raises(ValueError, whiten, obs) @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_whiten_not_finite_matrix(self, xp): + def test_whiten_not_finite_matrix(self): for bad_value in np.nan, np.inf, -np.inf: obs = matrix([[0.98744510, bad_value], [0.62093317, 0.19406729], @@ -138,9 +139,9 @@ def test_whiten_not_finite_matrix(self, xp): assert_raises(ValueError, whiten, obs) +@make_xp_test_case(vq) class TestVq: - @skip_xp_backends(cpu_only=True) def test_py_vq(self, xp): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) # label1.dtype varies between int32 and int64 over platforms @@ -150,28 +151,26 @@ def test_py_vq(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_py_vq_matrix(self, xp): + def test_py_vq_matrix(self): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) # label1.dtype varies between int32 and int64 over platforms label1 = py_vq(matrix(X), matrix(initc))[0] assert_array_equal(label1, LABEL1) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') def test_vq(self, xp): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) - label1, _ = _vq.vq(xp.asarray(X), xp.asarray(initc)) + label1, _ = _vq.vq(X, initc) assert_array_equal(label1, LABEL1) _, _ = vq(xp.asarray(X), xp.asarray(initc)) @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_vq_matrix(self, xp): + def test_vq_matrix(self): initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) label1, _ = _vq.vq(matrix(X), matrix(initc)) assert_array_equal(label1, LABEL1) _, _ = vq(matrix(X), matrix(initc)) - @skip_xp_backends(cpu_only=True) def test_vq_1d(self, xp): # Test special rank 1 vq algo, python implementation. data = X[:, 0] @@ -184,18 +183,15 @@ def test_vq_1d(self, xp): xp_assert_equal(ta, xp.asarray(a, dtype=xp.int64), check_dtype=False) xp_assert_equal(tb, xp.asarray(b)) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') - def test__vq_sametype(self, xp): - a = xp.asarray([1.0, 2.0], dtype=xp.float64) - b = a.astype(xp.float32) + def test__vq_sametype(self): + a = np.asarray([1.0, 2.0]) + b = a.astype(np.float32) assert_raises(TypeError, _vq.vq, a, b) - @skip_xp_backends(np_only=True, reason='`_vq` only supports NumPy backend') - def test__vq_invalid_type(self, xp): - a = xp.asarray([1, 2], dtype=int) + def test__vq_invalid_type(self): + a = np.asarray([1, 2], dtype=int) assert_raises(TypeError, _vq.vq, a, a) - @skip_xp_backends(cpu_only=True) def test_vq_large_nfeat(self, xp): X = np.random.rand(20, 20) code_book = np.random.rand(3, 20) @@ -219,7 +215,6 @@ def test_vq_large_nfeat(self, xp): # codes1.dtype varies between int32 and int64 over platforms xp_assert_equal(codes1, xp.asarray(codes0, dtype=xp.int64), check_dtype=False) - @skip_xp_backends(cpu_only=True) def test_vq_large_features(self, xp): X = np.random.rand(10, 5) * 1000000 code_book = np.random.rand(2, 5) * 1000000 @@ -235,8 +230,8 @@ def test_vq_large_features(self, xp): # Whole class skipped on GPU for now; # once pdist/cdist are hooked up for CuPy, more tests will work -@skip_xp_backends(cpu_only=True) -class TestKMean: +@make_xp_test_case(kmeans, kmeans2) +class TestKMeans: def test_large_features(self, xp): # Generate a data set with large values, and run kmeans on it to @@ -264,7 +259,7 @@ def test_kmeans_simple(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_kmeans_simple_matrix(self, xp): + def test_kmeans_simple_matrix(self): rng = np.random.default_rng(54321) initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) code1 = kmeans(matrix(X), matrix(initc), iter=1, rng=rng)[0] @@ -299,9 +294,9 @@ def test_kmeans2_simple(self, xp): @pytest.mark.skipif(SCIPY_ARRAY_API, reason='`np.matrix` unsupported in array API mode') - def test_kmeans2_simple_matrix(self, xp): + def test_kmeans2_simple_matrix(self): rng = np.random.default_rng(12345678) - initc = xp.asarray(np.concatenate([[X[0]], [X[1]], [X[2]]])) + initc = np.concatenate([[X[0]], [X[1]], [X[2]]]) code1 = kmeans2(matrix(X), matrix(initc), iter=1, rng=rng)[0] code2 = kmeans2(matrix(X), matrix(initc), iter=2, rng=rng)[0] diff --git a/scipy/cluster/vq.py b/scipy/cluster/vq.py index 8fba4b2e032d..65debbbde3ab 100644 --- a/scipy/cluster/vq.py +++ b/scipy/cluster/vq.py @@ -67,9 +67,8 @@ import warnings import numpy as np from collections import deque -from scipy._lib._array_api import ( - _asarray, array_namespace, xp_size, xp_copy -) +from scipy._lib._array_api import (_asarray, array_namespace, is_lazy_array, + xp_capabilities, xp_copy, xp_size) from scipy._lib._util import (check_random_state, rng_integers, _transition_to_rng) from scipy._lib import array_api_extra as xpx @@ -86,7 +85,8 @@ class ClusterError(Exception): pass -def whiten(obs, check_finite=True): +@xp_capabilities() +def whiten(obs, check_finite=None): """ Normalize a group of observations on a per feature basis. @@ -100,19 +100,19 @@ def whiten(obs, check_finite=True): ---------- obs : ndarray Each row of the array is an observation. The - columns are the features seen during each observation. + columns are the features seen during each observation:: - >>> # f0 f1 f2 - >>> obs = [[ 1., 1., 1.], #o0 - ... [ 2., 2., 2.], #o1 - ... [ 3., 3., 3.], #o2 - ... [ 4., 4., 4.]] #o3 + # f0 f1 f2 + obs = [[ 1., 1., 1.], #o0 + [ 2., 2., 2.], #o1 + [ 3., 3., 3.], #o2 + [ 4., 4., 4.]] #o3 check_finite : bool, optional Whether to check that the input matrices contain only finite numbers. Disabling may give a performance gain, but may result in problems (crashes, non-termination) if the inputs do contain infinities or NaNs. - Default: True + Default: True for eager backends and False for lazy ones. Returns ------- @@ -134,6 +134,8 @@ def whiten(obs, check_finite=True): """ xp = array_namespace(obs) + if check_finite is None: + check_finite = not is_lazy_array(obs) obs = _asarray(obs, check_finite=check_finite, xp=xp) std_dev = xp.std(obs, axis=0) zero_std_mask = std_dev == 0 @@ -145,6 +147,8 @@ def whiten(obs, check_finite=True): return obs / std_dev +@xp_capabilities(cpu_only=True, reason="uses spatial.distance.cdist", + jax_jit=False, allow_dask_compute=True) def vq(obs, code_book, check_finite=True): """ Assign codes from a code book to observations. @@ -168,13 +172,12 @@ def vq(obs, code_book, check_finite=True): code_book : ndarray The code book is usually generated using the k-means algorithm. Each row of the array holds a different code, and the columns are - the features of the code. + the features of the code:: - >>> # f0 f1 f2 f3 - >>> code_book = [ - ... [ 1., 2., 3., 4.], #c0 - ... [ 1., 2., 3., 4.], #c1 - ... [ 1., 2., 3., 4.]] #c2 + # f0 f1 f2 f3 + code_book = [[ 1., 2., 3., 4.], #c0 + [ 1., 2., 3., 4.], #c1 + [ 1., 2., 3., 4.]] #c2 check_finite : bool, optional Whether to check that the input matrices contain only finite numbers. @@ -208,10 +211,9 @@ def vq(obs, code_book, check_finite=True): code_book = _asarray(code_book, xp=xp, check_finite=check_finite) ct = xp.result_type(obs, code_book) - c_obs = xp.astype(obs, ct, copy=False) - c_code_book = xp.astype(code_book, ct, copy=False) - if xp.isdtype(ct, kind='real floating'): + c_obs = xp.astype(obs, ct, copy=False) + c_code_book = xp.astype(code_book, ct, copy=False) c_obs = np.asarray(c_obs) c_code_book = np.asarray(c_code_book) result = _vq.vq(c_obs, c_code_book) @@ -327,6 +329,7 @@ def _kmeans(obs, guess, thresh=1e-5, xp=None): return code_book, prev_avg_dists[1] +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) @_transition_to_rng("seed") def kmeans(obs, k_or_guess, iter=20, thresh=1e-5, check_finite=True, *, rng=None): @@ -642,6 +645,7 @@ def _missing_raise(): _valid_miss_meth = {'warn': _missing_warn, 'raise': _missing_raise} +@xp_capabilities(cpu_only=True, jax_jit=False, allow_dask_compute=True) @_transition_to_rng("seed") def kmeans2(data, k, iter=10, thresh=1e-5, minit='random', missing='warn', check_finite=True, *, rng=None): From f3a12885661b23136535df79d2f9138037378d60 Mon Sep 17 00:00:00 2001 From: Dan Schult Date: Sat, 17 May 2025 01:45:21 -0400 Subject: [PATCH 214/251] correct return of sum/mean and add tests of sum/mean/min/max/argmin/argmax --- scipy/sparse/_base.py | 2 +- scipy/sparse/tests/test_base.py | 49 ++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/scipy/sparse/_base.py b/scipy/sparse/_base.py index a8060fa4860a..08b193583346 100644 --- a/scipy/sparse/_base.py +++ b/scipy/sparse/_base.py @@ -1343,7 +1343,7 @@ def sum(self, axis=None, dtype=None, out=None): if out is None: # create out array with desired dtype - out = np.zeros(new_shape, dtype=dtype or res_dtype) + out = self._ascontainer(np.zeros(new_shape, dtype=dtype or res_dtype)) else: if out.shape != new_shape: raise ValueError("out dimensions do not match shape") diff --git a/scipy/sparse/tests/test_base.py b/scipy/sparse/tests/test_base.py index 1ad0e51656d7..f79980390a59 100644 --- a/scipy/sparse/tests/test_base.py +++ b/scipy/sparse/tests/test_base.py @@ -39,7 +39,7 @@ class for generic tests" section. coo_matrix, lil_matrix, dia_matrix, bsr_matrix, csc_array, csr_array, dok_array, coo_array, lil_array, dia_array, bsr_array, - eye, issparse, SparseEfficiencyWarning, sparray) + eye, issparse, SparseEfficiencyWarning, sparray, spmatrix) from scipy.sparse._base import _formats from scipy.sparse._sputils import (supported_dtypes, isscalarlike, get_index_dtype, asmatrix, matrix) @@ -1113,6 +1113,21 @@ def test_numpy_sum(self): assert_array_almost_equal(dat_sum, datsp_sum) assert_equal(dat_sum.dtype, datsp_sum.dtype) + def test_sum_mean_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + + assert isscalarlike(datsp.sum()) + matrix_or_array = ndarray if self.is_array_test else np.matrix + assert isinstance(datsp.sum(axis=0), matrix_or_array) + assert isinstance(datsp.sum(axis=1), matrix_or_array) + + assert isscalarlike(datsp.mean()) + assert isinstance(datsp.mean(axis=0), matrix_or_array) + assert isinstance(datsp.mean(axis=1), matrix_or_array) + def test_mean(self): keep = not self.is_array_test def check(dtype): @@ -3855,6 +3870,38 @@ def test_minmax_axis(self): assert_equal(X.max(axis=axis, explicit=ex).toarray(), D.max(axis=axis)) assert_equal(X.min(axis=axis, explicit=ex).toarray(), D.min(axis=axis)) + def test_minmax_container_type(self): + dat = array([[0, 1, 2], + [3, -4, 5], + [-6, 7, 9]]) + datsp = self.spcreator(dat) + matrix_or_array = ndarray if self.is_array_test else np.matrix + spmatrix_or_sparray = sparray if self.is_array_test else spmatrix + + assert isscalarlike(datsp.min()) + assert isinstance(datsp.min(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.min(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.max()) + assert isinstance(datsp.max(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.max(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmin()) + assert isinstance(datsp.nanmin(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmin(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.nanmax()) + assert isinstance(datsp.nanmax(axis=0), spmatrix_or_sparray) + assert isinstance(datsp.nanmax(axis=1), spmatrix_or_sparray) + + assert isscalarlike(datsp.argmin()) + assert isinstance(datsp.argmin(axis=0), matrix_or_array) + assert isinstance(datsp.argmin(axis=1), matrix_or_array) + + assert isscalarlike(datsp.argmax()) + assert isinstance(datsp.argmax(axis=0), matrix_or_array) + assert isinstance(datsp.argmax(axis=1), matrix_or_array) + def test_nanminmax(self): D = self.asdense(np.arange(50).reshape(5,10), dtype=float) D[1, :] = 0 From 0762fee6adc973ddf07c4e7fe214f9733823e783 Mon Sep 17 00:00:00 2001 From: Andrew Nelson Date: Sat, 17 May 2025 19:41:31 +1000 Subject: [PATCH 215/251] CI: address some zizmor security alerts (#22967) See gh-22966. --- .github/workflows/circle_artifacts.yml | 4 ++++ .github/workflows/commit_message.yml | 3 ++- .github/workflows/linux.yml | 2 ++ .github/workflows/macos.yml | 1 - .github/workflows/wheels.yml | 2 +- .github/workflows/windows.yml | 2 ++ 6 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/circle_artifacts.yml b/.github/workflows/circle_artifacts.yml index b51dac868fdf..9f6df27e8e15 100644 --- a/.github/workflows/circle_artifacts.yml +++ b/.github/workflows/circle_artifacts.yml @@ -1,6 +1,10 @@ name: Redirect circleci artifacts on: [status] + +permissions: + contents: read # to fetch code (actions/checkout) + jobs: circleci_artifacts_redirector_job: runs-on: ubuntu-22.04 diff --git a/.github/workflows/commit_message.yml b/.github/workflows/commit_message.yml index 05a3ad9dc02f..24026f1fb301 100644 --- a/.github/workflows/commit_message.yml +++ b/.github/workflows/commit_message.yml @@ -23,6 +23,7 @@ jobs: # Gets the correct commit message for pull request with: ref: ${{ github.event.pull_request.head.sha }} + - name: Check for skips id: skip_check # the lint workflow is not currently skipped with [docs only]. @@ -40,4 +41,4 @@ jobs: fi fi echo "message=$RUN" >> $GITHUB_OUTPUT - echo github.ref ${{ github.ref }} + echo github.ref $GITHUB_REF diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index af4a57b9eac8..ab3d8dac2b07 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -213,6 +213,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Configuring Test Environment run: | sudo apt-get update @@ -467,6 +468,7 @@ jobs: with: submodules: recursive fetch-tags: true + - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.13t' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 4e7c78a8f832..4b23c3b57ae1 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -157,7 +157,6 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive - - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8c8c18f80661..194aaf5fb06b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -46,6 +46,7 @@ jobs: # Gets the correct commit message for pull request with: ref: ${{ github.event.pull_request.head.sha }} + - name: Get commit message id: commit_message run: | @@ -56,7 +57,6 @@ jobs: RUN="1" fi echo "message=$RUN" >> $GITHUB_OUTPUT - echo github.ref ${{ github.ref }} build_wheels: name: Wheel, ${{ matrix.python[0] }}-${{ matrix.buildplat[1] }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3ef04e4313c1..19b848c047b8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,6 +35,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: @@ -76,6 +77,7 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive + - name: Setup Python uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: From f45ba7bd311ee60c5c2f74c272cd3cd6c1dece86 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 10:12:59 +0200 Subject: [PATCH 216/251] MAINT: signal: array API compat in several functions: - envelope - resample - resample_poly This completes the conversion of the 'filtering' rubic of scipy.signal (https://docs.scipy.org/doc/scipy/reference/signal.html#filtering) While at it, convert - remez - firwin - firwin2 - firls - minimum_phase This completes tests/test_fir_filter_design.py Co-authored-by: Dietrich Brunn --- scipy/signal/_fir_filter_design.py | 187 +++++---- scipy/signal/_signaltools.py | 64 +-- scipy/signal/tests/test_fir_filter_design.py | 401 ++++++++++++------- scipy/signal/tests/test_signaltools.py | 349 ++++++++++------ 4 files changed, 622 insertions(+), 379 deletions(-) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 981739e12418..0e81cc0fdc57 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -1,19 +1,20 @@ """Functions for FIR filter design.""" -from math import ceil, log -import operator +from math import ceil, log, log2 import warnings from typing import Literal import numpy as np -from numpy.fft import irfft, fft, ifft -from scipy.special import sinc +from scipy.fft import irfft, fft, ifft from scipy.linalg import (toeplitz, hankel, solve, LinAlgError, LinAlgWarning, lstsq) from scipy.signal._arraytools import _validate_fs from . import _sigtools +from scipy._lib._array_api import array_namespace, xp_size, xp_default_dtype +import scipy._lib.array_api_extra as xpx + __all__ = ['kaiser_beta', 'kaiser_atten', 'kaiserord', 'firwin', 'firwin2', 'firwin_2d', 'remez', 'firls', 'minimum_phase'] @@ -372,6 +373,9 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, array([ 0.04890915, 0.91284326, 0.04890915]) """ + # NB: scipy's version of array_namespace returns `np_compat` for int or floats + xp = array_namespace(cutoff) + # The major enhancements to this function added in November 2010 were # developed by Tom Krauss (see ticket #902). fs = _validate_fs(fs, allow_none=True) @@ -379,18 +383,19 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, nyq = 0.5 * fs - cutoff = np.atleast_1d(cutoff) / float(nyq) + cutoff = xp.asarray(cutoff, dtype=xp.float64) + cutoff = xpx.atleast_nd(cutoff, ndim=1) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: raise ValueError("The cutoff argument must be at most " "one-dimensional.") - if cutoff.size == 0: + if xp_size(cutoff) == 0: raise ValueError("At least one cutoff frequency must be given.") - if cutoff.min() <= 0 or cutoff.max() >= 1: + if xp.min(cutoff) <= 0 or xp.max(cutoff) >= 1: raise ValueError("Invalid cutoff frequency: frequencies must be " "greater than 0 and less than fs/2.") - if np.any(np.diff(cutoff) <= 0): + if xp.any(cutoff[1:] - cutoff[:-1] <= 0): raise ValueError("Invalid cutoff frequencies: the frequencies " "must be strictly increasing.") @@ -401,51 +406,49 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, beta = kaiser_beta(atten) window = ('kaiser', beta) - if isinstance(pass_zero, str): - if pass_zero in ('bandstop', 'lowpass'): - if pass_zero == 'lowpass': - if cutoff.size != 1: - raise ValueError('cutoff must have one element if ' - f'pass_zero=="lowpass", got {cutoff.shape}') - elif cutoff.size <= 1: - raise ValueError('cutoff must have at least two elements if ' - f'pass_zero=="bandstop", got {cutoff.shape}') - pass_zero = True - elif pass_zero in ('bandpass', 'highpass'): - if pass_zero == 'highpass': - if cutoff.size != 1: - raise ValueError('cutoff must have one element if ' - f'pass_zero=="highpass", got {cutoff.shape}') - elif cutoff.size <= 1: - raise ValueError('cutoff must have at least two elements if ' - f'pass_zero=="bandpass", got {cutoff.shape}') - pass_zero = False - else: - raise ValueError('pass_zero must be True, False, "bandpass", ' - '"lowpass", "highpass", or "bandstop", got ' - f'{pass_zero}') - pass_zero = bool(operator.index(pass_zero)) # ensure bool-like - - pass_nyquist = bool(cutoff.size & 1) ^ pass_zero + if pass_zero in ('bandstop', 'lowpass'): + if pass_zero == 'lowpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="lowpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandstop", got {cutoff.shape}') + pass_zero = True + elif pass_zero in ('bandpass', 'highpass'): + if pass_zero == 'highpass': + if xp_size(cutoff) != 1: + raise ValueError('cutoff must have one element if ' + f'pass_zero=="highpass", got {cutoff.shape}') + elif xp_size(cutoff) <= 1: + raise ValueError('cutoff must have at least two elements if ' + f'pass_zero=="bandpass", got {cutoff.shape}') + pass_zero = False + elif not (pass_zero is True or pass_zero is False): + raise ValueError(f"Parameter {pass_zero=} not in (True, False, 'bandpass', " + + "'lowpass', 'highpass', 'bandstop')") + + pass_nyquist = (xp_size(cutoff) % 2 == 0) == pass_zero if pass_nyquist and numtaps % 2 == 0: raise ValueError("A filter with an even number of coefficients must " "have zero response at the Nyquist frequency.") # Insert 0 and/or 1 at the ends of cutoff so that the length of cutoff # is even, and each pair in cutoff corresponds to passband. - cutoff = np.hstack(([0.0] * pass_zero, cutoff, [1.0] * pass_nyquist)) + cutoff = xp.concat((xp.zeros(int(pass_zero)), cutoff, xp.ones(int(pass_nyquist)))) + # `bands` is a 2-D array; each row gives the left and right edges of # a passband. - bands = cutoff.reshape(-1, 2) + bands = xp.reshape(cutoff, (-1, 2)) # Build up the coefficients. alpha = 0.5 * (numtaps - 1) - m = np.arange(0, numtaps) - alpha + m = xp.arange(0, numtaps) - alpha h = 0 for left, right in bands: - h += right * sinc(right * m) - h -= left * sinc(left * m) + h += right * xpx.sinc(right * m, xp=xp) + h -= left * xpx.sinc(left * m, xp=xp) # Get and apply the window function. from .windows import get_window @@ -462,7 +465,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, scale_frequency = 1.0 else: scale_frequency = 0.5 * (left + right) - c = np.cos(np.pi * m * scale_frequency) + c = xp.cos(xp.pi * m * scale_frequency) s = np.sum(h * c) h /= s @@ -573,11 +576,14 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', [-0.02286961 -0.06362756 0.57310236 0.57310236 -0.06362756 -0.02286961] """ + xp = array_namespace(freq, gain) + freq, gain = xp.asarray(freq), xp.asarray(gain) + fs = _validate_fs(fs, allow_none=True) fs = 2 if fs is None else fs nyq = 0.5 * fs - if len(freq) != len(gain): + if freq.shape[0] != gain.shape[0]: raise ValueError('freq and gain must be of same length.') if nfreqs is not None and numtaps >= nfreqs: @@ -588,11 +594,11 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if freq[0] != 0 or freq[-1] != nyq: raise ValueError('freq must start with 0 and end with fs/2.') - d = np.diff(freq) - if (d < 0).any(): + d = freq[1:] - freq[:-1] + if xp.any(d < 0): raise ValueError('The values in freq must be nondecreasing.') d2 = d[:-1] + d[1:] - if (d2 == 0).any(): + if xp.any(d2 == 0): raise ValueError('A value in freq must not occur more than twice.') if freq[1] == 0: raise ValueError('Value 0 must not be repeated in freq') @@ -623,28 +629,30 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if nfreqs is None: nfreqs = 1 + 2 ** int(ceil(log(numtaps, 2))) - if (d == 0).any(): + if xp.any(d == 0): # Tweak any repeated values in freq so that interp works. - freq = np.array(freq, copy=True) - eps = np.finfo(float).eps * nyq - for k in range(len(freq) - 1): + freq = xp.asarray(freq, copy=True) + eps = xp.finfo(xp_default_dtype(xp)).eps * nyq + for k in range(freq.shape[0] - 1): if freq[k] == freq[k + 1]: freq[k] = freq[k] - eps freq[k + 1] = freq[k + 1] + eps # Check if freq is strictly increasing after tweak - d = np.diff(freq) - if (d <= 0).any(): + d = freq[1:] - freq[:-1] + if xp.any(d <= 0): raise ValueError("freq cannot contain numbers that are too close " "(within eps * (fs/2): " f"{eps}) to a repeated value") # Linearly interpolate the desired response on a uniform mesh `x`. x = np.linspace(0.0, nyq, nfreqs) - fx = np.interp(x, freq, gain) + fx = np.interp(x, np.asarray(freq), np.asarray(gain)) # XXX array-api-extra#193 + x = xp.asarray(x) + fx = xp.asarray(fx) # Adjust the phases of the coefficients so that the first `ntaps` of the # inverse FFT are the desired filter coefficients. - shift = np.exp(-(numtaps - 1) / 2. * 1.j * np.pi * x / nyq) + shift = xp.exp(-(numtaps - 1) / 2. * 1j * xp.pi * x / nyq) if ftype > 2: shift *= 1j @@ -656,7 +664,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', if window is not None: # Create the window to apply to the filter coefficients. from .windows import get_window - wind = get_window(window, numtaps, fftbins=False) + wind = get_window(window, numtaps, fftbins=False, xp=xp) else: wind = 1 @@ -665,7 +673,7 @@ def firwin2(numtaps, freq, gain, *, nfreqs=None, window='hamming', out = out_full[:numtaps] * wind if ftype == 3: - out[out.size // 2] = 0.0 + out[xp_size(out) // 2] = 0.0 return out @@ -822,6 +830,12 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass', >>> plt.show() """ + xp = array_namespace(bands, desired, weight) + bands = np.asarray(bands) + desired = np.asarray(desired) + if weight: + weight = np.asarray(weight) + fs = _validate_fs(fs, allow_none=True) fs = 1.0 if fs is None else fs @@ -837,8 +851,9 @@ def remez(numtaps, bands, desired, *, weight=None, type='bandpass', weight = [1] * len(desired) bands = np.asarray(bands).copy() - return _sigtools._remez(numtaps, bands, desired, weight, tnum, fs, - maxiter, grid_density) + result = _sigtools._remez(numtaps, bands, desired, weight, tnum, fs, + maxiter, grid_density) + return xp.asarray(result) def firls(numtaps, bands, desired, *, weight=None, fs=None): @@ -951,6 +966,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): >>> plt.show() """ + xp = array_namespace(bands, desired) + bands = np.asarray(bands) + desired = np.asarray(desired) + fs = _validate_fs(fs, allow_none=True) fs = 2 if fs is None else fs nyq = 0.5 * fs @@ -1054,10 +1073,10 @@ def firls(numtaps, bands, desired, *, weight=None, fs=None): # make coefficients symmetric (linear phase) coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:])) - return coeffs + return xp.asarray(coeffs) -def _dhtm(mag): +def _dhtm(mag, xp): """Compute the modified 1-D discrete Hilbert transform Parameters @@ -1068,20 +1087,20 @@ def _dhtm(mag): """ # Adapted based on code by Niranjan Damera-Venkata, # Brian L. Evans and Shawn R. McCaslin (see refs for `minimum_phase`) - sig = np.zeros(len(mag)) + sig = xp.zeros(mag.shape[0]) # Leave Nyquist and DC at 0, knowing np.abs(fftfreq(N)[midpt]) == 0.5 - midpt = len(mag) // 2 + midpt = mag.shape[0] // 2 sig[1:midpt] = 1 sig[midpt+1:] = -1 # eventually if we want to support complex filters, we will need a # np.abs() on the mag inside the log, and should remove the .real - recon = ifft(mag * np.exp(fft(sig * ifft(np.log(mag))))).real + recon = xp.real(ifft(mag * xp.exp(fft(sig * ifft(xp.log(mag)))))) return recon -def minimum_phase(h: np.ndarray, +def minimum_phase(h, method: Literal['homomorphic', 'hilbert'] = 'homomorphic', - n_fft: int | None = None, *, half: bool = True) -> np.ndarray: + n_fft: int | None = None, *, half: bool = True): """Convert a linear-phase FIR filter to minimum phase Parameters @@ -1232,13 +1251,16 @@ def minimum_phase(h: np.ndarray, linear filter `h` whereas the other minimum phase filters have only half the order and the square root of the magnitude response. """ - h = np.asarray(h) - if np.iscomplexobj(h): + xp = array_namespace(h) + + h = xp.asarray(h) + if xp.isdtype(h.dtype, "complex floating"): raise ValueError('Complex filters not supported') - if h.ndim != 1 or h.size <= 2: + if h.ndim != 1 or h.shape[0] <= 2: raise ValueError('h must be 1-D and at least 2 samples long') - n_half = len(h) // 2 - if not np.allclose(h[-n_half:][::-1], h[:n_half]): + n_half = h.shape[0] // 2 + + if not xp.any(xp.flip(h[-n_half:]) - h[:n_half] <= 1e-8 + 1e-6*abs(h[:n_half])): warnings.warn('h does not appear to by symmetric, conversion may fail', RuntimeWarning, stacklevel=2) if not isinstance(method, str) or method not in \ @@ -1247,45 +1269,46 @@ def minimum_phase(h: np.ndarray, if method == "hilbert" and not half: raise ValueError("`half=False` is only supported when `method='homomorphic'`") if n_fft is None: - n_fft = 2 ** int(np.ceil(np.log2(2 * (len(h) - 1) / 0.01))) + n_fft = 2 ** int(ceil(log2(2 * (h.shape[0] - 1) / 0.01))) n_fft = int(n_fft) - if n_fft < len(h): + if n_fft < h.shape[0]: raise ValueError(f'n_fft must be at least len(h)=={len(h)}') + if method == 'hilbert': - w = np.arange(n_fft) * (2 * np.pi / n_fft * n_half) - H = np.real(fft(h, n_fft) * np.exp(1j * w)) + w = xp.arange(n_fft, dtype=xp.float64) * (2 * xp.pi / n_fft * n_half) + H = xp.real(fft(h, n_fft) * xp.exp(1j * w)) dp = max(H) - 1 ds = 0 - min(H) - S = 4. / (np.sqrt(1+dp+ds) + np.sqrt(1-dp+ds)) ** 2 + S = 4. / (xp.sqrt(1+dp+ds) + xp.sqrt(1-dp+ds)) ** 2 H += ds H *= S - H = np.sqrt(H, out=H) + H = xp.sqrt(H) H += 1e-10 # ensure that the log does not explode - h_minimum = _dhtm(H) + h_minimum = _dhtm(H, xp) else: # method == 'homomorphic' # zero-pad; calculate the DFT - h_temp = np.abs(fft(h, n_fft)) + h_temp = xp.abs(fft(h, n_fft)) # take 0.25*log(|H|**2) = 0.5*log(|H|) - h_temp += 1e-7 * h_temp[h_temp > 0].min() # don't let log blow up - np.log(h_temp, out=h_temp) + h_temp += 1e-7 * xp.min(h_temp[h_temp > 0]) # don't let log blow up + h_temp = xp.log(h_temp) if half: # halving of magnitude spectrum optional h_temp *= 0.5 # IDFT - h_temp = ifft(h_temp).real + h_temp = xp.real(ifft(h_temp)) # multiply pointwise by the homomorphic filter # lmin[n] = 2u[n] - d[n] # i.e., double the positive frequencies and zero out the negative ones; # Oppenheim+Shafer 3rd ed p991 eq13.42b and p1004 fig13.7 - win = np.zeros(n_fft) + win = xp.zeros(n_fft) win[0] = 1 stop = n_fft // 2 win[1:stop] = 2 if n_fft % 2: win[stop] = 1 h_temp *= win - h_temp = ifft(np.exp(fft(h_temp))) + h_temp = ifft(xp.exp(fft(h_temp))) h_minimum = h_temp.real - n_out = (n_half + len(h) % 2) if half else len(h) + n_out = (n_half + h.shape[0] % 2) if half else h.shape[0] return h_minimum[:n_out] diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index 578533cd1ae8..f941906097f9 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -27,9 +27,10 @@ from ._sosfilt import _sosfilt from scipy._lib._array_api import ( - array_namespace, is_torch, is_numpy, xp_copy, xp_size + array_namespace, is_torch, is_numpy, xp_copy, xp_size, xp_device, ) +from scipy._lib.array_api_compat import is_array_api_obj import scipy._lib.array_api_compat.numpy as np_compat import scipy._lib.array_api_extra as xpx @@ -2653,10 +2654,10 @@ def hilbert2(x, N=None): return x -def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, +def envelope(z, bp_in: tuple[int | None, int | None] = (1, None), *, n_out: int | None = None, squared: bool = False, residual: Literal['lowpass', 'all', None] = 'lowpass', - axis: int = -1) -> np.ndarray: + axis: int = -1): r"""Compute the envelope of a real- or complex-valued signal. Parameters @@ -2899,7 +2900,8 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, if xp.isdtype(z.dtype, 'complex floating'): Z = sp_fft.fft(z) else: # avoid calculating negative frequency bins for real signals: - Z = xp.zeros_like(z, dtype=sp_fft.rfft(z.flat[:1]).dtype) + dt = sp_fft.rfft(z[..., :1]).dtype + Z = xp.zeros_like(z, dtype=dt) Z[..., :n//2 + 1] = sp_fft.rfft(z) if bp.start > 0: # make signal analytic within bp_in band: Z[..., bp] *= 2 @@ -2911,7 +2913,7 @@ def envelope(z: np.ndarray, bp_in: tuple[int | None, int | None] = (1, None), *, bp_shift = slice(bp.start + n//2, bp.stop + n//2) z_bb = sp_fft.ifft(sp_fft.fftshift(Z, axes=-1)[..., bp_shift], n=n_out) * fak - z_env = xp.abs(z_bb) if not squared else z_bb.real ** 2 + z_bb.imag ** 2 + z_env = xp.abs(z_bb) if not squared else xp.real(z_bb) ** 2 + xp.imag(z_bb) ** 2 z_env = xp.moveaxis(z_env, -1, axis) # Calculate the residual from the input bandpass filter: @@ -3629,7 +3631,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): spectrum without antialiasing filter, which is a periodic continuation of the input spectrum. The blue x's and orange dots depict the FFT values of the signal created by the naive approach as well as this function's result. - + >>> import matplotlib.pyplot as plt >>> import numpy as np >>> from scipy.fft import fftshift, fftfreq, fft, rfft, irfft @@ -3672,7 +3674,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): ... ax1.grid() ... fig.legend(loc='outside lower center', ncols=4) >>> plt.show() - + The first figure shows that upsampling an odd number of samples produces identical results. The second figure illustrates that the signal produced with the naive approach (dashed blue line) from an even number of samples does not touch all @@ -3739,7 +3741,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): illustrates that for some use cases, adpating the `resample_poly` parameters may be beneficial. `resample` has a big advantage in this regard: It uses the ideal antialiasing filter with the maximum bandwidth by default. - + Note that the doubled spectral magnitude at the Nyqist frequency of 64 Hz is due the even number of ``n1=128`` output samples, which requires a special treatment as discussed in the previous example. @@ -3766,13 +3768,13 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): "is not equal to number of frequency bins!") W = xp.asarray(window, copy=True) # prevent modifying the function parameters else: - W = sp_fft.fftshift(get_window(window, n_x)) + W = sp_fft.fftshift(get_window(window, n_x, xp=xp)) if domain == 'time' and not xp.isdtype(x.dtype, 'complex floating'): # use rfft(): X = sp_fft.rfft(x) if W is not None: # fold window, i.e., W1[l] = (W[l] + W[-l]) / 2 for l > 0 n_X = X.shape[-1] - W[1:n_X] += W[:-n_X:-1] + W[1:n_X] += xp.flip(W[-n_X+1:]) #W[:-n_X:-1] W[1:n_X] /= 2 X *= W[:n_X] # apply window X = X[..., :m2] # extract relevant data @@ -3926,7 +3928,9 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), >>> plt.show() """ - x = np.asarray(x) + xp = array_namespace(x) + + x = xp.asarray(x) if up != int(up): raise ValueError("up must be an integer") if down != int(down): @@ -3945,31 +3949,29 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), up //= g_ down //= g_ if up == down == 1: - return x.copy() + return xp.asarray(x, copy=True) n_in = x.shape[axis] n_out = n_in * up n_out = n_out // down + bool(n_out % down) - if isinstance(window, (list | np.ndarray)): - window = np.array(window) # use array to force a copy (we modify it) + if isinstance(window, list) or is_array_api_obj(window): + window = xp.asarray(window, copy=True) # force a copy (we modify `window`) if window.ndim > 1: raise ValueError('window must be 1-D') - half_len = (window.size - 1) // 2 + half_len = (xp_size(window) - 1) // 2 h = window else: # Design a linear-phase low-pass FIR filter max_rate = max(up, down) f_c = 1. / max_rate # cutoff of FIR filter (rel. to Nyquist) half_len = 10 * max_rate # reasonable cutoff for sinc-like function - if np.issubdtype(x.dtype, np.complexfloating): - h = firwin(2 * half_len + 1, f_c, - window=window).astype(x.dtype) # match dtype of x - elif np.issubdtype(x.dtype, np.floating): - h = firwin(2 * half_len + 1, f_c, - window=window).astype(x.dtype) # match dtype of x + if xp.isdtype(x.dtype, ("real floating", "complex floating")): + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h, dtype=x.dtype) # match dtype of x else: - h = firwin(2 * half_len + 1, f_c, - window=window) + h = firwin(2 * half_len + 1, f_c, window=window) + h = xp.asarray(h) + h *= up # Zero-pad our filter to put the output samples at the center @@ -3977,16 +3979,20 @@ def resample_poly(x, up, down, axis=0, window=('kaiser', 5.0), n_post_pad = 0 n_pre_remove = (half_len + n_pre_pad) // down # We should rarely need to do this given our filter lengths... - while _output_len(len(h) + n_pre_pad + n_post_pad, n_in, + while _output_len(h.shape[0] + n_pre_pad + n_post_pad, n_in, up, down) < n_out + n_pre_remove: n_post_pad += 1 - h = np.concatenate((np.zeros(n_pre_pad, dtype=h.dtype), h, - np.zeros(n_post_pad, dtype=h.dtype))) + h = xp.concat((xp.zeros(n_pre_pad, dtype=h.dtype), h, + xp.zeros(n_post_pad, dtype=h.dtype))) n_pre_remove_end = n_pre_remove + n_out + # XXX consider using stats.quantile, which is natively Array API compatible + def _median(x, *args, **kwds): + return xp.asarray(np.median(np.asarray(x), *args, **kwds)) + # Remove background depending on the padtype option - funcs = {'mean': np.mean, 'median': np.median, - 'minimum': np.amin, 'maximum': np.amax} + funcs = {'mean': xp.mean, 'median': _median, + 'minimum': xp.min, 'maximum': xp.max} upfirdn_kwargs = {'mode': 'constant', 'cval': 0} if padtype in funcs: background_values = funcs[padtype](x, axis=axis, keepdims=True) @@ -4841,6 +4847,8 @@ def filtfilt(b, a, x, axis=-1, padtype='odd', padlen=None, method='pad', if edge > 0: # Slice the actual signal from the extended signal. y = axis_slice(y, start=edge, stop=-edge, axis=axis) + if is_torch(xp): + y = y.copy() # pytorch/pytorch#59786 : no negative strides in pytorch return xp.asarray(y) diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py index 4701e20c9475..9812bd5f1627 100644 --- a/scipy/signal/tests/test_fir_filter_design.py +++ b/scipy/signal/tests/test_fir_filter_design.py @@ -1,18 +1,23 @@ +import math import numpy as np + from numpy.testing import assert_warns -from scipy._lib._array_api import ( - xp_assert_close, xp_assert_equal, - assert_almost_equal, assert_array_almost_equal, -) from pytest import raises as assert_raises import pytest +import scipy._lib.array_api_extra as xpx +from scipy._lib._array_api import ( + xp_assert_close, xp_assert_equal, assert_almost_equal, assert_array_almost_equal, + array_namespace, xp_default_dtype, np_compat +) from scipy.fft import fft, fft2 -from scipy.special import sinc -from scipy.signal import kaiser_beta, kaiser_atten, kaiserord, \ - firwin, firwin2, freqz, remez, firls, minimum_phase, \ - convolve2d -from scipy.signal._fir_filter_design import firwin_2d +from scipy.signal import (kaiser_beta, kaiser_atten, kaiserord, + firwin, firwin2, freqz, remez, firls, minimum_phase, convolve2d, firwin_2d +) + +skip_xp_backends = pytest.mark.skip_xp_backends +xfail_xp_backends = pytest.mark.xfail_xp_backends + def test_kaiser_beta(): b = kaiser_beta(58.7) @@ -41,17 +46,19 @@ def test_kaiserord(): class TestFirwin: def check_response(self, h, expected_response, tol=.05): - N = len(h) + xp = array_namespace(h) + N = h.shape[0] alpha = 0.5 * (N-1) - m = np.arange(0,N) - alpha # time indices of taps + m = xp.arange(0, N) - alpha # time indices of taps for freq, expected in expected_response: - actual = abs(np.sum(h*np.exp(-1.j*np.pi*m*freq))) - mse = abs(actual-expected)**2 + actual = abs(xp.sum(h * xp.exp(-1j * xp.pi * m * freq))) + mse = abs(actual - expected)**2 assert mse < tol, f'response not as expected, mse={mse:g} > {tol:g}' - def test_response(self): + def test_response(self, xp): N = 51 f = .5 + # increase length just to try even/odd h = firwin(N, f) # low-pass from 0 to f self.check_response(h, [(.25,1), (.75,0)]) @@ -97,7 +104,7 @@ def mse(self, h, bands): mse = np.mean(abs(abs(H)-Hideal)**2) return mse - def test_scaling(self): + def test_scaling(self, xp): """ For one lowpass, bandpass, and highpass example filter, this test checks two things: @@ -130,29 +137,38 @@ def test_fs_validation(self): firwin(51, .5, fs=np.array([10, 20])) +@skip_xp_backends(cpu_only=True, reason="TODO convert freqs/freqz") class TestFirWinMore: """Different author, different style, different tests...""" - def test_lowpass(self): + def test_lowpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) kwargs = dict(cutoff=0.5, window=('kaiser', beta), scale=False) taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal( + taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat + ) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) + freq_samples = xp.asarray([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + + freqs = xp.asarray(freqs) # XXX: convert freqz + response = xp.asarray(response) + + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5 + ) taps_str = firwin(ntaps, pass_zero='lowpass', **kwargs) - xp_assert_close(taps, taps_str) + xp_assert_close(taps, taps_str, xp=np_compat) - def test_highpass(self): + def test_highpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) @@ -163,39 +179,47 @@ def test_highpass(self): taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal( + taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat + ) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='highpass', **kwargs) - xp_assert_close(taps, taps_str) + xp_assert_close(taps, taps_str, xp=np_compat) - def test_bandpass(self): + def test_bandpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) kwargs = dict(cutoff=[0.3, 0.7], window=('kaiser', beta), scale=False) taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal( + taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat + ) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.2, 0.3-width/2, 0.3+width/2, 0.5, - 0.7-width/2, 0.7+width/2, 0.8, 1.0]) + freq_samples = xp.asarray([0.0, 0.2, 0.3 - width/2, 0.3 + width/2, 0.5, + 0.7 - width/2, 0.7 + width/2, 0.8, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='bandpass', **kwargs) - xp_assert_close(taps, taps_str) + xp_assert_close(taps, taps_str, xp=np_compat) - def test_bandstop_multi(self): + def test_bandstop_multi(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) kwargs = dict(cutoff=[0.2, 0.5, 0.8], window=('kaiser', beta), @@ -203,22 +227,28 @@ def test_bandstop_multi(self): taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal( + taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat + ) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 0.1, 0.2-width/2, 0.2+width/2, 0.35, - 0.5-width/2, 0.5+width/2, 0.65, - 0.8-width/2, 0.8+width/2, 0.9, 1.0]) + freq_samples = xp.asarray([0.0, 0.1, 0.2 - width/2, 0.2 + width/2, 0.35, + 0.5 - width/2, 0.5 + width/2, 0.65, + 0.8 - width/2, 0.8 + width/2, 0.9, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], - decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), + decimal=5 + ) taps_str = firwin(ntaps, pass_zero='bandstop', **kwargs) - xp_assert_close(taps, taps_str) + xp_assert_close(taps, taps_str, xp=np_compat) - def test_fs_nyq(self): + def test_fs_nyq(self, xp): """Test the fs and nyq keywords.""" nyquist = 1000 width = 40.0 @@ -228,17 +258,22 @@ def test_fs_nyq(self): pass_zero=False, scale=False, fs=2*nyquist) # Check the symmetry of taps. - assert_array_almost_equal(taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1]) + assert_array_almost_equal( + taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2 - 1:-1], xp=np_compat + ) # Check the gain at a few samples where # we know it should be approximately 0 or 1. - freq_samples = np.array([0.0, 200, 300-width/2, 300+width/2, 500, - 700-width/2, 700+width/2, 800, 1000]) + freq_samples = xp.asarray([0.0, 200, 300 - width/2, 300 + width/2, 500, + 700 - width/2, 700 + width/2, 800, 1000]) freqs, response = freqz(taps, worN=np.pi*freq_samples/nyquist) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) - def test_bad_cutoff(self): + assert_array_almost_equal(xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) + + @skip_xp_backends(np_only=True) + def test_bad_cutoff(self, xp): """Test that invalid cutoff argument raises ValueError.""" # cutoff values must be greater than 0 and less than 1. assert_raises(ValueError, firwin, 99, -0.5) @@ -257,17 +292,19 @@ def test_bad_cutoff(self): assert_raises(ValueError, firwin, 99, 50.0, fs=80) assert_raises(ValueError, firwin, 99, [10, 20, 30], fs=50) + @skip_xp_backends(np_only=True) def test_even_highpass_raises_value_error(self): """Test that attempt to create a highpass filter with an even number of taps raises a ValueError exception.""" assert_raises(ValueError, firwin, 40, 0.5, pass_zero=False) assert_raises(ValueError, firwin, 40, [.25, 0.5]) + @skip_xp_backends(np_only=True) def test_bad_pass_zero(self): """Test degenerate pass_zero cases.""" - with assert_raises(ValueError, match='pass_zero must be'): + with assert_raises(ValueError, match="^Parameter pass_zero='foo' not in "): firwin(41, 0.5, pass_zero='foo') - with assert_raises(TypeError, match='cannot be interpreted'): + with assert_raises(ValueError, match="^Parameter pass_zero=1.0 not in "): firwin(41, 0.5, pass_zero=1.) for pass_zero in ('lowpass', 'highpass'): with assert_raises(ValueError, match='cutoff must have one'): @@ -276,14 +313,17 @@ def test_bad_pass_zero(self): with assert_raises(ValueError, match='must have at least two'): firwin(41, [0.5], pass_zero=pass_zero) + @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firwin2(51, .5, 1, fs=np.array([10, 20])) +@skip_xp_backends(cpu_only=True, reason="firwin2 uses np.interp") class TestFirwin2: - def test_invalid_args(self): + @skip_xp_backends(np_only=True) + def test_invalid_args(self, xp): # `freq` and `gain` have different lengths. with assert_raises(ValueError, match='must be of same length'): firwin2(50, [0, 0.5, 1], [0.0, 1.0]) @@ -330,110 +370,142 @@ def test_invalid_args(self): with assert_raises(ValueError, match='Type IV filter'): firwin2(16, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0], antisymmetric=True) - def test01(self): + def test01(self, xp): width = 0.04 beta = 12.0 ntaps = 400 # Filter is 1 from w=0 to w=0.5, then decreases linearly from 1 to 0 as w # increases from w=0.5 to w=1 (w=1 is the Nyquist frequency). - freq = [0.0, 0.5, 1.0] - gain = [1.0, 1.0, 0.0] + freq = xp.asarray([0.0, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.25, 0.5-width/2, 0.5+width/2, - 0.75, 1.0-width/2]) + freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, + 0.75, 1.0 - width/2]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 1.0, 1.0-width, 0.5, width], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 1.0, 1.0 - width, 0.5, width]), decimal=5 + ) - def test02(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test02(self, xp): width = 0.04 beta = 12.0 # ntaps must be odd for positive gain at Nyquist. ntaps = 401 # An ideal highpass filter. - freq = [0.0, 0.5, 0.5, 1.0] - gain = [0.0, 0.0, 1.0, 1.0] + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([0.0, 0.0, 1.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.25, 0.5-width, 0.5+width, 0.75, 1.0]) + freq_samples = np.array([0.0, 0.25, 0.5 - width, 0.5 + width, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) - def test03(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test03(self, xp): width = 0.02 ntaps, beta = kaiserord(120, width) # ntaps must be odd for positive gain at Nyquist. ntaps = int(ntaps) | 1 - freq = [0.0, 0.4, 0.4, 0.5, 0.5, 1.0] - gain = [1.0, 1.0, 0.0, 0.0, 1.0, 1.0] + freq = xp.asarray([0.0, 0.4, 0.4, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0, 1.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=('kaiser', beta)) - freq_samples = np.array([0.0, 0.4-width, 0.4+width, 0.45, - 0.5-width, 0.5+width, 0.75, 1.0]) + freq_samples = np.array([0.0, 0.4 - width, 0.4 + width, 0.45, + 0.5 - width, 0.5 + width, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - assert_array_almost_equal(np.abs(response), - [1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0], decimal=5) + freqs, response = xp.asarray(freqs), xp.asarray(response) + assert_array_almost_equal( + xp.abs(response), + xp.asarray([1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5 + ) - def test04(self): + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test04(self, xp): """Test firwin2 when window=None.""" ntaps = 5 # Ideal lowpass: gain is 1 on [0,0.5], and 0 on [0.5, 1.0] - freq = [0.0, 0.5, 0.5, 1.0] - gain = [1.0, 1.0, 0.0, 0.0] + freq = xp.asarray([0.0, 0.5, 0.5, 1.0]) + gain = xp.asarray([1.0, 1.0, 0.0, 0.0]) + taps = firwin2(ntaps, freq, gain, window=None, nfreqs=8193) alpha = 0.5 * (ntaps - 1) - m = np.arange(0, ntaps) - alpha - h = 0.5 * sinc(0.5 * m) + m = xp.arange(0, ntaps, dtype=freq.dtype) - alpha + h = 0.5 * xpx.sinc(0.5 * m) assert_array_almost_equal(h, taps) - def test05(self): + def test05(self, xp): """Test firwin2 for calculating Type IV filters""" ntaps = 1500 - freq = [0.0, 1.0] - gain = [0.0, 1.0] + freq = xp.asarray([0.0, 1.0]) + gain = xp.asarray([0.0, 1.0]) taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) - assert_array_almost_equal(taps[: ntaps // 2], -taps[ntaps // 2:][::-1]) - freqs, response = freqz(taps, worN=2048) - assert_array_almost_equal(abs(response), freqs / np.pi, decimal=4) + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], flip(-taps[ntaps // 2:]), **dec) - def test06(self): + freqs, response = freqz(np.asarray(taps), worN=2048) # XXX convert freqz + assert_array_almost_equal(abs(xp.asarray(response)), + xp.asarray(freqs / np.pi), decimal=4) + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test06(self, xp): """Test firwin2 for calculating Type III filters""" ntaps = 1501 - freq = [0.0, 0.5, 0.55, 1.0] - gain = [0.0, 0.5, 0.0, 0.0] + freq = xp.asarray([0.0, 0.5, 0.55, 1.0]) + gain = xp.asarray([0.0, 0.5, 0.0, 0.0]) taps = firwin2(ntaps, freq, gain, window=None, antisymmetric=True) assert taps[ntaps // 2] == 0.0 - assert_array_almost_equal(taps[: ntaps // 2], -taps[ntaps // 2 + 1:][::-1]) - freqs, response1 = freqz(taps, worN=2048) - response2 = np.interp(freqs / np.pi, freq, gain) + flip = array_namespace(freq).flip + dec = {'decimal': 4.5} if xp_default_dtype(xp) == xp.float32 else {} + assert_array_almost_equal(taps[: ntaps // 2], + flip(-taps[ntaps // 2 + 1:]), **dec + ) + + freqs, response1 = freqz(np.asarray(taps), worN=2048) # XXX convert freqz + response1 = xp.asarray(response1) + response2 = xp.asarray( + np.interp(np.asarray(freqs) / np.pi, np.asarray(freq), np.asarray(gain)) + ) assert_array_almost_equal(abs(response1), response2, decimal=3) - def test_fs_nyq(self): - taps1 = firwin2(80, [0.0, 0.5, 1.0], [1.0, 1.0, 0.0]) - taps2 = firwin2(80, [0.0, 30.0, 60.0], [1.0, 1.0, 0.0], fs=120.0) + def test_fs_nyq(self, xp): + taps1 = firwin2(80, xp.asarray([0.0, 0.5, 1.0]), xp.asarray([1.0, 1.0, 0.0])) + taps2 = firwin2(80, xp.asarray([0.0, 30.0, 60.0]), xp.asarray([1.0, 1.0, 0.0]), + fs=120.0) assert_array_almost_equal(taps1, taps2) - def test_tuple(self): + @skip_xp_backends(np_only=True, reason="test array-likes") + def test_tuple(self, xp): taps1 = firwin2(150, (0.0, 0.5, 0.5, 1.0), (1.0, 1.0, 0.0, 0.0)) taps2 = firwin2(150, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0]) assert_array_almost_equal(taps1, taps2) - def test_input_modyfication(self): - freq1 = np.array([0.0, 0.5, 0.5, 1.0]) - freq2 = np.array(freq1) - firwin2(80, freq1, [1.0, 1.0, 0.0, 0.0]) + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test_input_modyfication(self, xp): + freq1 = xp.asarray([0.0, 0.5, 0.5, 1.0]) + freq2 = xp.asarray(freq1) + firwin2(80, freq1, xp.asarray([1.0, 1.0, 0.0, 0.0])) xp_assert_equal(freq1, freq2) +@skip_xp_backends(cpu_only=True) class TestRemez: - def test_bad_args(self): + @skip_xp_backends(np_only=True) + def test_bad_args(self, xp): assert_raises(ValueError, remez, 11, [0.1, 0.4], [1], type='pooka') - def test_hilbert(self): + @skip_xp_backends(np_only=True) + def test_hilbert(self, xp): N = 11 # number of taps in the filter a = 0.1 # width of the transition band @@ -462,14 +534,15 @@ def test_hilbert(self): idx = np.logical_and(f > a, f < 0.5-a) assert (abs(Hmag[idx] - 1) < 0.015).all(), "Pass Band Close To Unity" - def test_compare(self): + def test_compare(self, xp): # test comparison to MATLAB k = [0.024590270518440, -0.041314581814658, -0.075943803756711, -0.003530911231040, 0.193140296954975, 0.373400753484939, 0.373400753484939, 0.193140296954975, -0.003530911231040, -0.075943803756711, -0.041314581814658, 0.024590270518440] - h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) - xp_assert_close(h, k) + h = remez(12, xp.asarray([0, 0.3, 0.5, 1]), xp.asarray([1, 0]), fs=2.) + atol_arg = {'atol': 1e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(h, xp.asarray(k, dtype=xp.float64), **atol_arg) h = [-0.038976016082299, 0.018704846485491, -0.014644062687875, 0.002879152556419, 0.016849978528150, -0.043276706138248, @@ -478,15 +551,24 @@ def test_compare(self): 0.129770906801075, -0.103908158578635, 0.073641298245579, -0.043276706138248, 0.016849978528150, 0.002879152556419, -0.014644062687875, 0.018704846485491, -0.038976016082299] - xp_assert_close(remez(21, [0, 0.8, 0.9, 1], [0, 1], fs=2.), h) + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close( + remez(21, xp.asarray([0, 0.8, 0.9, 1]), xp.asarray([0, 1]), fs=2.), + xp.asarray(h, dtype=xp.float64), **atol_arg + ) - def test_fs_validation(self): + @skip_xp_backends(np_only=True) + def test_fs_validation(self, xp): with pytest.raises(ValueError, match="Sampling.*single scalar"): remez(11, .1, 1, fs=np.array([10, 20])) + + +@skip_xp_backends(cpu_only=True, reason="lstsq") class TestFirls: - def test_bad_args(self): + @skip_xp_backends(np_only=True) + def test_bad_args(self, xp): # even numtaps assert_raises(ValueError, firls, 10, [0.1, 0.2], [0, 0]) # odd bands @@ -505,112 +587,131 @@ def test_bad_args(self): # negative weight assert_raises(ValueError, firls, 11, [0.1, 0.2], [0, 0], weight=[-1]) - def test_firls(self): + @skip_xp_backends("dask.array", reason="dask fancy indexing shape=(nan,)") + def test_firls(self, xp): N = 11 # number of taps in the filter a = 0.1 # width of the transition band # design a halfband symmetric low-pass filter - h = firls(11, [0, a, 0.5-a, 0.5], [1, 1, 0, 0], fs=1.0) + h = firls(11, xp.asarray([0, a, 0.5 - a, 0.5]), xp.asarray([1, 1, 0, 0]), + fs=1.0) # make sure the filter has correct # of taps assert h.shape[0] == N # make sure it is symmetric midx = (N-1) // 2 - assert_array_almost_equal(h[:midx], h[:-midx-1:-1]) + flip = array_namespace(h).flip + assert_array_almost_equal(h[:midx], flip(h[midx+1:])) # h[:-midx-1:-1]) # make sure the center tap is 0.5 - assert_almost_equal(h[midx], 0.5) + assert math.isclose(h[midx], 0.5, abs_tol=1e-8) # For halfband symmetric, odd coefficients (except the center) # should be zero (really small) - hodd = np.hstack((h[1:midx:2], h[-midx+1::2])) - assert_array_almost_equal(hodd, np.zeros_like(hodd)) + hodd = xp.stack((h[1:midx:2], h[-midx+1::2])) + assert_array_almost_equal(hodd, xp.zeros_like(hodd)) # now check the frequency response - w, H = freqz(h, 1) - f = w/2/np.pi - Hmag = np.abs(H) + w, H = freqz(np.asarray(h), 1) + w, H = xp.asarray(w), xp.asarray(H) + f = w/2/xp.pi + Hmag = xp.abs(H) # check that the pass band is close to unity - idx = np.logical_and(f > 0, f < a) - assert_array_almost_equal(Hmag[idx], np.ones_like(Hmag[idx]), decimal=3) + idx = xp.logical_and(f > 0, f < a) + assert_array_almost_equal(Hmag[idx], xp.ones_like(Hmag[idx]), decimal=3) # check that the stop band is close to zero - idx = np.logical_and(f > 0.5-a, f < 0.5) - assert_array_almost_equal(Hmag[idx], np.zeros_like(Hmag[idx]), decimal=3) + idx = xp.logical_and(f > 0.5 - a, f < 0.5) + assert_array_almost_equal(Hmag[idx], xp.zeros_like(Hmag[idx]), decimal=3) - def test_compare(self): + def test_compare(self, xp): # compare to OCTAVE output - taps = firls(9, [0, 0.5, 0.55, 1], [1, 1, 0, 0], weight=[1, 2]) + taps = firls(9, xp.asarray([0, 0.5, 0.55, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) # >> taps = firls(8, [0 0.5 0.55 1], [1 1 0 0], [1, 2]); known_taps = [-6.26930101730182e-04, -1.03354450635036e-01, -9.81576747564301e-03, 3.17271686090449e-01, 5.11409425599933e-01, 3.17271686090449e-01, -9.81576747564301e-03, -1.03354450635036e-01, -6.26930101730182e-04] - xp_assert_close(taps, known_taps) + atol_arg = {'atol': 5e-8} if xp_default_dtype(xp) == xp.float32 else {} + known_taps = xp.asarray(known_taps, dtype=xp.float64) + xp_assert_close(taps, known_taps, **atol_arg) # compare to MATLAB output - taps = firls(11, [0, 0.5, 0.5, 1], [1, 1, 0, 0], weight=[1, 2]) + taps = firls(11, xp.asarray([0, 0.5, 0.5, 1]), + xp.asarray([1, 1, 0, 0]), weight=xp.asarray([1, 2])) # >> taps = firls(10, [0 0.5 0.5 1], [1 1 0 0], [1, 2]); known_taps = [ 0.058545300496815, -0.014233383714318, -0.104688258464392, 0.012403323025279, 0.317930861136062, 0.488047220029700, 0.317930861136062, 0.012403323025279, -0.104688258464392, -0.014233383714318, 0.058545300496815] - xp_assert_close(taps, known_taps) + known_taps = xp.asarray(known_taps, dtype=xp.float64) + atol_arg = {'atol': 3e-8} if xp_default_dtype(xp) == xp.float32 else {} + xp_assert_close(taps, known_taps, **atol_arg) # With linear changes: - taps = firls(7, (0, 1, 2, 3, 4, 5), [1, 0, 0, 1, 1, 0], fs=20) + taps = firls(7, xp.asarray((0, 1, 2, 3, 4, 5)), + xp.asarray([1, 0, 0, 1, 1, 0]), fs=20) # >> taps = firls(6, [0, 0.1, 0.2, 0.3, 0.4, 0.5], [1, 0, 0, 1, 1, 0]) known_taps = [ 1.156090832768218, -4.1385894727395849, 7.5288619164321826, -8.5530572592947856, 7.5288619164321826, -4.1385894727395849, 1.156090832768218] + known_taps = xp.asarray(known_taps, dtype=xp.float64) xp_assert_close(taps, known_taps) - def test_rank_deficient(self): + def test_rank_deficient(self, xp): # solve() runs but warns (only sometimes, so here we don't use match) - x = firls(21, [0, 0.1, 0.9, 1], [1, 1, 0, 0]) - w, h = freqz(x, fs=2.) - absh2 = np.abs(h[:2]) - xp_assert_close(absh2, np.ones_like(absh2), atol=1e-5) - absh2 = np.abs(h[-2:]) - xp_assert_close(absh2, np.zeros_like(absh2), atol=1e-6, rtol=1e-7) + x = firls(21, xp.asarray([0, 0.1, 0.9, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(np.asarray(x), fs=2.) + w, h = map(xp.asarray, (w, h)) # XXX convert freqz + absh2 = xp.abs(h[:2]) + xp_assert_close(absh2, xp.ones_like(absh2), atol=1e-5) + absh2 = xp.abs(h[-2:]) + xp_assert_close(absh2, xp.zeros_like(absh2), atol=1e-6, rtol=1e-7) # switch to pinvh (tolerances could be higher with longer # filters, but using shorter ones is faster computationally and # the idea is the same) - x = firls(101, [0, 0.01, 0.99, 1], [1, 1, 0, 0]) - w, h = freqz(x, fs=2.) - mask = w < 0.01 - assert mask.sum() > 3 - habs = np.abs(h[mask]) - xp_assert_close(habs, np.ones_like(habs), atol=1e-4) - mask = w > 0.99 - assert mask.sum() > 3 - habs = np.abs(h[mask]) - xp_assert_close(habs, np.zeros_like(habs), atol=1e-4) - + x = firls(101, xp.asarray([0, 0.01, 0.99, 1]), xp.asarray([1, 1, 0, 0])) + w, h = freqz(np.asarray(x), fs=2.) + w, h = map(xp.asarray, (w, h)) # XXX convert freqz + mask = xp.asarray(w < 0.01) + h = xp.asarray(h) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.ones_like(habs), atol=1e-4) + mask = xp.asarray(w > 0.99) + assert xp.sum(xp.astype(mask, xp.int64)) > 3 + habs = xp.abs(h[mask]) + xp_assert_close(habs, xp.zeros_like(habs), atol=1e-4) + + @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firls(11, .1, 1, fs=np.array([10, 20])) class TestMinimumPhase: + + @skip_xp_backends(np_only=True) @pytest.mark.thread_unsafe - def test_bad_args(self): + def test_bad_args(self, xp): # not enough taps assert_raises(ValueError, minimum_phase, [1.]) assert_raises(ValueError, minimum_phase, [1., 1.]) assert_raises(ValueError, minimum_phase, np.full(10, 1j)) - assert_raises(ValueError, minimum_phase, 'foo') + assert_raises((ValueError, TypeError), minimum_phase, 'foo') assert_raises(ValueError, minimum_phase, np.ones(10), n_fft=8) assert_raises(ValueError, minimum_phase, np.ones(10), method='foo') assert_warns(RuntimeWarning, minimum_phase, np.arange(3)) with pytest.raises(ValueError, match="is only supported when"): minimum_phase(np.ones(3), method='hilbert', half=False) - def test_homomorphic(self): + @skip_xp_backends(np_only=True) + def test_homomorphic(self, xp): # check that it can recover frequency responses of arbitrary # linear-phase filters @@ -630,7 +731,9 @@ def test_homomorphic(self): assert len(h_linear) == len(h_new) xp_assert_close(np.abs(fft(h_new)), np.abs(fft(h_linear)), rtol=1e-4) - def test_hilbert(self): + @skip_xp_backends("dask.array", reason="too slow") + @skip_xp_backends("jax.numpy", reason="immutable arrays") + def test_hilbert(self, xp): # compare to MATLAB output of reference implementation # f=[0 0.3 0.5 1]; @@ -639,6 +742,8 @@ def test_hilbert(self): h = remez(12, [0, 0.3, 0.5, 1], [1, 0], fs=2.) k = [0.349585548646686, 0.373552164395447, 0.326082685363438, 0.077152207480935, -0.129943946349364, -0.059355880509749] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) m = minimum_phase(h, 'hilbert') xp_assert_close(m, k, rtol=5e-3) @@ -650,6 +755,8 @@ def test_hilbert(self): -0.157957283165866, 0.151739294892963, -0.129293146705090, 0.100787844523204, -0.065832656741252, 0.035361328741024, -0.014977068692269, -0.158416139047557] + h = xp.asarray(h) + k = xp.asarray(k, dtype=xp.float64) m = minimum_phase(h, 'hilbert', n_fft=2**19) xp_assert_close(m, k, rtol=2e-3) diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index eb02d9ac4039..7fd0a80d84eb 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -34,7 +34,7 @@ from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, is_numpy, is_torch, is_jax, is_cupy, assert_array_almost_equal, assert_almost_equal, - xp_copy, xp_size, xp_default_dtype + xp_copy, xp_size, xp_default_dtype, array_namespace ) skip_xp_backends = pytest.mark.skip_xp_backends xfail_xp_backends = pytest.mark.xfail_xp_backends @@ -1381,14 +1381,19 @@ def test_basic(self, xp): padtype_options += _upfirdn_modes -@skip_xp_backends(np_only=True) +@skip_xp_backends("dask.array", reason="XXX something in dask") class TestResample: + + @skip_xp_backends("jax.numpy", reason="immutable arrays") + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) def test_basic(self, xp): # Some basic tests # Regression test for issue #3603. # window.shape must equal to sig.shape[0] - sig = np.arange(128) + sig = xp.arange(128, dtype=xp.float64) num = 256 win = signal.get_window(('kaiser', 8.0), 160) assert_raises(ValueError, signal.resample, sig, num, window=win) @@ -1405,7 +1410,7 @@ def test_basic(self, xp): assert_raises(ValueError, signal.resample_poly, sig, 2, 1, window=np.eye(2)) # test for issue #6505 - should not modify window.shape when axis ≠ 0 - sig2 = np.tile(np.arange(160), (2, 1)) + sig2 = xp.tile(xp.arange(160, dtype=xp.float64), (2, 1)) signal.resample(sig2, num, axis=-1, window=win) assert win.shape == (160,) @@ -1422,21 +1427,30 @@ def test_basic(self, xp): def test_rfft(self, N, num, window, xp): # Make sure the speed up using rfft gives the same result as the normal # way using fft - x = np.linspace(0, 10, N, endpoint=False) - y = np.cos(-x**2/6.0) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + x = xp.linspace(0, 10, N, endpoint=False) + y = xp.cos(-x**2/6.0) + desired = signal.resample(xp.astype(y, dt_c), num, window=window) xp_assert_close(signal.resample(y, num, window=window), - signal.resample(y + 0j, num, window=window).real) + xp.real(desired)) + + y = xp.stack([xp.cos(-x**2/6.0), xp.sin(-x**2/6.0)]) + y_complex = xp.astype(y, dt_c) + resampled = signal.resample(y_complex, num, axis=1, window=window) + + atol = 1e-9 if dt_r == xp.float64 else 3e-7 - y = np.array([np.cos(-x**2/6.0), np.sin(-x**2/6.0)]) - y_complex = y + 0j xp_assert_close( signal.resample(y, num, axis=1, window=window), - signal.resample(y_complex, num, axis=1, window=window).real, - atol=1e-9) + xp.real(resampled), + atol=atol) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_input_domain(self, xp): # Test if both input domain modes produce the same results. - tsig = np.arange(256) + 0j + tsig = xp.astype(xp.arange(256), xp.complex128) fsig = sp_fft.fft(tsig) num = 256 xp_assert_close( @@ -1444,39 +1458,54 @@ def test_input_domain(self, xp): signal.resample(tsig, num, domain='time'), atol=1e-9) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize('nx', (1, 2, 3, 5, 8)) @pytest.mark.parametrize('ny', (1, 2, 3, 5, 8)) - @pytest.mark.parametrize('dtype', ('float', 'complex')) + @pytest.mark.parametrize('dtype', ('float64', 'complex128')) def test_dc(self, nx, ny, dtype, xp): - x = np.array([1] * nx, dtype) + dtype = getattr(xp, dtype) + x = xp.asarray([1] * nx, dtype=dtype) y = signal.resample(x, ny) - xp_assert_close(y, np.asarray([1] * ny, dtype=y.dtype)) + xp_assert_close(y, xp.asarray([1] * ny, dtype=y.dtype)) + @skip_xp_backends(cpu_only=True, reason="resample_poly/upfirdn is CPU only") @pytest.mark.parametrize('padtype', padtype_options) def test_mutable_window(self, padtype, xp): # Test that a mutable window is not modified - impulse = np.zeros(3) - window = np.random.RandomState(0).randn(2) - window_orig = window.copy() + impulse = xp.zeros(3) + window = xp.asarray(np.random.RandomState(0).randn(2)) + window_orig = xp.asarray(window, copy=True) signal.resample_poly(impulse, 5, 1, window=window, padtype=padtype) xp_assert_equal(window, window_orig) + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize('padtype', padtype_options) def test_output_float32(self, padtype, xp): # Test that float32 inputs yield a float32 output - x = np.arange(10, dtype=np.float32) - h = np.array([1, 1, 1], dtype=np.float32) + x = xp.arange(10, dtype=xp.float32) + h = xp.asarray([1, 1, 1], dtype=xp.float32) y = signal.resample_poly(x, 1, 2, window=h, padtype=padtype) - assert y.dtype == np.float32 + assert y.dtype == xp.float32 + + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize('padtype', padtype_options) - @pytest.mark.parametrize('dtype', [np.float32, np.float64]) + @pytest.mark.parametrize('dtype', ['float32', 'float64']) def test_output_match_dtype(self, padtype, dtype, xp): # Test that the dtype of x is preserved per issue #14733 - x = np.arange(10, dtype=dtype) + dtype = getattr(xp, dtype) + x = xp.arange(10, dtype=dtype) y = signal.resample_poly(x, 1, 2, padtype=padtype) assert y.dtype == x.dtype + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) @pytest.mark.parametrize( "method, ext, padtype", [("fft", False, None)] @@ -1492,13 +1521,13 @@ def test_resample_methods(self, method, ext, padtype, xp): rates_to = [49, 50, 51, 99, 100, 101, 199, 200, 201] # Sinusoids, windowed to avoid edge artifacts - t = np.arange(rate) / float(rate) - freqs = np.array((1., 10., 40.))[:, np.newaxis] - x = np.sin(2 * np.pi * freqs * t) * hann(rate) + t = xp.arange(rate, dtype=xp.float64) / float(rate) + freqs = xp.asarray((1., 10., 40.))[:, xp.newaxis] + x = xp.sin(2 * xp.pi * freqs * t) * hann(rate, xp=xp) for rate_to in rates_to: - t_to = np.arange(rate_to) / float(rate_to) - y_tos = np.sin(2 * np.pi * freqs * t_to) * hann(rate_to) + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) + y_tos = xp.sin(2 * xp.pi * freqs * t_to) * hann(rate_to, xp=xp) if method == 'fft': y_resamps = signal.resample(x, rate_to, axis=-1) else: @@ -1519,9 +1548,13 @@ def test_resample_methods(self, method, ext, padtype, xp): y_resamps = signal.resample_poly(x, rate_to, rate, axis=-1, **polyargs) - for y_to, y_resamp, freq in zip(y_tos, y_resamps, freqs): + for i in range(y_tos.shape[0]): + y_to = y_tos[i, :] + y_resamp = y_resamps[i, :] + freq = float(freqs[i, 0]) if freq >= 0.5 * rate_to: - y_to.fill(0.) # mostly low-passed away + #y_to.fill(0.) # mostly low-passed away + y_to = xp.zeros_like(y_to) # mostly low-passed away if padtype in ['minimum', 'maximum']: xp_assert_close(y_resamp, y_to, atol=3e-1) else: @@ -1534,9 +1567,10 @@ def test_resample_methods(self, method, ext, padtype, xp): # Random data rng = np.random.RandomState(0) x = hann(rate) * np.cumsum(rng.randn(rate)) # low-pass, wind + x = xp.asarray(x) for rate_to in rates_to: # random data - t_to = np.arange(rate_to) / float(rate_to) + t_to = xp.arange(rate_to, dtype=xp.float64) / float(rate_to) y_to = np.interp(t_to, t, x) if method == 'fft': y_resamp = signal.resample(x, rate_to) @@ -1544,22 +1578,21 @@ def test_resample_methods(self, method, ext, padtype, xp): y_resamp = signal.resample_poly(x, rate_to, rate, padtype=padtype) assert y_to.shape == y_resamp.shape - corr = np.corrcoef(y_to, y_resamp)[0, 1] + corr = xp.asarray(np.corrcoef(y_to, np.asarray(y_resamp))[0, 1]) assert corr > 0.99, corr # More tests of fft method (Master 0.18.1 fails these) if method == 'fft': - x1 = np.array([1.+0.j, 0.+0.j]) + x1 = xp.asarray([1.+0.j, 0.+0.j]) y1_test = signal.resample(x1, 4) # upsampling a complex array - y1_true = np.array([1.+0.j, 0.5+0.j, 0.+0.j, 0.5+0.j]) + y1_true = xp.asarray([1.+0.j, 0.5+0.j, 0.+0.j, 0.5+0.j]) xp_assert_close(y1_test, y1_true, atol=1e-12) - x2 = np.array([1., 0.5, 0., 0.5]) + x2 = xp.asarray([1., 0.5, 0., 0.5]) y2_test = signal.resample(x2, 2) # downsampling a real array - y2_true = np.array([1., 0.]) + y2_true = xp.asarray([1., 0.]) xp_assert_close(y2_test, y2_true, atol=1e-12) - @pytest.mark.parametrize("n_in", (8, 9)) @pytest.mark.parametrize("n_out", (3, 4)) def test_resample_win_func(self, n_in, n_out): @@ -1606,48 +1639,62 @@ def test_resample_nyquist(self, n0, n1): xp_assert_close(y1_r, x1, atol=1e-12) xp_assert_close(y1_c.real, x1, atol=1e-12) - def test_poly_vs_filtfilt(self, xp): + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="filtfilt is CPU-only" + ) + @pytest.mark.parametrize('down_factor', [2, 11, 79]) + def test_poly_vs_filtfilt(self, down_factor, xp): # Check that up=1.0 gives same answer as filtfilt + slicing random_state = np.random.RandomState(17) try_types = (int, np.float32, np.complex64, float, complex) size = 10000 - down_factors = [2, 11, 79] for dtype in try_types: x = random_state.randn(size).astype(dtype) if dtype in (np.complex64, np.complex128): x += 1j * random_state.randn(size) + x = xp.asarray(x) # resample_poly assumes zeros outside of signl, whereas filtfilt # can only constant-pad. Make them equivalent: x[0] = 0 x[-1] = 0 - for down in down_factors: - h = signal.firwin(31, 1. / down, window='hamming') - yf = filtfilt(h, 1.0, x, padtype='constant')[::down] + h = signal.firwin(31, 1. / down_factor, window='hamming') + h = xp.asarray(h) # XXX: convert firwin + yf = filtfilt(h, 1.0, x, padtype='constant')[::down_factor] - # Need to pass convolved version of filter to resample_poly, - # since filtfilt does forward and backward, but resample_poly - # only goes forward - hc = convolve(h, h[::-1]) - y = signal.resample_poly(x, 1, down, window=hc) - xp_assert_close(yf, y, atol=1e-7, rtol=1e-7) + # Need to pass convolved version of filter to resample_poly, + # since filtfilt does forward and backward, but resample_poly + # only goes forward + hc = convolve(h, xp.flip(h)) + y = signal.resample_poly(x, 1, down_factor, window=hc) + xp_assert_close(yf, y, atol=1e-7, rtol=1e-7) + @skip_xp_backends( + cpu_only=True, exceptions=["cupy"], reason="correlate1d is CPU-only" + ) def test_correlate1d(self, xp): for down in [2, 4]: for nx in range(1, 40, down): for nweights in (32, 33): x = np.random.random((nx,)) weights = np.random.random((nweights,)) - y_g = correlate1d(x, weights[::-1], mode='constant') + x, weights = map(xp.asarray, (x, weights)) + flip = array_namespace(x).flip + y_g = correlate1d(x, flip(weights), mode='constant') y_s = signal.resample_poly( x, up=1, down=down, window=weights) xp_assert_close(y_g[::down], y_s) - @pytest.mark.parametrize('dtype', [np.int32, np.float32]) + @skip_xp_backends( + cpu_only=True, reason="resample_poly/upfirdn is CPU only" + ) + @pytest.mark.parametrize('dtype', ['int32', 'float32']) def test_gh_15620(self, dtype, xp): - data = np.array([0, 1, 2, 3, 2, 1, 0], dtype=dtype) + dtype = getattr(xp, dtype) + data = xp.asarray([0, 1, 2, 3, 2, 1, 0], dtype=dtype) actual = signal.resample_poly(data, up=2, down=1, @@ -3457,14 +3504,18 @@ def test_hilbert2_types(self, dtype, xp): assert xp.real(signal.hilbert2(in_typed)).dtype == dtype -@skip_xp_backends(np_only=True) class TestEnvelope: """Unit tests for function `._signaltools.envelope()`. """ @staticmethod - def assert_close(actual, desired, msg): + def assert_close(actual, desired, msg, xp): + a_r_tol = ({'atol': 1e-12, 'rtol': 1e-12} + if xp_default_dtype(xp) == xp.float64 + else {'atol': 1e-5, 'rtol': 1e-5} + ) + """Little helper to compare to arrays with proper tolerances""" - xp_assert_close(actual, desired, atol=1e-12, rtol=1e-12, err_msg=msg) + xp_assert_close(actual, desired, **a_r_tol, err_msg=msg) def test_envelope_invalid_parameters(self, xp): """For `envelope()` Raise all exceptions that are used to verify function @@ -3474,72 +3525,85 @@ def test_envelope_invalid_parameters(self, xp): envelope(np.ones(3), axis=2) with pytest.raises(ValueError, match=r"z.shape\[axis\] not > 0 for z.shape=.*"): - envelope(np.ones((3, 0)), axis=1) + envelope(xp.ones((3, 0)), axis=1) for bp_in in [(0, 1, 2), (0, 2.), (None, 2.)]: ts = ', '.join(map(str, bp_in)) with pytest.raises(ValueError, match=rf"bp_in=\({ts}\) isn't a 2-tuple of.*"): # noinspection PyTypeChecker - envelope(np.ones(4), bp_in=bp_in) + envelope(xp.ones(4), bp_in=bp_in) with pytest.raises(ValueError, match="n_out=10.0 is not a positive integer or.*"): # noinspection PyTypeChecker - envelope(np.ones(4), n_out=10.) + envelope(xp.ones(4), n_out=10.) for bp_in in [(-1, 3), (1, 1), (0, 10)]: with pytest.raises(ValueError, match=r"`-n//2 <= bp_in\[0\] < bp_in\[1\] <=.*"): - envelope(np.ones(4), bp_in=bp_in) + envelope(xp.ones(4), bp_in=bp_in) with pytest.raises(ValueError, match="residual='undefined' not in .*"): # noinspection PyTypeChecker - envelope(np.ones(4), residual='undefined') + envelope(xp.ones(4), residual='undefined') + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_parameters(self, xp): """Ensure that the various parametrizations produce compatible results. """ - Z, Zr_a = [4, 2, 2, 3, 0], [4, 0, 0, 6, 0, 0, 0, 0] + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + Z = xp.asarray([4.0, 2, 2, 3, 0], dtype=dt_r) + Zr_a = xp.asarray([4.0, 0, 0, 6, 0, 0, 0, 0], dtype=dt_r) z = sp_fft.irfft(Z) - n = len(z) + n = z.shape[0] # the reference envelope: - ze2_0, zr_0 = envelope(z, (1, 3), residual='all', squared=True) - self.assert_close(sp_fft.rfft(ze2_0), np.array([4, 2, 0, 0, 0]).astype(complex), - msg="Envelope calculation error") - self.assert_close(sp_fft.rfft(zr_0), np.array([4, 0, 0, 3, 0]).astype(complex), - msg="Residual calculation error") - - ze_1, zr_1 = envelope(z, (1, 3), residual='all', squared=False) + ze2_0, zr_0 = xp.unstack(envelope(z, (1, 3), residual='all', squared=True)) + self.assert_close(sp_fft.rfft(ze2_0), + xp.asarray([4, 2, 0, 0, 0], dtype=dt_c), + msg="Envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_0), + xp.asarray([4, 0, 0, 3, 0], dtype=dt_c), + msg="Residual calculation error", xp=xp) + + ze_1, zr_1 = xp.unstack(envelope(z, (1, 3), residual='all', squared=False)) self.assert_close(ze_1**2, ze2_0, - msg="Unsquared versus Squared envelope calculation error") + msg="Unsquared versus Squared envelope calculation error", + xp=xp) self.assert_close(zr_1, zr_0, - msg="Unsquared versus Squared residual calculation error") + msg="Unsquared versus Squared residual calculation error", + xp=xp) - ze2_2, zr_2 = envelope(z, (1, 3), residual='all', squared=True, n_out=3*n) + ze2_2, zr_2 = xp.unstack( + envelope(z, (1, 3), residual='all', squared=True, n_out=3*n) + ) self.assert_close(ze2_2[::3], ze2_0, - msg="3x up-sampled envelope calculation error") + msg="3x up-sampled envelope calculation error", xp=xp) self.assert_close(zr_2[::3], zr_0, - msg="3x up-sampled residual calculation error") + msg="3x up-sampled residual calculation error", xp=xp) - ze2_3, zr_3 = envelope(z, (1, 3), residual='lowpass', squared=True) + ze2_3, zr_3 = xp.unstack(envelope(z, (1, 3), residual='lowpass', squared=True)) self.assert_close(ze2_3, ze2_0, - msg="`residual='lowpass'` envelope calculation error") - self.assert_close(sp_fft.rfft(zr_3), np.array([4, 0, 0, 0, 0]).astype(complex), - msg="`residual='lowpass'` residual calculation error") + msg="`residual='lowpass'` envelope calculation error", xp=xp) + self.assert_close(sp_fft.rfft(zr_3), + xp.asarray([4, 0, 0, 0, 0], dtype=dt_c), + msg="`residual='lowpass'` residual calculation error", xp=xp) ze2_4 = envelope(z, (1, 3), residual=None, squared=True) self.assert_close(ze2_4, ze2_0, - msg="`residual=None` envelope calculation error") + msg="`residual=None` envelope calculation error", xp=xp) # compare complex analytic signal to real version - Z_a = np.copy(Z) + Z_a = xp.asarray(Z, copy=True) Z_a[1:] *= 2 z_a = sp_fft.ifft(Z_a, n=n) # analytic signal of Z - self.assert_close(z_a.real, z, - msg="Reference analytic signal error") - ze2_a, zr_a = envelope(z_a, (1, 3), residual='all', squared=True) - self.assert_close(ze2_a, ze2_0.astype(complex), # dtypes must match - msg="Complex envelope calculation error") - self.assert_close(sp_fft.fft(zr_a), np.array(Zr_a).astype(complex), - msg="Complex residual calculation error") - + self.assert_close(xp.real(z_a), z, + msg="Reference analytic signal error", xp=xp) + ze2_a, zr_a = xp.unstack(envelope(z_a, (1, 3), residual='all', squared=True)) + self.assert_close(ze2_a, xp.astype(ze2_0, dt_c), # dtypes must match + msg="Complex envelope calculation error", xp=xp) + self.assert_close(sp_fft.fft(zr_a), xp.asarray(Zr_a, dtype=dt_c), + msg="Complex residual calculation error", xp=xp) + + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize( " Z, bp_in, Ze2_desired, Zr_desired", [([1, 0, 2, 2, 0], (1, None), [4, 2, 0, 0, 0], [1, 0, 0, 0, 0]), @@ -3559,25 +3623,30 @@ def test_envelope_real_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): determined by an FFT and that the absolute square of a Fourier series is again a Fourier series. """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.float64) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.float64) + z = sp_fft.irfft(Z) - ze2, zr = envelope(z, bp_in, residual='all', squared=True) - ze2_lp, zr_lp = envelope(z, bp_in, residual='lowpass', squared=True) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) + ze2_lp, zr_lp = xp.unstack(envelope(z, bp_in, residual='lowpass', squared=True)) Ze2, Zr, Ze2_lp, Zr_lp = (sp_fft.rfft(z_) for z_ in (ze2, zr, ze2_lp, zr_lp)) - Ze2_desired = np.array(Ze2_desired).astype(complex) - Zr_desired = np.array(Zr_desired).astype(complex) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) self.assert_close(Ze2, Ze2_desired, - msg="Envelope calculation error (residual='all')") + msg="Envelope calculation error (residual='all')", xp=xp) self.assert_close(Zr, Zr_desired, - msg="Residual calculation error (residual='all')") + msg="Residual calculation error (residual='all')", xp=xp) if bp_in[1] is not None: Zr_desired[bp_in[1]:] = 0 self.assert_close(Ze2_lp, Ze2_desired, - msg="Envelope calculation error (residual='lowpass')") + msg="Envelope calculation error (residual='lowpass')", xp=xp) self.assert_close(Zr_lp, Zr_desired, - msg="Residual calculation error (residual='lowpass')") + msg="Residual calculation error (residual='lowpass')", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize( " Z, bp_in, Ze2_desired, Zr_desired", [([0, 5, 0, 5, 0], (None, None), [5, 0, 10, 0, 5], [0, 0, 0, 0, 0]), @@ -3590,56 +3659,92 @@ def test_envelope_complex_signals(self, Z, bp_in, Ze2_desired, Zr_desired, xp): We only need to test for the complex envelope here, since the ``Nones``s in the bandpass filter were already tested in the previous test. """ + Z = xp.asarray(Z, dtype=xp.float64) + Ze2_desired = xp.asarray(Ze2_desired, dtype=xp.complex128) + Zr_desired = xp.asarray(Zr_desired, dtype=xp.complex128) + z = sp_fft.ifft(sp_fft.ifftshift(Z)) - ze2, zr = envelope(z, bp_in, residual='all', squared=True) + ze2, zr = xp.unstack(envelope(z, bp_in, residual='all', squared=True)) Ze2, Zr = (sp_fft.fftshift(sp_fft.fft(z_)) for z_ in (ze2, zr)) - self.assert_close(Ze2, np.array(Ze2_desired).astype(complex), - msg="Envelope calculation error") - self.assert_close(Zr, np.array(Zr_desired).astype(complex), - msg="Residual calculation error") + self.assert_close(Ze2, Ze2_desired, + msg="Envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, + msg="Residual calculation error", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_axis_parameter(self, xp): """Test for multi-channel envelope calculations. """ - z = sp_fft.irfft([[1, 0, 2, 2, 0], [7, 0, 4, 4, 0]]) - Ze2_desired = np.array([[4, 2, 0, 0, 0], [16, 8, 0, 0, 0]], - dtype=complex) - Zr_desired = np.array([[1, 0, 0, 0, 0], [7, 0, 0, 0, 0]], dtype=complex) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + + z = sp_fft.irfft(xp.asarray([[1.0, 0, 2, 2, 0], [7, 0, 4, 4, 0]], dtype=dt_r)) + Ze2_desired = xp.asarray([[4, 2, 0, 0, 0], [16, 8, 0, 0, 0]], + dtype=dt_c) + Zr_desired = xp.asarray([[1, 0, 0, 0, 0], [7, 0, 0, 0, 0]], dtype=dt_c) - ze2, zr = envelope(z, squared=True, axis=1) - ye2T, yrT = envelope(z.T, squared=True, axis=0) + ze2, zr = xp.unstack(envelope(z, squared=True, axis=1)) + ye2T, yrT = xp.unstack(envelope(z.T, squared=True, axis=0)) Ze2, Ye2, Zr, Yr = (sp_fft.rfft(z_) for z_ in (ze2, ye2T.T, zr, yrT.T)) - self.assert_close(Ze2, Ze2_desired, msg="2d envelope calculation error") - self.assert_close(Zr, Zr_desired, msg="2d residual calculation error") - self.assert_close(Ye2, Ze2_desired, msg="Transposed 2d envelope calc. error") - self.assert_close(Yr, Zr_desired, msg="Transposed 2d residual calc. error") + self.assert_close(Ze2, Ze2_desired, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_desired, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_desired, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close( + Yr, Zr_desired, msg="Transposed 2d residual calc. error", xp=xp + ) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") def test_envelope_verify_axis_parameter_complex(self, xp): """Test for multi-channel envelope calculations with complex values. """ - z = sp_fft.ifft(sp_fft.ifftshift([[1, 5, 0, 5, 2], [1, 10, 0, 10, 2]], axes=1)) - Ze2_des = np.array([[5, 0, 10, 0, 5], [20, 0, 40, 0, 20],], - dtype=complex) - Zr_des = np.array([[1, 0, 0, 0, 2], [1, 0, 0, 0, 2]], dtype=complex) + dt_r = xp_default_dtype(xp) + dt_c = xp.complex64 if dt_r == xp.float32 else xp.complex128 + inp = xp.asarray([[1.0, 5, 0, 5, 2], [1, 10, 0, 10, 2]], dtype=dt_r) + z = sp_fft.ifft(sp_fft.ifftshift(inp, axes=1)) + Ze2_des = xp.asarray([[5, 0, 10, 0, 5], [20, 0, 40, 0, 20],], dtype=dt_c) + Zr_des = xp.asarray([[1, 0, 0, 0, 2], [1, 0, 0, 0, 2]], dtype=dt_c) kw = dict(bp_in=(-1, 2), residual='all', squared=True) - ze2, zr = envelope(z, axis=1, **kw) - ye2T, yrT = envelope(z.T, axis=0, **kw) + ze2, zr = xp.unstack(envelope(z, axis=1, **kw)) + ye2T, yrT = xp.unstack(envelope(z.T, axis=0, **kw)) Ze2, Ye2, Zr, Yr = (sp_fft.fftshift(sp_fft.fft(z_), axes=1) for z_ in (ze2, ye2T.T, zr, yrT.T)) - self.assert_close(Ze2, Ze2_des, msg="2d envelope calculation error") - self.assert_close(Zr, Zr_des, msg="2d residual calculation error") - self.assert_close(Ye2, Ze2_des, msg="Transposed 2d envelope calc. error") - self.assert_close(Yr, Zr_des, msg="Transposed 2d residual calc. error") + self.assert_close(Ze2, Ze2_des, msg="2d envelope calculation error", xp=xp) + self.assert_close(Zr, Zr_des, msg="2d residual calculation error", xp=xp) + self.assert_close( + Ye2, Ze2_des, msg="Transposed 2d envelope calc. error", xp=xp + ) + self.assert_close(Yr, Zr_des, msg="Transposed 2d residual calc. error", xp=xp) + @skip_xp_backends("jax.numpy", reason="XXX: immutable arrays") @pytest.mark.parametrize('X', [[4, 0, 0, 1, 2], [4, 0, 0, 2, 1, 2]]) def test_compare_envelope_hilbert(self, X, xp): """Compare output of `envelope()` and `hilbert()`. """ + X = xp.asarray(X, dtype=xp.float64) x = sp_fft.irfft(X) - e_hil = np.abs(hilbert(x)) + e_hil = xp.abs(hilbert(x)) e_env = envelope(x, (None, None), residual=None) - self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error") + self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error", xp=xp) + + def test_nyquist(self): + """Test behavior when input is a cosine at the Nyquist frequency. + + Resampling even length signals, requires accounting for unpaired bins at the + Nyquist frequency (consults the source code of `resample`). + + Since `envelope` excludes the Nyquist frequency from the envelope calculation, + only the residues need to be investigated. + """ + x4 = sp_fft.irfft([0, 0, 8]) # = [2, -2, 2, -2] + x6 = signal.resample(x4, num=6) # = [2, -1, -1, 2, -1, -1] + y6, y6_res = envelope(x4, n_out=6, residual='all') # real-valued case + z6, z6_res = envelope(x4 + 0j, n_out=6, residual='all') # complex-valued case + + xp_assert_close(y6, np.zeros(6), atol=1e-12) + xp_assert_close(y6_res, x6, atol=1e-12) def test_nyquist(self): """Test behavior when input is a cosine at the Nyquist frequency. From d3587d98ba04e9c798df4718dd8e91ea95348342 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 11:03:10 +0200 Subject: [PATCH 217/251] MAINT: signal/ndimage: clean up delegators/support_alternative_backends --- scipy/ndimage/_delegators.py | 4 +-- scipy/ndimage/_ni_support.py | 4 --- scipy/signal/_delegators.py | 28 +++++++++++-------- scipy/signal/_support_alternative_backends.py | 3 +- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/scipy/ndimage/_delegators.py b/scipy/ndimage/_delegators.py index 180bb839dbad..bac1286a56c0 100644 --- a/scipy/ndimage/_delegators.py +++ b/scipy/ndimage/_delegators.py @@ -25,7 +25,7 @@ """ import numpy as np from scipy._lib._array_api import array_namespace -from scipy.ndimage._ni_support import _skip_if_dtype, _skip_if_int +from scipy.ndimage._ni_support import _skip_if_dtype def affine_transform_signature( @@ -202,7 +202,7 @@ def maximum_filter1d_signature(input, size, axis=-1, output=None, *args, **kwds) def maximum_signature(input, labels=None, index=None): - return array_namespace(input, labels, _skip_if_int(index)) + return array_namespace(input, labels, index) minimum_signature = maximum_signature median_signature = maximum_signature diff --git a/scipy/ndimage/_ni_support.py b/scipy/ndimage/_ni_support.py index 9b9e8b2c8feb..52c526347653 100644 --- a/scipy/ndimage/_ni_support.py +++ b/scipy/ndimage/_ni_support.py @@ -137,7 +137,3 @@ def _skip_if_dtype(arg): return None if issubclass(arg, np.generic) else arg else: return None if isinstance(arg, np.dtype) else arg - - -def _skip_if_int(arg): - return None if (arg is None or isinstance(arg, int)) else arg diff --git a/scipy/signal/_delegators.py b/scipy/signal/_delegators.py index e0e5c877641b..1d0363b0040e 100644 --- a/scipy/signal/_delegators.py +++ b/scipy/signal/_delegators.py @@ -28,8 +28,7 @@ """ import numpy as np -from scipy._lib._array_api import array_namespace -from scipy.ndimage._ni_support import _skip_if_int +from scipy._lib._array_api import array_namespace, np_compat def _skip_if_lti(arg): @@ -57,9 +56,6 @@ def _skip_if_poly1d(arg): return None if isinstance(arg, np.poly1d) else arg -def _skip_if_float(arg): - return None if isinstance(arg, float) else arg - ################### def abcd_normalize_signature(A=None, B=None, C=None, D=None): @@ -301,7 +297,7 @@ def deconvolve_signature(signal, divisor): def detrend_signature(data, axis=1, type='linear', bp=0, *args, **kwds): - return array_namespace(data, _skip_if_int(bp)) + return array_namespace(data, bp) def filtfilt_signature(b, a, x, *args, **kwds): @@ -312,6 +308,10 @@ def lfilter_signature(b, a, x, axis=-1, zi=None): return array_namespace(b, a, x, zi) +def envelope_signature(z, *args, **kwds): + return array_namespace(z) + + def find_peaks_signature( x, height=None, threshold=None, distance=None, prominence=None, width=None, wlen=None, rel_height=0.5, plateau_size=None @@ -334,7 +334,11 @@ def firls_signature(numtaps, bands, desired, *, weight=None, fs=None): def firwin_signature(numtaps, cutoff, *args, **kwds): - return array_namespace(cutoff) + if isinstance(cutoff, int | float): + xp = np_compat + else: + xp = array_namespace(cutoff) + return xp def firwin2_signature(numtaps, freq, gain, *args, **kwds): @@ -342,19 +346,19 @@ def firwin2_signature(numtaps, freq, gain, *args, **kwds): def freqs_zpk_signature(z, p, k, worN, *args, **kwds): - return array_namespace(z, p, _skip_if_int(worN)) + return array_namespace(z, p, worN) freqz_zpk_signature = freqs_zpk_signature def freqs_signature(b, a, worN=200, *args, **kwds): - return array_namespace(b, a, _skip_if_int(worN)) + return array_namespace(b, a, worN) freqz_signature = freqs_signature def freqz_sos_signature(sos, worN=512, *args, **kwds): - return array_namespace(sos, _skip_if_int(worN)) + return array_namespace(sos, worN) sosfreqz_signature = freqz_sos_signature @@ -365,7 +369,7 @@ def gausspulse_signature(t, *args, **kwds): def group_delay_signature(system, w=512, whole=False, fs=6.283185307179586): - return array_namespace(_skip_if_str_or_tuple(system), _skip_if_int(w)) + return array_namespace(_skip_if_str_or_tuple(system), w) def hilbert_signature(x, N=None, axis=-1): @@ -522,7 +526,7 @@ def symiirorder1_signature(signal, c0, z1, precision=-1.0): def symiirorder2_signature(input, r, omega, precision=-1.0): - return array_namespace(input, _skip_if_float(r), _skip_if_float(omega)) + return array_namespace(input, r, omega) def cspline1d_signature(signal, *args, **kwds): diff --git a/scipy/signal/_support_alternative_backends.py b/scipy/signal/_support_alternative_backends.py index a08b7da3068d..173e21d757ff 100644 --- a/scipy/signal/_support_alternative_backends.py +++ b/scipy/signal/_support_alternative_backends.py @@ -20,11 +20,12 @@ ] # some cupyx.scipy.signal functions are incompatible with their scipy counterparts -CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi', 'get_window'] +CUPY_BLACKLIST = ['lfilter_zi', 'sosfilt_zi', 'get_window', 'envelope', 'remez'] # freqz_sos is a sosfreqz rename, and cupy does not have the new name yet (in v13.x) CUPY_RENAMES = {'freqz_sos': 'sosfreqz'} + def delegate_xp(delegator, module_name): def inner(func): @functools.wraps(func) From bb68857d7ef337f356a0d3ae502e1bb29a5fb69a Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 11:41:08 +0200 Subject: [PATCH 218/251] BUG: signal: fix firwin for non-numpy inputs --- scipy/signal/_fir_filter_design.py | 13 +++++++------ scipy/signal/tests/test_fir_filter_design.py | 12 ++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 0e81cc0fdc57..0fd291ae4942 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -384,7 +384,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, nyq = 0.5 * fs cutoff = xp.asarray(cutoff, dtype=xp.float64) - cutoff = xpx.atleast_nd(cutoff, ndim=1) / float(nyq) + cutoff = xpx.atleast_nd(cutoff, ndim=1, xp=xp) / float(nyq) # Check for invalid input. if cutoff.ndim > 1: @@ -444,21 +444,22 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, # Build up the coefficients. alpha = 0.5 * (numtaps - 1) - m = xp.arange(0, numtaps) - alpha + m = xp.arange(0, numtaps, dtype=cutoff.dtype) - alpha h = 0 - for left, right in bands: + for j in range(bands.shape[0]): + left, right = bands[j, 0], bands[j, 1] h += right * xpx.sinc(right * m, xp=xp) h -= left * xpx.sinc(left * m, xp=xp) # Get and apply the window function. from .windows import get_window - win = get_window(window, numtaps, fftbins=False) + win = get_window(window, numtaps, fftbins=False, xp=xp) h *= win # Now handle scaling if desired. if scale: # Get the first passband. - left, right = bands[0] + left, right = bands[0, ...] if left == 0: scale_frequency = 0.0 elif right == 1: @@ -466,7 +467,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, else: scale_frequency = 0.5 * (left + right) c = xp.cos(xp.pi * m * scale_frequency) - s = np.sum(h * c) + s = xp.sum(h * c) h /= s return h diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py index 9812bd5f1627..875c6cbab448 100644 --- a/scipy/signal/tests/test_fir_filter_design.py +++ b/scipy/signal/tests/test_fir_filter_design.py @@ -137,7 +137,6 @@ def test_fs_validation(self): firwin(51, .5, fs=np.array([10, 20])) -@skip_xp_backends(cpu_only=True, reason="TODO convert freqs/freqz") class TestFirWinMore: """Different author, different style, different tests...""" @@ -272,8 +271,12 @@ def test_fs_nyq(self, xp): assert_array_almost_equal(xp.abs(response), xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) - @skip_xp_backends(np_only=True) - def test_bad_cutoff(self, xp): + def test_array_cutoff(self, xp): + taps = firwin(3, xp.asarray([.1, .2])) + # the value is as computed by scipy==1.5.2 + xp_assert_close(taps, xp.asarray([-0.00801395, 1.0160279, -0.00801395]), atol=1e-8) + + def test_bad_cutoff(self): """Test that invalid cutoff argument raises ValueError.""" # cutoff values must be greater than 0 and less than 1. assert_raises(ValueError, firwin, 99, -0.5) @@ -292,14 +295,12 @@ def test_bad_cutoff(self, xp): assert_raises(ValueError, firwin, 99, 50.0, fs=80) assert_raises(ValueError, firwin, 99, [10, 20, 30], fs=50) - @skip_xp_backends(np_only=True) def test_even_highpass_raises_value_error(self): """Test that attempt to create a highpass filter with an even number of taps raises a ValueError exception.""" assert_raises(ValueError, firwin, 40, 0.5, pass_zero=False) assert_raises(ValueError, firwin, 40, [.25, 0.5]) - @skip_xp_backends(np_only=True) def test_bad_pass_zero(self): """Test degenerate pass_zero cases.""" with assert_raises(ValueError, match="^Parameter pass_zero='foo' not in "): @@ -313,7 +314,6 @@ def test_bad_pass_zero(self): with assert_raises(ValueError, match='must have at least two'): firwin(41, [0.5], pass_zero=pass_zero) - @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firwin2(51, .5, 1, fs=np.array([10, 20])) From ca6fc21e39cd6f63f5cf9deb7e0a44b392fc9cc7 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 10:25:47 +0000 Subject: [PATCH 219/251] TST: signal: fix/expand firwin tests --- scipy/signal/_signaltools.py | 2 +- scipy/signal/tests/test_fir_filter_design.py | 94 ++++++++------------ 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index f941906097f9..caab2ea34631 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -27,7 +27,7 @@ from ._sosfilt import _sosfilt from scipy._lib._array_api import ( - array_namespace, is_torch, is_numpy, xp_copy, xp_size, xp_device, + array_namespace, is_torch, is_numpy, xp_copy, xp_size, ) from scipy._lib.array_api_compat import is_array_api_obj diff --git a/scipy/signal/tests/test_fir_filter_design.py b/scipy/signal/tests/test_fir_filter_design.py index 875c6cbab448..2c713f9d8c9a 100644 --- a/scipy/signal/tests/test_fir_filter_design.py +++ b/scipy/signal/tests/test_fir_filter_design.py @@ -8,7 +8,7 @@ import scipy._lib.array_api_extra as xpx from scipy._lib._array_api import ( xp_assert_close, xp_assert_equal, assert_almost_equal, assert_array_almost_equal, - array_namespace, xp_default_dtype, np_compat + array_namespace, xp_default_dtype ) from scipy.fft import fft, fft2 from scipy.signal import (kaiser_beta, kaiser_atten, kaiserord, @@ -143,21 +143,17 @@ class TestFirWinMore: def test_lowpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=0.5, window=('kaiser', beta), scale=False) + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal( - taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat - ) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. freq_samples = xp.asarray([0.0, 0.25, 0.5-width/2, 0.5+width/2, 0.75, 1.0]) - freqs, response = freqz(taps, worN=np.pi*freq_samples) - - freqs = xp.asarray(freqs) # XXX: convert freqz - response = xp.asarray(response) + freqs, response = freqz(taps, worN=xp.pi*freq_samples) assert_array_almost_equal( xp.abs(response), @@ -165,7 +161,7 @@ def test_lowpass(self, xp): ) taps_str = firwin(ntaps, pass_zero='lowpass', **kwargs) - xp_assert_close(taps, taps_str, xp=np_compat) + xp_assert_close(taps, taps_str) def test_highpass(self, xp): width = 0.04 @@ -174,61 +170,56 @@ def test_highpass(self, xp): # Ensure that ntaps is odd. ntaps |= 1 - kwargs = dict(cutoff=0.5, window=('kaiser', beta), scale=False) + cutoff = xp.asarray(0.5) + kwargs = dict(cutoff=cutoff, window=('kaiser', beta), scale=False) taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal( - taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat - ) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. freq_samples = xp.asarray([0.0, 0.25, 0.5 - width/2, 0.5 + width/2, 0.75, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - freqs, response = xp.asarray(freqs), xp.asarray(response) assert_array_almost_equal(xp.abs(response), xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='highpass', **kwargs) - xp_assert_close(taps, taps_str, xp=np_compat) + xp_assert_close(taps, taps_str) def test_bandpass(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=[0.3, 0.7], window=('kaiser', beta), scale=False) + kwargs = dict( + cutoff=xp.asarray([0.3, 0.7]), window=('kaiser', beta), scale=False + ) taps = firwin(ntaps, pass_zero=False, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal( - taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat - ) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. freq_samples = xp.asarray([0.0, 0.2, 0.3 - width/2, 0.3 + width/2, 0.5, 0.7 - width/2, 0.7 + width/2, 0.8, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - freqs, response = xp.asarray(freqs), xp.asarray(response) assert_array_almost_equal(xp.abs(response), xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) taps_str = firwin(ntaps, pass_zero='bandpass', **kwargs) - xp_assert_close(taps, taps_str, xp=np_compat) + xp_assert_close(taps, taps_str) def test_bandstop_multi(self, xp): width = 0.04 ntaps, beta = kaiserord(120, width) - kwargs = dict(cutoff=[0.2, 0.5, 0.8], window=('kaiser', beta), + kwargs = dict(cutoff=xp.asarray([0.2, 0.5, 0.8]), window=('kaiser', beta), scale=False) taps = firwin(ntaps, **kwargs) # Check the symmetry of taps. - assert_array_almost_equal( - taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2-1:-1], xp=np_compat - ) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. @@ -236,7 +227,6 @@ def test_bandstop_multi(self, xp): 0.5 - width/2, 0.5 + width/2, 0.65, 0.8 - width/2, 0.8 + width/2, 0.9, 1.0]) freqs, response = freqz(taps, worN=np.pi*freq_samples) - freqs, response = xp.asarray(freqs), xp.asarray(response) assert_array_almost_equal( xp.abs(response), @@ -245,7 +235,7 @@ def test_bandstop_multi(self, xp): ) taps_str = firwin(ntaps, pass_zero='bandstop', **kwargs) - xp_assert_close(taps, taps_str, xp=np_compat) + xp_assert_close(taps, taps_str) def test_fs_nyq(self, xp): """Test the fs and nyq keywords.""" @@ -253,28 +243,27 @@ def test_fs_nyq(self, xp): width = 40.0 relative_width = width/nyquist ntaps, beta = kaiserord(120, relative_width) - taps = firwin(ntaps, cutoff=[300, 700], window=('kaiser', beta), + taps = firwin(ntaps, cutoff=xp.asarray([300, 700]), window=('kaiser', beta), pass_zero=False, scale=False, fs=2*nyquist) # Check the symmetry of taps. - assert_array_almost_equal( - taps[:ntaps//2], taps[ntaps:ntaps-ntaps//2 - 1:-1], xp=np_compat - ) + assert_array_almost_equal(taps[:ntaps//2], xp.flip(taps)[:ntaps//2]) # Check the gain at a few samples where # we know it should be approximately 0 or 1. freq_samples = xp.asarray([0.0, 200, 300 - width/2, 300 + width/2, 500, 700 - width/2, 700 + width/2, 800, 1000]) freqs, response = freqz(taps, worN=np.pi*freq_samples/nyquist) - freqs, response = xp.asarray(freqs), xp.asarray(response) assert_array_almost_equal(xp.abs(response), xp.asarray([0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0]), decimal=5) def test_array_cutoff(self, xp): taps = firwin(3, xp.asarray([.1, .2])) - # the value is as computed by scipy==1.5.2 - xp_assert_close(taps, xp.asarray([-0.00801395, 1.0160279, -0.00801395]), atol=1e-8) + # smoke test against the value computed by scipy==1.5.2 + xp_assert_close( + taps, xp.asarray([-0.00801395, 1.0160279, -0.00801395]), atol=1e-8 + ) def test_bad_cutoff(self): """Test that invalid cutoff argument raises ValueError.""" @@ -322,8 +311,7 @@ def test_fs_validation(self): @skip_xp_backends(cpu_only=True, reason="firwin2 uses np.interp") class TestFirwin2: - @skip_xp_backends(np_only=True) - def test_invalid_args(self, xp): + def test_invalid_args(self): # `freq` and `gain` have different lengths. with assert_raises(ValueError, match='must be of same length'): firwin2(50, [0, 0.5, 1], [0.0, 1.0]) @@ -483,8 +471,7 @@ def test_fs_nyq(self, xp): fs=120.0) assert_array_almost_equal(taps1, taps2) - @skip_xp_backends(np_only=True, reason="test array-likes") - def test_tuple(self, xp): + def test_tuple(self): taps1 = firwin2(150, (0.0, 0.5, 0.5, 1.0), (1.0, 1.0, 0.0, 0.0)) taps2 = firwin2(150, [0.0, 0.5, 0.5, 1.0], [1.0, 1.0, 0.0, 0.0]) assert_array_almost_equal(taps1, taps2) @@ -500,12 +487,10 @@ def test_input_modyfication(self, xp): @skip_xp_backends(cpu_only=True) class TestRemez: - @skip_xp_backends(np_only=True) - def test_bad_args(self, xp): + def test_bad_args(self): assert_raises(ValueError, remez, 11, [0.1, 0.4], [1], type='pooka') - @skip_xp_backends(np_only=True) - def test_hilbert(self, xp): + def test_hilbert(self): N = 11 # number of taps in the filter a = 0.1 # width of the transition band @@ -557,8 +542,7 @@ def test_compare(self, xp): xp.asarray(h, dtype=xp.float64), **atol_arg ) - @skip_xp_backends(np_only=True) - def test_fs_validation(self, xp): + def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): remez(11, .1, 1, fs=np.array([10, 20])) @@ -567,8 +551,7 @@ def test_fs_validation(self, xp): @skip_xp_backends(cpu_only=True, reason="lstsq") class TestFirls: - @skip_xp_backends(np_only=True) - def test_bad_args(self, xp): + def test_bad_args(self): # even numtaps assert_raises(ValueError, firls, 10, [0.1, 0.2], [0, 0]) # odd bands @@ -689,16 +672,14 @@ def test_rank_deficient(self, xp): habs = xp.abs(h[mask]) xp_assert_close(habs, xp.zeros_like(habs), atol=1e-4) - @skip_xp_backends(np_only=True) def test_fs_validation(self): with pytest.raises(ValueError, match="Sampling.*single scalar"): firls(11, .1, 1, fs=np.array([10, 20])) class TestMinimumPhase: - @skip_xp_backends(np_only=True) @pytest.mark.thread_unsafe - def test_bad_args(self, xp): + def test_bad_args(self): # not enough taps assert_raises(ValueError, minimum_phase, [1.]) assert_raises(ValueError, minimum_phase, [1., 1.]) @@ -710,8 +691,7 @@ def test_bad_args(self, xp): with pytest.raises(ValueError, match="is only supported when"): minimum_phase(np.ones(3), method='hilbert', half=False) - @skip_xp_backends(np_only=True) - def test_homomorphic(self, xp): + def test_homomorphic(self): # check that it can recover frequency responses of arbitrary # linear-phase filters @@ -763,15 +743,15 @@ def test_hilbert(self, xp): class Testfirwin_2d: def test_invalid_args(self): - with pytest.raises(ValueError, + with pytest.raises(ValueError, match="hsize must be a 2-element tuple or list"): firwin_2d((50,), window=(("kaiser", 5.0), "boxcar"), fc=0.4) - - with pytest.raises(ValueError, + + with pytest.raises(ValueError, match="window must be a 2-element tuple or list"): firwin_2d((51, 51), window=("hamming",), fc=0.5) - - with pytest.raises(ValueError, + + with pytest.raises(ValueError, match="window must be a 2-element tuple or list"): firwin_2d((51, 51), window="invalid_window", fc=0.5) From 055a4c8f70f80dc2deb10fe01a440e07b77e23f4 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 10:30:57 +0000 Subject: [PATCH 220/251] MAINT: signal: remove a duplicate test --- scipy/signal/tests/test_signaltools.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index 7fd0a80d84eb..fccaa7aa531d 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -3729,23 +3729,6 @@ def test_compare_envelope_hilbert(self, X, xp): e_env = envelope(x, (None, None), residual=None) self.assert_close(e_hil, e_env, msg="Hilbert-Envelope comparison error", xp=xp) - def test_nyquist(self): - """Test behavior when input is a cosine at the Nyquist frequency. - - Resampling even length signals, requires accounting for unpaired bins at the - Nyquist frequency (consults the source code of `resample`). - - Since `envelope` excludes the Nyquist frequency from the envelope calculation, - only the residues need to be investigated. - """ - x4 = sp_fft.irfft([0, 0, 8]) # = [2, -2, 2, -2] - x6 = signal.resample(x4, num=6) # = [2, -1, -1, 2, -1, -1] - y6, y6_res = envelope(x4, n_out=6, residual='all') # real-valued case - z6, z6_res = envelope(x4 + 0j, n_out=6, residual='all') # complex-valued case - - xp_assert_close(y6, np.zeros(6), atol=1e-12) - xp_assert_close(y6_res, x6, atol=1e-12) - def test_nyquist(self): """Test behavior when input is a cosine at the Nyquist frequency. From 24aecf6f2c755a2d011afb72881ffee4871c2116 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 10:36:54 +0000 Subject: [PATCH 221/251] TST: signal: skip a test under jax --- scipy/signal/tests/test_signaltools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scipy/signal/tests/test_signaltools.py b/scipy/signal/tests/test_signaltools.py index fccaa7aa531d..10c5c245fc94 100644 --- a/scipy/signal/tests/test_signaltools.py +++ b/scipy/signal/tests/test_signaltools.py @@ -1424,6 +1424,7 @@ def test_basic(self, xp): @pytest.mark.parametrize('window', (None, 'hamming')) @pytest.mark.parametrize('N', (20, 19)) @pytest.mark.parametrize('num', (100, 101, 10, 11)) + @skip_xp_backends('jax.numpy', reason='immutable arrays') def test_rfft(self, N, num, window, xp): # Make sure the speed up using rfft gives the same result as the normal # way using fft From 2efb7a66ca77bf0c2530cfce0f2907bb20f85327 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Wed, 14 May 2025 16:16:22 +0200 Subject: [PATCH 222/251] BUG: signal: fix issues for the float32 default --- scipy/signal/_fir_filter_design.py | 2 +- scipy/signal/_signaltools.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scipy/signal/_fir_filter_design.py b/scipy/signal/_fir_filter_design.py index 0fd291ae4942..eb3a2702f66f 100644 --- a/scipy/signal/_fir_filter_design.py +++ b/scipy/signal/_fir_filter_design.py @@ -383,7 +383,7 @@ def firwin(numtaps, cutoff, *, width=None, window='hamming', pass_zero=True, nyq = 0.5 * fs - cutoff = xp.asarray(cutoff, dtype=xp.float64) + cutoff = xp.asarray(cutoff, dtype=xp_default_dtype(xp)) cutoff = xpx.atleast_nd(cutoff, ndim=1, xp=xp) / float(nyq) # Check for invalid input. diff --git a/scipy/signal/_signaltools.py b/scipy/signal/_signaltools.py index caab2ea34631..a64b307836c3 100644 --- a/scipy/signal/_signaltools.py +++ b/scipy/signal/_signaltools.py @@ -27,7 +27,7 @@ from ._sosfilt import _sosfilt from scipy._lib._array_api import ( - array_namespace, is_torch, is_numpy, xp_copy, xp_size, + array_namespace, is_torch, is_numpy, xp_copy, xp_size, xp_default_dtype ) from scipy._lib.array_api_compat import is_array_api_obj @@ -3769,6 +3769,7 @@ def resample(x, num, t=None, axis=0, window=None, domain='time'): W = xp.asarray(window, copy=True) # prevent modifying the function parameters else: W = sp_fft.fftshift(get_window(window, n_x, xp=xp)) + W = xp.astype(W, xp_default_dtype(xp)) # get_window always returns float64 if domain == 'time' and not xp.isdtype(x.dtype, 'complex floating'): # use rfft(): X = sp_fft.rfft(x) From 6754788d30477f16064c950f689961d532102a96 Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 17 May 2025 14:04:28 -0600 Subject: [PATCH 223/251] REL: set version to 1.17.0.dev0 * Don't merge until the `1.16.0` release notes updates have been merged and the `maintenance/1.16.x` branch has been pushed up (I'll probably merge when the time is right). * Some of the adjusted dependency version ranges in the `1.17.0` release notes are of course just drafts/subject to change. [docs only] --- doc/source/release.rst | 1 + doc/source/release/1.17.0-notes.rst | 112 ++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 doc/source/release/1.17.0-notes.rst diff --git a/doc/source/release.rst b/doc/source/release.rst index ba2fd4db2ed2..915e1dfc4457 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -8,6 +8,7 @@ see the `commit logs `_. .. toctree:: :maxdepth: 1 + release/1.17.0-notes release/1.16.0-notes release/1.15.3-notes release/1.15.2-notes diff --git a/doc/source/release/1.17.0-notes.rst b/doc/source/release/1.17.0-notes.rst new file mode 100644 index 000000000000..ca6cd6edefca --- /dev/null +++ b/doc/source/release/1.17.0-notes.rst @@ -0,0 +1,112 @@ +========================== +SciPy 1.17.0 Release Notes +========================== + +.. note:: SciPy 1.17.0 is not released yet! + +.. contents:: + +SciPy 1.17.0 is the culmination of X months of hard work. It contains +many new features, numerous bug-fixes, improved test coverage and better +documentation. There have been a number of deprecations and API changes +in this release, which are documented below. All users are encouraged to +upgrade to this release, as there are a large number of bug-fixes and +optimizations. Before upgrading, we recommend that users check that +their own code does not use deprecated SciPy functionality (to do so, +run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). +Our development attention will now shift to bug-fix releases on the +1.17.x branch, and on adding new features on the main branch. + +This release requires Python 3.11-3.14 and NumPy 1.25.2 or greater. + + +************************** +Highlights of this release +************************** + + +************ +New features +************ + +`scipy.cluster` improvements +============================ + + +`scipy.interpolate` improvements +================================ + + +`scipy.linalg` improvements +=========================== + + +`scipy.ndimage` improvements +============================ + + +`scipy.optimize` improvements +============================= + + +`scipy.signal` improvements +=========================== + + +`scipy.sparse` improvements +=========================== + + + +`scipy.spatial` improvements +============================ + + +`scipy.special` improvements +============================ + + +`scipy.stats` improvements +========================== + + + +******************* +Deprecated features +******************* + +`scipy.linalg` deprecations +=========================== + + +`scipy.spatial` deprecations +============================ + + + +****************************** +Backwards incompatible changes +****************************** + +************* +Other changes +************* + + + +******* +Authors +******* + + + +************************ +Issues closed for 1.17.0 +************************ + + +************************ +Pull requests for 1.17.0 +************************ + + diff --git a/pyproject.toml b/pyproject.toml index be113ca46f9a..fcf035f07f86 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ requires = [ [project] name = "scipy" -version = "1.16.0.dev0" +version = "1.17.0.dev0" # TODO: add `license-files` once PEP 639 is accepted (see meson-python#88) # at that point, no longer include them in `py3.install_sources()` license = { file = "LICENSE.txt" } From 291cc0506110a54202779a158b4ab31ab16694ea Mon Sep 17 00:00:00 2001 From: Tyler Reddy Date: Sat, 17 May 2025 21:03:03 -0600 Subject: [PATCH 224/251] DOC: update SciPy 1.16.0 release notes (#22991) * Update the SciPy `1.16.0` release notes before branching. --------- Co-authored-by: Matt Haberland Co-authored-by: Dan Schult --- .mailmap | 3 +- doc/source/release/1.16.0-notes.rst | 969 +++++++++++++++++++++++++++- 2 files changed, 947 insertions(+), 25 deletions(-) diff --git a/.mailmap b/.mailmap index 2761f330315b..3655c37384fa 100644 --- a/.mailmap +++ b/.mailmap @@ -207,7 +207,8 @@ Franziska Horn cod3licious levelfour G Young gfyoung G Young gfyoung -Gagandeep Singh czgdp1807 +ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) czgdp1807 +ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) Gagandeep Singh Ganesh Kathiresan ganesh-k13 Garrett Reynolds Garrett-R Gaël Varoquaux Gael varoquaux diff --git a/doc/source/release/1.16.0-notes.rst b/doc/source/release/1.16.0-notes.rst index 5b66a25ba1a0..7b3296a3544b 100644 --- a/doc/source/release/1.16.0-notes.rst +++ b/doc/source/release/1.16.0-notes.rst @@ -6,7 +6,7 @@ SciPy 1.16.0 Release Notes .. contents:: -SciPy 1.16.0 is the culmination of X months of hard work. It contains +SciPy 1.16.0 is the culmination of 6 months of hard work. It contains many new features, numerous bug-fixes, improved test coverage and better documentation. There have been a number of deprecations and API changes in this release, which are documented below. All users are encouraged to @@ -17,100 +17,278 @@ run your code with ``python -Wd`` and check for ``DeprecationWarning`` s). Our development attention will now shift to bug-fix releases on the 1.16.x branch, and on adding new features on the main branch. -This release requires Python 3.11-3.13 and NumPy 1.26.4 or greater. +This release requires Python 3.11-3.13 and NumPy 1.25.2 or greater. ************************** Highlights of this release ************************** +- Improved experimental support for the Python array API standard, including + new support in `scipy.signal`, and additional support in `scipy.stats` and + `scipy.special`. Improved support for JAX and Dask backends has been added, + with notable support in `scipy.cluster.hierarchy`, many functions in + `scipy.special`, and many of the trimmed ``stats`` functions. - ``scipy.optimize`` now uses the new Python implementation from the `PRIMA `_ package for COBYLA. The PRIMA implementation `fixes many bugs `_ in the old Fortran 77 implementation with `a better performance on average `_. +- ``scipy.sparse.coo_array`` now supports nD arrays with reshaping, arithmetic and + reduction operations like sum/mean/min/max. No nD indexing or ``random_array`` + support yet. +- Updated guide and tools for migration from sparse matrices to sparse arrays. +- All functions in the `scipy.linalg` namespace that accept array arguments + now support N-dimensional arrays to be processed as a batch. +- Two new `scipy.signal` functions, ``firwin_2d`` and + ``closest_STFT_dual_window``, for creation of a 2-D FIR filter and + ``ShortTimeFFT`` dual window calculation, respectivly. +- A new class, ``RigidTransform``, is available in the ``transform`` submodule. + It provides functionality to convert between different representations of + rigid transforms in 3D space. ************ New features ************ -``scipy.cluster`` improvements +``scipy.io`` improvements ============================== +- ``savemat`` now provides informative warnings for invalid field names. +- `scipy.io.mmread` now provides a clearer error message when provided with + a source file path that does not exist. +- `scipy.io.wavfile.read` can now read non-seekable files. -``scipy.differentiate`` improvements -==================================== - +``scipy.integrate`` improvements +================================== +- Improved the error estimate of `scipy.integrate.tanhsinh` ``scipy.interpolate`` improvements ================================== - +- Added batch support to `scipy.interpolate.make_smoothing_spline` ``scipy.linalg`` improvements ============================= - +- All functions in the `scipy.linalg` namespace that accept array arguments + now support N-dimensional arrays to be processed as a batch. + See :ref:`linalg_batch` for details. +- ``linalg.sqrtm`` is rewritten in C and its performance is improved. It also + tries harder to return real-valued results for real-valued inputs if + possible. See the function docstring for more details. In this version the + input argument ``disp`` and the optional output argument ``errest`` are + deprecated and will be removed four versions later. Similarly, after + changing the underlying algorithm to recursion, the ``blocksize`` keyword + argument has no effect and will be removed two versions later. +- Added wrappers for ``?stevd``, ``?langb``, ``?sytri``, ``?hetri`` and + ``?gbcon`` to `scipy.linalg.lapack`. +- Improved default driver of `scipy.linalg.eigh_tridiagonal`. +- `scipy.linalg.solve` can now estimate the reciprocal condition number and + the matrix norm calculation is more efficient. ``scipy.ndimage`` improvements ============================== - +- Added `scipy.ndimage.vectorized_filter` for generic filters that take advantage + of a vectorized Python callable. +- `scipy.ndimage.rotate` has improved performance, especially on ARM platforms. ``scipy.optimize`` improvements =============================== - COBYLA was updated to use the new Python implementation from the `PRIMA `_ package. - The PRIMA implementation `fixes many bugs `_ + The PRIMA implementation + `fixes many bugs `_ in the old Fortran 77 implementation. In addition, it results in `fewer function evaluations on average `_, but it depends on the problem and for some problems it can result in more function evaluations or a less optimal result. For those cases the user can try modifying the initial and final - trust region radii given by `rhobeg` and `tol` respectively. A larger `rhobeg` - can help the algorithm take bigger steps initially, while a smaller `tol` - can help it for keep going and find a better solution. + trust region radii given by ``rhobeg`` and ``tol`` respectively. A larger + ``rhobeg`` can help the algorithm take bigger steps initially, while a + smaller ``tol`` can help it continue and find a better solution. For more information, see the `PRIMA documentation `_. +- Several of the ``minimize`` methods, and the ``least_squares`` function, + have been given a ``workers`` keyword. This allows parallelization of some + calculations via a map-like callable, such as ``multiprocessing.Pool``. These + parallelization opportunities typically occur during numerical + differentiation. This can greatly speed-up minimization when the objective + function is expensive to calculate. +- The ``lm`` method of ``least_squares`` can now accept ``3-point`` and ``cs`` + for the ``jac`` keyword. +- SLSQP Fortran77 code ported to C. When this method is used now the constraint + multipliers are exposed to the user through the ``multiplier`` keyword of + the returned ``OptimizeResult`` object. +- NNLS code has been corrected and rewritten in C to address the performance + regression introduced in 1.15.x +- ``optimize.root`` now warns for invalid inner parameters when using the + ``newton_krylov`` method +- The return value of minimization with ``method='L-BFGS-B'`` now has + a faster ``hess_inv.todense()`` implementation. Time complexity has improved + from cubic to quadratic. +- ``optimize.least_squares`` has a new ``callback`` argument that is applicable + to ``trf`` and ``dogbox`` methods. ``callback`` may be used to track + optimization results at each step or to provide custom conditions for + stopping. ``scipy.signal`` improvements ============================= +- A new function ``firwin_2d`` for the creation of a 2-D FIR Filter using the + 1-D window method was added. +- ``signal.cspline1d_eval`` and ``signal.qspline1d_eval`` now provide an + informative error on empty input rather than hitting the recursion limit. +- A new function `scipy.signal.closest_STFT_dual_window` to calculate the + ``ShortTimeFFT`` dual window of a given window closest to a desired dual + window. +- A new classmethod `scipy.signal.ShortTimeFFT.from_win_equals_dual` to + create a ``ShortTimeFFT`` instance where the window and its dual are equal + up to a scaling factor. It allows to create short-time Fourier transforms + which are unitary mappings. +- The performance of ``signal.convolve2d`` has improved. ``scipy.sparse`` improvements ============================= +- ``coo_array`` supports nD arrays using binary and reduction operations. +- Math is faster between two DIA arrays/matrices: add, sub, multiply, matmul. +- ``csgraph.dijkstra`` shortest_path is more efficient. +- ``csgraph.yen`` has performance improvements. +- Import of ``_lib.issparse`` allows checking for ``scipy.sparse`` object + without full import of ``scipy``. +- add lazy loading of ``sparse.csgraph`` and ``sparse.linalg``. ``scipy.spatial`` improvements ============================== - - -``scipy.special`` improvements -============================== +- A new class ``RigidTransform`` is available in the ``transform`` submodule. It + provides functionality to convert between different representations of rigid + transforms in 3D space, its application to vectors and transform composition. + It follows the same design approach as `scipy.spatial.transform.Rotation`. +- `scipy.spatial.transform.Rotation` now has an appropriate ``__repr__`` method. +- ``Rotation.apply`` has improved performance. ``scipy.stats`` improvements ============================ +- Added `scipy.stats.quantile`, an array API compatible function for quantile + estimation. +- Extended `scipy.stats.make_distribution` to: + - work with existing discrete distributions and + - facilitate the creation of custom distributions + in the new random variable infrastructure. -******************* -Deprecated features -******************* +- Added `scipy.stats.Binomial`. +- Added ``equal_var`` keyword to: -``scipy.linalg`` deprecations -============================= + - `scipy.stats.tukey_hsd` (enables the Games-Howell test) and + - `scipy.stats.f_oneway` (enables Welch ANOVA). +- Improved moment calculation for `scipy.stats.gennorm`. +- Added native vectorization to `scipy.stats.mode` for faster batch calculation. +- Added ``axis``, ``nan_policy``, and ``keepdims`` to `scipy.stats.power_divergence`, + `scipy.stats.chisquare`, `scipy.stats.pointbiserialr`, `scipy.stats.kendalltau`, + `scipy.stats.weightedtau`, `scipy.stats.theilslopes`, `scipy.stats.siegelslopes`, + and `scipy.stats.boxcox_llf`. +- Improved speed of `scipy.stats.special_ortho_group`. +- Improved speed of `scipy.stats.pearsonr`. + + +************************** +Array API Standard Support +************************** + +Experimental support for array libraries other than NumPy has been added to +existing sub-packages in recent versions of SciPy. Please consider testing +these features by setting an environment variable ``SCIPY_ARRAY_API=1`` and +providing PyTorch, JAX, or CuPy arrays as array arguments. Many functions +in `scipy.stats`, `scipy.special`, `scipy.optimize`, and `scipy.constants` +now provide tables documenting compatible array and device types as well as +support for lazy arrays and JIT compilation. New features with support and +old features with support added for SciPy 1.16.0 include: + +- Most features of `scipy.signal` +- `scipy.ndimage.vectorized_filter` +- `scipy.special.stdtrit` +- `scipy.special.softmax` +- `scipy.special.log_softmax` +- `scipy.stats.quantile` +- `scipy.stats.gstd` +- `scipy.stats.rankdata` + +Features with extended array API support (generally, improved support +for JAX and Dask) in SciPy 1.16.0 include: + +- many of the `scipy.cluster.hierarchy` functions +- many functions in `scipy.special` +- many of the trimmed `stats` functions -``scipy.spatial`` deprecations -============================== +******************* +Deprecated features +******************* +- The unused ``atol`` argument of `scipy.optimize.nnls` is deprecated and will + be removed in SciPy 1.18.0. +- The ``disp`` argument of `scipy.linalg.signm`, `scipy.linalg.logm`, and + `scipy.linalg.sqrtm` will be removed in SciPy 1.18.0. +- `scipy.stats.multinomial` now emits a ``FutureWarning`` if the rows of ``p`` + do not sum to ``1.0``. This condition will produce NaNs beginning in SciPy + 1.18.0. + +******************** +Expired Deprecations +******************** +- ``scipy.sparse.conjtransp`` has been removed. Use ``.T.conj()`` instead. +- The ``quadrature='trapz'`` option has been removed from + `scipy.integrate.quad_vec` and ``scipy.stats.trapz`` has been removed. Use + ``trapezoid`` in both instances instead. +- `scipy.special.comb` and `scipy.special.perm` now raise when ``exact=True`` + and arguments are non-integral. +- Support for inference of the two sets of measurements from the single + argument ``x`` has been removed from `scipy.stats.linregress`. The data + must be specified separately as ``x`` and ``y``. +- Support for NumPy masked arrays has been removed from + `scipy.stats.power_divergence` and `scipy.stats.chisquare`. + ****************************** Backwards incompatible changes ****************************** +- Several of the `scipy.linalg` functions for solving a linear system (e.g. + `scipy.linalg.solve`) documented that the RHS argument must be either 1-D or + 2-D but did not always raise an error when the RHS argument had more the + two dimensions. Now, many-dimensional right hand sides are treated according + to the rules specified in :ref:`linalg_batch`. +- `scipy.stats.bootstrap` now explicitly broadcasts elements of ``data`` to the + same shape (ignoring ``axis``) before performing the calculation. ************* Other changes ************* +- The ``lm`` method of ``least_squares`` function now has a different behavior + for the maximum number of function evaluations, ``max_nfev``. The default for + the ``lm`` method is changed to ``100 * n``, for both a callable and a + numerically estimated jacobian. This limit on function evaluations excludes + those used for any numerical estimation of the Jacobian. Previously the + default when using an estimated jacobian was ``100 * n * (n + 1)``, because + the method included evaluations used in the estimation. In addition, for the + ``lm`` method the number of function calls used in Jacobian approximation + is no longer included in ``OptimizeResult.nfev``. This brings the behavior + of ``lm``, ``trf``, and ``dogbox`` into line. +- For ``Cython>=3.1.0b1``, SciPy now uses the new + ``cython --generate-shared`` functionality, which reduces the total size of + SciPy's wheels and on-disk installations significantly. +- The output of `scipy.stats.wrapcauchy` ``rvs`` is now mapped to the unit circle + between 0 and ``2 * pi``. +- The vendored Qhull library was upgraded from version 2019.1 to 2020.2. +- A new build option ``-Duse-system-libraries`` has been added. It allows + opting in to using system libraries instead of using vendored sources. + Currently ``Boost.Math`` and ``Qhull`` are supported as system build + dependencies. +- The ``namedtuple``-like bunch objects returned by some SciPy functions + now have improved compatibility with the ``polars`` library. +- The testsuite thread safety with free-threaded CPython has improved. @@ -118,13 +296,756 @@ Other changes Authors ******* +* Name (commits) +* h-vetinari (4) +* aiudirog (1) + +* Anton Akhmerov (2) +* Thorsten Alteholz (1) + +* Gabriel Augusto (1) + +* Backfisch263 (1) + +* Nickolai Belakovski (5) +* Peter Bell (1) +* Benoît W. (1) + +* Maxwell Bileschi (1) + +* Sam Birch (1) + +* Florian Bourgey (3) + +* Charles Bousseau (2) + +* Richard Strong Bowen (2) + +* Jake Bowhay (126) +* Matthew Brett (1) +* Dietrich Brunn (52) +* Evgeni Burovski (252) +* Christine P. Chai (12) + +* Saransh Chopra (2) + +* Omer Cohen (1) + +* Lucas Colley (91) +* crusaderky (56) + +* Yahya Darman (3) + +* dartvader316 (2) + +* Benjamin Eisele (1) + +* Donnie Erb (1) +* Evandro (1) +* Sagi Ezri (57) + +* Alexander Fabisch (2) + +* Matthew H Flamm (1) +* Gautzilla (1) + +* Neil Girdhar (1) +* Ralf Gommers (149) +* Rohit Goswami (4) +* Saarthak Gupta (4) + +* Matt Haberland (320) +* Sasha Hafner (1) + +* Joren Hammudoglu (9) +* Chengyu Han (1) + +* Charles Harris (1) +* Kim Hsieh (4) + +* Lukas Huber (1) + +* Guido Imperiale (47) + +* Jigyasu (1) + +* karthik-ganti-2025 (1) + +* Robert Kern (2) +* Harin Khakhi (2) + +* Agriya Khetarpal (4) +* Tetsuo Koyama (1) +* David Kun (1) + +* Eric Larson (3) +* lciti (1) +* Antony Lee (1) +* Kieran Leschinski (1) + +* Thomas Li (2) + +* Christian Lorentzen (2) +* Loïc Estève (4) +* Panos Mavrogiorgos (1) + +* Nikolay Mayorov (2) +* Melissa Weber Mendonça (10) +* Miguel Cárdenas (2) + +* MikhailRyazanov (6) + +* Swastik Mishra (1) + +* Sturla Molden (2) +* Andreas Nazlidis (1) + +* Andrew Nelson (209) +* Parth Nobel (1) + +* Nick ODell (9) +* Giacomo Petrillo (1) +* pmav99 (1) + +* Ilhan Polat (72) +* pratham-mcw (3) + +* Tyler Reddy (73) +* redpinecube (1) + +* Érico Nogueira Rolim (1) + +* Pamphile Roy (10) +* sagi-ezri (1) + +* Atsushi Sakai (9) +* Marco Salathe (1) + +* sanvi (1) + +* Neil Schemenauer (2) + +* Daniel Schmitz (20) +* Martin Schuck (1) + +* Dan Schult (32) +* Tomer Sery (19) +* Adrian Seyboldt (1) + +* Scott Shambaugh (4) +* ShannonS00 (1) + +* sildater (3) + +* PARAM SINGH (1) + +* G Sreeja (7) + +* Albert Steppi (133) +* Kai Striega (3) +* Anushka Suyal (2) +* Julia Tatz (1) + +* Tearyt (1) + +* Elia Tomasi (1) + +* Jamie Townsend (2) + +* Edgar Andrés Margffoy Tuay (4) +* Matthias Urlichs (1) + +* Jacob Vanderplas (2) +* David Varela (2) + +* Christian Veenhuis (3) +* vfdev (1) +* vpecanins (10) + +* vrossum (1) + +* Stefan van der Walt (2) +* Warren Weckesser (5) +* Jason N. White (1) + +* windows-server-2003 (5) +* Zhiqing Xiao (1) +* Pavadol Yamsiri (1) +* YongcaiHuang (2) + +* Rory Yorke (3) +* yuzie007 (2) + +* Irwin Zaid (4) +* zaikunzhang (1) + +* Austin Zhang (1) + +* William Zijie Zhang (1) + +* Eric Zitong Zhou (5) + +* zitongzhoueric (6) + +* Case Zumbrum (2) + +* ਗਗਨਦੀਪ ਸਿੰਘ (Gagandeep Singh) (45) + + A total of 124 people contributed to this release. + People with a "+" by their names contributed a patch for the first time. + This list of names is automatically generated, and may not be fully complete. ************************ Issues closed for 1.16.0 ************************ +* `#4800 `__: ENH: ndimage.median_filter: behavior with NaNs +* `#4878 `__: ENH: ndimage.median_filter: excessive memory usage +* `#5137 `__: ENH: ndimage.generic_filter: function to return higher-dimensional... +* `#5435 `__: savemat silently drops entries starting with "_" +* `#5451 `__: ENH: linalg.solve: support broadcasting +* `#6052 `__: savemat does not save keys starting with underscore +* `#6606 `__: BUG: signal.bilinear: can't handle leading zeros +* `#6689 `__: ENH: optimize: consider using NLopt's version of ``slsqp`` +* `#6755 `__: ENH: ndimage.percentile_filter: take multiple percentiles +* `#7518 `__: DOC: optimize: meaning of accuracy in ``fmin_slsqp`` undocumented +* `#7818 `__: ENH: ndimage.uniform_filter: expands NaNs all the way to the... +* `#8140 `__: sparse LU decomposition does not solve with complex right-hand... +* `#8367 `__: ENH: stats.mvndst: make thread-safe +* `#8411 `__: nan with betainc for a=0, b=3 and x=0.5 +* `#8916 `__: ENH: ndimage.generic_filter: slow on large images +* `#9077 `__: maximum_filter is not symetrical with nans +* `#9841 `__: ENH: linalg: 0-th dimension must be fixed to 1 but got 2 (real... +* `#9873 `__: ENH: ndimage: majority voting filter +* `#10416 `__: ENH: optimize.minimize: slsqp: give better error when work array... +* `#10793 `__: BUG: integrate: ``solve_ivp`` and ``odeint`` with ``lsoda`` have... +* `#11312 `__: BUG: signal.cont2discrete not handling lti instances as documented +* `#11328 `__: Scipy unable to read piped wav file +* `#12133 `__: How to define new distributions? +* `#12544 `__: signal.spectral._triage_segments doesn't support window as tuple... +* `#12994 `__: ENH: linalg.sqrtm: efficiently process upper triangular matrices +* `#13577 `__: Split scipy.signal.spectral._spectral_helper into two to support... +* `#13666 `__: ENH: invgauss.pdf should return correct output when mu=infinity +* `#13788 `__: Documentation for scipy.signal.resample should say what to use... +* `#13789 `__: Documentation for scipy.signal.decimate doesn't say what to use... +* `#13823 `__: BUG: signal.bilinear: doesn't work for complex valued arrays +* `#13914 `__: DOC: sparse.csgraph.shortest_path: predecessors array contains... +* `#13952 `__: fmin_cobyla result violates constraint +* `#13982 `__: ENH: linalg.eigh_tridiagonal: divide and conquer option +* `#14394 `__: ENH: optmize.slsqp: return Lagrange multipliers +* `#14569 `__: BUG: signal.resample: inconsistency across dtypes +* `#14915 `__: BUG: optimize.minimize: corruption/segfault with constraints +* `#15153 `__: BUG: signal.resample: incorrect with ``datetime[ns]`` for ``t``... +* `#15527 `__: BUG: optimize: COBYLA hangs on some CPUs +* `#16009 `__: BUG: ``act`` fails for local GitHub Actions CI run +* `#16142 `__: ENH: Fix the random state in ``scipy.stats.multivariate_normal.cdf()`` +* `#16203 `__: BUG: scipy.io.savemat discards nested names with a leading digit +* `#16234 `__: BUG: Memory leak in _superluobject.c when ``ENUM_CHECK`` is not... +* `#16452 `__: doit based dev interface garbles pdb command history (in some... +* `#17546 `__: ENH: Adding 'valid' mode to ndimage.generic_filter +* `#17787 `__: BUG: Erratic results from RectBivariateSpline when smoothing... +* `#17891 `__: BUG: inconsistent checks for integrality in several distributions +* `#17968 `__: ENH: creation of a 2-D FIR Filter using 1-D window method +* `#18046 `__: BUG: dev.py does not work in a Windows CI environment on GHA... +* `#18105 `__: ENH: optimize ``LbfgsInvHessProduct.todense()``\ , 10x speed... +* `#18118 `__: ENH: The Fortran 77 implementation of COBYLA is buggy and challenging... +* `#18214 `__: DOC: inconsistent definitions of "OP" and "OPinv" in eigsh +* `#18346 `__: DOC: optimize: l_bfgs_b: sets ``maxiter`` and ``maxfun`` to the... +* `#18437 `__: ENH: ndimage.generic_filter: support complex input +* `#18740 `__: BUG: scipy.optimize.bisect gives incorrect results for very small... +* `#18866 `__: MAINT: follow-up actions for array API support in ``cluster`` +* `#18951 `__: ENH: improve ``python dev.py test`` experience caused by imp... +* `#18998 `__: BUG: dev.py has issues with site-packages and Python installed... +* `#19254 `__: ENH: spatial.transform: cover proper rigid transformations with... +* `#19362 `__: BUG: optimize: warning generated by SLSQP is useless +* `#19415 `__: BUG: linalg.sqrtm results different between version 1.11.1 and... +* `#19459 `__: BUG: optimize.least_squares giving poor result compared to optimize.leastsq... +* `#20219 `__: BUG: failing ``sqrtm`` regression test +* `#20366 `__: ENH: Yens algorithm improvements and enhancements +* `#20608 `__: BUG: ``refguide-check`` incorrectly flags references to equations... +* `#20622 `__: DOC: signal: add an example cross-spectrogram application +* `#20806 `__: Failures for new ``pytest-fail-slow`` check in Windows CI jobs +* `#20972 `__: BUG: special.chdtrc: returns 1.0 when both degrees of freedom... +* `#20999 `__: BUG: ndimage.zoom: wrong output with zoom factor of 1 +* `#21020 `__: DOC: signal: Use ``where='post'`` when plotting discrete response +* `#21095 `__: DOC: ``RegularGridInterpolator`` uses half down rounding instead... +* `#21102 `__: RFC/ENH?: ``optimize.curve_fit``\ : option to use global optimization... +* `#21293 `__: DOC: stats.qmc.discrepancy: clarify deviation from reference +* `#21317 `__: BUG: ``special.gammainc``\ : returns finite results with NaN... +* `#21323 `__: DOC: build fails with Sphinx 8 +* `#21341 `__: DOC: signal.correlate: formula doesn't match behavior when ``x``... +* `#21484 `__: DEP: optimize.nnls: deprecate atol parameter which does nothing +* `#21531 `__: MAINT: ``stats.dirichlet_multinomial``\ : relax ``n`` to ``>=0`` +* `#21547 `__: STY/DEV: fix and enable lint rule UP038 +* `#21606 `__: ENH: stats: generic power law with negative index +* `#21649 `__: RFC: Splitting off special function scalar kernels into separate... +* `#21692 `__: BUG: optimize.shgo: not working with ``jac=True`` +* `#21717 `__: DOC: ``assert_allclose`` instead of ``xp_assert_close`` is recommended... +* `#21740 `__: CI: adding a GPU-enabled CI job +* `#21764 `__: ENH: linalg.lapack: add symmetric solvers +* `#21844 `__: ENH: linalg: wrap ?gbcon/?langb and use in linalg.solve +* `#21879 `__: BUG: ``scipy.datasets`` failing with Error 403 for readthedocs... +* `#21971 `__: ENH: ``ndimage.median_filter``\ : extended ``dtype`` support? +* `#21972 `__: STY: fix and enable lint rule UP031 +* `#21986 `__: ENH: optimize.root: warn when inner parameters are ignored with... +* `#21995 `__: BUG: ``optimize.curve_fit`` with ``method='lm'`` fails to determine... +* `#21999 `__: ENH: ``io.mmread``\ : Provide better error message when loading... +* `#22000 `__: DOC: ``ndimage.median_filter``\ : document behaviour with ``nan``\... +* `#22011 `__: BUG: interpolate.Akima1DInterpolator: different values on subsequent... +* `#22044 `__: TST: ``optimize.elementwise.bracket_minimum``\ : CuPy failure +* `#22045 `__: DOC: stats: clarify the support of a distribution is unaffected... +* `#22051 `__: BUG: AttributeError: module 'numpy' has no attribute 'AxisError'... +* `#22054 `__: BUG: ndimage, array types: ``minimum_position`` and ``extrema``... +* `#22055 `__: DOC: ndimage.minimum and maximum: incorrect return type +* `#22057 `__: DOC: ``stats.order_statistic``\ : docstring missing the "Returns"... +* `#22065 `__: DOC: sparse: Several functions are missing the 'Returns' section... +* `#22072 `__: DOC: PchipInterpolator: missing integrate function +* `#22086 `__: MAINT: signal: build warning (``sprintf``\ ) on macOS +* `#22093 `__: DOC: integrate.quad: uses Gauss-Kronrod not Curtis-Clenshaw? +* `#22136 `__: DOC: linalg.matrix_balance: equation does not render +* `#22144 `__: Query: optimize.minimize: trust_constr does not avoid Nonlinear... +* `#22163 `__: DOC: update ``scipy`` module docstring for lazy loading +* `#22164 `__: MAINT: undo ignored errors in mypy +* `#22195 `__: Query: optimize.basinhopping: lowest minimum not accepted if... +* `#22224 `__: MAINT: remove end year from copyright +* `#22252 `__: MAINT: Fix a dtype check in ``scipy.signal._waveforms.py`` +* `#22258 `__: BUG: Constructing sparse matrix with big-endian float32/64 raises... +* `#22263 `__: BUG: linalg.solve doesn't raise an error when A is a singular... +* `#22265 `__: BUG: linalg: ``hecon`` returns NaN incorrectly with some lower... +* `#22271 `__: Query: empty ``Rotation`` is not allowed in scipy=1.15 +* `#22282 `__: QUERY/DEV: test failure in IDE with ``SCIPY_ARRAY_API`` +* `#22288 `__: QUERY: Pyright raises error/warning in IDE +* `#22303 `__: ENH: stats.special_ortho_group: improve and simplify +* `#22309 `__: DOC: optimize.elementwise.find_minimum: harmonize documented/implemented... +* `#22328 `__: QUERY: stats.beta.fit: ``FitError`` on reasonable data +* `#22338 `__: QUERY: Intellisense Autocomplete Not Working for ``spatial.transform.Rotation`` +* `#22361 `__: BUG: interpolation test TestSmoothingSpline.test_compare_with_GCVSPL... +* `#22363 `__: BUG: special test TestHyp2f1.test_region3[hyp2f1_test_case23]... +* `#22367 `__: QUERY/TYP: sparse: Pylance reports unreachable after ``toarray()`` +* `#22378 `__: DOC/TST: interpolate, signal: ``smoke-docs`` failures +* `#22382 `__: ENH: sparse.spmatrix: allow fast import +* `#22395 `__: BUG: special: failure of TestSystematic.test_besselj_complex... +* `#22403 `__: DOC: ``gaussian_kde``\ 's ``bw_method='silverman'`` deviates... +* `#22415 `__: Two ``TestBatch`` failures in macOS x86-64 Accelerate wheel build... +* `#22429 `__: DOC: integrate: missing bold font for a vector in tutorial +* `#22437 `__: DOC: The code of conduct link is dead +* `#22449 `__: BUG: sparse.csgraph.construct_dist_matrix: buffer dtype mismatch +* `#22450 `__: QUERY: difference between ``namedtuple``\ s and objects produced... +* `#22461 `__: DOC: freqz_sos: claims that it was introduced in 0.19; no mention... +* `#22470 `__: BUG: ``lfiltic``\ 's handling of ``a[0] != 1`` differs from ``lfilter``\... +* `#22485 `__: DOC: remove links to the reference guide in the tutorials page +* `#22488 `__: DOC: interpolate.lagrange: the Lagrange function is using the... +* `#22495 `__: BUG: special test TestHyp2f1.test_region4[hyp2f1_test_case42]... +* `#22501 `__: BUG: ``min_weight_full_bipartite_matching`` fails for ``coo_matrix``... +* `#22508 `__: DOC: Inconsistent notation in Linear algebra (scipy.linalg) page +* `#22534 `__: CI: failures ``*/tests/test_extending`` due to a regression in... +* `#22559 `__: BUG: ``ndimage``\ : Numerical regressions in Dask 2025.2.0 +* `#22565 `__: BUG: stats.multinomial.pmf: inconsistent results? +* `#22581 `__: DOC: stats.gaussian_kde: clarify the meaning of ``factor`` +* `#22591 `__: BUG: sparse.coo: ``ImportError`` for ``upcast`` +* `#22601 `__: BUG: special.logsumexp: inconsistency in phase when one element... +* `#22626 `__: BUG: scipy.stats: tmin/tmax: loss of precision for large integers +* `#22646 `__: CI/DOC: CloughTocher2DInterpolator: ``UserWarning`` in docs build +* `#22659 `__: BUG: spatial: ``RigidTransform`` does not support zero-length... +* `#22692 `__: DOC: interpolate.make_smoothing_spline: example plot uses the... +* `#22700 `__: CI: new failures: segfault in free-threaded, ``linprog`` invalid... +* `#22703 `__: DOC: integrate: ``quad_vec`` info return type is ``_Bunch`` not... +* `#22767 `__: BUG: test_cython Failing on Windows on ARM64 with clang-cl +* `#22768 `__: DOC/DEV: outdated references to Cirrus CI +* `#22769 `__: ENH: optimize: Return bound multiplier for SLSQP +* `#22775 `__: ENH: Use cython shared utility module +* `#22791 `__: BUG: optimize.nnls: unstable on i686 (32-bit) machine +* `#22800 `__: BUG: ``signal.windows.kaiser_bessel_derived`` uses ``array``... +* `#22881 `__: DOC: Update minimum NumPy and Python in toolchain roadmap +* `#22904 `__: BUG: Wrong use of ``__builtin_prefetch()`` +* `#22912 `__: BUG: optimize: ``SyntaxWarning: 'break' in a 'finally' block``... +* `#22920 `__: BUG: ``check_test_name`` fails with ``UnicodeDecodeError``\ ? +* `#22921 `__: DOC: clarify the status of Apple's Accelerate Framework support +* `#22931 `__: BUG: interpolate._dierckx: ``check_array()`` can crash if the... +* `#22942 `__: TST: ``special``\ : ``test_compiles_in_cupy`` is broken +* `#22945 `__: TST: Nested arrays failing in array-api-strict git tip +* `#22951 `__: BUG: stats.wrapcauchy: output isn't wrapped around the unit circle +* `#22956 `__: BUG: special._ufuncs._ncx2_pdf: interpreter crash with extreme... +* `#22965 `__: BUG: The attribute "nit" is not found when using the callback... +* `#22981 `__: Bug with freqz when specifying worN after #22886 ************************ Pull requests for 1.16.0 ************************ + +* `#18375 `__: ENH: signal: Add ``firwin_2d`` filter +* `#20610 `__: ENH: signal.ShortTimeFFT: determine arbitrary dual windows +* `#20639 `__: ENH: stats.rankdata: add array API standard support +* `#20717 `__: ENH: Speed up sparse.csgraph.dijkstra 2.0 +* `#20772 `__: ENH: array types, signal: delegate to CuPy and JAX for correlations... +* `#20950 `__: ENH: spatial: speed up ``Rotation.apply`` by replacing ``np.einsum``... +* `#21180 `__: ENH: sparse: efficient arithmetic operations for DIA format +* `#21233 `__: ENH: ``stats.boxcox_llf``\ : vectorize for n-D arrays +* `#21270 `__: MAINT: make ``boost_math`` a ``subproject`` +* `#21462 `__: ENH: linalg.eig: support batched input +* `#21482 `__: MAINT/DEV: use Sphinx 8 for documentation builds +* `#21557 `__: ENH: ``stats._continued_fraction``\ : elementwise, Array API... +* `#21628 `__: BUG:signal: Fix passing lti as system to cont2discrete +* `#21674 `__: DEV: use ``spin`` +* `#21684 `__: MAINT: ``stats.dirichlet_multinomial`` relax ``n`` to ``>= 0`` +* `#21713 `__: ENH: signal: add array API support / delegation to lfilter et... +* `#21783 `__: ENH: signal.windows: add array API support (take 2) +* `#21863 `__: CI: use macos-15 for a macOS run +* `#21987 `__: STY: fix lint rule UP031 +* `#22008 `__: ENH: signal.vectorstrength: add array API standard support +* `#22010 `__: REL: set version to 1.16.0.dev0 +* `#22012 `__: MAINT: bump min NumPy to 1.25.2, min Python to 3.11 +* `#22013 `__: DEV: ``gh_lists``\ : fix asterisk sanitisation +* `#22015 `__: DEV: lint: add option to lint all files +* `#22019 `__: MAINT: signal: remove tempita templating +* `#22042 `__: DOC, MAINT: Add a ``"jupyterlite_sphinx_strip"`` tag to the ``scipy.stats``... +* `#22046 `__: TST: optimize: fix CuPy failure for ``bracket_minimum`` +* `#22052 `__: DOC: sparse.linalg: add note about complex matrices to ``splu``... +* `#22056 `__: MAINT: stats.wilcoxon: fix attempt to access np.AxisError +* `#22061 `__: BUG: ndimage: convert array scalars on return +* `#22062 `__: MAINT: ``_lib``\ : co-vendor array-api-extra and array-api-compat +* `#22064 `__: MAINT: ``sparse.linalg._isolve``\ : Remove postprocess function +* `#22068 `__: ENH: optimize: migrate to use sparray +* `#22070 `__: ENH: ``_lib``\ : JAX support (non-jitted) +* `#22071 `__: MAINT: Use ``ENUM_CHECK_NAME`` for avoiding memory leaks in ``_superluobject.c`` +* `#22073 `__: DEP: sparse: remove conjtransp +* `#22074 `__: DEP: remove remaining trapz references +* `#22075 `__: DEP: stats.linregress: remove one arg use +* `#22076 `__: BUG: datasets: add headers to fetchers to avoid 403 errors +* `#22079 `__: DEP: stats: remove support for masked arrays from ``power_divergence``... +* `#22087 `__: DEP: special: raise error for non-integer types with exact=True... +* `#22088 `__: TST: optimize.elementwise.find_root: refactor tests to use ``find_root``... +* `#22089 `__: TST: optimize: suppress incorrect sparray warning from scikit-sparse +* `#22090 `__: ENH: optimize: migrate to sparray (docs) +* `#22092 `__: MAINT: signal: fixed build warning (``sprintf``\ ) on MacOS +* `#22100 `__: DEP: signal.spline: use standard submodule deprecation machinery +* `#22101 `__: DOC: update ``stats``\ , ``integrate``\ , ``optimize``\ , and... +* `#22108 `__: CI: Run 'Checkout scipy' and 'Check for skips' only on Github... +* `#22110 `__: TST: linalg: use infinity norm of matrix when norm='I' +* `#22115 `__: DOC: release notes: ensure TOC links to headings below +* `#22116 `__: DOC: update the interpolate roadmap +* `#22122 `__: MAINT: signal.oaconvolve: avoid xp <-> numpy conversions +* `#22125 `__: TST: stats: ensure tests are thread-safe +* `#22127 `__: ENH: linalg: add batch support for matrix -> scalar funcs +* `#22130 `__: TST: ndimage: array API-related cosmetic tweaks in tests +* `#22131 `__: TST: ``skip|xfail_xp_backends`` disregards ``reason=`` +* `#22132 `__: TST: array types: enforce namespace in tests +* `#22133 `__: ENH: linalg: add batch support for functions that accept a single... +* `#22140 `__: DOC: linalg.matrix_balance: move math to notes; ensure that it... +* `#22142 `__: ENH: signal: add CuPy/JAX delegation to scipy.signal +* `#22148 `__: TST: ndimage: fix test skip typo +* `#22152 `__: ENH: stats.f_oneway: add ``equal_var`` for Welch ANOVA +* `#22154 `__: ENH: linalg.clarkson_woodruff_transform: add batch support +* `#22155 `__: ENH: stats: add axis/nan_policy/keepdims/etc. support to correlation... +* `#22157 `__: ENH: linalg: add batch support for remaining cholesky functions +* `#22160 `__: DEP: interpolate: remove incidental imports from private modules +* `#22161 `__: DOC, MAINT: Add updates for interactive notebooks via ``jupyterlite-sphinx``... +* `#22165 `__: ENH: linalg: add batch support to remaining eigenvalue functions +* `#22166 `__: ENH: linalg.block_diag: add batch support +* `#22169 `__: MAINT: sparse: refactor CSC to use CSR sparsetools +* `#22170 `__: ENH: signal: convert ``symiirorder`` and related filters to work... +* `#22172 `__: MAINT: improve overflow handling in factorial functions +* `#22173 `__: DOC: interpolate: add missing method ``integrate`` for ``PchipInterpolator`` +* `#22174 `__: MAINT: optimize: switch suppress_warnings to catch_warnings +* `#22176 `__: MAINT: special: Move Faddeeva into xsf +* `#22179 `__: DOC/DEV: mention ``scipy-stubs`` in building from source guide +* `#22182 `__: TST: ndimage: cupy tweaks for inplace out= +* `#22185 `__: ENH: stats.tukey_hsd: ``equal_var=False`` option to perform Games-Howell... +* `#22186 `__: DOC: interpolate: add a note about rounding rule of the ``nearest``... +* `#22190 `__: MAINT: special: Migrate remaining exp and log functions to xsf +* `#22192 `__: ENH: linalg: add batch support to linear system solvers +* `#22196 `__: DOC: update scipy module docstring for lazy loading +* `#22197 `__: ENH: linalg.cossin: add batch support +* `#22198 `__: DOC: basinhopping, clarify when lowest_optimization_result is... +* `#22201 `__: DOC: Clarify support behavior in rv_continuous documentation +* `#22208 `__: ENH: io.wavfile: read unseekable files +* `#22211 `__: DOC: interpolate: add missed ``integrate`` doc link for ``Akima1DInterpolator`` +* `#22212 `__: ENH: linalg: wrap ?gbcon +* `#22213 `__: BUG: zpk2tf works correctly with complex k, real p, z +* `#22214 `__: TST: make torch default dtype configurable +* `#22215 `__: ENH: io: throw ``FileNotFoundError`` exception when the source... +* `#22216 `__: TST: TestBracketMinimum MPS shims +* `#22217 `__: ENH: linalg: wrap ?langb +* `#22219 `__: ENH: ``_lib``\ : deobfuscate ``jax.jit`` crash in ``_asarray`` +* `#22220 `__: MAINT: stats: replace nonstandard calls in (mostly) array API... +* `#22221 `__: MAINT: linalg.leslie: use _apply_over_batch +* `#22222 `__: ENH: ``special``\ /``stats``\ : implement xp-compatible ``stdtrit``... +* `#22226 `__: ENH: signal.upfirdn: array API standard support +* `#22227 `__: TST: linalg: add missing lower arguments in test_sy_hetrs +* `#22228 `__: ENH: linalg.lapack: wrap ?sytri and ?hetri +* `#22229 `__: MAINT: cluster: remove unnecessary namespace changes +* `#22231 `__: ENH: add ``callback`` to ``optimize.least_squares`` +* `#22234 `__: MAINT: forward port 1.15.0 relnotes +* `#22237 `__: BENCH: sparse.csgraph.dijkstra: add benchmark +* `#22240 `__: ENH: array types: add dask.array support +* `#22242 `__: MAINT: integrate.cubature: fix undefined ``asarray`` use +* `#22243 `__: DOC: sparse: docstring example of random_array with uint32 data_sampler +* `#22251 `__: ENH: linalg.solve: use langb +* `#22255 `__: EHN: cluster: JAX support (non-jitted) +* `#22256 `__: ENH: special: JAX support (non-jitted) +* `#22259 `__: TST: signal: fix symiir tests +* `#22260 `__: TST: Make ``@pytest.mark.usefixtures("skip_xp_backends")`` redundant +* `#22261 `__: TST: dev.py quietly ignores user markers +* `#22262 `__: TST: Mark with ``xp`` all tests in Array API-compatible modules +* `#22264 `__: MAINT: interpolate: make BSpline allocate out arrays in C +* `#22266 `__: MAINT: linalg.solve: raise when diagonal matrix is exactly singular +* `#22267 `__: ENH: spatial.transform: baseline implementation of ``RigidTransform`` +* `#22268 `__: TST: clean up obsolete Array API fixtures +* `#22269 `__: DOC: optimize.curve_fit: add note about more advanced curve fitting +* `#22273 `__: ENH: linalg.solve: use gbcon +* `#22274 `__: ENH: ``_contains_nan`` for lazy arrays +* `#22275 `__: CI: add a GPU CI job +* `#22278 `__: BUG: Fix ``Akima1DInterpolator`` by returning linear interpolant... +* `#22279 `__: TST: Add skips for GPU CI failures +* `#22280 `__: TST: ``_lib``\ : more idiomatic conditional skips +* `#22281 `__: TST: special: better skip message for stdtrit on JAX +* `#22283 `__: BUG: Fix banded Jacobian for lsoda: ``ode`` and ``solve_ivp`` +* `#22284 `__: BUG: sparse: better error message for unsupported dtypes +* `#22289 `__: CI: fix skip/trigger condition of GPU CI job +* `#22293 `__: ENH: Add __repr__ method to scipy.spatial.transform.Rotation +* `#22295 `__: DOC: signal.ShortTimeFFT.nearest_k_p: fix typo +* `#22298 `__: MAINT: stats: remove ``mvn`` fortran calls from ``multivariate_normal.cdf`` +* `#22300 `__: MAINT: remove end year from copyright +* `#22302 `__: MAINT: remove unused library import +* `#22304 `__: ENH: stats.special_ortho_group: speed up, allow 1x1 and 0x0 ortho... +* `#22305 `__: MAINT, DOC: forward port 1.15.1 relnotes +* `#22308 `__: TST: ``_lib``\ : run tests with ``@jax.jit`` +* `#22311 `__: TST: replace ``pytest.xfail`` with ``skip/xfail_xp_backends`` +* `#22312 `__: ENH: stats.Binomial: add binomial distribution with new infrastructure +* `#22313 `__: BUG: signal.bilinear handles complex input, and strips leading... +* `#22320 `__: TST: array types: wrap namespaces centrally +* `#22324 `__: ENH: io: add invalid field name warning for ``savemat`` +* `#22330 `__: ENH: sparse.csgraph.yen: performance improvements +* `#22340 `__: MAINT: linalg: reorganize tridiagonal eigenvalue routines +* `#22342 `__: ENH: cluster: ``linkage`` support for jax.jit and dask +* `#22343 `__: ENH: ``signal.{envelope,resample,resample_poly}``\ : array API... +* `#22344 `__: BUG: Fix bug with dpss degenerate case +* `#22348 `__: DOC: Harmonize summary line of docstrings of iterative sparse... +* `#22350 `__: ENH: Replace Fortran COBYLA with Python version from PRIMA +* `#22351 `__: DOC: sparse.linalg.eigsh: fix inconsistent definitions of OP... +* `#22352 `__: ENH: stats.quantile: add array API compatible quantile function +* `#22358 `__: MAINT: ``special.nctdtrit``\ : migrate to boost +* `#22359 `__: MAINT: remove temporary ``# type: ignore``\ 's from #22162 +* `#22364 `__: TST: bump tolerance on TestHyp2f1.test_region3[hyp2f1_test_case23] +* `#22366 `__: DOC: integrate: fix quad documentation to correctly describe... +* `#22371 `__: ENH: stats.make_distribution: allow definition of custom distributions +* `#22375 `__: DOC: sparse.linalg: fix doctest in scipy.sparse.linalg._norm.py +* `#22376 `__: DOC: sparse.linalg: sparray updates in doc_strings and Sakurai... +* `#22379 `__: DOC: interpolate.AAA: add may vary to example +* `#22380 `__: DOC: Replace link to X in header with link to scientific python... +* `#22381 `__: MAINT: special: A bit of clean up in stirling2.h +* `#22386 `__: DEP: optimize.nnls: deprecate unused atol parameter +* `#22387 `__: DOC: Add example to show usage of ``predecessors`` matrix returned... +* `#22388 `__: DOC: Fix documentation for ``predecessors`` matrix in ``shortest_path``\... +* `#22389 `__: DOC: Add "Assert function selection guideline" doc in the new... +* `#22393 `__: TST: stats: test support for array API compatible masked arrays +* `#22396 `__: DOC: signal: Use where='post' when plotting discrete response... +* `#22397 `__: DOC: spatial: Added mention of Davenport Angles to Rotation class... +* `#22398 `__: MAINT: special: clean up os/warnings modules exposed in special... +* `#22399 `__: TST: remove thread-unsafe skips for a now fixed Cython fused... +* `#22401 `__: TYP: Runtime-subscriptable ``sparray`` and ``spmatrix`` types +* `#22406 `__: ENH: linalg: Rewrite ``sqrtm`` in C with low-level nD support +* `#22407 `__: MAINT: remove ``_lib``\ ->``sparse`` dependency +* `#22411 `__: DOC: stats.gaussian_kde: clarify Silverman method +* `#22413 `__: DOC: stats: Edited the NIST Handbook reference +* `#22416 `__: TST: linalg: bump tolerances in two TestBatch tests +* `#22419 `__: MAINT: special: Remove ``libsf_error_state`` shared library in... +* `#22420 `__: TST: use singular ``reason=`` in ``skip_xp_backends`` +* `#22421 `__: BUG: ndimage: ``binary_erosion`` vs. broadcasted input +* `#22422 `__: MAINT: ``_lib``\ : adapt ``array_namespace`` to accept scalars... +* `#22425 `__: MAINT: special: Update handling of ``betainc`` and ``betaincc``... +* `#22426 `__: ENH: linalg: wrap ?stevd +* `#22427 `__: DEP: linalg: deprecate disp argument for signm, logm, sqrtm +* `#22428 `__: DOC: add note on getting the version switcher to behave to release... +* `#22430 `__: MAINT: cluster: vectorize tests in ``is_valid_linkage`` +* `#22431 `__: DOC: integrate: correct tutorial formatting +* `#22433 `__: BUG: interpolate.RectBivariateSpline: fix ``NaN`` output when... +* `#22434 `__: DOC: integrate.tanhsinh: remove incorrect reference to _differentiate +* `#22435 `__: MAINT: bump to array-api-extra git tip +* `#22439 `__: MAINT: special: Add ``log1mexp`` for ``log(1 - exp(x))`` +* `#22440 `__: DOC: Fix year of publication in ``_dual_annealing.py`` +* `#22441 `__: BUG: special: Fix incorrect handling of ``nan`` input in ``gammainc``... +* `#22442 `__: DOC: Modified Link for code of conduct documentation +* `#22443 `__: DOC: Corrected Path +* `#22445 `__: CI: avoid mpmath pre-release version that's failing in CI +* `#22448 `__: DOC: optimize.elementwise.find_minimum: fix documented termination... +* `#22452 `__: ENH: linalg.eigh_tridiagonal: add stevd as a driver and make... +* `#22453 `__: DOC: Improve docstrs of ``dlsim``\ , ``dimpulse``\ , ``dstep``\... +* `#22454 `__: BUG: signal.ShortTimeFFT: make attributes ``win`` and ``dual_win``... +* `#22455 `__: ENH: stats.gstd: add array API support +* `#22456 `__: ENH: stats: add nan_policy support to power_divergence, chisquare +* `#22457 `__: TST: sparse: add tests for subscriptable types +* `#22459 `__: DOC: ndimage: fix wrong return type doc for ``ndimage.minimum``... +* `#22460 `__: MAINT: signal.csd: port away from using ``_spectral_helper`` +* `#22462 `__: ENH: stats.pearsonr: two simple (but substantial) efficiency... +* `#22463 `__: DOC: update Halton docs +* `#22464 `__: DOC: Prevent A@x=b from becoming a URL +* `#22467 `__: MAINT/TST: address nits from Dask PR +* `#22469 `__: TST: stats: improve JAX test coverage +* `#22475 `__: BUG: optimize.shgo: delegate ``options['jac']`` to ``minimizer_kwargs['jac']`` +* `#22478 `__: ENH: optimize: add ``workers`` kwarg to BFGS, SLSQP, trust-constr +* `#22480 `__: CI: use mpmath pre-release again +* `#22481 `__: BUG: fix ``make_lsq_spline`` with a non-default axis +* `#22483 `__: MAINT: spatial: missing Cython type in build +* `#22484 `__: ENH: allow batching in ``make_smoothing_spline`` +* `#22489 `__: MAINT: simplifications related to NumPy bounds +* `#22490 `__: ENH: stats: add ``marray`` support to most remaining array API... +* `#22491 `__: DOC: stats: resampling tutorial fixups +* `#22493 `__: DOC: Add a docstring to OptimizeWarning +* `#22494 `__: ENH: _lib._make_tuple_bunch: pretend to be namedtuple even more +* `#22496 `__: MAINT: ``stats.invgauss``\ : return correct result when ``mu=inf`` +* `#22498 `__: TST: bump tolerance in TestHyp2f1.test_region4[hyp2f1_test_case42] +* `#22499 `__: DOC: remove links to the reference guide in the tutorials page +* `#22504 `__: BLD: bump min version of Clang to 15.0, and macOS min version... +* `#22505 `__: ENH: stats.quantile: add discontinuous (HF 1-3) and Harrell-Davis... +* `#22507 `__: BENCH: make Benchmark.change_dimensionality a class variable +* `#22509 `__: DOC: sparse.linalg: add explanation for ``MatrixRankWarning`` +* `#22511 `__: BUG: sparse.csgraph: Added support for casting coo array to csc/csr... +* `#22514 `__: TST: special: Add edgecase tests for gammainc and friends +* `#22516 `__: STY: enable lint rule UP038 and fix instances in violation of... +* `#22518 `__: DOC: interpolate.FloaterHormannInterpolator: fix typos +* `#22519 `__: ENH: add workers to least_squares +* `#22520 `__: MAINT: Remove an extraneous dtype check in ``scipy/signal/_waveforms.py`` +* `#22524 `__: ENH:MAINT:optimize: Rewrite SLSQP and NNLS in C +* `#22526 `__: DOC: interpolate: reorganize the API listing +* `#22527 `__: DOC: sparse: add returns sections to some ``_construct.py`` functions +* `#22528 `__: DOC: interpolate: improve visibility of univariate interpolator... +* `#22529 `__: DOC: Update a link in SciPy Core Developer Guide +* `#22530 `__: DOC: interpolate: improve one-line descriptions +* `#22531 `__: DOC: batching in 1D/ND interpolation/smoothing routines +* `#22535 `__: DOC: update roadmap sparse +* `#22536 `__: DOC: io: link to netcdf4-python +* `#22537 `__: DOC: linalg: fix inconsistent notation +* `#22541 `__: Interpolate tutorial: discuss the bases and interconversions +* `#22542 `__: MAINT, DOC: forward port 1.15.2 release notes +* `#22546 `__: DOC: Add docstring for QhullError in _qhull.pyx [docs only] +* `#22548 `__: DOC: interpolate.lagrange: add notes / references; recommend... +* `#22549 `__: ENH: use ``workers`` keyword in ``optimize._differentiable_functions.VectorFunct``... +* `#22552 `__: MAINT: sparse.csgraph: Raise error if ``predecessors.dtype !=``... +* `#22554 `__: BUG: ``lfiltic``\ 's handling of ``a[0] != 1`` differs from ``lfilter``\... +* `#22556 `__: ENH: optimize: speed up ``LbfgsInvHessProduct.todense`` on large... +* `#22557 `__: ENH: Replace ``_lazywhere`` with ``xpx.apply_where`` +* `#22560 `__: ENH: Allow endpoints of custom distributions created with ``stats.make_distribut``... +* `#22562 `__: DOC: Correct a typo: MATLAB(R) -> MATLAB® +* `#22564 `__: TST: add missing custom markers to pytest.ini +* `#22566 `__: TST: ``skip_xp_backends(eager_only=True)`` +* `#22569 `__: CI: fix dev-deps job by not testing Meson master +* `#22572 `__: TST: skip two ndimage tests that are failing for Dask +* `#22573 `__: DOC: sparse: Add docstrings to warnings in ``scipy.sparse`` +* `#22575 `__: ENH: ``ndimage.vectorized_filter``\ : ``generic_filter`` with... +* `#22579 `__: DOC: signal.correlate: improve notes section +* `#22584 `__: TST: ndimage: tidy ``skip_xp_backends`` +* `#22585 `__: MAINT: stats.multinomial: ``FutureWarning`` about normalization... +* `#22593 `__: TST: add one more missing custom marker (``fail_slow``\ ) to... +* `#22597 `__: ENH: stats.make_distribution: improve interface for overriding... +* `#22598 `__: MAINT: stats.bootstrap: broadcast like other stats functions +* `#22602 `__: DOC: stats.pearsonr: add tutorial +* `#22603 `__: MAINT: _lib: bump version array_api_compat to 1.11 +* `#22605 `__: MAINT: signal: clean up unnecessary shims +* `#22606 `__: DOC: Ignore dict subclass docstring warning +* `#22607 `__: MAINT: special.logsumexp: improve behavior with complex infinities +* `#22609 `__: ENH: stats: shared array api support information to generate... +* `#22610 `__: ENH: _lib.doccer: Simplify and optimize indentation loop +* `#22611 `__: MAINT: stats: rewrite ``gaussian_kde.integrate_box``\ , remove... +* `#22614 `__: MAINT: linalg: fix cython lint failures in build output +* `#22616 `__: ENH: stats: use ``vecdot`` and ``nonzero`` where appropriate +* `#22618 `__: BUG: Fix dual quaternion normalization procedure +* `#22619 `__: DOC: stats.gaussian_kde: clarify the meaning of ``factor`` +* `#22621 `__: MAINT: sparse: remove incidental imports from private modules +* `#22623 `__: ENH: signal.convolve2d: Performance Enhancement on WoA +* `#22624 `__: BUG: stats: ``kde.integrate_box`` was missing an ``rng`` parameter +* `#22625 `__: MAINT: Bump array-api-compat and array-api-strict +* `#22628 `__: MAINT: stats.tmin/tmax: ensure exact results with unreasonably... +* `#22630 `__: MAINT: stats: tmin/tmax tweaks +* `#22631 `__: DOC: interpolate.BarycentricInterpolator: documentation improvements +* `#22632 `__: MAINT: stats.multinomial: use dtype-dependent tolerance +* `#22633 `__: ENH: special: ``softmax`` / ``log_softmax`` Array API support +* `#22634 `__: TST: special: cosmetic nits +* `#22636 `__: MAINT: fix domain check for ``ncfdtri`` +* `#22639 `__: ENH: special: ``support_alternative_backends`` on Dask and jax.jit +* `#22641 `__: ENH: special: add Dask support to ``rel_entr`` +* `#22645 `__: DOC: stats.special_ortho_group: update algorithm description +* `#22647 `__: MAINT: sparse: rewrite ``sparse._sputils.validateaxis`` to centralize... +* `#22648 `__: MAINT: stats.quantile: fixup quantile for p < minimum plotting... +* `#22649 `__: DOC, CI: Fix legend warning for CloughTocher2DInterpolator docstring +* `#22650 `__: TST: stats: mark ``nct`` fit xslow +* `#22651 `__: MAINT: ndimage.zoom: eliminate noise when ``zoom=1`` +* `#22653 `__: DOC: add COBYQA to local optimizer comparison table +* `#22658 `__: CI: clean up free-threading job, add new job using pytest-run-parallel +* `#22661 `__: TST: fix some test failures and excessive memory use on Guix +* `#22666 `__: MAINT: interpolate: move NdBSpline evaluations to C +* `#22667 `__: DEV: cap Sphinx version in environment.yml +* `#22668 `__: DOC: document Array API support for the constants module and... +* `#22669 `__: TST: constants: tidy up tests +* `#22671 `__: MAINT: enforce modularity with ``tach`` +* `#22675 `__: ENH: stats: Improvements to support/domain endpoints in custom... +* `#22676 `__: ENH: stats.mode: vectorize implementation +* `#22677 `__: MAINT: use function handles rather than custom strings in ``xp_capabilities_tabl``... +* `#22683 `__: MAINT: remove outdated ``xp_`` functions, ``xp.asarray`` on elementwise... +* `#22686 `__: TST/DOC: ``lazy_xp_backends`` in ``xp_capabilities`` +* `#22687 `__: MAINT: Bump Array API to 2024.12 +* `#22691 `__: DOC: signal: fix ``freqz_sos`` and ``sosfreqz`` docstrings +* `#22694 `__: DOC: interpolate.make_smoothing_spline: improve example visibility +* `#22695 `__: MAINT: improve dtype handling now that ``xp.result_type`` accepts... +* `#22696 `__: MAINT: spatial: support empty case in ``RigidTransform`` +* `#22698 `__: MAINT/DOC: Update incomplete examples of ``expectile()`` +* `#22701 `__: TST: optimize: add more tests +* `#22710 `__: DOC: integrate.quad_vec: returned object is not a dictionary +* `#22711 `__: DOC: stats: Extend documentation of random_correlation matrix +* `#22712 `__: MAINT: bump array-api-extra to 0.7.0 +* `#22713 `__: DOC: linalg.solve: clarify symmetry requirement +* `#22714 `__: MAINT: ndimage.maximum_filter: recommend ``vectorized_filter``... +* `#22715 `__: ENH: ndimage.vectorized_filter: make CuPy-compatible +* `#22716 `__: DOC: optimize: Clarify use of ``xtol`` in 1D rootfinder docstrings +* `#22718 `__: TST: special: overhaul test_support_alternative_backends +* `#22719 `__: TST: add tests for ``ncfdtri`` +* `#22722 `__: DOC: ndimage.affine_transformation: add examples to docstring +* `#22723 `__: DOC: fft.dst: add example to docstring +* `#22725 `__: MAINT: ndimage.affine_transform: remove outdated and unhelpful... +* `#22729 `__: DOC: datasets.download_all: add examples to docstring +* `#22735 `__: ENH: stats: lazy trimmed stats for Dask and JAX +* `#22738 `__: DOC: PRIMA licence and reference fix +* `#22740 `__: TST: special: remove test skips due to array-api-strict#131 +* `#22741 `__: CI: fix crash of free-threading job in ``sparse``\ , bump GHA... +* `#22742 `__: CI/MAINT: make special.errstate thread-safe and run pytest-run-parallel... +* `#22745 `__: DOC: fft.rfft2: add example to docstring +* `#22749 `__: ENH: stats: add support for multiple parameterizations for custom... +* `#22750 `__: DOC: fft.hfft2: added example +* `#22751 `__: TST: linalg.test_batch: minor tolerance bumps +* `#22755 `__: MAINT: special: refine ``logsumexp`` writeback behaviour +* `#22756 `__: BUG/TST: ``special.logsumexp`` on non-default device +* `#22759 `__: TST: weightedtau rng thread safety +* `#22760 `__: BUG: optimize: ``VectorFunction.f_updated`` wasn't being set... +* `#22761 `__: DOC: optimize: l-bfgs-b: clarify what is meant by ``maxfun``\... +* `#22764 `__: MAINT: optimize: ``VectorFunction``\ : remove reference cycle +* `#22766 `__: DOC: improve docstrings of boxcox and yeojohnson +* `#22770 `__: TST: stats: add marray tests for _length_nonmasked directly +* `#22771 `__: TST: stats: don't encapsulate ``pytest.warns`` +* `#22778 `__: MAINT: switch to vendoring libprima/prima +* `#22779 `__: MAINT: optimize: ``VectorFunction``\ : fix array copy for sparse +* `#22782 `__: MAINT: fix failures in free-threading(parallel=1) job +* `#22783 `__: TST/MAINT: signal.symiirorder2: r, omega, precision are floats;... +* `#22785 `__: DOC/DEV: remove references to CirrusCI in skipping CI doc +* `#22787 `__: DOC: optimize: Add the multiplier details to SLSQP funcs +* `#22788 `__: TST: stats.quantile: add edge test case for axis=None && keepdims=True +* `#22790 `__: MAINT: optimize.least_squares: change ``x_scale`` default +* `#22796 `__: ENH/BLD: cython: share memoryview utility between extension modules +* `#22798 `__: TST: stats: mark some tests as slow +* `#22802 `__: BUG: optimize: Fix instability with NNLS on 32bit systems +* `#22803 `__: MAINT: use ``xp.asarray`` instead of ``xp.array`` +* `#22805 `__: CI: start using the ``CIBW_ENABLE`` env var +* `#22807 `__: TST: fix issue with ``cython_special`` test which was missing... +* `#22808 `__: BUG: ``special.logsumexp`` device propagation on PyTorch +* `#22809 `__: ENH: ``optimize.root``\ : add warning for invalid inner parameters... +* `#22811 `__: ENH: ndimage.rotate: performance enhancement on WoA +* `#22814 `__: BUG: signal.resample: Fix bug for parameter num=2 (including... +* `#22815 `__: MAINT: sparse: add lazy loading for csgraph and linalg +* `#22818 `__: DEV: add ``.editorconfig`` +* `#22820 `__: MAINT: signal: consolidate ``order_filter`` tests +* `#22821 `__: ENH: signal.lp2{lp,hp,bp,bs}: add array API standard support +* `#22823 `__: MAINT: integrate.tanhsinh: simplify error estimate +* `#22829 `__: DOC: stats.qmc.discrepancy: clarify definitions +* `#22832 `__: DOC: interpolate: remove outdated deprecation notices +* `#22833 `__: DOC: special.comb: remove missed deprecation notice +* `#22835 `__: MAINT: stats.boxcox_llf: refactor for simplicity +* `#22842 `__: MAINT: bump boost_math to 1.88.0 +* `#22843 `__: DOC: ``special``\ : add ``xp_capabilities`` to logsumexp +* `#22844 `__: TST: ``stats``\ : minor nits to test_stats.py +* `#22845 `__: TST: ``stats``\ : reorder tests to match ``xp_capabilities`` +* `#22846 `__: MAINT: _lib/differentiate: update EIM with ``at.set`` +* `#22848 `__: MAINT: _lib: eliminate try/excepts in EIM +* `#22850 `__: TST: optimize ``VectorFunction`` add test for J0=None branch... +* `#22852 `__: TST: fix ``boxcox_llf`` test failure on main +* `#22854 `__: MAINT: special: Add ``xsf`` as a submodule of SciPy +* `#22855 `__: MAINT: spatial.pdist: make dimensionality error more descriptive +* `#22858 `__: DOC: Fix typo in ``ndimage.generic_gradient_magnitude()`` +* `#22859 `__: DOC: rewording of "ties" into "tied pairs" for clearer meaning +* `#22862 `__: TST: integrate/spatial: make fail_slow allowances +* `#22863 `__: TST: reintroduce ``eager_warns`` and fix free-threading test... +* `#22864 `__: MAINT: linalg.svd: raise correct error message for GESDD when... +* `#22873 `__: ENH: sparse: Support nD sum/mean/min/max/argmin for sparse arrays +* `#22875 `__: CI: limit pytest-fail-slow usage to a single CI job +* `#22886 `__: ENH: signal: filter design functions array API standard support +* `#22891 `__: DOC: Document allowed NumPy / Python versions +* `#22893 `__: MAINT: vendor qhull as subproject and add ``-Duse-system-libraries`` +* `#22895 `__: MAINT: signal: correct the ``get_window`` delegator +* `#22896 `__: ENH: signal: ``tf2zpk`` et al Array API +* `#22897 `__: ENH: sparse: ND binary operations support +* `#22898 `__: DEV: add editable install support for ``spin`` +* `#22899 `__: MAINT: bump array-api submodules +* `#22900 `__: MAINT: fix ``np.copyto`` warnings on Dask +* `#22908 `__: MAINT: bump qhull to 2020.2 +* `#22909 `__: TST: Use ``jax_autojit`` +* `#22913 `__: BUG: fix syntax warning break in finally block under 3.14 +* `#22915 `__: BLD: optimize sdist contents through a dist script +* `#22916 `__: DOC: integrate.solve_bvp: add missing reference details +* `#22917 `__: DEV: fix invocation of linter on Windows +* `#22918 `__: TST: ``linalg`` add test coverage to exception handling for invalid... +* `#22926 `__: MAINT: spatial.cKDTree: remove software prefetching and software... +* `#22927 `__: MAINT: tools/check_test_name: specify encoding +* `#22930 `__: DOC: linalg: update roadmap entry for BLAS/LAPACK bindings +* `#22932 `__: BUG: interpolate: do not call PyArray macros on non-arrays +* `#22934 `__: MAINT: optimize.zeros: fix error message +* `#22939 `__: TST: spatial.transform: Add array API standard support for testing +* `#22941 `__: MAINT: stats.qmc.Sobol: fix stacklevel of warning +* `#22944 `__: MAINT: fix regressions in array-api-strict after disabling np.float64 +* `#22946 `__: ENH: ``special``\ : add ``xp_capabilities`` +* `#22947 `__: MAINT: avoid nested ``asarray`` calls +* `#22949 `__: MAINT: mass rename ``make_skip_xp_backends`` to ``make_xp_test_case`` +* `#22950 `__: MAINT: refresh gpu-ci pixi.lock +* `#22952 `__: MAINT, DOC: forward port 1.15.3 release notes +* `#22955 `__: MAINT: wheel downloader +* `#22959 `__: ENH: ``cluster``\ : more lazy functions +* `#22960 `__: DOC/TST: ``cluster.hierarchy``\ : use ``xp_capabilities`` +* `#22961 `__: TST: ``cluster``\ : reduce test reliance from linkage +* `#22963 `__: MAINT: wrap wrapcauchy samples around the circle +* `#22967 `__: CI: address some potential vulnerabilities +* `#22968 `__: DOC: outline that not all attributes of OptimizeResult may be... +* `#22969 `__: MAINT: stats.make_distribution: fix most remaining discrete distribution... +* `#22970 `__: MAINT: stats.DiscreteDistribution: fix inversion methods +* `#22971 `__: MAINT: fix skellam distribution tests +* `#22973 `__: BUG: interpolate.make_splrep: raise error when ``residuals.sum()``... +* `#22976 `__: ENH: stats: Implement _munp for gennorm +* `#22982 `__: BUG: signal: fix incorrect vendoring of ``npp_polyval`` +* `#22984 `__: MAINT: special: remove test_compiles_in_cupy +* `#22987 `__: DOC: sparse: sparray migration guide updates +* `#22992 `__: ENH: ``signal.cspline1d_eval,qspline1d_eval`` throw exception... +* `#22994 `__: DOC: signal.csd: Small fixes to docstr +* `#22997 `__: CI: temporarily disable free-threaded job with parallel-threads +* `#22998 `__: BUG: Fix duplicate ``--pyargs`` due to double addition in SciPy... +* `#22999 `__: MAINT: bump up array-api-compat and array-api-extra +* `#23000 `__: ENH/DOC/TST: ``cluster.vq``\ : use ``xp_capabilities`` +* `#23001 `__: DOC: ``special``\ : update top-level docs to reflect ``xp_capabilities`` +* `#23005 `__: BUG: sparse: fix mean/sum change in return of np.matrix for sparse... From 450ef31bcbf1531d53e7ce98a9798dbde0d57d49 Mon Sep 17 00:00:00 2001 From: Nicolas Guidotti <38725499+nlg550@users.noreply.github.com> Date: Sun, 18 May 2025 06:55:36 +0100 Subject: [PATCH 225/251] ENH: sparse.linalg: a restarted Krylov method for evaluating f(A)b. (#22983) * ENH: sparse.linalg: a restarted Krylov method for evaluating f(A)b. * BUG: sparse.linalg: fix failing tests for krylov_funmv * BUG: sparse.linalg: fix invalid unicode and latex for krylov_funmv * BUG: sparse.linalg: fix the documentation for krylov_funmv * ENH: sparse.linalg: modified funm_mulitply_kyrlov code based on feedback * BUG: sparse.linalg: fix lint for funm_krylov_multipy * ENH: sparse.linalg: minor fixes to funm_multiply_krylov * BUG: sparse.linalg: fixed incorrect output in refguide * BUG: sparse.linalg: fix spacing for refguide * BUG: sparse.linalg: fix lint for test_funm_multiply_krylov * BUG: sparse.linalg: replace def with lambdas for the examples --- scipy/sparse/linalg/__init__.py | 2 + scipy/sparse/linalg/_funm_multiply_krylov.py | 348 ++++++++++++++++++ scipy/sparse/linalg/meson.build | 1 + scipy/sparse/linalg/tests/meson.build | 1 + .../linalg/tests/test_funm_multiply_krylov.py | 160 ++++++++ 5 files changed, 512 insertions(+) create mode 100644 scipy/sparse/linalg/_funm_multiply_krylov.py create mode 100644 scipy/sparse/linalg/tests/test_funm_multiply_krylov.py diff --git a/scipy/sparse/linalg/__init__.py b/scipy/sparse/linalg/__init__.py index ae19314d48b3..e32dd362f8d4 100644 --- a/scipy/sparse/linalg/__init__.py +++ b/scipy/sparse/linalg/__init__.py @@ -22,6 +22,7 @@ inv -- compute the sparse matrix inverse expm -- compute the sparse matrix exponential expm_multiply -- compute the product of a matrix exponential and a matrix + funm_multiply_krylov -- use a Krylov method to compute f(A)b for a general f matrix_power -- compute the matrix power by raising a matrix to an exponent Matrix norms @@ -136,6 +137,7 @@ from ._onenormest import * from ._norm import * from ._expm_multiply import * +from ._funm_multiply_krylov import * from ._special_sparse_arrays import * # Deprecated namespaces, to be removed in v2.0.0 diff --git a/scipy/sparse/linalg/_funm_multiply_krylov.py b/scipy/sparse/linalg/_funm_multiply_krylov.py new file mode 100644 index 000000000000..c0fb2e1aa5ae --- /dev/null +++ b/scipy/sparse/linalg/_funm_multiply_krylov.py @@ -0,0 +1,348 @@ +""" + Restared Krylov method for evaluating f(A)b + + The original code was written in MATLAB by Stefan Guttel. + It was later adapted to C++ and Python by Nicolas Guidotti. + Both authors agrees to relicense the code under the BSD license. + + Copyright (C) 2025 Nicolas Guidotti and Stefan Guttel. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" + +import numpy as np +from scipy.linalg import norm +from ._isolve.iterative import _get_atol_rtol + +__all__ = ['funm_multiply_krylov'] + +def _funm_multiply_krylov_arnoldi(A, b, bnorm, V, H, m): + """ + The Arnoldi iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + V[:, k + 1] = A.dot(V[:, k]) + + # Uses the modified Gram-Schmift process to orthogonalize V[:, k + 1] + # against the previous basis vectors + for i in range(0, k + 1): + H[i, k] = dotprod(V[:, i], V[:, k + 1]) + V[:, k + 1] = V[:, k + 1] - H[i, k] * V[:, i] + + H[k + 1, k] = norm(V[:, k + 1]) + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + + return False, m + +def _funm_multiply_krylov_lanczos(A, b, bnorm, V, H, m): + """ + The Lanczos iteration for constructing the basis V and the projection H = V * A V + for the Krylov subspace Km(A, b) of order m. A must be Hermitian. + + Parameters + ---------- + A : transposable linear operator + The operator whose matrix function is of interest. + b : ndarray + The vector b to multiply the f(A) with. + V : ndarray + The n x (m + 1) matrix whose columns determines the basis for + Krylov subspace Km(A, b). + H : ndarray + A (m + 1) x m upper Hessenberg matrix representing the projection of A + onto Km(A, b). + m : int + The order of the Krylov subspace. + + Returns + ------- + breakdown : bool + Indicate if the Arnoldi broke down or not + + iter : int + Returns the last valid iteration. + + """ + dotprod = np.vdot if np.iscomplexobj(b) else np.dot + norm_tol = np.finfo(b.dtype.char).eps ** 2 + V[:, 0] = b / bnorm + + for k in range(0, m): + if k > 0: + V[:, k + 1] = A.dot(V[:, k]) - H[k, k - 1] * V[:, k - 1] + else: + V[:, k + 1] = A.dot(V[:, k]) + + H[k, k] = dotprod(V[:, k + 1], V[:, k]) + V[:, k + 1] = V[:, k + 1] - H[k, k] * V[:, k] + + H[k + 1, k] = norm(V[:, k + 1]) + + if H[k + 1, k] < norm_tol: + return True, k + + V[:, k + 1] = V[:, k + 1] / H[k + 1, k] + if k < m - 1: + H[k, k + 1] = H[k + 1, k] + + return False, m + + +def funm_multiply_krylov(f, A, b, *, assume_a = "general", t = 1.0, atol = 0.0, + rtol = 1e-6, restart_every_m = None, max_restarts = 20): + """ + A restarted Krylov method for evaluating ``y = f(tA) b`` from [1]_ [2]_. + + Parameters + ---------- + f : callable + Callable object that computes the matrix function ``F = f(X)``. + + A : {sparse array, ndarray, LinearOperator} + A real or complex N-by-N matrix. + Alternatively, `A` can be a linear operator which can + produce ``Ax`` using, e.g., ``scipy.sparse.linalg.LinearOperator``. + + b : ndarray + A vector to multiply the ``f(tA)`` with. + + assume_a : string, optional + Indicate the structure of ``A``. The algorithm will use this information + to select the appropriated code path. The available options are + 'hermitian'/'her' and 'general'/'gen'. If ommited, then it is assumed + that ``A`` has a 'general' structure. + + t : float, optional + The value to scale the matrix ``A`` with. The default is ``t = 1.0`` + + atol, rtol : float, optional + Parameters for the convergence test. For convergence, + ``norm(||y_k - y_k-1||) <= max(rtol*norm(b), atol)`` should be satisfied. + The default is ``atol=0.`` and ``rtol=1e-6``. + + restart_every_m : integer + If the iteration number reaches this value a restart is triggered. + Larger values increase iteration cost but may be necessary for convergence. + If omitted, ``min(20, n)`` is used. + + max_restarts : int, optional + Maximum number of restart cycles. The algorithm will stop + after max_restarts cycles even if the specified tolerance has not been + achieved. The default is ``max_restarts=20`` + + Returns + ------- + y : ndarray + The result of ``f(tA) b``. + + Notes + ----- + The convergence of the Krylov method heavily depends on the spectrum + of ``A`` and the function ``f``. With restarting, there are only formal + proofs for functions of order 1 (e.g., ``exp``, ``sin``, ``cos``) and + Stieltjes functions [2]_ [3]_, while the general case remains an open problem. + + Examples + -------- + >>> import numpy as np + >>> from scipy.sparse import csr_array + >>> from scipy.sparse.linalg import funm_multiply_krylov + >>> from scipy.linalg import expm, solve + >>> A = csr_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float) + >>> b = np.array([2, 4, -1], dtype=float) + >>> t = 0.1 + + Compute ``y = exp(tA) b``. + + >>> y = funm_multiply_krylov(expm, A, b, t = t) + >>> y + array([3.6164913 , 3.88421511 , 0.96073457]) + + >>> ref = expm(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([4.44089210e-16 , 0.00000000e+00 , 2.22044605e-16]) + + Compute :math:`y = (A^3 - A) b`. + + >>> poly = lambda X : X @ X @ X - X + >>> y = funm_multiply_krylov(poly, A, b) + >>> y + array([132. , 24. , 70.]) + + >>> ref = poly(A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 7.10542736e-15 , -2.84217094e-14]) + + Compute :math:`y = f(tA) b`, where :math:`f(X) = X^{-1}(e^{X} - I)`. This is + known as the "phi function" from the exponential integrator literature. + + >>> phim_1 = lambda X : solve(X, expm(X) - np.eye(X.shape[0])) + >>> y = funm_multiply_krylov(phim_1, A, b, t = t) + >>> y + array([ 2.76984306 , 3.92769192 , -0.03111392]) + + >>> ref = phim_1(t * A.todense()) @ b + >>> err = y - ref + >>> err + array([ 0.00000000e+00 , 8.88178420e-16 , -4.60742555e-15]) + + References + ---------- + .. [1] M. Afanasjew, M. Eiermann, O. G. Ernst, and S. Güttel, + "Implementation of a restarted Krylov subspace method for the + evaluation of matrix functions," Linear Algebra and its Applications, + vol. 429, no. 10, pp. 2293-2314, Nov. 2008, :doi:`10.1016/j.laa.2008.06.029`. + + .. [2] M. Eiermann and O. G. Ernst, "A Restarted Krylov Subspace Method + for the Evaluation of Matrix Functions," SIAM J. Numer. Anal., vol. 44, + no. 6, pp. 2481-2504, Jan. 2006, :doi:`10.1137/050633846`. + + .. [3] A. Frommer, S. Güttel, and M. Schweitzer, "Convergence of Restarted + Krylov Subspace Methods for Stieltjes Functions of Matrices," SIAM J. + Matrix Anal. Appl., vol. 35, no. 4, pp. 1602-1624, + Jan. 2014, :doi:`10.1137/140973463`. + + """ + + if assume_a not in {'hermitian', 'general', 'her', 'gen'}: + raise ValueError(f'scipy.sparse.linalg.funm_multiply_krylov: {assume_a} ' + 'is not a recognized matrix structure') + is_hermitian = (assume_a == 'her') or (assume_a == 'hermitian') + + if len(b.shape) != 1: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'b' must be a 1D array.") + n = b.shape[0] + + if restart_every_m is None: + restart_every_m = min(20, n) + + restart_every_m = int(restart_every_m) + max_restarts = int(max_restarts) + + if restart_every_m <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'restart_every_m' must be positive.") + + if max_restarts <= 0: + raise ValueError("scipy.sparse.linalg.funm_multiply_krylov: " + "argument 'max_restarts' must be positive.") + + m = restart_every_m + max_restarts = min(max_restarts, int(n / m) + 1) + mmax = m * max_restarts + + bnorm = norm(b) + atol, _ = _get_atol_rtol("funm_multiply_krylov", bnorm, atol, rtol) + + if bnorm == 0: + y = np.array(b) + return y + + # Preallocate the maximum memory space. + # Using the column major order here since we work with + # each individual column separately. + internal_type = np.common_type(A, b) + V = np.zeros((n, m + 1), dtype = internal_type, order = 'F') + H = np.zeros((mmax + 1, mmax), dtype = internal_type, order = 'F') + + restart = 1 + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, b, bnorm, V, + H[:m + 1, :m], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, b, bnorm, V, + H[:m + 1, :m], m) + + fH = f(t * H[:j, :j]) + y = bnorm * V[:, :j].dot(fH[:, 0]) + + if breakdown: + return y + + update_norm = norm(bnorm * fH[:, 0]) + + while restart < max_restarts and update_norm > atol: + begin = restart * m + end = (restart + 1) * m + + if is_hermitian: + breakdown, j = _funm_multiply_krylov_lanczos(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + else: + breakdown, j = _funm_multiply_krylov_arnoldi(A, V[:, m], 1, V, + H[begin:end + 1, begin:end], m) + + if breakdown: + end = begin + j + fH = f(t * H[:end, :end]) + y[:end] = y[:end] + bnorm * V[:, :m].dot(fH[begin:end, 0]) + return y + + fH = f(t * H[:end, :end]) + y = y + bnorm * V[:, :m].dot(fH[begin:end, 0]) + update_norm = norm(bnorm * fH[begin:end, 0]) + restart += 1 + + return y diff --git a/scipy/sparse/linalg/meson.build b/scipy/sparse/linalg/meson.build index e201ae10943f..43f68a15af9a 100644 --- a/scipy/sparse/linalg/meson.build +++ b/scipy/sparse/linalg/meson.build @@ -1,6 +1,7 @@ py3.install_sources([ '__init__.py', '_expm_multiply.py', + '_funm_multiply_krylov.py', '_interface.py', '_matfuncs.py', '_norm.py', diff --git a/scipy/sparse/linalg/tests/meson.build b/scipy/sparse/linalg/tests/meson.build index ca7458878fb0..e80cf09ed5a5 100644 --- a/scipy/sparse/linalg/tests/meson.build +++ b/scipy/sparse/linalg/tests/meson.build @@ -1,6 +1,7 @@ py3.install_sources([ '__init__.py', 'propack_test_data.npz', + 'test_funm_multiply_krylov.py', 'test_expm_multiply.py', 'test_interface.py', 'test_matfuncs.py', diff --git a/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py b/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py new file mode 100644 index 000000000000..93b545e96462 --- /dev/null +++ b/scipy/sparse/linalg/tests/test_funm_multiply_krylov.py @@ -0,0 +1,160 @@ +"""Test functions for the sparse.linalg._krylov_funm module.""" +from functools import partial + +import numpy as np +import pytest +from numpy.testing import (assert_allclose) +import scipy.sparse +import scipy.linalg +from scipy.sparse.linalg import aslinearoperator +from scipy.linalg import (expm, cosm, coshm, sinm, sinhm) + +from scipy.sparse.linalg._funm_multiply_krylov import funm_multiply_krylov + +REAL_DTYPES = (np.float32, np.float64) +COMPLEX_DTYPES = (np.complex64, np.complex128) +DTYPES = REAL_DTYPES + COMPLEX_DTYPES + +# This the phi_1 function from exponential integrators: phi_1 (z) = (e^z - 1) / z +def custom(X): + return scipy.linalg.solve(X, expm(X) - np.eye(X.shape[0])) + +FUNCS = (expm, cosm, coshm, sinm, sinhm, custom) + +class TestKrylovFunmv: + + def test_krylov_funm_zero_vector(self): + n = 20 + A = np.zeros((n, n)) + b = np.zeros(n) + observed = funm_multiply_krylov(expm, A, b) + expected = np.zeros(n) + assert_allclose(observed, expected) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_dense(self, f): + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + A = rng.standard_normal((n, n)) + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_nonhermitian_sparse(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 10 + + for i in range(nsamples): + D = scipy.sparse.diags(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b) + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_dense(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 60 + nsamples = 10 + + for i in range(nsamples): + R = np.triu(rng.standard_normal((n, n))) + A = R.T + R + b = rng.standard_normal(n) + + fA = f(A) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + @pytest.mark.parametrize("f", FUNCS) + def test_funm_multiply_krylov_hermitian_sparse(self, f): + + rng = np.random.default_rng(1738151906092735) + n = 100 + nsamples = 10 + + for i in range(nsamples): + D = scipy.sparse.diags(rng.standard_normal(n)) + A = scipy.sparse.random_array((n, n), density = 0.01, rng = rng) + R = scipy.sparse.triu(A) + A = R + R.T + D + denseA = A.todense() + b = rng.standard_normal(n) + + fA = f(denseA) + expected = fA @ b + observed = funm_multiply_krylov(f, A, b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + observed = funm_multiply_krylov(f, aslinearoperator(A), b, assume_a = 'her') + assert_allclose(observed, expected, rtol = 1E-6, atol = 1E-8) + + def test_funm_multiply_krylov_breakdown(self): + rng = np.random.default_rng(1738151906092735) + + # From test_iterative + A = np.array([[0, 0, 0, 0, 0, 1, -1, -0, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -1, -0, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -1, -0, -0], + [0, 0, 0, 0, 0, 2, -0, -0, -0, -1, -0], + [0, 0, 0, 0, 0, 1, -0, -0, -0, -0, -1], + [1, 2, 2, 2, 1, 0, -0, -0, -0, -0, -0], + [-1, 0, 0, 0, 0, 0, -1, -0, -0, -0, -0], + [0, -1, 0, 0, 0, 0, -0, -1, -0, -0, -0], + [0, 0, -1, 0, 0, 0, -0, -0, -1, -0, -0], + [0, 0, 0, -1, 0, 0, -0, -0, -0, -1, -0], + [0, 0, 0, 0, -1, 0, -0, -0, -0, -0, -1]], dtype = float) + b = rng.standard_normal(A.shape[0]) + + fA = expm(A) + expected = fA @ b + observed = funm_multiply_krylov(expm, A, b, restart_every_m = 40) + assert_allclose(observed, expected) + + +@pytest.mark.parametrize("dtype_a", DTYPES) +@pytest.mark.parametrize("dtype_b", DTYPES) +def test_funm_multiply_krylov_types(dtype_a, dtype_b): + assert_allclose_ = (partial(assert_allclose, rtol = 1.8e-3, atol = 1e-5) + if {dtype_a, dtype_b} else assert_allclose) + + rng = np.random.default_rng(1738151906092735) + n = 50 + + if dtype_a in REAL_DTYPES: + A = rng.random([n, n]).astype(dtype_a) + else: + A = (rng.random([n, n]) + 1j * rng.random([n, n])).astype(dtype_a) + + if dtype_b in REAL_DTYPES: + b = (2 * rng.random(n)).astype(dtype_b) + else: + b = (rng.random(n) + 1j * rng.random(n)).astype(dtype_b) + + expA = expm(A) + expected = expA @ b + observed = funm_multiply_krylov(expm, A, b) + assert_allclose_(observed, expected) + observed = funm_multiply_krylov(expm, aslinearoperator(A), b) + assert_allclose_(observed, expected) From 5726f3a1279a8ec7d8663694687f3a2bb539ef6f Mon Sep 17 00:00:00 2001 From: Dan Raviv Date: Mon, 19 May 2025 15:13:23 -0700 Subject: [PATCH 226/251] DOC: scipy.signal.ShortTimeFFT.k_max: Fix slice index As depicted in https://docs.scipy.org/doc/scipy/tutorial/signal.html#sliding-windows, `k_max - 1` is the largest sample index of the slice `p_max - 1`, not `p_max`. [docs only] --- scipy/signal/_short_time_fft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_short_time_fft.py b/scipy/signal/_short_time_fft.py index 5da4017fce75..37fc00090338 100644 --- a/scipy/signal/_short_time_fft.py +++ b/scipy/signal/_short_time_fft.py @@ -1699,7 +1699,7 @@ def _post_padding(self, n: int) -> tuple[int, int]: def k_max(self, n: int) -> int: """First sample index after signal end not touched by a time slice. - `k_max` - 1 is the largest sample index of the slice `p_max` for a + `k_max` - 1 is the largest sample index of the slice `p_max - 1` for a given input signal of `n` samples. A detailed example is provided in the :ref:`tutorial_stft_sliding_win` section of the :ref:`user_guide`. From 7f75a948bd46d0453885dac81efded3aaa640907 Mon Sep 17 00:00:00 2001 From: Dietrich Brunn Date: Tue, 20 May 2025 11:21:45 +0200 Subject: [PATCH 227/251] DOC: scipy.signal.ShortTimeFFT.k_max: Fix small typo Having "`p_max` - 1" instead of "`p_max - 1`" renders `p_max` as internal link in HTML docs. [doc only] --- scipy/signal/_short_time_fft.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/signal/_short_time_fft.py b/scipy/signal/_short_time_fft.py index 37fc00090338..748ac698e282 100644 --- a/scipy/signal/_short_time_fft.py +++ b/scipy/signal/_short_time_fft.py @@ -1699,7 +1699,7 @@ def _post_padding(self, n: int) -> tuple[int, int]: def k_max(self, n: int) -> int: """First sample index after signal end not touched by a time slice. - `k_max` - 1 is the largest sample index of the slice `p_max - 1` for a + `k_max` - 1 is the largest sample index of the slice `p_max` - 1 for a given input signal of `n` samples. A detailed example is provided in the :ref:`tutorial_stft_sliding_win` section of the :ref:`user_guide`. From b0f5029a3a6c3dd9c0189c04895f2129eaca1b7a Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Tue, 20 May 2025 21:13:07 +0100 Subject: [PATCH 228/251] DEP: stats: remove find_repeats --- scipy/stats/__init__.py | 1 - scipy/stats/_morestats.py | 2 +- scipy/stats/_stats_py.py | 49 ++------------------------------- scipy/stats/morestats.py | 2 +- scipy/stats/stats.py | 2 +- scipy/stats/tests/test_stats.py | 20 -------------- 6 files changed, 5 insertions(+), 71 deletions(-) diff --git a/scipy/stats/__init__.py b/scipy/stats/__init__.py index c660957c1b20..5bd1ed2ef475 100644 --- a/scipy/stats/__init__.py +++ b/scipy/stats/__init__.py @@ -260,7 +260,6 @@ tstd -- tsem -- variation -- Coefficient of variation - find_repeats rankdata tiecorrect trim_mean diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index b701eb3e7068..28c1a97b16cb 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -22,7 +22,7 @@ from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon from ._fit import FitResult -from ._stats_py import (find_repeats, _get_pvalue, SignificanceResult, # noqa:F401 +from ._stats_py import (_get_pvalue, SignificanceResult, # noqa:F401 _SimpleNormal, _SimpleChi2, _length_nonmasked) from .contingency import chi2_contingency from . import distributions diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 90b6f45994e2..1822f27b0100 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -43,7 +43,7 @@ from scipy._lib._util import (check_random_state, _get_nan, _rename_parameter, _contains_nan, normalize_axis_index, np_vecdot, AxisError) -from scipy._lib.deprecation import _deprecate_positional_args, _deprecated +from scipy._lib.deprecation import _deprecate_positional_args import scipy.special as special # Import unused here but needs to stay until end of deprecation periode @@ -90,7 +90,7 @@ # Functions/classes in other files should be added in `__init__.py`, not here -__all__ = ['find_repeats', 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', +__all__ = ['gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', 'tmin', 'tmax', 'tstd', 'tsem', 'moment', 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', 'normaltest', 'jarque_bera', @@ -10074,51 +10074,6 @@ def _validate_distribution(values, weights): # SUPPORT FUNCTIONS # ##################################### -RepeatedResults = namedtuple('RepeatedResults', ('values', 'counts')) - - -@_deprecated("`scipy.stats.find_repeats` is deprecated as of SciPy 1.15.0 " - "and will be removed in SciPy 1.17.0. Please use " - "`numpy.unique`/`numpy.unique_counts` instead.") -def find_repeats(arr): - """Find repeats and repeat counts. - - .. deprecated:: 1.15.0 - - This function is deprecated as of SciPy 1.15.0 and will be removed - in SciPy 1.17.0. Please use `numpy.unique` / `numpy.unique_counts` instead. - - Parameters - ---------- - arr : array_like - Input array. This is cast to float64. - - Returns - ------- - values : ndarray - The unique values from the (flattened) input that are repeated. - - counts : ndarray - Number of times the corresponding 'value' is repeated. - - Notes - ----- - In numpy >= 1.9 `numpy.unique` provides similar functionality. The main - difference is that `find_repeats` only returns repeated values. - - Examples - -------- - >>> from scipy import stats - >>> stats.find_repeats([2, 1, 2, 3, 2, 2, 5]) - RepeatedResults(values=array([2.]), counts=array([4])) - - >>> stats.find_repeats([[10, 20, 1, 2], [5, 5, 4, 4]]) - RepeatedResults(values=array([4., 5.]), counts=array([2, 2])) - - """ - # Note: always copies. - return RepeatedResults(*_find_repeats(np.array(arr, dtype=np.float64))) - def _sum_of_squares(a, axis=0): """Square each element of the input array, and return the sum(s) of that. diff --git a/scipy/stats/morestats.py b/scipy/stats/morestats.py index ee8e6f43b7aa..76040ea0ca52 100644 --- a/scipy/stats/morestats.py +++ b/scipy/stats/morestats.py @@ -13,7 +13,7 @@ 'fligner', 'mood', 'wilcoxon', 'median_test', 'circmean', 'circvar', 'circstd', 'anderson_ksamp', 'yeojohnson_llf', 'yeojohnson', 'yeojohnson_normmax', - 'yeojohnson_normplot', 'find_repeats', 'chi2_contingency', 'distributions', + 'yeojohnson_normplot', 'chi2_contingency', 'distributions', ] diff --git a/scipy/stats/stats.py b/scipy/stats/stats.py index d5d278e209ce..6879c9c07cb0 100644 --- a/scipy/stats/stats.py +++ b/scipy/stats/stats.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 - 'find_repeats', 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', + 'gmean', 'hmean', 'pmean', 'mode', 'tmean', 'tvar', 'tmin', 'tmax', 'tstd', 'tsem', 'moment', 'skew', 'kurtosis', 'describe', 'skewtest', 'kurtosistest', 'normaltest', 'jarque_bera', diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py index e33b8ffae5ea..eb4d1847cc73 100644 --- a/scipy/stats/tests/test_stats.py +++ b/scipy/stats/tests/test_stats.py @@ -2141,26 +2141,6 @@ def weigher(x): rng.shuffle(rank) -class TestFindRepeats: - - def test_basic(self): - a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 5] - message = "`scipy.stats.find_repeats` is deprecated..." - with pytest.deprecated_call(match=message): - res, nums = stats.find_repeats(a) - assert_array_equal(res, [1, 2, 3, 4]) - assert_array_equal(nums, [3, 3, 2, 2]) - - def test_empty_result(self): - # Check that empty arrays are returned when there are no repeats. - for a in [[10, 20, 50, 30, 40], []]: - message = "`scipy.stats.find_repeats` is deprecated..." - with pytest.deprecated_call(match=message): - repeated, counts = stats.find_repeats(a) - assert_array_equal(repeated, []) - assert_array_equal(counts, []) - - class TestRegression: def test_linregressBIGX(self): # W.II.F. Regress BIG on X. From 0e22db7c307dfed9e80cdcda67641358bd61c4c1 Mon Sep 17 00:00:00 2001 From: Gagandeep Singh Date: Wed, 21 May 2025 16:34:44 +0530 Subject: [PATCH 229/251] DEV: Remove --parallel and rely on spin's --jobs argument --- .spin/cmds.py | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/.spin/cmds.py b/.spin/cmds.py index 1c09426c74fe..18d9a7930520 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -33,10 +33,6 @@ '--debug', '-d', default=False, is_flag=True, help="Debug build") @click.option( '--release', '-r', default=False, is_flag=True, help="Release build") -@click.option( - '--parallel', '-j', default=None, metavar='N_JOBS', - help=("Number of parallel jobs for building. " - "This defaults to the number of available physical CPU cores")) @click.option( '--setup-args', '-C', default=[], multiple=True, help=("Pass along one or more arguments to `meson setup` " @@ -63,7 +59,7 @@ ) @spin.util.extend_command(spin.cmds.meson.build) def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, - release, parallel, setup_args, show_build_log, + release, setup_args, show_build_log, with_scipy_openblas, with_accelerate, use_system_libraries, tags, **kwargs): """🔧 Build package with Meson/ninja and install @@ -135,13 +131,11 @@ def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, if use_system_libraries: meson_args = meson_args + ("-Duse-system-libraries=auto",) - if parallel is None: + if jobs is None: # Use number of physical cores rather than ninja's default of 2N+2, # to avoid out of memory issues (see gh-17941 and gh-18443) n_cores = cpu_count(only_physical_cores=True) jobs = n_cores - else: - jobs = parallel meson_install_args = meson_install_args + ("--tags=" + tags, ) @@ -166,10 +160,6 @@ def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, '--mode', '-m', default='not slow', metavar='MODE', show_default=True, help=("'fast', 'full', or something that could be passed to " "`pytest -m` as a marker expression")) -@click.option( - '--parallel', '-j', default=1, metavar='N_JOBS', - help="Number of parallel jobs for testing" -) @click.option( '--array-api-backend', '-b', default=None, metavar='ARRAY_BACKEND', multiple=True, @@ -181,8 +171,7 @@ def build(*, parent_callback, meson_args, jobs, verbose, werror, asan, debug, ) @spin.util.extend_command(spin.cmds.meson.test) def test(*, parent_callback, pytest_args, tests, coverage, - durations, submodule, mode, parallel, - array_api_backend, **kwargs): + durations, submodule, mode, array_api_backend, **kwargs): """🔧 Run tests PYTEST_ARGS are passed through directly to pytest, e.g.: @@ -263,10 +252,6 @@ def test(*, parent_callback, pytest_args, tests, coverage, if markexpr != "full": pytest_args = ('-m', markexpr) + pytest_args - n_jobs = parallel - if (n_jobs != 1) and ('-n' not in pytest_args): - pytest_args = ('-n', str(n_jobs)) + pytest_args - if durations: pytest_args += ('--durations', durations) @@ -280,10 +265,6 @@ def test(*, parent_callback, pytest_args, tests, coverage, '--list-targets', '-t', default=False, is_flag=True, help='List doc targets', ) -@click.option( - '--parallel', '-j', default="auto", metavar='N_JOBS', - help="Number of parallel jobs" - ) @click.option( '--no-cache', default=False, is_flag=True, help="Forces a full rebuild of the docs. Note that this may be " + \ @@ -292,7 +273,7 @@ def test(*, parent_callback, pytest_args, tests, coverage, ) @spin.util.extend_command(spin.cmds.meson.docs) def docs(*, parent_callback, sphinx_target, clean, jobs, - list_targets, parallel, no_cache, **kwargs): + list_targets, no_cache, **kwargs): """📖 Build Sphinx documentation By default, SPHINXOPTS="-W", raising errors on warnings. @@ -326,7 +307,6 @@ def docs(*, parent_callback, sphinx_target, clean, jobs, if no_cache: SPHINXOPTS += " -E" - jobs = parallel SPHINXOPTS = os.environ.get("SPHINXOPTS", "") + SPHINXOPTS os.environ["SPHINXOPTS"] = SPHINXOPTS From aad0bc50dcf2fe0cf05575fd15efeac826ad30bb Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 21 May 2025 17:45:59 +0200 Subject: [PATCH 230/251] DOC: add documentation for the new `use-system-libraries` build option [docs only] --- .../building/redistributable_binaries.rst | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/source/building/redistributable_binaries.rst b/doc/source/building/redistributable_binaries.rst index 9951f487e135..65aab3e83283 100644 --- a/doc/source/building/redistributable_binaries.rst +++ b/doc/source/building/redistributable_binaries.rst @@ -55,6 +55,40 @@ More detailed information on these build dependencies can be found in :ref:`toolchain-roadmap`. +Using system dependencies instead of vendored sources +----------------------------------------------------- + +SciPy contains a *lot* of code that has been vendored from libraries written in +C, C++ and Fortran. This is done for a mix of historical and robustness +reasons. Distro packagers sometimes have reasons to want to *unvendor* this +code, ensuring that they only have a single version of a particular library in +use. We offer a build option, ``-Duse-system-libraries``, to allow them to do +so, e.g.: + +.. code:: + + $ python -m build -wnx -Duse-system-libraries=boost.math + +The build option takes the following values: + +- ``none``: Use the source code shipped as part of SciPy, do not look for + external dependencies (default). +- ``all``: Use external libraries for all components controlled by + ``use-system-libraries``, and error out if they are not found. Detecting is + done by Meson, typically first through the ``pkg-config`` and then the + ``cmake`` method of the ``dependency()`` function. +- ``auto``: Try detecting external libraries, and if they are not found then + fall back onto vendored sources. +- A comma-separated list of dependency names (e.g., ``boost.math,qhull``). If + given, uses the ``all`` behavior for the named dependencies (and ``none`` for + the rest). See ``meson.options`` in the root of the SciPy repository or sdist + for all supported options. + +It's also valid to combine the generic and named-library options above, for +example ``boost.math,auto`` will apply the ``auto`` behavior to all +dependencies other than ``boost.math``. + + Stripping the test suite from a wheel or installed package ---------------------------------------------------------- From daf9b1488098a5c401c98ec38dc0802a997398dc Mon Sep 17 00:00:00 2001 From: h-vetinari Date: Fri, 23 May 2025 03:13:28 +1100 Subject: [PATCH 231/251] MAINT: fix inaccuracy in factorial2 docstring (#23024) --- scipy/special/_basic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index f9d6295b5645..e349a009b2d2 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -3231,7 +3231,7 @@ def factorial2(n, exact=False, extend="zero"): -------- >>> from scipy.special import factorial2 >>> factorial2(7, exact=False) - array(105.00000000000001) + np.float64(105.00000000000001) >>> factorial2(7, exact=True) 105 From 99ea7a46d481be60700b75965ae64610c9b2fff2 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 22 May 2025 20:42:36 +0200 Subject: [PATCH 232/251] TYP: fix and improve the ``linalg._decomp_lu_cython`` stub --- scipy/linalg/_decomp_lu_cython.pyi | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scipy/linalg/_decomp_lu_cython.pyi b/scipy/linalg/_decomp_lu_cython.pyi index 0a175b1de328..9b7434f5e92d 100644 --- a/scipy/linalg/_decomp_lu_cython.pyi +++ b/scipy/linalg/_decomp_lu_cython.pyi @@ -1,6 +1,8 @@ +import numpy as np from numpy.typing import NDArray -from typing import Any +from typing import TypeVar -def lu_decompose(a: NDArray[Any], lu: NDArray[Any], perm: NDArray[Any], permute_l: bool) -> None: ... # noqa: E501 +# this mimicks the `ctypedef fused lapack_t` +_LapackT = TypeVar("_LapackT", np.float32, np.float64, np.complex64, np.complex128) -def lu_dispatcher(a: NDArray[Any], lu: NDArray[Any], perm: NDArray[Any], permute_l: bool) -> None: ... # noqa: E501 +def lu_dispatcher(a: NDArray[_LapackT], lu: NDArray[_LapackT], perm: NDArray[np.integer], permute_l: bool) -> None: ... From 5c502e27148cad050c12b7c43e5f9d50e4dd9770 Mon Sep 17 00:00:00 2001 From: jorenham Date: Thu, 22 May 2025 20:56:05 +0200 Subject: [PATCH 233/251] TYP: fix incorrect parameter names of ``linalg._decomp_lu_cython.lu_dispatcher`` --- scipy/linalg/_decomp_lu_cython.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/linalg/_decomp_lu_cython.pyi b/scipy/linalg/_decomp_lu_cython.pyi index 9b7434f5e92d..99ee34e6f27e 100644 --- a/scipy/linalg/_decomp_lu_cython.pyi +++ b/scipy/linalg/_decomp_lu_cython.pyi @@ -5,4 +5,4 @@ from typing import TypeVar # this mimicks the `ctypedef fused lapack_t` _LapackT = TypeVar("_LapackT", np.float32, np.float64, np.complex64, np.complex128) -def lu_dispatcher(a: NDArray[_LapackT], lu: NDArray[_LapackT], perm: NDArray[np.integer], permute_l: bool) -> None: ... +def lu_dispatcher(a: NDArray[_LapackT], u: NDArray[_LapackT], piv: NDArray[np.integer], permute_l: bool) -> None: ... From ef89229d89c0bea949f10cf1528ada04149b90dd Mon Sep 17 00:00:00 2001 From: Jake Bowhay <60778417+j-bowhay@users.noreply.github.com> Date: Thu, 22 May 2025 22:58:41 +0100 Subject: [PATCH 234/251] DEP: linalg.interpolative: remove seed and rand (#23034) --- scipy/linalg/interpolative.py | 49 ----------------------------------- 1 file changed, 49 deletions(-) diff --git a/scipy/linalg/interpolative.py b/scipy/linalg/interpolative.py index 38070863aa51..37c2c0c7f10c 100644 --- a/scipy/linalg/interpolative.py +++ b/scipy/linalg/interpolative.py @@ -94,14 +94,6 @@ estimate_spectral_norm_diff estimate_rank -Following support functions are deprecated and will be removed in SciPy 1.17.0: - -.. autosummary:: - :toctree: generated/ - - seed - rand - References ========== @@ -367,7 +359,6 @@ import scipy.linalg._decomp_interpolative as _backend import numpy as np -import warnings __all__ = [ 'estimate_rank', @@ -375,11 +366,9 @@ 'estimate_spectral_norm_diff', 'id_to_svd', 'interp_decomp', - 'rand', 'reconstruct_interp_matrix', 'reconstruct_matrix_from_id', 'reconstruct_skel_matrix', - 'seed', 'svd', ] @@ -411,44 +400,6 @@ def _is_real(A): raise _TYPE_ERROR from e -def seed(seed=None): - """ - This function, historically, used to set the seed of the randomization algorithms - used in the `scipy.linalg.interpolative` functions written in Fortran77. - - The library has been ported to Python and now the functions use the native NumPy - generators and this function has no content and returns None. Thus this function - should not be used and will be removed in SciPy version 1.17.0. - """ - warnings.warn("`scipy.linalg.interpolative.seed` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) - - -def rand(*shape): - """ - This function, historically, used to generate uniformly distributed random number - for the randomization algorithms used in the `scipy.linalg.interpolative` functions - written in Fortran77. - - The library has been ported to Python and now the functions use the native NumPy - generators. Thus this function should not be used and will be removed in the - SciPy version 1.17.0. - - If pseudo-random numbers are needed, NumPy pseudo-random generators should be used - instead. - - Parameters - ---------- - *shape - Shape of output array - - """ - warnings.warn("`scipy.linalg.interpolative.rand` is deprecated and will be " - "removed in SciPy 1.17.0.", DeprecationWarning, stacklevel=3) - rng = np.random.default_rng() - return rng.uniform(low=0., high=1.0, size=shape) - - def interp_decomp(A, eps_or_k, rand=True, rng=None): """ Compute ID of a matrix. From fdb21376096080f432330ed1e90d6105e7389c7e Mon Sep 17 00:00:00 2001 From: Akshay Priyadarshi Date: Thu, 22 May 2025 23:34:03 +0100 Subject: [PATCH 235/251] DOC: fix typo in differential_evolution docstring for atol parameter (#22990) --- scipy/optimize/_differentialevolution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scipy/optimize/_differentialevolution.py b/scipy/optimize/_differentialevolution.py index f9ce15417856..f9cf1229e279 100644 --- a/scipy/optimize/_differentialevolution.py +++ b/scipy/optimize/_differentialevolution.py @@ -204,7 +204,7 @@ def differential_evolution(func, bounds, args=(), strategy='best1bin', convergence. atol : float, optional Absolute tolerance for convergence, the solving stops when - ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``, + ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``, where and `atol` and `tol` are the absolute and relative tolerance respectively. updating : {'immediate', 'deferred'}, optional From f8ca671a45e2ae9960a7faf9ad89ba45db02e3d5 Mon Sep 17 00:00:00 2001 From: Matt Haberland Date: Fri, 23 May 2025 00:14:57 -0700 Subject: [PATCH 236/251] ENH: stats: add `marray` support to a few remaining functions (#22827) * TST: stats.gstd: test gstd marray support * ENH: stats.directional_stats: add marray support * ENH: stats.bartlett: add marray support [skip ci] * ENH: stats.variation: add marray support * ENH: stats.pearsonr: add marray support * TST: stats.variation: skip test due to torch bug * ENH: stats.entropy: add marray support [skip ci] * DOC: stats.gaussian_kde: more adjustments [docs only] * Revert "DOC: stats.gaussian_kde: more adjustments" This reverts commit 1e34784483d4f208343498f803544f98fe36f661. [skip ci] * TST: stats: skip some Dask tests due to array-api-extra#196 * MAINT: stats.entropy: appease mypy * MAINT: stats: post merge main fixups * STY: stats: fix lint issue [lint only] * MAINT: stats: post-merge fixups * CI: unpin marray version --- .github/workflows/array_api.yml | 2 +- scipy/_lib/_array_api.py | 27 +++++++- scipy/_lib/tests/test_warnings.py | 1 + scipy/spatial/distance.py | 9 +-- scipy/stats/_entropy.py | 26 ++++--- scipy/stats/_morestats.py | 23 ++++-- scipy/stats/_quantile.py | 4 +- scipy/stats/_stats_py.py | 104 ++++++++++++---------------- scipy/stats/_variation.py | 35 ++++------ scipy/stats/tests/test_marray.py | 69 +++++++++++++++++- scipy/stats/tests/test_morestats.py | 5 +- scipy/stats/tests/test_variation.py | 1 + 12 files changed, 200 insertions(+), 106 deletions(-) diff --git a/.github/workflows/array_api.yml b/.github/workflows/array_api.yml index 95239d1b640a..af4c488e838a 100644 --- a/.github/workflows/array_api.yml +++ b/.github/workflows/array_api.yml @@ -76,7 +76,7 @@ jobs: - name: Install MArray run: | - python -m pip install marray==0.0.8 + python -m pip install marray - name: Prepare compiler cache id: prep-ccache diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index 5e1170ac51ef..fa70519d086f 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -6,6 +6,7 @@ The SciPy use case of the Array API is described on the following page: https://data-apis.org/array-api/latest/use_cases.html#use-case-scipy """ +import operator import contextlib import dataclasses import functools @@ -637,6 +638,7 @@ def xp_default_dtype(xp): return xp.float64 +### MArray Helpers ### def xp_result_device(*args): """Return the device of an array in `args`, for the purpose of input-output device propagation. @@ -656,6 +658,27 @@ def is_marray(xp): return "marray" in xp.__name__ +def _length_nonmasked(x, axis, keepdims=False, xp=None): + xp = array_namespace(x) if xp is None else xp + if is_marray(xp): + if np.iterable(axis): + message = '`axis` must be an integer or None for use with `MArray`.' + raise NotImplementedError(message) + return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype) + return (xp_size(x) if axis is None else + # compact way to deal with axis tuples or ints + int(np.prod(np.asarray(x.shape)[np.asarray(axis)]))) + + +def _share_masks(*args, xp): + if is_marray(xp): + mask = functools.reduce(operator.or_, (arg.mask for arg in args)) + args = [xp.asarray(arg.data, mask=mask) for arg in args] + return args[0] if len(args) == 1 else args + +### End MArray Helpers ### + + @dataclasses.dataclass(repr=False) class _XPSphinxCapability: cpu: bool | None # None if not applicable @@ -779,7 +802,7 @@ def xp_capabilities( SKIP/XFAIL markers and perform additional backend-specific testing, such as extra validation for Dask and JAX; 2. It automatically adds a note to the function's docstring, containing - a table matching what has been tested. + a table matching what has been tested. See Also -------- @@ -880,7 +903,7 @@ def make_xp_test_case(*funcs, capabilities_table=None): def make_xp_pytest_param(func, *args, capabilities_table=None): """Variant of ``make_xp_test_case`` that returns a pytest.param for a function, with all necessary skip_xp_backends and xfail_xp_backends marks applied:: - + @pytest.mark.parametrize( "func", [make_xp_pytest_param(f1), make_xp_pytest_param(f2)] ) diff --git a/scipy/_lib/tests/test_warnings.py b/scipy/_lib/tests/test_warnings.py index f200b1a6e975..0a5f5cfbc55d 100644 --- a/scipy/_lib/tests/test_warnings.py +++ b/scipy/_lib/tests/test_warnings.py @@ -120,6 +120,7 @@ def test_warning_calls_filters(warning_calls): os.path.join('stats', '_continuous_distns.py'), os.path.join('stats', '_binned_statistic.py'), # gh-19345 os.path.join('stats', '_stats_py.py'), # gh-20743 + os.path.join('stats', '_variation.py'), # gh-22827 os.path.join('stats', 'tests', 'test_axis_nan_policy.py'), # gh-20694 os.path.join('_lib', '_util.py'), # gh-19341 os.path.join('sparse', 'linalg', '_dsolve', 'linsolve.py'), # gh-17924 diff --git a/scipy/spatial/distance.py b/scipy/spatial/distance.py index 8f9cb116b6c0..cd1b3045c570 100644 --- a/scipy/spatial/distance.py +++ b/scipy/spatial/distance.py @@ -2294,7 +2294,8 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): X = _asarray(X) if X.ndim != 2: - raise ValueError(f'A 2-dimensional array must be passed. (Shape was {X.shape}).') + raise ValueError('A 2-dimensional array must be passed. ' + f'(Shape was {X.shape}).') n = X.shape[0] return xpx.lazy_apply(_np_pdist, X, out, @@ -2303,7 +2304,7 @@ def pdist(X, metric='euclidean', *, out=None, **kwargs): kwargs.pop('V', None), kwargs.pop('VI', None), # See src/distance_pybind.cpp::pdist - shape=((n * (n - 1)) // 2, ), dtype=X.dtype, + shape=((n * (n - 1)) // 2, ), dtype=X.dtype, as_numpy=True, metric=metric, **kwargs) @@ -2706,7 +2707,7 @@ def num_obs_dm(d): -------- Find the number of original observations corresponding to a square redundant distance matrix d. - + >>> from scipy.spatial.distance import num_obs_dm >>> d = [[0, 100, 200], [100, 0, 150], [200, 150, 0]] >>> num_obs_dm(d) @@ -2736,7 +2737,7 @@ def num_obs_y(Y): -------- Find the number of original observations corresponding to a condensed distance matrix Y. - + >>> from scipy.spatial.distance import num_obs_y >>> Y = [1, 2, 3.5, 7, 10, 4] >>> num_obs_y(Y) diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index 12e7c45a0dd7..6cd2b5cb6276 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -7,8 +7,9 @@ import math import numpy as np from scipy import special -from ._axis_nan_policy import _axis_nan_policy_factory, _broadcast_arrays -from scipy._lib._array_api import array_namespace, xp_promote +from ._axis_nan_policy import _axis_nan_policy_factory + +from scipy._lib._array_api import array_namespace, xp_promote, is_marray, _share_masks from scipy._lib import array_api_extra as xpx __all__ = ['entropy', 'differential_entropy'] @@ -140,19 +141,24 @@ def entropy(pk: np.typing.ArrayLike, if base is not None and base <= 0: raise ValueError("`base` must be a positive number or `None`.") - xp = array_namespace(pk) if qk is None else array_namespace(pk, qk) + xp = array_namespace(pk, qk) + pk, qk = xp_promote(pk, qk, broadcast=True, xp=xp) - pk = xp.asarray(pk) with np.errstate(invalid='ignore'): - pk = 1.0*pk / xp.sum(pk, axis=axis, keepdims=True) # type: ignore[operator] + if qk is not None: + pk, qk = _share_masks(pk, qk, xp=xp) + qk = qk / xp.sum(qk, axis=axis, keepdims=True) + pk = pk / xp.sum(pk, axis=axis, keepdims=True) + if qk is None: vec = special.entr(pk) else: - qk = xp.asarray(qk) - pk, qk = _broadcast_arrays((pk, qk), axis=None, xp=xp) # don't ignore any axes - sum_kwargs = dict(axis=axis, keepdims=True) - qk = 1.0*qk / xp.sum(qk, **sum_kwargs) # type: ignore[operator, call-overload] - vec = special.rel_entr(pk, qk) + if is_marray(xp): # compensate for mdhaber/marray#97 + vec = special.rel_entr(pk.data, qk.data) # type: ignore[union-attr] + vec = xp.asarray(vec, mask=pk.mask) # type: ignore[union-attr] + else: + vec = special.rel_entr(pk, qk) + S = xp.sum(vec, axis=axis) if base is not None: S /= math.log(base) diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 28c1a97b16cb..3ab28317973b 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -11,19 +11,23 @@ from scipy import optimize, special, interpolate, stats from scipy._lib._bunch import _make_tuple_bunch from scipy._lib._util import _rename_parameter, _contains_nan, _get_nan +import scipy._lib.array_api_extra as xpx from scipy._lib._array_api import ( array_namespace, xp_size, xp_vector_norm, xp_promote, + is_marray, + xp_ravel, + _length_nonmasked, ) from ._ansari_swilk_statistics import gscale, swilk from . import _stats_py, _wilcoxon from ._fit import FitResult from ._stats_py import (_get_pvalue, SignificanceResult, # noqa:F401 - _SimpleNormal, _SimpleChi2, _length_nonmasked) + _SimpleNormal, _SimpleChi2) from .contingency import chi2_contingency from . import distributions from ._distn_infrastructure import rv_generic @@ -2931,15 +2935,20 @@ def bartlett(*samples, axis=0): if k < 2: raise ValueError("Must enter at least two input sample vectors.") - samples = _broadcast_arrays(samples, axis=axis, xp=xp) - samples = [xp.moveaxis(sample, axis, -1) for sample in samples] + if axis is None: + samples = [xp_ravel(sample) for sample in samples] + else: + samples = _broadcast_arrays(samples, axis=axis, xp=xp) + samples = [xp.moveaxis(sample, axis, -1) for sample in samples] - Ni = [xp.asarray(sample.shape[-1], dtype=sample.dtype) for sample in samples] + Ni = [xp.asarray(_length_nonmasked(sample, axis=-1, xp=xp), dtype=sample.dtype) + for sample in samples] Ni = [xp.broadcast_to(N, samples[0].shape[:-1]) for N in Ni] ssq = [xp.var(sample, correction=1, axis=-1) for sample in samples] Ni = [arr[xp.newaxis, ...] for arr in Ni] ssq = [arr[xp.newaxis, ...] for arr in ssq] Ni = xp.concat(Ni, axis=0) + Ni = xpx.at(Ni)[Ni == 0].set(xp.nan) ssq = xp.concat(ssq, axis=0) dtype = Ni.dtype Ntot = xp.sum(Ni, axis=0) @@ -4416,6 +4425,12 @@ def directional_stats(samples, *, axis=0, normalize=True): raise ValueError("samples must at least be two-dimensional. " f"Instead samples has shape: {tuple(samples.shape)}") samples = xp.moveaxis(samples, axis, 0) + + if is_marray(xp): + _xp = array_namespace(samples.mask) + mask = _xp.any(samples.mask, axis=-1, keepdims=True) + samples = xp.asarray(samples.data, mask=mask) + if normalize: vectornorms = xp_vector_norm(samples, axis=-1, keepdims=True, xp=xp) samples = samples/vectornorms diff --git a/scipy/stats/_quantile.py b/scipy/stats/_quantile.py index ce51651ad95a..3f92632f0a48 100644 --- a/scipy/stats/_quantile.py +++ b/scipy/stats/_quantile.py @@ -1,9 +1,9 @@ import numpy as np from scipy.special import betainc -from scipy._lib._array_api import xp_ravel, array_namespace, xp_promote +from scipy._lib._array_api import (xp_ravel, array_namespace, xp_promote, + _length_nonmasked) import scipy._lib.array_api_extra as xpx from scipy.stats._axis_nan_policy import _broadcast_arrays, _contains_nan -from scipy.stats._stats_py import _length_nonmasked def _quantile_iv(x, p, method, axis, nan_policy, keepdims): diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 1822f27b0100..8d077277763a 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -26,7 +26,6 @@ York. 2000. """ -import functools import math import operator import warnings @@ -75,13 +74,14 @@ array_namespace, is_lazy_array, is_numpy, - is_marray, is_cupy, xp_size, xp_vector_norm, xp_promote, xp_capabilities, xp_ravel, + _length_nonmasked, + _share_masks, xp_swapaxes, xp_default_dtype, ) @@ -1244,25 +1244,6 @@ def _var(x, axis=0, ddof=0, mean=None, xp=None): return var -def _length_nonmasked(x, axis, keepdims=False, xp=None): - xp = array_namespace(x) if xp is None else xp - if is_marray(xp): - if np.iterable(axis): - message = '`axis` must be an integer or None for use with `MArray`.' - raise NotImplementedError(message) - return xp.astype(xp.count(x, axis=axis, keepdims=keepdims), x.dtype) - return (xp_size(x) if axis is None else - # compact way to deal with axis tuples or ints - int(np.prod(np.asarray(x.shape)[np.asarray(axis)]))) - - -def _share_masks(*args, xp): - if is_marray(xp): - mask = functools.reduce(operator.or_, (arg.mask for arg in args)) - args = [xp.asarray(arg.data, mask=mask) for arg in args] - return args[0] if len(args) == 1 else args - - @xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 @@ -2600,7 +2581,7 @@ def obrientransform(*samples): return np.array(arrays) -@xp_capabilities() +@xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory( lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, too_small=1 ) @@ -4239,33 +4220,35 @@ def _pearsonr_fisher_ci(r, n, confidence_level, alternative): """ xp = array_namespace(r) - with np.errstate(divide='ignore'): - zr = xp.atanh(r) - ones = xp.ones_like(r) n = xp.asarray(n, dtype=r.dtype) confidence_level = xp.asarray(confidence_level, dtype=r.dtype) - if n > 3: + + with np.errstate(divide='ignore', invalid='ignore'): + zr = xp.atanh(r) se = xp.sqrt(1 / (n - 3)) - if alternative == "two-sided": - h = special.ndtri(0.5 + confidence_level/2) - zlo = zr - h*se - zhi = zr + h*se - rlo = xp.tanh(zlo) - rhi = xp.tanh(zhi) - elif alternative == "less": - h = special.ndtri(confidence_level) - zhi = zr + h*se - rhi = xp.tanh(zhi) - rlo = -ones - else: - # alternative == "greater": - h = special.ndtri(confidence_level) - zlo = zr - h*se - rlo = xp.tanh(zlo) - rhi = ones + + if alternative == "two-sided": + h = special.ndtri(0.5 + confidence_level/2) + zlo = zr - h*se + zhi = zr + h*se + rlo = xp.tanh(zlo) + rhi = xp.tanh(zhi) + elif alternative == "less": + h = special.ndtri(confidence_level) + zhi = zr + h*se + rhi = xp.tanh(zhi) + rlo = -ones else: - rlo, rhi = -ones, ones + # alternative == "greater": + h = special.ndtri(confidence_level) + zlo = zr - h*se + rlo = xp.tanh(zlo) + rhi = ones + + mask = (n <= 3) + rlo = xpx.at(rlo)[mask].set(-1) + rhi = xpx.at(rhi)[mask].set(1) rlo = rlo[()] if rlo.ndim == 0 else rlo rhi = rhi[()] if rhi.ndim == 0 else rhi @@ -4387,7 +4370,8 @@ def confidence_interval(self, confidence_level=0.95, method=None): return ci -@xp_capabilities(cpu_only=True, exceptions=['cupy'], +@xp_capabilities(skip_backends = [('dask.array', 'data-apis/array-api-extra#196')], + cpu_only=True, exceptions=['cupy'], jax_jit=False, allow_dask_compute=True) def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): r""" @@ -4681,13 +4665,15 @@ def pearsonr(x, y, *, alternative='two-sided', method=None, axis=0): message = '`x` and `y` must be broadcastable.' raise ValueError(message) from e - n = x.shape[axis] - if n != y.shape[axis]: + if x.shape[axis] != y.shape[axis]: raise ValueError('`x` and `y` must have the same length along `axis`.') - if n < 2: + if x.shape[axis] < 2: raise ValueError('`x` and `y` must have length at least 2.') + x, y = _share_masks(x, y, xp=xp) + n = xp.asarray(_length_nonmasked(x, axis=axis), dtype=x.dtype) + x = xp.moveaxis(x, axis, -1) y = xp.moveaxis(y, axis, -1) axis = -1 @@ -4780,18 +4766,16 @@ def statistic(x, y, axis): r = xp.clip(r, -1., 1.) r = xpx.at(r, const_xy).set(xp.nan) - # Make sure we return exact 1.0 or -1.0 values for n == 2 case as promised - # in the docs. - if n == 2: - r = xp.round(r) - one = xp.asarray(1, dtype=dtype) - pvalue = xp.where(xp.asarray(xp.isnan(r)), xp.nan*one, one) - else: - # As explained in the docstring, the distribution of `r` under the null - # hypothesis is the beta distribution on (-1, 1) with a = b = n/2 - 1. - ab = xp.asarray(n/2 - 1) - dist = _SimpleBeta(ab, ab, loc=-1, scale=2) - pvalue = _get_pvalue(r, dist, alternative, xp=xp) + # As explained in the docstring, the distribution of `r` under the null + # hypothesis is the beta distribution on (-1, 1) with a = b = n/2 - 1. + ab = xp.asarray(n/2 - 1) + dist = _SimpleBeta(ab, ab, loc=-1, scale=2) + pvalue = _get_pvalue(r, dist, alternative, xp=xp) + + mask = (n == 2) # return exactly 1.0 or -1.0 values for n == 2 case as promised + def special_case(r): return xp.where(xp.isnan(r), xp.nan, xp.ones_like(r)) + r = xpx.apply_where(mask, (r,), xp.round, fill_value=r) + pvalue = xpx.apply_where(mask, (r,), special_case, fill_value=pvalue) r = r[()] if r.ndim == 0 else r pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue diff --git a/scipy/stats/_variation.py b/scipy/stats/_variation.py index 595098c5a3ee..8cbb55936449 100644 --- a/scipy/stats/_variation.py +++ b/scipy/stats/_variation.py @@ -1,7 +1,8 @@ +import warnings import numpy as np -from scipy._lib._util import _get_nan -from scipy._lib._array_api import array_namespace +from scipy._lib._array_api import array_namespace, _length_nonmasked +import scipy._lib.array_api_extra as xpx from ._axis_nan_policy import _axis_nan_policy_factory @@ -96,31 +97,25 @@ def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): """ xp = array_namespace(a) a = xp.asarray(a) + # `nan_policy` and `keepdims` are handled by `_axis_nan_policy` - # `axis=None` is only handled for NumPy backend if axis is None: a = xp.reshape(a, (-1,)) axis = 0 - n = a.shape[axis] - - if a.size == 0 or ddof > n: - # Handle as a special case to avoid spurious warnings. - # The return values, if any, are all nan. - shape = list(a.shape) - shape.pop(axis) - return _get_nan(a, shape=tuple(shape), xp=xp) + n = xp.asarray(_length_nonmasked(a, axis=axis), dtype=a.dtype) - mean_a = xp.mean(a, axis=axis) + with (np.errstate(divide='ignore', invalid='ignore'), warnings.catch_warnings()): + warnings.simplefilter("ignore") + mean_a = xp.mean(a, axis=axis) + std_a = xp.std(a, axis=axis) + correction = (n / (n - ddof))**0.5 # we may need uncorrected std below + result = std_a * correction / mean_a - if ddof == n: - # Another special case. Result is either inf or nan. - std_a = xp.std(a, axis=axis, correction=0) - result = xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), xp.nan) - return result[()] if result.ndim == 0 else result + def special_case(std_a, mean_a): + return xp.where(std_a > 0, xp.copysign(xp.inf, mean_a), xp.nan) - with np.errstate(divide='ignore', invalid='ignore'): - std_a = xp.std(a, axis=axis, correction=ddof) - result = std_a / mean_a + result = xpx.apply_where((ddof == n), (std_a, mean_a), + special_case, fill_value=result) return result[()] if result.ndim == 0 else result diff --git a/scipy/stats/tests/test_marray.py b/scipy/stats/tests/test_marray.py index ffb3121fcf35..901d28dcb3c1 100644 --- a/scipy/stats/tests/test_marray.py +++ b/scipy/stats/tests/test_marray.py @@ -2,8 +2,8 @@ import numpy as np from scipy import stats -from scipy._lib._array_api import xp_assert_close, xp_assert_equal -from scipy.stats._stats_py import _xp_mean, _xp_var, _length_nonmasked +from scipy._lib._array_api import xp_assert_close, xp_assert_equal, _length_nonmasked +from scipy.stats._stats_py import _xp_mean, _xp_var from scipy.stats._axis_nan_policy import _axis_nan_policy_factory @@ -82,6 +82,8 @@ def test_xp_mean(axis, keepdims, xp): (stats.circmean, {}), (stats.circvar, {}), (stats.circstd, {}), + (stats.gstd, {}), + (stats.variation, {}), (_xp_var, {}), (stats.tmean, {'limits': (0.1, 0.9)}), (stats.tvar, {'limits': (0.1, 0.9)}), @@ -275,6 +277,7 @@ def test_ttest_ind_from_stats(xp): assert res.statistic.shape == shape assert res.pvalue.shape == shape + def test_length_nonmasked_marray_iterable_axis_raises(): xp = marray._get_namespace(np) @@ -287,3 +290,65 @@ def test_length_nonmasked_marray_iterable_axis_raises(): with pytest.raises(NotImplementedError, match="`axis` must be an integer or None for use with `MArray`"): _length_nonmasked(marr, axis=(0, 1), xp=xp) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@pytest.mark.filterwarnings("ignore::RuntimeWarning") # mdhaber/marray#120 +def test_directional_stats(xp): + mxp, marrays, narrays = get_arrays(1, shape=(100, 3), xp=xp) + res = stats.directional_stats(*marrays) + narrays[0] = narrays[0][~np.any(np.isnan(narrays[0]), axis=1)] + ref = stats.directional_stats(*narrays) + xp_assert_close(res.mean_direction.data, xp.asarray(ref.mean_direction)) + xp_assert_close(res.mean_resultant_length.data, + xp.asarray(ref.mean_resultant_length)) + assert not xp.any(res.mean_direction.mask) + assert not xp.any(res.mean_resultant_length.mask) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@skip_backend('cupy', reason="special functions won't work") +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_bartlett(axis, xp): + mxp, marrays, narrays = get_arrays(3, xp=xp) + res = stats.bartlett(*marrays, axis=axis) + ref = stats.bartlett(*narrays, nan_policy='omit', axis=axis) + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@skip_backend('torch', reason="array-api-compat#242") +@skip_backend('cupy', reason="special functions won't work") +@pytest.mark.parametrize('f', [stats.pearsonr, stats.pointbiserialr]) +def test_pearsonr(f, xp): + mxp, marrays, narrays = get_arrays(2, shape=(25,), xp=xp) + res = f(*marrays) + + x, y = narrays + mask = np.isnan(x) | np.isnan(y) + ref = f(x[~mask], y[~mask]) + + xp_assert_close(res.statistic.data, xp.asarray(ref.statistic)) + xp_assert_close(res.pvalue.data, xp.asarray(ref.pvalue)) + + if f == stats.pearsonr: + res_ci_low, res_ci_high = res.confidence_interval() + ref_ci_low, ref_ci_high = ref.confidence_interval() + xp_assert_close(res_ci_low.data, xp.asarray(ref_ci_low)) + xp_assert_close(res_ci_high.data, xp.asarray(ref_ci_high)) + +@skip_backend('dask.array', reason='Arrays need `device` attribute: dask/dask#11711') +@skip_backend('jax.numpy', reason="JAX doesn't allow item assignment.") +@pytest.mark.parametrize('qk', [False, True]) +@pytest.mark.parametrize('axis', [0, 1, None]) +def test_entropy(qk, axis, xp): + mxp, marrays, narrays = get_arrays(2 if qk else 1, xp=xp) + res = stats.entropy(*marrays, axis=axis) + ref = stats.entropy(*narrays, nan_policy='omit', axis=axis) + xp_assert_close(res.data, xp.asarray(ref)) diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py index f3348d677ab5..936cf68ace69 100644 --- a/scipy/stats/tests/test_morestats.py +++ b/scipy/stats/tests/test_morestats.py @@ -27,7 +27,7 @@ from scipy.stats._axis_nan_policy import (SmallSampleWarning, too_small_nd_omit, too_small_1d_omit, too_small_1d_not_omit) -from scipy._lib._array_api import is_numpy +from scipy._lib._array_api import is_numpy, is_torch from scipy._lib._array_api_no_0d import ( xp_assert_close, xp_assert_equal, @@ -3230,6 +3230,7 @@ def test_edge_cases(self): assert_array_equal(stats.false_discovery_control([]), []) +@skip_xp_backends("dask.array", reason='data-apis/array-api-extra#196') class TestCommonAxis: # More thorough testing of `axis` in `test_axis_nan_policy`, # but those tests aren't run with array API yet. This class @@ -3242,6 +3243,8 @@ class TestCommonAxis: (stats.kstat, {'n': 2}), (stats.variation, {})]) def test_axis(self, case, xp): + if is_torch(xp) and case[0] == stats.variation: + pytest.xfail(reason="copysign doesn't accept scalar array-api-compat#271") fun, kwargs = case rng = np.random.default_rng(24598245982345) x = xp.asarray(rng.random((6, 7))) diff --git a/scipy/stats/tests/test_variation.py b/scipy/stats/tests/test_variation.py index 5660f733b4f7..222620c8befd 100644 --- a/scipy/stats/tests/test_variation.py +++ b/scipy/stats/tests/test_variation.py @@ -14,6 +14,7 @@ skip_xp_backends = pytest.mark.skip_xp_backends +@skip_xp_backends("dask.array", reason='data-apis/array-api-extra#196') @skip_xp_backends('torch', reason='data-apis/array-api-compat#271') class TestVariation: """ From 84a8e51a2f0349dc5a9e3e22d720557f1fce75c1 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 23 May 2025 09:24:07 +0100 Subject: [PATCH 237/251] DEP: linalg: remove kron --- scipy/linalg/__init__.py | 1 - scipy/linalg/_special_matrices.py | 56 +-------------------- scipy/linalg/special_matrices.py | 2 +- scipy/linalg/tests/test_special_matrices.py | 32 +----------- 4 files changed, 3 insertions(+), 88 deletions(-) diff --git a/scipy/linalg/__init__.py b/scipy/linalg/__init__.py index 9381d7a02c15..8fa8f6de8cc2 100644 --- a/scipy/linalg/__init__.py +++ b/scipy/linalg/__init__.py @@ -46,7 +46,6 @@ lstsq - Solve a linear least-squares problem pinv - Pseudo-inverse (Moore-Penrose) using lstsq pinvh - Pseudo-inverse of hermitian matrix - kron - Kronecker product of two arrays khatri_rao - Khatri-Rao product of two arrays orthogonal_procrustes - Solve an orthogonal Procrustes problem matrix_balance - Balance matrix entries with a similarity transformation diff --git a/scipy/linalg/_special_matrices.py b/scipy/linalg/_special_matrices.py index 5ba669ec66cf..2c7e36d756aa 100644 --- a/scipy/linalg/_special_matrices.py +++ b/scipy/linalg/_special_matrices.py @@ -7,7 +7,7 @@ __all__ = ['toeplitz', 'circulant', 'hankel', - 'hadamard', 'leslie', 'kron', 'block_diag', 'companion', + 'hadamard', 'leslie', 'block_diag', 'companion', 'helmert', 'hilbert', 'invhilbert', 'pascal', 'invpascal', 'dft', 'fiedler', 'fiedler_companion', 'convolution_matrix'] @@ -339,60 +339,6 @@ def leslie(f, s): return a -def kron(a, b): - """ - Kronecker product. - - .. deprecated:: 1.15.0 - `kron` has been deprecated in favour of `numpy.kron` and will be - removed in SciPy 1.17.0. - - The result is the block matrix:: - - a[0,0]*b a[0,1]*b ... a[0,-1]*b - a[1,0]*b a[1,1]*b ... a[1,-1]*b - ... - a[-1,0]*b a[-1,1]*b ... a[-1,-1]*b - - Parameters - ---------- - a : (M, N) ndarray - Input array - b : (P, Q) ndarray - Input array - - Returns - ------- - A : (M*P, N*Q) ndarray - Kronecker product of `a` and `b`. - - Examples - -------- - >>> from numpy import array - >>> from scipy.linalg import kron - >>> kron(array([[1,2],[3,4]]), array([[1,1,1]])) - array([[1, 1, 1, 2, 2, 2], - [3, 3, 3, 4, 4, 4]]) - - """ - msg = ("`kron` has been deprecated in favour of `numpy.kron` in SciPy" - " 1.15.0 and will be removed in SciPy 1.17.0.") - warnings.warn(msg, DeprecationWarning, stacklevel=2) - # accommodate empty arrays - if a.size == 0 or b.size == 0: - m = a.shape[0] * b.shape[0] - n = a.shape[1] * b.shape[1] - return np.empty_like(a, shape=(m, n)) - - if not a.flags['CONTIGUOUS']: - a = np.reshape(a, a.shape) - if not b.flags['CONTIGUOUS']: - b = np.reshape(b, b.shape) - o = np.outer(a, b) - o = o.reshape(a.shape + b.shape) - return np.concatenate(np.concatenate(o, axis=1), axis=1) - - def block_diag(*arrs): """ Create a block diagonal array from provided arrays. diff --git a/scipy/linalg/special_matrices.py b/scipy/linalg/special_matrices.py index a881ce765dfa..4810e8a45067 100644 --- a/scipy/linalg/special_matrices.py +++ b/scipy/linalg/special_matrices.py @@ -6,7 +6,7 @@ __all__ = [ # noqa: F822 'toeplitz', 'circulant', 'hankel', - 'hadamard', 'leslie', 'kron', 'block_diag', 'companion', + 'hadamard', 'leslie', 'block_diag', 'companion', 'helmert', 'hilbert', 'invhilbert', 'pascal', 'invpascal', 'dft', 'fiedler', 'fiedler_companion', 'convolution_matrix' ] diff --git a/scipy/linalg/tests/test_special_matrices.py b/scipy/linalg/tests/test_special_matrices.py index 41a93d3bf62d..813f09915bb6 100644 --- a/scipy/linalg/tests/test_special_matrices.py +++ b/scipy/linalg/tests/test_special_matrices.py @@ -8,7 +8,7 @@ from scipy.fft import fft from scipy.special import comb from scipy.linalg import (toeplitz, hankel, circulant, hadamard, leslie, dft, - companion, kron, block_diag, + companion, block_diag, helmert, hilbert, invhilbert, pascal, invpascal, fiedler, fiedler_companion, eigvals, convolution_matrix) @@ -207,36 +207,6 @@ def test_zerosized_matrix_arg(self): [0, 0, 6, 7, 0, 0]]) -class TestKron: - @pytest.mark.thread_unsafe - def test_dep(self): - with pytest.deprecated_call(match="`kron`"): - kron(np.array([[1, 2],[3, 4]]),np.array([[1, 1, 1]])) - - @pytest.mark.filterwarnings('ignore::DeprecationWarning') - def test_basic(self): - - a = kron(array([[1, 2], [3, 4]]), array([[1, 1, 1]])) - assert_array_equal(a, array([[1, 1, 1, 2, 2, 2], - [3, 3, 3, 4, 4, 4]])) - - m1 = array([[1, 2], [3, 4]]) - m2 = array([[10], [11]]) - a = kron(m1, m2) - expected = array([[10, 20], - [11, 22], - [30, 40], - [33, 44]]) - assert_array_equal(a, expected) - - @pytest.mark.filterwarnings('ignore::DeprecationWarning') - def test_empty(self): - m1 = np.empty((0, 2)) - m2 = np.empty((1, 3)) - a = kron(m1, m2) - assert_allclose(a, np.empty((0, 6))) - - class TestHelmert: def test_orthogonality(self): From 2ccbbbe151ab25956ec6879b0da4d1c87e5a50ee Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 23 May 2025 09:41:21 +0100 Subject: [PATCH 238/251] MAINT: remove some outdated warning skips --- doc/source/conf.py | 1 - scipy/conftest.py | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 22f1b4e0a2c7..201c2def2b52 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -360,7 +360,6 @@ plot_pre_code = """ import warnings for key in ( - 'interp2d` is deprecated', # Deprecation of scipy.interpolate.interp2d '`kurtosistest` p-value may be', # intentionally "bad" example in docstring ): warnings.filterwarnings(action='ignore', message='.*' + key + '.*') diff --git a/scipy/conftest.py b/scipy/conftest.py index 1ed736a2d3c9..f8098c30d4ad 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -493,14 +493,8 @@ def warnings_errors_and_rng(test=None): known_warnings[name] = dict(category=RuntimeWarning, message='divide by zero') - # Deprecated stuff in scipy.signal and elsewhere - deprecated = [ - 'scipy.signal.cwt', 'scipy.signal.morlet', 'scipy.signal.morlet2', - 'scipy.signal.ricker', - 'scipy.integrate.simpson', - 'scipy.interpolate.interp2d', - 'scipy.linalg.kron', - ] + # Deprecated stuff + deprecated = [] for name in deprecated: known_warnings[name] = dict(category=DeprecationWarning) From e8e49c782b82c23fe944766daf66b2caa0fbaba3 Mon Sep 17 00:00:00 2001 From: Lucas Colley Date: Fri, 23 May 2025 10:51:24 +0100 Subject: [PATCH 239/251] MAINT: create `_lib._array_api_override` to solve circular import (#23028) * MAINT: create `_lib._array_api_override` to solve circular import * lint * nit * lint * Update _array_api_override.py Co-authored-by: Guido Imperiale --------- Co-authored-by: Guido Imperiale --- scipy/_lib/_array_api.py | 118 ++---------------------- scipy/_lib/_array_api_compat_vendor.py | 2 +- scipy/_lib/_array_api_override.py | 122 +++++++++++++++++++++++++ scipy/_lib/meson.build | 1 + scipy/_lib/tests/test_array_api.py | 9 +- 5 files changed, 132 insertions(+), 120 deletions(-) create mode 100644 scipy/_lib/_array_api_override.py diff --git a/scipy/_lib/_array_api.py b/scipy/_lib/_array_api.py index fa70519d086f..f69a47f9d6c3 100644 --- a/scipy/_lib/_array_api.py +++ b/scipy/_lib/_array_api.py @@ -10,10 +10,9 @@ import contextlib import dataclasses import functools -import os import textwrap -from collections.abc import Generator, Iterable, Iterator +from collections.abc import Generator from contextlib import contextmanager from contextvars import ContextVar from types import ModuleType @@ -22,7 +21,6 @@ import numpy as np import numpy.typing as npt -from scipy._lib import array_api_compat from scipy._lib.array_api_compat import ( is_array_api_obj, is_lazy_array, @@ -36,13 +34,17 @@ is_dask_namespace as is_dask, is_array_api_strict_namespace as is_array_api_strict ) -from scipy._lib._sparse import issparse +from scipy._lib.array_api_extra.testing import lazy_xp_function +from scipy._lib._array_api_override import ( + array_namespace, SCIPY_ARRAY_API, SCIPY_DEVICE +) from scipy._lib._docscrape import FunctionDoc __all__ = [ '_asarray', 'array_namespace', 'assert_almost_equal', 'assert_array_almost_equal', 'default_xp', 'eager_warns', 'is_lazy_array', 'is_marray', 'is_array_api_strict', 'is_complex', 'is_cupy', 'is_jax', 'is_numpy', 'is_torch', + 'np_compat', 'SCIPY_ARRAY_API', 'SCIPY_DEVICE', 'scipy_namespace_for', 'xp_assert_close', 'xp_assert_equal', 'xp_assert_less', 'xp_copy', 'xp_device', 'xp_ravel', 'xp_size', @@ -51,123 +53,16 @@ ] -# To enable array API and strict array-like input validation -SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False) -# To control the default device - for use in the test suite only -SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu") - -_GLOBAL_CONFIG = { - "SCIPY_ARRAY_API": SCIPY_ARRAY_API, - "SCIPY_DEVICE": SCIPY_DEVICE, -} - - Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589) ArrayLike: TypeAlias = Array | npt.ArrayLike -def _compliance_scipy(arrays: Iterable[ArrayLike]) -> Iterator[Array]: - """Raise exceptions on known-bad subclasses. Discard 0-dimensional ArrayLikes - and convert 1+-dimensional ArrayLikes to numpy. - - The following subclasses are not supported and raise and error: - - `numpy.ma.MaskedArray` - - `numpy.matrix` - - NumPy arrays which do not have a boolean or numerical dtype - - Any array-like which is neither array API compatible nor coercible by NumPy - - Any array-like which is coerced by NumPy to an unsupported dtype - """ - for array in arrays: - if array is None: - continue - - # this comes from `_util._asarray_validated` - if issparse(array): - msg = ('Sparse arrays/matrices are not supported by this function. ' - 'Perhaps one of the `scipy.sparse.linalg` functions ' - 'would work instead.') - raise ValueError(msg) - - if isinstance(array, np.ma.MaskedArray): - raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.") - - if isinstance(array, np.matrix): - raise TypeError("Inputs of type `numpy.matrix` are not supported.") - - if isinstance(array, np.ndarray | np.generic): - dtype = array.dtype - if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): - raise TypeError(f"An argument has dtype `{dtype!r}`; " - f"only boolean and numerical dtypes are supported.") - - if is_array_api_obj(array): - yield array - else: - try: - array = np.asanyarray(array) - except TypeError: - raise TypeError("An argument is neither array API compatible nor " - "coercible by NumPy.") - dtype = array.dtype - if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): - message = ( - f"An argument was coerced to an unsupported dtype `{dtype!r}`; " - f"only boolean and numerical dtypes are supported." - ) - raise TypeError(message) - # Ignore 0-dimensional arrays, coherently with array-api-compat. - # Raise if there are 1+-dimensional array-likes mixed with non-numpy - # Array API objects. - if array.ndim: - yield array - - def _check_finite(array: Array, xp: ModuleType) -> None: """Check for NaNs or Infs.""" if not xp.all(xp.isfinite(array)): msg = "array must not contain infs or NaNs" raise ValueError(msg) - -def array_namespace(*arrays: Array) -> ModuleType: - """Get the array API compatible namespace for the arrays xs. - - Parameters - ---------- - *arrays : sequence of array_like - Arrays used to infer the common namespace. - - Returns - ------- - namespace : module - Common namespace. - - Notes - ----- - Thin wrapper around `array_api_compat.array_namespace`. - - 1. Check for the global switch: SCIPY_ARRAY_API. This can also be accessed - dynamically through ``_GLOBAL_CONFIG['SCIPY_ARRAY_API']``. - 2. `_compliance_scipy` raise exceptions on known-bad subclasses. See - its definition for more details. - - When the global switch is False, it defaults to the `numpy` namespace. - In that case, there is no compliance check. This is a convenience to - ease the adoption. Otherwise, arrays must comply with the new rules. - """ - if not _GLOBAL_CONFIG["SCIPY_ARRAY_API"]: - # here we could wrap the namespace if needed - return np_compat - - api_arrays = list(_compliance_scipy(arrays)) - # In case of a mix of array API compliant arrays and scalars, return - # the array API namespace. If there are only ArrayLikes (e.g. lists), - # return NumPy (wrapped by array-api-compat). - if api_arrays: - return array_api_compat.array_namespace(*api_arrays) - return np_compat - - def _asarray( array: ArrayLike, dtype: Any = None, @@ -849,7 +744,6 @@ def _make_xp_pytest_marks(*funcs, capabilities_table=None): capabilities_table = (xp_capabilities_table if capabilities_table is None else capabilities_table) import pytest - from scipy._lib.array_api_extra.testing import lazy_xp_function marks = [] for func in funcs: diff --git a/scipy/_lib/_array_api_compat_vendor.py b/scipy/_lib/_array_api_compat_vendor.py index 85c720f57a90..64d844ff4641 100644 --- a/scipy/_lib/_array_api_compat_vendor.py +++ b/scipy/_lib/_array_api_compat_vendor.py @@ -3,7 +3,7 @@ # to override functions of array_api_compat. from .array_api_compat import * # noqa: F403 -from ._array_api import array_namespace as scipy_array_namespace +from ._array_api_override import array_namespace as scipy_array_namespace # overrides array_api_compat.array_namespace inside array-api-extra array_namespace = scipy_array_namespace # type: ignore[assignment] diff --git a/scipy/_lib/_array_api_override.py b/scipy/_lib/_array_api_override.py new file mode 100644 index 000000000000..7eb204099e73 --- /dev/null +++ b/scipy/_lib/_array_api_override.py @@ -0,0 +1,122 @@ +""" +Override functions from array_api_compat, for use by array-api-extra +and internally. + +See also _array_api_compat_vendor.py +""" +import os + +from collections.abc import Iterable, Iterator +from types import ModuleType +from typing import Any, TypeAlias + +import numpy as np +import numpy.typing as npt + +from scipy._lib import array_api_compat +import scipy._lib.array_api_compat.numpy as np_compat +from scipy._lib.array_api_compat import is_array_api_obj +from scipy._lib._sparse import issparse + + +Array: TypeAlias = Any # To be changed to a Protocol later (see array-api#589) +ArrayLike: TypeAlias = Array | npt.ArrayLike + +# To enable array API and strict array-like input validation +SCIPY_ARRAY_API: str | bool = os.environ.get("SCIPY_ARRAY_API", False) +# To control the default device - for use in the test suite only +SCIPY_DEVICE = os.environ.get("SCIPY_DEVICE", "cpu") + + +def _compliance_scipy(arrays: Iterable[ArrayLike]) -> Iterator[Array]: + """Raise exceptions on known-bad subclasses. Discard 0-dimensional ArrayLikes + and convert 1+-dimensional ArrayLikes to numpy. + + The following subclasses are not supported and raise and error: + - `numpy.ma.MaskedArray` + - `numpy.matrix` + - NumPy arrays which do not have a boolean or numerical dtype + - Any array-like which is neither array API compatible nor coercible by NumPy + - Any array-like which is coerced by NumPy to an unsupported dtype + """ + for array in arrays: + if array is None: + continue + + # this comes from `_util._asarray_validated` + if issparse(array): + msg = ('Sparse arrays/matrices are not supported by this function. ' + 'Perhaps one of the `scipy.sparse.linalg` functions ' + 'would work instead.') + raise ValueError(msg) + + if isinstance(array, np.ma.MaskedArray): + raise TypeError("Inputs of type `numpy.ma.MaskedArray` are not supported.") + + if isinstance(array, np.matrix): + raise TypeError("Inputs of type `numpy.matrix` are not supported.") + + if isinstance(array, np.ndarray | np.generic): + dtype = array.dtype + if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): + raise TypeError(f"An argument has dtype `{dtype!r}`; " + f"only boolean and numerical dtypes are supported.") + + if is_array_api_obj(array): + yield array + else: + try: + array = np.asanyarray(array) + except TypeError: + raise TypeError("An argument is neither array API compatible nor " + "coercible by NumPy.") + dtype = array.dtype + if not (np.issubdtype(dtype, np.number) or np.issubdtype(dtype, np.bool_)): + message = ( + f"An argument was coerced to an unsupported dtype `{dtype!r}`; " + f"only boolean and numerical dtypes are supported." + ) + raise TypeError(message) + # Ignore 0-dimensional arrays, coherently with array-api-compat. + # Raise if there are 1+-dimensional array-likes mixed with non-numpy + # Array API objects. + if array.ndim: + yield array + + +def array_namespace(*arrays: Array) -> ModuleType: + """Get the array API compatible namespace for the arrays xs. + + Parameters + ---------- + *arrays : sequence of array_like + Arrays used to infer the common namespace. + + Returns + ------- + namespace : module + Common namespace. + + Notes + ----- + Thin wrapper around `array_api_compat.array_namespace`. + + 1. Check for the global switch: SCIPY_ARRAY_API. + 2. `_compliance_scipy` raise exceptions on known-bad subclasses. See + its definition for more details. + + When the global switch is False, it defaults to the `numpy` namespace. + In that case, there is no compliance check. This is a convenience to + ease the adoption. Otherwise, arrays must comply with the new rules. + """ + if not SCIPY_ARRAY_API: + # here we could wrap the namespace if needed + return np_compat + + api_arrays = list(_compliance_scipy(arrays)) + # In case of a mix of array API compliant arrays and scalars, return + # the array API namespace. If there are only ArrayLikes (e.g. lists), + # return NumPy (wrapped by array-api-compat). + if api_arrays: + return array_api_compat.array_namespace(*api_arrays) + return np_compat diff --git a/scipy/_lib/meson.build b/scipy/_lib/meson.build index fcfb2a8f98e5..56ef7393f91c 100644 --- a/scipy/_lib/meson.build +++ b/scipy/_lib/meson.build @@ -112,6 +112,7 @@ python_sources = [ '_array_api.py', '_array_api_compat_vendor.py', '_array_api_no_0d.py', + '_array_api_override.py', '_bunch.py', '_ccallback.py', '_disjoint_set.py', diff --git a/scipy/_lib/tests/test_array_api.py b/scipy/_lib/tests/test_array_api.py index f7f323adaa47..1a633a968ef5 100644 --- a/scipy/_lib/tests/test_array_api.py +++ b/scipy/_lib/tests/test_array_api.py @@ -4,7 +4,7 @@ import pytest from scipy._lib._array_api import ( - _GLOBAL_CONFIG, array_namespace, _asarray, xp_copy, xp_assert_equal, is_numpy, + SCIPY_ARRAY_API, array_namespace, _asarray, xp_copy, xp_assert_equal, is_numpy, np_compat, xp_default_dtype, xp_result_type, is_torch ) from scipy._lib import array_api_extra as xpx @@ -16,7 +16,7 @@ lazy_xp_function(xp_copy) -@pytest.mark.skipif(not _GLOBAL_CONFIG["SCIPY_ARRAY_API"], +@pytest.mark.skipif(not SCIPY_ARRAY_API, reason="Array API test; set environment variable SCIPY_ARRAY_API=1 to run it") class TestArrayAPI: @@ -25,11 +25,6 @@ def test_array_namespace(self): xp = array_namespace(x, y) assert 'array_api_compat.numpy' in xp.__name__ - _GLOBAL_CONFIG["SCIPY_ARRAY_API"] = False - xp = array_namespace(x, y) - assert 'array_api_compat.numpy' in xp.__name__ - _GLOBAL_CONFIG["SCIPY_ARRAY_API"] = True - def test_asarray(self, xp): x, y = _asarray([0, 1, 2], xp=xp), _asarray(np.arange(3), xp=xp) ref = xp.asarray([0, 1, 2]) From 93bfa8849c9be5d986b33397bf8b9ba6780d63b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Fri, 23 May 2025 15:43:37 +0530 Subject: [PATCH 240/251] Merge pull request #22041 from czgdp1807/fix_refcount BUG: Perform ``Py_DECREF`` on ``obj`` to avoid memory leak --- scipy/sparse/linalg/_dsolve/_superlu_utils.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scipy/sparse/linalg/_dsolve/_superlu_utils.c b/scipy/sparse/linalg/_dsolve/_superlu_utils.c index 63951b7f33f1..ea7e1bc70182 100644 --- a/scipy/sparse/linalg/_dsolve/_superlu_utils.c +++ b/scipy/sparse/linalg/_dsolve/_superlu_utils.c @@ -42,6 +42,11 @@ static SuperLUGlobalObject *get_tls_global(void) PyDict_SetItemString(thread_dict, key, (PyObject *)obj); + /* + Py_DECREF is added because get_tls_global returns + borrowed reference. This avoids memory leak of obj. + */ + Py_DECREF(obj); return obj; } From 75b1c8981050aa5efeb23ede730acf9722610ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 23 May 2025 13:29:20 +0200 Subject: [PATCH 241/251] TST: update all `result_to_tuple=` callables to accept two arguments Update all the `result_to_tuple=` callables to accept two arguments, and remove the compatibility hack for one-argument callables, fixing the test suite on PyPy3.11. The hack did not account for `inspect.signature(tuple)` including `*args, **keywords` on PyPy which led to it being misdetected as not requiring the compatibility wrapping. Rather than add further complexity to the hack, let's just update all the functions to accept two arguments. In addition to running the test suite with the changes, I have also tried replacing the original hack with an explicitly raised exception to make sure that no `result_to_tuple=` are passed a single-parameter function. Fixes #23035. --- scipy/stats/_axis_nan_policy.py | 10 +------- scipy/stats/_correlation.py | 2 +- scipy/stats/_entropy.py | 4 ++-- scipy/stats/_hypotests.py | 2 +- scipy/stats/_morestats.py | 14 +++++------ scipy/stats/_stats_mstats_common.py | 6 +++-- scipy/stats/_stats_py.py | 36 ++++++++++++++--------------- scipy/stats/_variation.py | 2 +- 8 files changed, 35 insertions(+), 41 deletions(-) diff --git a/scipy/stats/_axis_nan_policy.py b/scipy/stats/_axis_nan_policy.py index 0f1f0559633d..dd780d0f6270 100644 --- a/scipy/stats/_axis_nan_policy.py +++ b/scipy/stats/_axis_nan_policy.py @@ -403,17 +403,9 @@ def _axis_nan_policy_factory(tuple_to_result, default_axis=0, override.update(temp) if result_to_tuple is None: - def result_to_tuple(res): + def result_to_tuple(res, _): return res - # The only `result_to_tuple` that needs the second argument (number of - # outputs) is the one for `moment`, and this was realized very late. - # Rather than changing all `result_to_tuple` definitions, we wrap them - # here to accept a second argument if they don't already. - if len(inspect.signature(result_to_tuple).parameters) == 1: - def result_to_tuple(res, _, f=result_to_tuple): - return f(res) - if not callable(too_small): def is_too_small(samples, *ts_args, axis=-1, **ts_kwargs): for sample in samples: diff --git a/scipy/stats/_correlation.py b/scipy/stats/_correlation.py index 2e47cd618044..85aaae90b550 100644 --- a/scipy/stats/_correlation.py +++ b/scipy/stats/_correlation.py @@ -81,7 +81,7 @@ def _chatterjeexi_iv(y_continuous, method): return y_continuous, method -def _unpack(res): +def _unpack(res, _): return res.statistic, res.pvalue diff --git a/scipy/stats/_entropy.py b/scipy/stats/_entropy.py index 6cd2b5cb6276..aa32dc762e09 100644 --- a/scipy/stats/_entropy.py +++ b/scipy/stats/_entropy.py @@ -21,7 +21,7 @@ 2 if ("qk" in kwgs and kwgs["qk"] is not None) else 1 ), - n_outputs=1, result_to_tuple=lambda x: (x,), paired=True, + n_outputs=1, result_to_tuple=lambda x, _: (x,), paired=True, too_small=-1 # entropy doesn't have too small inputs ) def entropy(pk: np.typing.ArrayLike, @@ -176,7 +176,7 @@ def _differential_entropy_is_too_small(samples, kwargs, axis=-1): @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,), + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,), too_small=_differential_entropy_is_too_small ) def differential_entropy( diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 4535ea7fe9f8..10b5bafb6e6a 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -482,7 +482,7 @@ def _cdf_cvm(x, n=None): return y -def _cvm_result_to_tuple(res): +def _cvm_result_to_tuple(res, _): return res.statistic, res.pvalue diff --git a/scipy/stats/_morestats.py b/scipy/stats/_morestats.py index 3ab28317973b..552469afe83d 100644 --- a/scipy/stats/_morestats.py +++ b/scipy/stats/_morestats.py @@ -226,7 +226,7 @@ def mvsdist(data): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None ) def kstat(data, n=2, *, axis=None): r""" @@ -331,7 +331,7 @@ def kstat(data, n=2, *, axis=None): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, default_axis=None + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None ) def kstatvar(data, n=2, *, axis=None): r"""Return an unbiased estimator of the variance of the k-statistic. @@ -988,7 +988,7 @@ def boxcox_llf(lmb, data, *, axis=0, keepdims=False, nan_policy='propagate'): @_axis_nan_policy_factory(lambda x: x, n_outputs=1, default_axis=0, - result_to_tuple=lambda x: (x,)) + result_to_tuple=lambda x, _: (x,)) def _boxcox_llf(data, axis=0, *, lmb): xp = array_namespace(data) lmb, data = xp_promote(lmb, data, force_floating=True, xp=xp) @@ -3505,7 +3505,7 @@ def mood(x, y, axis=0, alternative="two-sided"): WilcoxonResult = _make_tuple_bunch('WilcoxonResult', ['statistic', 'pvalue']) -def wilcoxon_result_unpacker(res): +def wilcoxon_result_unpacker(res, _): if hasattr(res, 'zstatistic'): return res.statistic, res.pvalue, res.zstatistic else: @@ -4002,7 +4002,7 @@ def _circfuncs_common(samples, period, xp=None): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): r"""Compute the circular mean of a sample of angle observations. @@ -4095,7 +4095,7 @@ def circmean(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): r"""Compute the circular variance of a sample of angle observations. @@ -4189,7 +4189,7 @@ def circvar(samples, high=2*pi, low=0, axis=None, nan_policy='propagate'): @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def circstd(samples, high=2*pi, low=0, axis=None, nan_policy='propagate', *, normalize=False): diff --git a/scipy/stats/_stats_mstats_common.py b/scipy/stats/_stats_mstats_common.py index 9a621016e157..3a0ec400d36c 100644 --- a/scipy/stats/_stats_mstats_common.py +++ b/scipy/stats/_stats_mstats_common.py @@ -21,7 +21,8 @@ def _n_samples_optional_x(kwargs): @_axis_nan_policy_factory(TheilslopesResult, default_axis=None, n_outputs=4, n_samples=_n_samples_optional_x, - result_to_tuple=tuple, paired=True, too_small=1) + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) def theilslopes(y, x=None, alpha=0.95, method='separate'): r""" Computes the Theil-Sen estimator for a set of points (x, y). @@ -204,7 +205,8 @@ def _find_repeats(arr): @_axis_nan_policy_factory(SiegelslopesResult, default_axis=None, n_outputs=2, n_samples=_n_samples_optional_x, - result_to_tuple=tuple, paired=True, too_small=1) + result_to_tuple=lambda x, _: tuple(x), paired=True, + too_small=1) def siegelslopes(y, x=None, method="hierarchical"): r""" Computes the Siegel estimator for a set of points (x, y). diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py index 8d077277763a..2b2afe56dd24 100644 --- a/scipy/stats/_stats_py.py +++ b/scipy/stats/_stats_py.py @@ -160,7 +160,7 @@ def _pack_CorrelationResult(statistic, pvalue, correlation): return res -def _unpack_CorrelationResult(res): +def _unpack_CorrelationResult(res, _): return res.statistic, res.pvalue, res.correlation @@ -168,7 +168,7 @@ def _unpack_CorrelationResult(res): @xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def gmean(a, axis=0, dtype=None, weights=None): r"""Compute the weighted geometric mean along the specified axis. @@ -252,7 +252,7 @@ def gmean(a, axis=0, dtype=None, weights=None): @xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def hmean(a, axis=0, dtype=None, *, weights=None): r"""Calculate the weighted harmonic mean along the specified axis. @@ -353,7 +353,7 @@ def hmean(a, axis=0, dtype=None, *, weights=None): @xp_capabilities(jax_jit=False, allow_dask_compute=1) @_axis_nan_policy_factory( lambda x: x, n_samples=1, n_outputs=1, too_small=0, paired=True, - result_to_tuple=lambda x: (x,), kwd_samples=['weights']) + result_to_tuple=lambda x, _: (x,), kwd_samples=['weights']) def pmean(a, p, *, axis=0, dtype=None, weights=None): r"""Calculate the weighted power mean along the specified axis. @@ -634,7 +634,7 @@ def _put_val_to_limits(a, limits, inclusive, val=np.nan, xp=None): @xp_capabilities() @_axis_nan_policy_factory( lambda x: x, n_outputs=1, default_axis=None, - result_to_tuple=lambda x: (x,) + result_to_tuple=lambda x, _: (x,) ) def tmean(a, limits=None, inclusive=(True, True), axis=None): """Compute the trimmed mean. @@ -689,7 +689,7 @@ def tmean(a, limits=None, inclusive=(True, True), axis=None): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed variance. @@ -749,7 +749,7 @@ def tvar(a, limits=None, inclusive=(True, True), axis=0, ddof=1): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """Compute the trimmed minimum. @@ -813,7 +813,7 @@ def tmin(a, lowerlimit=None, axis=0, inclusive=True, nan_policy='propagate'): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): """Compute the trimmed maximum. @@ -876,7 +876,7 @@ def tmax(a, upperlimit=None, axis=0, inclusive=True, nan_policy='propagate'): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed sample standard deviation. @@ -929,7 +929,7 @@ def tstd(a, limits=None, inclusive=(True, True), axis=0, ddof=1): @xp_capabilities() @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def tsem(a, limits=None, inclusive=(True, True), axis=0, ddof=1): """Compute the trimmed standard error of the mean. @@ -1246,7 +1246,7 @@ def _var(x, axis=0, ddof=0, mean=None, xp=None): @xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 ) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -1347,7 +1347,7 @@ def skew(a, axis=0, bias=True, nan_policy='propagate'): @xp_capabilities(jax_jit=False, allow_dask_compute=2) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1 ) # nan_policy handled by `_axis_nan_policy`, but needs to be left # in signature to preserve use as a positional argument @@ -2583,7 +2583,7 @@ def obrientransform(*samples): @xp_capabilities(jax_jit=False, allow_dask_compute=True) @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, too_small=1 + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, too_small=1 ) def sem(a, axis=0, ddof=1, nan_policy='propagate'): """Compute standard error of the mean. @@ -3051,7 +3051,7 @@ def gstd(a, axis=0, ddof=1, *, keepdims=False, nan_policy='propagate'): @_axis_nan_policy_factory( - lambda x: x, result_to_tuple=lambda x: (x,), n_outputs=1, + lambda x: x, result_to_tuple=lambda x, _: (x,), n_outputs=1, default_axis=None, override={'nan_propagation': False} ) def iqr(x, axis=None, rng=(25, 75), scale=1.0, nan_policy='propagate', @@ -4049,7 +4049,7 @@ class AlexanderGovernResult: @_axis_nan_policy_factory( AlexanderGovernResult, n_samples=None, - result_to_tuple=lambda x: (x.statistic, x.pvalue), + result_to_tuple=lambda x, _: (x.statistic, x.pvalue), too_small=1 ) def alexandergovern(*samples, nan_policy='propagate', axis=0): @@ -6011,7 +6011,7 @@ def pack_TtestResult(statistic, pvalue, df, alternative, standard_error, standard_error=standard_error, estimate=estimate) -def unpack_TtestResult(res): +def unpack_TtestResult(res, _): return (res.statistic, res.pvalue, res.df, res._alternative, res._standard_error, res._estimate) @@ -7617,7 +7617,7 @@ def _tuple_to_KstestResult(statistic, pvalue, statistic_sign=statistic_sign) -def _KstestResult_to_tuple(res): +def _KstestResult_to_tuple(res, _): return *res, res.statistic_location, res.statistic_sign @@ -10590,7 +10590,7 @@ def _pack_LinregressResult(slope, intercept, rvalue, pvalue, stderr, intercept_s intercept_stderr=intercept_stderr) -def _unpack_LinregressResult(res): +def _unpack_LinregressResult(res, _): return tuple(res) + (res.intercept_stderr,) diff --git a/scipy/stats/_variation.py b/scipy/stats/_variation.py index 8cbb55936449..880c6f6f1b5a 100644 --- a/scipy/stats/_variation.py +++ b/scipy/stats/_variation.py @@ -8,7 +8,7 @@ @_axis_nan_policy_factory( - lambda x: x, n_outputs=1, result_to_tuple=lambda x: (x,) + lambda x: x, n_outputs=1, result_to_tuple=lambda x, _: (x,) ) def variation(a, axis=0, nan_policy='propagate', ddof=0, *, keepdims=False): """ From 29e960d648814b937eea66047ad58c42f3495d01 Mon Sep 17 00:00:00 2001 From: Jake Bowhay Date: Fri, 23 May 2025 14:19:05 +0100 Subject: [PATCH 242/251] DEP: interpolate.interpnd: remove incidental imports from private but present module --- scipy/_lib/tests/test_public_api.py | 1 + scipy/interpolate/interpnd.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/scipy/_lib/tests/test_public_api.py b/scipy/_lib/tests/test_public_api.py index f120b74a1830..b78e9d9d25db 100644 --- a/scipy/_lib/tests/test_public_api.py +++ b/scipy/_lib/tests/test_public_api.py @@ -360,6 +360,7 @@ def check_importable(module_name): ('scipy.interpolate.dfitpack', None), ('scipy.interpolate.fitpack', None), ('scipy.interpolate.fitpack2', None), + ('scipy.interpolate.interpnd', None), ('scipy.interpolate.interpolate', None), ('scipy.interpolate.ndgriddata', None), ('scipy.interpolate.polyint', None), diff --git a/scipy/interpolate/interpnd.py b/scipy/interpolate/interpnd.py index 2f4265377d20..36bfa6b9f566 100644 --- a/scipy/interpolate/interpnd.py +++ b/scipy/interpolate/interpnd.py @@ -7,10 +7,7 @@ __all__ = [ # noqa: F822 'CloughTocher2DInterpolator', - 'GradientEstimationWarning', 'LinearNDInterpolator', - 'NDInterpolatorBase', - 'estimate_gradients_2d_global', ] From d2fa9cb869049f03cd9267e01aaec7a06a00e114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Sat, 24 May 2025 14:46:54 +0530 Subject: [PATCH 243/251] DEV: add `spin notes` and `spin authors` commands (#23050) --- .spin/cmds.py | 37 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 4 ++++ 2 files changed, 41 insertions(+) diff --git a/.spin/cmds.py b/.spin/cmds.py index 18d9a7930520..f2a2be7588ab 100644 --- a/.spin/cmds.py +++ b/.spin/cmds.py @@ -572,6 +572,43 @@ def smoke_tutorials(ctx, pytest_args, tests, verbose, build_dir, *args, **kwargs click.secho(cmd_str, bold=True, fg="bright_blue") util.run(cmd) +@click.command() +@click.argument('version_args', nargs=2) +@click.pass_context +def notes(ctx_obj, version_args): + """Release notes and log generation. + + Example: + + spin notes v1.7.0 v1.8.0 + """ + if version_args: + sys.argv = version_args + log_start = sys.argv[0] + log_end = sys.argv[1] + cmd = ["python", "tools/write_release_and_log.py", f"{log_start}", f"{log_end}"] + click.secho(' '.join(cmd), bold=True, fg="bright_blue") + util.run(cmd) + +@click.command() +@click.argument('revision_args', nargs=2) +@click.pass_context +def authors(ctx_obj, revision_args): + """Generate list of authors who contributed within revision + interval. + + Example: + + spin authors v1.7.0 v1.8.0 + """ + if revision_args: + sys.argv = revision_args + start_revision = sys.argv[0] + end_revision = sys.argv[1] + cmd = ["python", "tools/authors.py", f"{start_revision}..{end_revision}"] + click.secho(' '.join(cmd), bold=True, fg="bright_blue") + util.run(cmd) + @click.command() @click.option( '--fix', default=False, is_flag=True, diff --git a/pyproject.toml b/pyproject.toml index fcf035f07f86..b94c25ca9202 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -192,4 +192,8 @@ PKG_CONFIG_PATH = "{project}" ".spin/cmds.py:refguide_check", ".spin/cmds.py:smoke_tutorials" ] +"Release" = [ + ".spin/cmds.py:notes", + ".spin/cmds.py:authors" +] "Metrics" = [".spin/cmds.py:bench"] From bb51aae0179eb73d0b8c0e56ceabfc0260737f01 Mon Sep 17 00:00:00 2001 From: "H. Vetinari" Date: Sun, 25 May 2025 13:48:31 +1100 Subject: [PATCH 244/251] MAINT: remove nan-handling from _factorialx_array_exact --- scipy/special/_basic.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/scipy/special/_basic.py b/scipy/special/_basic.py index e349a009b2d2..713fd8374b22 100644 --- a/scipy/special/_basic.py +++ b/scipy/special/_basic.py @@ -2859,15 +2859,9 @@ def _factorialx_array_exact(n, k=1): k > 1 corresponds to the multifactorial. """ un = np.unique(n) - # numpy changed nan-sorting behaviour with 1.21, see numpy/numpy#18070; - # to unify the behaviour, we remove the nan's here; the respective - # values will be set separately at the end - un = un[~np.isnan(un)] # Convert to object array if np.int64 can't handle size - if np.isnan(n).any(): - dt = float - elif k in _FACTORIALK_LIMITS_64BITS.keys(): + if k in _FACTORIALK_LIMITS_64BITS.keys(): if un[-1] > _FACTORIALK_LIMITS_64BITS[k]: # e.g. k=1: 21! > np.iinfo(np.int64).max dt = object @@ -2908,9 +2902,6 @@ def _factorialx_array_exact(n, k=1): val *= _range_prod(int(prev + 1), int(current), k=k) out[n == current] = val - if np.isnan(n).any(): - out = out.astype(np.float64) - out[np.isnan(n)] = np.nan return out From 2c2668aa62c58ce8c4ee28d8a97c45ae98edf8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A8=97=E0=A8=97=E0=A8=A8=E0=A8=A6=E0=A9=80=E0=A8=AA=20?= =?UTF-8?q?=E0=A8=B8=E0=A8=BF=E0=A9=B0=E0=A8=98=20=28Gagandeep=20Singh=29?= Date: Sun, 25 May 2025 14:50:39 +0530 Subject: [PATCH 245/251] DOC/DEV: Updated developer docs for `spin` (#23041) --- benchmarks/README.rst | 10 ++--- benchmarks/benchmarks/optimize.py | 2 +- doc/source/building/blas_lapack.rst | 11 +++-- doc/source/building/compilers_and_options.rst | 20 ++++----- doc/source/building/distutils_equivalents.rst | 2 +- doc/source/building/index.rst | 32 +++++++------- doc/source/building/understanding_meson.rst | 3 +- doc/source/dev/api-dev/array_api.rst | 12 ++--- doc/source/dev/contributor/benchmarking.rst | 8 ++-- doc/source/dev/contributor/compiled_code.rst | 6 +-- .../dev/contributor/contributor_toc.rst | 6 +-- doc/source/dev/contributor/cython.rst | 2 +- .../contributor/debugging_linalg_issues.rst | 34 +++++++------- .../dev/contributor/development_workflow.rst | 4 +- doc/source/dev/contributor/devpy_test.rst | 44 +++++++++---------- doc/source/dev/contributor/pep8.rst | 2 +- .../contributor/rendering_documentation.rst | 4 +- doc/source/dev/contributor/reviewing_prs.rst | 4 +- doc/source/dev/core-dev/releasing.rst.inc | 4 +- doc/source/dev/hacking.rst | 2 +- scipy/conftest.py | 2 +- tools/check_installation.py | 2 +- 22 files changed, 106 insertions(+), 110 deletions(-) diff --git a/benchmarks/README.rst b/benchmarks/README.rst index 9f844e47cad2..b1b817178445 100644 --- a/benchmarks/README.rst +++ b/benchmarks/README.rst @@ -12,21 +12,21 @@ Usage Airspeed Velocity manages building and Python environments by itself, unless told otherwise. Some of the benchmarking features in -``dev.py`` also tell ASV to use the SciPy compiled by -``dev.py``. To run the benchmarks, you do not need to install a +``spin`` also tell ASV to use the SciPy compiled by +``spin``. To run the benchmarks, you do not need to install a development version of SciPy to your current Python environment. Run a benchmark against currently checked-out SciPy version (don't record the result):: - python dev.py bench --submodule sparse.Arithmetic + spin bench --submodule sparse.Arithmetic Compare change in benchmark results with another branch:: - python dev.py bench --compare main --submodule sparse.Arithmetic + spin bench --compare main --submodule sparse.Arithmetic Run ASV commands directly (note, this will not set env vars for ``ccache`` -and disabling BLAS/LAPACK multi-threading, as ``dev.py`` does):: +and disabling BLAS/LAPACK multi-threading, as ``spin`` does):: cd benchmarks asv run --skip-existing-commits --steps 10 ALL diff --git a/benchmarks/benchmarks/optimize.py b/benchmarks/benchmarks/optimize.py index e48c845f1f76..0607df8b53e5 100644 --- a/benchmarks/benchmarks/optimize.py +++ b/benchmarks/benchmarks/optimize.py @@ -479,7 +479,7 @@ def track_all(self, problem_name, result_type): # `export SCIPY_GLOBAL_BENCH=AMGM,Adjiman,...` to run specific tests # `export SCIPY_GLOBAL_BENCH_NUMTRIALS=10` to specify n_iterations, default 100 # -# then run `python dev.py bench -S optimize.BenchGlobal` +# then run `spin bench -s optimize.BenchGlobal` # Note that it can take several hours to run; intermediate output # can be found under benchmarks/global-bench-results.json diff --git a/doc/source/building/blas_lapack.rst b/doc/source/building/blas_lapack.rst index 284c3672bb1e..0a7b5b5d1ad5 100644 --- a/doc/source/building/blas_lapack.rst +++ b/doc/source/building/blas_lapack.rst @@ -16,7 +16,7 @@ BLAS/LAPACK on Linux distros, and can be dynamically switched between implementations on conda-forge), use:: $ # for a development build - $ python dev.py build -C-Dblas=blas -C-Dlapack=lapack + $ spin build -C-Dblas=blas -C-Dlapack=lapack $ # to build and install a wheel $ python -m build -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack @@ -29,11 +29,11 @@ Other options that should work (as long as they're installed with ``pkg-config`` or CMake support) include ``mkl``, ``atlas``, ``blis`` and ``accelerate``. -Note that both Accelerate and ``scipy-openblas`` have flags in ``dev.py`` +Note that both Accelerate and ``scipy-openblas`` have flags in ``spin`` that are easier to remember, since they're commonly used for development:: - $ python dev.py build --with-accelerate - $ python dev.py build --with-scipy-openblas + $ spin build --with-accelerate + $ spin build --with-scipy-openblas The ``-Dlapack`` flag isn't needed for Accelerate, MKL or ``scipy-openblas``, since we can be sure that BLAS and LAPACK are the same for those options. @@ -93,7 +93,7 @@ user wants to override this autodetection mechanism for building against plain ``libblas``/``liblapack`` (this is what conda-forge does for example), use the ``-Duse-g77-abi=true`` build option. E.g.,:: - $ python -m build -C-Duse-g77-abi=true -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack + $ python -m build -C-Duse-g77-abi=true -Csetup-args=-Dblas=blas -Csetup-args=-Dlapack=lapack Work-in-progress @@ -107,4 +107,3 @@ of the box: LP64 (32-bit integer size) BLAS/LAPACK. - Automatically selecting from multiple possible BLAS and LAPACK options, with a user-provided order of precedence - diff --git a/doc/source/building/compilers_and_options.rst b/doc/source/building/compilers_and_options.rst index 53c89a5082ae..e76ef7be801e 100644 --- a/doc/source/building/compilers_and_options.rst +++ b/doc/source/building/compilers_and_options.rst @@ -52,10 +52,10 @@ you can configure the build as following to use the ``debug`` build type:: meson setup build --buildtype debug --prefix=$PWD/build-install -Now, you can use the ``dev.py`` interface for further building, installing and +Now, you can use the ``spin`` interface for further building, installing and testing SciPy:: - python dev.py -s linalg + spin -s linalg This will work because after initial configuration, Meson will remember the config options. @@ -75,7 +75,7 @@ such that you have at least 2 GB RAM per job. For example, to launch 6 jobs:: or:: - python dev.py build -j6 + spin build -j6 Use GCC and Clang builds in parallel @@ -89,14 +89,14 @@ For example, let us build using GCC and Clang. 1. Build with GCC:: - python dev.py build + spin build Using the above command, meson will build with the (default) GCC compilers in the ``build`` directory, and install to the ``build-install`` directory. 2. Build with Clang:: - CC=clang CXX=clang++ FC=gfortran python dev.py --build-dir=build-clang build + CC=clang CXX=clang++ FC=gfortran spin --build-dir=build-clang build Using the above commands, Meson will build with the Clang, Clang++ and Gfortran compilers in the ``build-clang`` directory, and then install SciPy into @@ -104,16 +104,14 @@ compilers in the ``build-clang`` directory, and then install SciPy into Meson will remember the compiler selection for the ``build-clang`` directory and it cannot be changed, so each future invocation of -``python dev.py --build-dir=build-clang `` it will automatically use Clang. +``spin --build-dir=build-clang `` it will automatically use Clang. Tip: use an alias to make this easier to use, e.g., -``alias dev-clang="python dev.py --build-dir=build-clang"`` and then +``alias dev-clang="spin --build-dir=build-clang"`` and then ``dev-clang build``. A common reason to have two builds is to compare between them. For example, to run the ``scipy.linalg`` tests for builds with both compilers, do:: - python dev.py -s linalg # run tests for the GCC build - python dev.py --build-dir build-clang -s linalg # run tests for the Clang build - - + spin -s linalg # run tests for the GCC build + spin --build-dir build-clang -s linalg # run tests for the Clang build diff --git a/doc/source/building/distutils_equivalents.rst b/doc/source/building/distutils_equivalents.rst index 440eef9215b2..8dfa07d1761d 100644 --- a/doc/source/building/distutils_equivalents.rst +++ b/doc/source/building/distutils_equivalents.rst @@ -22,7 +22,7 @@ The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdd *New workflows (Meson and meson-python based):* -1. ``python dev.py`` +1. ``spin`` 2. ``pip install -e . --no-build-isolation`` (see the ``meson-python`` docs) 3. the same as (2) 4. ``python -m build --no-isolation`` + ``pip install dist/scipy*.whl`` - see diff --git a/doc/source/building/index.rst b/doc/source/building/index.rst index 5453efe2c9c8..e7edf0593394 100644 --- a/doc/source/building/index.rst +++ b/doc/source/building/index.rst @@ -295,8 +295,8 @@ Then you want to do the following: 1. Create a dedicated development environment (virtual environment or conda environment), 2. Install all needed dependencies (*build*, and also *test*, *doc* and - *optional* dependencies), -3. Build SciPy with our ``dev.py`` developer interface. + *optional* dependencies), +3. Build SciPy with our ``spin`` developer interface. Step (3) is always the same, steps (1) and (2) are different between conda and virtual environments: @@ -363,43 +363,43 @@ virtual environments: # Alternatively, you can install just the dependencies for certain # development tasks: - # Build and dev dependencies (for `python dev.py {build, lint, mypy}`) + # Build and dev dependencies (for `spin {build, lint, mypy}`) python -m pip install -r requirements/build.txt -r requirements/dev.txt - # Doc dependencies (for `python dev.py {doc, refguide-check}`) + # Doc dependencies (for `spin {doc, refguide-check}`) python -m pip install -r requirements/doc.txt - # Test dependencies (for `python dev.py {test, bench, refguide-check}`) + # Test dependencies (for `spin {test, bench, refguide-check}`) python -m pip install -r requirements/test.txt To build SciPy in an activated development environment, run:: - python dev.py build + spin build This will install SciPy inside the repository (by default in a -``build-install`` directory). You can then run tests (``python dev.py test``), -drop into IPython (``python dev.py ipython``), or take other development steps -like build the html documentation or running benchmarks. The ``dev.py`` -interface is self-documenting, so please see ``python dev.py --help`` and -``python dev.py --help`` for detailed guidance. +``build-install`` directory). You can then run tests (``spin test``), +drop into IPython (``spin ipython``), or take other development steps +like build the html documentation or running benchmarks. The ``spin`` +interface is self-documenting, so please see ``spin --help`` and +``spin --help`` for detailed guidance. .. admonition:: IDE support & editable installs - While the ``dev.py`` interface is our recommended way of working on SciPy, + While the ``spin`` interface is our recommended way of working on SciPy, it has one limitation: because of the custom install location, SciPy - installed using ``dev.py`` will not be recognized automatically within an + installed using ``spin`` will not be recognized automatically within an IDE (e.g., for running a script via a "run" button, or setting breakpoints visually). This will work better with an *in-place build* (or "editable install"). Editable installs are supported. It is important to understand that **you - may use either an editable install or dev.py in a given repository clone, + may use either an editable install or spin in a given repository clone, but not both**. If you use editable installs, you have to use ``pytest`` - and other development tools directly instead of using ``dev.py``. + and other development tools directly instead of using ``spin``. To use an editable install, ensure you start from a clean repository (run - ``git clean -xdf`` if you've built with ``dev.py`` before) and have all + ``git clean -xdf`` if you've built with ``spin`` before) and have all dependencies set up correctly as described higher up on this page. Then do:: diff --git a/doc/source/building/understanding_meson.rst b/doc/source/building/understanding_meson.rst index d862a3a609ef..a84e2acca507 100644 --- a/doc/source/building/understanding_meson.rst +++ b/doc/source/building/understanding_meson.rst @@ -49,8 +49,7 @@ Explanation of build stages --------------------------- *This is for teaching purposes only; there should be no need to execute these -stages separately. The dev.py scripts in the root of the repo also contains -these steps and may be studied for insights.* +stages separately.* Assume we're starting from a clean repo and a fully set up conda environment:: diff --git a/doc/source/dev/api-dev/array_api.rst b/doc/source/dev/api-dev/array_api.rst index 530778de897f..28b2e67f2f1c 100644 --- a/doc/source/dev/api-dev/array_api.rst +++ b/doc/source/dev/api-dev/array_api.rst @@ -251,7 +251,7 @@ Adding tests ------------ To run a test on multiple array backends, you should add the ``xp`` fixture to it, -which is valued to the currently tested array namespace. +which is valued to the currently tested array namespace. The following pytest markers are available: @@ -278,7 +278,7 @@ The following pytest markers are available: * ``array_api_backends``: this marker is automatically added by the ``xp`` fixture to all tests that use it. This is useful e.g. to select all and only such tests:: - python dev.py test -b all -m array_api_backends + spin test -b all -m array_api_backends ``scipy._lib._array_api`` contains array-agnostic assertions such as ``xp_assert_close`` which can be used to replace assertions from `numpy.testing`. @@ -312,7 +312,7 @@ The following examples demonstrate how to use the markers:: Passing names of backends into ``exceptions`` means that they will not be skipped by ``cpu_only=True`` or ``eager_only=True``. This is useful when delegation -is implemented for some, but not all, non-CPU backends, and the CPU code path +is implemented for some, but not all, non-CPU backends, and the CPU code path requires conversion to NumPy for compiled code:: # array-api-strict and CuPy will always be skipped, for the given reasons. @@ -324,17 +324,17 @@ requires conversion to NumPy for compiled code:: def test_toto(self, xp): ... -After applying these markers, ``dev.py test`` can be used with the new option +After applying these markers, ``spin test`` can be used with the new option ``-b`` or ``--array-api-backend``:: - python dev.py test -b numpy -b torch -s cluster + spin test -b numpy -b torch -s cluster This automatically sets ``SCIPY_ARRAY_API`` appropriately. To test a library that has multiple devices with a non-default device, a second environment variable (``SCIPY_DEVICE``, only used in the test suite) can be set. Valid values depend on the array library under test, e.g. for PyTorch, valid values are ``"cpu", "cuda", "mps"``. To run the test suite with the PyTorch MPS -backend, use: ``SCIPY_DEVICE=mps python dev.py test -b torch``. +backend, use: ``SCIPY_DEVICE=mps spin test -b torch``. Note that there is a GitHub Actions workflow which tests with array-api-strict, PyTorch, and JAX on CPU. diff --git a/doc/source/dev/contributor/benchmarking.rst b/doc/source/dev/contributor/benchmarking.rst index 1826aff57914..d512856dabd4 100644 --- a/doc/source/dev/contributor/benchmarking.rst +++ b/doc/source/dev/contributor/benchmarking.rst @@ -92,7 +92,7 @@ submitting a pull request. To run all benchmarks, navigate to the root SciPy directory at the command line and execute:: - python dev.py bench + spin bench where ``bench`` activates the benchmark suite instead of the test suite. This builds SciPy and runs the benchmarks. (*Note: this could @@ -104,17 +104,17 @@ To run benchmarks from a particular benchmark module, such as ``optimize_linprog.py``, simply append the filename without the extension:: - python dev.py bench -t optimize_linprog + spin bench -t optimize_linprog To run a benchmark defined in a class, such as ``KleeMinty`` from ``optimize_linprog.py``:: - python dev.py bench -t optimize_linprog.KleeMinty + spin bench -t optimize_linprog.KleeMinty To compare benchmark results between the active branch and another, such as ``main``:: - python dev.py bench --compare main # select again by `-t optimize_linprog` + spin bench --compare main # select again by `-t optimize_linprog` All of the commands above display the results in plain text in the console, and the results are not saved for comparison with future diff --git a/doc/source/dev/contributor/compiled_code.rst b/doc/source/dev/contributor/compiled_code.rst index a9f9cb4c2831..813c4e876314 100644 --- a/doc/source/dev/contributor/compiled_code.rst +++ b/doc/source/dev/contributor/compiled_code.rst @@ -41,11 +41,11 @@ invokes the C code whose execution you want to debug. For instance Build SciPy in debug mode:: - python dev.py build -d + spin build -d Now, you can run:: - gdb --args python dev.py python mytest.py + gdb --args spin python mytest.py If you didn't compile with debug symbols enabled before, remove the ``build`` directory first. While in the debugger:: @@ -56,4 +56,4 @@ If you didn't compile with debug symbols enabled before, remove the The execution will now stop at the corresponding C function and you can step through it as usual. Instead of plain ``gdb`` you can, of course, use your favorite alternative debugger; run it on the -``python`` binary with arguments ``python dev.py python mytest.py``. +``python`` binary with arguments ``spin python mytest.py``. diff --git a/doc/source/dev/contributor/contributor_toc.rst b/doc/source/dev/contributor/contributor_toc.rst index c9dff5abf232..46d59bbf3a6d 100644 --- a/doc/source/dev/contributor/contributor_toc.rst +++ b/doc/source/dev/contributor/contributor_toc.rst @@ -15,7 +15,7 @@ though*). - :ref:`building-from-source` - how to set up a development environment, including installing compilers and SciPy dependencies, cloning the SciPy - repository on GitHub and updating git submodules, and using the ``dev.py`` + repository on GitHub and updating git submodules, and using the ``spin`` interface for building and running tests. - :ref:`editing-scipy` - how to edit SciPy Python code, with tips on finding which module contains SciPy functionality to be edited, adding new modules to @@ -52,9 +52,9 @@ Testing - :doc:`numpy:reference/testing` is the definitive guide to writing unit tests of NumPy or SciPy code (part of the NumPy documentation) - :ref:`writing-test-tips` contains tips for writing units tests -- :ref:`devpy-test` documents ``dev.py test``, the command to build SciPy and +- :ref:`devpy-test` documents ``spin test``, the command to build SciPy and run tests locally -- :ref:`debugging-linalg-issues` +- :ref:`debugging-linalg-issues` .. _docs: diff --git a/doc/source/dev/contributor/cython.rst b/doc/source/dev/contributor/cython.rst index 5c30131d3cfc..979db068fedc 100644 --- a/doc/source/dev/contributor/cython.rst +++ b/doc/source/dev/contributor/cython.rst @@ -120,7 +120,7 @@ Exercise #. Rebuild SciPy. Note that an extension module (a ``.so`` or ``.pyd`` file) has been added to the ``build/scipy/optimize/`` directory. -#. Time it, e.g. by dropping into IPython with ``python dev.py ipython`` and then: +#. Time it, e.g. by dropping into IPython with ``spin ipython`` and then: :: diff --git a/doc/source/dev/contributor/debugging_linalg_issues.rst b/doc/source/dev/contributor/debugging_linalg_issues.rst index a32e3b9700c0..31e95878bfb1 100644 --- a/doc/source/dev/contributor/debugging_linalg_issues.rst +++ b/doc/source/dev/contributor/debugging_linalg_issues.rst @@ -11,7 +11,7 @@ runtime dependency - and we support a significant number of BLAS/LAPACK libraries. This document aims to provide guidance about how to go about debugging linear -algebra issues. +algebra issues. If there is a real bug, it can be in one of three places: @@ -150,7 +150,7 @@ MKL is as simple as:: libblas 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib libcblas 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib liblapack 3.9.0-21_linux64_openblas --> 3.9.0-5_h92ddd45_netlib - + $ mamba install "libblas=*=*mkl" ... libblas 3.9.0-5_h92ddd45_netlib --> 3.9.0-21_linux64_mkl @@ -162,7 +162,7 @@ MKL is as simple as:: "user_api": "blas", "internal_api": "mkl", -This can be done for development builds as well, when building via ``dev.py`` +This can be done for development builds as well, when building via ``spin`` in the exact same way as in `SciPy's conda-forge build recipe `__ (outputs omitted for brevity, they're similar to the ones above):: @@ -170,10 +170,10 @@ in the exact same way as in `SciPy's conda-forge build recipe $ mamba env create -f environment.yml $ mamba activate scipy-dev $ mamba install "libblas=*=*netlib" # necessary, we need to build against blas/lapack - $ python dev.py build -C-Dblas=blas -C-Dlapack=lapack -C-Duse-g77-abi=true - $ python dev.py test -s linalg # run tests to verify + $ spin build -C-Dblas=blas -C-Dlapack=lapack -C-Duse-g77-abi=true + $ spin test -s linalg # run tests to verify $ mamba install "libblas=*=*mkl" - $ python dev.py test -s linalg + $ spin test -s linalg $ mamba install "libblas=*=*openblas" @@ -222,9 +222,9 @@ source. Once you have everything set up, the development experience is:: - $ python dev.py build -C-Dblas=flexiblas -C-Dlapack=flexiblas - $ FLEXIBLAS=NETLIB python dev.py test -s linalg - $ FLEXIBLAS=OpenBLAS python dev.py test -s linalg + $ spin build -C-Dblas=flexiblas -C-Dlapack=flexiblas + $ FLEXIBLAS=NETLIB spin test -s linalg + $ FLEXIBLAS=OpenBLAS spin test -s linalg # Or export the environment variable to make the selection stick: $ export FLEXIBLAS=OpenBLAS @@ -277,7 +277,7 @@ We're now ready to build SciPy against FlexiBLAS:: $ export PKG_CONFIG_PATH=$PWD/flexiblas-setup/built-libs/lib/pkgconfig/ $ cd scipy - $ python dev.py build -C-Dblas=flexiblas -C-Dlapack=flexiblas + $ spin build -C-Dblas=flexiblas -C-Dlapack=flexiblas ... Run-time dependency flexiblas found: YES 3.4.2 @@ -285,9 +285,9 @@ Now we can run the tests. Note that the ``NETLIB`` option is built without having to specify it; it's the default in FlexiBLAS and sources are included in its repository:: - $ FLEXIBLAS=OpenBLAS python dev.py test -s linalg - $ FLEXIBLAS=NETLIB python dev.py test -s linalg - $ python dev.py test -s linalg # uses the default (NETLIB) + $ FLEXIBLAS=OpenBLAS spin test -s linalg + $ FLEXIBLAS=NETLIB spin test -s linalg + $ spin test -s linalg # uses the default (NETLIB) This backend switching can also be done inside a Python interpreter with ``threadpoolctl`` (see `its README @@ -442,7 +442,7 @@ file to automate this and avoid the manual paths: $ ./build/repro_c # output may vary info = 0 - Re(eigv) = 4.000000 , 8.000000 , inf , -inf , + Re(eigv) = 4.000000 , 8.000000 , inf , -inf , Im(eigv = 0.000000 , 0.000000 , -nan , -nan , .. tab-item:: Fortran @@ -464,8 +464,8 @@ file to automate this and avoid the manual paths: info = 0 alphar = 1.0204501477442456 11.707793036240817 3.7423579363517347E-014 -1.1492523608519701E-014 - alphai = 0.0000000000000000 0.0000000000000000 0.0000000000000000 0.0000000000000000 - beta = 0.25511253693606051 1.4634741295300704 0.0000000000000000 0.0000000000000000 + alphai = 0.0000000000000000 0.0000000000000000 0.0000000000000000 0.0000000000000000 + beta = 0.25511253693606051 1.4634741295300704 0.0000000000000000 0.0000000000000000 Re(eigv) = 4.0000000000000142 8.0000000000001741 Infinity -Infinity Im(eigv) = 0.0000000000000000 0.0000000000000000 NaN NaN @@ -531,7 +531,7 @@ Here is an example ``gdb`` session:: (gdb) s # step through the C function Single stepping until exit from function dpotrf_, which has no line number information. - f2py_rout__flapack_dpotrf (capi_self=, capi_args=, + f2py_rout__flapack_dpotrf (capi_self=, capi_args=, capi_keywds=, f2py_func=0x7ffff4c48820 ) at scipy/linalg/_flapackmodule.c:63281 .... diff --git a/doc/source/dev/contributor/development_workflow.rst b/doc/source/dev/contributor/development_workflow.rst index 82d41c40c03c..9eb386c58c59 100644 --- a/doc/source/dev/contributor/development_workflow.rst +++ b/doc/source/dev/contributor/development_workflow.rst @@ -109,11 +109,11 @@ when it's time to submit a pull request. It's also a good idea to build this branch and run tests before continuing. Assuming you've followed one of the :ref:`building-from-source` pages to set up your development environment, you'll need to activate your development -environment and then run tests (note that the ``dev.py test`` command will +environment and then run tests (note that the ``spin test`` command will perform a build automatically if needed):: conda activate scipy-dev - python dev.py test -v + spin test -v .. _editing-workflow: diff --git a/doc/source/dev/contributor/devpy_test.rst b/doc/source/dev/contributor/devpy_test.rst index d98b9f2f7677..9cc1789acd3e 100644 --- a/doc/source/dev/contributor/devpy_test.rst +++ b/doc/source/dev/contributor/devpy_test.rst @@ -7,24 +7,24 @@ Running SciPy Tests Locally Basic test writing and execution from within the Python interpreter is documented in the :doc:`NumPy/SciPy testing guidelines `. This page -includes information about running tests from the command line using SciPy's -``dev.py`` command line tool. *Note: Before beginning, ensure that* |pytest|_ +includes information about running tests from the command line using the +``spin`` command line tool. *Note: Before beginning, ensure that* |pytest|_ *is installed.* .. note:: - The ``dev.py`` interface is self-documenting, in the sense that everything on + The ``spin`` interface is self-documenting, in the sense that everything on this page and more (including usage examples for each command) can be - accessed with ``python dev.py --help`` and for individual commands like - ``python dev.py --help``. In this case, you can check - ``python dev.py test --help``. + accessed with ``spin --help`` and for individual commands like + ``spin --help``. In this case, you can check + ``spin test --help``. To run all tests, navigate to the root SciPy directory at the command line and execute :: - python dev.py test + spin test This builds SciPy (or updates an existing build) and runs the tests. @@ -33,60 +33,60 @@ To run tests on a particular submodule, such as ``optimize``, use the :: - python dev.py test -s optimize + spin test -s optimize To run a particular test module, use the Pytest syntax of ``--test`` (or ``-t``):: - python dev.py test -t scipy..tests. + spin test -t scipy..tests. Example for |test-linprog|_ file tests, run: :: - python dev.py test -t scipy.optimize.tests.test_linprog + spin test -t scipy.optimize.tests.test_linprog To run a test class: :: - python dev.py test -t scipy..tests.:: + spin test -t scipy..tests.:: Example for ``TestLinprogRSCommon`` class from ``test_linprog.py``: :: - python dev.py test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon + spin test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon To run a particular test: :: - python dev.py test -t scipy..tests.:: + spin test -t scipy..tests.:: Example for ``test_unknown_solvers_and_options`` from ``test_linprog.py``: :: - python dev.py test -t scipy.optimize.tests.test_linprog::test_unknown_solvers_and_options + spin test -t scipy.optimize.tests.test_linprog::test_unknown_solvers_and_options For tests within a class, you need to specify the class name and the test name: :: - python dev.py test -t scipy..tests.:::: + spin test -t scipy..tests.:::: Example: :: - python dev.py test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon::test_nontrivial_problem_with_guess + spin test -t scipy.optimize.tests.test_linprog::TestLinprogRSCommon::test_nontrivial_problem_with_guess Other useful options include: - ``-v`` or ``--verbose``, which activates the verbose option for more - detailed output. + detailed output. - ``-b`` or ``--array-api-backend`` *backend* to include alternative array backends in array-api-compatible tests. See :ref:`dev-arrayapi` for details. @@ -96,15 +96,15 @@ Other useful options include: - ``-n`` or ``--no-build`` to prevent SciPy from updating the build before testing - ``-j`` or ``--parallel`` *n* to engage *n* cores when building SciPy; - e.g. \ ``python dev.py test -j 4`` engages four cores. As of `#10172`_ + e.g. \ ``spin test -j 4`` engages four cores. As of `#10172`_ this also runs the tests on four cores if |pytest-xdist|_ is installed. - ``-m full`` or ``--mode full`` to run the "full" test suite, including tests marked ``slow`` (e.g. with ``@pytest.mark.slow``). Note that this does not *run* tests marked ``xslow``; see Tips below. - ``--`` to send remaining command line arguments to ``pytest`` instead of - ``dev.py test``. For instance, while ``-n`` sent to ``pytest.py`` activates + ``spin test``. For instance, while ``-n`` sent to ``pytest.py`` activates the ``--no-build`` option, ``-n`` sent to ``pytest`` runs the tests on - multiple cores; e.g. \ ``python dev.py test -- -n 4`` runs tests using + multiple cores; e.g. \ ``spin test -- -n 4`` runs tests using four cores. *Note:* |pytest-xdist|_ *must be installed for testing on multiple cores.* Common command line arguments for ``pytest`` include: @@ -124,11 +124,11 @@ Tips: If you built SciPy from source but are having trouble running tests after a change to the codebase, try deleting the ``scipy/build`` -directory. This forces ``dev.py`` to completely rebuild SciPy before +directory. This forces ``spin`` to completely rebuild SciPy before performing tests. There is an additional level of very slow tests (several minutes), -which are disabled even when calling ``python dev.py test -m full``. +which are disabled even when calling ``spin test -m full``. They can be enabled by setting the environment variable ``SCIPY_XSLOW=1`` before running the test suite. diff --git a/doc/source/dev/contributor/pep8.rst b/doc/source/dev/contributor/pep8.rst index d89a213fe2b2..8408fc5b2f65 100644 --- a/doc/source/dev/contributor/pep8.rst +++ b/doc/source/dev/contributor/pep8.rst @@ -39,7 +39,7 @@ compliance before pushing your code: Alternatively, you can run the check manually from the SciPy root directory:: - python dev.py lint + spin lint You can also run the linter on specific files, using the ``--files`` option:: diff --git a/doc/source/dev/contributor/rendering_documentation.rst b/doc/source/dev/contributor/rendering_documentation.rst index 8b46aaa9eb36..2000be38f671 100644 --- a/doc/source/dev/contributor/rendering_documentation.rst +++ b/doc/source/dev/contributor/rendering_documentation.rst @@ -62,7 +62,7 @@ with Sphinx`_ \ *.* To render the documentation on your own machine: 0. Ensure that you have a working SciPy build (see :ref:`building-from-source`). -#. Then run ``python dev.py doc`` to build the documentation. +#. Then run ``spin docs`` to build the documentation. This can take a while the first time, but subsequent documentation builds are typically much faster. #. View the documentation in ``doc/build/html/``. You can start @@ -74,7 +74,7 @@ To render the documentation on your own machine: - Changes to certain documents do not take effect when Sphinx documentation is rebuilt. In this case, you can build from scratch by deleting the directories ``scipy/doc/build`` and ``source/reference/generated``, or by - running ``python dev.py doc clean`` then building the docs again. + running ``spin docs clean`` then building the docs again. - In case the SciPy version found by the above command is different from that of the latest commit in the repo, you will see a message like:: diff --git a/doc/source/dev/contributor/reviewing_prs.rst b/doc/source/dev/contributor/reviewing_prs.rst index 535a239d998c..051de88f80f5 100644 --- a/doc/source/dev/contributor/reviewing_prs.rst +++ b/doc/source/dev/contributor/reviewing_prs.rst @@ -72,9 +72,9 @@ Assuming you set up your development environment according to build the code and test it:: - python dev.py test -v + spin test -v -and if you ``import`` SciPy from within IPython (start it with ``python dev.py +and if you ``import`` SciPy from within IPython (start it with ``spin ipython``), you'll be importing the author's modified version of SciPy. If you want to collaborate with the author on their PR, you might instead diff --git a/doc/source/dev/core-dev/releasing.rst.inc b/doc/source/dev/core-dev/releasing.rst.inc index 8f3f6f430877..63336b726c50 100644 --- a/doc/source/dev/core-dev/releasing.rst.inc +++ b/doc/source/dev/core-dev/releasing.rst.inc @@ -148,7 +148,7 @@ Here is a complete list of artifacts created for a release: An ``sdist`` is generated by running ``python -m build --sdist`` (note: we still need to move this into a CI job!), and the Changelog and README are built -by running ``python dev.py notes`` (with tags, see ``python dev.py notes +by running ``spin notes`` (with tags, see ``spin notes --help``) in the repo root, and end up in ``REPO_ROOT/release/``. Do this after you've created the signed tag locally. If this completes without issues, push the release commit (not the tag, see section above) to the scipy repo. @@ -170,7 +170,7 @@ done in an automated fashion using ``tools/download-wheels.py``:: $ python tools/download-wheels.py 1.5.0rc1 -w REPO_ROOT/release/installers After this, we want to regenerate the README file, in order to have the MD5 and -SHA256 checksums of the just downloaded wheels in it. Run ``python dev.py +SHA256 checksums of the just downloaded wheels in it. Run ``spin notes`` again. diff --git a/doc/source/dev/hacking.rst b/doc/source/dev/hacking.rst index 60075ce5d4cb..e675987310a0 100644 --- a/doc/source/dev/hacking.rst +++ b/doc/source/dev/hacking.rst @@ -94,7 +94,7 @@ tests, benchmarks, and correct code style. Alternatively, you may run the linter manually:: - python dev.py lint + spin lint Most IDEs and text editors also have settings that can help you follow PEP8, for example by translating tabs by four spaces. More diff --git a/scipy/conftest.py b/scipy/conftest.py index f8098c30d4ad..455867380a3c 100644 --- a/scipy/conftest.py +++ b/scipy/conftest.py @@ -465,7 +465,7 @@ def test_device(xp, devices): ) # Profile is currently set by environment variable `SCIPY_HYPOTHESIS_PROFILE` -# In the future, it would be good to work the choice into dev.py. +# In the future, it would be good to work the choice into `.spin/cmds.py`. SCIPY_HYPOTHESIS_PROFILE = os.environ.get("SCIPY_HYPOTHESIS_PROFILE", "deterministic") hypothesis.settings.load_profile(SCIPY_HYPOTHESIS_PROFILE) diff --git a/tools/check_installation.py b/tools/check_installation.py index 9ffa58ef03bb..32287a47c722 100644 --- a/tools/check_installation.py +++ b/tools/check_installation.py @@ -7,7 +7,7 @@ install_directory_name: the relative path from the root of the repo to the directory where - SciPy is installed (for dev.py usually "build-install") + SciPy is installed (for `spin` usually "build-install") Notes ===== From 810231bc3e3131f6285f321dbf4efdcc21d49d5c Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Mon, 26 May 2025 21:38:02 +0200 Subject: [PATCH 246/251] CI: clean up free-threading job, use released Cython/Pythran --- .github/workflows/linux.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ab3d8dac2b07..84981c4d6bf8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -478,19 +478,11 @@ jobs: sudo apt-get update sudo apt-get install -y gfortran - - name: Install nightly Cython - run: | - pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple cython - - name: Install Python dependencies run: | - pip install numpy ninja meson-python pybind11 spin pytest pytest-xdist threadpoolctl pooch hypothesis + pip install -r requirements/build.txt pip install -r requirements/openblas.txt - - - name: Install Pythran master - run: | - # 0.17.0 doesn't support free-threading, update once the next release is out - python -m pip install git+https://github.com/serge-sans-paille/pythran.git + pip install spin pytest pytest-xdist threadpoolctl pooch hypothesis - name: Install pytest-run-parallel if: ${{ matrix.parallel == '1'}} From c1e2a04541289ae35757b135c21de66c592ff509 Mon Sep 17 00:00:00 2001 From: "Christine P. Chai" Date: Mon, 26 May 2025 17:31:31 -0700 Subject: [PATCH 247/251] DOC: Revise a footnote in meson-distutils [docs only] --- doc/source/building/distutils_equivalents.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/building/distutils_equivalents.rst b/doc/source/building/distutils_equivalents.rst index 8dfa07d1761d..de1fd88aa427 100644 --- a/doc/source/building/distutils_equivalents.rst +++ b/doc/source/building/distutils_equivalents.rst @@ -5,7 +5,7 @@ Meson and ``distutils`` ways of doing things *Old workflows (numpy.distutils based):* -The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdda03f92` [^1^_]. +The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdda03f92` [#]_. 1. ``python runtests.py`` 2. ``python setup.py build_ext -i`` + ``export @@ -29,4 +29,4 @@ The `runtests.py` file was removed in commit `0f73f92255253ec5dff2de5ca45d8d3bdd `pypa/build `_. 5. ``pip install .`` -[^1^_]: [Commit 0f73f92255253ec5dff2de5ca45d8d3bdda03f92 on GitHub](https://github.com/scipy/scipy/commit/0f73f92255253ec5dff2de5ca45d8d3bdda03f92). +.. [#] `Commit 0f73f92255253ec5dff2de5ca45d8d3bdda03f92 on GitHub `_. From 2cf1345a1e87de75478abd8cc2ae60170048198e Mon Sep 17 00:00:00 2001 From: crusaderky Date: Tue, 27 May 2025 11:08:03 +0100 Subject: [PATCH 248/251] DEV: `linalg`: help debug free-threading build --- .gitignore | 1 + scipy/meson.build | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index e3be9776ed22..c96535571841 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ benchmarks/html benchmarks/scipy-benchmarks .github/workflows/.pixi .openblas +scipy-openblas.pc scipy/_distributor_init_local.py scipy/__config__.py scipy/_lib/_ccallback_c.c diff --git a/scipy/meson.build b/scipy/meson.build index 1bc50f5026e8..7043a875dcb5 100644 --- a/scipy/meson.build +++ b/scipy/meson.build @@ -36,10 +36,12 @@ thread_dep = dependency('threads', required: false) # version easily for >=2.0. _numpy_dep = dependency('numpy', required: false) f2py_freethreading_arg = [] -if _numpy_dep.found() - if _numpy_dep.version().version_compare('>=2.1.0') - f2py_freethreading_arg = ['--free-threading'] - endif +if _numpy_dep.found() and _numpy_dep.version().version_compare('>=2.1.0') + f2py_freethreading_arg = ['--free-threading'] + message('f2py free-threading enabled') +else + message('f2py free-threading disabled; need numpy >=2.1.0.') + message('See https://github.com/mesonbuild/meson/issues/14651') endif # NumPy include directory - needed in all submodules From c399f27c93b0a486f5cbf1d01e7902d2548fb443 Mon Sep 17 00:00:00 2001 From: crusaderky Date: Tue, 27 May 2025 11:43:34 +0100 Subject: [PATCH 249/251] DEV: ignore `pixi-dev-scipystack` dirs --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e3be9776ed22..5f24740d8948 100644 --- a/.gitignore +++ b/.gitignore @@ -56,8 +56,9 @@ _configtest.c # Python files # ################ -# build directory +# build directories build +build-* # sphinx build directory doc/_build # cython files @@ -85,7 +86,6 @@ pip-wheel-metadata ######### .mesonpy-native-file.ini installdir/ -build-install/ .mesonpy/ # doit @@ -153,6 +153,7 @@ benchmarks/html benchmarks/scipy-benchmarks .github/workflows/.pixi .openblas +scipy-openblas.pc scipy/_distributor_init_local.py scipy/__config__.py scipy/_lib/_ccallback_c.c From ee96dee5c72f9ec5c3819d484de1b3f4e2f34a95 Mon Sep 17 00:00:00 2001 From: Guido Imperiale Date: Wed, 28 May 2025 22:29:23 +0100 Subject: [PATCH 250/251] TST: `linalg`: refactor `test_blas` (#23073) * TST: `linalg`: refactor `test_blas` * normalize * Expect fblas to contain all tests --- scipy/linalg/tests/test_blas.py | 1579 +++++++++++++++---------------- 1 file changed, 747 insertions(+), 832 deletions(-) diff --git a/scipy/linalg/tests/test_blas.py b/scipy/linalg/tests/test_blas.py index b6645d0ad5d9..3f1c526885a4 100644 --- a/scipy/linalg/tests/test_blas.py +++ b/scipy/linalg/tests/test_blas.py @@ -5,14 +5,12 @@ import math import pytest import numpy as np -import numpy.random -from numpy.testing import (assert_equal, assert_almost_equal, assert_, +from numpy.testing import (assert_equal, assert_almost_equal, assert_array_almost_equal, assert_allclose) from pytest import raises as assert_raises -from numpy import float32, float64, complex64, complex128, arange, triu, \ - tril, zeros, tril_indices, ones, mod, diag, append, eye, \ - nonzero +from numpy import (arange, triu, tril, zeros, tril_indices, ones, + diag, append, eye, nonzero) import scipy from scipy.linalg import _fblas as fblas, get_blas_funcs, toeplitz, solve @@ -22,8 +20,8 @@ except ImportError: cblas = None -REAL_DTYPES = [float32, float64] -COMPLEX_DTYPES = [complex64, complex128] +REAL_DTYPES = [np.float32, np.float64] +COMPLEX_DTYPES = [np.complex64, np.complex128] DTYPES = REAL_DTYPES + COMPLEX_DTYPES @@ -77,154 +75,111 @@ def test_get_blas_funcs_alias(): assert f is h -class TestCBLAS1Simple: +def parametrize_blas(mod, func_name, prefixes): + if mod is None: + return pytest.mark.skip(reason="cblas not available") + params = [] + for prefix in prefixes: + if 'z' in prefix: + dtype = np.complex128 + elif 'c' in prefix: + dtype = np.complex64 + elif 'd' in prefix: + dtype = np.float64 + else: + assert 's' in prefix + dtype = np.float32 + + f = getattr(mod, prefix + func_name) + params.append(pytest.param(f, dtype, id=prefix + func_name)) + + return pytest.mark.parametrize("f,dtype", params) + - def test_axpy(self): - for p in 'sd': - f = getattr(cblas, p+'axpy', None) - if f is None: - continue - assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), - [7, 9, 18]) - for p in 'cz': - f = getattr(cblas, p+'axpy', None) - if f is None: - continue +class TestCBLAS1Simple: + @parametrize_blas(cblas, "axpy", "sdcz") + def test_axpy(self, f, dtype): + assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), + [7, 9, 18]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([1, 2j, 3], [2, -1, 3], a=5), [7, 10j-1, 18]) class TestFBLAS1Simple: - def test_axpy(self): - for p in 'sd': - f = getattr(fblas, p+'axpy', None) - if f is None: - continue - assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), - [7, 9, 18]) - for p in 'cz': - f = getattr(fblas, p+'axpy', None) - if f is None: - continue + @parametrize_blas(fblas, "axpy", "sdcz") + def test_axpy(self, f, dtype): + assert_array_almost_equal(f([1, 2, 3], [2, -1, 3], a=5), + [7, 9, 18]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([1, 2j, 3], [2, -1, 3], a=5), [7, 10j-1, 18]) - def test_copy(self): - for p in 'sd': - f = getattr(fblas, p+'copy', None) - if f is None: - continue - assert_array_almost_equal(f([3, 4, 5], [8]*3), [3, 4, 5]) - for p in 'cz': - f = getattr(fblas, p+'copy', None) - if f is None: - continue + @parametrize_blas(fblas, "copy", "sdcz") + def test_copy(self, f, dtype): + assert_array_almost_equal(f([3, 4, 5], [8]*3), [3, 4, 5]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f([3, 4j, 5+3j], [8]*3), [3, 4j, 5+3j]) - def test_asum(self): - for p in 'sd': - f = getattr(fblas, p+'asum', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5]), 12) - for p in ['sc', 'dz']: - f = getattr(fblas, p+'asum', None) - if f is None: - continue + @parametrize_blas(fblas, "asum", ["s", "d", "sc", "dz"]) + def test_asum(self, f, dtype): + assert_almost_equal(f([3, -4, 5]), 12) + if dtype in COMPLEX_DTYPES: assert_almost_equal(f([3j, -4, 3-4j]), 14) - def test_dot(self): - for p in 'sd': - f = getattr(fblas, p+'dot', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5], [2, 5, 1]), -9) - - def test_complex_dotu(self): - for p in 'cz': - f = getattr(fblas, p+'dotu', None) - if f is None: - continue - assert_almost_equal(f([3j, -4, 3-4j], [2, 3, 1]), -9+2j) - - def test_complex_dotc(self): - for p in 'cz': - f = getattr(fblas, p+'dotc', None) - if f is None: - continue - assert_almost_equal(f([3j, -4, 3-4j], [2, 3j, 1]), 3-14j) - - def test_nrm2(self): - for p in 'sd': - f = getattr(fblas, p+'nrm2', None) - if f is None: - continue - assert_almost_equal(f([3, -4, 5]), math.sqrt(50)) - for p in ['c', 'z', 'sc', 'dz']: - f = getattr(fblas, p+'nrm2', None) - if f is None: - continue + @parametrize_blas(fblas, "dot", "sd") + def test_dot(self, f, dtype): + assert_almost_equal(f([3, -4, 5], [2, 5, 1]), -9) + + @parametrize_blas(fblas, "dotu", "cz") + def test_dotu(self, f, dtype): + assert_almost_equal(f([3j, -4, 3-4j], [2, 3, 1]), -9+2j) + + @parametrize_blas(fblas, "dotc", "cz") + def test_dotc(self, f, dtype): + assert_almost_equal(f([3j, -4, 3-4j], [2, 3j, 1]), 3-14j) + + @parametrize_blas(fblas, "nrm2", ["s", "d", "sc", "dz"]) + def test_nrm2(self, f, dtype): + assert_almost_equal(f([3, -4, 5]), math.sqrt(50)) + if dtype in COMPLEX_DTYPES: assert_almost_equal(f([3j, -4, 3-4j]), math.sqrt(50)) - def test_scal(self): - for p in 'sd': - f = getattr(fblas, p+'scal', None) - if f is None: - continue - assert_array_almost_equal(f(2, [3, -4, 5]), [6, -8, 10]) - for p in 'cz': - f = getattr(fblas, p+'scal', None) - if f is None: - continue - assert_array_almost_equal(f(3j, [3j, -4, 3-4j]), [-9, -12j, 12+9j]) - for p in ['cs', 'zd']: - f = getattr(fblas, p+'scal', None) - if f is None: - continue + @parametrize_blas(fblas, "scal", ["s", "d", "cs", "zd"]) + def test_scal(self, f, dtype): + assert_array_almost_equal(f(2, [3, -4, 5]), [6, -8, 10]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3, [3j, -4, 3-4j]), [9j, -12, 9-12j]) - def test_swap(self): - for p in 'sd': - f = getattr(fblas, p+'swap', None) - if f is None: - continue - x, y = [2, 3, 1], [-2, 3, 7] - x1, y1 = f(x, y) - assert_array_almost_equal(x1, y) - assert_array_almost_equal(y1, x) - for p in 'cz': - f = getattr(fblas, p+'swap', None) - if f is None: - continue + @parametrize_blas(fblas, "swap", "sdcz") + def test_swap(self, f, dtype): + x, y = [2, 3, 1], [-2, 3, 7] + x1, y1 = f(x, y) + assert_array_almost_equal(x1, y) + assert_array_almost_equal(y1, x) + + if dtype in COMPLEX_DTYPES: x, y = [2, 3j, 1], [-2, 3, 7-3j] x1, y1 = f(x, y) assert_array_almost_equal(x1, y) assert_array_almost_equal(y1, x) - def test_amax(self): - for p in 'sd': - f = getattr(fblas, 'i'+p+'amax') - assert_equal(f([-2, 4, 3]), 1) - for p in 'cz': - f = getattr(fblas, 'i'+p+'amax') + @parametrize_blas(fblas, "amax", ["is", "id", "ic", "iz"]) + def test_amax(self, f, dtype): + assert_equal(f([-2, 4, 3]), 1) + if dtype in COMPLEX_DTYPES: assert_equal(f([-5, 4+3j, 6]), 1) + # XXX: need tests for rot,rotm,rotg,rotmg class TestFBLAS2Simple: - - def test_gemv(self): - for p in 'sd': - f = getattr(fblas, p+'gemv', None) - if f is None: - continue - assert_array_almost_equal(f(3, [[3]], [-4]), [-36]) - assert_array_almost_equal(f(3, [[3]], [-4], 3, [5]), [-21]) - for p in 'cz': - f = getattr(fblas, p+'gemv', None) - if f is None: - continue + @parametrize_blas(fblas, "gemv", "sdcz") + def test_gemv(self, f, dtype): + assert_array_almost_equal(f(3, [[3]], [-4]), [-36]) + assert_array_almost_equal(f(3, [[3]], [-4], 3, [5]), [-21]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3j, [[3-4j]], [-4]), [-48-36j]) assert_array_almost_equal(f(3j, [[3-4j]], [-4], 3, [5j]), [-48-21j]) @@ -232,84 +187,56 @@ def test_gemv(self): # All of these *ger* functions are segfaulting when called from multiple # threads under free-threaded CPython, see gh-21936. @pytest.mark.thread_unsafe - def test_ger(self): - - for p in 'sd': - f = getattr(fblas, p+'ger', None) - if f is None: - continue - assert_array_almost_equal(f(1, [1, 2], [3, 4]), [[3, 4], [6, 8]]) - assert_array_almost_equal(f(2, [1, 2, 3], [3, 4]), - [[6, 8], [12, 16], [18, 24]]) - - assert_array_almost_equal(f(1, [1, 2], [3, 4], - a=[[1, 2], [3, 4]]), [[4, 6], [9, 12]]) - - for p in 'cz': - f = getattr(fblas, p+'geru', None) - if f is None: - continue + @parametrize_blas(fblas, "ger", "sd") + def test_ger(self, f, dtype): + assert_array_almost_equal(f(1, [1, 2], [3, 4]), [[3, 4], [6, 8]]) + assert_array_almost_equal(f(2, [1, 2, 3], [3, 4]), + [[6, 8], [12, 16], [18, 24]]) + assert_array_almost_equal(f(1, [1, 2], [3, 4], + a=[[1, 2], [3, 4]]), [[4, 6], [9, 12]]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(1, [1j, 2], [3, 4]), [[3j, 4j], [6, 8]]) - assert_array_almost_equal(f(-2, [1j, 2j, 3j], [3j, 4j]), + assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), [[6, 8], [12, 16], [18, 24]]) - for p in 'cz': - for name in ('ger', 'gerc'): - f = getattr(fblas, p+name, None) - if f is None: - continue - assert_array_almost_equal(f(1, [1j, 2], [3, 4]), - [[3j, 4j], [6, 8]]) - assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), - [[6, 8], [12, 16], [18, 24]]) - - def test_syr_her(self): + @pytest.mark.thread_unsafe + @parametrize_blas(fblas, "geru", "cz") + def test_geru(self, f, dtype): + assert_array_almost_equal(f(1, [1j, 2], [3, 4]), + [[3j, 4j], [6, 8]]) + assert_array_almost_equal(f(-2, [1j, 2j, 3j], [3j, 4j]), + [[6, 8], [12, 16], [18, 24]]) + + @pytest.mark.thread_unsafe + @parametrize_blas(fblas, "gerc", "cz") + def test_gerc(self, f, dtype): + assert_array_almost_equal(f(1, [1j, 2], [3, 4]), + [[3j, 4j], [6, 8]]) + assert_array_almost_equal(f(2, [1j, 2j, 3j], [3j, 4j]), + [[6, 8], [12, 16], [18, 24]]) + + @parametrize_blas(fblas, "syr", "sdcz") + def test_syr(self, f, dtype): x = np.arange(1, 5, dtype='d') resx = np.triu(x[:, np.newaxis] * x) resx_reverse = np.triu(x[::-1, np.newaxis] * x[::-1]) - y = np.linspace(0, 8.5, 17, endpoint=False) - z = np.arange(1, 9, dtype='d').view('D') resz = np.triu(z[:, np.newaxis] * z) resz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1]) - rehz = np.triu(z[:, np.newaxis] * z.conj()) - rehz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1].conj()) - w = np.c_[np.zeros(4), z, np.zeros(4)].ravel() - for p, rtol in zip('sd', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr', None) - if f is None: - continue - assert_allclose(f(1.0, x), resx, rtol=rtol) - assert_allclose(f(1.0, x, lower=True), resx.T, rtol=rtol) - assert_allclose(f(1.0, y, incx=2, offx=2, n=4), resx, rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, y, incx=-2, offx=2, n=4), - resx_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'f' if p == 's' else 'd', 'F') - b = f(1.0, x, a=a, overwrite_a=True) - assert_allclose(a, resx, rtol=rtol) + rtol = np.finfo(dtype).eps - b = f(2.0, x, a=a) - assert_(a is not b) - assert_allclose(b, 3*resx, rtol=rtol) + assert_allclose(f(1.0, x), resx, rtol=rtol) + assert_allclose(f(1.0, x, lower=True), resx.T, rtol=rtol) + assert_allclose(f(1.0, y, incx=2, offx=2, n=4), resx, rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, y, incx=-2, offx=2, n=4), + resx_reverse, rtol=rtol) - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr', None) - if f is None: - continue + if dtype in COMPLEX_DTYPES: assert_allclose(f(1.0, z), resz, rtol=rtol) assert_allclose(f(1.0, z, lower=True), resz.T, rtol=rtol) assert_allclose(f(1.0, w, incx=3, offx=1, n=4), resz, rtol=rtol) @@ -317,50 +244,64 @@ def test_syr_her(self): assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), resz_reverse, rtol=rtol) - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') + a = np.zeros((4, 4), dtype, 'F') b = f(1.0, z, a=a, overwrite_a=True) assert_allclose(a, resz, rtol=rtol) - b = f(2.0, z, a=a) - assert_(a is not b) + assert a is not b assert_allclose(b, 3*resz, rtol=rtol) - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'her', None) - if f is None: - continue - assert_allclose(f(1.0, z), rehz, rtol=rtol) - assert_allclose(f(1.0, z, lower=True), rehz.T.conj(), rtol=rtol) - assert_allclose(f(1.0, w, incx=3, offx=1, n=4), rehz, rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), - rehz_reverse, rtol=rtol) + else: + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, a=a, overwrite_a=True) + assert_allclose(a, resx, rtol=rtol) + b = f(2.0, x, a=a) + assert a is not b + assert_allclose(b, 3*resx, rtol=rtol) - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') - b = f(1.0, z, a=a, overwrite_a=True) - assert_allclose(a, rehz, rtol=rtol) + assert_raises(Exception, f, 1.0, x, incx=0) + assert_raises(Exception, f, 1.0, x, offx=5) + assert_raises(Exception, f, 1.0, x, offx=-2) + assert_raises(Exception, f, 1.0, x, n=-2) + assert_raises(Exception, f, 1.0, x, n=5) + assert_raises(Exception, f, 1.0, x, lower=2) + assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - b = f(2.0, z, a=a) - assert_(a is not b) - assert_allclose(b, 3*rehz, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, incx=0) - assert_raises(Exception, f, 1.0, x, offx=5) - assert_raises(Exception, f, 1.0, x, offx=-2) - assert_raises(Exception, f, 1.0, x, n=-2) - assert_raises(Exception, f, 1.0, x, n=5) - assert_raises(Exception, f, 1.0, x, lower=2) - assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) - - def test_syr2(self): + @parametrize_blas(fblas, "her", "cz") + def test_her(self, f, dtype): + x = np.arange(1, 5, dtype='d') + z = np.arange(1, 9, dtype='d').view('D') + rehz = np.triu(z[:, np.newaxis] * z.conj()) + rehz_reverse = np.triu(z[::-1, np.newaxis] * z[::-1].conj()) + w = np.c_[np.zeros(4), z, np.zeros(4)].ravel() + + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, z), rehz, rtol=rtol) + assert_allclose(f(1.0, z, lower=True), rehz.T.conj(), rtol=rtol) + assert_allclose(f(1.0, w, incx=3, offx=1, n=4), rehz, rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, w, incx=-3, offx=1, n=4), + rehz_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, z, a=a, overwrite_a=True) + assert_allclose(a, rehz, rtol=rtol) + + b = f(2.0, z, a=a) + assert a is not b + assert_allclose(b, 3*rehz, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, incx=0) + assert_raises(Exception, f, 1.0, x, offx=5) + assert_raises(Exception, f, 1.0, x, offx=-2) + assert_raises(Exception, f, 1.0, x, n=-2) + assert_raises(Exception, f, 1.0, x, n=5) + assert_raises(Exception, f, 1.0, x, lower=2) + assert_raises(Exception, f, 1.0, x, a=np.zeros((2, 2), 'd', 'F')) + + @parametrize_blas(fblas, "syr2", "sd") + def test_syr2(self, f, dtype): x = np.arange(1, 5, dtype='d') y = np.arange(5, 9, dtype='d') resxy = np.triu(x[:, np.newaxis] * y + y[:, np.newaxis] * x) @@ -368,44 +309,41 @@ def test_syr2(self): + y[::-1, np.newaxis] * x[::-1]) q = np.linspace(0, 8.5, 17, endpoint=False) - - for p, rtol in zip('sd', [1e-7, 1e-14]): - f = getattr(fblas, p+'syr2', None) - if f is None: - continue - assert_allclose(f(1.0, x, y), resxy, rtol=rtol) - assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) - assert_allclose(f(1.0, x, y, lower=True), resxy.T, rtol=rtol) - - assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10), - resxy, rtol=rtol) - assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10, n=3), - resxy[:3, :3], rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, q, q, incx=-2, offx=2, incy=-2, offy=10), - resxy_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'f' if p == 's' else 'd', 'F') - b = f(1.0, x, y, a=a, overwrite_a=True) - assert_allclose(a, resxy, rtol=rtol) - - b = f(2.0, x, y, a=a) - assert_(a is not b) - assert_allclose(b, 3*resxy, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, y, incx=0) - assert_raises(Exception, f, 1.0, x, y, offx=5) - assert_raises(Exception, f, 1.0, x, y, offx=-2) - assert_raises(Exception, f, 1.0, x, y, incy=0) - assert_raises(Exception, f, 1.0, x, y, offy=5) - assert_raises(Exception, f, 1.0, x, y, offy=-2) - assert_raises(Exception, f, 1.0, x, y, n=-2) - assert_raises(Exception, f, 1.0, x, y, n=5) - assert_raises(Exception, f, 1.0, x, y, lower=2) - assert_raises(Exception, f, 1.0, x, y, - a=np.zeros((2, 2), 'd', 'F')) - - def test_her2(self): + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, x, y), resxy, rtol=rtol) + assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) + assert_allclose(f(1.0, x, y, lower=True), resxy.T, rtol=rtol) + + assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10), + resxy, rtol=rtol) + assert_allclose(f(1.0, q, q, incx=2, offx=2, incy=2, offy=10, n=3), + resxy[:3, :3], rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, q, q, incx=-2, offx=2, incy=-2, offy=10), + resxy_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, y, a=a, overwrite_a=True) + assert_allclose(a, resxy, rtol=rtol) + + b = f(2.0, x, y, a=a) + assert a is not b + assert_allclose(b, 3*resxy, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, y, incx=0) + assert_raises(Exception, f, 1.0, x, y, offx=5) + assert_raises(Exception, f, 1.0, x, y, offx=-2) + assert_raises(Exception, f, 1.0, x, y, incy=0) + assert_raises(Exception, f, 1.0, x, y, offy=5) + assert_raises(Exception, f, 1.0, x, y, offy=-2) + assert_raises(Exception, f, 1.0, x, y, n=-2) + assert_raises(Exception, f, 1.0, x, y, n=5) + assert_raises(Exception, f, 1.0, x, y, lower=2) + assert_raises(Exception, f, 1.0, x, y, a=np.zeros((2, 2), 'd', 'F')) + + @parametrize_blas(fblas, "her2", "cz") + def test_her2(self, f, dtype): x = np.arange(1, 9, dtype='d').view('D') y = np.arange(9, 17, dtype='d').view('D') resxy = x[:, np.newaxis] * y.conj() + y[:, np.newaxis] * x.conj() @@ -418,416 +356,400 @@ def test_her2(self): u = np.c_[np.zeros(4), x, np.zeros(4)].ravel() v = np.c_[np.zeros(4), y, np.zeros(4)].ravel() - for p, rtol in zip('cz', [1e-7, 1e-14]): - f = getattr(fblas, p+'her2', None) - if f is None: - continue - assert_allclose(f(1.0, x, y), resxy, rtol=rtol) - assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) - assert_allclose(f(1.0, x, y, lower=True), resxy.T.conj(), - rtol=rtol) - - assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1), - resxy, rtol=rtol) - assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1, n=3), - resxy[:3, :3], rtol=rtol) - # negative increments imply reversed vectors in blas - assert_allclose(f(1.0, u, v, incx=-3, offx=1, incy=-3, offy=1), - resxy_reverse, rtol=rtol) - - a = np.zeros((4, 4), 'F' if p == 'c' else 'D', 'F') - b = f(1.0, x, y, a=a, overwrite_a=True) - assert_allclose(a, resxy, rtol=rtol) - - b = f(2.0, x, y, a=a) - assert_(a is not b) - assert_allclose(b, 3*resxy, rtol=rtol) - - assert_raises(Exception, f, 1.0, x, y, incx=0) - assert_raises(Exception, f, 1.0, x, y, offx=5) - assert_raises(Exception, f, 1.0, x, y, offx=-2) - assert_raises(Exception, f, 1.0, x, y, incy=0) - assert_raises(Exception, f, 1.0, x, y, offy=5) - assert_raises(Exception, f, 1.0, x, y, offy=-2) - assert_raises(Exception, f, 1.0, x, y, n=-2) - assert_raises(Exception, f, 1.0, x, y, n=5) - assert_raises(Exception, f, 1.0, x, y, lower=2) - assert_raises(Exception, f, 1.0, x, y, - a=np.zeros((2, 2), 'd', 'F')) - - def test_gbmv(self): + rtol = np.finfo(dtype).eps + + assert_allclose(f(1.0, x, y), resxy, rtol=rtol) + assert_allclose(f(1.0, x, y, n=3), resxy[:3, :3], rtol=rtol) + assert_allclose(f(1.0, x, y, lower=True), resxy.T.conj(), + rtol=rtol) + + assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1), + resxy, rtol=rtol) + assert_allclose(f(1.0, u, v, incx=3, offx=1, incy=3, offy=1, n=3), + resxy[:3, :3], rtol=rtol) + # negative increments imply reversed vectors in blas + assert_allclose(f(1.0, u, v, incx=-3, offx=1, incy=-3, offy=1), + resxy_reverse, rtol=rtol) + + a = np.zeros((4, 4), dtype, 'F') + b = f(1.0, x, y, a=a, overwrite_a=True) + assert_allclose(a, resxy, rtol=rtol) + + b = f(2.0, x, y, a=a) + assert a is not b + assert_allclose(b, 3*resxy, rtol=rtol) + + assert_raises(Exception, f, 1.0, x, y, incx=0) + assert_raises(Exception, f, 1.0, x, y, offx=5) + assert_raises(Exception, f, 1.0, x, y, offx=-2) + assert_raises(Exception, f, 1.0, x, y, incy=0) + assert_raises(Exception, f, 1.0, x, y, offy=5) + assert_raises(Exception, f, 1.0, x, y, offy=-2) + assert_raises(Exception, f, 1.0, x, y, n=-2) + assert_raises(Exception, f, 1.0, x, y, n=5) + assert_raises(Exception, f, 1.0, x, y, lower=2) + assert_raises(Exception, f, 1.0, x, y, + a=np.zeros((2, 2), 'd', 'F')) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_gbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 7 - m = 5 - kl = 1 - ku = 2 - # fake a banded matrix via toeplitz - A = toeplitz(append(rng.random(kl+1), zeros(m-kl-1)), - append(rng.random(ku+1), zeros(n-ku-1))) - A = A.astype(dtype) - Ab = zeros((kl+ku+1, n), dtype=dtype) - - # Form the banded storage - Ab[2, :5] = A[0, 0] # diag - Ab[1, 1:6] = A[0, 1] # sup1 - Ab[0, 2:7] = A[0, 2] # sup2 - Ab[3, :4] = A[1, 0] # sub1 - - x = rng.random(n).astype(dtype) - y = rng.random(m).astype(dtype) - alpha, beta = dtype(3), dtype(-5) - - func, = get_blas_funcs(('gbmv',), dtype=dtype) - y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, - x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, - x=y, y=x, beta=beta, trans=1) - y2 = alpha * A.T.dot(y) + beta * x - assert_array_almost_equal(y1, y2) - - def test_sbmv_hbmv(self): + n = 7 + m = 5 + kl = 1 + ku = 2 + # fake a banded matrix via toeplitz + A = toeplitz(append(rng.random(kl+1), zeros(m-kl-1)), + append(rng.random(ku+1), zeros(n-ku-1))) + A = A.astype(dtype) + Ab = zeros((kl+ku+1, n), dtype=dtype) + + # Form the banded storage + Ab[2, :5] = A[0, 0] # diag + Ab[1, 1:6] = A[0, 1] # sup1 + Ab[0, 2:7] = A[0, 2] # sup2 + Ab[3, :4] = A[1, 0] # sub1 + + x = rng.random(n).astype(dtype) + y = rng.random(m).astype(dtype) + alpha, beta = dtype(3), dtype(-5) + + func, = get_blas_funcs(('gbmv',), dtype=dtype) + y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, + x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + y1 = func(m=m, n=n, ku=ku, kl=kl, alpha=alpha, a=Ab, + x=y, y=x, beta=beta, trans=1) + y2 = alpha * A.T.dot(y) + beta * x + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_sbmv_hbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 6 - k = 2 - A = zeros((n, n), dtype=dtype) - Ab = zeros((k+1, n), dtype=dtype) - - # Form the array and its packed banded storage - A[arange(n), arange(n)] = rng.random(n) - for ind2 in range(1, k+1): - temp = rng.random(n-ind2) - A[arange(n-ind2), arange(ind2, n)] = temp - Ab[-1-ind2, ind2:] = temp - A = A.astype(dtype) - A = A + A.T if ind < 2 else A + A.conj().T - Ab[-1, :] = diag(A) - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - alpha, beta = dtype(1.25), dtype(3) - - if ind > 1: - func, = get_blas_funcs(('hbmv',), dtype=dtype) - else: - func, = get_blas_funcs(('sbmv',), dtype=dtype) - y1 = func(k=k, alpha=alpha, a=Ab, x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - def test_spmv_hpmv(self): - rng = np.random.default_rng(12345698) - for ind, dtype in enumerate(DTYPES+COMPLEX_DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 4 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - xlong = arange(2*n).astype(dtype) - ylong = ones(2*n).astype(dtype) - alpha, beta = dtype(1.25), dtype(2) - - if ind > 3: - func, = get_blas_funcs(('hpmv',), dtype=dtype) - else: - func, = get_blas_funcs(('spmv',), dtype=dtype) - y1 = func(n=n, alpha=alpha, ap=Ap, x=x, y=y, beta=beta) - y2 = alpha * A.dot(x) + beta * y - assert_array_almost_equal(y1, y2) - - # Test inc and offsets - y1 = func(n=n-1, alpha=alpha, beta=beta, x=xlong, y=ylong, ap=Ap, - incx=2, incy=2, offx=n, offy=n) - y2 = (alpha * A[:-1, :-1]).dot(xlong[3::2]) + beta * ylong[3::2] - assert_array_almost_equal(y1[3::2], y2) - assert_almost_equal(y1[4], ylong[4]) - - def test_spr_hpr(self): + n = 6 + k = 2 + A = zeros((n, n), dtype=dtype) + Ab = zeros((k+1, n), dtype=dtype) + + # Form the array and its packed banded storage + A[arange(n), arange(n)] = rng.random(n) + for ind2 in range(1, k+1): + temp = rng.random(n-ind2) + A[arange(n-ind2), arange(ind2, n)] = temp + Ab[-1-ind2, ind2:] = temp + A = A.astype(dtype) + if dtype in COMPLEX_DTYPES: + A += A.conj().T + func, = get_blas_funcs(('hbmv',), dtype=dtype) + else: + A += A.T + func, = get_blas_funcs(('sbmv',), dtype=dtype) + + Ab[-1, :] = diag(A) + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + alpha, beta = dtype(1.25), dtype(3) + + y1 = func(k=k, alpha=alpha, a=Ab, x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("fname,dtype", [ + *[('spmv', dtype) for dtype in REAL_DTYPES + COMPLEX_DTYPES], + *[('hpmv', dtype) for dtype in COMPLEX_DTYPES], + ]) + def test_spmv_hpmv(self, fname, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES+COMPLEX_DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 4 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - alpha = (DTYPES+COMPLEX_DTYPES)[mod(ind, 4)](2.5) - - if ind > 3: - func, = get_blas_funcs(('hpr',), dtype=dtype) - y2 = alpha * x[:, None].dot(x[None, :].conj()) + A - else: - func, = get_blas_funcs(('spr',), dtype=dtype) - y2 = alpha * x[:, None].dot(x[None, :]) + A - - y1 = func(n=n, alpha=alpha, ap=Ap, x=x) - y1f = zeros((3, 3), dtype=dtype) - y1f[r, c] = y1 - y1f[c, r] = y1.conj() if ind > 3 else y1 - assert_array_almost_equal(y1f, y2) - - def test_spr2_hpr2(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.T if fname == 'spmv' else A.conj().T + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + xlong = arange(2*n).astype(dtype) + ylong = ones(2*n).astype(dtype) + alpha, beta = dtype(1.25), dtype(2) + + func, = get_blas_funcs((fname,), dtype=dtype) + y1 = func(n=n, alpha=alpha, ap=Ap, x=x, y=y, beta=beta) + y2 = alpha * A.dot(x) + beta * y + assert_array_almost_equal(y1, y2) + + # Test inc and offsets + y1 = func(n=n-1, alpha=alpha, beta=beta, x=xlong, y=ylong, ap=Ap, + incx=2, incy=2, offx=n, offy=n) + y2 = (alpha * A[:-1, :-1]).dot(xlong[3::2]) + beta * ylong[3::2] + assert_array_almost_equal(y1[3::2], y2) + assert_almost_equal(y1[4], ylong[4]) + + @pytest.mark.parametrize("fname,dtype", [ + *[('spr', dtype) for dtype in REAL_DTYPES + COMPLEX_DTYPES], + *[('hpr', dtype) for dtype in COMPLEX_DTYPES], + ]) + def test_spr_hpr(self, fname, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 3 - A = rng.random((n, n)).astype(dtype) - if ind > 1: - A += rng.random((n, n))*1j - A = A.astype(dtype) - A = A + A.T if ind < 2 else A + A.conj().T - c, r = tril_indices(n) - Ap = A[r, c] - x = rng.random(n).astype(dtype) - y = rng.random(n).astype(dtype) - alpha = dtype(2) - - if ind > 1: - func, = get_blas_funcs(('hpr2',), dtype=dtype) - else: - func, = get_blas_funcs(('spr2',), dtype=dtype) - - u = alpha.conj() * x[:, None].dot(y[None, :].conj()) - y2 = A + u + u.conj().T - y1 = func(n=n, alpha=alpha, x=x, y=y, ap=Ap) - y1f = zeros((3, 3), dtype=dtype) - y1f[r, c] = y1 - y1f[[1, 2, 2], [0, 0, 1]] = y1[[1, 3, 4]].conj() - assert_array_almost_equal(y1f, y2) - - def test_tbmv(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.T if fname == 'spr' else A.conj().T + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + + alpha = np.finfo(dtype).dtype.type(2.5) + if fname == 'hpr': + func, = get_blas_funcs(('hpr',), dtype=dtype) + y2 = alpha * x[:, None].dot(x[None, :].conj()) + A + else: + func, = get_blas_funcs(('spr',), dtype=dtype) + y2 = alpha * x[:, None].dot(x[None, :]) + A + + y1 = func(n=n, alpha=alpha, ap=Ap, x=x) + y1f = zeros((3, 3), dtype=dtype) + y1f[r, c] = y1 + y1f[c, r] = y1.conj() if fname == 'hpr' else y1 + assert_array_almost_equal(y1f, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_spr2_hpr2(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - k = 3 - x = rng.random(n).astype(dtype) - A = zeros((n, n), dtype=dtype) - # Banded upper triangular array - for sup in range(k+1): - A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) - - # Add complex parts for c,z - if ind > 1: - A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) - - # Form the banded storage - Ab = zeros((k+1, n), dtype=dtype) - for row in range(k+1): - Ab[-row-1, row:] = diag(A, k=row) - func, = get_blas_funcs(('tbmv',), dtype=dtype) - - y1 = func(k=k, a=Ab, x=x) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) - y2 = A.T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) - y2 = A.conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_tbsv(self): + n = 3 + A = rng.random((n, n)).astype(dtype) + if dtype in COMPLEX_DTYPES: + A += rng.random((n, n))*1j + A += A.conj().T + func, = get_blas_funcs(('hpr2',), dtype=dtype) + else: + A += A.T + func, = get_blas_funcs(('spr2',), dtype=dtype) + + c, r = tril_indices(n) + Ap = A[r, c] + x = rng.random(n).astype(dtype) + y = rng.random(n).astype(dtype) + alpha = dtype(2) + + u = alpha.conj() * x[:, None].dot(y[None, :].conj()) + y2 = A + u + u.conj().T + y1 = func(n=n, alpha=alpha, x=x, y=y, ap=Ap) + y1f = zeros((3, 3), dtype=dtype) + y1f[r, c] = y1 + y1f[[1, 2, 2], [0, 0, 1]] = y1[[1, 3, 4]].conj() + assert_array_almost_equal(y1f, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tbmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 6 - k = 3 - x = rng.random(n).astype(dtype) - A = zeros((n, n), dtype=dtype) - # Banded upper triangular array - for sup in range(k+1): - A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) - - # Add complex parts for c,z - if ind > 1: - A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) - - # Form the banded storage - Ab = zeros((k+1, n), dtype=dtype) - for row in range(k+1): - Ab[-row-1, row:] = diag(A, k=row) - func, = get_blas_funcs(('tbsv',), dtype=dtype) - - y1 = func(k=k, a=Ab, x=x) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) - y2 = solve(A.T, x) - assert_array_almost_equal(y1, y2) - - y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) - y2 = solve(A.conj().T, x) - assert_array_almost_equal(y1, y2) - - def test_tpmv(self): + n = 10 + k = 3 + x = rng.random(n).astype(dtype) + A = zeros((n, n), dtype=dtype) + # Banded upper triangular array + for sup in range(k+1): + A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) + + # Add complex parts for c,z + if dtype in COMPLEX_DTYPES: + A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) + + # Form the banded storage + Ab = zeros((k+1, n), dtype=dtype) + for row in range(k+1): + Ab[-row-1, row:] = diag(A, k=row) + func, = get_blas_funcs(('tbmv',), dtype=dtype) + + y1 = func(k=k, a=Ab, x=x) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) + y2 = A.T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) + y2 = A.conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tbsv(self, dtype): + rng = np.random.default_rng(12345) + n = 6 + k = 3 + x = rng.random(n).astype(dtype) + A = zeros((n, n), dtype=dtype) + # Banded upper triangular array + for sup in range(k+1): + A[arange(n-sup), arange(sup, n)] = rng.random(n-sup) + + # Add complex parts for c,z + if dtype in COMPLEX_DTYPES: + A[nonzero(A)] += 1j * rng.random((k+1)*n-(k*(k+1)//2)).astype(dtype) + + # Form the banded storage + Ab = zeros((k+1, n), dtype=dtype) + for row in range(k+1): + Ab[-row-1, row:] = diag(A, k=row) + func, = get_blas_funcs(('tbsv',), dtype=dtype) + + y1 = func(k=k, a=Ab, x=x) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=1) + y2 = solve(A.T, x) + assert_array_almost_equal(y1, y2) + + y1 = func(k=k, a=Ab, x=x, diag=1, trans=2) + y2 = solve(A.conj().T, x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tpmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - x = rng.random(n).astype(dtype) - # Upper triangular array - if ind < 2: - A = triu(rng.random((n, n))) - else: - A = triu(rng.random((n, n)) + rng.random((n, n))*1j) - - # Form the packed storage - c, r = tril_indices(n) - Ap = A[r, c] - func, = get_blas_funcs(('tpmv',), dtype=dtype) - - y1 = func(n=n, ap=Ap, x=x) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = A.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) - y2 = A.T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) - y2 = A.conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_tpsv(self): + n = 10 + x = rng.random(n).astype(dtype) + # Upper triangular array + if dtype in COMPLEX_DTYPES: + A = triu(rng.random((n, n)) + rng.random((n, n))*1j) + else: + A = triu(rng.random((n, n))) + + # Form the packed storage + c, r = tril_indices(n) + Ap = A[r, c] + func, = get_blas_funcs(('tpmv',), dtype=dtype) + + y1 = func(n=n, ap=Ap, x=x) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = A.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) + y2 = A.T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) + y2 = A.conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_tpsv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 10 - x = rng.random(n).astype(dtype) - # Upper triangular array - if ind < 2: - A = triu(rng.random((n, n))) - else: - A = triu(rng.random((n, n)) + rng.random((n, n))*1j) - A += eye(n) - # Form the packed storage - c, r = tril_indices(n) - Ap = A[r, c] - func, = get_blas_funcs(('tpsv',), dtype=dtype) - - y1 = func(n=n, ap=Ap, x=x) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(A, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) - y2 = solve(A.T, x) - assert_array_almost_equal(y1, y2) - - y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) - y2 = solve(A.conj().T, x) - assert_array_almost_equal(y1, y2) - - def test_trmv(self): + n = 10 + x = rng.random(n).astype(dtype) + # Upper triangular array + if dtype in COMPLEX_DTYPES: + A = triu(rng.random((n, n)) + rng.random((n, n))*1j) + else: + A = triu(rng.random((n, n))) + A += eye(n) + # Form the packed storage + c, r = tril_indices(n) + Ap = A[r, c] + func, = get_blas_funcs(('tpsv',), dtype=dtype) + + y1 = func(n=n, ap=Ap, x=x) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(A, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=1) + y2 = solve(A.T, x) + assert_array_almost_equal(y1, y2) + + y1 = func(n=n, ap=Ap, x=x, diag=1, trans=2) + y2 = solve(A.conj().T, x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_trmv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 3 - A = (rng.random((n, n))+eye(n)).astype(dtype) - x = rng.random(3).astype(dtype) - func, = get_blas_funcs(('trmv',), dtype=dtype) - - y1 = func(a=A, x=x) - y2 = triu(A).dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = triu(A).dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1, trans=1) - y2 = triu(A).T.dot(x) - assert_array_almost_equal(y1, y2) - - y1 = func(a=A, x=x, diag=1, trans=2) - y2 = triu(A).conj().T.dot(x) - assert_array_almost_equal(y1, y2) - - def test_trsv(self): + n = 3 + A = (rng.random((n, n))+eye(n)).astype(dtype) + x = rng.random(3).astype(dtype) + func, = get_blas_funcs(('trmv',), dtype=dtype) + + y1 = func(a=A, x=x) + y2 = triu(A).dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = triu(A).dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1, trans=1) + y2 = triu(A).T.dot(x) + assert_array_almost_equal(y1, y2) + + y1 = func(a=A, x=x, diag=1, trans=2) + y2 = triu(A).conj().T.dot(x) + assert_array_almost_equal(y1, y2) + + @pytest.mark.parametrize("dtype", DTYPES) + def test_trsv(self, dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - n = 15 - A = (rng.random((n, n))+eye(n)).astype(dtype) - x = rng.random(n).astype(dtype) - func, = get_blas_funcs(('trsv',), dtype=dtype) + n = 15 + A = (rng.random((n, n))+eye(n)).astype(dtype) + x = rng.random(n).astype(dtype) + func, = get_blas_funcs(('trsv',), dtype=dtype) - y1 = func(a=A, x=x) - y2 = solve(triu(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x) + y2 = solve(triu(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, lower=1) - y2 = solve(tril(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, lower=1) + y2 = solve(tril(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1) - A[arange(n), arange(n)] = dtype(1) - y2 = solve(triu(A), x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1) + A[arange(n), arange(n)] = dtype(1) + y2 = solve(triu(A), x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1, trans=1) - y2 = solve(triu(A).T, x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1, trans=1) + y2 = solve(triu(A).T, x) + assert_array_almost_equal(y1, y2) - y1 = func(a=A, x=x, diag=1, trans=2) - y2 = solve(triu(A).conj().T, x) - assert_array_almost_equal(y1, y2) + y1 = func(a=A, x=x, diag=1, trans=2) + y2 = solve(triu(A).conj().T, x) + assert_array_almost_equal(y1, y2) class TestFBLAS3Simple: - - def test_gemm(self): - for p in 'sd': - f = getattr(fblas, p+'gemm', None) - if f is None: - continue - assert_array_almost_equal(f(3, [3], [-4]), [[-36]]) - assert_array_almost_equal(f(3, [3], [-4], 3, [5]), [-21]) - for p in 'cz': - f = getattr(fblas, p+'gemm', None) - if f is None: - continue + @parametrize_blas(fblas, "gemm", "sdcz") + def test_gemm(self, f, dtype): + assert_array_almost_equal(f(3, [3], [-4]), [[-36]]) + assert_array_almost_equal(f(3, [3], [-4], 3, [5]), [-21]) + if dtype in COMPLEX_DTYPES: assert_array_almost_equal(f(3j, [3-4j], [-4]), [[-48-36j]]) assert_array_almost_equal(f(3j, [3-4j], [-4], 3, [5j]), [-48-21j]) -def _get_func(func, ps='sdzc'): - """Just a helper: return a specified BLAS function w/typecode.""" - for p in ps: - f = getattr(fblas, p+func, None) - if f is None: - continue - yield f - - class TestBLAS3Symm: def setup_method(self): @@ -839,38 +761,37 @@ def setup_method(self): self.t = np.array([[2., -1., 8.], [3., 0., 9.]]) - def test_symm(self): - for f in _get_func('symm'): - res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) - assert_array_almost_equal(res, self.t) + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm(self, f, dtype): + res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) + assert_array_almost_equal(res, self.t) - res = f(a=self.a.T, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) - assert_array_almost_equal(res, self.t) + res = f(a=self.a.T, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) + assert_array_almost_equal(res, self.t) - res = f(a=self.a, b=self.b.T, side=1, c=self.c.T, - alpha=1., beta=1.) - assert_array_almost_equal(res, self.t.T) + res = f(a=self.a, b=self.b.T, side=1, c=self.c.T, + alpha=1., beta=1.) + assert_array_almost_equal(res, self.t.T) - def test_summ_wrong_side(self): - f = getattr(fblas, 'dsymm', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, 'b': self.b, - 'alpha': 1, 'side': 1}) - # `side=1` means C <- B*A, hence shapes of A and B are to be - # compatible. Otherwise, f2py exception is raised + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm_wrong_side(self, f, dtype): + """`side=1` means C <- B*A, hence shapes of A and B are to be + compatible. Otherwise, f2py exception is raised. + """ + # FIXME narrow down to _fblas.error + with pytest.raises(Exception): + f(a=self.a, b=self.b, alpha=1, side=1) - def test_symm_wrong_uplo(self): + @parametrize_blas(fblas, "symm", "sdcz") + def test_symm_wrong_uplo(self, f, dtype): """SYMM only considers the upper/lower part of A. Hence setting wrong value for `lower` (default is lower=0, meaning upper triangle) gives a wrong result. """ - f = getattr(fblas, 'dsymm', None) - if f is not None: - res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) - assert np.allclose(res, self.t) - - res = f(a=self.a, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) - assert not np.allclose(res, self.t) + res = f(a=self.a, b=self.b, c=self.c, alpha=1., beta=1.) + assert np.allclose(res, self.t) + res = f(a=self.a, b=self.b, lower=1, c=self.c, alpha=1., beta=1.) + assert not np.allclose(res, self.t) class TestBLAS3Syrk: @@ -884,29 +805,28 @@ def setup_method(self): self.tt = np.array([[5., 6.], [6., 13.]]) - def test_syrk(self): - for f in _get_func('syrk'): - c = f(a=self.a, alpha=1.) - assert_array_almost_equal(np.triu(c), np.triu(self.t)) + @parametrize_blas(fblas, "syrk", "sdcz") + def test_syrk(self, f, dtype): + c = f(a=self.a, alpha=1.) + assert_array_almost_equal(np.triu(c), np.triu(self.t)) - c = f(a=self.a, alpha=1., lower=1) - assert_array_almost_equal(np.tril(c), np.tril(self.t)) + c = f(a=self.a, alpha=1., lower=1) + assert_array_almost_equal(np.tril(c), np.tril(self.t)) - c0 = np.ones(self.t.shape) - c = f(a=self.a, alpha=1., beta=1., c=c0) - assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) + c0 = np.ones(self.t.shape) + c = f(a=self.a, alpha=1., beta=1., c=c0) + assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) - c = f(a=self.a, alpha=1., trans=1) - assert_array_almost_equal(np.triu(c), np.triu(self.tt)) + c = f(a=self.a, alpha=1., trans=1) + assert_array_almost_equal(np.triu(c), np.triu(self.tt)) # prints '0-th dimension must be fixed to 3 but got 5', # FIXME: suppress? - # FIXME: how to catch the _fblas.error? - def test_syrk_wrong_c(self): - f = getattr(fblas, 'dsyrk', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, 'alpha': 1., - 'c': np.ones((5, 8))}) + @parametrize_blas(fblas, "syrk", "sdcz") + def test_syrk_wrong_c(self, f, dtype): + # FIXME narrow down to _fblas.error + with pytest.raises(Exception): + f(a=self.a, alpha=1., c=np.ones((5, 8))) # if C is supplied, it must have compatible dimensions @@ -924,29 +844,26 @@ def setup_method(self): self.tt = np.array([[0., 1.], [1., 6]]) - def test_syr2k(self): - for f in _get_func('syr2k'): - c = f(a=self.a, b=self.b, alpha=1.) - assert_array_almost_equal(np.triu(c), np.triu(self.t)) + @parametrize_blas(fblas, "syr2k", "sdcz") + def test_syr2k(self, f, dtype): + c = f(a=self.a, b=self.b, alpha=1.) + assert_array_almost_equal(np.triu(c), np.triu(self.t)) - c = f(a=self.a, b=self.b, alpha=1., lower=1) - assert_array_almost_equal(np.tril(c), np.tril(self.t)) + c = f(a=self.a, b=self.b, alpha=1., lower=1) + assert_array_almost_equal(np.tril(c), np.tril(self.t)) - c0 = np.ones(self.t.shape) - c = f(a=self.a, b=self.b, alpha=1., beta=1., c=c0) - assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) + c0 = np.ones(self.t.shape) + c = f(a=self.a, b=self.b, alpha=1., beta=1., c=c0) + assert_array_almost_equal(np.triu(c), np.triu(self.t+c0)) - c = f(a=self.a, b=self.b, alpha=1., trans=1) - assert_array_almost_equal(np.triu(c), np.triu(self.tt)) + c = f(a=self.a, b=self.b, alpha=1., trans=1) + assert_array_almost_equal(np.triu(c), np.triu(self.tt)) # prints '0-th dimension must be fixed to 3 but got 5', FIXME: suppress? - def test_syr2k_wrong_c(self): - f = getattr(fblas, 'dsyr2k', None) - if f is not None: - assert_raises(Exception, f, **{'a': self.a, - 'b': self.b, - 'alpha': 1., - 'c': np.zeros((15, 8))}) + @parametrize_blas(fblas, "syr2k", "sdcz") + def test_syr2k_wrong_c(self, f, dtype): + with pytest.raises(Exception): + f(a=self.a, b=self.b, alpha=1., c=np.zeros((15, 8))) # if C is supplied, it must have compatible dimensions @@ -957,41 +874,41 @@ def setup_method(self): self.sigma_y = np.array([[0., -1.j], [1.j, 0.]]) - def test_symm_zc(self): - for f in _get_func('symm', 'zc'): - # NB: a is symmetric w/upper diag of ONLY - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, -1])) + @parametrize_blas(fblas, "symm", "zc") + def test_symm(self, f, dtype): + # NB: a is symmetric w/upper diag of ONLY + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, -1])) - def test_hemm_zc(self): - for f in _get_func('hemm', 'zc'): - # NB: a is hermitian w/upper diag of ONLY - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, 1])) + @parametrize_blas(fblas, "hemm", "zc") + def test_hemm(self, f, dtype): + # NB: a is hermitian w/upper diag of ONLY + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, 1])) - def test_syrk_zr(self): - for f in _get_func('syrk', 'zc'): - res = f(a=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([-1, -1])) + @parametrize_blas(fblas, "syrk", "zc") + def test_syrk(self, f, dtype): + res = f(a=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([-1, -1])) - def test_herk_zr(self): - for f in _get_func('herk', 'zc'): - res = f(a=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), np.diag([1, 1])) + @parametrize_blas(fblas, "herk", "zc") + def test_herk(self, f, dtype): + res = f(a=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), np.diag([1, 1])) - def test_syr2k_zr(self): - for f in _get_func('syr2k', 'zc'): - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), 2.*np.diag([-1, -1])) + @parametrize_blas(fblas, "syr2k", "zc") + def test_syr2k_zr(self, f, dtype): + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), 2.*np.diag([-1, -1])) - def test_her2k_zr(self): - for f in _get_func('her2k', 'zc'): - res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) - assert_array_almost_equal(np.triu(res), 2.*np.diag([1, 1])) + @parametrize_blas(fblas, "her2k", "zc") + def test_her2k_zr(self, f, dtype): + res = f(a=self.sigma_y, b=self.sigma_y, alpha=1.) + assert_array_almost_equal(np.triu(res), 2.*np.diag([1, 1])) class TestTRMM: - """Quick and simple tests for dtrmm.""" + """Quick and simple tests for *trmm.""" def setup_method(self): self.a = np.array([[1., 2., ], @@ -1006,106 +923,104 @@ def setup_method(self): self.b2 = np.array([[1, 4], [2, 5], [3, 6], [7, 8], [9, 10]], order="f") - @pytest.mark.parametrize("dtype_", DTYPES) - def test_side(self, dtype_): - trmm = get_blas_funcs("trmm", dtype=dtype_) + @pytest.mark.parametrize("dtype", DTYPES) + def test_side(self, dtype): + trmm = get_blas_funcs("trmm", dtype=dtype) # Provide large A array that works for side=1 but not 0 (see gh-10841) assert_raises(Exception, trmm, 1.0, self.a2, self.b2) - res = trmm(1.0, self.a2.astype(dtype_), self.b2.astype(dtype_), + res = trmm(1.0, self.a2.astype(dtype), self.b2.astype(dtype), side=1) k = self.b2.shape[1] assert_allclose(res, self.b2 @ self.a2[:k, :k], rtol=0., - atol=100*np.finfo(dtype_).eps) - - def test_ab(self): - f = getattr(fblas, 'dtrmm', None) - if f is not None: - result = f(1., self.a, self.b) - # default a is upper triangular - expected = np.array([[13., 16., -5.], - [5., 6., -2.]]) - assert_array_almost_equal(result, expected) - - def test_ab_lower(self): - f = getattr(fblas, 'dtrmm', None) - if f is not None: - result = f(1., self.a, self.b, lower=True) - expected = np.array([[3., 4., -1.], - [-1., -2., 0.]]) # now a is lower triangular - assert_array_almost_equal(result, expected) - - def test_b_overwrites(self): - # BLAS dtrmm modifies B argument in-place. + atol=100*np.finfo(dtype).eps) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_ab(self, f, dtype): + result = f(1., self.a, self.b) + # default a is upper triangular + expected = np.array([[13., 16., -5.], + [ 5., 6., -2.]]) + assert_array_almost_equal(result, expected) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_ab_lower(self, f, dtype): + result = f(1., self.a, self.b, lower=True) + expected = np.array([[ 3., 4., -1.], + [-1., -2., 0.]]) # now a is lower triangular + assert_array_almost_equal(result, expected) + + @parametrize_blas(fblas, "trmm", "sdcz") + def test_b_overwrites(self, f, dtype): + # BLAS *trmm modifies B argument in-place. # Here the default is to copy, but this can be overridden - f = getattr(fblas, 'dtrmm', None) - if f is not None: - for overwr in [True, False]: - bcopy = self.b.copy() - result = f(1., self.a, bcopy, overwrite_b=overwr) - # C-contiguous arrays are copied - assert_(bcopy.flags.f_contiguous is False and - np.may_share_memory(bcopy, result) is False) - assert_equal(bcopy, self.b) - - bcopy = np.asfortranarray(self.b.copy()) # or just transpose it - result = f(1., self.a, bcopy, overwrite_b=True) - assert_(bcopy.flags.f_contiguous is True and - np.may_share_memory(bcopy, result) is True) - assert_array_almost_equal(bcopy, result) - - -def test_trsm(): + b = self.b.astype(dtype) + for overwr in [True, False]: + bcopy = b.copy() + result = f(1., self.a, bcopy, overwrite_b=overwr) + # C-contiguous arrays are copied + assert not bcopy.flags.f_contiguous + assert not np.may_share_memory(bcopy, result) + assert_equal(bcopy, b) + + bcopy = np.asfortranarray(b.copy()) # or just transpose it + result = f(1., self.a, bcopy, overwrite_b=True) + assert bcopy.flags.f_contiguous + assert np.may_share_memory(bcopy, result) + assert_array_almost_equal(bcopy, result) + + +@pytest.mark.parametrize("dtype", DTYPES) +def test_trsm(dtype): rng = np.random.default_rng(1234) - for ind, dtype in enumerate(DTYPES): - tol = np.finfo(dtype).eps*1000 - func, = get_blas_funcs(('trsm',), dtype=dtype) - - # Test protection against size mismatches - A = rng.random((4, 5)).astype(dtype) - B = rng.random((4, 4)).astype(dtype) - alpha = dtype(1) - assert_raises(Exception, func, alpha, A, B) - assert_raises(Exception, func, alpha, A.T, B) - - n = 8 - m = 7 - alpha = dtype(-2.5) - if ind < 2: - A = rng.random((m, m)) + eye(m) - else: - A = (rng.random((m, m)) + rng.random((m, m))*1j) + eye(m) - A = A.astype(dtype) - Au = triu(A) - Al = tril(A) - B1 = rng.random((m, n)).astype(dtype) - B2 = rng.random((n, m)).astype(dtype) - - x1 = func(alpha=alpha, a=A, b=B1) - assert_equal(B1.shape, x1.shape) - x2 = solve(Au, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, trans_a=1) - x2 = solve(Au.T, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, trans_a=2) - x2 = solve(Au.conj().T, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B1, diag=1) - Au[arange(m), arange(m)] = dtype(1) - x2 = solve(Au, alpha*B1) - assert_allclose(x1, x2, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1) - x2 = solve(Au.conj().T, alpha*B2.conj().T) - assert_allclose(x1, x2.conj().T, atol=tol) - - x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1, lower=1) - Al[arange(m), arange(m)] = dtype(1) - x2 = solve(Al.conj().T, alpha*B2.conj().T) - assert_allclose(x1, x2.conj().T, atol=tol) + tol = np.finfo(dtype).eps*1000 + func, = get_blas_funcs(('trsm',), dtype=dtype) + + # Test protection against size mismatches + A = rng.random((4, 5)).astype(dtype) + B = rng.random((4, 4)).astype(dtype) + alpha = dtype(1) + assert_raises(Exception, func, alpha, A, B) + assert_raises(Exception, func, alpha, A.T, B) + + n = 8 + m = 7 + alpha = dtype(-2.5) + if dtype in COMPLEX_DTYPES: + A = (rng.random((m, m)) + rng.random((m, m))*1j) + eye(m) + else: + A = rng.random((m, m)) + eye(m) + A = A.astype(dtype) + Au = triu(A) + Al = tril(A) + B1 = rng.random((m, n)).astype(dtype) + B2 = rng.random((n, m)).astype(dtype) + + x1 = func(alpha=alpha, a=A, b=B1) + assert_equal(B1.shape, x1.shape) + x2 = solve(Au, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, trans_a=1) + x2 = solve(Au.T, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, trans_a=2) + x2 = solve(Au.conj().T, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B1, diag=1) + Au[arange(m), arange(m)] = dtype(1) + x2 = solve(Au, alpha*B1) + assert_allclose(x1, x2, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1) + x2 = solve(Au.conj().T, alpha*B2.conj().T) + assert_allclose(x1, x2.conj().T, atol=tol) + + x1 = func(alpha=alpha, a=A, b=B2, diag=1, side=1, lower=1) + Al[arange(m), arange(m)] = dtype(1) + x2 = solve(Al.conj().T, alpha*B2.conj().T) + assert_allclose(x1, x2.conj().T, atol=tol) @pytest.mark.xfail(run=False, From 7d936fa25e2e8fa74ec6399620dd6490e4dfb33e Mon Sep 17 00:00:00 2001 From: Joren Hammudoglu Date: Thu, 29 May 2025 15:18:13 +0200 Subject: [PATCH 251/251] ENH: stats.tukey_hsd: improve array index iteration in `TukeyHSDResult.__str__` (#23065) --- scipy/stats/_hypotests.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/scipy/stats/_hypotests.py b/scipy/stats/_hypotests.py index 10b5bafb6e6a..ca451fdb9bb2 100644 --- a/scipy/stats/_hypotests.py +++ b/scipy/stats/_hypotests.py @@ -1749,13 +1749,12 @@ def __str__(self): s = ("Pairwise Group Comparisons" f" ({self._ci_cl*100:.1f}% Confidence Interval)\n") s += "Comparison Statistic p-value Lower CI Upper CI\n" - for i in range(self.pvalue.shape[0]): - for j in range(self.pvalue.shape[0]): - if i != j: - s += (f" ({i} - {j}) {self.statistic[i, j]:>10.3f}" - f"{self.pvalue[i, j]:>10.3f}" - f"{self._ci.low[i, j]:>10.3f}" - f"{self._ci.high[i, j]:>10.3f}\n") + for i, j in np.ndindex(self.pvalue.shape): + if i != j: + s += (f" ({i} - {j}) {self.statistic[i, j]:>10.3f}" + f"{self.pvalue[i, j]:>10.3f}" + f"{self._ci.low[i, j]:>10.3f}" + f"{self._ci.high[i, j]:>10.3f}\n") return s def confidence_interval(self, confidence_level=.95):

    B-s5JyyQY(R*#s& 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