diff --git a/unumpy/__init__.py b/unumpy/__init__.py index 936089b..aac246a 100644 --- a/unumpy/__init__.py +++ b/unumpy/__init__.py @@ -152,6 +152,9 @@ """ from ._multimethods import * from .lib import c_, r_, s_ +from . import linalg +from . import lib +from . import random from ._version import get_versions diff --git a/unumpy/_multimethods.py b/unumpy/_multimethods.py index 042c2ab..66b5c2a 100644 --- a/unumpy/_multimethods.py +++ b/unumpy/_multimethods.py @@ -29,6 +29,9 @@ def _dtype_argreplacer(args, kwargs, dispatchables): def replacer(*a, dtype=None, **kw): out_kw = kw.copy() out_kw["dtype"] = dispatchables[0] + if "out" in out_kw: + out_kw["out"] = dispatchables[1] + return a, out_kw return replacer(*args, **kwargs) @@ -45,6 +48,13 @@ def self_method(a, *args, **kwargs): return self_method(*args, **kwargs) +def _skip_self_argreplacer(args, kwargs, dispatchables): + def replacer(self, *args, **kwargs): + return (self,) + dispatchables, kwargs + + return replacer(*args, **kwargs) + + def _ureduce_argreplacer(args, kwargs, dispatchables): def ureduce(self, a, axis=0, dtype=None, out=None, keepdims=False): return ( diff --git a/unumpy/cupy_backend.py b/unumpy/cupy_backend.py index f05ee24..dacf5c1 100644 --- a/unumpy/cupy_backend.py +++ b/unumpy/cupy_backend.py @@ -23,14 +23,15 @@ def overridden_class(self): def _get_from_name_domain(name, domain): module = cp - domain_hierarchy = domain.split(".") + name_hierarchy = name.split(".") + domain_hierarchy = domain.split(".") + name_hierarchy[0:-1] for d in domain_hierarchy[1:]: if hasattr(module, d): module = getattr(module, d) else: return NotImplemented - if hasattr(module, name): - return getattr(module, name) + if hasattr(module, name_hierarchy[-1]): + return getattr(module, name_hierarchy[-1]) else: return NotImplemented @@ -48,7 +49,7 @@ def __ua_function__(method, args, kwargs): if len(args) != 0 and isinstance(args[0], unumpy.ClassOverrideMeta): return NotImplemented - cupy_method = _get_from_name_domain(method.__name__, method.domain) + cupy_method = _get_from_name_domain(method.__qualname__, method.domain) if cupy_method is NotImplemented: return NotImplemented diff --git a/unumpy/dask_backend.py b/unumpy/dask_backend.py index 53a0d75..d149056 100644 --- a/unumpy/dask_backend.py +++ b/unumpy/dask_backend.py @@ -31,14 +31,15 @@ def overridden_class(self): def _get_from_name_domain(name, domain): module = da - domain_hierarchy = domain.split(".") + name_hierarchy = name.split(".") + domain_hierarchy = domain.split(".") + name_hierarchy[0:-1] for d in domain_hierarchy[1:]: if hasattr(module, d): module = getattr(module, d) else: return NotImplemented - if hasattr(module, name): - return getattr(module, name) + if hasattr(module, name_hierarchy[-1]): + return getattr(module, name_hierarchy[-1]) else: return NotImplemented @@ -144,7 +145,7 @@ def __ua_function__(self, method, args, kwargs): if len(args) != 0 and isinstance(args[0], unumpy.ClassOverrideMeta): return NotImplemented - dask_method = _get_from_name_domain(method.__name__, method.domain) + dask_method = _get_from_name_domain(method.__qualname__, method.domain) if dask_method is NotImplemented: return NotImplemented diff --git a/unumpy/numpy_backend.py b/unumpy/numpy_backend.py index 596ba59..e4cfbc5 100644 --- a/unumpy/numpy_backend.py +++ b/unumpy/numpy_backend.py @@ -1,6 +1,6 @@ import numpy as np from uarray import Dispatchable, wrap_single_convertor -from unumpy import ufunc, ufunc_list, ndarray, dtype, linalg +from unumpy import ufunc, ufunc_list, ndarray, dtype import unumpy import functools @@ -29,11 +29,12 @@ def overridden_class(self): def _get_from_name_domain(name, domain): module = np - domain_hierarchy = domain.split(".") + name_hierarchy = name.split(".") + domain_hierarchy = domain.split(".") + name_hierarchy[0:-1] for d in domain_hierarchy[1:]: module = getattr(module, d) - if hasattr(module, name): - return getattr(module, name) + if hasattr(module, name_hierarchy[-1]): + return getattr(module, name_hierarchy[-1]) else: return NotImplemented @@ -45,7 +46,7 @@ def __ua_function__(method, args, kwargs): if len(args) != 0 and isinstance(args[0], unumpy.ClassOverrideMeta): return NotImplemented - method_numpy = _get_from_name_domain(method.__name__, method.domain) + method_numpy = _get_from_name_domain(method.__qualname__, method.domain) if method_numpy is NotImplemented: return NotImplemented diff --git a/unumpy/random/__init__.py b/unumpy/random/__init__.py new file mode 100644 index 0000000..ecdf4d9 --- /dev/null +++ b/unumpy/random/__init__.py @@ -0,0 +1 @@ +from ._multimethods import * diff --git a/unumpy/random/_multimethods.py b/unumpy/random/_multimethods.py new file mode 100644 index 0000000..0003fe7 --- /dev/null +++ b/unumpy/random/_multimethods.py @@ -0,0 +1,681 @@ +import functools +import operator +from uarray import create_multimethod, mark_as, all_of_type, Dispatchable +import builtins + +create_numpy = functools.partial(create_multimethod, domain="numpy.random") + +from .._multimethods import ( + ClassOverrideMetaWithConstructor, + ClassOverrideMetaWithGetAttr, + ClassOverrideMetaWithConstructorAndGetAttr, + ndarray, + _identity_argreplacer, + _dtype_argreplacer, + _self_argreplacer, + _skip_self_argreplacer, + _first2argreplacer, + _first3argreplacer, + mark_dtype, + mark_non_coercible, +) + + +@create_numpy(_identity_argreplacer) +def default_rng(seed=None): + return () + + +class RandomState(metaclass=ClassOverrideMetaWithConstructor): + pass + + +class BitGenerator(metaclass=ClassOverrideMetaWithGetAttr): + pass + + +class SeedSequence(metaclass=ClassOverrideMetaWithConstructorAndGetAttr): + pass + + +@create_numpy(_identity_argreplacer) +def rand(*tup): + return () + + +@create_numpy(_identity_argreplacer) +def randn(*tup): + return () + + +def _randint_argreplacer(args, kwargs, dispatchables): + def replacer(low, high=None, size=None, dtype=int): + return ( + (dispatchables[0],), + dict(high=dispatchables[1], size=size, dtype=dispatchables[2]), + ) + + return replacer(*args, **kwargs) + + +@create_numpy(_randint_argreplacer) +@all_of_type(ndarray) +def randint(low, high=None, size=None, dtype=int): + return (low, high, mark_dtype(dtype)) + + +@create_numpy(_identity_argreplacer) +def random_integers(low, high=None, size=None): + return () + + +@create_numpy(_identity_argreplacer) +def random_sample(size=None): + return () + + +@create_numpy(_identity_argreplacer) +def random(size=None): + return () + + +@create_numpy(_identity_argreplacer) +def ranf(size=None): + return () + + +@create_numpy(_identity_argreplacer) +def sample(size=None): + return () + + +def _choice_argreplacer(args, kwargs, dispatchables): + def replacer(a, size=None, replace=True, p=None): + return (dispatchables[0],), dict(size=size, replace=replace, p=dispatchables[1]) + + return replacer(*args, **kwargs) + + +@create_numpy(_choice_argreplacer) +@all_of_type(ndarray) +def choice(a, size=None, replace=True, p=None): + return (a, p) + + +@create_numpy(_identity_argreplacer) +def bytes(length): + return () + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def shuffle(x): + return (x,) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def permutation(x): + return (x,) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def beta(a, b, size=None): + return (a, b) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def binomial(n, p, size=None): + return (n, p) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def chisquare(df, size=None): + return (df,) + + +@create_numpy(_identity_argreplacer) +def dirichlet(alpha, size=None): + return () + + +def _exponential_argreplacer(args, kwargs, dispatchables): + def replacer(scale=1.0, size=None): + return (), dict(scale=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_exponential_argreplacer) +@all_of_type(ndarray) +def exponential(scale=1.0, size=None): + return (scale,) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def f(dfnum, dfden, size=None): + return (dfnum, dfden) + + +def _gamma_argreplacer(args, kwargs, dispatchables): + def replacer(shape, scale=1.0, size=None): + return (dispatchables[0],), dict(scale=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_gamma_argreplacer) +@all_of_type(ndarray) +def gamma(shape, scale=1.0, size=None): + return (shape, scale) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def geometric(p, size=None): + return (p,) + + +def _loc_scale_argreplacer(args, kwargs, dispatchables): + def replacer(loc=0.0, scale=1.0, size=None): + return (), dict(loc=dispatchables[0], scale=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_loc_scale_argreplacer) +@all_of_type(ndarray) +def gumbel(loc=0.0, scale=1.0, size=None): + return (loc, scale) + + +@create_numpy(_first3argreplacer) +@all_of_type(ndarray) +def hypergeometric(ngood, nbad, nsample, size=None): + return (ngood, nbad, nsample) + + +@create_numpy(_loc_scale_argreplacer) +@all_of_type(ndarray) +def laplace(loc=0.0, scale=1.0, size=None): + return (loc, scale) + + +@create_numpy(_loc_scale_argreplacer) +@all_of_type(ndarray) +def logistic(loc=0.0, scale=1.0, size=None): + return (loc, scale) + + +def _lognormal_argreplacer(args, kwargs, dispatchables): + def replacer(mean=0.0, sigma=1.0, size=None): + return (), dict(mean=dispatchables[0], sigma=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_lognormal_argreplacer) +@all_of_type(ndarray) +def lognormal(mean=0.0, sigma=1.0, size=None): + return (mean, sigma) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def logseries(p, size=None): + return (p,) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def multinomial(n, pvals, size=None): + return (n,) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def multivariate_normal(mean, cov, size=None, check_valid="warn", tol=1e-8): + return (mean, cov) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def negative_binomial(n, p, size=None): + return (n, p) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def noncentral_chisquare(df, nonc, size=None): + return (df, nonc) + + +@create_numpy(_first3argreplacer) +@all_of_type(ndarray) +def noncentral_f(dfnum, dfden, nonc, size=None): + return (dfnum, dfden, nonc) + + +@create_numpy(_loc_scale_argreplacer) +@all_of_type(ndarray) +def normal(loc=0.0, scale=1.0, size=None): + return (loc, scale) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def pareto(a, size=None): + return (a,) + + +def _poisson_argreplacer(args, kwargs, dispatchables): + def replacer(lam=1.0, size=None): + return (), dict(lam=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_poisson_argreplacer) +@all_of_type(ndarray) +def poisson(lam=1.0, size=None): + return (lam,) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def power(a, size=None): + return (a,) + + +def _rayleigh_argreplacer(args, kwargs, dispatchables): + def replacer(scale=1.0, size=None): + return (), dict(scale=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_rayleigh_argreplacer) +@all_of_type(ndarray) +def rayleigh(scale=1.0, size=None): + return (scale,) + + +@create_numpy(_identity_argreplacer) +def standard_cauchy(size=None): + return () + + +@create_numpy(_identity_argreplacer) +def standard_exponential(size=None): + return () + + +@create_numpy(_identity_argreplacer) +def standard_gamma(shape, size=None): + return () + + +@create_numpy(_identity_argreplacer) +def standard_normal(size=None): + return () + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def standard_t(df, size=None): + return (df,) + + +@create_numpy(_first3argreplacer) +@all_of_type(ndarray) +def triangular(left, mode, right, size=None): + return (left, mode, right) + + +def _uniform_argreplacer(args, kwargs, dispatchables): + def replacer(low=0.0, high=1.0, size=None): + return (), dict(low=dispatchables[0], high=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +@create_numpy(_uniform_argreplacer) +@all_of_type(ndarray) +def uniform(low=0.0, high=1.0, size=None): + return (low, high) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def vonmises(mu, kappa, size=None): + return (mu, kappa) + + +@create_numpy(_first2argreplacer) +@all_of_type(ndarray) +def wald(mean, scale, size=None): + return (mean, scale) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def weibull(a, size=None): + return (a,) + + +@create_numpy(_self_argreplacer) +@all_of_type(ndarray) +def zipf(a, size=None): + return (a,) + + +@create_numpy(_identity_argreplacer) +def seed(seed=None): + return () + + +@create_numpy(_identity_argreplacer) +def get_state(): + return () + + +@create_numpy(_identity_argreplacer) +def set_state(state): + return () + + +def _integers_argreplacer(args, kwargs, dispatchables): + def replacer(self, low, high=None, size=None, dtype=int, endpoint=False): + return ( + (self, dispatchables[0],), + dict( + high=dispatchables[1], + size=size, + dtype=dispatchables[2], + endpoint=endpoint, + ), + ) + + return replacer(*args, **kwargs) + + +def _Generator_choice_argreplacer(args, kwargs, dispatchables): + def replacer(self, a, size=None, replace=True, p=None, axis=0, shuffle=True): + return ( + (self, dispatchables[0],), + dict( + size=size, + replace=replace, + p=dispatchables[1], + axis=axis, + shuffle=shuffle, + ), + ) + + return replacer(*args, **kwargs) + + +def _Generator_exponential_argreplacer(args, kwargs, dispatchables): + def replacer(self, scale=1.0, size=None): + return (self,), dict(scale=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_gamma_argreplacer(args, kwargs, dispatchables): + def replacer(self, shape, scale=1.0, size=None): + return (self, dispatchables[0],), dict(scale=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_loc_scale_argreplacer(args, kwargs, dispatchables): + def replacer(self, loc=0.0, scale=1.0, size=None): + return (self,), dict(loc=dispatchables[0], scale=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_lognormal_argreplacer(args, kwargs, dispatchables): + def replacer(self, mean=0.0, sigma=1.0, size=None): + return (self,), dict(mean=dispatchables[0], sigma=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_multinomial_argreplacer(args, kwargs, dispatchables): + def replacer(self, n, pvals, size=None): + return (self, dispatchables[0], pvals), dict(size=size) + + return replacer(*args, **kwargs) + + +def _Generator_poisson_argreplacer(args, kwargs, dispatchables): + def replacer(self, lam=1.0, size=None): + return (self,), dict(lam=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_rayleigh_argreplacer(args, kwargs, dispatchables): + def replacer(self, scale=1.0, size=None): + return (self,), dict(scale=dispatchables[0], size=size) + + return replacer(*args, **kwargs) + + +def _Generator_uniform_argreplacer(args, kwargs, dispatchables): + def replacer(self, low=0.0, high=1.0, size=None): + return (self,), dict(low=dispatchables[0], high=dispatchables[1], size=size) + + return replacer(*args, **kwargs) + + +class Generator(metaclass=ClassOverrideMetaWithConstructorAndGetAttr): + @create_numpy(_integers_argreplacer) + @all_of_type(ndarray) + def integers(self, low, high=None, size=None, dtype=int, endpoint=False): + return (low, high, mark_dtype(dtype)) + + @create_numpy(_dtype_argreplacer) + def random(self, size=None, dtype=float, out=None): + return (mark_dtype(dtype), mark_non_coercible(out)) + + @create_numpy(_Generator_choice_argreplacer) + @all_of_type(ndarray) + def choice(self, a, size=None, replace=True, p=None, axis=0, shuffle=True): + return (a, p) + + @create_numpy(_identity_argreplacer) + def bytes(self, length): + return () + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def shuffle(self, x, axis=0): + return (x,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def permutation(self, x, axis=0): + return (x,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def beta(self, a, b, size=None): + return (a, b) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def binomial(self, n, p, size=None): + return (n, p) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def chisquare(self, df, size=None): + return (df,) + + @create_numpy(_identity_argreplacer) + def dirichlet(self, alpha, size=None): + return () + + @create_numpy(_Generator_exponential_argreplacer) + @all_of_type(ndarray) + def exponential(self, scale=1.0, size=None): + return (scale,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def f(self, dfnum, dfden, size=None): + return (dfnum, dfden) + + @create_numpy(_Generator_gamma_argreplacer) + @all_of_type(ndarray) + def gamma(self, shape, scale=1.0, size=None): + return (shape, scale) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def geometric(self, p, size=None): + return (p,) + + @create_numpy(_Generator_loc_scale_argreplacer) + @all_of_type(ndarray) + def gumbel(self, loc=0.0, scale=1.0, size=None): + return (loc, scale) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def hypergeometric(self, ngood, nbad, nsample, size=None): + return (ngood, nbad, nsample) + + @create_numpy(_Generator_loc_scale_argreplacer) + @all_of_type(ndarray) + def laplace(self, loc=0.0, scale=1.0, size=None): + return (loc, scale) + + @create_numpy(_Generator_loc_scale_argreplacer) + @all_of_type(ndarray) + def logistic(self, loc=0.0, scale=1.0, size=None): + return (loc, scale) + + @create_numpy(_Generator_lognormal_argreplacer) + @all_of_type(ndarray) + def lognormal(self, mean=0.0, sigma=1.0, size=None): + return (mean, sigma) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def logseries(self, p, size=None): + return (p,) + + @create_numpy(_Generator_multinomial_argreplacer) + @all_of_type(ndarray) + def multinomial(self, n, pvals, size=None): + return (n,) + + @create_numpy(_identity_argreplacer) + def multivariate_hypergeometric( + self, colors, nsample, size=None, method="marginals" + ): + return () + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def multivariate_normal(self, mean, cov, size=None, check_valid="warn", tol=1e-8): + return (mean, cov) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def negative_binomial(self, n, p, size=None): + return (n, p) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def noncentral_chisquare(self, df, nonc, size=None): + return (df, nonc) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def noncentral_f(self, dfnum, dfden, nonc, size=None): + return (dfnum, dfden, nonc) + + @create_numpy(_Generator_loc_scale_argreplacer) + @all_of_type(ndarray) + def normal(self, loc=0.0, scale=1.0, size=None): + return (loc, scale) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def pareto(self, a, size=None): + return (a,) + + @create_numpy(_Generator_poisson_argreplacer) + @all_of_type(ndarray) + def poisson(self, lam=1.0, size=None): + return (lam,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def power(self, a, size=None): + return (a,) + + @create_numpy(_Generator_rayleigh_argreplacer) + @all_of_type(ndarray) + def rayleigh(self, scale=1.0, size=None): + return (scale,) + + @create_numpy(_identity_argreplacer) + def standard_cauchy(self, size=None): + return () + + @create_numpy(_dtype_argreplacer) + def standard_exponential(self, size=None, dtype=float, method="zig", out=None): + return (mark_dtype(dtype), mark_non_coercible(out)) + + @create_numpy(_dtype_argreplacer) + def standard_gamma(self, shape, size=None, dtype=float, out=None): + return (mark_dtype(dtype), mark_non_coercible(out)) + + @create_numpy(_dtype_argreplacer) + def standard_normal(self, size=None, dtype=float, out=None): + return (mark_dtype(dtype), mark_non_coercible(out)) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def standard_t(self, df, size=None): + return (df,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def triangular(self, left, mode, right, size=None): + return (left, mode, right) + + @create_numpy(_Generator_uniform_argreplacer) + @all_of_type(ndarray) + def uniform(self, low=0.0, high=1.0, size=None): + return (low, high) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def vonmises(self, mu, kappa, size=None): + return (mu, kappa) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def wald(self, mean, scale, size=None): + return (mean, scale) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def weibull(self, a, size=None): + return (a,) + + @create_numpy(_skip_self_argreplacer) + @all_of_type(ndarray) + def zipf(self, a, size=None): + return (a,) diff --git a/unumpy/sparse_backend.py b/unumpy/sparse_backend.py index fd4c251..3677c79 100644 --- a/unumpy/sparse_backend.py +++ b/unumpy/sparse_backend.py @@ -2,6 +2,7 @@ import sparse from uarray import Dispatchable, wrap_single_convertor from unumpy import ufunc, ufunc_list, ndarray, dtype +from unumpy.random import RandomState, Generator import unumpy import functools @@ -23,7 +24,13 @@ def array(x, *args, **kwargs): return sparse.COO.from_numpy(np.asarray(x)) -_class_mapping = {ndarray: sparse.SparseArray, dtype: np.dtype, ufunc: np.ufunc} +_class_mapping = { + ndarray: sparse.SparseArray, + dtype: np.dtype, + ufunc: np.ufunc, + RandomState: np.random.RandomState, + Generator: np.random.Generator, +} def overridden_class(self): @@ -45,14 +52,15 @@ def overridden_class(self): def _get_from_name_domain(name, domain): module = sparse - domain_hierarchy = domain.split(".") + name_hierarchy = name.split(".") + domain_hierarchy = domain.split(".") + name_hierarchy[0:-1] for d in domain_hierarchy[1:]: if hasattr(module, d): module = getattr(module, d) else: return NotImplemented - if hasattr(module, name): - return getattr(module, name) + if hasattr(module, name_hierarchy[-1]): + return getattr(module, name_hierarchy[-1]) else: return NotImplemented @@ -64,7 +72,7 @@ def __ua_function__(method, args, kwargs): if len(args) != 0 and isinstance(args[0], unumpy.ClassOverrideMeta): return NotImplemented - sparse_method = _get_from_name_domain(method.__name__, method.domain) + sparse_method = _get_from_name_domain(method.__qualname__, method.domain) if sparse_method is NotImplemented: return NotImplemented diff --git a/unumpy/tests/test_numpy.py b/unumpy/tests/test_numpy.py index 2a8839c..ad5d002 100644 --- a/unumpy/tests/test_numpy.py +++ b/unumpy/tests/test_numpy.py @@ -16,9 +16,22 @@ dtypes = ["int8", "int16", "int32", "float32", "float64"] LIST_BACKENDS = [ - (NumpyBackend, (onp.ndarray, onp.generic, onp.ufunc)), - (DaskBackend(), (da.Array, onp.generic, da.ufunc.ufunc)), - (SparseBackend, (sparse.SparseArray, onp.ndarray, onp.generic)), + ( + NumpyBackend, + ( + onp.ndarray, + onp.generic, + onp.ufunc, + onp.random.RandomState, + onp.random.Generator, + onp.random.SeedSequence, + ), + ), + (DaskBackend(), (da.Array, onp.generic, da.ufunc.ufunc, da.random.RandomState)), + ( + SparseBackend, + (sparse.SparseArray, onp.ndarray, onp.generic, onp.random.mtrand.RandomState), + ), pytest.param( (TorchBackend, (torch.Tensor,)), marks=pytest.mark.xfail(reason="PyTorch not fully NumPy compatible."), @@ -597,6 +610,156 @@ def test_linalg(backend, method, args, kwargs): ret.compute() +@pytest.mark.parametrize( + "method, args, kwargs", + [ + (np.random.default_rng, (42,), {}), + (np.random.RandomState, (42,), {}), + # (np.random.Generator, (), {}), + # (np.random.BitGenerator, (), {}), + (np.random.SeedSequence, (42,), {}), + (np.random.rand, (1, 2), {}), + (np.random.randn, (1, 2), {}), + (np.random.randint, ([1, 2],), {}), + (np.random.random_integers, (2,), {}), + (np.random.random_sample, (), {"size": 2}), + (np.random.random, (), {"size": 2}), + (np.random.ranf, (), {"size": 2}), + (np.random.sample, (), {"size": 2}), + (np.random.choice, ([1, 2],), {}), + (np.random.bytes, (10,), {}), + (np.random.shuffle, ([1, 2, 3, 4],), {}), + (np.random.permutation, ([1, 2, 3, 4],), {}), + (np.random.beta, (1, 2), {"size": 2}), + (np.random.binomial, (10, 0.5), {"size": 2}), + (np.random.chisquare, (2,), {"size": 2}), + (np.random.dirichlet, ((10, 5, 3),), {}), + (np.random.exponential, (), {"size": 2}), + (np.random.f, (1.0, 48.0), {"size": 2}), + (np.random.gamma, (2.0, 2.0), {"size": 2}), + (np.random.geometric, (0.35,), {"size": 2}), + (np.random.gumbel, (0.0, 0.1), {"size": 2}), + (np.random.hypergeometric, (100, 2, 10), {"size": 2}), + (np.random.laplace, (0.0, 1.0), {"size": 2}), + (np.random.logistic, (10, 1), {"size": 2}), + (np.random.lognormal, (3.0, 1.0), {"size": 2}), + (np.random.logseries, (0.6,), {"size": 2}), + (np.random.multinomial, (20, [1 / 6.0] * 6), {}), + (np.random.multivariate_normal, ([0, 0], [[1, 0], [0, 100]]), {}), + (np.random.negative_binomial, (1, 0.1), {"size": 2}), + (np.random.noncentral_chisquare, (3, 20), {"size": 2}), + (np.random.noncentral_f, (3, 20, 3.0), {"size": 2}), + (np.random.normal, (0, 0.1), {"size": 2}), + (np.random.pareto, (3.0,), {"size": 2}), + (np.random.poisson, (5,), {"size": 2}), + (np.random.power, (5.0,), {"size": 2}), + (np.random.rayleigh, (3,), {"size": 2}), + (np.random.standard_cauchy, (), {"size": 2}), + (np.random.standard_exponential, (), {"size": 2}), + (np.random.standard_gamma, (2.0,), {"size": 2}), + (np.random.standard_normal, (), {"size": 2}), + (np.random.standard_t, (10,), {"size": 2}), + (np.random.triangular, (-3, 0, 8), {"size": 2}), + (np.random.uniform, (-1, 0), {"size": 2}), + (np.random.vonmises, (0.0, 4.0), {"size": 2}), + (np.random.wald, (3, 2), {"size": 2}), + (np.random.weibull, (5.0,), {"size": 2}), + (np.random.zipf, (2.0,), {"size": 2}), + (np.random.seed, (), {}), + (np.random.get_state, (), {}), + # (np.random.set_state, (), {}), + ], +) +def test_random(backend, method, args, kwargs): + backend, types = backend + try: + with ua.set_backend(backend, coerce=True): + ret = method(*args, **kwargs) + except ua.BackendNotImplementedError: + if backend in FULLY_TESTED_BACKENDS and (backend, method) not in EXCEPTIONS: + raise + pytest.xfail(reason="The backend has no implementation for this ufunc.") + + if method is np.random.bytes: + assert isinstance(ret, bytes) + elif method in {np.random.shuffle, np.random.seed}: + assert ret is None + elif method is np.random.get_state: + assert isinstance(ret, tuple) + else: + assert isinstance(ret, types) + + if isinstance(ret, da.Array): + ret.compute() + + +@pytest.mark.parametrize( + "method, args, kwargs", + [ + (np.random.Generator.random, (), {"size": 2}), + (np.random.Generator.choice, ([1, 2],), {}), + (np.random.Generator.bytes, (10,), {}), + (np.random.Generator.shuffle, ([1, 2, 3, 4],), {}), + (np.random.Generator.permutation, ([1, 2, 3, 4],), {}), + (np.random.Generator.beta, (1, 2), {"size": 2}), + (np.random.Generator.binomial, (10, 0.5), {"size": 2}), + (np.random.Generator.chisquare, (2,), {"size": 2}), + (np.random.Generator.dirichlet, ((10, 5, 3),), {}), + (np.random.Generator.exponential, (), {"size": 2}), + (np.random.Generator.f, (1.0, 48.0), {"size": 2}), + (np.random.Generator.gamma, (2.0, 2.0), {"size": 2}), + (np.random.Generator.geometric, (0.35,), {"size": 2}), + (np.random.Generator.gumbel, (0.0, 0.1), {"size": 2}), + (np.random.Generator.hypergeometric, (100, 2, 10), {"size": 2}), + (np.random.Generator.laplace, (0.0, 1.0), {"size": 2}), + (np.random.Generator.logistic, (10, 1), {"size": 2}), + (np.random.Generator.lognormal, (3.0, 1.0), {"size": 2}), + (np.random.Generator.logseries, (0.6,), {"size": 2}), + (np.random.Generator.multinomial, (20, [1 / 6.0] * 6), {}), + (np.random.Generator.multivariate_normal, ([0, 0], [[1, 0], [0, 100]]), {}), + (np.random.Generator.negative_binomial, (1, 0.1), {"size": 2}), + (np.random.Generator.noncentral_chisquare, (3, 20), {"size": 2}), + (np.random.Generator.noncentral_f, (3, 20, 3.0), {"size": 2}), + (np.random.Generator.normal, (0, 0.1), {"size": 2}), + (np.random.Generator.pareto, (3.0,), {"size": 2}), + (np.random.Generator.poisson, (5,), {"size": 2}), + (np.random.Generator.power, (5.0,), {"size": 2}), + (np.random.Generator.rayleigh, (3,), {"size": 2}), + (np.random.Generator.standard_cauchy, (), {"size": 2}), + (np.random.Generator.standard_exponential, (), {"size": 2}), + (np.random.Generator.standard_gamma, (2.0,), {"size": 2}), + (np.random.Generator.standard_normal, (), {"size": 2}), + (np.random.Generator.standard_t, (10,), {"size": 2}), + (np.random.Generator.triangular, (-3, 0, 8), {"size": 2}), + (np.random.Generator.uniform, (-1, 0), {"size": 2}), + (np.random.Generator.vonmises, (0.0, 4.0), {"size": 2}), + (np.random.Generator.wald, (3, 2), {"size": 2}), + (np.random.Generator.weibull, (5.0,), {"size": 2}), + (np.random.Generator.zipf, (2.0,), {"size": 2}), + ], +) +def test_Generator(backend, method, args, kwargs): + backend, types = backend + try: + with ua.set_backend(backend, coerce=True): + rng = np.random.default_rng() + ret = method(rng, *args, **kwargs) + except ua.BackendNotImplementedError: + if backend in FULLY_TESTED_BACKENDS and (backend, method) not in EXCEPTIONS: + raise + pytest.xfail(reason="The backend has no implementation for this ufunc.") + + if method is np.random.Generator.bytes: + assert isinstance(ret, bytes) + elif method is np.random.Generator.shuffle: + assert ret is None + else: + assert isinstance(ret, types) + + if isinstance(ret, da.Array): + ret.compute() + + @pytest.mark.parametrize( "method, args, kwargs", [ @@ -664,21 +827,31 @@ def test_class_overriding(): assert isinstance(onp.dtype("float64"), np.dtype) assert np.dtype("float64") == onp.float64 assert isinstance(np.dtype("float64"), onp.dtype) + assert isinstance(onp.random.RandomState(), np.random.RandomState) + assert isinstance(onp.random.Generator(onp.random.PCG64()), np.random.Generator) assert issubclass(onp.ufunc, np.ufunc) + assert issubclass(onp.random.RandomState, np.random.RandomState) + assert issubclass(onp.random.Generator, np.random.Generator) with ua.set_backend(DaskBackend(), coerce=True): assert isinstance(da.add, np.ufunc) assert isinstance(onp.dtype("float64"), np.dtype) assert np.dtype("float64") == onp.float64 assert isinstance(np.dtype("float64"), onp.dtype) + assert isinstance(da.random.RandomState(), np.random.RandomState) assert issubclass(da.ufunc.ufunc, np.ufunc) + assert issubclass(da.random.RandomState, np.random.RandomState) with ua.set_backend(SparseBackend, coerce=True): assert isinstance(onp.add, np.ufunc) assert isinstance(onp.dtype("float64"), np.dtype) assert np.dtype("float64") == onp.float64 assert isinstance(np.dtype("float64"), onp.dtype) + assert isinstance(onp.random.RandomState(), np.random.RandomState) + assert isinstance(onp.random.Generator(onp.random.PCG64()), np.random.Generator) assert issubclass(onp.ufunc, np.ufunc) + assert issubclass(onp.random.RandomState, np.random.RandomState) + assert issubclass(onp.random.Generator, np.random.Generator) if hasattr(CupyBackend, "__ua_function__"): with ua.set_backend(CupyBackend, coerce=True): @@ -686,4 +859,6 @@ def test_class_overriding(): assert isinstance(cp.dtype("float64"), np.dtype) assert np.dtype("float64") == cp.float64 assert isinstance(np.dtype("float64"), cp.dtype) + assert isinstance(cp.random.RandomState(), np.random.RandomState) assert issubclass(cp.ufunc, np.ufunc) + assert issubclass(cp.random.RandomState, np.random.RandomState)